Skip to content

Commit 587bb3e

Browse files
nicolincwilldeacon
authored andcommitted
iommu/arm-smmu-v3: Add arm_smmu_invs based arm_smmu_domain_inv_range()
Each 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. Co-developed-by: Jason Gunthorpe <jgg@nvidia.com> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com> Signed-off-by: Nicolin Chen <nicolinc@nvidia.com> Signed-off-by: Will Deacon <will@kernel.org>
1 parent b774297 commit 587bb3e

2 files changed

Lines changed: 221 additions & 13 deletions

File tree

drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c

Lines changed: 212 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2595,23 +2595,19 @@ static void arm_smmu_tlb_inv_context(void *cookie)
25952595
arm_smmu_atc_inv_domain(smmu_domain, 0, 0);
25962596
}
25972597

2598-
static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd,
2599-
unsigned long iova, size_t size,
2600-
size_t granule,
2601-
struct arm_smmu_domain *smmu_domain)
2598+
static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,
2599+
struct arm_smmu_cmdq_batch *cmds,
2600+
struct arm_smmu_cmdq_ent *cmd,
2601+
unsigned long iova, size_t size,
2602+
size_t granule, size_t pgsize)
26022603
{
2603-
struct arm_smmu_device *smmu = smmu_domain->smmu;
2604-
unsigned long end = iova + size, num_pages = 0, tg = 0;
2604+
unsigned long end = iova + size, num_pages = 0, tg = pgsize;
26052605
size_t inv_range = granule;
2606-
struct arm_smmu_cmdq_batch cmds;
26072606

26082607
if (!size)
26092608
return;
26102609

26112610
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
2612-
/* Get the leaf page size */
2613-
tg = __ffs(smmu_domain->domain.pgsize_bitmap);
2614-
26152611
num_pages = size >> tg;
26162612

26172613
/* Convert page size of 12,14,16 (log2) to 1,2,3 */
@@ -2631,8 +2627,6 @@ static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd,
26312627
num_pages++;
26322628
}
26332629

2634-
arm_smmu_cmdq_batch_init(smmu, &cmds, cmd);
2635-
26362630
while (iova < end) {
26372631
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV) {
26382632
/*
@@ -2660,9 +2654,26 @@ static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd,
26602654
}
26612655

26622656
cmd->tlbi.addr = iova;
2663-
arm_smmu_cmdq_batch_add(smmu, &cmds, cmd);
2657+
arm_smmu_cmdq_batch_add(smmu, cmds, cmd);
26642658
iova += inv_range;
26652659
}
2660+
}
2661+
2662+
static void __arm_smmu_tlb_inv_range(struct arm_smmu_cmdq_ent *cmd,
2663+
unsigned long iova, size_t size,
2664+
size_t granule,
2665+
struct arm_smmu_domain *smmu_domain)
2666+
{
2667+
struct arm_smmu_device *smmu = smmu_domain->smmu;
2668+
struct arm_smmu_cmdq_batch cmds;
2669+
size_t pgsize;
2670+
2671+
/* Get the leaf page size */
2672+
pgsize = __ffs(smmu_domain->domain.pgsize_bitmap);
2673+
2674+
arm_smmu_cmdq_batch_init(smmu, &cmds, cmd);
2675+
arm_smmu_cmdq_batch_add_range(smmu, &cmds, cmd, iova, size, granule,
2676+
pgsize);
26662677
arm_smmu_cmdq_batch_submit(smmu, &cmds);
26672678
}
26682679

@@ -2718,6 +2729,194 @@ void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid,
27182729
__arm_smmu_tlb_inv_range(&cmd, iova, size, granule, smmu_domain);
27192730
}
27202731

