iommu/arm-smmu-v3: Introduce an RCU-protected invalidation array
Subsystems (3)
v9 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-v9
Changelog
v9:
* Set cur->ssid correctly in arm_smmu_master_build_inv()
* Add comments for INV_TYPE_S1_ASID in arm_smmu_master_build_inv()
v8:
https://lore.kernel.org/all/[email protected]/
* Update comments for smp_mb() in arm_smmu_install_new/old_domain_invs()
v7:
https://lore.kernel.org/all/[email protected]/
* Rebase on v6.19-rc1
* Fix has_ats in arm_smmu_invs_merge()
* Fix a re-attach case with a different ats_enabled
* Constify the inv parameter in arm_smmu_inv_is_ats()
* Update kunit test data to be closer to real-world use cases
* Clean up SVA domain's invs in arm_smmu_blocking_set_dev_pasid()
v6:
https://lore.kernel.org/all/[email protected]/
* Fix typo in kdocs
* Fix arm_smmu_domain_free() in SVA
* Add arm_smmu_master_build_inv() helper
* Add max_invs and __counted_by(max_invs)
* Return NULL if arm_smmu_invs_alloc() fails
* Sort master->streams instead of fwspec->ids
* s/flush_fn/free_fn (prepare for future patches)
* s/arm_smmu_invs_flush_iotlb_tags/arm_smmu_inv_flush_iotlb_tag
* Define arm_smmu_invs macros and helpers to reduce duplications
* Save num_trashes in "struct arm_smmu_invs" (to avoid a re-scan)
* Move smp_mb() in install() to PATCH-6 that adds the pairing barrier and description
v5:
https://lore.kernel.org/all/[email protected]/
* 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:
https://lore.kernel.org/all/[email protected]/
* 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:
https://lore.kernel.org/all/[email protected]/
* 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 | 143 ++- .../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 35 +- .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 92 ++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 925 +++++++++++++++--- 4 files changed, 1015 insertions(+), 180 deletions(-)
Signature · 1 line
2.43.0
PATCH v9 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
@@ -3065,6 +3065,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
Re: [PATCH v9 1/7] iommu/arm-smmu-v3: Explicitly set smmu_domain->stage for SVA
Quoted 13 lines · Nicolin Chen
Both the
ARM_SMMU_DOMAIN_S1case and the SVA case use ASID, requiring ASID based invalidation commands to flush the TLB.Define an
ARM_SMMU_DOMAIN_SVAto make the SVA case clear to share the same path with theARM_SMMU_DOMAIN_S1case, which will be a part of the routine to build a new per-domain invalidation array.There is no function change.
Suggested-by Jason Gunthorpe <[email protected]>Acked-by Balbir Singh <[email protected]>Reviewed-by Jason Gunthorpe <[email protected]>Signed-off-by Nicolin Chen <[email protected]>
Quoted 6 lines
--- 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(+)
Thanks,
Praan
PATCH v9 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 | 5 +++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 4 ++-- 3 files changed, 10 insertions(+), 4 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 +3 -2
@@ -197,7 +197,8 @@ static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)static void arm_smmu_mmu_notifier_free (struct mmu_notifier *mn) { -kfree (container_of(mn ,struct arm_smmu_domain ,mmu_notifier )); +arm_smmu_domain_free ( + container_of(mn ,struct arm_smmu_domain ,mmu_notifier )); }static const struct mmu_notifier_ops arm_smmu_mmu_notifier_ops = {
@@ -365,6 +366,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,
@@ -3359,7 +3359,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
Re: [PATCH v9 2/7] iommu/arm-smmu-v3: Add an inline arm_smmu_domain_free()
Quoted 7 lines · Nicolin Chen
There will be a bit more things to free than
smmu_domainitself. So keep a simple inline function in the header to share aross files.Suggested-by Jason Gunthorpe <[email protected]>Reviewed-by Jason Gunthorpe <[email protected]>Acked-by Balbir Singh <[email protected]>Signed-off-by Nicolin Chen <[email protected]>
Quoted 6 lines
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 5 +++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 5 +++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-)
Thanks
Praan
PATCH v9 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 | 98 +++++++ .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 92 +++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 258 ++++++++++++++++++ 3 files changed, 448 insertions(+)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +98 -0
@@ -649,6 +649,93 @@ 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 (const 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 +* @max_invs: maximum capacity of the flexible array +* @num_invs: number of invalidations in the flexible array. May be smaller than +* @max_invs after a tailing trash entry is excluded, but must not be +* greater than @max_invs +* @num_trashes: number of trash entries in the array for arm_smmu_invs_purge(). +* Must not be greater than @num_invs +* @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 max_invs; +size_t num_invs; +size_t num_trashes; +rwlock_t rwlock; +bool has_ats; +struct rcu_head rcu; + struct arm_smmu_inv inv[] __counted_by(max_invs); +}; + +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 NULL ; + new_invs->max_invs = num_invs; + new_invs->num_invs = num_invs; +rwlock_init (&new_invs->rwlock); +return new_invs; +} +struct arm_smmu_evtq {struct arm_smmu_queue q;struct iopf_queue *iopf;
@@ -875,6 +962,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 +1012,13 @@ 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); +void arm_smmu_invs_unref (struct arm_smmu_invs *invs, +struct arm_smmu_invs *to_unref, +void (*free_fn)(struct arm_smmu_inv *inv)); +struct arm_smmu_invs *arm_smmu_invs_purge (struct arm_smmu_invs *invs);#endif struct arm_smmu_master_domain {
@@ -956,6 +1052,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 +92 -0
104 lines not on this page · raw message
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +258 -0
283 lines not on this page · raw message
Signature · 1 line
2.43.0
Re: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 27 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]>
Quoted 6 lines
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 98 +++++++ .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 92 +++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 258 ++++++++++++++++++ 3 files changed, 448 insertions(+)
Thanks,
Praan
Will Deacon
maintainerRe: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
In reply to Nicolin ChenQuoted 55 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 | 98 +++++++ .../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 92 +++++++ drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 258 ++++++++++++++++++ 3 files changed, 448 insertions(+)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +14
-0@@ -649,6 +649,93 @@ 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;
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +10
-0+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 */
The refcount_t API uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.
Quoted 44 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +44
-0+static void arm_smmu_v3_invs_test (struct kunit *test) +{ +const int results1[2 ][3 ] = { {1 ,1 ,3 , }, {1 ,1 ,1 , }, }; +const int results2[2 ][5 ] = { {1 ,1 ,3 ,4 ,5 , }, {2 ,1 ,1 ,1 ,1 , }, }; +const int results3[2 ][3 ] = { {1 ,1 ,3 , }, {1 ,1 ,1 , }, }; +const int results4[2 ][5 ] = { {1 ,1 ,3 ,5 ,6 , }, {2 ,1 ,1 ,1 ,1 , }, }; +const int results5[2 ][5 ] = { {1 ,1 ,3 ,5 ,6 , }, {1 ,0 ,0 ,1 ,1 , }, }; +const int results6[2 ][3 ] = { {1 ,5 ,6 , }, {1 ,1 ,1 , }, }; +struct arm_smmu_invs *test_a, *test_b; + +/* New array */ + test_a =arm_smmu_invs_alloc (0 ); +KUNIT_EXPECT_EQ (test, test_a->num_invs,0 ); + +/* Test1: merge invs1 (new array) */ + test_b =arm_smmu_invs_merge (test_a, &invs1); +kfree (test_a); +arm_smmu_v3_invs_test_verify (test, test_b,ARRAY_SIZE (results1[0 ]), + results1[0 ], results1[1 ]); + +/* Test2: merge invs2 (new array) */ + test_a =arm_smmu_invs_merge (test_b, &invs2); +kfree (test_b); +arm_smmu_v3_invs_test_verify (test, test_a,ARRAY_SIZE (results2[0 ]), + results2[0 ], results2[1 ]); + +/* Test3: unref invs2 (same array) */ +arm_smmu_invs_unref (test_a, &invs2,NULL ); +arm_smmu_v3_invs_test_verify (test, test_a,ARRAY_SIZE (results3[0 ]), + results3[0 ], results3[1 ]); +KUNIT_EXPECT_EQ (test, test_a->num_trashes,0 ); + +/* Test4: merge invs3 (new array) */ + test_b =arm_smmu_invs_merge (test_a, &invs3); +kfree (test_a); +arm_smmu_v3_invs_test_verify (test, test_b,ARRAY_SIZE (results4[0 ]), + results4[0 ], results4[1 ]); + +/* Test5: unref invs1 (same array) */ +arm_smmu_invs_unref (test_b, &invs1,NULL ); +arm_smmu_v3_invs_test_verify (test, test_b,ARRAY_SIZE (results5[0 ]), + results5[0 ], results5[1 ]); +KUNIT_EXPECT_EQ (test, test_b->num_trashes,2 ); +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 729
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +10
-0+/* Test6: purge test_b (new array) */ + test_a =arm_smmu_invs_purge (test_b); +kfree (test_b); +arm_smmu_v3_invs_test_verify (test, test_a,ARRAY_SIZE (results6[0 ]), + results6[0 ], results6[1 ]); + +/* Test7: unref invs3 (same array) */ +arm_smmu_invs_unref (test_a, &invs3,NULL ); +KUNIT_EXPECT_EQ (test, test_a->num_invs,0 ); +KUNIT_EXPECT_EQ (test, test_a->num_trashes,0 );
Wouldn't we be better off testing num_trashes == 0 after test 6 has completed?
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 739
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +10
-0+/** +* arm_smmu_invs_for_each_entry - Iterate over two sorted arrays computing for +* arm_smmu_invs_merge() or arm_smmu_invs_unref() +* @invs_l: the base invalidation array +* @idx_l: a stack variable of 'size_t', to store the base array index +* @invs_r: the build_invs array as to_merge or to_unref +* @idx_r: a stack variable of 'size_t', to store the build_invs index +* @cmp: a stack variable of 'int', to store return value (-1, 0, or 1) +*/ +#define arm_smmu_invs_for_each_cmp (invs_l, idx_l, invs_r, idx_r, cmp) \
nit: the kerneldoc comment doesn't match the name of this function.
Will
Re: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 10 lines · Will Deacon
Quoted 9 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +9
-0+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 */
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 3 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +3
-0+ }; + +refcount_t users;/* users=0 to mark as a trash to be purged */ The
refcount_tAPI uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.
But we can't use that mutex in the invalidation function that might be an IRQ context?
Quoted 5 lines
Quoted 5 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c +5
-0+/* Test6: purge test_b (new array) */ + test_a =arm_smmu_invs_purge (test_b); +kfree (test_b); +arm_smmu_v3_invs_test_verify (test, test_a,ARRAY_SIZE (results6[0 ]), + results6[0 ], results6[1 ]);
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c static void arm_smmu_v3_write_cd_test_sva_release(struct kunit *test) · line 656
Quoted 5 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c +5
-0+ +/* Test7: unref invs3 (same array) */ +arm_smmu_invs_unref (test_a, &invs3,NULL ); +KUNIT_EXPECT_EQ (test, test_a->num_invs,0 ); +KUNIT_EXPECT_EQ (test, test_a->num_trashes,0 );Wouldn't we be better off testing
num_trashes== 0 after test 6 has completed?
OK.
Quoted 4 lines
Quoted 4 lines
+/**
+ *arm_smmu_invs_for_each_entry- Iterate over two sorted arrays computing for
+ *arm_smmu_invs_merge()orarm_smmu_invs_unref()
+ * @invs_l: the base invalidation array
Quoted 8 lines
Quoted 6 lines
+ * @idx_l: a stack variable of '
size_t', to store the base array index
+ * @invs_r: thebuild_invsarray asto_mergeorto_unref
+ * @idx_r: a stack variable of 'size_t', to store thebuild_invsindex
+ * @cmp: a stack variable of 'int', to store return value (-1, 0, or 1)
+ */
+#definearm_smmu_invs_for_each_cmp(invs_l, idx_l, invs_r, idx_r, cmp)\nit: the kerneldoc comment doesn't match the name of this function.
I will fix this.
Thanks
Nicolin
Will Deacon
maintainerRe: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 20 lines · Nicolin Chen
Quoted 10 lines · Will Deacon
Quoted 9 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +9
-0+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 */ Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 3 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +3
-0+ }; + +refcount_t users;/* users=0 to mark as a trash to be purged */ The
refcount_tAPI uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.
Quoted 2 lines
But we can't use that mutex in the invalidation function that might be an IRQ context?
My question, really, is why do you need the atomic properties in this patch series? It just looks like overhead at the moment.
Will
Re: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 21 lines · Will Deacon
Quoted 20 lines · Nicolin Chen
Quoted 18 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 12 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +12
-0+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 */ The
refcount_tAPI uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.
Quoted 5 lines
Quoted 2 lines
But we can't use that mutex in the invalidation function that might be an IRQ context?
My question, really, is why do you need the atomic properties in this patch series? It just looks like overhead at the moment.
Hmm, shouldn't it be atomic, since..
(might be IRQ, no mutex) __arm_smmu_domain_inv_range() reads it. (mutex protected) arm_smmu_attach_dev() writes it.
..?
Nicolin
Re: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 28 lines · Nicolin Chen
Quoted 21 lines · Will Deacon
Quoted 20 lines · Nicolin Chen
Quoted 18 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 12 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +12
-0+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 */ The
refcount_tAPI uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.Quoted 5 lines
Quoted 2 lines
But we can't use that mutex in the invalidation function that might be an IRQ context?
My question, really, is why do you need the atomic properties in this patch series? It just looks like overhead at the moment.
Quoted 6 lines
Hmm, shouldn't it be atomic, since..
(might be IRQ, no mutex)
__arm_smmu_domain_inv_range()reads it. (mutex protected)arm_smmu_attach_dev()writes it...?
It doesn't need to be a full atomic, you can use WRITE_ONCE/READ_ONCE instead for this case.
The general argument of this scheme is it doesn't matter if the invalidation side is doing extra invalidation, that is supposed to always be safe except for ATS. For ATS we hold locks and then it doesn't to be atomic because of the lock.
So I think Will has it right, just use READ_ONCE/WRITE_ONCE on a naked unsigned int.
Jason
Re: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 39 lines · Jason Gunthorpe
Quoted 28 lines · Nicolin Chen
Quoted 21 lines · Will Deacon
Quoted 20 lines · Nicolin Chen
Quoted 18 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 12 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +12
-0+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 */ The
refcount_tAPI uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.Quoted 5 lines
Quoted 2 lines
But we can't use that mutex in the invalidation function that might be an IRQ context?
My question, really, is why do you need the atomic properties in this patch series? It just looks like overhead at the moment.
Quoted 6 lines
Hmm, shouldn't it be atomic, since..
(might be IRQ, no mutex)
__arm_smmu_domain_inv_range()reads it. (mutex protected)arm_smmu_attach_dev()writes it...?
It doesn't need to be a full atomic, you can use WRITE_ONCE/READ_ONCE instead for this case.
Quoted 7 lines
The general argument of this scheme is it doesn't matter if the invalidation side is doing extra invalidation, that is supposed to always be safe except for ATS. For ATS we hold locks and then it doesn't to be atomic because of the lock.
So I think Will has it right, just use READ_ONCE/WRITE_ONCE on a naked unsigned int.
OK. I'll change that.
Thanks
Nicolin
Will Deacon
maintainerRe: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
In reply to Jason GunthorpeQuoted 39 lines · Jason Gunthorpe
Quoted 28 lines · Nicolin Chen
Quoted 21 lines · Will Deacon
Quoted 20 lines · Nicolin Chen
Quoted 18 lines · Will Deacon
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_cmdq_batch { · line 675
Quoted 12 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +12
-0+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 */ The
refcount_tAPI uses atomics with barrier semantics. Do we actually need those properties when updating the refcounts here? The ASID lock gives us pretty strong serialisation even after this patch series and we rely heavily on that.Quoted 5 lines
Quoted 2 lines
But we can't use that mutex in the invalidation function that might be an IRQ context?
My question, really, is why do you need the atomic properties in this patch series? It just looks like overhead at the moment.
Quoted 6 lines
Hmm, shouldn't it be atomic, since..
(might be IRQ, no mutex)
__arm_smmu_domain_inv_range()reads it. (mutex protected)arm_smmu_attach_dev()writes it...?
It doesn't need to be a full atomic, you can use WRITE_ONCE/READ_ONCE instead for this case.
Quoted 7 lines
The general argument of this scheme is it doesn't matter if the invalidation side is doing extra invalidation, that is supposed to always be safe except for ATS. For ATS we hold locks and then it doesn't to be atomic because of the lock.
So I think Will has it right, just use READ_ONCE/WRITE_ONCE on a naked unsigned int.
There's the usual scary ordering worries with this stuff, but they exist with refcount_read() too. We're ok because cur->users is address dependent on the read of the invalidation array pointer, which is RCU protected.
(I think!)
Will
Re: [PATCH v9 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
Quoted 5 lines · Will Deacon
There's the usual scary ordering worries with this stuff, but they exist with
refcount_read()too. We're ok becausecur->usersis address dependent on the read of the invalidation array pointer, which is RCU protected.(I think!)
This has been my understanding at least :\
Jason
PATCH v9 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 | 41 +++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +8 -0
@@ -930,6 +930,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 +37 -4
75 lines not on this page · raw message
Signature · 1 line
2.43.0
Re: [PATCH v9 4/7] iommu/arm-smmu-v3: Pre-allocate a per-master invalidation array
Quoted 17 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.
Quoted 9 lines
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 | 41 +++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-)
Thanks
Praan
PATCH v9 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 | 258 +++++++++++++++++++- 2 files changed, 274 insertions(+), 1 deletion(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +17 -0
@@ -1101,6 +1101,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 domain->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;
@@ -1110,6 +1125,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 +257 -1
311 lines not on this page · raw message
Signature · 1 line
2.43.0
PATCH v9 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 | 258 +++++++++++++++++++- 2 files changed, 254 insertions(+), 13 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +9 -0
@@ -1086,6 +1086,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 +245 -13
303 lines not on this page · raw message
Signature · 1 line
2.43.0
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 290 lines · Nicolin Chen
Each
smmu_domainnow has anarm_smmu_invsthat 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_invhelper iteratingsmmu_domain->invsto 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.
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 | 9 + drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 258 +++++++++++++++++++- 2 files changed, 254 insertions(+), 13 deletions(-)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +9
-0@@ -1086,6 +1086,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 +192
-13@@ -2516,23 +2516,19 @@ static void arm_smmu_tlb_inv_context(void *cookie)arm_smmu_atc_inv_domain (smmu_domain,0 ,0 ); } -static void __arm_smmu_tlb_inv_range (struct arm_smmu_cmdq_ent *cmd, -unsigned long iova,size_t size, -size_t granule, -struct arm_smmu_domain *smmu_domain) +static void arm_smmu_cmdq_batch_add_range (struct arm_smmu_device *smmu, +struct arm_smmu_cmdq_batch *cmds, +struct arm_smmu_cmdq_ent *cmd, +unsigned long iova,size_t size, +size_t granule,size_t pgsize) { -struct arm_smmu_device *smmu = smmu_domain->smmu; -unsigned long end = iova + size, num_pages =0 , tg =0 ; +unsigned long end = iova + size, num_pages =0 , tg = pgsize;size_t inv_range = granule; -struct arm_smmu_cmdq_batch cmds;if (!size)return ;if (smmu->features &ARM_SMMU_FEAT_RANGE_INV ) { -/* Get the leaf page size */ - tg =__ffs (smmu_domain->domain.pgsize_bitmap); - num_pages = size >> tg;/* Convert page size of 12,14,16 (log2) to 1,2,3 */ @@ -2552,8 +2548,6 @@ static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd, num_pages++; } -arm_smmu_cmdq_batch_init (smmu, &cmds, cmd); -while (iova < end) {if (smmu->features &ARM_SMMU_FEAT_RANGE_INV ) {/* @@ -2581,9 +2575,26 @@ static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd, } cmd->tlbi.addr = iova; -arm_smmu_cmdq_batch_add (smmu, &cmds, cmd); +arm_smmu_cmdq_batch_add (smmu, cmds, cmd); iova += inv_range; } +} + +static void __arm_smmu_tlb_inv_range (struct arm_smmu_cmdq_ent *cmd, +unsigned long iova,size_t size, +size_t granule, +struct arm_smmu_domain *smmu_domain) +{ +struct arm_smmu_device *smmu = smmu_domain->smmu; +struct arm_smmu_cmdq_batch cmds; +size_t pgsize; + +/* Get the leaf page size */ + pgsize =__ffs (smmu_domain->domain.pgsize_bitmap); + +arm_smmu_cmdq_batch_init (smmu, &cmds, cmd); +arm_smmu_cmdq_batch_add_range (smmu, &cmds, cmd, iova, size, granule, + pgsize);arm_smmu_cmdq_batch_submit (smmu, &cmds); }@@ -2639,6 +2650,193 @@ void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid,__arm_smmu_tlb_inv_range (&cmd, iova, size, granule, smmu_domain); } +static bool arm_smmu_inv_size_too_big (struct arm_smmu_device *smmu,size_t size, +size_t granule) +{ +size_t max_tlbi_ops; + +/* 0 size means invalidate all */ +if (!size || size ==SIZE_MAX ) +return true; + +if (smmu->features &ARM_SMMU_FEAT_RANGE_INV ) +return false; + +/* +* Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h, +* this is used as a threshold to replace "size_opcode" commands with a +* single "nsize_opcode" command, when SMMU doesn't implement the range +* invalidation feature, where there can be too many per-granule TLBIs, +* resulting in a soft lockup. +*/ + max_tlbi_ops =1 << (ilog2 (granule) -3 ); +return size >= max_tlbi_ops * granule; +} + +/* Used by non INV_TYPE_ATS* invalidations */ +static void arm_smmu_inv_to_cmdq_batch (struct arm_smmu_inv *inv, +struct arm_smmu_cmdq_batch *cmds, +struct arm_smmu_cmdq_ent *cmd, +unsigned long iova,size_t size, +unsigned int granule) +{ +if (arm_smmu_inv_size_too_big (inv->smmu, size, granule)) { + cmd->opcode = inv->nsize_opcode; +arm_smmu_cmdq_batch_add (inv->smmu, cmds, cmd); +return ; + } + + cmd->opcode = inv->size_opcode; +arm_smmu_cmdq_batch_add_range (inv->smmu, cmds, cmd, iova, size, granule, + inv->pgsize); +} + +static inline bool arm_smmu_invs_end_batch (struct arm_smmu_inv *cur, +struct arm_smmu_inv *next) +{ +/* Changing smmu means changing command queue */ +if (cur->smmu != next->smmu) +return true; +/* The batch for S2 TLBI must be done before nested S1 ASIDs */ +if (cur->type !=INV_TYPE_S2_VMID_S1_CLEAR && + next->type ==INV_TYPE_S2_VMID_S1_CLEAR ) +return true; +/* ATS must be after a sync of the S1/S2 invalidations */ +if (!arm_smmu_inv_is_ats (cur) &&arm_smmu_inv_is_ats (next)) +return true; +return false; +} + +static void __arm_smmu_domain_inv_range (struct arm_smmu_invs *invs, +unsigned long iova,size_t size, +unsigned int granule,bool leaf) +{ +struct arm_smmu_cmdq_batch cmds = {}; +struct arm_smmu_inv *cur; +struct arm_smmu_inv *end; + + cur = invs->inv; + end = cur +READ_ONCE (invs->num_invs); +/* Skip any leading entry marked as a trash */ +for (; cur != end; cur++) +if (refcount_read (&cur->users)) +break ; +while (cur != end) { +struct arm_smmu_device *smmu = cur->smmu; +struct arm_smmu_cmdq_ent cmd = { +/* +* Pick size_opcode to run arm_smmu_get_cmdq(). This can +* be changed to nsize_opcode, which would result in the +* same CMDQ pointer. +*/ + .opcode = cur->size_opcode, + }; +struct arm_smmu_inv *next; + +if (!cmds.num) +arm_smmu_cmdq_batch_init (smmu, &cmds, &cmd); + +switch (cur->type) { +case INV_TYPE_S1_ASID : + cmd.tlbi.asid = cur->id; + cmd.tlbi.leaf = leaf; +arm_smmu_inv_to_cmdq_batch (cur, &cmds, &cmd, iova, size, + granule); +break ; +case INV_TYPE_S2_VMID : + cmd.tlbi.vmid = cur->id; + cmd.tlbi.leaf = leaf; +arm_smmu_inv_to_cmdq_batch (cur, &cmds, &cmd, iova, size, + granule); +break ; +case INV_TYPE_S2_VMID_S1_CLEAR : +/* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */ +if (arm_smmu_inv_size_too_big (cur->smmu, size, granule)) +continue ; + cmd.tlbi.vmid = cur->id; +arm_smmu_cmdq_batch_add (smmu, &cmds, &cmd); +break ; +case INV_TYPE_ATS : +arm_smmu_atc_inv_to_cmd (cur->ssid, iova, size, &cmd); + cmd.atc.sid = cur->id; +arm_smmu_cmdq_batch_add (smmu, &cmds, &cmd); +break ; +case INV_TYPE_ATS_FULL : +arm_smmu_atc_inv_to_cmd (IOMMU_NO_PASID ,0 ,0 , &cmd); + cmd.atc.sid = cur->id; +arm_smmu_cmdq_batch_add (smmu, &cmds, &cmd); +break ; +default : +WARN_ON_ONCE (1 ); +continue ; + } + +/* Skip any trash entry in-between */ +for (next = cur +1 ; next != end; next++) +if (refcount_read (&next->users)) +break ; + +if (cmds.num && + (next == end ||arm_smmu_invs_end_batch (cur, next))) { +arm_smmu_cmdq_batch_submit (smmu, &cmds); + cmds.num =0 ; + } + cur = next; + } +} + +void arm_smmu_domain_inv_range (struct arm_smmu_domain *smmu_domain, +unsigned long iova,size_t size, +unsigned int granule,bool leaf) +{ +struct arm_smmu_invs *invs; + +/* +* An invalidation request must follow some IOPTE change and then load +* an invalidation array. In the meantime, a domain attachment mutates +* the array and then stores an STE/CD asking SMMU HW to acquire those +* changed IOPTEs. In other word, these two are interdependent and can +* race. +* +* In a race, the RCU design (with its underlying memory barriers) can +* ensure the invalidation array to always get updated before loaded. +* +* smp_mb() is used here, paired with the smp_mb() following the array +* update in a concurrent attach, to ensure: +* - HW sees the new IOPTEs if it walks after STE installation +* - Invalidation thread sees the updated array with the new ASID. +* +* [CPU0] | [CPU1] +* | +* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2830
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+ +rcu_read_lock (); + invs =rcu_dereference (smmu_domain->invs); + +/* +* Avoid locking unless ATS is being used. No ATC invalidation can be +* going on after a domain is detached. +*/ +if (invs->has_ats) { +read_lock (&invs->rwlock);
Shouldn't these be read_lock_irqsave for all rwlock variants here? Invalidations might happen in IRQ context as well..
Quoted 50 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +36
-0+__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); +read_unlock (&invs->rwlock); + }else { +__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); + } + +rcu_read_unlock (); +} +static void arm_smmu_tlb_inv_page_nosync (struct iommu_iotlb_gather *gather,unsigned long iova,size_t granule,void *cookie)@@ -3285,6 +3483,23 @@ arm_smmu_install_new_domain_invs(struct arm_smmu_attach_state *state)return ;rcu_assign_pointer (*invst->invs_ptr, invst->new_invs); +/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | +* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();kfree_rcu (invst->old_invs, rcu); }@@ -3334,6 +3549,23 @@ arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state)return ;rcu_assign_pointer (*invst->invs_ptr, new_invs); +/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | +* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs);
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3571
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();kfree_rcu (old_invs, rcu); }
For INV_TYPE_S1_ASID, the new code loops and checks size_too_big via arm_smmu_inv_to_cmdq_batch. However, for INV_TYPE_ATS, it issues a single command for the entire range. While this matches the current driver, are we confident arm_smmu_atc_inv_to_cmd handles all massive sizes correctly without needing a similar loop or "too big" fallback?
Thanks,
Praan
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 5 lines · Pranjal Shrivastava
For
INV_TYPE_S1_ASID, the new code loops and checkssize_too_bigviaarm_smmu_inv_to_cmdq_batch. However, forINV_TYPE_ATS, it issues a single command for the entire range. While this matches the current driver, are we confidentarm_smmu_atc_inv_to_cmdhandles all massive sizes correctly without needing a similar loop or "too big" fallback?
At the PCI level ATS invalidations accept an arbitary byte range, so all ranges should be converted into a single CMDQ command and pushed as a single TLP.
The issue the "too big" stuff is solving is for the IOTLB itself which can require an invalidation rage to be exploded into many smaller commands.
Jason
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
In reply to Pranjal ShrivastavaHi Pranjal,
Sorry, I missed this!
Quoted 11 lines · Pranjal Shrivastava
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2830
Quoted 6 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +6
-0+/* +* Avoid locking unless ATS is being used. No ATC invalidation can be +* going on after a domain is detached. +*/ +if (invs->has_ats) { +read_lock (&invs->rwlock);Shouldn't these be
read_lock_irqsavefor all rwlock variants here? Invalidations might happen in IRQ context as well..
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2832
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +2
-0+__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); +read_unlock (&invs->rwlock);
It was kept from the older versions where we had a trylock. Jason had an insight about this, mainly for less latency on invalidation threads.
Yet, now we have a plain locking. TBH, I can't find a good reason justifying this. And it does look a bit unsafe to me. So, I think I will just change to the _irqsave version. (Jason?)
Thanks
Nicolin
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 23 lines · Nicolin Chen
Hi Pranjal,
Sorry, I missed this!
Quoted 11 lines · Pranjal Shrivastava
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2830
Quoted 6 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +6
-0+/* +* Avoid locking unless ATS is being used. No ATC invalidation can be +* going on after a domain is detached. +*/ +if (invs->has_ats) { +read_lock (&invs->rwlock);Shouldn't these be
read_lock_irqsavefor all rwlock variants here? Invalidations might happen in IRQ context as well..Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2832
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +2
-0+__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); +read_unlock (&invs->rwlock);It was kept from the older versions where we had a trylock. Jason had an insight about this, mainly for less latency on invalidation threads.
Quoted 3 lines
Yet, now we have a plain locking. TBH, I can't find a good reason justifying this. And it does look a bit unsafe to me. So, I think I will just change to the
_irqsaveversion. (Jason?)
My understanding has been that this invalidation can run from an IRQ context - we permit the use of the DMA API from an interrupt handler?
I though that for rwsem the read side does not require the _irqsave, even if it is in an irq context, unless the write side runs from an IRQ.
Here the write side always runs from a process context.
So the write side will block the IRQ which ensures we don't spin during read in an IRQ.
IOW I think this is OK? Pranjal do you know otherwise?
Jason
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 28 lines · Jason Gunthorpe
Quoted 23 lines · Nicolin Chen
Hi Pranjal,
Sorry, I missed this!
Quoted 11 lines · Pranjal Shrivastava
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2830
Quoted 6 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +6
-0+/* +* Avoid locking unless ATS is being used. No ATC invalidation can be +* going on after a domain is detached. +*/ +if (invs->has_ats) { +read_lock (&invs->rwlock);Shouldn't these be
read_lock_irqsavefor all rwlock variants here? Invalidations might happen in IRQ context as well..Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2832
Quoted 2 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +2
-0+__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); +read_unlock (&invs->rwlock);It was kept from the older versions where we had a trylock. Jason had an insight about this, mainly for less latency on invalidation threads.
Quoted 3 lines
Yet, now we have a plain locking. TBH, I can't find a good reason justifying this. And it does look a bit unsafe to me. So, I think I will just change to the
_irqsaveversion. (Jason?)
Quoted 6 lines
My understanding has been that this invalidation can run from an IRQ context - we permit the use of the DMA API from an interrupt handler?
I though that for rwsem the read side does not require the
_irqsave, even if it is in an irq context, unless the write side runs from an IRQ.
Hmm, is "rwsem" a typo? Because it's rwlock_t, which is spinlock :-/
Quoted 4 lines
Here the write side always runs from a process context.
So the write side will block the IRQ which ensures we don't spin during read in an IRQ.
And, does write_lock_irqsave() disable global IRQ or local IRQ only?
Documentation/locking/locktypes.rst mentions "local_irq_disable()"..
Thanks
Nicolin
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 8 lines · Nicolin Chen
Quoted 6 lines
My understanding has been that this invalidation can run from an IRQ context - we permit the use of the DMA API from an interrupt handler?
I though that for rwsem the read side does not require the
_irqsave, even if it is in an irq context, unless the write side runs from an IRQ.Hmm, is "rwsem" a typo? Because it's
rwlock_t, which is spinlock :-/
Yeah, sorry
Quoted 8 lines
Quoted 4 lines
Here the write side always runs from a process context.
So the write side will block the IRQ which ensures we don't spin during read in an IRQ.
And, does
write_lock_irqsave()disable global IRQ or local IRQ only?
Documentation/locking/locktypes.rstmentions "local_irq_disable()"..
It will only disable the local IRQ, since it is a spin type lock an IRQ on another CPU can spin until it is unlocked.
The main issue is if this CPU takes an IRQ while the write side is locked and spins, then it will never unlock.
Jason
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 19 lines · Jason Gunthorpe
Quoted 8 lines · Nicolin Chen
Quoted 6 lines
My understanding has been that this invalidation can run from an IRQ context - we permit the use of the DMA API from an interrupt handler?
I though that for rwsem the read side does not require the
_irqsave, even if it is in an irq context, unless the write side runs from an IRQ.Hmm, is "rwsem" a typo? Because it's
rwlock_t, which is spinlock :-/Yeah, sorry
Quoted 7 lines
Quoted 4 lines
Here the write side always runs from a process context.
So the write side will block the IRQ which ensures we don't spin during read in an IRQ.
And, does
write_lock_irqsave()disable global IRQ or local IRQ only?
Quoted 7 lines
Quoted 1 line
Documentation/locking/locktypes.rstmentions "local_irq_disable()"..It will only disable the local IRQ, since it is a spin type lock an IRQ on another CPU can spin until it is unlocked.
The main issue is if this CPU takes an IRQ while the write side is locked and spins, then it will never unlock.
Yea, that sounds unsafe. I'll send a v11 with read_lock_irqsave().
We can also pass in iommu_domain to arm_smmu_attach_prepare_invs() that you pointed out in the followup series.
Thanks!
Nicolin
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 22 lines · Nicolin Chen
Quoted 21 lines · Jason Gunthorpe
Quoted 8 lines · Nicolin Chen
Quoted 6 lines
My understanding has been that this invalidation can run from an IRQ context - we permit the use of the DMA API from an interrupt handler?
I though that for rwsem the read side does not require the
_irqsave, even if it is in an irq context, unless the write side runs from an IRQ.Hmm, is "rwsem" a typo? Because it's
rwlock_t, which is spinlock :-/Yeah, sorry
Quoted 8 lines
Quoted 4 lines
Here the write side always runs from a process context.
So the write side will block the IRQ which ensures we don't spin during read in an IRQ.
And, does
write_lock_irqsave()disable global IRQ or local IRQ only?
Documentation/locking/locktypes.rstmentions "local_irq_disable()"..
Quoted 7 lines
Quoted 5 lines
It will only disable the local IRQ, since it is a spin type lock an IRQ on another CPU can spin until it is unlocked.
The main issue is if this CPU takes an IRQ while the write side is locked and spins, then it will never unlock.
Yea, that sounds unsafe. I'll send a v11 with
read_lock_irqsave().
I'm explaining why it is safe now, the write side takes the irqsave so the above can't happen.
There is no case where the read side needs to block IRQ because if the read side succeeds, an IRQ happens and tries to take another read side, it will succeed not spin.
But it is a very very small optimization so let's just go ahead with the thing that is obviously safe.
Jason
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 26 lines · Jason Gunthorpe
Quoted 25 lines · Nicolin Chen
Quoted 24 lines · Jason Gunthorpe
Quoted 8 lines · Nicolin Chen
Quoted 6 lines
My understanding has been that this invalidation can run from an IRQ context - we permit the use of the DMA API from an interrupt handler?
I though that for rwsem the read side does not require the
_irqsave, even if it is in an irq context, unless the write side runs from an IRQ.Hmm, is "rwsem" a typo? Because it's
rwlock_t, which is spinlock :-/Yeah, sorry
Quoted 8 lines
Quoted 4 lines
Here the write side always runs from a process context.
So the write side will block the IRQ which ensures we don't spin during read in an IRQ.
And, does
write_lock_irqsave()disable global IRQ or local IRQ only?
Documentation/locking/locktypes.rstmentions "local_irq_disable()"..It will only disable the local IRQ, since it is a spin type lock an IRQ on another CPU can spin until it is unlocked.
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
The main issue is if this CPU takes an IRQ while the write side is locked and spins, then it will never unlock.
Yea, that sounds unsafe. I'll send a v11 with
read_lock_irqsave().I'm explaining why it is safe now, the write side takes the irqsave so the above can't happen.
Sorry, I misunderstood..
Quoted 3 lines
There is no case where the read side needs to block IRQ because if the read side succeeds, an IRQ happens and tries to take another read side, it will succeed not spin.
Yea, I also went a bit deeper.
It seems to depend on the CONFIG_QUEUED_RWLOCKS (ARM sets =y)
252 config QUEUED_RWLOCKS
253 def_bool y if ARCH_USE_QUEUED_RWLOCKS
254 depends on SMP && !PREEMPT_RT
where a reader will not get blocked in our particular use case:
21 void __lockfunc queued_read_lock_slowpath(struct qrwlock *lock) 22 {
23 /*
24 * Readers come here when they cannot get the lock without waiting
25 */
26 if (unlikely(in_interrupt())) {
27 /*
28 * Readers in interrupt context will get the lock immediately 29 * if the writer is just waiting (not holding the lock yet), 30 * so spin with ACQUIRE semantics until the lock is available 31 * without waiting in the queue. 32 */
33 atomic_cond_read_acquire(&lock->cnts, !(VAL & _QW_LOCKED));
34 return;
And I don't see any non-hackable way for CONFIG_QUEUED_RWLOCKS=n unless CONFIG_PREEMPT_RT=y, which would be a different ball game that I assume SMMUv3 might not be completely compatible with.
Thanks
Nicolin
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 3 lines · Nicolin Chen
And I don't see any non-hackable way for
CONFIG_QUEUED_RWLOCKS=nunlessCONFIG_PREEMPT_RT=y, which would be a different ball game that I assume SMMUv3 might not be completely compatible with.
SMMUv3 should work with PREEMPT_RT=y
Preempt is a good point, I don't know what the rules are for preempt at all. So it is OK to be conservative here, I doubt we could measure the difference anyhow
Jason
Will Deacon
maintainerRe: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
In reply to Nicolin ChenQuoted 90 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +76
-0+void arm_smmu_domain_inv_range (struct arm_smmu_domain *smmu_domain, +unsigned long iova,size_t size, +unsigned int granule,bool leaf) +{ +struct arm_smmu_invs *invs; + +/* +* An invalidation request must follow some IOPTE change and then load +* an invalidation array. In the meantime, a domain attachment mutates +* the array and then stores an STE/CD asking SMMU HW to acquire those +* changed IOPTEs. In other word, these two are interdependent and can +* race. +* +* In a race, the RCU design (with its underlying memory barriers) can +* ensure the invalidation array to always get updated before loaded. +* +* smp_mb() is used here, paired with the smp_mb() following the array +* update in a concurrent attach, to ensure: +* - HW sees the new IOPTEs if it walks after STE installation +* - Invalidation thread sees the updated array with the new ASID. +* +* [CPU0] | [CPU1] +* | +* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb (); + +rcu_read_lock (); + invs =rcu_dereference (smmu_domain->invs); + +/* +* Avoid locking unless ATS is being used. No ATC invalidation can be +* going on after a domain is detached. +*/ +if (invs->has_ats) { +read_lock (&invs->rwlock); +__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); +read_unlock (&invs->rwlock); + }else { +__arm_smmu_domain_inv_range (invs, iova, size, granule, leaf); + } + +rcu_read_unlock (); +} +static void arm_smmu_tlb_inv_page_nosync (struct iommu_iotlb_gather *gather,unsigned long iova,size_t granule,void *cookie)@@ -3285,6 +3483,23 @@ arm_smmu_install_new_domain_invs(struct arm_smmu_attach_state *state)return ;rcu_assign_pointer (*invst->invs_ptr, invst->new_invs); +/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | +* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();kfree_rcu (invst->old_invs, rcu); }@@ -3334,6 +3549,23 @@ arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state)return ;rcu_assign_pointer (*invst->invs_ptr, new_invs); +/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* |
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3568
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();
I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in arm_smmu_domain_inv_range()").
However, isn't the comment above misleading for this case? arm_smmu_install_old_domain_invs() has the sequencing the other way around on CPU 1: we should update the STE first.
Will
Will Deacon
maintainerRe: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 23 lines · Will Deacon
Quoted 7 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3568
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in
arm_smmu_domain_inv_range()").
Quoted 3 lines
However, isn't the comment above misleading for this case?
arm_smmu_install_old_domain_invs()has the sequencing the other way around on CPU 1: we should update the STE first.
I also think we probably want a dma_mb() instead of an smp_mb() for all of these examples? It won't make any practical difference but I think it helps readability given that one of the readers is the PTW.
Will
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 15 lines · Will Deacon
Quoted 14 lines · Will Deacon
Quoted 13 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +13
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | +* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | }
Quoted 8 lines
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid, · line 2820
Quoted 4 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +4
-0+* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in
arm_smmu_domain_inv_range()").
I'll drop the duplicates.
Quoted 3 lines
Quoted 3 lines
However, isn't the comment above misleading for this case?
arm_smmu_install_old_domain_invs()has the sequencing the other way around on CPU 1: we should update the STE first.
Ah, that's true. It installs new domain invs first then ste, and lastly updates the old domain invs.
Quoted 3 lines
I also think we probably want a
dma_mb()instead of ansmp_mb()for all of these examples? It won't make any practical difference but I think it helps readability given that one of the readers is the PTW.
OK. I'll change to dma_mb().
Nicolin
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
In reply to Will DeaconQuoted 24 lines · Will Deacon
Quoted 23 lines · Will Deacon
Quoted 7 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3568
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in
arm_smmu_domain_inv_range()").
Quoted 7 lines
Quoted 3 lines
However, isn't the comment above misleading for this case?
arm_smmu_install_old_domain_invs()has the sequencing the other way around on CPU 1: we should update the STE first.I also think we probably want a
dma_mb()instead of ansmp_mb()for all of these examples? It won't make any practical difference but I think it helps readability given that one of the readers is the PTW.
The only actual dma_wmb() is inside arm_smmu_install_ste_for_dev() after updating the STE. Adding that line explicitly would help as that is the only point where we must have the writes actually visible to the DMA HW.
The ones written here as smp_mb() are not required to be DMA ones and could all be NOP's on UP..
Jason
Will Deacon
maintainerRe: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 33 lines · Jason Gunthorpe
Quoted 24 lines · Will Deacon
Quoted 23 lines · Will Deacon
Quoted 7 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3568
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in
arm_smmu_domain_inv_range()").Quoted 7 lines
Quoted 3 lines
However, isn't the comment above misleading for this case?
arm_smmu_install_old_domain_invs()has the sequencing the other way around on CPU 1: we should update the STE first.I also think we probably want a
dma_mb()instead of ansmp_mb()for all of these examples? It won't make any practical difference but I think it helps readability given that one of the readers is the PTW.
Quoted 7 lines
The only actual
dma_wmb()is insidearm_smmu_install_ste_for_dev()after updating the STE. Adding that line explicitly would help as that is the only point where we must have the writes actually visible to the DMA HW.The ones written here as
smp_mb()are not required to be DMA ones and could all be NOP's on UP..
Hmm, I'm not sure about that.
If we've written a new (i.e. previously invalid) valid PTE to a page-table and then we install that page-table into an STE hitlessly (let's say we write the S2TTB field) then isn't there a window before we do the STE invalidation where the page-table might be accessible to the SMMU but the new PTE is still sitting in the CPU?
i.e. we can't rely on the command insertion barrier for that.
Will
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 42 lines · Will Deacon
Quoted 33 lines · Jason Gunthorpe
Quoted 24 lines · Will Deacon
Quoted 23 lines · Will Deacon
Quoted 7 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3568
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in
arm_smmu_domain_inv_range()").Quoted 7 lines
Quoted 3 lines
However, isn't the comment above misleading for this case?
arm_smmu_install_old_domain_invs()has the sequencing the other way around on CPU 1: we should update the STE first.I also think we probably want a
dma_mb()instead of ansmp_mb()for all of these examples? It won't make any practical difference but I think it helps readability given that one of the readers is the PTW.Quoted 7 lines
The only actual
dma_wmb()is insidearm_smmu_install_ste_for_dev()after updating the STE. Adding that line explicitly would help as that is the only point where we must have the writes actually visible to the DMA HW.The ones written here as
smp_mb()are not required to be DMA ones and could all be NOP's on UP..
Quoted 7 lines
Hmm, I'm not sure about that.
If we've written a new (i.e. previously invalid) valid PTE to a page-table and then we install that page-table into an STE hitlessly (let's say we write the S2TTB field) then isn't there a window before we do the STE invalidation where the page-table might be accessible to the SMMU but the new PTE is still sitting in the CPU?
Hmm! Yes seems like it.
However, that's seems like a general bug, if we allocate an iommu_domain and immediately hitlessly install it, then there would be no dma_wmb() for the page table memory prior to the earliest point the HW is able to read the STE.
What I wrote is is how things are intended to work, so lets fix it with this?
@@ -1173,6 +1173,13 @@ void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry,
__le64 unused_update[NUM_ENTRY_QWORDS];
u8 used_qword_diff;+ /*
+ * Many of the entry structures have pointers to other structures that
+ * need to have their updates be visible before any writes of the entry
+ * happen.
+ */
+ dma_wmb();
+
used_qword_diff =
arm_smmu_entry_qword_diff(writer, entry, target, unused_update);
if (hweight8(used_qword_diff) == 1) {Jason
Will Deacon
maintainerRe: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 62 lines · Jason Gunthorpe
Quoted 42 lines · Will Deacon
Quoted 33 lines · Jason Gunthorpe
Quoted 24 lines · Will Deacon
Quoted 23 lines · Will Deacon
Quoted 7 lines · Nicolin Chen
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +7
-0+/* +* We are committed to updating the STE. Ensure the invalidation array +* is visible to concurrent map/unmap threads, and acquire any racing +* IOPTE updates. +* +* [CPU0] | [CPU1] +* | drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c arm_smmu_install_old_domain_invs(struct arm_smmu_attach_state *state) · line 3568
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +10
-0+* change IOPTEs and TLB flush: | +* arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { +* ... | rcu_assign_pointer(new_invs); +* smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs +* ... | kfree_rcu(old_invs, rcu); +* // load invalidation array | } +* invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { +* | STE = TTB0 // read new IOPTEs +*/ +smp_mb ();I don't think we need to duplicate this comment three times, you can just refer to the first function (e.g. "See ordering comment in
arm_smmu_domain_inv_range()").Quoted 7 lines
Quoted 3 lines
However, isn't the comment above misleading for this case?
arm_smmu_install_old_domain_invs()has the sequencing the other way around on CPU 1: we should update the STE first.I also think we probably want a
dma_mb()instead of ansmp_mb()for all of these examples? It won't make any practical difference but I think it helps readability given that one of the readers is the PTW.Quoted 7 lines
The only actual
dma_wmb()is insidearm_smmu_install_ste_for_dev()after updating the STE. Adding that line explicitly would help as that is the only point where we must have the writes actually visible to the DMA HW.The ones written here as
smp_mb()are not required to be DMA ones and could all be NOP's on UP..Quoted 7 lines
Hmm, I'm not sure about that.
If we've written a new (i.e. previously invalid) valid PTE to a page-table and then we install that page-table into an STE hitlessly (let's say we write the S2TTB field) then isn't there a window before we do the STE invalidation where the page-table might be accessible to the SMMU but the new PTE is still sitting in the CPU?
Hmm! Yes seems like it.
However, that's seems like a general bug, if we allocate an
iommu_domainand immediately hitlessly install it, then there would be nodma_wmb()for the page table memory prior to the earliest point the HW is able to read the STE.What I wrote is is how things are intended to work, so lets fix it with this?
Review void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry, · line 1181
Quoted 9 lines
(quoted diff) +6
-0@@ -1173,6 +1173,13 @@ void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry, __le64 unused_update[NUM_ENTRY_QWORDS]; u8 used_qword_diff; + /* + * Many of the entry structures have pointers to other structures that + * need to have their updates be visible before any writes of the entry + * happen. + */ + dma_wmb();
If we do that, can we drop the smp_mb()s from
arm_smmu_install_{old,new}_domain_invs()?
Then the barrier in arm_smmu_domain_inv_range() is kind of irritating but it demonstrates the need for additional ordering (beyond what we get from the pgtable code) to ensure that a prior update to the page-table is ordered before the RCU-protected read of the invalidation array pointer. So that becomes the special case, with everybody else relying on a dma_wmb() from either the cmdq insertion or the CD/STE update.
Will
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 18 lines · Will Deacon
Quoted 17 lines · Jason Gunthorpe
Quoted 16 lines · Will Deacon
Quoted 15 lines · Jason Gunthorpe
Quoted 14 lines · Will Deacon
Quoted 13 lines · Will Deacon
Quoted 12 lines · Nicolin Chen
(quoted diff) +12
-0+ /* + * We are committed to updating the STE. Ensure the invalidation array + * is visible to concurrent map/unmap threads, and acquire any racing + * IOPTE updates. + * + * [CPU0] | [CPU1] + * | + * change IOPTEs and TLB flush: | + * arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { + * ... | rcu_assign_pointer(new_invs); + * smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs + * ... | kfree_rcu(old_invs, rcu);
Quoted 8 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ * // load invalidation array | } + * invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { + * | STE = TTB0 // read new IOPTEs + */ + smp_mb();If we do that, can we drop the smp_mb()s from
arm_smmu_install_{old,new}_domain_invs()?
I suppose so, but domain attach isn't a performance path so it depends on your preference for strict pairing of barriers. Currently the two smp_mbs() are paired. Can we reliably pair smp_mb() with dma_wmb()? Are you happy with that clarity?
My view is attach isn't a performance path, so having extra barriers is fine if it helps understandability.
Jason
Will Deacon
maintainerRe: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 28 lines · Jason Gunthorpe
Quoted 18 lines · Will Deacon
Quoted 17 lines · Jason Gunthorpe
Quoted 16 lines · Will Deacon
Quoted 15 lines · Jason Gunthorpe
Quoted 14 lines · Will Deacon
Quoted 13 lines · Will Deacon
Quoted 12 lines · Nicolin Chen
(quoted diff) +12
-0+ /* + * We are committed to updating the STE. Ensure the invalidation array + * is visible to concurrent map/unmap threads, and acquire any racing + * IOPTE updates. + * + * [CPU0] | [CPU1] + * | + * change IOPTEs and TLB flush: | + * arm_smmu_domain_inv_range() { | arm_smmu_install_old_domain_invs { + * ... | rcu_assign_pointer(new_invs); + * smp_mb(); // ensure IOPTEs | smp_mb(); // ensure new_invs + * ... | kfree_rcu(old_invs, rcu);Quoted 8 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ * // load invalidation array | } + * invs = rcu_dereference(); | arm_smmu_install_ste_for_dev { + * | STE = TTB0 // read new IOPTEs + */ + smp_mb();If we do that, can we drop the smp_mb()s from
arm_smmu_install_{old,new}_domain_invs()?
Quoted 4 lines
I suppose so, but domain attach isn't a performance path so it depends on your preference for strict pairing of barriers. Currently the two
smp_mbs()are paired. Can we reliably pairsmp_mb()withdma_wmb()? Are you happy with that clarity?
Yeah, I think that's ok.
Quoted 2 lines
My view is attach isn't a performance path, so having extra barriers is fine if it helps understandability.
I think that the more barriers we have, the harder the code is to understand so I would prefer just to have the smp_mb() for the write->read case in arm_smmu_domain_inv_range() along with a comment explaining why it's needed.
I think that also means that my concern about the old comments on the other patch largely disappears.
Will
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Quoted 15 lines · Will Deacon
Quoted 8 lines · Jason Gunthorpe
Quoted 2 lines · Will Deacon
If we do that, can we drop the smp_mb()s from
arm_smmu_install_{old,new}_domain_invs()?I suppose so, but domain attach isn't a performance path so it depends on your preference for strict pairing of barriers. Currently the two
smp_mbs()are paired. Can we reliably pairsmp_mb()withdma_wmb()? Are you happy with that clarity?Yeah, I think that's ok.
Quoted 2 lines
My view is attach isn't a performance path, so having extra barriers is fine if it helps understandability.
Quoted 7 lines
I think that the more barriers we have, the harder the code is to understand so I would prefer just to have the
smp_mb()for thewrite->readcase inarm_smmu_domain_inv_range()along with a comment explaining why it's needed.I think that also means that my concern about the old comments on the other patch largely disappears.
I sent v10. Thanks for the insightful reviews!
Nicolin
Re: [PATCH v9 6/7] iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
In reply to Jason GunthorpeQuoted 21 lines · Jason Gunthorpe
Quoted 5 lines · Will Deacon
If we've written a new (i.e. previously invalid) valid PTE to a page-table and then we install that page-table into an STE hitlessly (let's say we write the S2TTB field) then isn't there a window before we do the STE invalidation where the page-table might be accessible to the SMMU but the new PTE is still sitting in the CPU?
Hmm! Yes seems like it.
However, that's seems like a general bug, if we allocate an
iommu_domainand immediately hitlessly install it, then there would be nodma_wmb()for the page table memory prior to the earliest point the HW is able to read the STE.What I wrote is is how things are intended to work, so lets fix it with this?
(quoted diff) +0
-0@@ -1173,6 +1173,13 @@ void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry, __le64 unused_update[NUM_ENTRY_QWORDS]; u8 used_qword_diff;
Review void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry, · line 1185
Quoted 10 lines
(quoted diff) +7
-0+ /* + * Many of the entry structures have pointers to other structures that + * need to have their updates be visible before any writes of the entry + * happen. + */ + dma_wmb(); + used_qword_diff = arm_smmu_entry_qword_diff(writer, entry, target, unused_update); if (hweight8(used_qword_diff) == 1) {
I will attach this patch as PATCH-1 in v10.
Thanks
Nicolin
PATCH v9 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
@@ -1079,13 +1079,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)
@@ -302,7 +279,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
Will Deacon
maintainerRe: [PATCH v9 7/7] iommu/arm-smmu-v3: Perform per-domain invalidations using arm_smmu_invs
Quoted 9 lines · Nicolin Chen
Replace 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 theCMDQ_MAX_TLBI_OPSas 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 aWARN_ON_ONCEto catch any missed case.
Quoted 7 lines
Reviewed-by Jason Gunthorpe <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- 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(-)
It's one thing replacing the invalidation implementation but I think you need to update some of the old ordering comments, too. In particular, the old code relies on the dma_wmb() during cmdq insertion to order updates to in-memory structures, which includes the pgtable in non-strict mode.
I don't think any of that is true now?
Will
Re: [PATCH v9 7/7] iommu/arm-smmu-v3: Perform per-domain invalidations using arm_smmu_invs
Quoted 18 lines · Will Deacon
Quoted 9 lines · Nicolin Chen
Replace 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 theCMDQ_MAX_TLBI_OPSas 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 aWARN_ON_ONCEto catch any missed case.Quoted 7 lines
Reviewed-by Jason Gunthorpe <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- 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(-)
Quoted 7 lines
It's one thing replacing the invalidation implementation but I think you need to update some of the old ordering comments, too. In particular, the old code relies on the
dma_wmb()during cmdq insertion to order updates to in-memory structures, which includes the pgtable in non-strict mode.I don't think any of that is true now?
OK. I'll update those ordering comments. Combining your latest suggestion of using dma_mb() v.s. smp_mb(). I assume this will be just doing s/dma_wmb/dma_mb in those comments.
Nicolin
Re: [PATCH v9 7/7] iommu/arm-smmu-v3: Perform per-domain invalidations using arm_smmu_invs
In reply to Will DeaconQuoted 18 lines · Will Deacon
Quoted 9 lines · Nicolin Chen
Replace 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 theCMDQ_MAX_TLBI_OPSas 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 aWARN_ON_ONCEto catch any missed case.Quoted 7 lines
Reviewed-by Jason Gunthorpe <[email protected]>Signed-off-by Nicolin Chen <[email protected]>--- 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(-)
Quoted 7 lines
It's one thing replacing the invalidation implementation but I think you need to update some of the old ordering comments, too. In particular, the old code relies on the
dma_wmb()during cmdq insertion to order updates to in-memory structures, which includes the pgtable in non-strict mode.I don't think any of that is true now?
You are talking about this comment?
/*
* NOTE: when io-pgtable is in non-strict mode, we may get here with
* PTEs previously cleared by unmaps on the current CPU not yet visible
* to the SMMU. We are relying on the dma_wmb() implicit during cmd
* insertion to guarantee those are observed before the TLBI. Do be
* careful, 007.
*/
Maybe we can restate that a little bit:
/*
* If the DMA API is running in non-strict mode then another CPU could
* have changed the page table and not invoked any flush op. Instead the
* other CPU will do an atomic_read() and this CPU will have done an
* atomic_write(). That handshake is enough to acquire the page table
* writes from the other CPU.
*
* All command execution has a dma_wmb() to release all the in-memory
* structures written by this CPU, that barrier must also release the
* writes acquired from all the other CPUs too.
*
* There are other barriers and atomics on this path, but the above is
* the essential mechanism for ensuring that HW sees the page table
* writes from another CPU before it executes the IOTLB invalidation. */
I'm sure this series adds more barriers that might move things earlier in the sequence, but that isn't why those barries exist.
I think the original documentation was on to something important so I'd like to keep the information, though perhaps the comment belongs in dma-iommu.c as it really applies to all iommu drivers on all architectures.
It is also why things were done as a smp_XX not a dma_XX. The intention was not to release things all the way to DMA, but just to release things enough that the thread writing the STE can acquire them and it will ensure they are released to DMA.
For instance on UP it is fine to have no barriers at all in the invalidation code, the dma_wmb() in the STE store command is sufficient.
Jason
Hi Will,
Quoted 21 lines · Nicolin Chen
* 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 ofs2_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.
Quoted 7 lines
This is on Github:
https://github.com/nicolinc/iommufd/commits/arm_smmu_invs-v9Changelog
v9:
* Setcur->ssidcorrectly inarm_smmu_master_build_inv()
* Add comments forINV_TYPE_S1_ASIDinarm_smmu_master_build_inv()
Sorry for a gentle push. This has been sitting here for a while. And we are at rc6. Any chance that it can make it to this cycle?
We have a dependent series that I am about to send v2:
https://lore.kernel.org/linux-iommu/[email protected]/
Anything that you think I should improve, please let me know.
Thanks!
Nicolin