iommu/arm-smmu-v3: Introduce an RCU-protected invalidation array
Subsystems (3)
v5 cover iommu/arm-smmu-v3: Introduce an RCU-protected invalidation array
This is a work based on Jason's design and algorithm. This implementation follows his initial draft and revising as well.
The new arm_smmu_invs array is an RCU-protected array, mutated when device attach to the domain, iterated when an invalidation is required for IOPTE changes in this domain. This keeps the current invalidation efficiency of a smb_mb() followed by a conditional rwlock replacing the atomic/spinlock combination.
A new data structure is defined for the array and its entry, representing invalidation operations, such as S1_ASID, S2_VMID, and ATS. The algorithm adds and deletes array entries efficiently and also keeps the array sorted so as to group similar invalidations into batches.
During an invalidation, a new invalidation function iterates domain->invs, and converts each entry to the corresponding invalidation command(s). This new function is fully compatible with all the existing use cases, allowing a simple rework/replacement.
Some races to keep in mind:
1) A domain can be shared across SMMU instances. When an SMMU instance is removed, the updated invs array has to be sync-ed via synchronize_rcu() to prevent an concurrent invalidation routine that is accessing the old array from issuing commands to the removed SMMU instance.
2) When there are concurrent IOPTE changes (followed by invalidations) and a domain attachment, the new attachment must not become out of sync at the HW level, meaning that an STE store and invalidation array load must be sequenced by the CPU's memory model.
3) When an ATS-enabled device attaches to a blocking domain, the core code requires a hard fence to ensure all ATS invalidations to the device are completed. Relying on RCU alone requires calling synchronize_rcu() that can be too slow. Instead, when ATS is in use, hold a conditional rwlock till all concurrent invalidations are finished.
Related future work and dependent projects:
* NVIDIA is building systems with > 10 SMMU instances where > 8 are being used concurrently in a single VM. So having 8 copies of an identical S2 page table is not efficient. Instead, all vSMMU instances should check compatibility on a shared S2 iopt, to eliminate 7 copies.
Previous attempt based on the list/spinlock design:
iommu/arm-smmu-v3: Allocate vmid per vsmmu instead of s2_parent
https://lore.kernel.org/all/[email protected]/
now can adopt this invs array, avoiding adding complex lists/locks.
* The guest support for BTM requires temporarily invalidating two ASIDs for a single instance. When it renumbers ASIDs this can now be done via the invs array.
* SVA with multiple devices being used by a single process (NVIDIA today has 4-8) sequentially iterates the invalidations through all instances. This ignores the HW concurrency available in each instance. It would be nice to not spin on each sync but go forward and issue batches to other instances also. Reducing to a single SVA domain shared across instances is required to look at this.
This is on Github:
https://github.com/nicolinc/iommufd/commits/arm_smmu_invs-v5
Changelog
v5:
* Add Reviewed/Acked-by from Jason and Balbir
* Add two inline comments
* Move cmp after the trash entry validation
* Batch commands in arm_smmu_inv_size_too_big case
* Replace kfree_rcu() with kfree() in arm_smmu_domain_free()
v4:
* Fix build errors with CONFIG_KUNIT=n
* Fix uninitialized cmp in arm_smmu_invs_unref()
* Add missing "__rcu" in struct arm_smmu_inv_state
* Add missing rcu_derference_protected() in arm_smmu_domain_free()
* Bisect two paths for the conditional lock in arm_smmu_domain_inv_range() to fix a sparse waring
v3:
* Add Reviewed/Acked-by from Jason and Balbir
* Rebase on v6.18-rc1
* Drop arm_smmu_invs_dbg()
* Improve kdocs and inline/commit comments
* Rename arm_smmu_invs_cmp to arm_smmu_inv_cmp
* Rename arm_smmu_invs_merge_cmp to arm_smmu_invs_cmp
* Call arm_smmu_invs_flush_iotlb_tags() from arm_smmu_invs_unref()
* Unconditionally trim the invs->num_invs inside arm_smmu_invs_unref(), and simplify arm_smmu_install_old_domain_invs().
v2:
https://lore.kernel.org/all/[email protected]/
* Rebase on v6.17-rc5
* Improve kdocs and inline comments
* Add arm_smmu_invs_dbg() for tracing
* Use users refcount to replace todel flag
* Initialize num_invs in arm_smmu_invs_alloc()
* Add a struct arm_smmu_inv_state to group invs pointers
* Add in struct arm_smmu_invs two flags (has_ats and old)
* Rename master->invs to master->build_invs, and sort the array
* Rework arm_smmu_domain_inv_range() and arm_smmu_invs_end_batch()
* Copy entries by struct arm_smmu_inv in arm_smmu_master_build_invs()
* Add arm_smmu_invs_flush_iotlb_tags() for IOTLB flush by last device
* Rework three invs mutation helpers, and prioritize use the in-place mutation for detach
* Take writer's lock unconditionally but keep it short, and only take reader's lock conditionally on a has_ats flag
v1:
https://lore.kernel.org/all/[email protected]/
Thanks
Nicolin
Jason Gunthorpe (1):
iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Nicolin Chen (6):
iommu/arm-smmu-v3: Explicitly set smmu_domain->stage for SVA iommu/arm-smmu-v3: Add an inline arm_smmu_domain_free() iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array iommu/arm-smmu-v3: Populate smmu_domain->invs when attaching masters iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range() iommu/arm-smmu-v3: Perform per-domain invalidations using
arm_smmu_invs
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 136 ++- .../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 32 +- .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 93 ++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 851 +++++++++++++++--- 4 files changed, 937 insertions(+), 175 deletions(-)
Signature · 1 line
2.43.0
PATCH v5 1/7 iommu/arm-smmu-v3: Explicitly set smmu_domain->stage for SVA
Both the ARM_SMMU_DOMAIN_S1 case and the SVA case use ASID, requiring ASID based invalidation commands to flush the TLB.
Define an ARM_SMMU_DOMAIN_SVA to make the SVA case clear to share the same path with the ARM_SMMU_DOMAIN_S1 case, which will be a part of the routine to build a new per-domain invalidation array.
There is no function change.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 1 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 3 +++ 3 files changed, 5 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +1 -0
@@ -858,6 +858,7 @@ struct arm_smmu_master {enum arm_smmu_domain_stage {ARM_SMMU_DOMAIN_S1 =0 ,ARM_SMMU_DOMAIN_S2 , +ARM_SMMU_DOMAIN_SVA , };struct arm_smmu_domain {
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c +1 -0
@@ -346,6 +346,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,* ARM_SMMU_FEAT_RANGE_INV is present */ smmu_domain->domain.pgsize_bitmap =PAGE_SIZE ; + smmu_domain->stage =ARM_SMMU_DOMAIN_SVA ; smmu_domain->smmu = smmu; ret = xa_alloc(&arm_smmu_asid_xa, &asid, smmu_domain,
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3 -0
@@ -3064,6 +3064,9 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)arm_smmu_install_ste_for_dev (master, &target);arm_smmu_clear_cd (master,IOMMU_NO_PASID );break ; +default : +WARN_ON (true); +break ; }arm_smmu_attach_commit (&state);
Signature · 1 line
2.43.0
PATCH v5 2/7 iommu/arm-smmu-v3: Add an inline arm_smmu_domain_free()
In reply to Nicolin ChenThere will be a bit more things to free than smmu_domain itself. So keep a simple inline function in the header to share aross files.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 5 +++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 2 +- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +5 -0
@@ -954,6 +954,11 @@ extern struct mutex arm_smmu_asid_lock;struct arm_smmu_domain *arm_smmu_domain_alloc (void ); +static inline void arm_smmu_domain_free (struct arm_smmu_domain *smmu_domain) +{ +kfree (smmu_domain); +} +void arm_smmu_clear_cd (struct arm_smmu_master *master,ioasid_t ssid);struct arm_smmu_cd *arm_smmu_get_cd_ptr (struct arm_smmu_master *master,u32 ssid);
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c +1 -1
@@ -365,6 +365,6 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev, err_asid:xa_erase (&arm_smmu_asid_xa, smmu_domain->cd.asid); err_free: -kfree (smmu_domain); +arm_smmu_domain_free (smmu_domain);return ERR_PTR (ret); }
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +2 -2
@@ -2492,7 +2492,7 @@ static void arm_smmu_domain_free_paging(struct iommu_domain *domain)ida_free (&smmu->vmid_map, cfg->vmid); } -kfree (smmu_domain); +arm_smmu_domain_free (smmu_domain); } static int arm_smmu_domain_finalise_s1(struct arm_smmu_device *smmu,
@@ -3353,7 +3353,7 @@ arm_smmu_domain_alloc_paging_flags(struct device *dev, u32 flags,return &smmu_domain->domain; err_free: -kfree (smmu_domain); +arm_smmu_domain_free (smmu_domain);return ERR_PTR (ret); }
Signature · 1 line
2.43.0
PATCH v5 3/7 iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
In reply to Nicolin ChenFrom: Jason Gunthorpe <[email protected]>
Create a new data structure to hold an array of invalidations that need to be performed for the domain based on what masters are attached, to replace the single smmu pointer and linked list of masters in the current design.
Each array entry holds one of the invalidation actions - S1_ASID, S2_VMID, ATS or their variant with information to feed invalidation commands to HW. It is structured so that multiple SMMUs can participate in the same array, removing one key limitation of the current system.
To maximize performance, a sorted array is used as the data structure. It allows grouping SYNCs together to parallelize invalidations. For instance, it will group all the ATS entries after the ASID/VMID entry, so they will all be pushed to the PCI devices in parallel with one SYNC.
To minimize the locking cost on the invalidation fast path (reader of the invalidation array), the array is managed with RCU.
Provide a set of APIs to add/delete entries to/from an array, which cover cannot-fail attach cases, e.g. attaching to arm_smmu_blocked_domain. Also add kunit coverage for those APIs.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 91 +++++++ .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 93 +++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 248 ++++++++++++++++++ 3 files changed, 432 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +91 -0
@@ -649,6 +649,85 @@ struct arm_smmu_cmdq_batch {int num; }; +/* +* The order here also determines the sequence in which commands are sent to the +* command queue. E.g. TLBI must be done before ATC_INV. +*/ +enum arm_smmu_inv_type { +INV_TYPE_S1_ASID , +INV_TYPE_S2_VMID , +INV_TYPE_S2_VMID_S1_CLEAR , +INV_TYPE_ATS , +INV_TYPE_ATS_FULL , +}; + +struct arm_smmu_inv { +struct arm_smmu_device *smmu; +u8 type; +u8 size_opcode; +u8 nsize_opcode; +u32 id;/* ASID or VMID or SID */ +union { +size_t pgsize;/* ARM_SMMU_FEAT_RANGE_INV */ +u32 ssid;/* INV_TYPE_ATS */ + }; + +refcount_t users;/* users=0 to mark as a trash to be purged */ +}; + +static inline bool arm_smmu_inv_is_ats (struct arm_smmu_inv *inv) +{ +return inv->type ==INV_TYPE_ATS || inv->type ==INV_TYPE_ATS_FULL ; +} + +/** +* struct arm_smmu_invs - Per-domain invalidation array +* @num_invs: number of invalidations in the flexible array +* @rwlock: optional rwlock to fench ATS operations +* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL +* @rcu: rcu head for kfree_rcu() +* @inv: flexible invalidation array +* +* The arm_smmu_invs is an RCU data structure. During a ->attach_dev callback, +* arm_smmu_invs_merge(), arm_smmu_invs_unref() and arm_smmu_invs_purge() will +* be used to allocate a new copy of an old array for addition and deletion in +* the old domain's and new domain's invs arrays. +* +* The arm_smmu_invs_unref() mutates a given array, by internally reducing the +* users counts of some given entries. This exists to support a no-fail routine +* like attaching to an IOMMU_DOMAIN_BLOCKED. And it could pair with a followup +* arm_smmu_invs_purge() call to generate a new clean array. +* +* Concurrent invalidation thread will push every invalidation described in the +* array into the command queue for each invalidation event. It is designed like +* this to optimize the invalidation fast path by avoiding locks. +* +* A domain can be shared across SMMU instances. When an instance gets removed, +* it would delete all the entries that belong to that SMMU instance. Then, a +* synchronize_rcu() would have to be called to sync the array, to prevent any +* concurrent invalidation thread accessing the old array from issuing commands +* to the command queue of a removed SMMU instance. +*/ +struct arm_smmu_invs { +size_t num_invs; +rwlock_t rwlock; +bool has_ats; +struct rcu_head rcu; +struct arm_smmu_inv inv[]; +}; + +static inline struct arm_smmu_invs *arm_smmu_invs_alloc (size_t num_invs) +{ +struct arm_smmu_invs *new_invs; + + new_invs =kzalloc (struct_size (new_invs, inv, num_invs),GFP_KERNEL ); +if (!new_invs) +return ERR_PTR (-ENOMEM ); +rwlock_init (&new_invs->rwlock); + new_invs->num_invs = num_invs; +return new_invs; +} +struct arm_smmu_evtq {struct arm_smmu_queue q;struct iopf_queue *iopf;
@@ -875,6 +954,8 @@ struct arm_smmu_domain {struct iommu_domain domain; +struct arm_smmu_invs __rcu *invs; +/* List of struct arm_smmu_master_domain */ struct list_head devices;spinlock_t devices_lock;
@@ -923,6 +1004,14 @@ void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,void arm_smmu_make_sva_cd (struct arm_smmu_cd *target,struct arm_smmu_master *master,struct mm_struct *mm,u16 asid); + +struct arm_smmu_invs *arm_smmu_invs_merge (struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_merge); +size_t arm_smmu_invs_unref (struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_unref, +void (*flush_fn)(struct arm_smmu_inv *inv)); +struct arm_smmu_invs *arm_smmu_invs_purge (struct arm_smmu_invs *invs, +size_t num_trashes);#endif struct arm_smmu_master_domain {
@@ -956,6 +1045,8 @@ struct arm_smmu_domain *arm_smmu_domain_alloc(void);static inline void arm_smmu_domain_free (struct arm_smmu_domain *smmu_domain) { +/* No concurrency with invalidation is possible at this point */ +kfree (rcu_dereference_protected (smmu_domain->invs, true));kfree (smmu_domain); }
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c +93 -0
105 lines not on this page · raw message
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +248 -0
273 lines not on this page · raw message
Signature · 1 line
2.43.0
Will Deacon
maintainerRe: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 97 lines · Nicolin Chen
From: Jason Gunthorpe <[email protected]>
Create a new data structure to hold an array of invalidations that need to be performed for the domain based on what masters are attached, to replace the single smmu pointer and linked list of masters in the current design.
Each array entry holds one of the invalidation actions -
S1_ASID,S2_VMID, ATS or their variant with information to feed invalidation commands to HW. It is structured so that multiple SMMUs can participate in the same array, removing one key limitation of the current system.To maximize performance, a sorted array is used as the data structure. It allows grouping SYNCs together to parallelize invalidations. For instance, it will group all the ATS entries after the ASID/VMID entry, so they will all be pushed to the PCI devices in parallel with one SYNC.
To minimize the locking cost on the invalidation fast path (reader of the invalidation array), the array is managed with RCU.
Provide a set of APIs to add/delete entries to/from an array, which cover cannot-fail attach cases, e.g. attaching to
arm_smmu_blocked_domain. Also add kunit coverage for those APIs.Signed-off-by Jason Gunthorpe <[email protected]>Reviewed-by Jason Gunthorpe <[email protected]>Co-developed-by Nicolin Chen <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 91 +++++++ .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 93 +++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 248 ++++++++++++++++++ 3 files changed, 432 insertions(+)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +56
-0@@ -649,6 +649,85 @@ struct arm_smmu_cmdq_batch {int num; }; +/* +* The order here also determines the sequence in which commands are sent to the +* command queue. E.g. TLBI must be done before ATC_INV. +*/ +enum arm_smmu_inv_type { +INV_TYPE_S1_ASID , +INV_TYPE_S2_VMID , +INV_TYPE_S2_VMID_S1_CLEAR , +INV_TYPE_ATS , +INV_TYPE_ATS_FULL , +}; + +struct arm_smmu_inv { +struct arm_smmu_device *smmu; +u8 type; +u8 size_opcode; +u8 nsize_opcode; +u32 id;/* ASID or VMID or SID */ +union { +size_t pgsize;/* ARM_SMMU_FEAT_RANGE_INV */ +u32 ssid;/* INV_TYPE_ATS */ + }; + +refcount_t users;/* users=0 to mark as a trash to be purged */ +}; + +static inline bool arm_smmu_inv_is_ats (struct arm_smmu_inv *inv) +{ +return inv->type ==INV_TYPE_ATS || inv->type ==INV_TYPE_ATS_FULL ; +} + +/** +* struct arm_smmu_invs - Per-domain invalidation array +* @num_invs: number of invalidations in the flexible array +* @rwlock: optional rwlock to fench ATS operations +* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL +* @rcu: rcu head for kfree_rcu() +* @inv: flexible invalidation array +* +* The arm_smmu_invs is an RCU data structure. During a ->attach_dev callback, +* arm_smmu_invs_merge(), arm_smmu_invs_unref() and arm_smmu_invs_purge() will +* be used to allocate a new copy of an old array for addition and deletion in +* the old domain's and new domain's invs arrays. +* +* The arm_smmu_invs_unref() mutates a given array, by internally reducing the +* users counts of some given entries. This exists to support a no-fail routine +* like attaching to an IOMMU_DOMAIN_BLOCKED. And it could pair with a followup +* arm_smmu_invs_purge() call to generate a new clean array. +* +* Concurrent invalidation thread will push every invalidation described in the +* array into the command queue for each invalidation event. It is designed like +* this to optimize the invalidation fast path by avoiding locks. +* +* A domain can be shared across SMMU instances. When an instance gets removed, +* it would delete all the entries that belong to that SMMU instance. Then, a +* synchronize_rcu() would have to be called to sync the array, to prevent any
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 717
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +10
-0+* concurrent invalidation thread accessing the old array from issuing commands +* to the command queue of a removed SMMU instance. +*/ +struct arm_smmu_invs { +size_t num_invs; +rwlock_t rwlock; +bool has_ats; +struct rcu_head rcu; +struct arm_smmu_inv inv[]; +};
Can you use __counted_by(num_invs) here?
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 725
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +8
-0+ +static inline struct arm_smmu_invs *arm_smmu_invs_alloc (size_t num_invs) +{ +struct arm_smmu_invs *new_invs; + + new_invs =kzalloc (struct_size (new_invs, inv, num_invs),GFP_KERNEL ); +if (!new_invs) +return ERR_PTR (-ENOMEM );
Just return NULL on failure like most allocator functions?
Quoted 5 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +5
-0+rwlock_init (&new_invs->rwlock); + new_invs->num_invs = num_invs; +return new_invs; +} +
[...]
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
Quoted 11 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +11
-0+VISIBLE_IF_KUNIT +struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_merge) +{ +struct arm_smmu_invs *new_invs; +struct arm_smmu_inv *new; +size_t num_trashes =0 ; +size_t num_adds =0 ; +size_t i, j; + +for (i = j =0 ; i < invs->num_invs || j < to_merge->num_invs;) {
Maybe worth having a simple iterator macro for this?
Quoted 56 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +56
-0+int cmp; + +/* Skip any trash entry */ +if (i < invs->num_invs && !refcount_read (&invs->inv[i].users)) { + num_trashes++; + i++; +continue ; + } + + cmp =arm_smmu_invs_cmp (invs, i, to_merge, j); +if (cmp <0 ) { +/* not found in to_merge, leave alone */ + i++; + }else if (cmp ==0 ) { +/* same item */ + i++; + j++; + }else { +/* unique to to_merge */ + num_adds++; + j++; + } + } + + new_invs =arm_smmu_invs_alloc (invs->num_invs - num_trashes + num_adds); +if (IS_ERR (new_invs)) +return new_invs; + + new = new_invs->inv; +for (i = j =0 ; i < invs->num_invs || j < to_merge->num_invs;) { +int cmp; + +if (i < invs->num_invs && !refcount_read (&invs->inv[i].users)) { + i++; +continue ; + } + + cmp =arm_smmu_invs_cmp (invs, i, to_merge, j); +if (cmp <0 ) { + *new = invs->inv[i]; + i++; + }else if (cmp ==0 ) { + *new = invs->inv[i]; +refcount_inc (&new->users); + i++; + j++; + }else { + *new = to_merge->inv[j]; +refcount_set (&new->users,1 ); + j++; + } + +/* +* Check that the new array is sorted. This also validates that +* to_merge is sorted. +*/
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +10
-0+if (new != new_invs->inv) +WARN_ON_ONCE (arm_smmu_inv_cmp (new -1 , new) ==1 ); + new++; + } + +WARN_ON (new != new_invs->inv + new_invs->num_invs); + +return new_invs; +} +EXPORT_SYMBOL_IF_KUNIT (arm_smmu_invs_merge);
There's nothing really SMMU-specific about this data structure manipulation. Do you think we can abstract the invalidation array concept into a library which other IOMMU drivers could use too?
Will
Re: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 7 lines · Will Deacon
Quoted 1 line · Nicolin Chen
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_merge);
There's nothing really SMMU-specific about this data structure manipulation. Do you think we can abstract the invalidation array concept into a library which other IOMMU drivers could use too?
Yea, I am trying to shift to that at this moment, hopefully to combine the iotlb tag (asid/vmid) allocation as well. We do see it could be quite useful in AMD driver already.
And I will address the other remarks of yours.
Thanks
Nicolin
Re: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 5 lines · Nicolin Chen
Quoted 4 lines · Will Deacon
Quoted 1 line · Nicolin Chen
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_merge);
Quoted 7 lines
Quoted 3 lines
There's nothing really SMMU-specific about this data structure manipulation. Do you think we can abstract the invalidation array concept into a library which other IOMMU drivers could use too?
Yea, I am trying to shift to that at this moment, hopefully to combine the iotlb tag (asid/vmid) allocation as well. We do see it could be quite useful in AMD driver already.
I do want to see this, but also without doing work on the other drivers to integrate something it will be hard to design.
My preference is to get the basic system agreed here in ARM and then move to a generalization. There is quite a bit of work needed in the other drivers before they could use it - which makes me worry it might never happen anyhow. riscv is probably the closest, followed by intel.
Alternatively we could try to just move it as is to a little library without pushing for any generalization at this moment.
Jason
Re: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 14 lines · Jason Gunthorpe
Quoted 5 lines · Nicolin Chen
Quoted 4 lines · Will Deacon
Quoted 1 line · Nicolin Chen
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_merge);
Quoted 7 lines
Quoted 3 lines
There's nothing really SMMU-specific about this data structure manipulation. Do you think we can abstract the invalidation array concept into a library which other IOMMU drivers could use too?
Yea, I am trying to shift to that at this moment, hopefully to combine the iotlb tag (asid/vmid) allocation as well. We do see it could be quite useful in AMD driver already.
Quoted 7 lines
I do want to see this, but also without doing work on the other drivers to integrate something it will be hard to design.
My preference is to get the basic system agreed here in ARM and then move to a generalization. There is quite a bit of work needed in the other drivers before they could use it - which makes me worry it might never happen anyhow. riscv is probably the closest, followed by intel.
OK. I'm sending a v6, keeping it as-is. I think this itself could be merged as an introductory series, so long as Will is okay with it.
Meanwhile, I will spare a bit more time to organize the ASID/VMID allocation (part-2) to hopefully integrate into the array helpers that will be required to share a nesting parent domain.
After that, we may think of shifting it to a core library.
Thanks
Nicolin
Re: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
In reply to Will DeaconQuoted 14 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused · line 1071
Quoted 11 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +11
-0+VISIBLE_IF_KUNIT +struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_merge) +{ +struct arm_smmu_invs *new_invs; +struct arm_smmu_inv *new; +size_t num_trashes =0 ; +size_t num_adds =0 ; +size_t i, j; + +for (i = j =0 ; i < invs->num_invs || j < to_merge->num_invs;) {Maybe worth having a simple iterator macro for this?
I added two macros:
+#define arm_smmu_invs_for_each_inv(invs, idx, inv) \
+ for (idx = 0, inv = &invs->inv[0]; idx < invs->num_invs; \
+ inv = &invs->inv[++idx])
+#define arm_smmu_invs_for_each_idx_dual(invs1, idx1, invs2, idx2) \
+ for (idx1 = idx2 = 0; idx1 < invs1->num_invs || idx2 < invs2->num_invs;)
Thanks
Nicolin
Re: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 18 lines · Nicolin Chen
Quoted 14 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused · line 1071
Quoted 11 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +11
-0+VISIBLE_IF_KUNIT +struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_merge) +{ +struct arm_smmu_invs *new_invs; +struct arm_smmu_inv *new; +size_t num_trashes =0 ; +size_t num_adds =0 ; +size_t i, j; + +for (i = j =0 ; i < invs->num_invs || j < to_merge->num_invs;) {Maybe worth having a simple iterator macro for this?
I added two macros:
Review
Quoted 5 lines
(quoted diff) +5
-0+#define arm_smmu_invs_for_each_inv(invs, idx, inv) \ + for (idx = 0, inv = &invs->inv[0]; idx < invs->num_invs; \ + inv = &invs->inv[++idx]) +#define arm_smmu_invs_for_each_idx_dual(invs1, idx1, invs2, idx2) \ + for (idx1 = idx2 = 0; idx1 < invs1->num_invs || idx2 < invs2->num_invs;)
I think pull more stuff in. Something like this:
static inline struct arm_smmu_inv * arm_smmu_invs_iter_next(struct arm_smmu_invs *invs, size_t next,
size_t *idx)
{
while (true) {
if (next >= invs->num_invs) {
*idx = next;
return NULL;
}
if (!refcount_read(&invs->inv[next].users)) {
next++;
continue;
}
*idx = next;
return &invs->inv[next];
}
}static int arm_smmu_inv_cmp(const struct arm_smmu_inv *l,
const struct arm_smmu_inv *r)
{
if (l->smmu != r->smmu)
return cmp_int((uintptr_t)l->smmu, (uintptr_t)r->smmu);
if (l->type != r->type)
return cmp_int(l->type, r->type);
return cmp_int(l->id, r->id);
}static inline int arm_smmu_invs_iter_next_cmp(struct arm_smmu_invs *invs_lhs,
size_t next_lhs, size_t *idx_lhs, struct arm_smmu_invs *invs_rhs, size_t next_rhs, size_t *idx_rhs)
{
struct arm_smmu_inv *cur_lhs =
arm_smmu_invs_iter_next(invs_lhs, 0, idx_lhs);
/*
* Compare of two sorted arrays items. If one side is past the end of
* the array, return the other side to let it run out the iteration.
*/
if (!cur_lhs)
return -1;
if (next_rhs >= invs_rhs->num_invs)
return 1;
return arm_smmu_inv_cmp(cur_lhs, &invs_rhs->inv[next_rhs]);
}/*
* Iterates over all non-trash entries in invs. idx is a stack variable
* to store the index, cur is a stack variable of 'struct arm_smmu_inv *' */
#define arm_smmu_invs_for_each_inv(invs, idx, cur) \ for (cur = arm_smmu_invs_iter_next(invs, 0, &(idx)); cur; \ cur = arm_smmu_invs_iter_next(invs, idx + 1, &(idx)))
/*
* Iterate over two sorted arrays computing a merge sort */
#define arm_smmu_invs_for_each_merge(invs_lhs, idx_lhs, invs_rhs, idx_rhs, \ cmp) \ for (cmp = arm_smmu_invs_iter_next_cmp(invs_lhs, 0, &(idx_lhs), \ invs_rhs, 0, &(idx_rhs)); \ idx_lhs < invs_lhs->num_invs || idx_rhs < invs_rhs->num_invs; \ cmp = arm_smmu_invs_iter_next_cmp( \ invs_lhs, idx_lhs + (cmp <= 0 ? 1 : 0), &(idx_lhs), \ invs_rhs, idx_rhs + (cmp >= 0 ? 1 : 0), &(idx_rhs)))
And then change the loops computing num_trash to work directly on actual things ignoring trash:
arm_smmu_invs_for_each_merge(invs, i, to_merge, j, cmp) new_size++; new_invs = arm_smmu_invs_alloc(new_size);
Name should probably be for_each_.... though
Jason
Re: [PATCH v5 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 96 lines · Jason Gunthorpe
Quoted 18 lines · Nicolin Chen
Quoted 14 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused · line 1071
Quoted 11 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +11
-0+VISIBLE_IF_KUNIT +struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_merge) +{ +struct arm_smmu_invs *new_invs; +struct arm_smmu_inv *new; +size_t num_trashes =0 ; +size_t num_adds =0 ; +size_t i, j; + +for (i = j =0 ; i < invs->num_invs || j < to_merge->num_invs;) {Maybe worth having a simple iterator macro for this?
I added two macros:
Review
Quoted 5 lines
(quoted diff) +5
-0+#define arm_smmu_invs_for_each_inv(invs, idx, inv) \ + for (idx = 0, inv = &invs->inv[0]; idx < invs->num_invs; \ + inv = &invs->inv[++idx]) +#define arm_smmu_invs_for_each_idx_dual(invs1, idx1, invs2, idx2) \ + for (idx1 = idx2 = 0; idx1 < invs1->num_invs || idx2 < invs2->num_invs;)I think pull more stuff in. Something like this:
static inline struct
arm_smmu_inv* arm_smmu_invs_iter_next(structarm_smmu_invs*invs,size_tnext,
size_t*idx){ while (true) { if (next >= invs->num_invs) { *idx = next; return NULL; } if (!refcount_read(&invs->inv[next].users)) { next++; continue; } *idx = next; return &invs->inv[next]; } }static int arm_smmu_inv_cmp(const struct
arm_smmu_inv*l,
const structarm_smmu_inv*r){ if (l->smmu != r->smmu) return cmp_int((uintptr_t)l->smmu, (uintptr_t)r->smmu); if (l->type != r->type) return cmp_int(l->type, r->type); return cmp_int(l->id, r->id); }static inline int arm_smmu_invs_iter_next_cmp(struct
arm_smmu_invs*invs_lhs,
size_tnext_lhs,size_t*idx_lhs, structarm_smmu_invs*invs_rhs,size_tnext_rhs,size_t*idx_rhs){ struct arm_smmu_inv *cur_lhs = arm_smmu_invs_iter_next(invs_lhs, 0, idx_lhs); /* * Compare of two sorted arrays items. If one side is past the end of * the array, return the other side to let it run out the iteration. */ if (!cur_lhs) return -1; if (next_rhs >= invs_rhs->num_invs) return 1; return arm_smmu_inv_cmp(cur_lhs, &invs_rhs->inv[next_rhs]); }/*
* Iterates over all non-trash entries in invs. idx is a stack variable
* to store the index, cur is a stack variable of 'structarm_smmu_inv*' */#define arm_smmu_invs_for_each_inv(invs, idx, cur) \ for (cur = arm_smmu_invs_iter_next(invs, 0, &(idx)); cur; \ cur = arm_smmu_invs_iter_next(invs, idx + 1, &(idx)))/*
* Iterate over two sorted arrays computing a merge sort */#define arm_smmu_invs_for_each_merge(invs_lhs, idx_lhs, invs_rhs, idx_rhs, \ cmp) \ for (cmp = arm_smmu_invs_iter_next_cmp(invs_lhs, 0, &(idx_lhs), \ invs_rhs, 0, &(idx_rhs)); \ idx_lhs < invs_lhs->num_invs || idx_rhs < invs_rhs->num_invs; \ cmp = arm_smmu_invs_iter_next_cmp( \ invs_lhs, idx_lhs + (cmp <= 0 ? 1 : 0), &(idx_lhs), \ invs_rhs, idx_rhs + (cmp >= 0 ? 1 : 0), &(idx_rhs)))
Quoted 5 lines
And then change the loops computing
num_trashto work directly on actual things ignoring trash:arm_smmu_invs_for_each_merge(invs, i, to_merge, j, cmp) new_size++; new_invs = arm_smmu_invs_alloc(new_size);
Cool. I will integrate this and send v6 today. Thanks for the help!
Quoted 1 line
Name should probably be
for_each_.... though
Hmm, I thought it's more common to see structname_for_each_item, such as list_for_each_item and xa_for_each?
Thanks
Nicolin
PATCH v5 4/7 iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
In reply to Nicolin ChenWhen a master is attached from an old domain to a new domain, it needs to build an invalidation array to delete and add the array entries from/onto the invalidation arrays of those two domains, passed via the to_merge and to_unref arguments into arm_smmu_invs_merge/unref() respectively.
Since the master->num_streams might differ across masters, a memory would have to be allocated when building an to_merge/to_unref array which might fail with -ENOMEM.
On the other hand, an attachment to arm_smmu_blocked_domain must not fail so it's the best to avoid any memory allocation in that path.
Pre-allocate a fixed size invalidation array for every master. This array will be used as a scratch to fill dynamically when building a to_merge or to_unref invs array. Sort fwspec->ids in an ascending order to fit to the arm_smmu_invs_merge() function.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 8 ++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 +++++++++++++++++++++ 2 files changed, 35 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +8 -0
@@ -922,6 +922,14 @@ struct arm_smmu_master {struct arm_smmu_device *smmu;struct device *dev;struct arm_smmu_stream *streams; +/* +* Scratch memory for a to_merge or to_unref array to build a per-domain +* invalidation array. It'll be pre-allocated with enough enries for all +* possible build scenarios. It can be used by only one caller at a time +* until the arm_smmu_invs_merge/unref() finishes. Must be locked by the +* iommu_group mutex. +*/ +struct arm_smmu_invs *build_invs;struct arm_smmu_vmaster *vmaster;/* use smmu->streams_mutex */ /* Locked by the iommu core using the group mutex */ struct arm_smmu_ctx_desc_cfg cd_table;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +27 -0
@@ -3693,12 +3693,22 @@ static int arm_smmu_init_sid_strtab(struct arm_smmu_device *smmu, u32 sid)return 0 ; } +static int arm_smmu_ids_cmp (const void *_l,const void *_r) +{ + const typeof_member(struct iommu_fwspec, ids[0]) *l = _l; + const typeof_member(struct iommu_fwspec, ids[0]) *r = _r; + +return cmp_int (*l, *r); +} +static int arm_smmu_insert_master (struct arm_smmu_device *smmu,struct arm_smmu_master *master) {int i;int ret =0 ;struct iommu_fwspec *fwspec =dev_iommu_fwspec_get (master->dev); +bool ats_supported =dev_is_pci (master->dev) && +pci_ats_supported (to_pci_dev (master->dev)); master->streams =kcalloc (fwspec->num_ids,sizeof (*master->streams),GFP_KERNEL );
@@ -3706,6 +3716,21 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,return -ENOMEM ; master->num_streams = fwspec->num_ids; +if (!ats_supported) { +/* Base case has 1 ASID entry or maximum 2 VMID entries */ + master->build_invs =arm_smmu_invs_alloc (2 ); + }else { +/* Put the ids into order for sorted to_merge/to_unref arrays */ +sort_nonatomic (fwspec->ids, fwspec->num_ids, +sizeof (fwspec->ids[0 ]), arm_smmu_ids_cmp,NULL ); +/* ATS case adds num_ids of entries, on top of the base case */ + master->build_invs =arm_smmu_invs_alloc (2 + fwspec->num_ids); + } +if (IS_ERR (master->build_invs)) { +kfree (master->streams); +return PTR_ERR (master->build_invs); + } +mutex_lock (&smmu->streams_mutex);for (i =0 ; i < fwspec->num_ids; i++) {struct arm_smmu_stream *new_stream = &master->streams[i];
@@ -3743,6 +3768,7 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,for (i--; i >=0 ; i--)rb_erase (&master->streams[i].node, &smmu->streams);kfree (master->streams); +kfree (master->build_invs); }mutex_unlock (&smmu->streams_mutex);
@@ -3764,6 +3790,7 @@ static void arm_smmu_remove_master(struct arm_smmu_master *master)mutex_unlock (&smmu->streams_mutex);kfree (master->streams); +kfree (master->build_invs); }static struct iommu_device *arm_smmu_probe_device (struct device *dev)
Signature · 1 line
2.43.0
Will Deacon
maintainerRe: [PATCH v5 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
Quoted 75 lines · Nicolin Chen
When a master is attached from an old domain to a new domain, it needs to build an invalidation array to delete and add the array entries from/onto the invalidation arrays of those two domains, passed via the
to_mergeandto_unrefarguments into arm_smmu_invs_merge/unref() respectively.Since the
master->num_streamsmight differ across masters, a memory would have to be allocated when building an to_merge/to_unref array which might fail with-ENOMEM.On the other hand, an attachment to
arm_smmu_blocked_domainmust not fail so it's the best to avoid any memory allocation in that path.Pre-allocate a fixed size invalidation array for every master. This array will be used as a scratch to fill dynamically when building a
to_mergeorto_unrefinvs array. Sortfwspec->idsin an ascending order to fit to thearm_smmu_invs_merge()function.Co-developed-by Jason Gunthorpe <[email protected]>Signed-off-by Jason Gunthorpe <[email protected]>Reviewed-by Jason Gunthorpe <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 8 ++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 27 +++++++++++++++++++++ 2 files changed, 35 insertions(+)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +8
-0@@ -922,6 +922,14 @@ struct arm_smmu_master {struct arm_smmu_device *smmu;struct device *dev;struct arm_smmu_stream *streams; +/* +* Scratch memory for a to_merge or to_unref array to build a per-domain +* invalidation array. It'll be pre-allocated with enough enries for all +* possible build scenarios. It can be used by only one caller at a time +* until the arm_smmu_invs_merge/unref() finishes. Must be locked by the +* iommu_group mutex. +*/ +struct arm_smmu_invs *build_invs;struct arm_smmu_vmaster *vmaster;/* use smmu->streams_mutex */ /* Locked by the iommu core using the group mutex */ struct arm_smmu_ctx_desc_cfg cd_table;drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0@@ -3693,12 +3693,22 @@ static int arm_smmu_init_sid_strtab(struct arm_smmu_device *smmu, u32 sid)return 0 ; } +static int arm_smmu_ids_cmp (const void *_l,const void *_r) +{ + const typeof_member(struct iommu_fwspec, ids[0]) *l = _l; + const typeof_member(struct iommu_fwspec, ids[0]) *r = _r; + +return cmp_int (*l, *r); +} +static int arm_smmu_insert_master (struct arm_smmu_device *smmu,struct arm_smmu_master *master) {int i;int ret =0 ;struct iommu_fwspec *fwspec =dev_iommu_fwspec_get (master->dev); +bool ats_supported =dev_is_pci (master->dev) && +pci_ats_supported (to_pci_dev (master->dev)); master->streams =kcalloc (fwspec->num_ids,sizeof (*master->streams),GFP_KERNEL );@@ -3706,6 +3716,21 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,return -ENOMEM ; master->num_streams = fwspec->num_ids;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_insert_master(struct arm_smmu_device *smmu, · line 3727
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +9
-0+if (!ats_supported) { +/* Base case has 1 ASID entry or maximum 2 VMID entries */ + master->build_invs =arm_smmu_invs_alloc (2 ); + }else { +/* Put the ids into order for sorted to_merge/to_unref arrays */ +sort_nonatomic (fwspec->ids, fwspec->num_ids, +sizeof (fwspec->ids[0 ]), arm_smmu_ids_cmp,NULL ); +/* ATS case adds num_ids of entries, on top of the base case */ + master->build_invs =arm_smmu_invs_alloc (2 + fwspec->num_ids);
Although I can't point at a specific issue here, I'm nervous about mutating the 'fwspec->ids' array from within the driver, The array isn't allocated or populated directly by the driver and so I don't think we really have any business sorting it. Could we hack iommu_fwspec_add_ids() to keep the array ordered instead?
Will
Re: [PATCH v5 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
Quoted 12 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_insert_master(struct arm_smmu_device *smmu, · line 3727
Quoted 5 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+/* Put the ids into order for sorted to_merge/to_unref arrays */ +sort_nonatomic (fwspec->ids, fwspec->num_ids, +sizeof (fwspec->ids[0 ]), arm_smmu_ids_cmp,NULL ); +/* ATS case adds num_ids of entries, on top of the base case */ + master->build_invs =arm_smmu_invs_alloc (2 + fwspec->num_ids);Although I can't point at a specific issue here, I'm nervous about mutating the '
fwspec->ids' array from within the driver, The array isn't allocated or populated directly by the driver and so I don't think we really have any business sorting it. Could we hackiommu_fwspec_add_ids()to keep the array ordered instead?
Yea, I think it makes sense to do it in the core, once we have the data structure provided by the core as well.
Thanks
Nicolin
Re: [PATCH v5 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
Quoted 14 lines · Nicolin Chen
Quoted 12 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_insert_master(struct arm_smmu_device *smmu, · line 3727
Quoted 5 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+/* Put the ids into order for sorted to_merge/to_unref arrays */ +sort_nonatomic (fwspec->ids, fwspec->num_ids, +sizeof (fwspec->ids[0 ]), arm_smmu_ids_cmp,NULL ); +/* ATS case adds num_ids of entries, on top of the base case */ + master->build_invs =arm_smmu_invs_alloc (2 + fwspec->num_ids);Although I can't point at a specific issue here, I'm nervous about mutating the '
fwspec->ids' array from within the driver, The array isn't allocated or populated directly by the driver and so I don't think we really have any business sorting it. Could we hackiommu_fwspec_add_ids()to keep the array ordered instead?
Quoted 2 lines
Yea, I think it makes sense to do it in the core, once we have the data structure provided by the core as well.
I would be more worried about sorting it everywhere for every driver. I feel confident SMMUv3 doesn't use it, but something really old and embedded focused like tegra or omap, IDK.
So I wouldn't propose to change iommu_fwspec_add_ids().
If you want to be conservative then the thing to do is sort the master->streams that arm_smmu_insert_master() copies the fwspec into. It just has to be sorted prior to feeding it into the rbtree.
Then consistently use master->streams as the sorted list.
Jason
Re: [PATCH v5 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
Quoted 15 lines · Jason Gunthorpe
Quoted 14 lines · Nicolin Chen
Quoted 12 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_insert_master(struct arm_smmu_device *smmu, · line 3727
Quoted 5 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+/* Put the ids into order for sorted to_merge/to_unref arrays */ +sort_nonatomic (fwspec->ids, fwspec->num_ids, +sizeof (fwspec->ids[0 ]), arm_smmu_ids_cmp,NULL ); +/* ATS case adds num_ids of entries, on top of the base case */ + master->build_invs =arm_smmu_invs_alloc (2 + fwspec->num_ids);Although I can't point at a specific issue here, I'm nervous about mutating the '
fwspec->ids' array from within the driver, The array isn't allocated or populated directly by the driver and so I don't think we really have any business sorting it. Could we hackiommu_fwspec_add_ids()to keep the array ordered instead?
Quoted 8 lines
Quoted 2 lines
Yea, I think it makes sense to do it in the core, once we have the data structure provided by the core as well.
I would be more worried about sorting it everywhere for every driver. I feel confident SMMUv3 doesn't use it, but something really old and embedded focused like tegra or omap, IDK.
So I wouldn't propose to change
iommu_fwspec_add_ids().
Perhaps a different helper iommu_fwspec_add_ids_sorted()? Drivers can choose to use the sorted version, when they want to implement the invalidation array. And SMMU can be the first only caller.
Quoted 5 lines
If you want to be conservative then the thing to do is sort the
master->streamsthatarm_smmu_insert_master()copies the fwspec into. It just has to be sorted prior to feeding it into the rbtree.Then consistently use
master->streamsas the sorted list.
How about kmemdup() an local id array to bridge betwen fwspec->ids and rbtree?
Thanks
Nicolin
Re: [PATCH v5 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
Quoted 29 lines · Nicolin Chen
Quoted 15 lines · Jason Gunthorpe
Quoted 14 lines · Nicolin Chen
Quoted 12 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_insert_master(struct arm_smmu_device *smmu, · line 3727
Quoted 5 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+/* Put the ids into order for sorted to_merge/to_unref arrays */ +sort_nonatomic (fwspec->ids, fwspec->num_ids, +sizeof (fwspec->ids[0 ]), arm_smmu_ids_cmp,NULL ); +/* ATS case adds num_ids of entries, on top of the base case */ + master->build_invs =arm_smmu_invs_alloc (2 + fwspec->num_ids);Although I can't point at a specific issue here, I'm nervous about mutating the '
fwspec->ids' array from within the driver, The array isn't allocated or populated directly by the driver and so I don't think we really have any business sorting it. Could we hackiommu_fwspec_add_ids()to keep the array ordered instead?Quoted 8 lines
Quoted 2 lines
Yea, I think it makes sense to do it in the core, once we have the data structure provided by the core as well.
I would be more worried about sorting it everywhere for every driver. I feel confident SMMUv3 doesn't use it, but something really old and embedded focused like tegra or omap, IDK.
So I wouldn't propose to change
iommu_fwspec_add_ids().Perhaps a different helper
iommu_fwspec_add_ids_sorted()? Drivers can choose to use the sorted version, when they want to implement the invalidation array. And SMMU can be the first only caller.
Quoted 8 lines
Quoted 5 lines
If you want to be conservative then the thing to do is sort the
master->streamsthatarm_smmu_insert_master()copies the fwspec into. It just has to be sorted prior to feeding it into the rbtree.Then consistently use
master->streamsas the sorted list.How about
kmemdup()an local id array to bridge betwenfwspec->idsand rbtree?
Does this look okay?
@@ -3713,6 +3713,7 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
{
int i;
int ret = 0;
+ u32 *ids, *ids_sorted = NULL;
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(master->dev); bool ats_supported = dev_is_pci(master->dev) &&
pci_ats_supported(to_pci_dev(master->dev));
@@ -3723,26 +3724,38 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
return -ENOMEM;
master->num_streams = fwspec->num_ids;
+ ids = fwspec->ids;
if (!ats_supported) {
/* Base case has 1 ASID entry or maximum 2 VMID entries */ master->build_invs = arm_smmu_invs_alloc(2);
} else {
/* Put the ids into order for sorted to_merge/to_unref arrays */
- sort_nonatomic(fwspec->ids, fwspec->num_ids,
- sizeof(fwspec->ids[0]), arm_smmu_ids_cmp, NULL);
+ if (fwspec->num_ids > 1) {
+ ids = kmemdup_array(fwspec->ids, fwspec->num_ids,
+ sizeof(*ids), GFP_KERNEL);
+ if (!ids) {
+ kfree(master->streams);
+ return -ENOMEM;
+ }
+
+ sort_nonatomic(ids, fwspec->num_ids, sizeof(*ids),
+ arm_smmu_ids_cmp, NULL);
+ ids_sorted = ids;
+ }
/* ATS case adds num_ids of entries, on top of the base case */ master->build_invs = arm_smmu_invs_alloc(2 + fwspec->num_ids);
}
- if (IS_ERR(master->build_invs)) {
+ if (!master->build_invs) {
kfree(master->streams);
- return PTR_ERR(master->build_invs);
+ ret = -ENOMEM;
+ goto out;
}
mutex_lock(&smmu->streams_mutex);
for (i = 0; i < fwspec->num_ids; i++) {
struct arm_smmu_stream *new_stream = &master->streams[i]; struct rb_node *existing;
- u32 sid = fwspec->ids[i];
+ u32 sid = ids[i];
new_stream->id = sid;
new_stream->master = master;
@@ -3779,6 +3792,8 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
}
mutex_unlock(&smmu->streams_mutex);
+out:
+ kfree(ids_sorted);
return ret;
}
Thanks
Nicolin
Re: [PATCH v5 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
In reply to Nicolin ChenQuoted 8 lines · Nicolin Chen
Quoted 5 lines
If you want to be conservative then the thing to do is sort the
master->streamsthatarm_smmu_insert_master()copies the fwspec into. It just has to be sorted prior to feeding it into the rbtree.Then consistently use
master->streamsas the sorted list.How about
kmemdup()an local id array to bridge betwenfwspec->idsand rbtree?
Not needed, just like this:
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +11 -5
@@ -3454,15 +3454,21 @@ static int arm_smmu_insert_master(struct arm_smmu_device *smmu,if (!master->streams)return -ENOMEM ; master->num_streams = fwspec->num_ids; - -mutex_lock (&smmu->streams_mutex);for (i =0 ; i < fwspec->num_ids; i++) {struct arm_smmu_stream *new_stream = &master->streams[i]; -struct rb_node *existing; -u32 sid = fwspec->ids[i]; - new_stream->id = sid; + new_stream->id = fwspec->ids[i]; new_stream->master = master; + } + +sort_nonatomic (master->streams, master->num_streams, +sizeof (master->streams[0 ]), arm_smmu_ids_cmp,NULL ); + +mutex_lock (&smmu->streams_mutex); +for (i =0 ; i < master->num_streams; i++) { +struct arm_smmu_stream *new_stream = &master->streams[i]; +struct rb_node *existing; +u32 sid = new_stream->id; ret =arm_smmu_init_sid_strtab (smmu, sid);if (ret)
cmp would work on a struct arm_smmu_stream.
Jason
PATCH v5 5/7 iommu/arm-smmu-v3: Populate smmu_domain->invs when attaching masters
In reply to Nicolin ChenUpdate the invs array with the invalidations required by each domain type during attachment operations.
Only an SVA domain or a paging domain will have an invs array:
a. SVA domain will add an INV_TYPE_S1_ASID per SMMU and an INV_TYPE_ATS
per SID
b. Non-nesting-parent paging domain with no ATS-enabled master will add
a single INV_TYPE_S1_ASID or INV_TYPE_S2_VMID per SMMU
c. Non-nesting-parent paging domain with ATS-enabled master(s) will do
(b) and add an INV_TYPE_ATS per SID
d. Nesting-parent paging domain will add an INV_TYPE_S2_VMID followed by
an INV_TYPE_S2_VMID_S1_CLEAR per vSMMU. For an ATS-enabled master, it will add an INV_TYPE_ATS_FULL per SID
Note that case #d prepares for a future implementation of VMID allocation which requires a followup series for S2 domain sharing. So when a nesting parent domain is attached through a vSMMU instance using a nested domain. VMID will be allocated per vSMMU instance v.s. currectly per S2 domain.
The per-domain invalidation is not needed until the domain is attached to a master (when it starts to possibly use TLB). This will make it possible to attach the domain to multiple SMMUs and avoid unnecessary invalidation overhead during teardown if no STEs/CDs refer to the domain. It also means that when the last device is detached, the old domain must flush its ASID or VMID, since any new iommu_unmap() call would not trigger invalidations given an empty domain->invs array.
Introduce some arm_smmu_invs helper functions for building scratch arrays, preparing and installing old/new domain's invalidation arrays.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 17 ++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 242 +++++++++++++++++++- 2 files changed, 258 insertions(+), 1 deletion(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +17 -0
@@ -1094,6 +1094,21 @@ static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master)IOMMU_FWSPEC_PCI_RC_CANWBS ; } +/** +* struct arm_smmu_inv_state - Per-domain invalidation array state +* @invs_ptr: points to the domain->invs (unwinding nesting/etc.) or is NULL if +* no change should be made +* @old_invs: the original invs array +* @new_invs: for new domain, this is the new invs array to update domin->invs; +* for old domain, this is the master->build_invs to pass in as the +* to_unref argument to an arm_smmu_invs_unref() call +*/ +struct arm_smmu_inv_state { +struct arm_smmu_invs __rcu **invs_ptr; +struct arm_smmu_invs *old_invs; +struct arm_smmu_invs *new_invs; +}; +struct arm_smmu_attach_state {/* Inputs */ struct iommu_domain *old_domain;
@@ -1103,6 +1118,8 @@ struct arm_smmu_attach_state {ioasid_t ssid;/* Resulting state */ struct arm_smmu_vmaster *vmaster; +struct arm_smmu_inv_state old_domain_invst; +struct arm_smmu_inv_state new_domain_invst;bool ats_enabled; };
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +241 -1
283 lines not on this page · raw message
Signature · 1 line
2.43.0
Will Deacon
maintainerRe: [PATCH v5 5/7] iommu/arm-smmu-v3: Populate smmu_domain->invs when attaching masters
Quoted 47 lines · Nicolin Chen
Update the invs array with the invalidations required by each domain type during attachment operations.
Only an SVA domain or a paging domain will have an invs array:
a. SVA domain will add anINV_TYPE_S1_ASIDper SMMU and anINV_TYPE_ATS
per SIDb. Non-nesting-parent paging domain with no ATS-enabled master will add
a singleINV_TYPE_S1_ASIDorINV_TYPE_S2_VMIDper SMMUc. Non-nesting-parent paging domain with ATS-enabled master(s) will do
(b) and add anINV_TYPE_ATSper SIDd. Nesting-parent paging domain will add an
INV_TYPE_S2_VMIDfollowed by
anINV_TYPE_S2_VMID_S1_CLEARper vSMMU. For an ATS-enabled master, it will add anINV_TYPE_ATS_FULLper SIDNote that case #d prepares for a future implementation of VMID allocation which requires a followup series for S2 domain sharing. So when a nesting parent domain is attached through a vSMMU instance using a nested domain. VMID will be allocated per vSMMU instance v.s. currectly per S2 domain.
The per-domain invalidation is not needed until the domain is attached to a master (when it starts to possibly use TLB). This will make it possible to attach the domain to multiple SMMUs and avoid unnecessary invalidation overhead during teardown if no STEs/CDs refer to the domain. It also means that when the last device is detached, the old domain must flush its ASID or VMID, since any new
iommu_unmap()call would not trigger invalidations given an emptydomain->invsarray.Introduce some
arm_smmu_invshelper functions for building scratch arrays, preparing and installing old/new domain's invalidation arrays.Co-developed-by Jason Gunthorpe <[email protected]>Signed-off-by Jason Gunthorpe <[email protected]>Reviewed-by Jason Gunthorpe <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 17 ++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 242 +++++++++++++++++++- 2 files changed, 258 insertions(+), 1 deletion(-)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +0
-0
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master) · line 1102
Quoted 9 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +6
-0@@ -1094,6 +1094,21 @@ static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master)IOMMU_FWSPEC_PCI_RC_CANWBS ; } +/** +* struct arm_smmu_inv_state - Per-domain invalidation array state +* @invs_ptr: points to the domain->invs (unwinding nesting/etc.) or is NULL if +* no change should be made +* @old_invs: the original invs array +* @new_invs: for new domain, this is the new invs array to update domin->invs;
nit: typo "domin"
Quoted 61 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +11
-0+* for old domain, this is the master->build_invs to pass in as the +* to_unref argument to an arm_smmu_invs_unref() call +*/ +struct arm_smmu_inv_state { +struct arm_smmu_invs __rcu **invs_ptr; +struct arm_smmu_invs *old_invs; +struct arm_smmu_invs *new_invs; +}; +struct arm_smmu_attach_state {/* Inputs */ struct iommu_domain *old_domain;@@ -1103,6 +1118,8 @@ struct arm_smmu_attach_state {ioasid_t ssid;/* Resulting state */ struct arm_smmu_vmaster *vmaster; +struct arm_smmu_inv_state old_domain_invst; +struct arm_smmu_inv_state new_domain_invst;bool ats_enabled; };drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +32
-0@@ -3058,6 +3058,97 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,iopf_queue_remove_device (master->smmu->evtq.iopf, master->dev); } +/* +* Use the preallocated scratch array at master->build_invs, to build a to_merge +* or to_unref array, to pass into a following arm_smmu_invs_merge/unref() call. +* +* Do not free the returned invs array. It is reused, and will be overwritten by +* the next arm_smmu_master_build_invs() call. +*/ +static struct arm_smmu_invs * +arm_smmu_master_build_invs (struct arm_smmu_master *master,bool ats_enabled, +ioasid_t ssid,struct arm_smmu_domain *smmu_domain) +{ +const bool e2h = master->smmu->features &ARM_SMMU_FEAT_E2H ; +struct arm_smmu_invs *build_invs = master->build_invs; +const bool nesting = smmu_domain->nest_parent; +struct arm_smmu_inv *cur; + +iommu_group_mutex_assert (master->dev); + + cur = build_invs->inv; + +switch (smmu_domain->stage) { +case ARM_SMMU_DOMAIN_SVA : +case ARM_SMMU_DOMAIN_S1 : + *cur = (struct arm_smmu_inv ){ + .smmu = master->smmu, + .type =INV_TYPE_S1_ASID , + .id = smmu_domain->cd.asid, + .size_opcode = e2h ?CMDQ_OP_TLBI_EL2_VA : +CMDQ_OP_TLBI_NH_VA , + .nsize_opcode = e2h ?CMDQ_OP_TLBI_EL2_ASID : +CMDQ_OP_TLBI_NH_ASID + };
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_disable_iopf(struct arm_smmu_master *master, · line 3102
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+break ; +case ARM_SMMU_DOMAIN_S2 : + *cur = (struct arm_smmu_inv ){ + .smmu = master->smmu, + .type =INV_TYPE_S2_VMID , + .id = smmu_domain->s2_cfg.vmid, + .size_opcode =CMDQ_OP_TLBI_S2_IPA , + .nsize_opcode =CMDQ_OP_TLBI_S12_VMALL , + }; +break ;
Having a helper to add an invalidation command would make this a little more compact and you could also check against the size of the array.
Quoted 125 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +118
-0+default : +WARN_ON (true); +return NULL ; + } + +/* Range-based invalidation requires the leaf pgsize for calculation */ +if (master->smmu->features &ARM_SMMU_FEAT_RANGE_INV ) + cur->pgsize =__ffs (smmu_domain->domain.pgsize_bitmap); + cur++; + +/* All the nested S1 ASIDs have to be flushed when S2 parent changes */ +if (nesting) { + *cur = (struct arm_smmu_inv ){ + .smmu = master->smmu, + .type =INV_TYPE_S2_VMID_S1_CLEAR , + .id = smmu_domain->s2_cfg.vmid, + .size_opcode =CMDQ_OP_TLBI_NH_ALL , + .nsize_opcode =CMDQ_OP_TLBI_NH_ALL , + }; + cur++; + } + +if (ats_enabled) { +size_t i; + +for (i =0 ; i < master->num_streams; i++) { +/* +* If an S2 used as a nesting parent is changed we have +* no option but to completely flush the ATC. +*/ + *cur = (struct arm_smmu_inv ){ + .smmu = master->smmu, + .type = nesting ?INV_TYPE_ATS_FULL : +INV_TYPE_ATS , + .id = master->streams[i].id, + .ssid = ssid, + .size_opcode =CMDQ_OP_ATC_INV , + .nsize_opcode =CMDQ_OP_ATC_INV , + }; + cur++; + } + } + +/* Note this build_invs must have been sorted */ + + build_invs->num_invs = cur - build_invs->inv; +return build_invs; +} +static void arm_smmu_remove_master_domain (struct arm_smmu_master *master,struct iommu_domain *domain,ioasid_t ssid)@@ -3087,6 +3178,146 @@ static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,kfree (master_domain); } +/* +* During attachment, the updates of the two domain->invs arrays are sequenced: +* 1. new domain updates its invs array, merging master->build_invs +* 2. new domain starts to include the master during its invalidation +* 3. master updates its STE switching from the old domain to the new domain +* 4. old domain still includes the master during its invalidation +* 5. old domain updates its invs array, unreferencing master->build_invs +* +* For 1 and 5, prepare the two updated arrays in advance, handling any changes +* that can possibly failure. So the actual update of either 1 or 5 won't fail. +* arm_smmu_asid_lock ensures that the old invs in the domains are intact while +* we are sequencing to update them. +*/ +static int arm_smmu_attach_prepare_invs (struct arm_smmu_attach_state *state, +struct arm_smmu_domain *new_smmu_domain) +{ +struct arm_smmu_domain *old_smmu_domain = +to_smmu_domain_devices (state->old_domain); +struct arm_smmu_master *master = state->master; +ioasid_t ssid = state->ssid; + +/* A re-attach case doesn't need to update invs array */ +if (new_smmu_domain == old_smmu_domain) +return 0 ; + +/* +* At this point a NULL domain indicates the domain doesn't use the +* IOTLB, see to_smmu_domain_devices(). +*/ +if (new_smmu_domain) { +struct arm_smmu_inv_state *invst = &state->new_domain_invst; +struct arm_smmu_invs *build_invs; + + invst->invs_ptr = &new_smmu_domain->invs; + invst->old_invs =rcu_dereference_protected ( + new_smmu_domain->invs, +lockdep_is_held (&arm_smmu_asid_lock)); + build_invs =arm_smmu_master_build_invs ( + master, state->ats_enabled, ssid, new_smmu_domain); +if (!build_invs) +return -EINVAL ; + + invst->new_invs = +arm_smmu_invs_merge (invst->old_invs, build_invs); +if (IS_ERR (invst->new_invs)) +return PTR_ERR (invst->new_invs); + } + +if (old_smmu_domain) { +struct arm_smmu_inv_state *invst = &state->old_domain_invst; + + invst->invs_ptr = &old_smmu_domain->invs; + invst->old_invs =rcu_dereference_protected ( + old_smmu_domain->invs, +lockdep_is_held (&arm_smmu_asid_lock)); +/* For old_smmu_domain, new_invs points to master->build_invs */ + invst->new_invs =arm_smmu_master_build_invs ( + master, master->ats_enabled, ssid, old_smmu_domain); + } + +return 0 ; +} + +/* Must be installed before arm_smmu_install_ste_for_dev() */ +static void +arm_smmu_install_new_domain_invs (struct arm_smmu_attach_state *state) +{ +struct arm_smmu_inv_state *invst = &state->new_domain_invst; +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_remove_master_domain(struct arm_smmu_master *master, · line 3259
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+if (!invst->invs_ptr) +return ; + +rcu_assign_pointer (*invst->invs_ptr, invst->new_invs); +/* +* We are committed to updating the STE. Ensure the invalidation array +* is visable to concurrent map/unmap threads, and acquire any racying +* IOPTE updates. +*/ +smp_mb ();
Sorry, but the comment hasn't really helped me here. We're ordering the publishing of the invalidation array above before ... what?
Quoted 48 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +48
-0+kfree_rcu (invst->old_invs, rcu); +} + +/* +* When an array entry's users count reaches zero, it means the ASID/VMID is no +* longer being invalidated by map/unmap and must be cleaned. The rule is that +* all ASIDs/VMIDs not in an invalidation array are left cleared in the IOTLB. +*/ +static void arm_smmu_invs_flush_iotlb_tags (struct arm_smmu_inv *inv) +{ +struct arm_smmu_cmdq_ent cmd = {}; + +switch (inv->type) { +case INV_TYPE_S1_ASID : + cmd.tlbi.asid = inv->id; +break ; +case INV_TYPE_S2_VMID : +/* S2_VMID using nsize_opcode covers S2_VMID_S1_CLEAR */ + cmd.tlbi.vmid = inv->id; +break ; +default : +return ; + } + + cmd.opcode = inv->nsize_opcode; +arm_smmu_cmdq_issue_cmd_with_sync (inv->smmu, &cmd); +} + +/* Should be installed after arm_smmu_install_ste_for_dev() */ +static void +arm_smmu_install_old_domain_invs (struct arm_smmu_attach_state *state) +{ +struct arm_smmu_inv_state *invst = &state->old_domain_invst; +struct arm_smmu_invs *old_invs = invst->old_invs; +struct arm_smmu_invs *new_invs; +size_t num_trashes; + +lockdep_assert_held (&arm_smmu_asid_lock); + +if (!invst->invs_ptr) +return ; + + num_trashes =arm_smmu_invs_unref (old_invs, invst->new_invs, + arm_smmu_invs_flush_iotlb_tags); +if (!num_trashes) +return ; + + new_invs =arm_smmu_invs_purge (old_invs, num_trashes);
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_remove_master_domain(struct arm_smmu_master *master, · line 3317
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+if (!new_invs) +return ; + +rcu_assign_pointer (*invst->invs_ptr, new_invs); +/* +* We are committed to updating the STE. Ensure the invalidation array +* is visable to concurrent map/unmap threads, and acquire any racying +* IOPTE updates. +*/ +smp_mb ();
(same here)
Will
Re: [PATCH v5 5/7] iommu/arm-smmu-v3: Populate smmu_domain->invs when attaching masters
Quoted 17 lines · Will Deacon
Quoted 17 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +17
-0+switch (smmu_domain->stage) { +case ARM_SMMU_DOMAIN_SVA : +case ARM_SMMU_DOMAIN_S1 : + *cur = (struct arm_smmu_inv ){ + .smmu = master->smmu, + .type =INV_TYPE_S1_ASID , + .id = smmu_domain->cd.asid, + .size_opcode = e2h ?CMDQ_OP_TLBI_EL2_VA : +CMDQ_OP_TLBI_NH_VA , + .nsize_opcode = e2h ?CMDQ_OP_TLBI_EL2_ASID : +CMDQ_OP_TLBI_NH_ASID + }; +break ; +case ARM_SMMU_DOMAIN_S2 : + *cur = (struct arm_smmu_inv ){ + .smmu = master->smmu, + .type =INV_TYPE_S2_VMID ,
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_disable_iopf(struct arm_smmu_master *master, · line 3102
Quoted 5 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+ .id = smmu_domain->s2_cfg.vmid, + .size_opcode = CMDQ_OP_TLBI_S2_IPA, + .nsize_opcode = CMDQ_OP_TLBI_S12_VMALL, + }; +break ;Having a helper to add an invalidation command would make this a little more compact and you could also check against the size of the array.
Yeah but it makes all the parameters positional instead of nicely named..
Quoted 11 lines
Quoted 11 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +11
-0+/* Must be installed before arm_smmu_install_ste_for_dev() */ +static void +arm_smmu_install_new_domain_invs (struct arm_smmu_attach_state *state) +{ +struct arm_smmu_inv_state *invst = &state->new_domain_invst; + +if (!invst->invs_ptr) +return ; + +rcu_assign_pointer (*invst->invs_ptr, invst->new_invs); +/*
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_remove_master_domain(struct arm_smmu_master *master, · line 3259
Quoted 5 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5
-0+* We are committed to updating the STE. Ensure the invalidation array +* is visable to concurrent map/unmap threads, and acquire any racying +* IOPTE updates. +*/ +smp_mb ();Sorry, but the comment hasn't really helped me here. We're ordering the publishing of the invalidation array above before ... what?
"concurrent map/unmap threads" means other threads calling arm_smmu_iotlb_sync().. I think the matching comment describing this and introducing the pair'd smp_mb() is in a later patch.
Either the HW observes the latest page table and needs no invalidation or arm_smmu_iotlb_sync observes the invalidation list and issues invalidation.
The smb_mb() is intended to make one of the two statements true prior to storing the STE.
Nicolin maybe this barrier line should be moved to be added in the patch that add's the pair'd barrier and description?
Jason
Re: [PATCH v5 5/7] iommu/arm-smmu-v3: Populate smmu_domain->invs when attaching masters
Quoted 14 lines · Jason Gunthorpe
Quoted 9 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static void arm_smmu_remove_master_domain(struct arm_smmu_master *master, · line 3259
Quoted 6 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +6
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visable to concurrent map/unmap threads, and acquire any racying +* IOPTE updates. +*/ +smp_mb ();Sorry, but the comment hasn't really helped me here. We're ordering the publishing of the invalidation array above before ... what?
"concurrent map/unmap threads" means other threads calling
arm_smmu_iotlb_sync().. I think the matching comment describing this and introducing the pair'dsmp_mb()is in a later patch.
...
Quoted 2 lines
Nicolin maybe this barrier line should be moved to be added in the patch that add's the pair'd barrier and description?
Sure. I can move that to PATCH-6.
Thanks
Nicolin
Re: [PATCH v5 5/7] iommu/arm-smmu-v3: Populate smmu_domain->invs when attaching masters
In reply to Jason GunthorpeQuoted 27 lines · Jason Gunthorpe
Quoted 17 lines · Will Deacon
Quoted 17 lines
(quoted diff) +17
-0+ switch (smmu_domain->stage) { + case ARM_SMMU_DOMAIN_SVA: + case ARM_SMMU_DOMAIN_S1: + *cur = (struct arm_smmu_inv){ + .smmu = master->smmu, + .type = INV_TYPE_S1_ASID, + .id = smmu_domain->cd.asid, + .size_opcode = e2h ? CMDQ_OP_TLBI_EL2_VA : + CMDQ_OP_TLBI_NH_VA, + .nsize_opcode = e2h ? CMDQ_OP_TLBI_EL2_ASID : + CMDQ_OP_TLBI_NH_ASID + }; + break; + case ARM_SMMU_DOMAIN_S2: + *cur = (struct arm_smmu_inv){ + .smmu = master->smmu, + .type = INV_TYPE_S2_VMID,Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ .id = smmu_domain->s2_cfg.vmid, + .size_opcode = CMDQ_OP_TLBI_S2_IPA, + .nsize_opcode = CMDQ_OP_TLBI_S12_VMALL, + }; + break;Having a helper to add an invalidation command would make this a little more compact and you could also check against the size of the array.
Quoted 2 lines
Yeah but it makes all the parameters positional instead of nicely named..
Will's remarks did raise an issue that __counted_by() requires a max_invs v.s. num_invs that can be smaller due to the removal of tailing trash entries.
Then, it's probably nicer to add a check against the max_invs.
I did the following, which doesn't look too bad to me:
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +73 -54
@@ -3066,6 +3066,51 @@ static void arm_smmu_disable_iopf(struct arm_smmu_master *master,iopf_queue_remove_device (master->smmu->evtq.iopf, master->dev); } +static struct arm_smmu_inv * +arm_smmu_master_build_inv (struct arm_smmu_master *master, +enum arm_smmu_inv_type type,u32 id,ioasid_t ssid, +size_t pgsize) +{ +struct arm_smmu_invs *build_invs = master->build_invs; +struct arm_smmu_inv *cur, inv = { + .smmu = master->smmu, + .type = type, + .id = id, + .pgsize = pgsize, + }; + +if (WARN_ON (build_invs->num_invs >= build_invs->max_invs)) +return NULL ; + cur = &build_invs->inv[build_invs->num_invs]; + build_invs->num_invs++; + + *cur = inv; +switch (type) { +case INV_TYPE_S1_ASID : +if (master->smmu->features &ARM_SMMU_FEAT_E2H ) { + cur->size_opcode =CMDQ_OP_TLBI_EL2_VA ; + cur->nsize_opcode =CMDQ_OP_TLBI_EL2_ASID ; + }else { + cur->size_opcode =CMDQ_OP_TLBI_NH_VA ; + cur->nsize_opcode =CMDQ_OP_TLBI_NH_ASID ; + } +break ; +case INV_TYPE_S2_VMID : + cur->size_opcode =CMDQ_OP_TLBI_S2_IPA ; + cur->nsize_opcode =CMDQ_OP_TLBI_S12_VMALL ; +break ; +case INV_TYPE_S2_VMID_S1_CLEAR : + cur->size_opcode = cur->nsize_opcode =CMDQ_OP_TLBI_NH_ALL ; +break ; +case INV_TYPE_ATS : +case INV_TYPE_ATS_FULL : + cur->size_opcode = cur->nsize_opcode =CMDQ_OP_ATC_INV ; +break ; + } + +return cur; +} +/* * Use the preallocated scratch array at master->build_invs, to build a to_merge * or to_unref array, to pass into a following arm_smmu_invs_merge/unref() call.
@@ -3077,84 +3122,58 @@ static struct arm_smmu_invs * arm_smmu_master_build_invs(struct arm_smmu_master *master, bool ats_enabled, ioasid_t ssid, struct arm_smmu_domain *smmu_domain) { - const bool e2h = master->smmu->features & ARM_SMMU_FEAT_E2H; -struct arm_smmu_invs *build_invs = master->build_invs; const bool nesting = smmu_domain->nest_parent; -struct arm_smmu_inv *cur; +size_t pgsize =0 , i;iommu_group_mutex_assert (master->dev); - cur = build_invs->inv; + master->build_invs->num_invs =0 ; + +/* Range-based invalidation requires the leaf pgsize for calculation */ +if (master->smmu->features &ARM_SMMU_FEAT_RANGE_INV ) + pgsize =__ffs (smmu_domain->domain.pgsize_bitmap);switch (smmu_domain->stage) {case ARM_SMMU_DOMAIN_SVA :case ARM_SMMU_DOMAIN_S1 : - *cur = (struct arm_smmu_inv ){ - .smmu = master->smmu, - .type =INV_TYPE_S1_ASID , - .id = smmu_domain->cd.asid, - .size_opcode = e2h ?CMDQ_OP_TLBI_EL2_VA : -CMDQ_OP_TLBI_NH_VA , - .nsize_opcode = e2h ?CMDQ_OP_TLBI_EL2_ASID : -CMDQ_OP_TLBI_NH_ASID - }; +if (!arm_smmu_master_build_inv (master,INV_TYPE_S1_ASID , + smmu_domain->cd.asid, +IOMMU_NO_PASID , pgsize)) +return NULL ;break ;case ARM_SMMU_DOMAIN_S2 : - *cur = (struct arm_smmu_inv ){ - .smmu = master->smmu, - .type =INV_TYPE_S2_VMID , - .id = smmu_domain->s2_cfg.vmid, - .size_opcode =CMDQ_OP_TLBI_S2_IPA , - .nsize_opcode =CMDQ_OP_TLBI_S12_VMALL , - }; +if (!arm_smmu_master_build_inv (master,INV_TYPE_S2_VMID , + smmu_domain->s2_cfg.vmid, +IOMMU_NO_PASID , pgsize)) +return NULL ;break ;default :WARN_ON (true);return NULL ; } -/* Range-based invalidation requires the leaf pgsize for calculation */ -if (master->smmu->features &ARM_SMMU_FEAT_RANGE_INV ) - cur->pgsize =__ffs (smmu_domain->domain.pgsize_bitmap); - cur++; -/* All the nested S1 ASIDs have to be flushed when S2 parent changes */ if (nesting) { - *cur = (struct arm_smmu_inv ){ - .smmu = master->smmu, - .type =INV_TYPE_S2_VMID_S1_CLEAR , - .id = smmu_domain->s2_cfg.vmid, - .size_opcode =CMDQ_OP_TLBI_NH_ALL , - .nsize_opcode =CMDQ_OP_TLBI_NH_ALL , - }; - cur++; +if (!arm_smmu_master_build_inv ( + master,INV_TYPE_S2_VMID_S1_CLEAR , + smmu_domain->s2_cfg.vmid,IOMMU_NO_PASID ,0 )) +return NULL ; } -if (ats_enabled) { -size_t i; - -for (i =0 ; i < master->num_streams; i++) { -/* -* If an S2 used as a nesting parent is changed we have -* no option but to completely flush the ATC. -*/ - *cur = (struct arm_smmu_inv ){ - .smmu = master->smmu, - .type = nesting ?INV_TYPE_ATS_FULL : -INV_TYPE_ATS , - .id = master->streams[i].id, - .ssid = ssid, - .size_opcode =CMDQ_OP_ATC_INV , - .nsize_opcode =CMDQ_OP_ATC_INV , - }; - cur++; - } +for (i =0 ; ats_enabled && i < master->num_streams; i++) { +/* +* If an S2 used as a nesting parent is changed we have no +* option but to completely flush the ATC. +*/ +if (!arm_smmu_master_build_inv ( + master, nesting ?INV_TYPE_ATS_FULL :INV_TYPE_ATS , + master->streams[i].id, ssid,0 )) +return NULL ; }/* Note this build_invs must have been sorted */ - build_invs->num_invs = cur - build_invs->inv; -return build_invs; +return master->build_invs; } static void arm_smmu_remove_master_domain(struct arm_smmu_master *master,
Thanks
Nicolin
PATCH v5 6/7 iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
In reply to Nicolin ChenEach smmu_domain now has an arm_smmu_invs that specifies the invalidation steps to perform after any change the IOPTEs. This includes supports for basic ASID/VMID, the special case for nesting, and ATC invalidations.
Introduce a new arm_smmu_domain_inv helper iterating smmu_domain->invs to convert the invalidation array to commands. Any invalidation request with no size specified means an entire flush over a range based one.
Take advantage of the sorted array to compatible batch operations together to the same SMMU. For instance, ATC invaliations for multiple SIDs can be pushed as a batch.
ATC invalidations must be completed before the driver disables ATS. Or the device is permitted to ignore any racing invalidation that would cause an SMMU timeout. The sequencing is done with a rwlock where holding the write side of the rwlock means that there are no outstanding ATC invalidations. If ATS is not used the rwlock is ignored, similar to the existing code.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 9 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 224 ++++++++++++++++++-- 2 files changed, 220 insertions(+), 13 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +9 -0
@@ -1079,6 +1079,15 @@ void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid,int arm_smmu_atc_inv_domain (struct arm_smmu_domain *smmu_domain,unsigned long iova,size_t size); +void arm_smmu_domain_inv_range (struct arm_smmu_domain *smmu_domain, +unsigned long iova,size_t size, +unsigned int granule,bool leaf); + +static inline void arm_smmu_domain_inv (struct arm_smmu_domain *smmu_domain) +{ +arm_smmu_domain_inv_range (smmu_domain,0 ,0 ,0 , false); +} +void __arm_smmu_cmdq_skip_err (struct arm_smmu_device *smmu,struct arm_smmu_cmdq *cmdq); int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +211 -13
257 lines not on this page · raw message
Signature · 1 line
2.43.0
PATCH v5 7/7 iommu/arm-smmu-v3: Perform per-domain invalidations using arm_smmu_invs
In reply to Nicolin ChenReplace the old invalidation functions with arm_smmu_domain_inv_range() in all the existing invalidation routines. And deprecate the old functions.
The new arm_smmu_domain_inv_range() handles the CMDQ_MAX_TLBI_OPS as well, so drop it in the SVA function.
Since arm_smmu_cmdq_batch_add_range() has only one caller now, and it must be given a valid size, add a WARN_ON_ONCE to catch any missed case.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 7 - .../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 29 +-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 165 +----------------- 3 files changed, 11 insertions(+), 190 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +0 -7
@@ -1072,13 +1072,6 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master, struct arm_smmu_domain *smmu_domain, ioasid_t pasid, struct arm_smmu_cd *cd, struct iommu_domain *old); -void arm_smmu_tlb_inv_asid (struct arm_smmu_device *smmu,u16 asid); -void arm_smmu_tlb_inv_range_asid (unsigned long iova,size_t size,int asid, -size_t granule,bool leaf, -struct arm_smmu_domain *smmu_domain); -int arm_smmu_atc_inv_domain (struct arm_smmu_domain *smmu_domain, -unsigned long iova,size_t size); -void arm_smmu_domain_inv_range (struct arm_smmu_domain *smmu_domain,unsigned long iova,size_t size,unsigned int granule,bool leaf);
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c +3 -26
@@ -122,15 +122,6 @@ void arm_smmu_make_sva_cd(struct arm_smmu_cd *target, }EXPORT_SYMBOL_IF_KUNIT (arm_smmu_make_sva_cd); -/* -* Cloned from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h, this -* is used as a threshold to replace per-page TLBI commands to issue in the -* command queue with an address-space TLBI command, when SMMU w/o a range -* invalidation feature handles too many per-page TLBI commands, which will -* otherwise result in a soft lockup. -*/ -#define CMDQ_MAX_TLBI_OPS (1 << (PAGE_SHIFT - 3)) - static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn, struct mm_struct *mm, unsigned long start,
@@ -146,21 +137,8 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,* range. So do a simple translation here by calculating size correctly. */ size = end - start; -if (!(smmu_domain->smmu->features &ARM_SMMU_FEAT_RANGE_INV )) { -if (size >=CMDQ_MAX_TLBI_OPS *PAGE_SIZE ) - size =0 ; - }else { -if (size ==ULONG_MAX ) - size =0 ; - } - -if (!size) -arm_smmu_tlb_inv_asid (smmu_domain->smmu, smmu_domain->cd.asid); -else -arm_smmu_tlb_inv_range_asid (start, size, smmu_domain->cd.asid, -PAGE_SIZE , false, smmu_domain); -arm_smmu_atc_inv_domain (smmu_domain, start, size); +arm_smmu_domain_inv_range (smmu_domain, start, size,PAGE_SIZE , false); }static void arm_smmu_mm_release (struct mmu_notifier *mn,struct mm_struct *mm)
@@ -191,8 +169,7 @@ static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm) }spin_unlock_irqrestore (&smmu_domain->devices_lock, flags); -arm_smmu_tlb_inv_asid (smmu_domain->smmu, smmu_domain->cd.asid); -arm_smmu_atc_inv_domain (smmu_domain,0 ,0 ); +arm_smmu_domain_inv (smmu_domain); }static void arm_smmu_mmu_notifier_free (struct mmu_notifier *mn)
@@ -301,7 +278,7 @@ static void arm_smmu_sva_domain_free(struct iommu_domain *domain)/* * Ensure the ASID is empty in the iommu cache before allowing reuse. */ -arm_smmu_tlb_inv_asid (smmu_domain->smmu, smmu_domain->cd.asid); +arm_smmu_domain_inv (smmu_domain);/* * Notice that the arm_smmu_mm_arch_invalidate_secondary_tlbs op can
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +8 -157
211 lines not on this page · raw message
Signature · 1 line
2.43.0