[Intel-gfx] [PATCH] drm/i915: Always use kref tracking for all contexts.
Ben Widawsky
ben at bwidawsk.net
Fri Apr 4 06:44:43 CEST 2014
On Thu, Apr 03, 2014 at 08:06:30AM +0100, Chris Wilson wrote:
> If we always initialize kref for the context, even if we are using fake
> contexts for hangstats when there is no hw support, we can forgo the
> dance to dereference the ctx->obj and inspect whether we are permitted
> to use kref inside i915_gem_context_reference() and _unreference().
>
> My ulterior motive here is to improve the debugging of a use-after-free
> of ctx->obj. This patch avoids the dereference here and instead forces
> the assertion checks associated with kref.
>
> v2: Refactor the fake contexts to being even more like the real
> contexts, so that there is much less duplicated and special case code.
>
> Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=76671
> Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>
> Tested-by: lu hua <huax.lu at intel.com>
> Cc: Ben Widawsky <benjamin.widawsky at intel.com>
> ---
> drivers/gpu/drm/i915/i915_drv.h | 8 +-
> drivers/gpu/drm/i915/i915_gem.c | 2 +-
> drivers/gpu/drm/i915/i915_gem_context.c | 175 ++++++++++++-----------------
> drivers/gpu/drm/i915/i915_gem_execbuffer.c | 2 +-
> 4 files changed, 76 insertions(+), 111 deletions(-)
>
> diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
> index c0e2de9d54b8..97bf793f6552 100644
> --- a/drivers/gpu/drm/i915/i915_drv.h
> +++ b/drivers/gpu/drm/i915/i915_drv.h
> @@ -2275,20 +2275,18 @@ int i915_gem_context_open(struct drm_device *dev, struct drm_file *file);
> int i915_gem_context_enable(struct drm_i915_private *dev_priv);
> void i915_gem_context_close(struct drm_device *dev, struct drm_file *file);
> int i915_switch_context(struct intel_ring_buffer *ring,
> - struct drm_file *file, struct i915_hw_context *to);
> + struct i915_hw_context *to);
> struct i915_hw_context *
> i915_gem_context_get(struct drm_i915_file_private *file_priv, u32 id);
> void i915_gem_context_free(struct kref *ctx_ref);
> static inline void i915_gem_context_reference(struct i915_hw_context *ctx)
> {
> - if (ctx->obj && HAS_HW_CONTEXTS(ctx->obj->base.dev))
> - kref_get(&ctx->ref);
> + kref_get(&ctx->ref);
> }
>
> static inline void i915_gem_context_unreference(struct i915_hw_context *ctx)
> {
> - if (ctx->obj && HAS_HW_CONTEXTS(ctx->obj->base.dev))
> - kref_put(&ctx->ref, i915_gem_context_free);
> + kref_put(&ctx->ref, i915_gem_context_free);
> }
>
> static inline bool i915_gem_context_is_default(const struct i915_hw_context *c)
> diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
> index 221f6ab3a498..0ff65666c5f7 100644
> --- a/drivers/gpu/drm/i915/i915_gem.c
> +++ b/drivers/gpu/drm/i915/i915_gem.c
> @@ -2819,7 +2819,7 @@ int i915_gpu_idle(struct drm_device *dev)
>
> /* Flush everything onto the inactive list. */
> for_each_ring(ring, dev_priv, i) {
> - ret = i915_switch_context(ring, NULL, ring->default_context);
> + ret = i915_switch_context(ring, ring->default_context);
> if (ret)
> return ret;
>
> diff --git a/drivers/gpu/drm/i915/i915_gem_context.c b/drivers/gpu/drm/i915/i915_gem_context.c
> index edbad2787d65..1439cc6bd439 100644
> --- a/drivers/gpu/drm/i915/i915_gem_context.c
> +++ b/drivers/gpu/drm/i915/i915_gem_context.c
> @@ -96,9 +96,6 @@
> #define GEN6_CONTEXT_ALIGN (64<<10)
> #define GEN7_CONTEXT_ALIGN 4096
>
> -static int do_switch(struct intel_ring_buffer *ring,
> - struct i915_hw_context *to);
> -
> static void do_ppgtt_cleanup(struct i915_hw_ppgtt *ppgtt)
> {
> struct drm_device *dev = ppgtt->base.dev;
> @@ -185,13 +182,15 @@ void i915_gem_context_free(struct kref *ctx_ref)
> typeof(*ctx), ref);
> struct i915_hw_ppgtt *ppgtt = NULL;
>
> - /* We refcount even the aliasing PPGTT to keep the code symmetric */
> - if (USES_PPGTT(ctx->obj->base.dev))
> - ppgtt = ctx_to_ppgtt(ctx);
> + if (ctx->obj) {
> + /* We refcount even the aliasing PPGTT to keep the code symmetric */
> + if (USES_PPGTT(ctx->obj->base.dev))
> + ppgtt = ctx_to_ppgtt(ctx);
>
> - /* XXX: Free up the object before tearing down the address space, in
> - * case we're bound in the PPGTT */
> - drm_gem_object_unreference(&ctx->obj->base);
> + /* XXX: Free up the object before tearing down the address space, in
> + * case we're bound in the PPGTT */
> + drm_gem_object_unreference(&ctx->obj->base);
> + }
>
> if (ppgtt)
> kref_put(&ppgtt->ref, ppgtt_release);
> @@ -232,32 +231,32 @@ __create_hw_context(struct drm_device *dev,
> return ERR_PTR(-ENOMEM);
>
> kref_init(&ctx->ref);
> - ctx->obj = i915_gem_alloc_object(dev, dev_priv->hw_context_size);
> - INIT_LIST_HEAD(&ctx->link);
> - if (ctx->obj == NULL) {
> - kfree(ctx);
> - DRM_DEBUG_DRIVER("Context object allocated failed\n");
> - return ERR_PTR(-ENOMEM);
> - }
> + list_add_tail(&ctx->link, &dev_priv->context_list);
>
> - if (INTEL_INFO(dev)->gen >= 7) {
> - ret = i915_gem_object_set_cache_level(ctx->obj,
> - I915_CACHE_L3_LLC);
> - /* Failure shouldn't ever happen this early */
> - if (WARN_ON(ret))
> + if (dev_priv->hw_context_size) {
> + ctx->obj = i915_gem_alloc_object(dev, dev_priv->hw_context_size);
> + if (ctx->obj == NULL) {
> + ret = -ENOMEM;
> goto err_out;
> - }
> + }
>
> - list_add_tail(&ctx->link, &dev_priv->context_list);
> + if (INTEL_INFO(dev)->gen >= 7) {
> + ret = i915_gem_object_set_cache_level(ctx->obj,
> + I915_CACHE_L3_LLC);
> + /* Failure shouldn't ever happen this early */
> + if (WARN_ON(ret))
> + goto err_out;
> + }
> + }
>
> /* Default context will never have a file_priv */
> - if (file_priv == NULL)
> - return ctx;
> -
> - ret = idr_alloc(&file_priv->context_idr, ctx, DEFAULT_CONTEXT_ID, 0,
> - GFP_KERNEL);
> - if (ret < 0)
> - goto err_out;
> + if (file_priv != NULL) {
> + ret = idr_alloc(&file_priv->context_idr, ctx,
> + DEFAULT_CONTEXT_ID, 0, GFP_KERNEL);
> + if (ret < 0)
> + goto err_out;
> + } else
> + ret = DEFAULT_CONTEXT_ID;
>
> ctx->file_priv = file_priv;
> ctx->id = ret;
> @@ -294,7 +293,7 @@ i915_gem_create_context(struct drm_device *dev,
> if (IS_ERR(ctx))
> return ctx;
>
> - if (is_global_default_ctx) {
> + if (is_global_default_ctx && ctx->obj) {
> /* We may need to do things with the shrinker which
> * require us to immediately switch back to the default
> * context. This can cause a problem as pinning the
> @@ -342,7 +341,7 @@ i915_gem_create_context(struct drm_device *dev,
> return ctx;
>
> err_unpin:
> - if (is_global_default_ctx)
> + if (is_global_default_ctx && ctx->obj)
> i915_gem_object_ggtt_unpin(ctx->obj);
> err_destroy:
> i915_gem_context_unreference(ctx);
> @@ -352,16 +351,14 @@ err_destroy:
> void i915_gem_context_reset(struct drm_device *dev)
> {
> struct drm_i915_private *dev_priv = dev->dev_private;
> - struct intel_ring_buffer *ring;
> int i;
>
> - if (!HAS_HW_CONTEXTS(dev))
> - return;
> -
> /* Prevent the hardware from restoring the last context (which hung) on
> * the next switch */
> for (i = 0; i < I915_NUM_RINGS; i++) {
> struct i915_hw_context *dctx;
> + struct intel_ring_buffer *ring;
> +
> if (!(INTEL_INFO(dev)->ring_mask & (1<<i)))
> continue;
>
> @@ -377,7 +374,7 @@ void i915_gem_context_reset(struct drm_device *dev)
> if (ring->last_context == dctx)
> continue;
>
> - if (i == RCS) {
> + if (dctx->obj && i == RCS) {
> WARN_ON(i915_gem_obj_ggtt_pin(dctx->obj,
> get_context_alignment(dev), 0));
> /* Fake a finish/inactive */
I am beginning to feel ambivalent now as to whether or not the
simplicity gained by making fake contexts out weighs. For example,
before, last_context was always equal to dctx without HW contexts, which
made places like the above nice and simple.
> @@ -394,44 +391,35 @@ void i915_gem_context_reset(struct drm_device *dev)
> int i915_gem_context_init(struct drm_device *dev)
> {
> struct drm_i915_private *dev_priv = dev->dev_private;
> - struct intel_ring_buffer *ring;
> + struct i915_hw_context *ctx;
> int i;
>
> - if (!HAS_HW_CONTEXTS(dev))
> - return 0;
> -
> /* Init should only be called once per module load. Eventually the
> * restriction on the context_disabled check can be loosened. */
> if (WARN_ON(dev_priv->ring[RCS].default_context))
> return 0;
>
> - dev_priv->hw_context_size = round_up(get_context_size(dev), 4096);
> -
> - if (dev_priv->hw_context_size > (1<<20)) {
> - DRM_DEBUG_DRIVER("Disabling HW Contexts; invalid size\n");
> - return -E2BIG;
> + if (HAS_HW_CONTEXTS(dev)) {
> + dev_priv->hw_context_size = round_up(get_context_size(dev), 4096);
> + if (dev_priv->hw_context_size > (1<<20)) {
> + DRM_DEBUG_DRIVER("Disabling HW Contexts; invalid size %d\n",
> + dev_priv->hw_context_size);
> + dev_priv->hw_context_size = 0;
> + }
> }
>
> - dev_priv->ring[RCS].default_context =
> - i915_gem_create_context(dev, NULL, USES_PPGTT(dev));
> -
> - if (IS_ERR_OR_NULL(dev_priv->ring[RCS].default_context)) {
> - DRM_DEBUG_DRIVER("Disabling HW Contexts; create failed %ld\n",
> - PTR_ERR(dev_priv->ring[RCS].default_context));
> - return PTR_ERR(dev_priv->ring[RCS].default_context);
> + ctx = i915_gem_create_context(dev, NULL, USES_PPGTT(dev));
> + if (IS_ERR(ctx)) {
> + DRM_ERROR("Failed to create default global context (error %ld)\n",
> + PTR_ERR(ctx));
> + return PTR_ERR(ctx);
> }
>
> - for (i = RCS + 1; i < I915_NUM_RINGS; i++) {
> - if (!(INTEL_INFO(dev)->ring_mask & (1<<i)))
> - continue;
> + /* NB: RCS will hold a ref for all rings */
> + for (i = 0; i < I915_NUM_RINGS; i++)
> + dev_priv->ring[i].default_context = ctx;
>
> - ring = &dev_priv->ring[i];
> -
> - /* NB: RCS will hold a ref for all rings */
> - ring->default_context = dev_priv->ring[RCS].default_context;
> - }
> -
> - DRM_DEBUG_DRIVER("HW context support initialized\n");
> + DRM_DEBUG_DRIVER("%s context support initialized\n", dev_priv->hw_context_size ? "HW" : "fake");
> return 0;
Some nice cleanups here.
> }
>
> @@ -441,13 +429,11 @@ void i915_gem_context_fini(struct drm_device *dev)
> struct i915_hw_context *dctx = dev_priv->ring[RCS].default_context;
> int i;
>
> - if (!HAS_HW_CONTEXTS(dev))
> - return;
> -
> - /* The only known way to stop the gpu from accessing the hw context is
> - * to reset it. Do this as the very last operation to avoid confusing
> - * other code, leading to spurious errors. */
> - intel_gpu_reset(dev);
> + if (dev_priv->hw_context_size)
> + /* The only known way to stop the gpu from accessing the hw context is
> + * to reset it. Do this as the very last operation to avoid confusing
> + * other code, leading to spurious errors. */
> + intel_gpu_reset(dev);
Perhaps it's time to
static inline bool i915_gem_hw_contexts_enabled(dev_priv)
{
return dev_priv->hw_context_size == 0;
}
or #define USES_HW_CONTEXTS?
>
> /* When default context is created and switched to, base object refcount
> * will be 2 (+1 from object creation and +1 from do_switch()).
> @@ -456,7 +442,7 @@ void i915_gem_context_fini(struct drm_device *dev)
> * to offset the do_switch part, so that i915_gem_context_unreference()
> * can then free the base object correctly. */
> WARN_ON(!dev_priv->ring[RCS].last_context);
> - if (dev_priv->ring[RCS].last_context == dctx) {
> + if (dctx->obj && dev_priv->ring[RCS].last_context == dctx) {
> /* Fake switch to NULL context */
> WARN_ON(dctx->obj->active);
> i915_gem_object_ggtt_unpin(dctx->obj);
> @@ -466,6 +452,7 @@ void i915_gem_context_fini(struct drm_device *dev)
>
> for (i = 0; i < I915_NUM_RINGS; i++) {
> struct intel_ring_buffer *ring = &dev_priv->ring[i];
> +
> if (!(INTEL_INFO(dev)->ring_mask & (1<<i)))
> continue;
>
> @@ -478,7 +465,6 @@ void i915_gem_context_fini(struct drm_device *dev)
>
> i915_gem_object_ggtt_unpin(dctx->obj);
> i915_gem_context_unreference(dctx);
> - dev_priv->mm.aliasing_ppgtt = NULL;
> }
>
> int i915_gem_context_enable(struct drm_i915_private *dev_priv)
> @@ -486,9 +472,6 @@ int i915_gem_context_enable(struct drm_i915_private *dev_priv)
> struct intel_ring_buffer *ring;
> int ret, i;
>
> - if (!HAS_HW_CONTEXTS(dev_priv->dev))
> - return 0;
> -
> /* This is the only place the aliasing PPGTT gets enabled, which means
> * it has to happen before we bail on reset */
> if (dev_priv->mm.aliasing_ppgtt) {
> @@ -503,7 +486,7 @@ int i915_gem_context_enable(struct drm_i915_private *dev_priv)
> BUG_ON(!dev_priv->ring[RCS].default_context);
>
> for_each_ring(ring, dev_priv, i) {
> - ret = do_switch(ring, ring->default_context);
> + ret = i915_switch_context(ring, ring->default_context);
> if (ret)
> return ret;
> }
> @@ -526,19 +509,6 @@ static int context_idr_cleanup(int id, void *p, void *data)
> int i915_gem_context_open(struct drm_device *dev, struct drm_file *file)
> {
> struct drm_i915_file_private *file_priv = file->driver_priv;
> - struct drm_i915_private *dev_priv = dev->dev_private;
> -
> - if (!HAS_HW_CONTEXTS(dev)) {
> - /* Cheat for hang stats */
> - file_priv->private_default_ctx =
> - kzalloc(sizeof(struct i915_hw_context), GFP_KERNEL);
> -
> - if (file_priv->private_default_ctx == NULL)
> - return -ENOMEM;
> -
> - file_priv->private_default_ctx->vm = &dev_priv->gtt.base;
> - return 0;
> - }
>
> idr_init(&file_priv->context_idr);
>
> @@ -559,14 +529,13 @@ void i915_gem_context_close(struct drm_device *dev, struct drm_file *file)
> {
> struct drm_i915_file_private *file_priv = file->driver_priv;
>
> - if (!HAS_HW_CONTEXTS(dev)) {
> - kfree(file_priv->private_default_ctx);
> + if (IS_ERR_OR_NULL(file_priv->private_default_ctx))
> return;
I am not convinced. This should be
BUG_ON(!file_priv->private_default_ctx). They don't get back an fd is
context_open failed.
> - }
>
> idr_for_each(&file_priv->context_idr, context_idr_cleanup, NULL);
> - i915_gem_context_unreference(file_priv->private_default_ctx);
> idr_destroy(&file_priv->context_idr);
> +
> + i915_gem_context_unreference(file_priv->private_default_ctx);
> }
>
> struct i915_hw_context *
> @@ -574,9 +543,6 @@ i915_gem_context_get(struct drm_i915_file_private *file_priv, u32 id)
> {
> struct i915_hw_context *ctx;
>
> - if (!HAS_HW_CONTEXTS(file_priv->dev_priv->dev))
> - return file_priv->private_default_ctx;
> -
> ctx = (struct i915_hw_context *)idr_find(&file_priv->context_idr, id);
> if (!ctx)
> return ERR_PTR(-ENOENT);
> @@ -758,7 +724,6 @@ unpin_out:
> /**
> * i915_switch_context() - perform a GPU context switch.
> * @ring: ring for which we'll execute the context switch
> - * @file_priv: file_priv associated with the context, may be NULL
> * @to: the context to switch to
> *
> * The context life cycle is simple. The context refcount is incremented and
> @@ -767,18 +732,20 @@ unpin_out:
> * object while letting the normal object tracking destroy the backing BO.
> */
> int i915_switch_context(struct intel_ring_buffer *ring,
> - struct drm_file *file,
> struct i915_hw_context *to)
> {
> struct drm_i915_private *dev_priv = ring->dev->dev_private;
>
> WARN_ON(!mutex_is_locked(&dev_priv->dev->struct_mutex));
>
> - BUG_ON(file && to == NULL);
> -
> - /* We have the fake context */
> - if (!HAS_HW_CONTEXTS(ring->dev)) {
> - ring->last_context = to;
> + if (to->obj == NULL) { /* We have the fake context */
> + if (to != ring->last_context) {
> + if (to)
> + i915_gem_context_reference(to);
> + if (ring->last_context)
> + i915_gem_context_unreference(ring->last_context);
> + ring->last_context = to;
> + }
> return 0;
Here's some more ambivalence. Adding complexity to i915_switch_context
IMHO outweighs the gains elsewhere.
> }
>
> @@ -793,7 +760,7 @@ int i915_gem_context_create_ioctl(struct drm_device *dev, void *data,
> struct i915_hw_context *ctx;
> int ret;
>
> - if (!HAS_HW_CONTEXTS(dev))
> + if (to_i915(dev)->hw_context_size == 0)
> return -ENODEV;
>
> ret = i915_mutex_lock_interruptible(dev);
> diff --git a/drivers/gpu/drm/i915/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/i915_gem_execbuffer.c
> index 7447160155a3..2c9d9cbaf653 100644
> --- a/drivers/gpu/drm/i915/i915_gem_execbuffer.c
> +++ b/drivers/gpu/drm/i915/i915_gem_execbuffer.c
> @@ -1221,7 +1221,7 @@ i915_gem_do_execbuffer(struct drm_device *dev, void *data,
> if (ret)
> goto err;
>
> - ret = i915_switch_context(ring, file, ctx);
> + ret = i915_switch_context(ring, ctx);
> if (ret)
> goto err;
>
> --
> 1.9.1
>
Other than Mika's comment about the unnecessary "if (to)" and mine about
what I think is a false condition for context_close() it looks okay to
me. I am a bit disappointed with the two spots I pointed out ambivalence,
but I do think it's a net-win.
So with the fix to my comment, and/or an explanation as to why I am an
idiot:
Reviewed-by: Ben Widawsky <ben at bwidawsk.net>
--
Ben Widawsky, Intel Open Source Technology Center
More information about the Intel-gfx
mailing list