Commit 691e6415 authored by Chris Wilson's avatar Chris Wilson Committed by Jani Nikula

drm/i915: Always use kref tracking for all contexts.

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=76671Signed-off-by: default avatarChris Wilson <chris@chris-wilson.co.uk>
Tested-by: default avatarlu hua <huax.lu@intel.com>
Cc: Ben Widawsky <benjamin.widawsky@intel.com>
Cc: Mika Kuoppala <mika.kuoppala@intel.com>
Reviewed-by: default avatarBen Widawsky <ben@bwidawsk.net>
[Jani: tiny change to backport to drm-intel-fixes.]
Signed-off-by: default avatarJani Nikula <jani.nikula@intel.com>
parent c675949e
...@@ -2432,20 +2432,18 @@ int i915_gem_context_open(struct drm_device *dev, struct drm_file *file); ...@@ -2432,20 +2432,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); int i915_gem_context_enable(struct drm_i915_private *dev_priv);
void i915_gem_context_close(struct drm_device *dev, struct drm_file *file); void i915_gem_context_close(struct drm_device *dev, struct drm_file *file);
int i915_switch_context(struct intel_ring_buffer *ring, 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 * struct i915_hw_context *
i915_gem_context_get(struct drm_i915_file_private *file_priv, u32 id); i915_gem_context_get(struct drm_i915_file_private *file_priv, u32 id);
void i915_gem_context_free(struct kref *ctx_ref); void i915_gem_context_free(struct kref *ctx_ref);
static inline void i915_gem_context_reference(struct i915_hw_context *ctx) 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) 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) static inline bool i915_gem_context_is_default(const struct i915_hw_context *c)
......
...@@ -2790,7 +2790,7 @@ int i915_gpu_idle(struct drm_device *dev) ...@@ -2790,7 +2790,7 @@ int i915_gpu_idle(struct drm_device *dev)
/* Flush everything onto the inactive list. */ /* Flush everything onto the inactive list. */
for_each_ring(ring, dev_priv, i) { 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) if (ret)
return ret; return ret;
......
...@@ -96,9 +96,6 @@ ...@@ -96,9 +96,6 @@
#define GEN6_CONTEXT_ALIGN (64<<10) #define GEN6_CONTEXT_ALIGN (64<<10)
#define GEN7_CONTEXT_ALIGN 4096 #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) static void do_ppgtt_cleanup(struct i915_hw_ppgtt *ppgtt)
{ {
struct drm_device *dev = ppgtt->base.dev; struct drm_device *dev = ppgtt->base.dev;
...@@ -185,13 +182,15 @@ void i915_gem_context_free(struct kref *ctx_ref) ...@@ -185,13 +182,15 @@ void i915_gem_context_free(struct kref *ctx_ref)
typeof(*ctx), ref); typeof(*ctx), ref);
struct i915_hw_ppgtt *ppgtt = NULL; struct i915_hw_ppgtt *ppgtt = NULL;
/* We refcount even the aliasing PPGTT to keep the code symmetric */ if (ctx->obj) {
if (USES_PPGTT(ctx->obj->base.dev)) /* We refcount even the aliasing PPGTT to keep the code symmetric */
ppgtt = ctx_to_ppgtt(ctx); if (USES_PPGTT(ctx->obj->base.dev))
ppgtt = ctx_to_ppgtt(ctx);
/* XXX: Free up the object before tearing down the address space, in /* XXX: Free up the object before tearing down the address space, in
* case we're bound in the PPGTT */ * case we're bound in the PPGTT */
drm_gem_object_unreference(&ctx->obj->base); drm_gem_object_unreference(&ctx->obj->base);
}
if (ppgtt) if (ppgtt)
kref_put(&ppgtt->ref, ppgtt_release); kref_put(&ppgtt->ref, ppgtt_release);
...@@ -232,32 +231,32 @@ __create_hw_context(struct drm_device *dev, ...@@ -232,32 +231,32 @@ __create_hw_context(struct drm_device *dev,
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
kref_init(&ctx->ref); kref_init(&ctx->ref);
ctx->obj = i915_gem_alloc_object(dev, dev_priv->hw_context_size); list_add_tail(&ctx->link, &dev_priv->context_list);
INIT_LIST_HEAD(&ctx->link);
if (ctx->obj == NULL) {
kfree(ctx);
DRM_DEBUG_DRIVER("Context object allocated failed\n");
return ERR_PTR(-ENOMEM);
}
if (INTEL_INFO(dev)->gen >= 7) { if (dev_priv->hw_context_size) {
ret = i915_gem_object_set_cache_level(ctx->obj, ctx->obj = i915_gem_alloc_object(dev, dev_priv->hw_context_size);
I915_CACHE_L3_LLC); if (ctx->obj == NULL) {
/* Failure shouldn't ever happen this early */ ret = -ENOMEM;
if (WARN_ON(ret))
goto err_out; 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 */ /* Default context will never have a file_priv */
if (file_priv == NULL) if (file_priv != NULL) {
return ctx; ret = idr_alloc(&file_priv->context_idr, ctx,
DEFAULT_CONTEXT_ID, 0, GFP_KERNEL);
ret = idr_alloc(&file_priv->context_idr, ctx, DEFAULT_CONTEXT_ID, 0, if (ret < 0)
GFP_KERNEL); goto err_out;
if (ret < 0) } else
goto err_out; ret = DEFAULT_CONTEXT_ID;
ctx->file_priv = file_priv; ctx->file_priv = file_priv;
ctx->id = ret; ctx->id = ret;
...@@ -294,7 +293,7 @@ i915_gem_create_context(struct drm_device *dev, ...@@ -294,7 +293,7 @@ i915_gem_create_context(struct drm_device *dev,
if (IS_ERR(ctx)) if (IS_ERR(ctx))
return 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 /* We may need to do things with the shrinker which
* require us to immediately switch back to the default * require us to immediately switch back to the default
* context. This can cause a problem as pinning the * context. This can cause a problem as pinning the
...@@ -342,7 +341,7 @@ i915_gem_create_context(struct drm_device *dev, ...@@ -342,7 +341,7 @@ i915_gem_create_context(struct drm_device *dev,
return ctx; return ctx;
err_unpin: err_unpin:
if (is_global_default_ctx) if (is_global_default_ctx && ctx->obj)
i915_gem_object_ggtt_unpin(ctx->obj); i915_gem_object_ggtt_unpin(ctx->obj);
err_destroy: err_destroy:
i915_gem_context_unreference(ctx); i915_gem_context_unreference(ctx);
...@@ -352,32 +351,22 @@ i915_gem_create_context(struct drm_device *dev, ...@@ -352,32 +351,22 @@ i915_gem_create_context(struct drm_device *dev,
void i915_gem_context_reset(struct drm_device *dev) void i915_gem_context_reset(struct drm_device *dev)
{ {
struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_private *dev_priv = dev->dev_private;
struct intel_ring_buffer *ring;
int i; int i;
if (!HAS_HW_CONTEXTS(dev))
return;
/* Prevent the hardware from restoring the last context (which hung) on /* Prevent the hardware from restoring the last context (which hung) on
* the next switch */ * the next switch */
for (i = 0; i < I915_NUM_RINGS; i++) { for (i = 0; i < I915_NUM_RINGS; i++) {
struct i915_hw_context *dctx; struct intel_ring_buffer *ring = &dev_priv->ring[i];
if (!(INTEL_INFO(dev)->ring_mask & (1<<i))) struct i915_hw_context *dctx = ring->default_context;
continue;
/* Do a fake switch to the default context */ /* Do a fake switch to the default context */
ring = &dev_priv->ring[i]; if (ring->last_context == dctx)
dctx = ring->default_context;
if (WARN_ON(!dctx))
continue; continue;
if (!ring->last_context) if (!ring->last_context)
continue; continue;
if (ring->last_context == dctx) if (dctx->obj && i == RCS) {
continue;
if (i == RCS) {
WARN_ON(i915_gem_obj_ggtt_pin(dctx->obj, WARN_ON(i915_gem_obj_ggtt_pin(dctx->obj,
get_context_alignment(dev), 0)); get_context_alignment(dev), 0));
/* Fake a finish/inactive */ /* Fake a finish/inactive */
...@@ -394,44 +383,35 @@ void i915_gem_context_reset(struct drm_device *dev) ...@@ -394,44 +383,35 @@ void i915_gem_context_reset(struct drm_device *dev)
int i915_gem_context_init(struct drm_device *dev) int i915_gem_context_init(struct drm_device *dev)
{ {
struct drm_i915_private *dev_priv = dev->dev_private; struct drm_i915_private *dev_priv = dev->dev_private;
struct intel_ring_buffer *ring; struct i915_hw_context *ctx;
int i; int i;
if (!HAS_HW_CONTEXTS(dev))
return 0;
/* Init should only be called once per module load. Eventually the /* Init should only be called once per module load. Eventually the
* restriction on the context_disabled check can be loosened. */ * restriction on the context_disabled check can be loosened. */
if (WARN_ON(dev_priv->ring[RCS].default_context)) if (WARN_ON(dev_priv->ring[RCS].default_context))
return 0; return 0;
dev_priv->hw_context_size = round_up(get_context_size(dev), 4096); 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)) { if (dev_priv->hw_context_size > (1<<20)) {
DRM_DEBUG_DRIVER("Disabling HW Contexts; invalid size\n"); DRM_DEBUG_DRIVER("Disabling HW Contexts; invalid size %d\n",
return -E2BIG; dev_priv->hw_context_size);
dev_priv->hw_context_size = 0;
}
} }
dev_priv->ring[RCS].default_context = ctx = i915_gem_create_context(dev, NULL, USES_PPGTT(dev));
i915_gem_create_context(dev, NULL, USES_PPGTT(dev)); if (IS_ERR(ctx)) {
DRM_ERROR("Failed to create default global context (error %ld)\n",
if (IS_ERR_OR_NULL(dev_priv->ring[RCS].default_context)) { PTR_ERR(ctx));
DRM_DEBUG_DRIVER("Disabling HW Contexts; create failed %ld\n", return PTR_ERR(ctx);
PTR_ERR(dev_priv->ring[RCS].default_context));
return PTR_ERR(dev_priv->ring[RCS].default_context);
} }
for (i = RCS + 1; i < I915_NUM_RINGS; i++) { /* NB: RCS will hold a ref for all rings */
if (!(INTEL_INFO(dev)->ring_mask & (1<<i))) for (i = 0; i < I915_NUM_RINGS; i++)
continue; dev_priv->ring[i].default_context = ctx;
ring = &dev_priv->ring[i];
/* NB: RCS will hold a ref for all rings */ DRM_DEBUG_DRIVER("%s context support initialized\n", dev_priv->hw_context_size ? "HW" : "fake");
ring->default_context = dev_priv->ring[RCS].default_context;
}
DRM_DEBUG_DRIVER("HW context support initialized\n");
return 0; return 0;
} }
...@@ -441,33 +421,30 @@ void i915_gem_context_fini(struct drm_device *dev) ...@@ -441,33 +421,30 @@ void i915_gem_context_fini(struct drm_device *dev)
struct i915_hw_context *dctx = dev_priv->ring[RCS].default_context; struct i915_hw_context *dctx = dev_priv->ring[RCS].default_context;
int i; int i;
if (!HAS_HW_CONTEXTS(dev)) if (dctx->obj) {
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
/* The only known way to stop the gpu from accessing the hw context is * other code, leading to spurious errors. */
* to reset it. Do this as the very last operation to avoid confusing intel_gpu_reset(dev);
* 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()).
/* When default context is created and switched to, base object refcount * i915_gem_context_fini() will be called after gpu_idle() has switched
* will be 2 (+1 from object creation and +1 from do_switch()). * to default context. So we need to unreference the base object once
* i915_gem_context_fini() will be called after gpu_idle() has switched * to offset the do_switch part, so that i915_gem_context_unreference()
* to default context. So we need to unreference the base object once * can then free the base object correctly. */
* to offset the do_switch part, so that i915_gem_context_unreference() WARN_ON(!dev_priv->ring[RCS].last_context);
* can then free the base object correctly. */ if (dev_priv->ring[RCS].last_context == dctx) {
WARN_ON(!dev_priv->ring[RCS].last_context); /* Fake switch to NULL context */
if (dev_priv->ring[RCS].last_context == dctx) { WARN_ON(dctx->obj->active);
/* Fake switch to NULL context */ i915_gem_object_ggtt_unpin(dctx->obj);
WARN_ON(dctx->obj->active); i915_gem_context_unreference(dctx);
i915_gem_object_ggtt_unpin(dctx->obj); dev_priv->ring[RCS].last_context = NULL;
i915_gem_context_unreference(dctx); }
dev_priv->ring[RCS].last_context = NULL;
} }
for (i = 0; i < I915_NUM_RINGS; i++) { for (i = 0; i < I915_NUM_RINGS; i++) {
struct intel_ring_buffer *ring = &dev_priv->ring[i]; struct intel_ring_buffer *ring = &dev_priv->ring[i];
if (!(INTEL_INFO(dev)->ring_mask & (1<<i)))
continue;
if (ring->last_context) if (ring->last_context)
i915_gem_context_unreference(ring->last_context); i915_gem_context_unreference(ring->last_context);
...@@ -478,7 +455,6 @@ void i915_gem_context_fini(struct drm_device *dev) ...@@ -478,7 +455,6 @@ void i915_gem_context_fini(struct drm_device *dev)
i915_gem_object_ggtt_unpin(dctx->obj); i915_gem_object_ggtt_unpin(dctx->obj);
i915_gem_context_unreference(dctx); i915_gem_context_unreference(dctx);
dev_priv->mm.aliasing_ppgtt = NULL;
} }
int i915_gem_context_enable(struct drm_i915_private *dev_priv) int i915_gem_context_enable(struct drm_i915_private *dev_priv)
...@@ -486,9 +462,6 @@ int i915_gem_context_enable(struct drm_i915_private *dev_priv) ...@@ -486,9 +462,6 @@ int i915_gem_context_enable(struct drm_i915_private *dev_priv)
struct intel_ring_buffer *ring; struct intel_ring_buffer *ring;
int ret, i; int ret, i;
if (!HAS_HW_CONTEXTS(dev_priv->dev))
return 0;
/* This is the only place the aliasing PPGTT gets enabled, which means /* This is the only place the aliasing PPGTT gets enabled, which means
* it has to happen before we bail on reset */ * it has to happen before we bail on reset */
if (dev_priv->mm.aliasing_ppgtt) { if (dev_priv->mm.aliasing_ppgtt) {
...@@ -503,7 +476,7 @@ int i915_gem_context_enable(struct drm_i915_private *dev_priv) ...@@ -503,7 +476,7 @@ int i915_gem_context_enable(struct drm_i915_private *dev_priv)
BUG_ON(!dev_priv->ring[RCS].default_context); BUG_ON(!dev_priv->ring[RCS].default_context);
for_each_ring(ring, dev_priv, i) { for_each_ring(ring, dev_priv, i) {
ret = do_switch(ring, ring->default_context); ret = i915_switch_context(ring, ring->default_context);
if (ret) if (ret)
return ret; return ret;
} }
...@@ -526,19 +499,6 @@ static int context_idr_cleanup(int id, void *p, void *data) ...@@ -526,19 +499,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) 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_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); idr_init(&file_priv->context_idr);
...@@ -559,14 +519,10 @@ void i915_gem_context_close(struct drm_device *dev, struct drm_file *file) ...@@ -559,14 +519,10 @@ void i915_gem_context_close(struct drm_device *dev, struct drm_file *file)
{ {
struct drm_i915_file_private *file_priv = file->driver_priv; 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); 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); idr_destroy(&file_priv->context_idr);
i915_gem_context_unreference(file_priv->private_default_ctx);
} }
struct i915_hw_context * struct i915_hw_context *
...@@ -574,9 +530,6 @@ i915_gem_context_get(struct drm_i915_file_private *file_priv, u32 id) ...@@ -574,9 +530,6 @@ i915_gem_context_get(struct drm_i915_file_private *file_priv, u32 id)
{ {
struct i915_hw_context *ctx; 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); ctx = (struct i915_hw_context *)idr_find(&file_priv->context_idr, id);
if (!ctx) if (!ctx)
return ERR_PTR(-ENOENT); return ERR_PTR(-ENOENT);
...@@ -758,7 +711,6 @@ static int do_switch(struct intel_ring_buffer *ring, ...@@ -758,7 +711,6 @@ static int do_switch(struct intel_ring_buffer *ring,
/** /**
* i915_switch_context() - perform a GPU context switch. * i915_switch_context() - perform a GPU context switch.
* @ring: ring for which we'll execute the 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 * @to: the context to switch to
* *
* The context life cycle is simple. The context refcount is incremented and * The context life cycle is simple. The context refcount is incremented and
...@@ -767,24 +719,30 @@ static int do_switch(struct intel_ring_buffer *ring, ...@@ -767,24 +719,30 @@ static int do_switch(struct intel_ring_buffer *ring,
* object while letting the normal object tracking destroy the backing BO. * object while letting the normal object tracking destroy the backing BO.
*/ */
int i915_switch_context(struct intel_ring_buffer *ring, int i915_switch_context(struct intel_ring_buffer *ring,
struct drm_file *file,
struct i915_hw_context *to) struct i915_hw_context *to)
{ {
struct drm_i915_private *dev_priv = ring->dev->dev_private; struct drm_i915_private *dev_priv = ring->dev->dev_private;
WARN_ON(!mutex_is_locked(&dev_priv->dev->struct_mutex)); WARN_ON(!mutex_is_locked(&dev_priv->dev->struct_mutex));
BUG_ON(file && to == NULL); if (to->obj == NULL) { /* We have the fake context */
if (to != ring->last_context) {
/* We have the fake context */ i915_gem_context_reference(to);
if (!HAS_HW_CONTEXTS(ring->dev)) { if (ring->last_context)
ring->last_context = to; i915_gem_context_unreference(ring->last_context);
ring->last_context = to;
}
return 0; return 0;
} }
return do_switch(ring, to); 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, int i915_gem_context_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *file) struct drm_file *file)
{ {
...@@ -793,7 +751,7 @@ int i915_gem_context_create_ioctl(struct drm_device *dev, void *data, ...@@ -793,7 +751,7 @@ int i915_gem_context_create_ioctl(struct drm_device *dev, void *data,
struct i915_hw_context *ctx; struct i915_hw_context *ctx;
int ret; int ret;
if (!HAS_HW_CONTEXTS(dev)) if (!hw_context_enabled(dev))
return -ENODEV; return -ENODEV;
ret = i915_mutex_lock_interruptible(dev); ret = i915_mutex_lock_interruptible(dev);
......
...@@ -1221,7 +1221,7 @@ i915_gem_do_execbuffer(struct drm_device *dev, void *data, ...@@ -1221,7 +1221,7 @@ i915_gem_do_execbuffer(struct drm_device *dev, void *data,
if (ret) if (ret)
goto err; goto err;
ret = i915_switch_context(ring, file, ctx); ret = i915_switch_context(ring, ctx);
if (ret) if (ret)
goto err; goto err;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment