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 <ch...@chris-wilson.co.uk>
> Tested-by: lu hua <huax...@intel.com>
> Cc: Ben Widawsky <benjamin.widaw...@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 <b...@bwidawsk.net>

-- 
Ben Widawsky, Intel Open Source Technology Center
_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to