On Thu, May 27, 2021 at 11:26:45AM -0500, Jason Ekstrand wrote: > The current context uAPI allows for two methods of setting context > parameters: SET_CONTEXT_PARAM and CONTEXT_CREATE_EXT_SETPARAM. The > former is allowed to be called at any time while the later happens as > part of GEM_CONTEXT_CREATE. Currently, everything settable via one is > settable via the other. While some params are fairly simple and setting > them on a live context is harmless such the context priority, others are such _as_ the > far trickier such as the VM or the set of engines. In order to swap out > the VM, for instance, we have to delay until all current in-flight work > is complete, swap in the new VM, and then continue. This leads to a > plethora of potential race conditions we'd really rather avoid. > > In previous patches, we added a i915_gem_proto_context struct which is > capable of storing and tracking all such create parameters. This commit > delays the creation of the actual context until after the client is done > configuring it with SET_CONTEXT_PARAM. From the perspective of the > client, it has the same u32 context ID the whole time. From the > perspective of i915, however, it's an i915_gem_proto_context right up > until the point where we attempt to do something which the proto-context > can't handle at which point the real context gets created. s/ at which point/. Then/ At least feels a bit like a run-on sentence :-) > This is accomplished via a little xarray dance. When GEM_CONTEXT_CREATE > is called, we create a proto-context, reserve a slot in context_xa but > leave it NULL, the proto-context in the corresponding slot in > proto_context_xa. Then, whenever we go to look up a context, we first > check context_xa. If it's there, we return the i915_gem_context and > we're done. If it's not, we look in proto_context_xa and, if we find it > there, we create the actual context and kill the proto-context. > > In order for this dance to work properly, everything which ever touches > a proto-context is guarded by drm_i915_file_private::proto_context_lock, > including context creation. Yes, this means context creation now takes > a giant global lock but it can't really be helped and that should never > be on any driver's fast-path anyway. > > Signed-off-by: Jason Ekstrand <jason@xxxxxxxxxxxxxx> > --- > drivers/gpu/drm/i915/gem/i915_gem_context.c | 211 ++++++++++++++---- > drivers/gpu/drm/i915/gem/i915_gem_context.h | 3 + > .../gpu/drm/i915/gem/i915_gem_context_types.h | 54 +++++ > .../gpu/drm/i915/gem/selftests/mock_context.c | 5 +- > drivers/gpu/drm/i915/i915_drv.h | 24 +- > 5 files changed, 239 insertions(+), 58 deletions(-) > > diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.c b/drivers/gpu/drm/i915/gem/i915_gem_context.c > index 8288af0d33245..f7c83730ee07f 100644 > --- a/drivers/gpu/drm/i915/gem/i915_gem_context.c > +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.c > @@ -298,6 +298,42 @@ proto_context_create(struct drm_i915_private *i915, unsigned int flags) > return err; > } > > +static int proto_context_register_locked(struct drm_i915_file_private *fpriv, > + struct i915_gem_proto_context *pc, > + u32 *id) > +{ > + int ret; > + void *old; > + > + lockdep_assert_held(&fpriv->proto_context_lock); > + > + ret = xa_alloc(&fpriv->context_xa, id, NULL, xa_limit_32b, GFP_KERNEL); > + if (ret) > + return ret; > + > + old = xa_store(&fpriv->proto_context_xa, *id, pc, GFP_KERNEL); > + if (xa_is_err(old)) { > + xa_erase(&fpriv->context_xa, *id); > + return xa_err(old); > + } > + GEM_BUG_ON(old); I'd go with WARN_ON here. We just leak, and BUG_ON kills the box. GEM_BUG_ON is for the additional gem consistency checks which are too expensive to have enabled in production. Registering a proto context isn't one of these things. > + > + return 0; > +} > + > +static int proto_context_register(struct drm_i915_file_private *fpriv, > + struct i915_gem_proto_context *pc, > + u32 *id) > +{ > + int ret; > + > + mutex_lock(&fpriv->proto_context_lock); > + ret = proto_context_register_locked(fpriv, pc, id); > + mutex_unlock(&fpriv->proto_context_lock); > + > + return ret; > +} > + > static int set_proto_ctx_vm(struct drm_i915_file_private *fpriv, > struct i915_gem_proto_context *pc, > const struct drm_i915_gem_context_param *args) > @@ -1448,12 +1484,12 @@ void i915_gem_init__contexts(struct drm_i915_private *i915) > init_contexts(&i915->gem.contexts); > } > > -static int gem_context_register(struct i915_gem_context *ctx, > - struct drm_i915_file_private *fpriv, > - u32 *id) > +static void gem_context_register(struct i915_gem_context *ctx, > + struct drm_i915_file_private *fpriv, > + u32 id) > { > struct drm_i915_private *i915 = ctx->i915; > - int ret; > + void *old; > > ctx->file_priv = fpriv; > > @@ -1462,19 +1498,12 @@ static int gem_context_register(struct i915_gem_context *ctx, > current->comm, pid_nr(ctx->pid)); > > /* And finally expose ourselves to userspace via the idr */ > - ret = xa_alloc(&fpriv->context_xa, id, ctx, xa_limit_32b, GFP_KERNEL); > - if (ret) > - goto err_pid; > + old = xa_store(&fpriv->context_xa, id, ctx, GFP_KERNEL); > + GEM_BUG_ON(old); Same song about WARN_ON here. > > spin_lock(&i915->gem.contexts.lock); > list_add_tail(&ctx->link, &i915->gem.contexts.list); > spin_unlock(&i915->gem.contexts.lock); > - > - return 0; > - > -err_pid: > - put_pid(fetch_and_zero(&ctx->pid)); > - return ret; > } > > int i915_gem_context_open(struct drm_i915_private *i915, > @@ -1484,9 +1513,12 @@ int i915_gem_context_open(struct drm_i915_private *i915, > struct i915_gem_proto_context *pc; > struct i915_gem_context *ctx; > int err; > - u32 id; > > - xa_init_flags(&file_priv->context_xa, XA_FLAGS_ALLOC); > + mutex_init(&file_priv->proto_context_lock); > + xa_init_flags(&file_priv->proto_context_xa, XA_FLAGS_ALLOC); > + > + /* 0 reserved for the default context */ > + xa_init_flags(&file_priv->context_xa, XA_FLAGS_ALLOC1); > > /* 0 reserved for invalid/unassigned ppgtt */ > xa_init_flags(&file_priv->vm_xa, XA_FLAGS_ALLOC1); > @@ -1504,28 +1536,31 @@ int i915_gem_context_open(struct drm_i915_private *i915, > goto err; > } > > - err = gem_context_register(ctx, file_priv, &id); > - if (err < 0) > - goto err_ctx; > + gem_context_register(ctx, file_priv, 0); > > - GEM_BUG_ON(id); > return 0; > > -err_ctx: > - context_close(ctx); > err: > xa_destroy(&file_priv->vm_xa); > xa_destroy(&file_priv->context_xa); > + xa_destroy(&file_priv->proto_context_xa); > + mutex_destroy(&file_priv->proto_context_lock); > return err; > } > > void i915_gem_context_close(struct drm_file *file) > { > struct drm_i915_file_private *file_priv = file->driver_priv; > + struct i915_gem_proto_context *pc; > struct i915_address_space *vm; > struct i915_gem_context *ctx; > unsigned long idx; > > + xa_for_each(&file_priv->proto_context_xa, idx, pc) > + proto_context_close(pc); > + xa_destroy(&file_priv->proto_context_xa); > + mutex_destroy(&file_priv->proto_context_lock); > + > xa_for_each(&file_priv->context_xa, idx, ctx) > context_close(ctx); > xa_destroy(&file_priv->context_xa); > @@ -2480,12 +2515,73 @@ static bool client_is_banned(struct drm_i915_file_private *file_priv) > return atomic_read(&file_priv->ban_score) >= I915_CLIENT_SCORE_BANNED; > } > > +static inline struct i915_gem_context * > +__context_lookup(struct drm_i915_file_private *file_priv, u32 id) > +{ > + struct i915_gem_context *ctx; > + > + rcu_read_lock(); > + ctx = xa_load(&file_priv->context_xa, id); > + if (ctx && !kref_get_unless_zero(&ctx->ref)) > + ctx = NULL; > + rcu_read_unlock(); > + > + return ctx; > +} > + > +struct i915_gem_context * > +lazy_create_context_locked(struct drm_i915_file_private *file_priv, > + struct i915_gem_proto_context *pc, u32 id) My bikeshed would call this finalize_create_context_locked or something like that ... At least I'm thinking of this more as "finializing the process of creating a context" and less of "creating context on-demand". The latter is e.g. what we're doing with the default engine set. Different beasts conceptually. > +{ > + struct i915_gem_context *ctx; > + void *old; > + > + lockdep_assert_held(&file_priv->proto_context_lock); > + > + ctx = i915_gem_create_context(file_priv->dev_priv, pc); > + if (IS_ERR(ctx)) > + return ctx; > + > + gem_context_register(ctx, file_priv, id); > + > + old = xa_erase(&file_priv->proto_context_xa, id); > + GEM_BUG_ON(old != pc); > + proto_context_close(pc); > + > + /* One for the xarray and one for the caller */ > + return i915_gem_context_get(ctx); > +} > + > +struct i915_gem_context * > +i915_gem_context_lookup(struct drm_i915_file_private *file_priv, u32 id) > +{ > + struct i915_gem_proto_context *pc; > + struct i915_gem_context *ctx; > + > + ctx = __context_lookup(file_priv, id); > + if (ctx) > + return ctx; > + > + mutex_lock(&file_priv->proto_context_lock); > + /* Try one more time under the lock */ > + ctx = __context_lookup(file_priv, id); > + if (!ctx) { > + pc = xa_load(&file_priv->proto_context_xa, id); > + if (!pc) > + ctx = ERR_PTR(-ENOENT); > + else > + ctx = lazy_create_context_locked(file_priv, pc, id); > + } > + mutex_unlock(&file_priv->proto_context_lock); > + > + return ctx; > +} > + > int i915_gem_context_create_ioctl(struct drm_device *dev, void *data, > struct drm_file *file) > { > struct drm_i915_private *i915 = to_i915(dev); > struct drm_i915_gem_context_create_ext *args = data; > - struct i915_gem_context *ctx; > struct create_ext ext_data; > int ret; > u32 id; > @@ -2517,28 +2613,21 @@ int i915_gem_context_create_ioctl(struct drm_device *dev, void *data, > create_extensions, > ARRAY_SIZE(create_extensions), > &ext_data); > - if (ret) { > - proto_context_close(ext_data.pc); > - return ret; > - } > + if (ret) > + goto err_pc; > } > > - ctx = i915_gem_create_context(i915, ext_data.pc); > - proto_context_close(ext_data.pc); > - if (IS_ERR(ctx)) > - return PTR_ERR(ctx); > - > - ret = gem_context_register(ctx, ext_data.fpriv, &id); > + ret = proto_context_register(ext_data.fpriv, ext_data.pc, &id); > if (ret < 0) > - goto err_ctx; > + goto err_pc; > > args->ctx_id = id; > drm_dbg(&i915->drm, "HW context %d created\n", args->ctx_id); > > return 0; > > -err_ctx: > - context_close(ctx); > +err_pc: > + proto_context_close(ext_data.pc); > return ret; > } > > @@ -2547,6 +2636,7 @@ int i915_gem_context_destroy_ioctl(struct drm_device *dev, void *data, > { > struct drm_i915_gem_context_destroy *args = data; > struct drm_i915_file_private *file_priv = file->driver_priv; > + struct i915_gem_proto_context *pc; > struct i915_gem_context *ctx; > > if (args->pad != 0) > @@ -2555,11 +2645,21 @@ int i915_gem_context_destroy_ioctl(struct drm_device *dev, void *data, > if (!args->ctx_id) > return -ENOENT; > > + mutex_lock(&file_priv->proto_context_lock); I think a comment here that we need to hold the proto mutex even for finalized context to avoid races with finalization would be nice. > ctx = xa_erase(&file_priv->context_xa, args->ctx_id); > - if (!ctx) > + pc = xa_erase(&file_priv->proto_context_xa, args->ctx_id); > + mutex_unlock(&file_priv->proto_context_lock); > + > + if (!ctx && !pc) > return -ENOENT; > + GEM_WARN_ON(ctx && pc); > + > + if (pc) > + proto_context_close(pc); > + > + if (ctx) > + context_close(ctx); > > - context_close(ctx); > return 0; > } > > @@ -2692,16 +2792,41 @@ int i915_gem_context_setparam_ioctl(struct drm_device *dev, void *data, > { > struct drm_i915_file_private *file_priv = file->driver_priv; > struct drm_i915_gem_context_param *args = data; > + struct i915_gem_proto_context *pc; > struct i915_gem_context *ctx; > - int ret; > + int ret = 0; > > - ctx = i915_gem_context_lookup(file_priv, args->ctx_id); > - if (IS_ERR(ctx)) > - return PTR_ERR(ctx); > + ctx = __context_lookup(file_priv, args->ctx_id); > + if (ctx) > + goto set_ctx_param; > > - ret = ctx_setparam(file_priv, ctx, args); > + mutex_lock(&file_priv->proto_context_lock); > + ctx = __context_lookup(file_priv, args->ctx_id); Not unconditionally taking the mutex here feels a bit like overkill? Do we really need that fast path? > + if (ctx) > + goto unlock; > + > + pc = xa_load(&file_priv->proto_context_xa, args->ctx_id); > + if (!pc) { > + ret = -ENOENT; > + goto unlock; > + } > + > + /* FIXME: We should consider disallowing SET_CONTEXT_PARAM for most > + * things on future platforms. Clients should be using > + * CONTEXT_CREATE_EXT_PARAM instead. > + */ I think the way to do that is to finalize the context creation from CONTEXT_CREATE_EXT on these platforms. That plugs this hole for good and by design. Maybe on gen13+ or something like that. Or whatever it is we're using for enumerating generations now. > + ret = set_proto_ctx_param(file_priv, pc, args); > + > +unlock: > + mutex_unlock(&file_priv->proto_context_lock); > + > +set_ctx_param: > + if (!ret && ctx) I don't think you need to check for ret here? It's not set by any path leading to here where ctx != NULL. Also mildly unhappy about the control flow here, we could simplify it if we don't do the lockless faspath. > + ret = ctx_setparam(file_priv, ctx, args); > + > + if (ctx) > + i915_gem_context_put(ctx); > > - i915_gem_context_put(ctx); > return ret; > } > > diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context.h b/drivers/gpu/drm/i915/gem/i915_gem_context.h > index b5c908f3f4f22..20411db84914a 100644 > --- a/drivers/gpu/drm/i915/gem/i915_gem_context.h > +++ b/drivers/gpu/drm/i915/gem/i915_gem_context.h > @@ -133,6 +133,9 @@ int i915_gem_context_setparam_ioctl(struct drm_device *dev, void *data, > int i915_gem_context_reset_stats_ioctl(struct drm_device *dev, void *data, > struct drm_file *file); > > +struct i915_gem_context * > +i915_gem_context_lookup(struct drm_i915_file_private *file_priv, u32 id); > + > static inline struct i915_gem_context * > i915_gem_context_get(struct i915_gem_context *ctx) > { > diff --git a/drivers/gpu/drm/i915/gem/i915_gem_context_types.h b/drivers/gpu/drm/i915/gem/i915_gem_context_types.h > index 2ac341f805c8f..b673061f4f5ba 100644 > --- a/drivers/gpu/drm/i915/gem/i915_gem_context_types.h > +++ b/drivers/gpu/drm/i915/gem/i915_gem_context_types.h > @@ -122,6 +122,60 @@ struct i915_gem_proto_engine { > * an i915_gem_context. This is used to gather parameters provided either > * through creation flags or via SET_CONTEXT_PARAM so that, when we create > * the final i915_gem_context, those parameters can be immutable. > + * > + * The context uAPI allows for two methods of setting context parameters: > + * SET_CONTEXT_PARAM and CONTEXT_CREATE_EXT_SETPARAM. The former is > + * allowed to be called at any time while the later happens as part of > + * GEM_CONTEXT_CREATE. When these were initially added, Currently, > + * everything settable via one is settable via the other. While some > + * params are fairly simple and setting them on a live context is harmless > + * such the context priority, others are far trickier such as the VM or the > + * set of engines. To avoid some truly nasty race conditions, we don't > + * allow setting the VM or the set of engines on live contexts. > + * > + * The way we dealt with this without breaking older userspace that sets > + * the VM or engine set via SET_CONTEXT_PARAM is to delay the creation of > + * the actual context until after the client is done configuring it with > + * SET_CONTEXT_PARAM. From the perspective of the client, it has the same > + * u32 context ID the whole time. From the perspective of i915, however, > + * it's an i915_gem_proto_context right up until the point where we attempt > + * to do something which the proto-context can't handle at which point the > + * real context gets created. > + * > + * This is accomplished via a little xarray dance. When GEM_CONTEXT_CREATE > + * is called, we create a proto-context, reserve a slot in context_xa but > + * leave it NULL, the proto-context in the corresponding slot in > + * proto_context_xa. Then, whenever we go to look up a context, we first > + * check context_xa. If it's there, we return the i915_gem_context and > + * we're done. If it's not, we look in proto_context_xa and, if we find it > + * there, we create the actual context and kill the proto-context. > + * > + * At the time we made this change (April, 2021), we did a fairly complete > + * audit of existing userspace to ensure this wouldn't break anything: > + * > + * - Mesa/i965 didn't use the engines or VM APIs at all > + * > + * - Mesa/ANV used the engines API but via CONTEXT_CREATE_EXT_SETPARAM and > + * didn't use the VM API. > + * > + * - Mesa/iris didn't use the engines or VM APIs at all > + * > + * - The open-source compute-runtime didn't yet use the engines API but > + * did use the VM API via SET_CONTEXT_PARAM. However, CONTEXT_SETPARAM > + * was always the second ioctl on that context, immediately following > + * GEM_CONTEXT_CREATE. > + * > + * - The media driver sets engines and bonding/balancing via > + * SET_CONTEXT_PARAM. However, CONTEXT_SETPARAM to set the VM was > + * always the second ioctl on that context, immediately following > + * GEM_CONTEXT_CREATE and setting engines immediately followed that. > + * > + * In order for this dance to work properly, any modification to an > + * i915_gem_proto_context that is exposed to the client via > + * drm_i915_file_private::proto_context_xa must be guarded by > + * drm_i915_file_private::proto_context_lock. The exception is when a > + * proto-context has not yet been exposed such as when handling > + * CONTEXT_CREATE_SET_PARAM during GEM_CONTEXT_CREATE. > */ > struct i915_gem_proto_context { > /** @vm: See i915_gem_context::vm */ > diff --git a/drivers/gpu/drm/i915/gem/selftests/mock_context.c b/drivers/gpu/drm/i915/gem/selftests/mock_context.c > index cbeefd060e97b..61aaac4a334cf 100644 > --- a/drivers/gpu/drm/i915/gem/selftests/mock_context.c > +++ b/drivers/gpu/drm/i915/gem/selftests/mock_context.c > @@ -81,6 +81,7 @@ void mock_init_contexts(struct drm_i915_private *i915) > struct i915_gem_context * > live_context(struct drm_i915_private *i915, struct file *file) > { > + struct drm_i915_file_private *fpriv = to_drm_file(file)->driver_priv; > struct i915_gem_proto_context *pc; > struct i915_gem_context *ctx; > int err; > @@ -97,10 +98,12 @@ live_context(struct drm_i915_private *i915, struct file *file) > > i915_gem_context_set_no_error_capture(ctx); > > - err = gem_context_register(ctx, to_drm_file(file)->driver_priv, &id); > + err = xa_alloc(&fpriv->context_xa, &id, NULL, xa_limit_32b, GFP_KERNEL); > if (err < 0) > goto err_ctx; > > + gem_context_register(ctx, fpriv, id); > + > return ctx; > > err_ctx: > diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h > index d7bd732ceacfc..8b00292e1ae56 100644 > --- a/drivers/gpu/drm/i915/i915_drv.h > +++ b/drivers/gpu/drm/i915/i915_drv.h > @@ -201,6 +201,16 @@ struct drm_i915_file_private { > struct rcu_head rcu; > }; > > + /** @proto_context_lock: Guards all i915_gem_proto_context operations > + * > + * See i915_gem_proto_context. Please add locking rules here, like "This is always held whenever we manipulate any proto context, including finalizing it on first actual use of the GEM context". > + */ > + struct mutex proto_context_lock; > + > + /** @proto_context_xa: xarray of i915_gem_proto_context */ Pls fix hyperlinks. Also please put your nice explainer from the commit message here ... > + struct xarray proto_context_xa; > + > + /** @context_xa: xarray of fully created i915_gem_context */ ... and reference it with a "See @proto_context_xa" here. Maybe also reference i915_gem_context_lookup() from these so readers of the code can easily find all the pieces of this magic. Also mention here that write access to @context_xa is protected by @proto_context_lock. It must be held to avoid races with finalization of proto context in e.g. i915_gem_context_destroy_ioctl(), and this wasn't obvious to me at all. > struct xarray context_xa; > struct xarray vm_xa; > > @@ -1857,20 +1867,6 @@ struct drm_gem_object *i915_gem_prime_import(struct drm_device *dev, > > struct dma_buf *i915_gem_prime_export(struct drm_gem_object *gem_obj, int flags); > > -static inline struct i915_gem_context * > -i915_gem_context_lookup(struct drm_i915_file_private *file_priv, u32 id) > -{ > - struct i915_gem_context *ctx; > - > - rcu_read_lock(); > - ctx = xa_load(&file_priv->context_xa, id); > - if (ctx && !kref_get_unless_zero(&ctx->ref)) > - ctx = NULL; > - rcu_read_unlock(); > - > - return ctx ? ctx : ERR_PTR(-ENOENT); > -} > - > static inline struct i915_address_space * > i915_gem_vm_lookup(struct drm_i915_file_private *file_priv, u32 id) > { > -- > 2.31.1 Nothing big looks wrong, with the nits addressed: Reviewed-by: Daniel Vetter <daniel.vetter@xxxxxxxx> > -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch