[Intel-gfx] [PATCH] drm/i915: Always use kref tracking for all contexts.
Ben Widawsky
benjamin.widawsky at intel.com
Fri Apr 11 08:37:47 CEST 2014
On Wed, Apr 09, 2014 at 09:07:36AM +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.
>
> v3: Tweaks.
> v4: Tweaks, minor.
>
> 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>
> Cc: Mika Kuoppala <mika.kuoppala at intel.com>
I couldn't spot any problems, but I got a bit lazier with each review
since v2.
Reviewed-by: Ben Widawsky <ben at bwidawsk.net>
> ---
> drivers/gpu/drm/i915/i915_drv.h | 8 +-
> drivers/gpu/drm/i915/i915_gem.c | 2 +-
> drivers/gpu/drm/i915/i915_gem_context.c | 234 ++++++++++++-----------------
> drivers/gpu/drm/i915/i915_gem_execbuffer.c | 2 +-
> 4 files changed, 101 insertions(+), 145 deletions(-)
>
> diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
> index 533bd8f6a5b1..0557bd92b26b 100644
> --- a/drivers/gpu/drm/i915/i915_drv.h
> +++ b/drivers/gpu/drm/i915/i915_drv.h
> @@ -2276,20 +2276,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 eb5cf077c626..4368437458fd 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 30b355afb362..f77b4c126465 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,40 +231,40 @@ __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);
>
> - /*
> - * Try to make the context utilize L3 as well as LLC.
> - *
> - * On VLV we don't have L3 controls in the PTEs so we
> - * shouldn't touch the cache level, especially as that
> - * would make the object snooped which might have a
> - * negative performance impact.
> - */
> - if (INTEL_INFO(dev)->gen >= 7 && !IS_VALLEYVIEW(dev)) {
> - 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);
> + /*
> + * Try to make the context utilize L3 as well as LLC.
> + *
> + * On VLV we don't have L3 controls in the PTEs so we
> + * shouldn't touch the cache level, especially as that
> + * would make the object snooped which might have a
> + * negative performance impact.
> + */
> + if (INTEL_INFO(dev)->gen >= 7 && !IS_VALLEYVIEW(dev)) {
> + 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;
> @@ -302,7 +301,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
> @@ -350,7 +349,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);
> @@ -360,32 +359,22 @@ 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;
> - if (!(INTEL_INFO(dev)->ring_mask & (1<<i)))
> - continue;
> + struct intel_ring_buffer *ring = &dev_priv->ring[i];
> + struct i915_hw_context *dctx = ring->default_context;
>
> /* Do a fake switch to the default context */
> - ring = &dev_priv->ring[i];
> - dctx = ring->default_context;
> - if (WARN_ON(!dctx))
> + if (ring->last_context == dctx)
> continue;
>
> if (!ring->last_context)
> continue;
>
> - 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 */
> @@ -402,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;
> -
> - ring = &dev_priv->ring[i];
> -
> - /* NB: RCS will hold a ref for all rings */
> - ring->default_context = dev_priv->ring[RCS].default_context;
> - }
> + /* NB: RCS will hold a ref for all rings */
> + for (i = 0; i < I915_NUM_RINGS; i++)
> + dev_priv->ring[i].default_context = ctx;
>
> - 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;
> }
>
> @@ -449,33 +429,30 @@ 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);
> -
> - /* When default context is created and switched to, base object refcount
> - * will be 2 (+1 from object creation and +1 from do_switch()).
> - * i915_gem_context_fini() will be called after gpu_idle() has switched
> - * to default context. So we need to unreference the base object once
> - * 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) {
> - /* Fake switch to NULL context */
> - WARN_ON(dctx->obj->active);
> - i915_gem_object_ggtt_unpin(dctx->obj);
> - i915_gem_context_unreference(dctx);
> - dev_priv->ring[RCS].last_context = NULL;
> + if (dctx->obj) {
> + /* 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);
> +
> + /* When default context is created and switched to, base object refcount
> + * will be 2 (+1 from object creation and +1 from do_switch()).
> + * i915_gem_context_fini() will be called after gpu_idle() has switched
> + * to default context. So we need to unreference the base object once
> + * 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) {
> + /* Fake switch to NULL context */
> + WARN_ON(dctx->obj->active);
> + i915_gem_object_ggtt_unpin(dctx->obj);
> + i915_gem_context_unreference(dctx);
> + dev_priv->ring[RCS].last_context = NULL;
> + }
> }
>
> 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;
>
> if (ring->last_context)
> i915_gem_context_unreference(ring->last_context);
> @@ -486,7 +463,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)
> @@ -494,9 +470,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) {
> @@ -511,7 +484,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;
> }
> @@ -534,19 +507,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);
>
> @@ -567,14 +527,10 @@ 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);
> - return;
> - }
> -
> 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 *
> @@ -582,9 +538,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);
> @@ -766,7 +719,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
> @@ -775,24 +727,30 @@ 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) {
> + i915_gem_context_reference(to);
> + if (ring->last_context)
> + i915_gem_context_unreference(ring->last_context);
> + ring->last_context = to;
> + }
> return 0;
> }
>
> return do_switch(ring, to);
> }
>
> +static bool hw_context_enabled(struct drm_device *dev)
> +{
> + return to_i915(dev)->hw_context_size;
> +}
> +
> int i915_gem_context_create_ioctl(struct drm_device *dev, void *data,
> struct drm_file *file)
> {
> @@ -801,7 +759,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 (!hw_context_enabled(dev))
> 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 34914025e750..0ec8621eb4f8 100644
> --- a/drivers/gpu/drm/i915/i915_gem_execbuffer.c
> +++ b/drivers/gpu/drm/i915/i915_gem_execbuffer.c
> @@ -1222,7 +1222,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
>
--
Ben Widawsky, Intel Open Source Technology Center
More information about the Intel-gfx
mailing list