2732+
static bool arm_smmu_inv_size_too_big(struct arm_smmu_device *smmu, size_t size,
2733+
size_t granule)
2734+
{
2735+
size_t max_tlbi_ops;
2736+
2737+
/* 0 size means invalidate all */
2738+
if (!size || size == SIZE_MAX)
2739+
return true;
2740+
2741+
if (smmu->features & ARM_SMMU_FEAT_RANGE_INV)
2742+
return false;
2743+
2744+
/*
2745+
* Borrowed from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h,
2746+
* this is used as a threshold to replace "size_opcode" commands with a
2747+
* single "nsize_opcode" command, when SMMU doesn't implement the range
2748+
* invalidation feature, where there can be too many per-granule TLBIs,
2749+
* resulting in a soft lockup.
2750+
*/
2751+
max_tlbi_ops = 1 << (ilog2(granule) - 3);
2752+
return size >= max_tlbi_ops * granule;
2753+
}
2754+
2755+
/* Used by non INV_TYPE_ATS* invalidations */
2756+
static void arm_smmu_inv_to_cmdq_batch(struct arm_smmu_inv *inv,
2757+
struct arm_smmu_cmdq_batch *cmds,
2758+
struct arm_smmu_cmdq_ent *cmd,
2759+
unsigned long iova, size_t size,
2760+
unsigned int granule)
2761+
{
2762+
if (arm_smmu_inv_size_too_big(inv->smmu, size, granule)) {
2763+
cmd->opcode = inv->nsize_opcode;
2764+
arm_smmu_cmdq_batch_add(inv->smmu, cmds, cmd);
2765+
return;
2766+
}
2767+
2768+
cmd->opcode = inv->size_opcode;
2769+
arm_smmu_cmdq_batch_add_range(inv->smmu, cmds, cmd, iova, size, granule,
2770+
inv->pgsize);
2771+
}
2772+
2773+
static inline bool arm_smmu_invs_end_batch(struct arm_smmu_inv *cur,
2774+
struct arm_smmu_inv *next)
2775+
{
2776+
/* Changing smmu means changing command queue */
2777+
if (cur->smmu != next->smmu)
2778+
return true;
2779+
/* The batch for S2 TLBI must be done before nested S1 ASIDs */
2780+
if (cur->type != INV_TYPE_S2_VMID_S1_CLEAR &&
2781+
next->type == INV_TYPE_S2_VMID_S1_CLEAR)
2782+
return true;
2783+
/* ATS must be after a sync of the S1/S2 invalidations */
2784+
if (!arm_smmu_inv_is_ats(cur) && arm_smmu_inv_is_ats(next))
2785+
return true;
2786+
return false;
2787+
}
2788+
2789+
static void __arm_smmu_domain_inv_range(struct arm_smmu_invs *invs,
2790+
unsigned long iova, size_t size,
2791+
unsigned int granule, bool leaf)
2792+
{
2793+
struct arm_smmu_cmdq_batch cmds = {};
2794+
struct arm_smmu_inv *cur;
2795+
struct arm_smmu_inv *end;
2796+
2797+
cur = invs->inv;
2798+
end = cur + READ_ONCE(invs->num_invs);
2799+
/* Skip any leading entry marked as a trash */
2800+
for (; cur != end; cur++)
2801+
if (READ_ONCE(cur->users))
2802+
break;
2803+
while (cur != end) {
2804+
struct arm_smmu_device *smmu = cur->smmu;
2805+
struct arm_smmu_cmdq_ent cmd = {
2806+
/*
2807+
* Pick size_opcode to run arm_smmu_get_cmdq(). This can
2808+
* be changed to nsize_opcode, which would result in the
2809+
* same CMDQ pointer.
2810+
*/
2811+
.opcode = cur->size_opcode,
2812+
};
2813+
struct arm_smmu_inv *next;
2814+
2815+
if (!cmds.num)
2816+
arm_smmu_cmdq_batch_init(smmu, &cmds, &cmd);
2817+
2818+
switch (cur->type) {
2819+
case INV_TYPE_S1_ASID:
2820+
cmd.tlbi.asid = cur->id;
2821+
cmd.tlbi.leaf = leaf;
2822+
arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, iova, size,
2823+
granule);
2824+
break;
2825+
case INV_TYPE_S2_VMID:
2826+
cmd.tlbi.vmid = cur->id;
2827+
cmd.tlbi.leaf = leaf;
2828+
arm_smmu_inv_to_cmdq_batch(cur, &cmds, &cmd, iova, size,
2829+
granule);
2830+
break;
2831+
case INV_TYPE_S2_VMID_S1_CLEAR:
2832+
/* CMDQ_OP_TLBI_S12_VMALL already flushed S1 entries */
2833+
if (arm_smmu_inv_size_too_big(cur->smmu, size, granule))
2834+
continue;
2835+
cmd.tlbi.vmid = cur->id;
2836+
arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd);
2837+
break;
2838+
case INV_TYPE_ATS:
2839+
arm_smmu_atc_inv_to_cmd(cur->ssid, iova, size, &cmd);
2840+
cmd.atc.sid = cur->id;
2841+
arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd);
2842+
break;
2843+
case INV_TYPE_ATS_FULL:
2844+
arm_smmu_atc_inv_to_cmd(IOMMU_NO_PASID, 0, 0, &cmd);
2845+
cmd.atc.sid = cur->id;
2846+
arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd);
2847+
break;
2848+
default:
2849+
WARN_ON_ONCE(1);
2850+
continue;
2851+
}
2852+
2853+
/* Skip any trash entry in-between */
2854+
for (next = cur + 1; next != end; next++)
2855+
if (READ_ONCE(next->users))
2856+
break;
2857+
2858+
if (cmds.num &&
2859+
(next == end || arm_smmu_invs_end_batch(cur, next))) {
2860+
arm_smmu_cmdq_batch_submit(smmu, &cmds);
2861+
cmds.num = 0;
2862+
}
2863+
cur = next;
2864+
}
2865+
}
2866+
2867+
void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
2868+
unsigned long iova, size_t size,
2869+
unsigned int granule, bool leaf)
2870+
{
2871+
struct arm_smmu_invs *invs;
2872+
2873+
/*
2874+
* An invalidation request must follow some IOPTE change and then load
2875+
* an invalidation array. In the meantime, a domain attachment mutates
2876+
* the array and then stores an STE/CD asking SMMU HW to acquire those
2877+
* changed IOPTEs.
2878+
*
2879+
* When running alone, a domain attachment relies on the dma_wmb() in
2880+
* arm_smmu_write_entry() used by arm_smmu_install_ste_for_dev().
2881+
*
2882+
* But in a race, these two can be interdependent, making it a special
2883+
* case requiring an additional smp_mb() for the write->read ordering.
2884+
* Pairing with the dma_wmb() in arm_smmu_install_ste_for_dev(), this
2885+
* makes sure that IOPTE update prior to this point is visable to SMMU
2886+
* hardware before we load the updated invalidation array.
2887+
*
2888+
* [CPU0] | [CPU1]
2889+
* change IOPTE on new domain: |
2890+
* arm_smmu_domain_inv_range() { | arm_smmu_install_new_domain_invs()
2891+
* smp_mb(); // ensures IOPTE | arm_smmu_install_ste_for_dev {
2892+
* // seen by SMMU | dma_wmb(); // ensures invs update
2893+
* // load the updated invs | // before updating STE
2894+
* invs = rcu_dereference(); | STE = TTB0;
2895+
* ... | ...
2896+
* } | }
2897+
*/
2898+
smp_mb();
2899+
2900+
rcu_read_lock();
2901+
invs = rcu_dereference(smmu_domain->invs);
2902+
2903+
/*
2904+
* Avoid locking unless ATS is being used. No ATC invalidation can be
2905+
* going on after a domain is detached.
2906+
*/
2907+
if (invs->has_ats) {
2908+
unsigned long flags;
2909+
2910+
read_lock_irqsave(&invs->rwlock, flags);
2911+
__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
2912+
read_unlock_irqrestore(&invs->rwlock, flags);
2913+
} else {
2914+
__arm_smmu_domain_inv_range(invs, iova, size, granule, leaf);
2915+
}
2916+
2917+
rcu_read_unlock();
2918+
}
2919+
27212920
static void arm_smmu_tlb_inv_page_nosync(struct iommu_iotlb_gather *gather,
27222921
unsigned long iova, size_t granule,
27232922
void *cookie)

drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,15 @@ void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid,
10871087
int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain,
10881088
unsigned long iova, size_t size);
10891089

1090+
void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
1091+
unsigned long iova, size_t size,
1092+
unsigned int granule, bool leaf);
1093+
1094+
static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)
1095+
{
1096+
arm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);
1097+
}
1098+
10901099
void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
10911100
struct arm_smmu_cmdq *cmdq);
10921101
int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,

0 commit comments

Comments
 (0)