From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-15 17:03:44
On Wed, Sep 15, 2021 at 09:24:15AM +0100, Tvrtko Ursulin wrote:
On 14/09/2021 19:04, Matthew Brost wrote:
quoted
On Tue, Sep 14, 2021 at 09:34:08AM +0100, Tvrtko Ursulin wrote:
quoted
8<
quoted
quoted
Today we have:
for_each intel_engines: // intel_engines is a flat list of all engines
intel_engine_setup()
You propose to change it to:
for_each engine_class:
for 0..max_global_engine_instance:
for_each intel_engines:
skip engine not present
skip class not matching
count logical instance
for_each intel_engines:
skip engine not present
skip wrong class
intel_engine_setup()
I propose:
// Leave as is:
for_each intel_engines:
intel_engine_setup()
// Add:
for_each engine_class:
logical = 0
for_each gt->engine_class[class]:
skip engine not present
engine->logical_instance = logical++
When code which actually needs a preturbed "map" arrives you add that in to
this second loop.
See above, why introduce an algorithm that doesn't work for future parts
+ future patches are land imminently? It makes zero sense whatsoever.
With your proposal we would literally land code to just throw it away a
couple of months from now + break patches we intend to land soon. This
It sure works, it just walks the per class list instead of walking the flat
list skipping one class at the time.
Just add the map based transformation to the second pass later, when it
becomes required.
I can flatten the algorithm if that helps alleviate your concerns but
with that being said, I've played around this locally and IMO makes the
code way more ugly. Sure it eliminates some iterations of the loop but
who really cares about that in a one time setup function?
quoted
algorithm works and has no reason whatsoever to be optimal as it a one
time setup call. I really don't understand why we are still talking
about this paint color.
I don't think bike shedding is not an appropriate term when complaint is how
proposed algorithm is needlessly complicated.
Are you just ignoring the fact that the algorithm (map) is needed in
pending patches? IMO it is more complicated to write throw away code
when the proper algorithm is already written. If the logical mapping was
straight forward on all platforms as the ones currently upstream I would
100% agree with your suggestion, but it isn't on unembargoed platforms
eminently going upstream. The algorithm I have works for the current
platforms + the pending platforms. IMO is 100% acceptable to merge
something looking towards a known future.
Matt
From: John Harrison <hidden> Date: 2021-09-15 19:21:45
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
Add multi-lrc context registration H2G. In addition a workqueue and
process descriptor are setup during multi-lrc context registration as
these data structures are needed for multi-lrc submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/intel_context_types.h | 12 ++
drivers/gpu/drm/i915/gt/intel_lrc.c | 5 +
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 2 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 109 +++++++++++++++++-
4 files changed, 126 insertions(+), 2 deletions(-)
@@ -232,8 +232,20 @@ struct intel_context {/** @parent: pointer to parent if child */structintel_context*parent;++/** @guc_wqi_head: head pointer in work queue */+u16guc_wqi_head;+/** @guc_wqi_tail: tail pointer in work queue */+u16guc_wqi_tail;+
These should be in the 'guc_state' sub-struct? Would be good to keep all GuC specific content in one self-contained struct. Especially given the other child/parent fields are no going to be guc_ prefixed any more.
quoted hunk
/** @guc_number_children: number of children if parent */ u8 guc_number_children;++ /**+ * @parent_page: page in context used by parent for work queue,
Maybe 'page in context record'? Otherwise, exactly what 'context' is meant here? It isn't the 'struct intel_context'. The contetx record is saved as 'ce->state' / 'ce->lrc_reg_state', yes? Is it possible to link to either of those field? Probably not given that they don't appear to have any kerneldoc description :(. Maybe add that in too :).
+ * work queue descriptor
Later on, it is described as 'process descriptor and work queue'. It would be good to be consistent.
Can this not be derived from GUC_WQ_SIZE given that the two are fundamentally linked? E.g. '#define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'? And maybe have a '#define WQ_TOTAL_SIZE PAGE_SIZE' and use that in all of WQ_OFFSET, GUC_WQ_SIZE and the allocation itself in intel_lrc.c?
Also, the process descriptor is actually an array of descriptors sized by the number of children? Or am I misunderstanding the code below? In so, shouldn't there be a 'COMPILE_BUG_ON((MAX_ENGINE_INSTANCE * sizeof(descriptor)) < (WQ_DESC_SIZE)' where WQ_DESC_SIZE is WQ_TOTAL_SIZE - WQ_SIZE?
@@ -1423,6 +1487,7 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) struct guc_lrc_desc *desc; bool context_registered; intel_wakeref_t wakeref;+ struct intel_context *child; int ret = 0; GEM_BUG_ON(!engine->mask);
@@ -1448,6 +1513,42 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD; guc_context_policy_init(engine, desc);+ /*+ * Context is a parent, we need to register a process descriptor+ * describing a work queue and register all child contexts.
Technically, this should say 'If the context is a parent'. Or just move it to be inside the if block.
Should this not be inherited from the ce? And same below. Or are we not using this priority in that way?
John.
quoted hunk
+ pdesc->wq_status = WQ_STATUS_ACTIVE;++ for_each_child(ce, child) {+ desc = __get_lrc_desc(guc, child->guc_id.id);++ desc->engine_class =+ engine_class_to_guc_class(engine->class);+ desc->hw_context_desc = child->lrc.lrca;+ desc->priority = GUC_CLIENT_PRIORITY_KMD_NORMAL;+ desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD;+ guc_context_policy_init(engine, desc);+ }+ }+ /* * The context_lookup xarray is used to determine if the hardware * context is currently registered. There are two cases in which it
From: John Harrison <hidden> Date: 2021-09-15 19:24:51
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
In GuC parent-child contexts the parent context controls the scheduling,
ensure only the parent does the scheduling operations.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 24 ++++++++++++++-----
1 file changed, 18 insertions(+), 6 deletions(-)
@@ -2358,11 +2369,12 @@ static int guc_request_alloc(struct i915_request *rq)*exhaustedandreturn-EAGAINtotheuserindicatingthattheycantry*againinthefuture.*-*Thereisnoneedforalockhereasthetimelinemutexensuresat-*mostonecontextcanbeexecutingthiscodepathatonce.The-*guc_id_refisincrementedonceforeveryrequestinflightand-*decrementedoneachretire.Whenitiszero,alockaroundthe-*increment(inpin_guc_id)isneededtosealaracewithunpin_guc_id.+*Thereisnoneedforalockhereasthetimelinemutex(or+*parallel_submitmutexinthecaseofmulti-lrc)ensuresatmostone+*contextcanbeexecutingthiscodepathatonce.Theguc_id_refis
Isn't that now two? One uni-LRC holding the timeline mutex and one multi-LRC holding the parallel submit mutex?
John.
quoted hunk
+ * incremented once for every request in flight and decremented on each+ * retire. When it is zero, a lock around the increment (in pin_guc_id)+ * is needed to seal a race with unpin_guc_id. */ if (atomic_add_unless(&ce->guc_id.ref, 1, 0)) goto out;
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-15 19:36:31
On Wed, Sep 15, 2021 at 12:21:35PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Add multi-lrc context registration H2G. In addition a workqueue and
process descriptor are setup during multi-lrc context registration as
these data structures are needed for multi-lrc submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/intel_context_types.h | 12 ++
drivers/gpu/drm/i915/gt/intel_lrc.c | 5 +
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 2 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 109 +++++++++++++++++-
4 files changed, 126 insertions(+), 2 deletions(-)
@@ -232,8 +232,20 @@ struct intel_context {/** @parent: pointer to parent if child */structintel_context*parent;++/** @guc_wqi_head: head pointer in work queue */+u16guc_wqi_head;+/** @guc_wqi_tail: tail pointer in work queue */+u16guc_wqi_tail;+
These should be in the 'guc_state' sub-struct? Would be good to keep all GuC
specific content in one self-contained struct. Especially given the other
child/parent fields are no going to be guc_ prefixed any more.
Right now I have everything in guc_state protected by guc_state.lock,
these fields are not protected by this lock. IMO it is better to use a
different sub-structure for the parallel fields (even if anonymous).
quoted
/** @guc_number_children: number of children if parent */ u8 guc_number_children;++ /**+ * @parent_page: page in context used by parent for work queue,
Maybe 'page in context record'? Otherwise, exactly what 'context' is meant
here? It isn't the 'struct intel_context'. The contetx record is saved as
'ce->state' / 'ce->lrc_reg_state', yes? Is it possible to link to either of
It is the page in ce->state / page minus LRC reg offset in
ce->lrg_reg_state. Will update the commit to make that clear.
those field? Probably not given that they don't appear to have any kerneldoc
description :(. Maybe add that in too :).
quoted
+ * work queue descriptor
Later on, it is described as 'process descriptor and work queue'. It would
be good to be consistent.
Can this not be derived from GUC_WQ_SIZE given that the two are
fundamentally linked? E.g. '#define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'?
Yes. I like 'define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'. Will change.
And maybe have a '#define WQ_TOTAL_SIZE PAGE_SIZE' and use that in all of
WQ_OFFSET, GUC_WQ_SIZE and the allocation itself in intel_lrc.c?
Also, the process descriptor is actually an array of descriptors sized by
the number of children? Or am I misunderstanding the code below? In so,
No, it is fixed size descriptor.
A later patch in the series uses the space of above the process
descriptor for insertation of preeemption points handshake. That does
depend on the number of children. I will add a COMPILE_BUG_ON for that
to ensure everything fits in the memory layout.
shouldn't there be a 'COMPILE_BUG_ON((MAX_ENGINE_INSTANCE *
sizeof(descriptor)) < (WQ_DESC_SIZE)' where WQ_DESC_SIZE is WQ_TOTAL_SIZE -
WQ_SIZE?
Where did the LRC_STATE_OFFSET come from? Is that built in to the
lrg_reg_state pointer itself? That needs to be documented somewhere.
In gt/intel_lrc.c (lrc_pin) ce->lrc_reg_state is assigned to
mmap(ce->state) + LRC_STATE_OFFSET. I can update the kerneldoc for that
field in this patch.
@@ -1423,6 +1487,7 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) struct guc_lrc_desc *desc; bool context_registered; intel_wakeref_t wakeref;+ struct intel_context *child; int ret = 0; GEM_BUG_ON(!engine->mask);
@@ -1448,6 +1513,42 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD; guc_context_policy_init(engine, desc);+ /*+ * Context is a parent, we need to register a process descriptor+ * describing a work queue and register all child contexts.
Technically, this should say 'If the context is a parent'. Or just move it
to be inside the if block.
Should this not be inherited from the ce? And same below. Or are we not
using this priority in that way?
Honestly I don't think this field is used or maybe doesn't even exist
anymore. I'll check the GuC code and likely delete this or if it is
still present I'll inherited this from the ce.
Matt
John.
quoted
+ pdesc->wq_status = WQ_STATUS_ACTIVE;++ for_each_child(ce, child) {+ desc = __get_lrc_desc(guc, child->guc_id.id);++ desc->engine_class =+ engine_class_to_guc_class(engine->class);+ desc->hw_context_desc = child->lrc.lrca;+ desc->priority = GUC_CLIENT_PRIORITY_KMD_NORMAL;+ desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD;+ guc_context_policy_init(engine, desc);+ }+ }+ /* * The context_lookup xarray is used to determine if the hardware * context is currently registered. There are two cases in which it
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-15 19:39:47
On Wed, Sep 15, 2021 at 12:24:41PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
In GuC parent-child contexts the parent context controls the scheduling,
ensure only the parent does the scheduling operations.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 24 ++++++++++++++-----
1 file changed, 18 insertions(+), 6 deletions(-)
@@ -2358,11 +2369,12 @@ static int guc_request_alloc(struct i915_request *rq)*exhaustedandreturn-EAGAINtotheuserindicatingthattheycantry*againinthefuture.*-*Thereisnoneedforalockhereasthetimelinemutexensuresat-*mostonecontextcanbeexecutingthiscodepathatonce.The-*guc_id_refisincrementedonceforeveryrequestinflightand-*decrementedoneachretire.Whenitiszero,alockaroundthe-*increment(inpin_guc_id)isneededtosealaracewithunpin_guc_id.+*Thereisnoneedforalockhereasthetimelinemutex(or+*parallel_submitmutexinthecaseofmulti-lrc)ensuresatmostone+*contextcanbeexecutingthiscodepathatonce.Theguc_id_refis
Isn't that now two? One uni-LRC holding the timeline mutex and one multi-LRC
holding the parallel submit mutex?
This is actually a stale comment and need scrub this. The
parallel_submit mutex is gone, now we grab the ce->timeline locks
starting at the parent and then all children in a loop. I think the
original comment is sufficient.
Matt
John.
quoted
+ * incremented once for every request in flight and decremented on each+ * retire. When it is zero, a lock around the increment (in pin_guc_id)+ * is needed to seal a race with unpin_guc_id. */ if (atomic_add_unless(&ce->guc_id.ref, 1, 0)) goto out;
From: John Harrison <hidden> Date: 2021-09-15 20:04:55
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
Assign contexts in parent-child relationship consecutive guc_ids. This
is accomplished by partitioning guc_id space between ones that need to
be consecutive (1/16 available guc_ids) and ones that do not (15/16 of
available guc_ids). The consecutive search is implemented via the bitmap
API.
This is a precursor to the full GuC multi-lrc implementation but aligns
to how GuC mutli-lrc interface is defined - guc_ids must be consecutive
when using the GuC multi-lrc interface.
v2:
(Daniel Vetter)
- Explictly state why we assign consecutive guc_ids
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/uc/intel_guc.h | 6 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 107 +++++++++++++-----
2 files changed, 86 insertions(+), 27 deletions(-)
@@ -61,9 +61,13 @@ struct intel_guc {*/spinlock_tlock;/**-*@guc_ids:usedtoallocatenewguc_ids+*@guc_ids:usedtoallocatenewguc_ids,single-lrc*/structidaguc_ids;+/**+*@guc_ids_bitmap:usedtoallocatenewguc_ids,multi-lrc+*/+unsignedlong*guc_ids_bitmap;/** @num_guc_ids: number of guc_ids that can be used */u32num_guc_ids;/** @max_guc_ids: max number of guc_ids that can be used */
The 'hence' clause seems to be attached to the wrong reason. The id space is partition because of the contiguous vs random requirements of multi vs single LRC, not because a different allocator is used in one partion vs the other.
quoted hunk
+ * partition the guc_id space. We believe the number of multi-lrc contexts in+ * use should be low and 1/16 should be sufficient. Minimum of 32 guc_ids for+ * multi-lrc.+ */+#define NUMBER_MULTI_LRC_GUC_ID(guc) \+ ((guc)->submission_state.num_guc_ids / 16 > 32 ? \+ (guc)->submission_state.num_guc_ids / 16 : 32)+ /* * Below is a set of functions which control the GuC scheduling state which * require a lock.
@@ -1239,18 +1256,43 @@ static void guc_submit_request(struct i915_request *rq) spin_unlock_irqrestore(&sched_engine->lock, flags); }-static int new_guc_id(struct intel_guc *guc)+static int new_guc_id(struct intel_guc *guc, struct intel_context *ce) {- return ida_simple_get(&guc->submission_state.guc_ids, 0,- guc->submission_state.num_guc_ids, GFP_KERNEL |- __GFP_RETRY_MAYFAIL | __GFP_NOWARN);+ int ret;++ GEM_BUG_ON(intel_context_is_child(ce));++ if (intel_context_is_parent(ce))+ ret = bitmap_find_free_region(guc->submission_state.guc_ids_bitmap,+ NUMBER_MULTI_LRC_GUC_ID(guc),+ order_base_2(ce->guc_number_children+ + 1));+ else+ ret = ida_simple_get(&guc->submission_state.guc_ids,+ NUMBER_MULTI_LRC_GUC_ID(guc),+ guc->submission_state.num_guc_ids,+ GFP_KERNEL | __GFP_RETRY_MAYFAIL |+ __GFP_NOWARN);+ if (unlikely(ret < 0))+ return ret;++ ce->guc_id.id = ret;+ return 0; } static void __release_guc_id(struct intel_guc *guc, struct intel_context *ce) {+ GEM_BUG_ON(intel_context_is_child(ce));+ if (!context_guc_id_invalid(ce)) {- ida_simple_remove(&guc->submission_state.guc_ids,- ce->guc_id.id);+ if (intel_context_is_parent(ce))+ bitmap_release_region(guc->submission_state.guc_ids_bitmap,+ ce->guc_id.id,+ order_base_2(ce->guc_number_children+ + 1));
Is there any check against adding/removing children when the guc_ids are allocated? Presumably it shouldn't ever happen but if it did then the bitmap_release would not match the allocation. Maybe add BUG_ON(ce->guc_id) to the parent/child link functions (if it's not there already?).
As far as I can tell, the only actual change to this function (beyond 'ce_in->id = id' vs 'return id' and adding anti-family asserts) is that this spinlock was dropped. However, I'm not seeing any replacement for it or any comment about why the spinlock is no longer necessary.
John.
quoted hunk
- clr_context_registered(ce);- spin_unlock(&ce->guc_state.lock);+ list_del_init(&cn->guc_id.link);+ ce->guc_id = cn->guc_id;+ clr_context_registered(cn);+ set_context_guc_id_invalid(cn);- set_context_guc_id_invalid(ce);- return guc_id;+ return 0; } else { return -EAGAIN; } }-static int assign_guc_id(struct intel_guc *guc, u16 *out)+static int assign_guc_id(struct intel_guc *guc, struct intel_context *ce) { int ret; lockdep_assert_held(&guc->submission_state.lock);+ GEM_BUG_ON(intel_context_is_child(ce));- ret = new_guc_id(guc);+ ret = new_guc_id(guc, ce); if (unlikely(ret < 0)) {- ret = steal_guc_id(guc);+ if (intel_context_is_parent(ce))+ return -ENOSPC;++ ret = steal_guc_id(guc, ce); if (ret < 0) return ret; }- *out = ret;+ if (intel_context_is_parent(ce)) {+ struct intel_context *child;+ int i = 1;++ for_each_child(ce, child)+ child->guc_id.id = ce->guc_id.id + i++;+ }+ return 0; }
@@ -1327,7 +1380,7 @@ static int pin_guc_id(struct intel_guc *guc, struct intel_context *ce) might_lock(&ce->guc_state.lock); if (context_guc_id_invalid(ce)) {- ret = assign_guc_id(guc, &ce->guc_id.id);+ ret = assign_guc_id(guc, ce); if (ret) goto out_unlock; ret = 1; /* Indidcates newly assigned guc_id */
@@ -1369,8 +1422,10 @@ static void unpin_guc_id(struct intel_guc *guc, struct intel_context *ce) unsigned long flags; GEM_BUG_ON(atomic_read(&ce->guc_id.ref) < 0);+ GEM_BUG_ON(intel_context_is_child(ce));- if (unlikely(context_guc_id_invalid(ce)))+ if (unlikely(context_guc_id_invalid(ce) ||+ intel_context_is_parent(ce))) return; spin_lock_irqsave(&guc->submission_state.lock, flags);
From: John Harrison <hidden> Date: 2021-09-15 20:23:29
On 9/15/2021 12:31, Matthew Brost wrote:
On Wed, Sep 15, 2021 at 12:21:35PM -0700, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Add multi-lrc context registration H2G. In addition a workqueue and
process descriptor are setup during multi-lrc context registration as
these data structures are needed for multi-lrc submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/intel_context_types.h | 12 ++
drivers/gpu/drm/i915/gt/intel_lrc.c | 5 +
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 2 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 109 +++++++++++++++++-
4 files changed, 126 insertions(+), 2 deletions(-)
@@ -232,8 +232,20 @@ struct intel_context {/** @parent: pointer to parent if child */structintel_context*parent;++/** @guc_wqi_head: head pointer in work queue */+u16guc_wqi_head;+/** @guc_wqi_tail: tail pointer in work queue */+u16guc_wqi_tail;+
These should be in the 'guc_state' sub-struct? Would be good to keep all GuC
specific content in one self-contained struct. Especially given the other
child/parent fields are no going to be guc_ prefixed any more.
Right now I have everything in guc_state protected by guc_state.lock,
these fields are not protected by this lock. IMO it is better to use a
different sub-structure for the parallel fields (even if anonymous).
Hmm, I still think it is bad to be scattering back-end specific fields amongst regular fields. The GuC patches include a whole bunch of complaints about execlist back-end specific stuff leaking through to the higher levels, we really shouldn't be guilty of doing the same with GuC if at all possible. At the very least, the GuC specific fields should be grouped together at the end of the struct rather than inter-mingled.
quoted
quoted
/** @guc_number_children: number of children if parent */ u8 guc_number_children;++ /**+ * @parent_page: page in context used by parent for work queue,
Maybe 'page in context record'? Otherwise, exactly what 'context' is meant
here? It isn't the 'struct intel_context'. The contetx record is saved as
'ce->state' / 'ce->lrc_reg_state', yes? Is it possible to link to either of
It is the page in ce->state / page minus LRC reg offset in
ce->lrg_reg_state. Will update the commit to make that clear.
quoted
those field? Probably not given that they don't appear to have any kerneldoc
description :(. Maybe add that in too :).
quoted
+ * work queue descriptor
Later on, it is described as 'process descriptor and work queue'. It would
be good to be consistent.
Can this not be derived from GUC_WQ_SIZE given that the two are
fundamentally linked? E.g. '#define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'?
Yes. I like 'define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'. Will change.
quoted
And maybe have a '#define WQ_TOTAL_SIZE PAGE_SIZE' and use that in all of
WQ_OFFSET, GUC_WQ_SIZE and the allocation itself in intel_lrc.c?
Also, the process descriptor is actually an array of descriptors sized by
the number of children? Or am I misunderstanding the code below? In so,
No, it is fixed size descriptor.
Yeah, I think I was getting confused between pdesc and desc in the code below.
I still think it would be a good idea to have everything explicitly named and the only mention of PAGE_SIZE is in the 'total size' definition.
John.
A later patch in the series uses the space of above the process
descriptor for insertation of preeemption points handshake. That does
depend on the number of children. I will add a COMPILE_BUG_ON for that
to ensure everything fits in the memory layout.
quoted
shouldn't there be a 'COMPILE_BUG_ON((MAX_ENGINE_INSTANCE *
sizeof(descriptor)) < (WQ_DESC_SIZE)' where WQ_DESC_SIZE is WQ_TOTAL_SIZE -
WQ_SIZE?
Where did the LRC_STATE_OFFSET come from? Is that built in to the
lrg_reg_state pointer itself? That needs to be documented somewhere.
In gt/intel_lrc.c (lrc_pin) ce->lrc_reg_state is assigned to
mmap(ce->state) + LRC_STATE_OFFSET. I can update the kerneldoc for that
field in this patch.
@@ -1423,6 +1487,7 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) struct guc_lrc_desc *desc; bool context_registered; intel_wakeref_t wakeref;+ struct intel_context *child; int ret = 0; GEM_BUG_ON(!engine->mask);
@@ -1448,6 +1513,42 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD; guc_context_policy_init(engine, desc);+ /*+ * Context is a parent, we need to register a process descriptor+ * describing a work queue and register all child contexts.
Technically, this should say 'If the context is a parent'. Or just move it
to be inside the if block.
Should this not be inherited from the ce? And same below. Or are we not
using this priority in that way?
Honestly I don't think this field is used or maybe doesn't even exist
anymore. I'll check the GuC code and likely delete this or if it is
still present I'll inherited this from the ce.
Matt
quoted
John.
quoted
+ pdesc->wq_status = WQ_STATUS_ACTIVE;++ for_each_child(ce, child) {+ desc = __get_lrc_desc(guc, child->guc_id.id);++ desc->engine_class =+ engine_class_to_guc_class(engine->class);+ desc->hw_context_desc = child->lrc.lrca;+ desc->priority = GUC_CLIENT_PRIORITY_KMD_NORMAL;+ desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD;+ guc_context_policy_init(engine, desc);+ }+ }+ /* * The context_lookup xarray is used to determine if the hardware * context is currently registered. There are two cases in which it
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-15 20:38:53
On Wed, Sep 15, 2021 at 01:23:19PM -0700, John Harrison wrote:
On 9/15/2021 12:31, Matthew Brost wrote:
quoted
On Wed, Sep 15, 2021 at 12:21:35PM -0700, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Add multi-lrc context registration H2G. In addition a workqueue and
process descriptor are setup during multi-lrc context registration as
these data structures are needed for multi-lrc submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/intel_context_types.h | 12 ++
drivers/gpu/drm/i915/gt/intel_lrc.c | 5 +
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 2 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 109 +++++++++++++++++-
4 files changed, 126 insertions(+), 2 deletions(-)
@@ -232,8 +232,20 @@ struct intel_context {/** @parent: pointer to parent if child */structintel_context*parent;++/** @guc_wqi_head: head pointer in work queue */+u16guc_wqi_head;+/** @guc_wqi_tail: tail pointer in work queue */+u16guc_wqi_tail;+
These should be in the 'guc_state' sub-struct? Would be good to keep all GuC
specific content in one self-contained struct. Especially given the other
child/parent fields are no going to be guc_ prefixed any more.
Right now I have everything in guc_state protected by guc_state.lock,
these fields are not protected by this lock. IMO it is better to use a
different sub-structure for the parallel fields (even if anonymous).
Hmm, I still think it is bad to be scattering back-end specific fields
amongst regular fields. The GuC patches include a whole bunch of complaints
about execlist back-end specific stuff leaking through to the higher levels,
we really shouldn't be guilty of doing the same with GuC if at all possible.
At the very least, the GuC specific fields should be grouped together at the
end of the struct rather than inter-mingled.
How 2 different sub-structures - parallel (shared) & guc_parallel (guc specific)?
quoted
quoted
quoted
/** @guc_number_children: number of children if parent */ u8 guc_number_children;++ /**+ * @parent_page: page in context used by parent for work queue,
Maybe 'page in context record'? Otherwise, exactly what 'context' is meant
here? It isn't the 'struct intel_context'. The contetx record is saved as
'ce->state' / 'ce->lrc_reg_state', yes? Is it possible to link to either of
It is the page in ce->state / page minus LRC reg offset in
ce->lrg_reg_state. Will update the commit to make that clear.
quoted
those field? Probably not given that they don't appear to have any kerneldoc
description :(. Maybe add that in too :).
quoted
+ * work queue descriptor
Later on, it is described as 'process descriptor and work queue'. It would
be good to be consistent.
Can this not be derived from GUC_WQ_SIZE given that the two are
fundamentally linked? E.g. '#define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'?
Yes. I like 'define WQ_OFFSET (PAGE_SIZE - GUC_WQ_SIZE)'. Will change.
quoted
And maybe have a '#define WQ_TOTAL_SIZE PAGE_SIZE' and use that in all of
WQ_OFFSET, GUC_WQ_SIZE and the allocation itself in intel_lrc.c?
Also, the process descriptor is actually an array of descriptors sized by
the number of children? Or am I misunderstanding the code below? In so,
No, it is fixed size descriptor.
Yeah, I think I was getting confused between pdesc and desc in the code
below.
I still think it would be a good idea to have everything explicitly named
and the only mention of PAGE_SIZE is in the 'total size' definition.
#define PARENT_SCRATCH_SIZE PAGE_SIZE?
Matt
John.
quoted
A later patch in the series uses the space of above the process
descriptor for insertation of preeemption points handshake. That does
depend on the number of children. I will add a COMPILE_BUG_ON for that
to ensure everything fits in the memory layout.
quoted
shouldn't there be a 'COMPILE_BUG_ON((MAX_ENGINE_INSTANCE *
sizeof(descriptor)) < (WQ_DESC_SIZE)' where WQ_DESC_SIZE is WQ_TOTAL_SIZE -
WQ_SIZE?
Where did the LRC_STATE_OFFSET come from? Is that built in to the
lrg_reg_state pointer itself? That needs to be documented somewhere.
In gt/intel_lrc.c (lrc_pin) ce->lrc_reg_state is assigned to
mmap(ce->state) + LRC_STATE_OFFSET. I can update the kerneldoc for that
field in this patch.
@@ -1423,6 +1487,7 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) struct guc_lrc_desc *desc; bool context_registered; intel_wakeref_t wakeref;+ struct intel_context *child; int ret = 0; GEM_BUG_ON(!engine->mask);
@@ -1448,6 +1513,42 @@ static int guc_lrc_desc_pin(struct intel_context *ce, bool loop) desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD; guc_context_policy_init(engine, desc);+ /*+ * Context is a parent, we need to register a process descriptor+ * describing a work queue and register all child contexts.
Technically, this should say 'If the context is a parent'. Or just move it
to be inside the if block.
Should this not be inherited from the ce? And same below. Or are we not
using this priority in that way?
Honestly I don't think this field is used or maybe doesn't even exist
anymore. I'll check the GuC code and likely delete this or if it is
still present I'll inherited this from the ce.
Matt
quoted
John.
quoted
+ pdesc->wq_status = WQ_STATUS_ACTIVE;++ for_each_child(ce, child) {+ desc = __get_lrc_desc(guc, child->guc_id.id);++ desc->engine_class =+ engine_class_to_guc_class(engine->class);+ desc->hw_context_desc = child->lrc.lrca;+ desc->priority = GUC_CLIENT_PRIORITY_KMD_NORMAL;+ desc->context_flags = CONTEXT_REGISTRATION_FLAG_KMD;+ guc_context_policy_init(engine, desc);+ }+ }+ /* * The context_lookup xarray is used to determine if the hardware * context is currently registered. There are two cases in which it
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-15 21:00:37
On Wed, Sep 15, 2021 at 01:04:45PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Assign contexts in parent-child relationship consecutive guc_ids. This
is accomplished by partitioning guc_id space between ones that need to
be consecutive (1/16 available guc_ids) and ones that do not (15/16 of
available guc_ids). The consecutive search is implemented via the bitmap
API.
This is a precursor to the full GuC multi-lrc implementation but aligns
to how GuC mutli-lrc interface is defined - guc_ids must be consecutive
when using the GuC multi-lrc interface.
v2:
(Daniel Vetter)
- Explictly state why we assign consecutive guc_ids
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/uc/intel_guc.h | 6 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 107 +++++++++++++-----
2 files changed, 86 insertions(+), 27 deletions(-)
@@ -61,9 +61,13 @@ struct intel_guc {*/spinlock_tlock;/**-*@guc_ids:usedtoallocatenewguc_ids+*@guc_ids:usedtoallocatenewguc_ids,single-lrc*/structidaguc_ids;+/**+*@guc_ids_bitmap:usedtoallocatenewguc_ids,multi-lrc+*/+unsignedlong*guc_ids_bitmap;/** @num_guc_ids: number of guc_ids that can be used */u32num_guc_ids;/** @max_guc_ids: max number of guc_ids that can be used */
The 'hence' clause seems to be attached to the wrong reason. The id space is
partition because of the contiguous vs random requirements of multi vs
single LRC, not because a different allocator is used in one partion vs the
other.
Kinda? The reason I partitioned it because to algorithms are different,
we could a unified space with a single algorithm, right? It was just
easier split the space and use 2 already existing data structures rather
cook up an algorithm in a unified space. There isn't a requirement from
the GuC that the space is partitioned, the only requirement is multi-lrc
IDs are contiguous. All this being said, I think comment is correct.
quoted
+ * partition the guc_id space. We believe the number of multi-lrc contexts in+ * use should be low and 1/16 should be sufficient. Minimum of 32 guc_ids for+ * multi-lrc.+ */+#define NUMBER_MULTI_LRC_GUC_ID(guc) \+ ((guc)->submission_state.num_guc_ids / 16 > 32 ? \+ (guc)->submission_state.num_guc_ids / 16 : 32)+ /* * Below is a set of functions which control the GuC scheduling state which * require a lock.
As far as I can tell, the only actual change to this function (beyond
'ce_in->id = id' vs 'return id' and adding anti-family asserts) is that this
spinlock was dropped. However, I'm not seeing any replacement for it or any
comment about why the spinlock is no longer necessary.
Good catch, the lock shouldn't be dropped.
Matt
John.
quoted
- clr_context_registered(ce);- spin_unlock(&ce->guc_state.lock);+ list_del_init(&cn->guc_id.link);+ ce->guc_id = cn->guc_id;+ clr_context_registered(cn);+ set_context_guc_id_invalid(cn);- set_context_guc_id_invalid(ce);- return guc_id;+ return 0; } else { return -EAGAIN; } }-static int assign_guc_id(struct intel_guc *guc, u16 *out)+static int assign_guc_id(struct intel_guc *guc, struct intel_context *ce) { int ret; lockdep_assert_held(&guc->submission_state.lock);+ GEM_BUG_ON(intel_context_is_child(ce));- ret = new_guc_id(guc);+ ret = new_guc_id(guc, ce); if (unlikely(ret < 0)) {- ret = steal_guc_id(guc);+ if (intel_context_is_parent(ce))+ return -ENOSPC;++ ret = steal_guc_id(guc, ce); if (ret < 0) return ret; }- *out = ret;+ if (intel_context_is_parent(ce)) {+ struct intel_context *child;+ int i = 1;++ for_each_child(ce, child)+ child->guc_id.id = ce->guc_id.id + i++;+ }+ return 0; }
@@ -1327,7 +1380,7 @@ static int pin_guc_id(struct intel_guc *guc, struct intel_context *ce) might_lock(&ce->guc_state.lock); if (context_guc_id_invalid(ce)) {- ret = assign_guc_id(guc, &ce->guc_id.id);+ ret = assign_guc_id(guc, ce); if (ret) goto out_unlock; ret = 1; /* Indidcates newly assigned guc_id */
@@ -1369,8 +1422,10 @@ static void unpin_guc_id(struct intel_guc *guc, struct intel_context *ce) unsigned long flags; GEM_BUG_ON(atomic_read(&ce->guc_id.ref) < 0);+ GEM_BUG_ON(intel_context_is_child(ce));- if (unlikely(context_guc_id_invalid(ce)))+ if (unlikely(context_guc_id_invalid(ce) ||+ intel_context_is_parent(ce))) return; spin_lock_irqsave(&guc->submission_state.lock, flags);
On Wed, Sep 15, 2021 at 09:24:15AM +0100, Tvrtko Ursulin wrote:
quoted
On 14/09/2021 19:04, Matthew Brost wrote:
quoted
On Tue, Sep 14, 2021 at 09:34:08AM +0100, Tvrtko Ursulin wrote:
quoted
8<
quoted
quoted
Today we have:
for_each intel_engines: // intel_engines is a flat list of all engines
intel_engine_setup()
You propose to change it to:
for_each engine_class:
for 0..max_global_engine_instance:
for_each intel_engines:
skip engine not present
skip class not matching
count logical instance
for_each intel_engines:
skip engine not present
skip wrong class
intel_engine_setup()
I propose:
// Leave as is:
for_each intel_engines:
intel_engine_setup()
// Add:
for_each engine_class:
logical = 0
for_each gt->engine_class[class]:
skip engine not present
engine->logical_instance = logical++
When code which actually needs a preturbed "map" arrives you add that in to
this second loop.
See above, why introduce an algorithm that doesn't work for future parts
+ future patches are land imminently? It makes zero sense whatsoever.
With your proposal we would literally land code to just throw it away a
couple of months from now + break patches we intend to land soon. This
It sure works, it just walks the per class list instead of walking the flat
list skipping one class at the time.
Just add the map based transformation to the second pass later, when it
becomes required.
I can flatten the algorithm if that helps alleviate your concerns but
with that being said, I've played around this locally and IMO makes the
code way more ugly. Sure it eliminates some iterations of the loop but
who really cares about that in a one time setup function?
quoted
quoted
algorithm works and has no reason whatsoever to be optimal as it a one
time setup call. I really don't understand why we are still talking
about this paint color.
I don't think bike shedding is not an appropriate term when complaint is how
proposed algorithm is needlessly complicated.
Are you just ignoring the fact that the algorithm (map) is needed in
pending patches? IMO it is more complicated to write throw away code
when the proper algorithm is already written. If the logical mapping was
straight forward on all platforms as the ones currently upstream I would
100% agree with your suggestion, but it isn't on unembargoed platforms
eminently going upstream. The algorithm I have works for the current
platforms + the pending platforms. IMO is 100% acceptable to merge
something looking towards a known future.
FWIW my 2c is that unused bits detract from review. And my gut feeling still is that code can be written in a simpler way and that the map transform can still plug in easily on top in a later series.
I said FWIW since even if I am right you can still view my comments as external/community inputs at this point and proceed however you wish.
Regards,
Tvrtko
From: John Harrison <hidden> Date: 2021-09-20 21:49:13
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
Implement multi-lrc submission via a single workqueue entry and single
H2G. The workqueue entry contains an updated tail value for each
request, of all the contexts in the multi-lrc submission, and updates
these values simultaneously. As such, the tasklet and bypass path have
been updated to coalesce requests into a single submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/uc/intel_guc.c | 21 ++
drivers/gpu/drm/i915/gt/uc/intel_guc.h | 8 +
drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 24 +-
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 6 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 312 +++++++++++++++---
drivers/gpu/drm/i915/i915_request.h | 8 +
6 files changed, 317 insertions(+), 62 deletions(-)
Granted, this patch is just moving code from one file to another not changing it. However, I think it would be worth adding a blank line in here. Otherwise the 'this register' comment below can be confusingly read as referring to the send_regs.fw_domain entry above.
And maybe add a comment why it is a bug for the send_regs value to be set? I'm not seeing any obvious connection between it and the reset of this code.
quoted hunk
+ /*+ * This register is used by the i915 and GuC for MMIO based+ * communication. Once we are in this code CTBs are the only+ * method the i915 uses to communicate with the GuC so it is+ * safe to write to this register (a value of 0 is NOP for MMIO+ * communication). If we ever start mixing CTBs and MMIOs a new+ * register will have to be chosen.+ */+ intel_uncore_write_fw(gt->uncore, GEN11_SOFT_SCRATCH(0), 0);+ } else {+ /* wmb() sufficient for a barrier if in smem */+ wmb();+ }+}
@@ -37,6 +37,12 @@ struct intel_guc {/* Global engine used to submit requests to GuC */structi915_sched_engine*sched_engine;structi915_request*stalled_request;+enum{+STALL_NONE,+STALL_REGISTER_CONTEXT,+STALL_MOVE_LRC_TAIL,+STALL_ADD_REQUEST,+}submission_stall_reason;/* intel_guc_recv interrupt related state */spinlock_tirq_lock;
@@ -377,28 +377,6 @@ static u32 ct_get_next_fence(struct intel_guc_ct *ct)return++ct->requests.last_fence;}-staticvoidwrite_barrier(structintel_guc_ct*ct)-{-structintel_guc*guc=ct_to_guc(ct);-structintel_gt*gt=guc_to_gt(guc);--if(i915_gem_object_is_lmem(guc->ct.vma->obj)){-GEM_BUG_ON(guc->send_regs.fw_domains);-/*-*Thisregisterisusedbythei915andGuCforMMIObased-*communication.OnceweareinthiscodeCTBsaretheonly-*methodthei915usestocommunicatewiththeGuCsoitis-*safetowritetothisregister(avalueof0isNOPforMMIO-*communication).IfweeverstartmixingCTBsandMMIOsanew-*registerwillhavetobechosen.-*/-intel_uncore_write_fw(gt->uncore,GEN11_SOFT_SCRATCH(0),0);-}else{-/* wmb() sufficient for a barrier if in smem */-wmb();-}-}-staticintct_write(structintel_guc_ct*ct,constu32*action,u32len/* in dwords */,
@@ -468,7 +446,7 @@ static int ct_write(struct intel_guc_ct *ct,*makesureH2GbufferupdateandLRCtailupdate(ifthistriggeringa*submission)arevisiblebeforeupdatingthedescriptortail*/-write_barrier(ct);+intel_guc_write_barrier(ct_to_guc(ct));/* update local copies */ctb->tail=tail;
Presumably all of these API changes are not actually new? They really came in with the reset of the v40 re-write? It's just that this is the first time we are using them and therefore need to finally update the defines?
@@ -547,10 +570,10 @@ int intel_guc_wait_for_idle(struct intel_guc *guc, long timeout)staticintguc_lrc_desc_pin(structintel_context*ce,boolloop);-staticintguc_add_request(structintel_guc*guc,structi915_request*rq)+staticint__guc_add_request(structintel_guc*guc,structi915_request*rq){interr=0;-structintel_context*ce=rq->context;+structintel_context*ce=request_to_scheduling_context(rq);u32action[3];intlen=0;u32g2h_len_dw=0;
Would be better to say '|| is_parent(ce)' rather than blocked? The reason for reason for claiming enabled when not is because it's a multi-LRC parent, right? Or can there be a parent that is neither enabled nor blocked for which we don't want to do the processing? But why would that make sense/be possible?
quoted hunk
if (!enabled) {
action[len++] = INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
@@ -609,6 +623,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) trace_intel_context_sched_enable(ce); atomic_inc(&guc->outstanding_submission_g2h); set_context_enabled(ce);++ /*+ * Without multi-lrc KMD does the submission step (moving the+ * lrc tail) so enabling scheduling is sufficient to submit the+ * context. This isn't the case in multi-lrc submission as the+ * GuC needs to move the tails, hence the need for another H2G+ * to submit a multi-lrc context after enabling scheduling.+ */+ if (intel_context_is_parent(ce)) {+ action[0] = INTEL_GUC_ACTION_SCHED_CONTEXT;+ err = intel_guc_send_nb(guc, action, len - 1, 0);+ } } else if (!enabled) { clr_context_pending_enable(ce); intel_context_put(ce);
@@ -621,6 +647,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) return err; }+static int guc_add_request(struct intel_guc *guc, struct i915_request *rq)+{+ int ret = __guc_add_request(guc, rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request= rq;+ guc->submission_stall_reason = STALL_ADD_REQUEST;+ }++ return ret;+}+ static void guc_set_lrc_tail(struct i915_request *rq) { rq->context->lrc_reg_state[CTX_RING_TAIL] =
This should have a BUG_ON check that the requested size fits within the WQ_LEN field?
Indeed, would be better to use the FIELD macros as they do that kind of thing for you.
quoted hunk
+ ce->guc_wqi_tail = 0;++ return 0;+}++static int __guc_wq_item_append(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ struct intel_context *child;+ struct guc_process_desc *desc = __get_process_desc(ce);+ unsigned int wqi_size = (ce->guc_number_children + 4) *+ sizeof(u32);+ u32 *wqi;+ int ret;++ /* Ensure context is in correct state updating work queue */+ GEM_BUG_ON(!atomic_read(&ce->guc_id.ref));+ GEM_BUG_ON(context_guc_id_invalid(ce));+ GEM_BUG_ON(context_wait_for_deregister_to_register(ce));+ GEM_BUG_ON(!lrc_desc_registered(ce_to_guc(ce), ce->guc_id.id));++ /* Insert NOOP if this work queue item will wrap the tail pointer. */+ if (wqi_size > wq_space_until_wrap(ce)) {+ ret = guc_wq_noop_append(ce);+ if (ret)+ return ret;+ }++ wqi = get_wq_pointer(desc, ce, wqi_size);+ if (!wqi)+ return -EBUSY;++ *wqi++ = WQ_TYPE_MULTI_LRC |+ ((wqi_size / sizeof(u32) - 1) << WQ_LEN_SHIFT);+ *wqi++ = ce->lrc.lrca;+ *wqi++ = (ce->guc_id.id << WQ_GUC_ID_SHIFT) |+ ((ce->ring->tail / sizeof(u64)) << WQ_RING_TAIL_SHIFT);
As above, would be better to use FIELD macros instead of manual shifting.
John.
quoted hunk
+ *wqi++ = 0; /* fence_id */+ for_each_child(ce, child)+ *wqi++ = child->ring->tail / sizeof(u64);++ write_wqi(desc, ce, wqi_size);++ return 0;+}++static int guc_wq_item_append(struct intel_guc *guc,+ struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ int ret = 0;++ if (likely(!intel_context_is_banned(ce))) {+ ret = __guc_wq_item_append(rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request = rq;+ guc->submission_stall_reason = STALL_MOVE_LRC_TAIL;+ }+ }++ return ret;+}++static bool multi_lrc_submit(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);++ intel_ring_set_tail(rq->ring, rq->tail);++ /*+ * We expect the front end (execbuf IOCTL) to set this flag on the last+ * request generated from a multi-BB submission. This indicates to the+ * backend (GuC interface) that we should submit this context thus+ * submitting all the requests generated in parallel.+ */+ return test_bit(I915_FENCE_FLAG_SUBMIT_PARALLEL, &rq->fence.flags) ||+ intel_context_is_banned(ce);+}+ static int guc_dequeue_one_context(struct intel_guc *guc) { struct i915_sched_engine * const sched_engine = guc->sched_engine;
@@ -645,7 +804,17 @@ static int guc_dequeue_one_context(struct intel_guc *guc) if (guc->stalled_request) { submit = true; last = guc->stalled_request;- goto resubmit;++ switch (guc->submission_stall_reason) {+ case STALL_REGISTER_CONTEXT:+ goto register_context;+ case STALL_MOVE_LRC_TAIL:+ goto move_lrc_tail;+ case STALL_ADD_REQUEST:+ goto add_request;+ default:+ MISSING_CASE(guc->submission_stall_reason);+ } } while ((rb = rb_first_cached(&sched_engine->queue))) {
@@ -1247,8 +1483,7 @@ static void guc_submit_request(struct i915_request *rq) /* Will be called from irq-context when using foreign fences. */ spin_lock_irqsave(&sched_engine->lock, flags);- if (submission_disabled(guc) || guc->stalled_request ||- !i915_sched_engine_is_empty(sched_engine))+ if (need_tasklet(guc, rq)) queue_request(sched_engine, rq, rq_prio(rq)); else if (guc_bypass_tasklet_submit(guc, rq) == -EBUSY) tasklet_hi_schedule(&sched_engine->tasklet);
From: John Harrison <hidden> Date: 2021-09-20 21:57:36
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
The GuC must receive requests in the order submitted for contexts in a
parent-child relationship to function correctly. To ensure this, insert
a submit fence between the current request and last request submitted
for requests / contexts in a parent child relationship. This is
conceptually similar to a single timeline.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Cc: John Harrison <redacted>
---
drivers/gpu/drm/i915/gt/intel_context.h | 5 +
drivers/gpu/drm/i915/gt/intel_context_types.h | 7 +
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 5 +-
drivers/gpu/drm/i915/i915_request.c | 120 ++++++++++++++----
4 files changed, 109 insertions(+), 28 deletions(-)
From: John Harrison <hidden> Date: 2021-09-20 22:44:27
On 8/20/2021 15:44, Matthew Brost wrote:
Update context and full GPU reset to work with multi-lrc. The idea is
parent context tracks all the active requests inflight for itself and
its' children. The parent context owns the reset replaying / canceling
Should this not check the parent as well/instead?
And to be clear, this can be called on regular contexts (where ce == parent) and on both the parent or child contexts of multi-LRC contexts (where ce may or may not match parent)?
quoted hunk
- spin_lock_irqsave(&ce->guc_state.lock, flags);- list_for_each_entry_reverse(rq, &ce->guc_state.requests,+ spin_lock_irqsave(&parent->guc_state.lock, flags);+ list_for_each_entry_reverse(rq, &parent->guc_state.requests, sched.link) {- if (i915_request_completed(rq))+ if (i915_request_completed(rq) && rq->context == ce)
'rq->context == ce' means:
1. single-LRC context, rq is owned by ce
2. multi-LRC context, ce is child, rq really belongs to ce but is being
tracked by parent
3. multi-LRC context, ce is parent, rq really is owned by ce
So when 'rq->ce != ce', it means that the request is owned by a different child to 'ce' but within the same multi-LRC group. So we want to ignore that request and keep searching until we find one that is really owned by the target ce?
quoted hunk
break;- active = rq;+ active = (rq->context == ce) ? rq : active;
Would be clearer to say 'if(rq->ce != ce) continue;' and leave 'active = rq;' alone?
And again, the intention is to ignore requests that are owned by other members of the same multi-LRC group?
Would be good to add some documentation to this function to explain the above (assuming my description is correct?).
If this is a child context, does it not need to pull the child count from the parent? Likewise the list/link pointers below? Or does each child context have a full list of its siblings + parent?
From: John Harrison <hidden> Date: 2021-09-20 22:49:08
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
Display the workqueue status in debugfs for GuC contexts that are in
parent-child relationship.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 51 ++++++++++++++-----
1 file changed, 37 insertions(+), 14 deletions(-)
There should be some indication that this is a child context and/or how many children there are. Otherwise how does the reader differentiation between the list of child contexts and the next parent/single context?
John.
From: John Harrison <hidden> Date: 2021-09-20 22:57:18
On 8/20/2021 15:44, Matthew Brost wrote:
quoted hunk
Set number of engines before attempting to create contexts so the
function free_engines can clean up properly.
Fixes: d4433c7600f7 ("drm/i915/gem: Use the proto-context to handle create parameters (v5)")
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Cc: <redacted>
---
drivers/gpu/drm/i915/gem/i915_gem_context.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
This can return null when out of memory. There needs to be an early exit check before dereferencing a null pointer. Not sure if that is a worse bug or not than leaking memory! Either way, it would be good to fix that too.
John.
quoted hunk
+ e->num_engines = num_engines; for (n = 0; n < num_engines; n++) { struct intel_context *ce; int ret;
Is this link still not available?
Also, see 'kernel test robot' emails saying that sparse is complaining about something I don't understand but presumably needs to be fixed.
@@ -78,6 +78,9 @@ enum i915_gem_engine_type {/** @I915_GEM_ENGINE_TYPE_BALANCED: A load-balanced engine set */I915_GEM_ENGINE_TYPE_BALANCED,++/** @I915_GEM_ENGINE_TYPE_PARALLEL: A parallel engine set */+I915_GEM_ENGINE_TYPE_PARALLEL,};/**
@@ -108,6 +111,9 @@ struct i915_gem_proto_engine {/** @num_siblings: Number of balanced siblings */unsignedintnum_siblings;+/** @width: Width of each sibling */+unsignedintwidth;+/** @siblings: Balanced siblings */structintel_engine_cs**siblings;
@@ -3733,7 +3733,7 @@ static int nop_virtual_engine(struct intel_gt *gt,GEM_BUG_ON(!nctx||nctx>ARRAY_SIZE(ve));for(n=0;n<nctx;n++){-ve[n]=intel_engine_create_virtual(siblings,nsibling);+ve[n]=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve[n])){err=PTR_ERR(ve[n]);nctx=n;
@@ -3929,7 +3929,7 @@ static int mask_virtual_engine(struct intel_gt *gt,*restrictittoourdesiredenginewithinthevirtualengine.*/-ve=intel_engine_create_virtual(siblings,nsibling);+ve=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve)){err=PTR_ERR(ve);gotoout_close;
@@ -4060,7 +4060,7 @@ static int slicein_virtual_engine(struct intel_gt *gt,i915_request_add(rq);}-ce=intel_engine_create_virtual(siblings,nsibling);+ce=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ce)){err=PTR_ERR(ce);gotoout;
@@ -4112,7 +4112,7 @@ static int sliceout_virtual_engine(struct intel_gt *gt,/* XXX We do not handle oversubscription and fairness with normal rq */for(n=0;n<nsibling;n++){-ce=intel_engine_create_virtual(siblings,nsibling);+ce=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ce)){err=PTR_ERR(ce);gotoout;
@@ -4214,7 +4214,7 @@ static int preserved_virtual_engine(struct intel_gt *gt,if(err)gotoout_scratch;-ve=intel_engine_create_virtual(siblings,nsibling);+ve=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve)){err=PTR_ERR(ve);gotoout_scratch;
@@ -4354,7 +4354,7 @@ static int reset_virtual_engine(struct intel_gt *gt,if(igt_spinner_init(&spin,gt))return-ENOMEM;-ve=intel_engine_create_virtual(siblings,nsibling);+ve=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve)){err=PTR_ERR(ve);gotoout_spin;
@@ -2827,8 +2834,6 @@ static const struct intel_context_ops virtual_guc_context_ops = {.get_sibling=guc_virtual_get_sibling,};-/* Future patches will use this function */-__maybe_unusedstaticintguc_parent_context_pin(structintel_context*ce,void*vaddr){structintel_engine_cs*engine=guc_virtual_get_sibling(ce->engine,0);
@@ -2845,8 +2850,6 @@ static int guc_parent_context_pin(struct intel_context *ce, void *vaddr)return__guc_context_pin(ce,engine,vaddr);}-/* Future patches will use this function */-__maybe_unusedstaticintguc_child_context_pin(structintel_context*ce,void*vaddr){structintel_engine_cs*engine=guc_virtual_get_sibling(ce->engine,0);
@@ -2858,8 +2861,6 @@ static int guc_child_context_pin(struct intel_context *ce, void *vaddr)return__guc_context_pin(ce,engine,vaddr);}-/* Future patches will use this function */-__maybe_unusedstaticvoidguc_parent_context_unpin(structintel_context*ce){structintel_guc*guc=ce_to_guc(ce);
@@ -2875,8 +2876,6 @@ static void guc_parent_context_unpin(struct intel_context *ce)lrc_unpin(ce);}-/* Future patches will use this function */-__maybe_unusedstaticvoidguc_child_context_unpin(structintel_context*ce){GEM_BUG_ON(context_enabled(ce));
@@ -2887,8 +2886,6 @@ static void guc_child_context_unpin(struct intel_context *ce)lrc_unpin(ce);}-/* Future patches will use this function */-__maybe_unusedstaticvoidguc_child_context_post_unpin(structintel_context*ce){GEM_BUG_ON(!intel_context_is_child(ce));
+ * run these BBs. Once a slot is configured for N BBs only N BBs can be+ * submitted in each execbuf IOCTL and this is implicit behavior e.g. The user+ * doesn't tell the execbuf IOCTL there are N BBs, the execbuf IOCTL knows how+ * many BBs there are based on the slot's configuration. The N BBs are the last+ * N buffer objects or first N if I915_EXEC_BATCH_FIRST is set.+ *+ * The default placement behavior is to create implicit bonds between each+ * context if each context maps to more than 1 physical engine (e.g. context is+ * a virtual engine). Also we only allow contexts of same engine class and these+ * contexts must be in logically contiguous order. Examples of the placement+ * behavior described below. Lastly, the default is to not allow BBs to
behaviour described -> behaviour are described
+ * preempted mid BB rather insert coordinated preemption on all hardware
to preempted mid BB rather -> to be preempted mid-batch. Rather
coordinated preemption on -> coordinated preemption points on
+ * contexts between each set of BBs. Flags may be added in the future to change
may -> could - 'may' implies we are thinking about doing it (maybe just for fun or because we're bored), 'could' implies a user has to ask for the facility if they need it.
quoted hunk
+ * both of these default behaviors.+ *+ * Returns -EINVAL if hardware context placement configuration is invalid or if+ * the placement configuration isn't supported on the platform / submission+ * interface.+ * Returns -ENODEV if extension isn't supported on the platform / submission+ * interface.+ *+ * .. code-block:: none+ *+ * Example 1 pseudo code:+ * CS[X] = generic engine of same class, logical instance X+ * INVALID = I915_ENGINE_CLASS_INVALID, I915_ENGINE_CLASS_INVALID_NONE
I would put these two terminology explanations above the 'example 1' line given that they are generic to all examples.
quoted hunk
+ * set_engines(INVALID)+ * set_parallel(engine_index=0, width=2, num_siblings=1,+ * engines=CS[0],CS[1])+ *+ * Results in the following valid placement:+ * CS[0], CS[1]+ *+ * Example 2 pseudo code:+ * CS[X] = generic engine of same class, logical instance X+ * INVALID = I915_ENGINE_CLASS_INVALID, I915_ENGINE_CLASS_INVALID_NONE
And drop them from here.
quoted hunk
+ * set_engines(INVALID)+ * set_parallel(engine_index=0, width=2, num_siblings=2,+ * engines=CS[0],CS[2],CS[1],CS[3])+ *+ * Results in the following valid placements:+ * CS[0], CS[1]+ * CS[2], CS[3]+ *+ * This can also be thought of as 2 virtual engines described by 2-D array+ * in the engines the field with bonds placed between each index of the+ * virtual engines. e.g. CS[0] is bonded to CS[1], CS[2] is bonded to+ * CS[3].
I find this description just adds to the confusion. It doesn't help that the sentence is broken/unparsable - 'described by 2-D array in the engines the field with bonds'?
"This can be thought of as two virtual engines, each containing two engines thereby making a 2D array. However, there are bonds tying the entries together and placing restrictions on how they can be scheduled. Specifically, the scheduler can choose only vertical columns from the 2D array. That is, CS[0] is bonded to CS[1] and CS[2] to CS[3]. So if the scheduler wants to submit to CS[0], it must also choose CS[1] and vice versa. Same for CS[2] requires also using CS[3]."
Does that make sense?
+ * set_engines(INVALID)+ * set_parallel(engine_index=0, width=2, num_siblings=2,+ * engines=CS[0],CS[1],CS[1],CS[3])+ *+ * Results in the following valid and invalid placements:+ * CS[0], CS[1]+ * CS[1], CS[3] - Not logical contiguous, return -EINVAL
logical -> logically
quoted hunk
+ */+struct i915_context_engines_parallel_submit {+ /**+ * @base: base user extension.+ */+ struct i915_user_extension base;++ /**+ * @engine_index: slot for parallel engine+ */+ __u16 engine_index;++ /**+ * @width: number of contexts per parallel engine
Meaning number of engines in the virtual engine? As in, width = 3 means that the scheduler has a choice of three different engines to submit the one single batch buffer to?
quoted hunk
+ */+ __u16 width;++ /**+ * @num_siblings: number of siblings per context+ */+ __u16 num_siblings;
Meaning the number of engines which must run in parallel. As in, num_siblings = 2 means that there will be two batch buffers submitted to every execbuf IOCTL call and that both must execute concurrently on two separate engines?
John.
quoted hunk
++ /**+ * @mbz16: reserved for future use; must be zero+ */+ __u16 mbz16;++ /**+ * @flags: all undefined flags must be zero, currently not defined flags+ */+ __u64 flags;++ /**+ * @mbz64: reserved for future use; must be zero+ */+ __u64 mbz64[3];++ /**+ * @engines: 2-d array of engine instances to configure parallel engine+ *+ * length = width (i) * num_siblings (j)+ * index = j + i * num_siblings+ */+ struct i915_engine_class_instance engines[0];++} __packed;++#define I915_DEFINE_CONTEXT_ENGINES_PARALLEL_SUBMIT(name__, N__) struct { \+ struct i915_user_extension base; \+ __u16 engine_index; \+ __u16 width; \+ __u16 num_siblings; \+ __u16 mbz16; \+ __u64 flags; \+ __u64 mbz64[3]; \+ struct i915_engine_class_instance engines[N__]; \+} __attribute__((packed)) name__+ /** * DOC: Context Engine Map uAPI *
@@ -135,8 +135,8 @@ Add I915_CONTEXT_ENGINES_EXT_PARALLEL_SUBMIT and drm_i915_context_engines_parallel_submit to the uAPI to implement this extension.-..kernel-doc:: Documentation/gpu/rfc/i915_parallel_execbuf.h-:functions: drm_i915_context_engines_parallel_submit+..kernel-doc:: include/uapi/drm/i915_drm.h+:functions: i915_context_engines_parallel_submit Extend execbuf2 IOCTL to support submitting N BBs in a single IOCTL -------------------------------------------------------------------
Set number of engines before attempting to create contexts so the
function free_engines can clean up properly.
Fixes: d4433c7600f7 ("drm/i915/gem: Use the proto-context to handle create parameters (v5)")
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Cc: <redacted>
---
drivers/gpu/drm/i915/gem/i915_gem_context.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
This can return null when out of memory. There needs to be an early exit check before dereferencing a null pointer. Not sure if that is a worse bug or not than leaking memory! Either way, it would be good to fix that too.
Pull out from the series and send a fix standalone ASAP? Also suggest adding author and reviewer to cc for typically quicker turnaround time.
Regards,
Tvrtko
John.
quoted
+ e->num_engines = num_engines;
for (n = 0; n < num_engines; n++) {
struct intel_context *ce;
int ret;
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-21 19:18:17
On Mon, Sep 20, 2021 at 03:48:59PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Display the workqueue status in debugfs for GuC contexts that are in
parent-child relationship.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 51 ++++++++++++++-----
1 file changed, 37 insertions(+), 14 deletions(-)
There should be some indication that this is a child context and/or how many
children there are. Otherwise how does the reader differentiation between
the list of child contexts and the next parent/single context?
We don't log the priority info or work queue info for child contexts so
it is actually pretty easy to parse. That being said, I'll output the
number of children if the context is a parent.
Matt
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-21 19:33:17
On Mon, Sep 20, 2021 at 03:57:06PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Set number of engines before attempting to create contexts so the
function free_engines can clean up properly.
Fixes: d4433c7600f7 ("drm/i915/gem: Use the proto-context to handle create parameters (v5)")
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Cc: <redacted>
---
drivers/gpu/drm/i915/gem/i915_gem_context.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
This can return null when out of memory. There needs to be an early exit
check before dereferencing a null pointer. Not sure if that is a worse bug
or not than leaking memory! Either way, it would be good to fix that too.
Indeed there is another bug. Will fix that one too.
Matt
John.
quoted
+ e->num_engines = num_engines; for (n = 0; n < num_engines; n++) { struct intel_context *ce; int ret;
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-21 19:33:39
On Tue, Sep 21, 2021 at 03:49:53PM +0100, Tvrtko Ursulin wrote:
On 20/09/2021 23:57, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Set number of engines before attempting to create contexts so the
function free_engines can clean up properly.
Fixes: d4433c7600f7 ("drm/i915/gem: Use the proto-context to handle
create parameters (v5)")
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
Cc: <redacted>
---
drivers/gpu/drm/i915/gem/i915_gem_context.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
*user_engines(struct i915_gem_context *ctx,
unsigned int n;
e = alloc_engines(num_engines);
This can return null when out of memory. There needs to be an early exit
check before dereferencing a null pointer. Not sure if that is a worse
bug or not than leaking memory! Either way, it would be good to fix that
too.
Pull out from the series and send a fix standalone ASAP? Also suggest adding
Sure, will do.
Matt
author and reviewer to cc for typically quicker turnaround time.
Regards,
Tvrtko
quoted
John.
quoted
+ e->num_engines = num_engines;
for (n = 0; n < num_engines; n++) {
struct intel_context *ce;
int ret;
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-22 16:23:56
On Mon, Sep 20, 2021 at 03:44:18PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
Update context and full GPU reset to work with multi-lrc. The idea is
parent context tracks all the active requests inflight for itself and
its' children. The parent context owns the reset replaying / canceling
its' -> its
requests as needed.
Signed-off-by: Matthew Brost [off-list ref]
---
drivers/gpu/drm/i915/gt/intel_context.c | 11 ++--
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 63 +++++++++++++------
2 files changed, 51 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/i915/gt/intel_context.c b/drivers/gpu/drm/i915/gt/intel_context.c
index 00d1aee6d199..5615be32879c 100644
--- a/drivers/gpu/drm/i915/gt/intel_context.c
+++ b/drivers/gpu/drm/i915/gt/intel_context.c
@@ -528,20 +528,21 @@ struct i915_request *intel_context_create_request(struct intel_context *ce)
struct i915_request *intel_context_find_active_request(struct intel_context *ce)
{
+ struct intel_context *parent = intel_context_to_parent(ce);
struct i915_request *rq, *active = NULL;
unsigned long flags;
GEM_BUG_ON(!intel_engine_uses_guc(ce->engine));
Should this not check the parent as well/instead?
I don't think so. The 'ce' could be a not parallel context, a parent
context, or a child context.
And to be clear, this can be called on regular contexts (where ce == parent)
and on both the parent or child contexts of multi-LRC contexts (where ce may or
may not match parent)?
Right. The parent owns the parent->guc_state.lock/requests and search
that list for the first non-completed request that matches submitted
'ce'.
- spin_lock_irqsave(&ce->guc_state.lock, flags);
- list_for_each_entry_reverse(rq, &ce->guc_state.requests,
+ spin_lock_irqsave(&parent->guc_state.lock, flags);
+ list_for_each_entry_reverse(rq, &parent->guc_state.requests,
sched.link) {
- if (i915_request_completed(rq))
+ if (i915_request_completed(rq) && rq->context == ce)
'rq->context == ce' means:
1. single-LRC context, rq is owned by ce
2. multi-LRC context, ce is child, rq really belongs to ce but is being
tracked by parent
3. multi-LRC context, ce is parent, rq really is owned by ce
So when 'rq->ce != ce', it means that the request is owned by a different child
to 'ce' but within the same multi-LRC group. So we want to ignore that request
and keep searching until we find one that is really owned by the target ce?
All correct.
break;
- active = rq;
+ active = (rq->context == ce) ? rq : active;
Would be clearer to say 'if(rq->ce != ce) continue;' and leave 'active = rq;'
alone?
Yes, that is probably cleaner.
And again, the intention is to ignore requests that are owned by other members
of the same multi-LRC group?
Would be good to add some documentation to this function to explain the above
(assuming my description is correct?).
Will add a comment explaining this.
}
- spin_unlock_irqrestore(&ce->guc_state.lock, flags);
+ spin_unlock_irqrestore(&parent->guc_state.lock, flags);
return active;
}
diff --git a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c
index f0b60fecf253..e34e0ea9136a 100644
--- a/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c
+++ b/drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c
@@ -670,6 +670,11 @@ static int rq_prio(const struct i915_request *rq)
return rq->sched.attr.priority;
}
+static inline bool is_multi_lrc(struct intel_context *ce)
+{
+ return intel_context_is_parallel(ce);
+}
+
static bool is_multi_lrc_rq(struct i915_request *rq)
{
return intel_context_is_parallel(rq->context);
@@ -1179,10 +1184,13 @@ __unwind_incomplete_requests(struct intel_context *ce)
static void __guc_reset_context(struct intel_context *ce, bool stalled)
{
+ bool local_stalled;
struct i915_request *rq;
unsigned long flags;
u32 head;
+ int i, number_children = ce->guc_number_children;
If this is a child context, does it not need to pull the child count from the
parent? Likewise the list/link pointers below? Or does each child context have
a full list of its siblings + parent?
This function shouldn't be called by a child. Will add
GEM_BUG_ON(intel_context_is_child(ce)) to this function.
bool skip = false;
+ struct intel_context *parent = ce;
intel_context_get(ce);
@@ -1209,25 +1217,34 @@ static void __guc_reset_context(struct intel_context *ce, bool stalled)
if (unlikely(skip))
goto out_put;
- rq = intel_context_find_active_request(ce);
- if (!rq) {
- head = ce->ring->tail;
- stalled = false;
- goto out_replay;
- }
+ for (i = 0; i < number_children + 1; ++i) {
+ if (!intel_context_is_pinned(ce))
+ goto next_context;
+
+ local_stalled = false;
+ rq = intel_context_find_active_request(ce);
+ if (!rq) {
+ head = ce->ring->tail;
+ goto out_replay;
+ }
- if (!i915_request_started(rq))
- stalled = false;
+ GEM_BUG_ON(i915_active_is_idle(&ce->active));
+ head = intel_ring_wrap(ce->ring, rq->head);
- GEM_BUG_ON(i915_active_is_idle(&ce->active));
- head = intel_ring_wrap(ce->ring, rq->head);
- __i915_request_reset(rq, stalled);
+ if (i915_request_started(rq))
Why change the ordering of the started test versus the wrap/reset call? Is it
significant? Why is it now important to be reversed?
Off the top of my head I'm not sure what the ordering is changed. I'll
have to double check on this.
+ local_stalled = true;
+ __i915_request_reset(rq, local_stalled && stalled);
out_replay:
- guc_reset_state(ce, head, stalled);
- __unwind_incomplete_requests(ce);
+ guc_reset_state(ce, head, local_stalled && stalled);
+next_context:
+ if (i != number_children)
+ ce = list_next_entry(ce, guc_child_link);
Can this not be put in to the step clause of the for statement?
Maybe? Does list_next_entry blow up on that last entry? Idk? If not,
then yes this can be added to the loop. Will double check on this.
+ }
+
+ __unwind_incomplete_requests(parent);
out_put:
- intel_context_put(ce);
+ intel_context_put(parent);
As above, I think this function would benefit from some comments to explain
exactly what is being done and why.
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-22 16:31:30
On Mon, Sep 20, 2021 at 02:48:52PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Implement multi-lrc submission via a single workqueue entry and single
H2G. The workqueue entry contains an updated tail value for each
request, of all the contexts in the multi-lrc submission, and updates
these values simultaneously. As such, the tasklet and bypass path have
been updated to coalesce requests into a single submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/uc/intel_guc.c | 21 ++
drivers/gpu/drm/i915/gt/uc/intel_guc.h | 8 +
drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 24 +-
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 6 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 312 +++++++++++++++---
drivers/gpu/drm/i915/i915_request.h | 8 +
6 files changed, 317 insertions(+), 62 deletions(-)
Granted, this patch is just moving code from one file to another not
changing it. However, I think it would be worth adding a blank line in here.
Otherwise the 'this register' comment below can be confusingly read as
referring to the send_regs.fw_domain entry above.
And maybe add a comment why it is a bug for the send_regs value to be set?
I'm not seeing any obvious connection between it and the reset of this code.
Can add a blank line. I think the GEM_BUG_ON relates to being able to
use intel_uncore_write_fw vs intel_uncore_write. Can add comment.
quoted
+ /*+ * This register is used by the i915 and GuC for MMIO based+ * communication. Once we are in this code CTBs are the only+ * method the i915 uses to communicate with the GuC so it is+ * safe to write to this register (a value of 0 is NOP for MMIO+ * communication). If we ever start mixing CTBs and MMIOs a new+ * register will have to be chosen.+ */+ intel_uncore_write_fw(gt->uncore, GEN11_SOFT_SCRATCH(0), 0);+ } else {+ /* wmb() sufficient for a barrier if in smem */+ wmb();+ }+}
@@ -37,6 +37,12 @@ struct intel_guc {/* Global engine used to submit requests to GuC */structi915_sched_engine*sched_engine;structi915_request*stalled_request;+enum{+STALL_NONE,+STALL_REGISTER_CONTEXT,+STALL_MOVE_LRC_TAIL,+STALL_ADD_REQUEST,+}submission_stall_reason;/* intel_guc_recv interrupt related state */spinlock_tirq_lock;
@@ -377,28 +377,6 @@ static u32 ct_get_next_fence(struct intel_guc_ct *ct)return++ct->requests.last_fence;}-staticvoidwrite_barrier(structintel_guc_ct*ct)-{-structintel_guc*guc=ct_to_guc(ct);-structintel_gt*gt=guc_to_gt(guc);--if(i915_gem_object_is_lmem(guc->ct.vma->obj)){-GEM_BUG_ON(guc->send_regs.fw_domains);-/*-*Thisregisterisusedbythei915andGuCforMMIObased-*communication.OnceweareinthiscodeCTBsaretheonly-*methodthei915usestocommunicatewiththeGuCsoitis-*safetowritetothisregister(avalueof0isNOPforMMIO-*communication).IfweeverstartmixingCTBsandMMIOsanew-*registerwillhavetobechosen.-*/-intel_uncore_write_fw(gt->uncore,GEN11_SOFT_SCRATCH(0),0);-}else{-/* wmb() sufficient for a barrier if in smem */-wmb();-}-}-staticintct_write(structintel_guc_ct*ct,constu32*action,u32len/* in dwords */,
@@ -468,7 +446,7 @@ static int ct_write(struct intel_guc_ct *ct,*makesureH2GbufferupdateandLRCtailupdate(ifthistriggeringa*submission)arevisiblebeforeupdatingthedescriptortail*/-write_barrier(ct);+intel_guc_write_barrier(ct_to_guc(ct));/* update local copies */ctb->tail=tail;
Presumably all of these API changes are not actually new? They really came
in with the reset of the v40 re-write? It's just that this is the first time
we are using them and therefore need to finally update the defines?
@@ -547,10 +570,10 @@ int intel_guc_wait_for_idle(struct intel_guc *guc, long timeout)staticintguc_lrc_desc_pin(structintel_context*ce,boolloop);-staticintguc_add_request(structintel_guc*guc,structi915_request*rq)+staticint__guc_add_request(structintel_guc*guc,structi915_request*rq){interr=0;-structintel_context*ce=rq->context;+structintel_context*ce=request_to_scheduling_context(rq);u32action[3];intlen=0;u32g2h_len_dw=0;
Would be better to say '|| is_parent(ce)' rather than blocked? The reason
for reason for claiming enabled when not is because it's a multi-LRC parent,
right? Or can there be a parent that is neither enabled nor blocked for
which we don't want to do the processing? But why would that make sense/be
possible?
No. If it is parent and blocked we want to submit the enable but not
enable submission. In the non-multi-lrc case the submit has already been
done by the i915 (moving lrc tail).
quoted
if (!enabled) {
action[len++] = INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
@@ -609,6 +623,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) trace_intel_context_sched_enable(ce); atomic_inc(&guc->outstanding_submission_g2h); set_context_enabled(ce);++ /*+ * Without multi-lrc KMD does the submission step (moving the+ * lrc tail) so enabling scheduling is sufficient to submit the+ * context. This isn't the case in multi-lrc submission as the+ * GuC needs to move the tails, hence the need for another H2G+ * to submit a multi-lrc context after enabling scheduling.+ */+ if (intel_context_is_parent(ce)) {+ action[0] = INTEL_GUC_ACTION_SCHED_CONTEXT;+ err = intel_guc_send_nb(guc, action, len - 1, 0);+ } } else if (!enabled) { clr_context_pending_enable(ce); intel_context_put(ce);
@@ -621,6 +647,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) return err; }+static int guc_add_request(struct intel_guc *guc, struct i915_request *rq)+{+ int ret = __guc_add_request(guc, rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request= rq;+ guc->submission_stall_reason = STALL_ADD_REQUEST;+ }++ return ret;+}+ static void guc_set_lrc_tail(struct i915_request *rq) { rq->context->lrc_reg_state[CTX_RING_TAIL] =
This should have a BUG_ON check that the requested size fits within the
WQ_LEN field?
I could add that.
Indeed, would be better to use the FIELD macros as they do that kind of
thing for you.
Yes, they do. I forget how they work, will figure this out and use the
macros.
quoted
+ ce->guc_wqi_tail = 0;++ return 0;+}++static int __guc_wq_item_append(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ struct intel_context *child;+ struct guc_process_desc *desc = __get_process_desc(ce);+ unsigned int wqi_size = (ce->guc_number_children + 4) *+ sizeof(u32);+ u32 *wqi;+ int ret;++ /* Ensure context is in correct state updating work queue */+ GEM_BUG_ON(!atomic_read(&ce->guc_id.ref));+ GEM_BUG_ON(context_guc_id_invalid(ce));+ GEM_BUG_ON(context_wait_for_deregister_to_register(ce));+ GEM_BUG_ON(!lrc_desc_registered(ce_to_guc(ce), ce->guc_id.id));++ /* Insert NOOP if this work queue item will wrap the tail pointer. */+ if (wqi_size > wq_space_until_wrap(ce)) {+ ret = guc_wq_noop_append(ce);+ if (ret)+ return ret;+ }++ wqi = get_wq_pointer(desc, ce, wqi_size);+ if (!wqi)+ return -EBUSY;++ *wqi++ = WQ_TYPE_MULTI_LRC |+ ((wqi_size / sizeof(u32) - 1) << WQ_LEN_SHIFT);+ *wqi++ = ce->lrc.lrca;+ *wqi++ = (ce->guc_id.id << WQ_GUC_ID_SHIFT) |+ ((ce->ring->tail / sizeof(u64)) << WQ_RING_TAIL_SHIFT);
As above, would be better to use FIELD macros instead of manual shifting.
Will do.
Matt
John.
quoted
+ *wqi++ = 0; /* fence_id */+ for_each_child(ce, child)+ *wqi++ = child->ring->tail / sizeof(u64);++ write_wqi(desc, ce, wqi_size);++ return 0;+}++static int guc_wq_item_append(struct intel_guc *guc,+ struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ int ret = 0;++ if (likely(!intel_context_is_banned(ce))) {+ ret = __guc_wq_item_append(rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request = rq;+ guc->submission_stall_reason = STALL_MOVE_LRC_TAIL;+ }+ }++ return ret;+}++static bool multi_lrc_submit(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);++ intel_ring_set_tail(rq->ring, rq->tail);++ /*+ * We expect the front end (execbuf IOCTL) to set this flag on the last+ * request generated from a multi-BB submission. This indicates to the+ * backend (GuC interface) that we should submit this context thus+ * submitting all the requests generated in parallel.+ */+ return test_bit(I915_FENCE_FLAG_SUBMIT_PARALLEL, &rq->fence.flags) ||+ intel_context_is_banned(ce);+}+ static int guc_dequeue_one_context(struct intel_guc *guc) { struct i915_sched_engine * const sched_engine = guc->sched_engine;
@@ -645,7 +804,17 @@ static int guc_dequeue_one_context(struct intel_guc *guc) if (guc->stalled_request) { submit = true; last = guc->stalled_request;- goto resubmit;++ switch (guc->submission_stall_reason) {+ case STALL_REGISTER_CONTEXT:+ goto register_context;+ case STALL_MOVE_LRC_TAIL:+ goto move_lrc_tail;+ case STALL_ADD_REQUEST:+ goto add_request;+ default:+ MISSING_CASE(guc->submission_stall_reason);+ } } while ((rb = rb_first_cached(&sched_engine->queue))) {
@@ -1247,8 +1483,7 @@ static void guc_submit_request(struct i915_request *rq) /* Will be called from irq-context when using foreign fences. */ spin_lock_irqsave(&sched_engine->lock, flags);- if (submission_disabled(guc) || guc->stalled_request ||- !i915_sched_engine_is_empty(sched_engine))+ if (need_tasklet(guc, rq)) queue_request(sched_engine, rq, rq_prio(rq)); else if (guc_bypass_tasklet_submit(guc, rq) == -EBUSY) tasklet_hi_schedule(&sched_engine->tasklet);
This is per Daniel Vetters suggestion in previous rev. Basically to
simplify the parallel submit implementation pin the contexts are
creation time and unpin when the contexts get destroyed. It removes
complexity from gt/intel_context.c, the execbuf IOCTL, and backend
pinning / unpinning functions.
quoted
+{+ struct intel_context *child;+ int i = 0, j = 0, ret;++ GEM_BUG_ON(!intel_context_is_parent(ce));++ ret = intel_context_pin(ce);+ if (unlikely(ret))+ return ret;++ for_each_child(ce, child) {+ ret = intel_context_pin(child);+ if (unlikely(ret))+ goto unwind;+ ++i;+ }++ set_bit(CONTEXT_PERMA_PIN, &ce->flags);++ return 0;++unwind:+ intel_context_unpin(ce);+ for_each_child(ce, child) {+ if (j++ < i)+ intel_context_unpin(child);+ else+ break;+ }++ return ret;+}+ static struct i915_gem_engines *user_engines(struct i915_gem_context *ctx, unsigned int num_engines, struct i915_gem_proto_engine *pe)
@@ -946,7 +1139,7 @@ static struct i915_gem_engines *user_engines(struct i915_gem_context *ctx, e = alloc_engines(num_engines); e->num_engines = num_engines; for (n = 0; n < num_engines; n++) {- struct intel_context *ce;+ struct intel_context *ce, *child; int ret; switch (pe[n].type) {
@@ -956,7 +1149,13 @@ static struct i915_gem_engines *user_engines(struct i915_gem_context *ctx, case I915_GEM_ENGINE_TYPE_BALANCED: ce = intel_engine_create_virtual(pe[n].siblings,- pe[n].num_siblings);+ pe[n].num_siblings, 0);+ break;++ case I915_GEM_ENGINE_TYPE_PARALLEL:+ ce = intel_engine_create_parallel(pe[n].siblings,+ pe[n].num_siblings,+ pe[n].width); break; case I915_GEM_ENGINE_TYPE_INVALID:
@@ -977,6 +1176,22 @@ static struct i915_gem_engines *user_engines(struct i915_gem_context *ctx, err = ERR_PTR(ret); goto free_engines; }+ for_each_child(ce, child) {+ ret = intel_context_set_gem(child, ctx, pe->sseu);+ if (ret) {+ err = ERR_PTR(ret);+ goto free_engines;+ }+ }++ /* XXX: Must be done after setting gem context */
Why the 'XXX'? Is it saying that the ordering is a problem that needs to be
fixed?
Add 'XXX' because originally I hid this behavior in the vfunc used in
intel_engine_create_parallel but as I say we need the gem context set
first. In theory we could fix this with a bit more of rework so all of
this is in the backend, thus the 'XXX'.
quoted
+ if (pe[n].type == I915_GEM_ENGINE_TYPE_PARALLEL) {+ ret = perma_pin_contexts(ce);+ if (ret) {+ err = ERR_PTR(ret);+ goto free_engines;+ }+ } } return e;
@@ -78,6 +78,9 @@ enum i915_gem_engine_type {/** @I915_GEM_ENGINE_TYPE_BALANCED: A load-balanced engine set */I915_GEM_ENGINE_TYPE_BALANCED,++/** @I915_GEM_ENGINE_TYPE_PARALLEL: A parallel engine set */+I915_GEM_ENGINE_TYPE_PARALLEL,};/**
@@ -108,6 +111,9 @@ struct i915_gem_proto_engine {/** @num_siblings: Number of balanced siblings */unsignedintnum_siblings;+/** @width: Width of each sibling */+unsignedintwidth;+/** @siblings: Balanced siblings */structintel_engine_cs**siblings;
@@ -3733,7 +3733,7 @@ static int nop_virtual_engine(struct intel_gt *gt,GEM_BUG_ON(!nctx||nctx>ARRAY_SIZE(ve));for(n=0;n<nctx;n++){-ve[n]=intel_engine_create_virtual(siblings,nsibling);+ve[n]=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve[n])){err=PTR_ERR(ve[n]);nctx=n;
@@ -3929,7 +3929,7 @@ static int mask_virtual_engine(struct intel_gt *gt,*restrictittoourdesiredenginewithinthevirtualengine.*/-ve=intel_engine_create_virtual(siblings,nsibling);+ve=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve)){err=PTR_ERR(ve);gotoout_close;
@@ -4060,7 +4060,7 @@ static int slicein_virtual_engine(struct intel_gt *gt,i915_request_add(rq);}-ce=intel_engine_create_virtual(siblings,nsibling);+ce=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ce)){err=PTR_ERR(ce);gotoout;
@@ -4112,7 +4112,7 @@ static int sliceout_virtual_engine(struct intel_gt *gt,/* XXX We do not handle oversubscription and fairness with normal rq */for(n=0;n<nsibling;n++){-ce=intel_engine_create_virtual(siblings,nsibling);+ce=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ce)){err=PTR_ERR(ce);gotoout;
@@ -4214,7 +4214,7 @@ static int preserved_virtual_engine(struct intel_gt *gt,if(err)gotoout_scratch;-ve=intel_engine_create_virtual(siblings,nsibling);+ve=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve)){err=PTR_ERR(ve);gotoout_scratch;
@@ -4354,7 +4354,7 @@ static int reset_virtual_engine(struct intel_gt *gt,if(igt_spinner_init(&spin,gt))return-ENOMEM;-ve=intel_engine_create_virtual(siblings,nsibling);+ve=intel_engine_create_virtual(siblings,nsibling,0);if(IS_ERR(ve)){err=PTR_ERR(ve);gotoout_spin;
@@ -2827,8 +2834,6 @@ static const struct intel_context_ops virtual_guc_context_ops = {.get_sibling=guc_virtual_get_sibling,};-/* Future patches will use this function */-__maybe_unusedstaticintguc_parent_context_pin(structintel_context*ce,void*vaddr){structintel_engine_cs*engine=guc_virtual_get_sibling(ce->engine,0);
@@ -2845,8 +2850,6 @@ static int guc_parent_context_pin(struct intel_context *ce, void *vaddr)return__guc_context_pin(ce,engine,vaddr);}-/* Future patches will use this function */-__maybe_unusedstaticintguc_child_context_pin(structintel_context*ce,void*vaddr){structintel_engine_cs*engine=guc_virtual_get_sibling(ce->engine,0);
@@ -2858,8 +2861,6 @@ static int guc_child_context_pin(struct intel_context *ce, void *vaddr)return__guc_context_pin(ce,engine,vaddr);}-/* Future patches will use this function */-__maybe_unusedstaticvoidguc_parent_context_unpin(structintel_context*ce){structintel_guc*guc=ce_to_guc(ce);
@@ -2875,8 +2876,6 @@ static void guc_parent_context_unpin(struct intel_context *ce)lrc_unpin(ce);}-/* Future patches will use this function */-__maybe_unusedstaticvoidguc_child_context_unpin(structintel_context*ce){GEM_BUG_ON(context_enabled(ce));
@@ -2887,8 +2886,6 @@ static void guc_child_context_unpin(struct intel_context *ce)lrc_unpin(ce);}-/* Future patches will use this function */-__maybe_unusedstaticvoidguc_child_context_post_unpin(structintel_context*ce){GEM_BUG_ON(!intel_context_is_child(ce));
+ * run these BBs. Once a slot is configured for N BBs only N BBs can be+ * submitted in each execbuf IOCTL and this is implicit behavior e.g. The user+ * doesn't tell the execbuf IOCTL there are N BBs, the execbuf IOCTL knows how+ * many BBs there are based on the slot's configuration. The N BBs are the last+ * N buffer objects or first N if I915_EXEC_BATCH_FIRST is set.+ *+ * The default placement behavior is to create implicit bonds between each+ * context if each context maps to more than 1 physical engine (e.g. context is+ * a virtual engine). Also we only allow contexts of same engine class and these+ * contexts must be in logically contiguous order. Examples of the placement+ * behavior described below. Lastly, the default is to not allow BBs to
behaviour described -> behaviour are described
quoted
+ * preempted mid BB rather insert coordinated preemption on all hardware
to preempted mid BB rather -> to be preempted mid-batch. Rather
coordinated preemption on -> coordinated preemption points on
quoted
+ * contexts between each set of BBs. Flags may be added in the future to change
may -> could - 'may' implies we are thinking about doing it (maybe just for
fun or because we're bored), 'could' implies a user has to ask for the
facility if they need it.
Will reword all of this.
quoted
+ * both of these default behaviors.+ *+ * Returns -EINVAL if hardware context placement configuration is invalid or if+ * the placement configuration isn't supported on the platform / submission+ * interface.+ * Returns -ENODEV if extension isn't supported on the platform / submission+ * interface.+ *+ * .. code-block:: none+ *+ * Example 1 pseudo code:+ * CS[X] = generic engine of same class, logical instance X+ * INVALID = I915_ENGINE_CLASS_INVALID, I915_ENGINE_CLASS_INVALID_NONE
I would put these two terminology explanations above the 'example 1' line
given that they are generic to all examples.
quoted
+ * set_engines(INVALID)+ * set_parallel(engine_index=0, width=2, num_siblings=1,+ * engines=CS[0],CS[1])+ *+ * Results in the following valid placement:+ * CS[0], CS[1]+ *+ * Example 2 pseudo code:+ * CS[X] = generic engine of same class, logical instance X+ * INVALID = I915_ENGINE_CLASS_INVALID, I915_ENGINE_CLASS_INVALID_NONE
And drop them from here.
Sure.
quoted
+ * set_engines(INVALID)+ * set_parallel(engine_index=0, width=2, num_siblings=2,+ * engines=CS[0],CS[2],CS[1],CS[3])+ *+ * Results in the following valid placements:+ * CS[0], CS[1]+ * CS[2], CS[3]+ *+ * This can also be thought of as 2 virtual engines described by 2-D array+ * in the engines the field with bonds placed between each index of the+ * virtual engines. e.g. CS[0] is bonded to CS[1], CS[2] is bonded to+ * CS[3].
I find this description just adds to the confusion. It doesn't help that the
sentence is broken/unparsable - 'described by 2-D array in the engines the
field with bonds'?
"This can be thought of as two virtual engines, each containing two engines
thereby making a 2D array. However, there are bonds tying the entries
together and placing restrictions on how they can be scheduled.
Specifically, the scheduler can choose only vertical columns from the 2D
array. That is, CS[0] is bonded to CS[1] and CS[2] to CS[3]. So if the
scheduler wants to submit to CS[0], it must also choose CS[1] and vice
versa. Same for CS[2] requires also using CS[3]."
Does that make sense?
+ * set_engines(INVALID)+ * set_parallel(engine_index=0, width=2, num_siblings=2,+ * engines=CS[0],CS[1],CS[1],CS[3])+ *+ * Results in the following valid and invalid placements:+ * CS[0], CS[1]+ * CS[1], CS[3] - Not logical contiguous, return -EINVAL
logical -> logically
Yep.
quoted
+ */+struct i915_context_engines_parallel_submit {+ /**+ * @base: base user extension.+ */+ struct i915_user_extension base;++ /**+ * @engine_index: slot for parallel engine+ */+ __u16 engine_index;++ /**+ * @width: number of contexts per parallel engine
Meaning number of engines in the virtual engine? As in, width = 3 means that
the scheduler has a choice of three different engines to submit the one
single batch buffer to?
No, width is number of BBs in a single submission. Will update the
comment to reflect that.
quoted
+ */+ __u16 width;++ /**+ * @num_siblings: number of siblings per context+ */+ __u16 num_siblings;
Meaning the number of engines which must run in parallel. As in,
num_siblings = 2 means that there will be two batch buffers submitted to
every execbuf IOCTL call and that both must execute concurrently on two
separate engines?
This means the number of possible different engine sets the N (width)
batch buffers could be placed on. Will update this comment too.
Matt
John.
quoted
++ /**+ * @mbz16: reserved for future use; must be zero+ */+ __u16 mbz16;++ /**+ * @flags: all undefined flags must be zero, currently not defined flags+ */+ __u64 flags;++ /**+ * @mbz64: reserved for future use; must be zero+ */+ __u64 mbz64[3];++ /**+ * @engines: 2-d array of engine instances to configure parallel engine+ *+ * length = width (i) * num_siblings (j)+ * index = j + i * num_siblings+ */+ struct i915_engine_class_instance engines[0];++} __packed;++#define I915_DEFINE_CONTEXT_ENGINES_PARALLEL_SUBMIT(name__, N__) struct { \+ struct i915_user_extension base; \+ __u16 engine_index; \+ __u16 width; \+ __u16 num_siblings; \+ __u16 mbz16; \+ __u64 flags; \+ __u64 mbz64[3]; \+ struct i915_engine_class_instance engines[N__]; \+} __attribute__((packed)) name__+ /** * DOC: Context Engine Map uAPI *
From: John Harrison <hidden> Date: 2021-09-22 20:16:01
On 9/22/2021 09:25, Matthew Brost wrote:
On Mon, Sep 20, 2021 at 02:48:52PM -0700, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Implement multi-lrc submission via a single workqueue entry and single
H2G. The workqueue entry contains an updated tail value for each
request, of all the contexts in the multi-lrc submission, and updates
these values simultaneously. As such, the tasklet and bypass path have
been updated to coalesce requests into a single submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/uc/intel_guc.c | 21 ++
drivers/gpu/drm/i915/gt/uc/intel_guc.h | 8 +
drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 24 +-
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 6 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 312 +++++++++++++++---
drivers/gpu/drm/i915/i915_request.h | 8 +
6 files changed, 317 insertions(+), 62 deletions(-)
Granted, this patch is just moving code from one file to another not
changing it. However, I think it would be worth adding a blank line in here.
Otherwise the 'this register' comment below can be confusingly read as
referring to the send_regs.fw_domain entry above.
And maybe add a comment why it is a bug for the send_regs value to be set?
I'm not seeing any obvious connection between it and the reset of this code.
Can add a blank line. I think the GEM_BUG_ON relates to being able to
use intel_uncore_write_fw vs intel_uncore_write. Can add comment.
quoted
quoted
+ /*+ * This register is used by the i915 and GuC for MMIO based+ * communication. Once we are in this code CTBs are the only+ * method the i915 uses to communicate with the GuC so it is+ * safe to write to this register (a value of 0 is NOP for MMIO+ * communication). If we ever start mixing CTBs and MMIOs a new+ * register will have to be chosen.+ */+ intel_uncore_write_fw(gt->uncore, GEN11_SOFT_SCRATCH(0), 0);+ } else {+ /* wmb() sufficient for a barrier if in smem */+ wmb();+ }+}
@@ -37,6 +37,12 @@ struct intel_guc {/* Global engine used to submit requests to GuC */structi915_sched_engine*sched_engine;structi915_request*stalled_request;+enum{+STALL_NONE,+STALL_REGISTER_CONTEXT,+STALL_MOVE_LRC_TAIL,+STALL_ADD_REQUEST,+}submission_stall_reason;/* intel_guc_recv interrupt related state */spinlock_tirq_lock;
@@ -377,28 +377,6 @@ static u32 ct_get_next_fence(struct intel_guc_ct *ct)return++ct->requests.last_fence;}-staticvoidwrite_barrier(structintel_guc_ct*ct)-{-structintel_guc*guc=ct_to_guc(ct);-structintel_gt*gt=guc_to_gt(guc);--if(i915_gem_object_is_lmem(guc->ct.vma->obj)){-GEM_BUG_ON(guc->send_regs.fw_domains);-/*-*Thisregisterisusedbythei915andGuCforMMIObased-*communication.OnceweareinthiscodeCTBsaretheonly-*methodthei915usestocommunicatewiththeGuCsoitis-*safetowritetothisregister(avalueof0isNOPforMMIO-*communication).IfweeverstartmixingCTBsandMMIOsanew-*registerwillhavetobechosen.-*/-intel_uncore_write_fw(gt->uncore,GEN11_SOFT_SCRATCH(0),0);-}else{-/* wmb() sufficient for a barrier if in smem */-wmb();-}-}-staticintct_write(structintel_guc_ct*ct,constu32*action,u32len/* in dwords */,
@@ -468,7 +446,7 @@ static int ct_write(struct intel_guc_ct *ct,*makesureH2GbufferupdateandLRCtailupdate(ifthistriggeringa*submission)arevisiblebeforeupdatingthedescriptortail*/-write_barrier(ct);+intel_guc_write_barrier(ct_to_guc(ct));/* update local copies */ctb->tail=tail;
Presumably all of these API changes are not actually new? They really came
in with the reset of the v40 re-write? It's just that this is the first time
we are using them and therefore need to finally update the defines?
@@ -547,10 +570,10 @@ int intel_guc_wait_for_idle(struct intel_guc *guc, long timeout)staticintguc_lrc_desc_pin(structintel_context*ce,boolloop);-staticintguc_add_request(structintel_guc*guc,structi915_request*rq)+staticint__guc_add_request(structintel_guc*guc,structi915_request*rq){interr=0;-structintel_context*ce=rq->context;+structintel_context*ce=request_to_scheduling_context(rq);u32action[3];intlen=0;u32g2h_len_dw=0;
Would be better to say '|| is_parent(ce)' rather than blocked? The reason
for reason for claiming enabled when not is because it's a multi-LRC parent,
right? Or can there be a parent that is neither enabled nor blocked for
which we don't want to do the processing? But why would that make sense/be
possible?
No. If it is parent and blocked we want to submit the enable but not
enable submission. In the non-multi-lrc case the submit has already been
done by the i915 (moving lrc tail).
quoted
quoted
if (!enabled) {
action[len++] = INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
@@ -609,6 +623,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) trace_intel_context_sched_enable(ce); atomic_inc(&guc->outstanding_submission_g2h); set_context_enabled(ce);++ /*+ * Without multi-lrc KMD does the submission step (moving the+ * lrc tail) so enabling scheduling is sufficient to submit the+ * context. This isn't the case in multi-lrc submission as the+ * GuC needs to move the tails, hence the need for another H2G+ * to submit a multi-lrc context after enabling scheduling.+ */+ if (intel_context_is_parent(ce)) {+ action[0] = INTEL_GUC_ACTION_SCHED_CONTEXT;+ err = intel_guc_send_nb(guc, action, len - 1, 0);+ } } else if (!enabled) { clr_context_pending_enable(ce); intel_context_put(ce);
@@ -621,6 +647,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) return err; }+static int guc_add_request(struct intel_guc *guc, struct i915_request *rq)+{+ int ret = __guc_add_request(guc, rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request= rq;+ guc->submission_stall_reason = STALL_ADD_REQUEST;+ }++ return ret;+}+ static void guc_set_lrc_tail(struct i915_request *rq) { rq->context->lrc_reg_state[CTX_RING_TAIL] =
WQI (work queue instance) is the convention used but I actually like WQE
(work queue entry) better. Will change the name to WQE everywhere.
I thought it was Work Queue Item. Which is basically the same as entry. Ether works but simpler just to change this one instance to WQI. And maybe make sure the abbreviation is actually written out in full somewhere to be clear? I think patch #12 is adding these fields to the ce, maybe update the comments to explain what wqi means.
John.
This should have a BUG_ON check that the requested size fits within the
WQ_LEN field?
I could add that.
quoted
Indeed, would be better to use the FIELD macros as they do that kind of
thing for you.
Yes, they do. I forget how they work, will figure this out and use the
macros.
quoted
quoted
+ ce->guc_wqi_tail = 0;++ return 0;+}++static int __guc_wq_item_append(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ struct intel_context *child;+ struct guc_process_desc *desc = __get_process_desc(ce);+ unsigned int wqi_size = (ce->guc_number_children + 4) *+ sizeof(u32);+ u32 *wqi;+ int ret;++ /* Ensure context is in correct state updating work queue */+ GEM_BUG_ON(!atomic_read(&ce->guc_id.ref));+ GEM_BUG_ON(context_guc_id_invalid(ce));+ GEM_BUG_ON(context_wait_for_deregister_to_register(ce));+ GEM_BUG_ON(!lrc_desc_registered(ce_to_guc(ce), ce->guc_id.id));++ /* Insert NOOP if this work queue item will wrap the tail pointer. */+ if (wqi_size > wq_space_until_wrap(ce)) {+ ret = guc_wq_noop_append(ce);+ if (ret)+ return ret;+ }++ wqi = get_wq_pointer(desc, ce, wqi_size);+ if (!wqi)+ return -EBUSY;++ *wqi++ = WQ_TYPE_MULTI_LRC |+ ((wqi_size / sizeof(u32) - 1) << WQ_LEN_SHIFT);+ *wqi++ = ce->lrc.lrca;+ *wqi++ = (ce->guc_id.id << WQ_GUC_ID_SHIFT) |+ ((ce->ring->tail / sizeof(u64)) << WQ_RING_TAIL_SHIFT);
As above, would be better to use FIELD macros instead of manual shifting.
Will do.
Matt
quoted
John.
quoted
+ *wqi++ = 0; /* fence_id */+ for_each_child(ce, child)+ *wqi++ = child->ring->tail / sizeof(u64);++ write_wqi(desc, ce, wqi_size);++ return 0;+}++static int guc_wq_item_append(struct intel_guc *guc,+ struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ int ret = 0;++ if (likely(!intel_context_is_banned(ce))) {+ ret = __guc_wq_item_append(rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request = rq;+ guc->submission_stall_reason = STALL_MOVE_LRC_TAIL;+ }+ }++ return ret;+}++static bool multi_lrc_submit(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);++ intel_ring_set_tail(rq->ring, rq->tail);++ /*+ * We expect the front end (execbuf IOCTL) to set this flag on the last+ * request generated from a multi-BB submission. This indicates to the+ * backend (GuC interface) that we should submit this context thus+ * submitting all the requests generated in parallel.+ */+ return test_bit(I915_FENCE_FLAG_SUBMIT_PARALLEL, &rq->fence.flags) ||+ intel_context_is_banned(ce);+}+ static int guc_dequeue_one_context(struct intel_guc *guc) { struct i915_sched_engine * const sched_engine = guc->sched_engine;
@@ -645,7 +804,17 @@ static int guc_dequeue_one_context(struct intel_guc *guc) if (guc->stalled_request) { submit = true; last = guc->stalled_request;- goto resubmit;++ switch (guc->submission_stall_reason) {+ case STALL_REGISTER_CONTEXT:+ goto register_context;+ case STALL_MOVE_LRC_TAIL:+ goto move_lrc_tail;+ case STALL_ADD_REQUEST:+ goto add_request;+ default:+ MISSING_CASE(guc->submission_stall_reason);+ } } while ((rb = rb_first_cached(&sched_engine->queue))) {
@@ -1247,8 +1483,7 @@ static void guc_submit_request(struct i915_request *rq) /* Will be called from irq-context when using foreign fences. */ spin_lock_irqsave(&sched_engine->lock, flags);- if (submission_disabled(guc) || guc->stalled_request ||- !i915_sched_engine_is_empty(sched_engine))+ if (need_tasklet(guc, rq)) queue_request(sched_engine, rq, rq_prio(rq)); else if (guc_bypass_tasklet_submit(guc, rq) == -EBUSY) tasklet_hi_schedule(&sched_engine->tasklet);
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-23 02:49:55
On Wed, Sep 22, 2021 at 01:15:46PM -0700, John Harrison wrote:
On 9/22/2021 09:25, Matthew Brost wrote:
quoted
On Mon, Sep 20, 2021 at 02:48:52PM -0700, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
Implement multi-lrc submission via a single workqueue entry and single
H2G. The workqueue entry contains an updated tail value for each
request, of all the contexts in the multi-lrc submission, and updates
these values simultaneously. As such, the tasklet and bypass path have
been updated to coalesce requests into a single submission.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
drivers/gpu/drm/i915/gt/uc/intel_guc.c | 21 ++
drivers/gpu/drm/i915/gt/uc/intel_guc.h | 8 +
drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 24 +-
drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 6 +-
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 312 +++++++++++++++---
drivers/gpu/drm/i915/i915_request.h | 8 +
6 files changed, 317 insertions(+), 62 deletions(-)
Granted, this patch is just moving code from one file to another not
changing it. However, I think it would be worth adding a blank line in here.
Otherwise the 'this register' comment below can be confusingly read as
referring to the send_regs.fw_domain entry above.
And maybe add a comment why it is a bug for the send_regs value to be set?
I'm not seeing any obvious connection between it and the reset of this code.
Can add a blank line. I think the GEM_BUG_ON relates to being able to
use intel_uncore_write_fw vs intel_uncore_write. Can add comment.
quoted
quoted
+ /*+ * This register is used by the i915 and GuC for MMIO based+ * communication. Once we are in this code CTBs are the only+ * method the i915 uses to communicate with the GuC so it is+ * safe to write to this register (a value of 0 is NOP for MMIO+ * communication). If we ever start mixing CTBs and MMIOs a new+ * register will have to be chosen.+ */+ intel_uncore_write_fw(gt->uncore, GEN11_SOFT_SCRATCH(0), 0);+ } else {+ /* wmb() sufficient for a barrier if in smem */+ wmb();+ }+}
@@ -37,6 +37,12 @@ struct intel_guc {/* Global engine used to submit requests to GuC */structi915_sched_engine*sched_engine;structi915_request*stalled_request;+enum{+STALL_NONE,+STALL_REGISTER_CONTEXT,+STALL_MOVE_LRC_TAIL,+STALL_ADD_REQUEST,+}submission_stall_reason;/* intel_guc_recv interrupt related state */spinlock_tirq_lock;
@@ -377,28 +377,6 @@ static u32 ct_get_next_fence(struct intel_guc_ct *ct)return++ct->requests.last_fence;}-staticvoidwrite_barrier(structintel_guc_ct*ct)-{-structintel_guc*guc=ct_to_guc(ct);-structintel_gt*gt=guc_to_gt(guc);--if(i915_gem_object_is_lmem(guc->ct.vma->obj)){-GEM_BUG_ON(guc->send_regs.fw_domains);-/*-*Thisregisterisusedbythei915andGuCforMMIObased-*communication.OnceweareinthiscodeCTBsaretheonly-*methodthei915usestocommunicatewiththeGuCsoitis-*safetowritetothisregister(avalueof0isNOPforMMIO-*communication).IfweeverstartmixingCTBsandMMIOsanew-*registerwillhavetobechosen.-*/-intel_uncore_write_fw(gt->uncore,GEN11_SOFT_SCRATCH(0),0);-}else{-/* wmb() sufficient for a barrier if in smem */-wmb();-}-}-staticintct_write(structintel_guc_ct*ct,constu32*action,u32len/* in dwords */,
@@ -468,7 +446,7 @@ static int ct_write(struct intel_guc_ct *ct,*makesureH2GbufferupdateandLRCtailupdate(ifthistriggeringa*submission)arevisiblebeforeupdatingthedescriptortail*/-write_barrier(ct);+intel_guc_write_barrier(ct_to_guc(ct));/* update local copies */ctb->tail=tail;
Presumably all of these API changes are not actually new? They really came
in with the reset of the v40 re-write? It's just that this is the first time
we are using them and therefore need to finally update the defines?
@@ -547,10 +570,10 @@ int intel_guc_wait_for_idle(struct intel_guc *guc, long timeout)staticintguc_lrc_desc_pin(structintel_context*ce,boolloop);-staticintguc_add_request(structintel_guc*guc,structi915_request*rq)+staticint__guc_add_request(structintel_guc*guc,structi915_request*rq){interr=0;-structintel_context*ce=rq->context;+structintel_context*ce=request_to_scheduling_context(rq);u32action[3];intlen=0;u32g2h_len_dw=0;
Would be better to say '|| is_parent(ce)' rather than blocked? The reason
for reason for claiming enabled when not is because it's a multi-LRC parent,
right? Or can there be a parent that is neither enabled nor blocked for
which we don't want to do the processing? But why would that make sense/be
possible?
No. If it is parent and blocked we want to submit the enable but not
enable submission. In the non-multi-lrc case the submit has already been
done by the i915 (moving lrc tail).
quoted
quoted
if (!enabled) {
action[len++] = INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
@@ -609,6 +623,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) trace_intel_context_sched_enable(ce); atomic_inc(&guc->outstanding_submission_g2h); set_context_enabled(ce);++ /*+ * Without multi-lrc KMD does the submission step (moving the+ * lrc tail) so enabling scheduling is sufficient to submit the+ * context. This isn't the case in multi-lrc submission as the+ * GuC needs to move the tails, hence the need for another H2G+ * to submit a multi-lrc context after enabling scheduling.+ */+ if (intel_context_is_parent(ce)) {+ action[0] = INTEL_GUC_ACTION_SCHED_CONTEXT;+ err = intel_guc_send_nb(guc, action, len - 1, 0);+ } } else if (!enabled) { clr_context_pending_enable(ce); intel_context_put(ce);
@@ -621,6 +647,18 @@ static int guc_add_request(struct intel_guc *guc, struct i915_request *rq) return err; }+static int guc_add_request(struct intel_guc *guc, struct i915_request *rq)+{+ int ret = __guc_add_request(guc, rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request= rq;+ guc->submission_stall_reason = STALL_ADD_REQUEST;+ }++ return ret;+}+ static void guc_set_lrc_tail(struct i915_request *rq) { rq->context->lrc_reg_state[CTX_RING_TAIL] =
WQI (work queue instance) is the convention used but I actually like WQE
(work queue entry) better. Will change the name to WQE everywhere.
I thought it was Work Queue Item. Which is basically the same as entry.
Ether works but simpler just to change this one instance to WQI. And maybe
make sure the abbreviation is actually written out in full somewhere to be
clear? I think patch #12 is adding these fields to the ce, maybe update the
comments to explain what wqi means.
I think in the GuC spec it is 'work queue item', guess I'll stick with that.
Matt
This should have a BUG_ON check that the requested size fits within the
WQ_LEN field?
I could add that.
quoted
Indeed, would be better to use the FIELD macros as they do that kind of
thing for you.
Yes, they do. I forget how they work, will figure this out and use the
macros.
quoted
quoted
+ ce->guc_wqi_tail = 0;++ return 0;+}++static int __guc_wq_item_append(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ struct intel_context *child;+ struct guc_process_desc *desc = __get_process_desc(ce);+ unsigned int wqi_size = (ce->guc_number_children + 4) *+ sizeof(u32);+ u32 *wqi;+ int ret;++ /* Ensure context is in correct state updating work queue */+ GEM_BUG_ON(!atomic_read(&ce->guc_id.ref));+ GEM_BUG_ON(context_guc_id_invalid(ce));+ GEM_BUG_ON(context_wait_for_deregister_to_register(ce));+ GEM_BUG_ON(!lrc_desc_registered(ce_to_guc(ce), ce->guc_id.id));++ /* Insert NOOP if this work queue item will wrap the tail pointer. */+ if (wqi_size > wq_space_until_wrap(ce)) {+ ret = guc_wq_noop_append(ce);+ if (ret)+ return ret;+ }++ wqi = get_wq_pointer(desc, ce, wqi_size);+ if (!wqi)+ return -EBUSY;++ *wqi++ = WQ_TYPE_MULTI_LRC |+ ((wqi_size / sizeof(u32) - 1) << WQ_LEN_SHIFT);+ *wqi++ = ce->lrc.lrca;+ *wqi++ = (ce->guc_id.id << WQ_GUC_ID_SHIFT) |+ ((ce->ring->tail / sizeof(u64)) << WQ_RING_TAIL_SHIFT);
As above, would be better to use FIELD macros instead of manual shifting.
Will do.
Matt
quoted
John.
quoted
+ *wqi++ = 0; /* fence_id */+ for_each_child(ce, child)+ *wqi++ = child->ring->tail / sizeof(u64);++ write_wqi(desc, ce, wqi_size);++ return 0;+}++static int guc_wq_item_append(struct intel_guc *guc,+ struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);+ int ret = 0;++ if (likely(!intel_context_is_banned(ce))) {+ ret = __guc_wq_item_append(rq);++ if (unlikely(ret == -EBUSY)) {+ guc->stalled_request = rq;+ guc->submission_stall_reason = STALL_MOVE_LRC_TAIL;+ }+ }++ return ret;+}++static bool multi_lrc_submit(struct i915_request *rq)+{+ struct intel_context *ce = request_to_scheduling_context(rq);++ intel_ring_set_tail(rq->ring, rq->tail);++ /*+ * We expect the front end (execbuf IOCTL) to set this flag on the last+ * request generated from a multi-BB submission. This indicates to the+ * backend (GuC interface) that we should submit this context thus+ * submitting all the requests generated in parallel.+ */+ return test_bit(I915_FENCE_FLAG_SUBMIT_PARALLEL, &rq->fence.flags) ||+ intel_context_is_banned(ce);+}+ static int guc_dequeue_one_context(struct intel_guc *guc) { struct i915_sched_engine * const sched_engine = guc->sched_engine;
@@ -645,7 +804,17 @@ static int guc_dequeue_one_context(struct intel_guc *guc) if (guc->stalled_request) { submit = true; last = guc->stalled_request;- goto resubmit;++ switch (guc->submission_stall_reason) {+ case STALL_REGISTER_CONTEXT:+ goto register_context;+ case STALL_MOVE_LRC_TAIL:+ goto move_lrc_tail;+ case STALL_ADD_REQUEST:+ goto add_request;+ default:+ MISSING_CASE(guc->submission_stall_reason);+ } } while ((rb = rb_first_cached(&sched_engine->queue))) {
@@ -1247,8 +1483,7 @@ static void guc_submit_request(struct i915_request *rq) /* Will be called from irq-context when using foreign fences. */ spin_lock_irqsave(&sched_engine->lock, flags);- if (submission_disabled(guc) || guc->stalled_request ||- !i915_sched_engine_is_empty(sched_engine))+ if (need_tasklet(guc, rq)) queue_request(sched_engine, rq, rq_prio(rq)); else if (guc_bypass_tasklet_submit(guc, rq) == -EBUSY) tasklet_hi_schedule(&sched_engine->tasklet);
@@ -48,5 +48,6 @@ selftest(ring_submission, intel_ring_submission_live_selftests)selftest(perf,i915_perf_live_selftests)selftest(slpc,intel_slpc_live_selftests)selftest(guc,intel_guc_live_selftests)+selftest(guc_multi_lrc,intel_guc_multi_lrc_live_selftests)/* Here be dragons: keep last to run last! */selftest(late_gt_pm,intel_gt_pm_late_selftests)
From: John Harrison <hidden> Date: 2021-09-28 22:20:57
On 8/20/2021 15:44, Matthew Brost wrote:
For some users of multi-lrc, e.g. split frame, it isn't safe to preempt
mid BB. To safely enable preemption at the BB boundary, a handshake
between to parent and child is needed. This is implemented via custom
emit_bb_start & emit_fini_breadcrumb functions and enabled via by
via by -> by
default if a context is configured by set parallel extension.
I tend to agree with Tvrtko that this should probably be an opt in change. Is there a flags word passed in when creating the context?
Also, it's not just a change in pre-emption behaviour but a change in synchronisation too, right? Previously, if you had a whole bunch of back to back submissions then one child could run ahead of another and/or the parent. After this change, there is a forced regroup at the end of each batch. So while one could end sooner/later than the others, they can't ever get an entire batch (or more) ahead or behind. Or was that synchronisation already in there through other means anyway?
@@ -246,6 +246,9 @@ struct intel_context {/** @guc_number_children: number of children if parent */u8guc_number_children;+/** @guc_child_index: index into guc_child_list if child */+u8guc_child_index;+/***@parent_page:pageincontextusedbyparentforworkqueue,*workqueuedescriptor
Would be better written as '[num_children]' instead of '...' to make it clear it is a per child array.
Also, maybe create a struct for this to get rid of the magic '+1's and 'BYTES / sizeof' constructs in the functions below.
quoted hunk
* ... unused
* PAGE_SIZE / 2 work queue start
* ... work queue
@@ -2943,6 +2974,31 @@ static const struct intel_context_ops virtual_child_context_ops = { .get_sibling = guc_virtual_get_sibling, };+/*+ * The below override of the breadcrumbs is enabled when the user configures a+ * context for parallel submission (multi-lrc, parent-child).+ *+ * The overridden breadcrumbs implements an algorithm which allows the GuC to+ * safely preempt all the hw contexts configured for parallel submission+ * between each BB. The contract between the i915 and GuC is if the parent+ * context can be preempted, all the children can be preempted, and the GuC will+ * always try to preempt the parent before the children. A handshake between the+ * parent / children breadcrumbs ensures the i915 holds up its end of the deal+ * creating a window to preempt between each set of BBs.+ */+static int emit_bb_start_parent_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static int emit_bb_start_child_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static u32 *+emit_fini_breadcrumb_parent_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+static u32 *+emit_fini_breadcrumb_child_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+ static struct intel_context * guc_create_parallel(struct intel_engine_cs **engines, unsigned int num_siblings,
This is backwards compared to the parent?
Parent: wait on children then enable pre-emption
Child: enable pre-emption then signal parent
Makes for a window where the parent is waiting in atomic context for a signal from a child that has been pre-empted and might not get to run for some time?
John.
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-28 22:38:53
On Tue, Sep 28, 2021 at 03:20:42PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
For some users of multi-lrc, e.g. split frame, it isn't safe to preempt
mid BB. To safely enable preemption at the BB boundary, a handshake
between to parent and child is needed. This is implemented via custom
emit_bb_start & emit_fini_breadcrumb functions and enabled via by
via by -> by
quoted
default if a context is configured by set parallel extension.
I tend to agree with Tvrtko that this should probably be an opt in change.
Is there a flags word passed in when creating the context?
I don't disagree but the uAPI in this series is where we landed. It has
been acked all by the relevant parties in the RFC, ported to our
internal tree, and the media UMD has been updated / posted. Concerns
with the uAPI should've been raised in the RFC phase, not now. I really
don't feel like changing this uAPI another time.
Also, it's not just a change in pre-emption behaviour but a change in
synchronisation too, right? Previously, if you had a whole bunch of back to
back submissions then one child could run ahead of another and/or the
parent. After this change, there is a forced regroup at the end of each
batch. So while one could end sooner/later than the others, they can't ever
get an entire batch (or more) ahead or behind. Or was that synchronisation
already in there through other means anyway?
Yes, each parent / child sync at the of each batch - this is the only
way safely insert preemption points. Without this the GuC could attempt
a preemption and hang the batches.
@@ -246,6 +246,9 @@ struct intel_context {/** @guc_number_children: number of children if parent */u8guc_number_children;+/** @guc_child_index: index into guc_child_list if child */+u8guc_child_index;+/***@parent_page:pageincontextusedbyparentforworkqueue,*workqueuedescriptor
What is this extra space for? All the extra storage is grabbed from after
the end of this structure, isn't it?
This is the size of process descriptor in the GuC spec. Even though this
is unused space we really don't want the child go / join memory using
anything within the process descriptor.
@@ -2943,6 +2974,31 @@ static const struct intel_context_ops virtual_child_context_ops = { .get_sibling = guc_virtual_get_sibling, };+/*+ * The below override of the breadcrumbs is enabled when the user configures a+ * context for parallel submission (multi-lrc, parent-child).+ *+ * The overridden breadcrumbs implements an algorithm which allows the GuC to+ * safely preempt all the hw contexts configured for parallel submission+ * between each BB. The contract between the i915 and GuC is if the parent+ * context can be preempted, all the children can be preempted, and the GuC will+ * always try to preempt the parent before the children. A handshake between the+ * parent / children breadcrumbs ensures the i915 holds up its end of the deal+ * creating a window to preempt between each set of BBs.+ */+static int emit_bb_start_parent_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static int emit_bb_start_child_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static u32 *+emit_fini_breadcrumb_parent_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+static u32 *+emit_fini_breadcrumb_child_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+ static struct intel_context * guc_create_parallel(struct intel_engine_cs **engines, unsigned int num_siblings,
This is backwards compared to the parent?
Parent: wait on children then enable pre-emption
Child: enable pre-emption then signal parent
Makes for a window where the parent is waiting in atomic context for a
signal from a child that has been pre-empted and might not get to run for
some time?
No, this is correct. The rule is if the parent can be preempted all the
children can be preempted, thus we can't enable preemption on the parent
until all the children have preemption enabled, thus the parent waits
for all the children to join before enabling its preemption.
Matt
From: John Harrison <hidden> Date: 2021-09-28 23:33:40
On 9/28/2021 15:33, Matthew Brost wrote:
On Tue, Sep 28, 2021 at 03:20:42PM -0700, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
For some users of multi-lrc, e.g. split frame, it isn't safe to preempt
mid BB. To safely enable preemption at the BB boundary, a handshake
between to parent and child is needed. This is implemented via custom
emit_bb_start & emit_fini_breadcrumb functions and enabled via by
via by -> by
quoted
default if a context is configured by set parallel extension.
I tend to agree with Tvrtko that this should probably be an opt in change.
Is there a flags word passed in when creating the context?
I don't disagree but the uAPI in this series is where we landed. It has
been acked all by the relevant parties in the RFC, ported to our
internal tree, and the media UMD has been updated / posted. Concerns
with the uAPI should've been raised in the RFC phase, not now. I really
don't feel like changing this uAPI another time.
The counter argument is that once a UAPI has been merged, it cannot be changed. Ever. So it is worth taking the trouble to get it right first time.
The proposal isn't a major re-write of the interface. It is simply a request to set an extra flag when creating the context.
quoted
Also, it's not just a change in pre-emption behaviour but a change in
synchronisation too, right? Previously, if you had a whole bunch of back to
back submissions then one child could run ahead of another and/or the
parent. After this change, there is a forced regroup at the end of each
batch. So while one could end sooner/later than the others, they can't ever
get an entire batch (or more) ahead or behind. Or was that synchronisation
already in there through other means anyway?
Yes, each parent / child sync at the of each batch - this is the only
way safely insert preemption points. Without this the GuC could attempt
a preemption and hang the batches.
To be clear, I'm not saying that this is wrong. I'm just saying that this appears to be new behaviour with this patch but it is not explicitly called out in the description of the patch.
@@ -246,6 +246,9 @@ struct intel_context {/** @guc_number_children: number of children if parent */u8guc_number_children;+/** @guc_child_index: index into guc_child_list if child */+u8guc_child_index;+/***@parent_page:pageincontextusedbyparentforworkqueue,*workqueuedescriptor
What is this extra space for? All the extra storage is grabbed from after
the end of this structure, isn't it?
This is the size of process descriptor in the GuC spec. Even though this
is unused space we really don't want the child go / join memory using
anything within the process descriptor.
Okay. So it's more that the code was previously broken and we just hadn't hit a problem because of it? Again, worth adding a comment in the description to call it out as a bug fix.
@@ -2943,6 +2974,31 @@ static const struct intel_context_ops virtual_child_context_ops = { .get_sibling = guc_virtual_get_sibling, };+/*+ * The below override of the breadcrumbs is enabled when the user configures a+ * context for parallel submission (multi-lrc, parent-child).+ *+ * The overridden breadcrumbs implements an algorithm which allows the GuC to+ * safely preempt all the hw contexts configured for parallel submission+ * between each BB. The contract between the i915 and GuC is if the parent+ * context can be preempted, all the children can be preempted, and the GuC will+ * always try to preempt the parent before the children. A handshake between the+ * parent / children breadcrumbs ensures the i915 holds up its end of the deal+ * creating a window to preempt between each set of BBs.+ */+static int emit_bb_start_parent_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static int emit_bb_start_child_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static u32 *+emit_fini_breadcrumb_parent_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+static u32 *+emit_fini_breadcrumb_child_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+ static struct intel_context * guc_create_parallel(struct intel_engine_cs **engines, unsigned int num_siblings,
This is backwards compared to the parent?
Parent: wait on children then enable pre-emption
Child: enable pre-emption then signal parent
Makes for a window where the parent is waiting in atomic context for a
signal from a child that has been pre-empted and might not get to run for
some time?
No, this is correct. The rule is if the parent can be preempted all the
children can be preempted, thus we can't enable preemption on the parent
until all the children have preemption enabled, thus the parent waits
for all the children to join before enabling its preemption.
Matt
But,
The write to PARENT_GO_FINI can't fail or stall, right? So if it happens before the ARB_ON then the child is guaranteed to execute the ARB_ON once it has signalled the parent. Indeed, by the time the parent context gets to see the update memory value, the child is practically certain to have passed the ARB_ON. So, by the time the parent becomes pre-emptible, the children will all be pre-emptible. Even if the parent is superfast, the children are guaranteed to become pre-emptible immediately - certainly before any fail-to-preempt timeout could occur.
Whereas, with the current ordering, it is possible for the child to be preempted before it has issued the signal to the parent. So now you have a non-preemptible parent hogging the hardware, waiting for a signal that isn't going to come for an entire execution quantum. Indeed, it is actually quite likely the child would be preempted before it can signal the parent because any pre-emption request that was issued at any time during the child's execution will take effect immediately on the ARB_ON instruction.
John.
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-29 00:27:51
On Tue, Sep 28, 2021 at 04:33:24PM -0700, John Harrison wrote:
On 9/28/2021 15:33, Matthew Brost wrote:
quoted
On Tue, Sep 28, 2021 at 03:20:42PM -0700, John Harrison wrote:
quoted
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
For some users of multi-lrc, e.g. split frame, it isn't safe to preempt
mid BB. To safely enable preemption at the BB boundary, a handshake
between to parent and child is needed. This is implemented via custom
emit_bb_start & emit_fini_breadcrumb functions and enabled via by
via by -> by
quoted
default if a context is configured by set parallel extension.
I tend to agree with Tvrtko that this should probably be an opt in change.
Is there a flags word passed in when creating the context?
I don't disagree but the uAPI in this series is where we landed. It has
been acked all by the relevant parties in the RFC, ported to our
internal tree, and the media UMD has been updated / posted. Concerns
with the uAPI should've been raised in the RFC phase, not now. I really
don't feel like changing this uAPI another time.
The counter argument is that once a UAPI has been merged, it cannot be
changed. Ever. So it is worth taking the trouble to get it right first time.
The proposal isn't a major re-write of the interface. It is simply a request
to set an extra flag when creating the context.
We are basically just talking about the polarity of a flag at this
point. Either by default you can't be preempted mid batch (current GPU /
UMD requirement) or by default you can be preempted mid-batch (no
current GPU / UMD can do this yet but add flags that everyone opts
into). I think Daniel's opinion was just default to what the current GPU
/ UMD wants and if future requirements arise we add flags to the
interface. I understand both points of view for flag / not flag but in
the end it doesn't really matter. Either way the interface works now and
will in the future too.
quoted
quoted
Also, it's not just a change in pre-emption behaviour but a change in
synchronisation too, right? Previously, if you had a whole bunch of back to
back submissions then one child could run ahead of another and/or the
parent. After this change, there is a forced regroup at the end of each
batch. So while one could end sooner/later than the others, they can't ever
get an entire batch (or more) ahead or behind. Or was that synchronisation
already in there through other means anyway?
Yes, each parent / child sync at the of each batch - this is the only
way safely insert preemption points. Without this the GuC could attempt
a preemption and hang the batches.
To be clear, I'm not saying that this is wrong. I'm just saying that this
appears to be new behaviour with this patch but it is not explicitly called
out in the description of the patch.
Will add some comments explaining this behavior (unless I already have
them).
@@ -246,6 +246,9 @@ struct intel_context {/** @guc_number_children: number of children if parent */u8guc_number_children;+/** @guc_child_index: index into guc_child_list if child */+u8guc_child_index;+/***@parent_page:pageincontextusedbyparentforworkqueue,*workqueuedescriptor
What is this extra space for? All the extra storage is grabbed from after
the end of this structure, isn't it?
This is the size of process descriptor in the GuC spec. Even though this
is unused space we really don't want the child go / join memory using
anything within the process descriptor.
Okay. So it's more that the code was previously broken and we just hadn't
hit a problem because of it? Again, worth adding a comment in the
description to call it out as a bug fix.
@@ -2943,6 +2974,31 @@ static const struct intel_context_ops virtual_child_context_ops = { .get_sibling = guc_virtual_get_sibling, };+/*+ * The below override of the breadcrumbs is enabled when the user configures a+ * context for parallel submission (multi-lrc, parent-child).+ *+ * The overridden breadcrumbs implements an algorithm which allows the GuC to+ * safely preempt all the hw contexts configured for parallel submission+ * between each BB. The contract between the i915 and GuC is if the parent+ * context can be preempted, all the children can be preempted, and the GuC will+ * always try to preempt the parent before the children. A handshake between the+ * parent / children breadcrumbs ensures the i915 holds up its end of the deal+ * creating a window to preempt between each set of BBs.+ */+static int emit_bb_start_parent_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static int emit_bb_start_child_no_preempt_mid_batch(struct i915_request *rq,+ u64 offset, u32 len,+ const unsigned int flags);+static u32 *+emit_fini_breadcrumb_parent_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+static u32 *+emit_fini_breadcrumb_child_no_preempt_mid_batch(struct i915_request *rq,+ u32 *cs);+ static struct intel_context * guc_create_parallel(struct intel_engine_cs **engines, unsigned int num_siblings,
This is backwards compared to the parent?
Parent: wait on children then enable pre-emption
Child: enable pre-emption then signal parent
Makes for a window where the parent is waiting in atomic context for a
signal from a child that has been pre-empted and might not get to run for
some time?
No, this is correct. The rule is if the parent can be preempted all the
children can be preempted, thus we can't enable preemption on the parent
until all the children have preemption enabled, thus the parent waits
for all the children to join before enabling its preemption.
Matt
But,
The write to PARENT_GO_FINI can't fail or stall, right? So if it happens
before the ARB_ON then the child is guaranteed to execute the ARB_ON once it
has signalled the parent. Indeed, by the time the parent context gets to see
the update memory value, the child is practically certain to have passed the
ARB_ON. So, by the time the parent becomes pre-emptible, the children will
all be pre-emptible. Even if the parent is superfast, the children are
guaranteed to become pre-emptible immediately - certainly before any
fail-to-preempt timeout could occur.
Whereas, with the current ordering, it is possible for the child to be
preempted before it has issued the signal to the parent. So now you have a
non-preemptible parent hogging the hardware, waiting for a signal that isn't
To be clear the parent is always preempted first by the GuC. The parent
can't be running if the child preempt is attempted.
going to come for an entire execution quantum. Indeed, it is actually quite
likely the child would be preempted before it can signal the parent because
any pre-emption request that was issued at any time during the child's
execution will take effect immediately on the ARB_ON instruction.
Looking at the code, I do think I have a bug though.
I think I'm missing a MI_ARB_CHECK in the parent after turning on
preemption before releasing the children, right?
This covers the case where the GuC issues a preemption to the parent
while it is waiting on the children, all the children join, the parent
turns on preemption and is preempted with the added MI_ARB_CHECK
instruction, and the children all can be preempted waiting on the parent
go semaphore. Does that sound correct?
Matt
From: John Harrison <hidden> Date: 2021-09-29 20:45:24
On 8/20/2021 15:44, Matthew Brost wrote:
If an error occurs in the front end when multi-lrc requests are getting
generated we need to skip these in the backend but we still need to
emit the breadcrumbs seqno. An issues arrises because with multi-lrc
arrises -> arises
breadcrumbs there is a handshake between the parent and children to make
forwad progress. If all the requests are not present this handshake
forwad -> forward
quoted hunk
doesn't work. To work around this, if multi-lrc request has an error we
skip the handshake but still emit the breadcrumbs seqno.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 61 ++++++++++++++++++-
1 file changed, 58 insertions(+), 3 deletions(-)
Why -6? There are 12 words about to be written. Indeed the value of emit_..._dw is '12 + 4*num_children'. This should only be skipping over the 4*children, right? As it stands, it will skip all but the last six words, then write an extra twelve words and thus overflow the reservation by six. Unless I am totally confused?
I assume there is some reason why the amount of data written must exactly match the space reserved? It's a while since I've looked at the ring buffer code!
Seems like it would be clearer to not split the semaphore writes out but have them right next to the skip code that is meant to replicate them but with no-ops.
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-29 21:03:27
On Wed, Sep 29, 2021 at 01:44:10PM -0700, John Harrison wrote:
On 8/20/2021 15:44, Matthew Brost wrote:
quoted
If an error occurs in the front end when multi-lrc requests are getting
generated we need to skip these in the backend but we still need to
emit the breadcrumbs seqno. An issues arrises because with multi-lrc
arrises -> arises
Yep.
quoted
breadcrumbs there is a handshake between the parent and children to make
forwad progress. If all the requests are not present this handshake
forwad -> forward
Yep.
quoted
doesn't work. To work around this, if multi-lrc request has an error we
skip the handshake but still emit the breadcrumbs seqno.
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 61 ++++++++++++++++++-
1 file changed, 58 insertions(+), 3 deletions(-)
Why -6? There are 12 words about to be written. Indeed the value of
emit_..._dw is '12 + 4*num_children'. This should only be skipping over the
4*children, right? As it stands, it will skip all but the last six words,
then write an extra twelve words and thus overflow the reservation by six.
Unless I am totally confused?
Let me decode the length:
'Wait on children' (in __emit_fini_breadcrumb_parent_no_preempt_mid_batch) = 4 * num_children
'Turn on preemption' (in __emit_fini_breadcrumb_parent_no_preempt_mid_batch) = 2
'Tell children go' (in __emit_fini_breadcrumb_parent_no_preempt_mid_batch) = 4
'Emit fini breadcrumb' (in emit_fini_breadcrumb_child_no_preempt_mid_batch) = 4
'User interrupt' (in emit_fini_breadcrumb_child_no_preempt_mid_batch) = 2
So for a total (emit_fini_breadcrumb_dw) we have '12 + 4 * num_children'
We want skip everything in __emit_fini_breadcrumb_parent_no_preempt_mid_batch, so that is
'6 + 4 * num_children' or 'emit_fini_breadcrumb_dw - 6'
Make sense?
I assume there is some reason why the amount of data written must exactly
match the space reserved? It's a while since I've looked at the ring buffer
code!
I think it because the ring space is reserved at request creation time
but the fini breadcrumbs are not written until submission time.
Seems like it would be clearer to not split the semaphore writes out but
have them right next to the skip code that is meant to replicate them but
with no-ops.
I guess that works too, I personally like the way it is but if you
insist I can change it.
From: Matthew Brost <matthew.brost@intel.com> Date: 2021-09-30 22:21:05
On Fri, Aug 20, 2021 at 03:44:43PM -0700, Matthew Brost wrote:
Did a review offline with John Harrison, adding notes for what we found.
Allow multiple batch buffers to be submitted in a single execbuf IOCTL
after a context has been configured with the 'set_parallel' extension.
The number batches is implicit based on the contexts configuration.
This is implemented with a series of loops. First a loop is used to find
all the batches, a loop to pin all the HW contexts, a loop to generate
all the requests, a loop to submit all the requests, a loop to commit
all the requests, and finally a loop to tie the requests to the VMAs
they touch.
Clarify these steps a bit, also tieing requests to the VMAs is the 2nd to last
step with commiting requests to be the last.
A composite fence is also created for the also the generated requests to
return to the user and to stick in dma resv slots.
Add a comment saying there should be no change in behavior for existing IOCTLs
expect if throttling because to space in the ring, the wait is done with the VMA
locks being held rather than dropping the locks and putting to the slow path.
quoted hunk
IGT: https://patchwork.freedesktop.org/patch/447008/?series=93071&rev=1
media UMD: link to come
Signed-off-by: Matthew Brost <matthew.brost@intel.com>
---
.../gpu/drm/i915/gem/i915_gem_execbuffer.c | 765 ++++++++++++------
drivers/gpu/drm/i915/gt/intel_context.h | 8 +-
drivers/gpu/drm/i915/gt/intel_context_types.h | 12 +
.../gpu/drm/i915/gt/uc/intel_guc_submission.c | 2 +
drivers/gpu/drm/i915/i915_request.h | 9 +
drivers/gpu/drm/i915/i915_vma.c | 21 +-
drivers/gpu/drm/i915/i915_vma.h | 13 +-
7 files changed, 573 insertions(+), 257 deletions(-)
@@ -244,17 +244,23 @@ struct i915_execbuffer {structdrm_i915_gem_exec_object2*exec;/** ioctl execobj[] */structeb_vma*vma;-structintel_engine_cs*engine;/** engine to queue the request to */+structintel_gt*gt;/* gt for the execbuf */structintel_context*context;/* logical state for the request */structi915_gem_context*gem_context;/** caller's context */-structi915_request*request;/** our request to build */-structeb_vma*batch;/** identity of the batch obj/vma */
Line wrap of these comments.
quoted hunk
+ struct i915_request *requests[MAX_ENGINE_INSTANCE + 1]; /** our requests to build */+ struct eb_vma *batches[MAX_ENGINE_INSTANCE + 1]; /** identity of the batch obj/vma */ struct i915_vma *trampoline; /** trampoline used for chaining */+ /** used for excl fence in dma_resv objects when > 1 BB submitted */+ struct dma_fence *composite_fence;+ /** actual size of execobj[] as we may extend it for the cmdparser */ unsigned int buffer_count;+ /* number of batches in execbuf IOCTL */+ unsigned int num_batches;+ /** list of vma not yet bound during reservation phase */ struct list_head unbound;
@@ -281,7 +287,7 @@ struct i915_execbuffer { u64 invalid_flags; /** Set of execobj.flags that are invalid */- u64 batch_len; /** Length of batch within object */+ u64 batch_len[MAX_ENGINE_INSTANCE + 1]; /** Length of batch within object */ u32 batch_start_offset; /** Location within object of batch */ u32 batch_flags; /** Flags composed for emit_bb_start() */ struct intel_gt_buffer_pool_node *batch_pool; /** pool node for batch buffer */
@@ -1657,21 +1669,6 @@ static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb, eb_release_vmas(eb, false); i915_gem_ww_ctx_fini(&eb->ww);- if (rq) {- /* nonblocking is always false */- if (i915_request_wait(rq, I915_WAIT_INTERRUPTIBLE,- MAX_SCHEDULE_TIMEOUT) < 0) {- i915_request_put(rq);- rq = NULL;-- err = -EINTR;- goto err_relock;- }-- i915_request_put(rq);- rq = NULL;- }- /* * We take 3 passes through the slowpatch. *
@@ -1698,28 +1695,21 @@ static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb, if (!err) err = eb_reinit_userptr(eb);-err_relock: i915_gem_ww_ctx_init(&eb->ww, true); if (err) goto out; /* reacquire the objects */ repeat_validate:- rq = eb_pin_engine(eb, false);- if (IS_ERR(rq)) {- err = PTR_ERR(rq);- rq = NULL;+ err = eb_pin_engine(eb, false);+ if (err) goto err;- }-- /* We didn't throttle, should be NULL */- GEM_WARN_ON(rq); err = eb_validate_vmas(eb); if (err) goto err;- GEM_BUG_ON(!eb->batch);+ GEM_BUG_ON(!eb->batches[0]); list_for_each_entry(ev, &eb->relocs, reloc_link) { if (!have_copy) {
@@ -1783,46 +1773,23 @@ static noinline int eb_relocate_parse_slow(struct i915_execbuffer *eb, } }- if (rq)- i915_request_put(rq);- return err; } static int eb_relocate_parse(struct i915_execbuffer *eb) { int err;- struct i915_request *rq = NULL; bool throttle = true; retry:- rq = eb_pin_engine(eb, throttle);- if (IS_ERR(rq)) {- err = PTR_ERR(rq);- rq = NULL;+ err = eb_pin_engine(eb, throttle);+ if (err) { if (err != -EDEADLK) return err; goto err; }- if (rq) {- bool nonblock = eb->file->filp->f_flags & O_NONBLOCK;-- /* Need to drop all locks now for throttling, take slowpath */- err = i915_request_wait(rq, I915_WAIT_INTERRUPTIBLE, 0);- if (err == -ETIME) {- if (nonblock) {- err = -EWOULDBLOCK;- i915_request_put(rq);- goto err;- }- goto slow;- }- i915_request_put(rq);- rq = NULL;- }- /* only throttle once, even if we didn't need to throttle */ throttle = false;
@@ -1862,7 +1829,7 @@ static int eb_relocate_parse(struct i915_execbuffer *eb) return err; slow:- err = eb_relocate_parse_slow(eb, rq);+ err = eb_relocate_parse_slow(eb); if (err) /* * If the user expects the execobject.offset and
+{+ int i;++ for_each_batch_add_order(eb, i)+ if (eb->requests[i])+ return eb->requests[i];++ GEM_BUG_ON("Request not found");++ return NULL;+}+ static int eb_move_to_gpu(struct i915_execbuffer *eb) { const unsigned int count = eb->buffer_count; unsigned int i = count;- int err = 0;+ int err = 0, j; while (i--) { struct eb_vma *ev = &eb->vma[i];
@@ -2055,14 +2065,16 @@ static int eb_parse(struct i915_execbuffer *eb) int err; if (!eb_use_cmdparser(eb)) {- batch = eb_dispatch_secure(eb, eb->batch->vma);+ batch = eb_dispatch_secure(eb, eb->batches[0]->vma); if (IS_ERR(batch)) return PTR_ERR(batch); goto secure_batch; }- len = eb->batch_len;+ GEM_BUG_ON(intel_context_is_parallel(eb->context));
Return -EINVAL (or -ENODEV) rather than BUG_ON
quoted hunk
++ len = eb->batch_len[0]; if (!CMDPARSER_USES_GGTT(eb->i915)) { /* * ppGTT backed shadow buffers must be mapped RO, to prevent
@@ -2076,11 +2088,11 @@ static int eb_parse(struct i915_execbuffer *eb) } else { len += I915_CMD_PARSER_TRAMPOLINE_SIZE; }- if (unlikely(len < eb->batch_len)) /* last paranoid check of overflow */+ if (unlikely(len < eb->batch_len[0])) /* last paranoid check of overflow */ return -EINVAL; if (!pool) {- pool = intel_gt_get_buffer_pool(eb->engine->gt, len,+ pool = intel_gt_get_buffer_pool(eb->gt, len, I915_MAP_WB); if (IS_ERR(pool)) return PTR_ERR(pool);
@@ -2162,19 +2175,18 @@ static int eb_parse(struct i915_execbuffer *eb) return err; }-static int eb_submit(struct i915_execbuffer *eb, struct i915_vma *batch)+static int eb_request_submit(struct i915_execbuffer *eb,+ struct i915_request *rq,+ struct i915_vma *batch,+ u64 batch_len) { int err;- if (intel_context_nopreempt(eb->context))- __set_bit(I915_FENCE_FLAG_NOPREEMPT, &eb->request->fence.flags);-- err = eb_move_to_gpu(eb);- if (err)- return err;+ if (intel_context_nopreempt(rq->context))+ __set_bit(I915_FENCE_FLAG_NOPREEMPT, &rq->fence.flags); if (eb->args->flags & I915_EXEC_GEN7_SOL_RESET) {- err = i915_reset_gen7_sol_offsets(eb->request);+ err = i915_reset_gen7_sol_offsets(rq); if (err) return err; }
@@ -2185,26 +2197,26 @@ static int eb_submit(struct i915_execbuffer *eb, struct i915_vma *batch) * allows us to determine if the batch is still waiting on the GPU * or actually running by checking the breadcrumb. */- if (eb->engine->emit_init_breadcrumb) {- err = eb->engine->emit_init_breadcrumb(eb->request);+ if (rq->context->engine->emit_init_breadcrumb) {+ err = rq->context->engine->emit_init_breadcrumb(rq); if (err) return err; }- err = eb->engine->emit_bb_start(eb->request,- batch->node.start +- eb->batch_start_offset,- eb->batch_len,- eb->batch_flags);+ err = rq->context->engine->emit_bb_start(rq,+ batch->node.start ++ eb->batch_start_offset,+ batch_len,+ eb->batch_flags); if (err) return err; if (eb->trampoline) {+ GEM_BUG_ON(intel_context_is_parallel(rq->context)); GEM_BUG_ON(eb->batch_start_offset);- err = eb->engine->emit_bb_start(eb->request,- eb->trampoline->node.start +- eb->batch_len,- 0, 0);+ err = rq->context->engine->emit_bb_start(rq,+ eb->trampoline->node.start ++ batch_len, 0, 0); if (err) return err; }
@@ -2212,6 +2224,27 @@ static int eb_submit(struct i915_execbuffer *eb, struct i915_vma *batch) return 0; }+static int eb_submit(struct i915_execbuffer *eb)+{+ unsigned int i;+ int err;++ err = eb_move_to_gpu(eb);++ for_each_batch_create_order(eb, i) {+ if (!eb->requests[i])+ break;++ trace_i915_request_queue(eb->requests[i], eb->batch_flags);+ if (!err)+ err = eb_request_submit(eb, eb->requests[i],+ eb->batches[i]->vma,+ eb->batch_len[i]);+ }++ return err;+}+ static int num_vcs_engines(const struct drm_i915_private *i915) { return hweight_long(VDBOX_MASK(&i915->gt));
@@ -2277,26 +2310,11 @@ static struct i915_request *eb_throttle(struct i915_execbuffer *eb, struct intel return i915_request_get(rq); }-static struct i915_request *eb_pin_engine(struct i915_execbuffer *eb, bool throttle)+static int eb_pin_timeline(struct i915_execbuffer *eb, struct intel_context *ce,+ bool throttle) {- struct intel_context *ce = eb->context; struct intel_timeline *tl;- struct i915_request *rq = NULL;- int err;-- GEM_BUG_ON(eb->args->flags & __EXEC_ENGINE_PINNED);-- if (unlikely(intel_context_is_banned(ce)))- return ERR_PTR(-EIO);-- /*- * Pinning the contexts may generate requests in order to acquire- * GGTT space, so do this first before we reserve a seqno for- * ourselves.- */- err = intel_context_pin_ww(ce, &eb->ww);- if (err)- return ERR_PTR(err);+ struct i915_request *rq; /* * Take a local wakeref for preparing to dispatch the execbuf as
@@ -2307,33 +2325,108 @@ static struct i915_request *eb_pin_engine(struct i915_execbuffer *eb, bool throt * taken on the engine, and the parent device. */ tl = intel_context_timeline_lock(ce);- if (IS_ERR(tl)) {- intel_context_unpin(ce);- return ERR_CAST(tl);- }+ if (IS_ERR(tl))+ return PTR_ERR(tl); intel_context_enter(ce); if (throttle) rq = eb_throttle(eb, ce); intel_context_timeline_unlock(tl);+ if (rq) {+ bool nonblock = eb->file->filp->f_flags & O_NONBLOCK;+ long timeout = nonblock ? 0 : MAX_SCHEDULE_TIMEOUT;++ if (i915_request_wait(rq, I915_WAIT_INTERRUPTIBLE,+ timeout) < 0) {+ i915_request_put(rq);++ tl = intel_context_timeline_lock(ce);+ intel_context_exit(ce);+ intel_context_timeline_unlock(tl);++ if (nonblock)+ return -EWOULDBLOCK;+ else+ return -EINTR;+ }+ i915_request_put(rq);+ }++ return 0;+}++static int eb_pin_engine(struct i915_execbuffer *eb, bool throttle)+{+ struct intel_context *ce = eb->context, *child;+ int err;+ int i = 0, j = 0;++ GEM_BUG_ON(eb->args->flags & __EXEC_ENGINE_PINNED);++ if (unlikely(intel_context_is_banned(ce)))+ return -EIO;++ /*+ * Pinning the contexts may generate requests in order to acquire+ * GGTT space, so do this first before we reserve a seqno for+ * ourselves.+ */+ err = intel_context_pin_ww(ce, &eb->ww);+ if (err)+ return err;+ for_each_child(ce, child) {+ err = intel_context_pin_ww(child, &eb->ww);+ GEM_BUG_ON(err); /* perma-pinned should incr a counter */+ }++ for_each_child(ce, child) {+ err = eb_pin_timeline(eb, child, throttle);+ if (err)+ goto unwind;+ ++i;+ }+ err = eb_pin_timeline(eb, ce, throttle);+ if (err)+ goto unwind;+ eb->args->flags |= __EXEC_ENGINE_PINNED;- return rq;+ return 0;++unwind:+ for_each_child(ce, child) {+ if (j++ < i) {+ mutex_lock(&child->timeline->mutex);+ intel_context_exit(child);+ mutex_unlock(&child->timeline->mutex);+ }+ }+ for_each_child(ce, child)+ intel_context_unpin(child);+ intel_context_unpin(ce);+ return err; } static void eb_unpin_engine(struct i915_execbuffer *eb) {- struct intel_context *ce = eb->context;- struct intel_timeline *tl = ce->timeline;+ struct intel_context *ce = eb->context, *child; if (!(eb->args->flags & __EXEC_ENGINE_PINNED)) return; eb->args->flags &= ~__EXEC_ENGINE_PINNED;- mutex_lock(&tl->mutex);+ for_each_child(ce, child) {+ mutex_lock(&child->timeline->mutex);+ intel_context_exit(child);+ mutex_unlock(&child->timeline->mutex);++ intel_context_unpin(child);+ }++ mutex_lock(&ce->timeline->mutex); intel_context_exit(ce);- mutex_unlock(&tl->mutex);+ mutex_unlock(&ce->timeline->mutex); intel_context_unpin(ce); }
@@ -2384,7 +2477,7 @@ eb_select_legacy_ring(struct i915_execbuffer *eb) static int eb_select_engine(struct i915_execbuffer *eb) {- struct intel_context *ce;+ struct intel_context *ce, *child; unsigned int idx; int err;
@@ -2397,6 +2490,20 @@ eb_select_engine(struct i915_execbuffer *eb) if (IS_ERR(ce)) return PTR_ERR(ce);+ if (intel_context_is_parallel(ce)) {+ if (eb->buffer_count < ce->guc_number_children + 1) {+ intel_context_put(ce);+ return -EINVAL;+ }+ if (eb->batch_start_offset || eb->args->batch_len) {+ intel_context_put(ce);+ return -EINVAL;+ }+ }+ eb->num_batches = ce->guc_number_children + 1;++ for_each_child(ce, child)+ intel_context_get(child); intel_gt_pm_get(ce->engine->gt); if (!test_bit(CONTEXT_ALLOC_BIT, &ce->flags)) {
@@ -2404,6 +2511,13 @@ eb_select_engine(struct i915_execbuffer *eb) if (err) goto err; }+ for_each_child(ce, child) {+ if (!test_bit(CONTEXT_ALLOC_BIT, &child->flags)) {+ err = intel_context_alloc_state(child);+ if (err)+ goto err;+ }+ }
Probably could delete this as this should be done when context is perma-pinned
but harmless to leave so may leave it.
quoted hunk
/*
* ABI: Before userspace accesses the GPU (e.g. execbuffer), report
@@ -2414,7 +2528,7 @@ eb_select_engine(struct i915_execbuffer *eb) goto err; eb->context = ce;- eb->engine = ce->engine;+ eb->gt = ce->engine->gt; /* * Make sure engine pool stays alive even if we call intel_context_put
@@ -2912,70 +3227,25 @@ i915_gem_do_execbuffer(struct drm_device *dev, ww_acquire_done(&eb.ww.ctx);- batch = eb.batch->vma;-- /* Allocate a request for this batch buffer nice and early. */- eb.request = i915_request_create(eb.context);- if (IS_ERR(eb.request)) {- err = PTR_ERR(eb.request);- goto err_vma;- }-- if (unlikely(eb.gem_context->syncobj)) {- struct dma_fence *fence;-- fence = drm_syncobj_fence_get(eb.gem_context->syncobj);- err = i915_request_await_dma_fence(eb.request, fence);- dma_fence_put(fence);- if (err)- goto err_ext;- }-- if (in_fence) {- if (args->flags & I915_EXEC_FENCE_SUBMIT)- err = i915_request_await_execution(eb.request,- in_fence);- else- err = i915_request_await_dma_fence(eb.request,- in_fence);- if (err < 0)- goto err_request;- }-- if (eb.fences) {- err = await_fence_array(&eb);- if (err)- goto err_request;- }-- if (out_fence_fd != -1) {- out_fence = sync_file_create(&eb.request->fence);- if (!out_fence) {- err = -ENOMEM;+ out_fence = eb_requests_create(&eb, in_fence, out_fence_fd);+ if (IS_ERR(out_fence)) {+ err = PTR_ERR(out_fence);+ if (eb.requests[0]) goto err_request;- }+ else+ goto err_vma; }- /*- * Whilst this request exists, batch_obj will be on the- * active_list, and so will hold the active reference. Only when this- * request is retired will the the batch_obj be moved onto the- * inactive_list and lose its active reference. Hence we do not need- * to explicitly hold another reference here.- */- eb.request->batch = batch;- if (eb.batch_pool)- intel_gt_buffer_pool_mark_active(eb.batch_pool, eb.request);-- trace_i915_request_queue(eb.request, eb.batch_flags);- err = eb_submit(&eb, batch);+ err = eb_submit(&eb); err_request:- i915_request_get(eb.request);- err = eb_request_add(&eb, err);+ eb_requests_get(&eb);+ err = eb_requests_add(&eb, err); if (eb.fences)- signal_fence_array(&eb);+ signal_fence_array(&eb, eb.composite_fence ?+ eb.composite_fence :+ &eb.requests[0]->fence); if (out_fence) { if (err == 0) {