From: Jason Gunthorpe <jgg@nvidia.com>
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 <jgg@nvidia.com>
Co-developed-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 90 +++++++
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c | 93 +++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 242 ++++++++++++++++++
3 files changed, 425 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 96a23ca633cb6..d079c66a41e94 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -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(struct arm_smmu_inv *inv)
+{
+ return inv->type == INV_TYPE_ATS || inv->type == INV_TYPE_ATS_FULL;
+}
+
+/**
+ * struct arm_smmu_invs - Per-domain invalidation array
+ * @num_invs: number of invalidations in the flexible array
+ * @rwlock: optional rwlock to fench ATS operations
+ * @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
+ * @rcu: rcu head for kfree_rcu()
+ * @inv: flexible invalidation array
+ *
+ * The arm_smmu_invs is an RCU data structure. During a ->attach_dev callback,
+ * arm_smmu_invs_merge(), arm_smmu_invs_unref() and arm_smmu_invs_purge() will
+ * be used to allocate a new copy of an old array for addition and deletion in
+ * the old domain's and new domain's invs arrays.
+ *
+ * The arm_smmu_invs_unref() mutates a given array, by internally reducing the
+ * users counts of some given entries. This exists to support a no-fail routine
+ * like attaching to an IOMMU_DOMAIN_BLOCKED. And it could pair with a followup
+ * arm_smmu_invs_purge() call to generate a new clean array.
+ *
+ * Concurrent invalidation thread will push every invalidation described in the
+ * array into the command queue for each invalidation event. It is designed like
+ * this to optimize the invalidation fast path by avoiding locks.
+ *
+ * A domain can be shared across SMMU instances. When an instance gets removed,
+ * it would delete all the entries that belong to that SMMU instance. Then, a
+ * synchronize_rcu() would have to be called to sync the array, to prevent any
+ * concurrent invalidation thread accessing the old array from issuing commands
+ * to the command queue of a removed SMMU instance.
+ */
+struct arm_smmu_invs {
+ size_t num_invs;
+ rwlock_t rwlock;
+ bool has_ats;
+ struct rcu_head rcu;
+ struct arm_smmu_inv inv[];
+};
+
+static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
+{
+ struct arm_smmu_invs *new_invs;
+
+ new_invs = kzalloc(struct_size(new_invs, inv, num_invs), GFP_KERNEL);
+ if (!new_invs)
+ return ERR_PTR(-ENOMEM);
+ rwlock_init(&new_invs->rwlock);
+ new_invs->num_invs = num_invs;
+ return new_invs;
+}
+
+struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
+ struct arm_smmu_invs *to_merge);
+size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
+ struct arm_smmu_invs *to_unref,
+ void (*flush_fn)(struct arm_smmu_inv *inv));
+struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs,
+ size_t num_trashes);
+
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;
@@ -956,6 +1045,7 @@ struct arm_smmu_domain *arm_smmu_domain_alloc(void);
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
{
+ kfree_rcu(smmu_domain->invs, rcu);
kfree(smmu_domain);
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
index d2671bfd37981..a37a55480b3ff 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-test.c
@@ -567,6 +567,98 @@ static void arm_smmu_v3_write_cd_test_sva_release(struct kunit *test)
NUM_EXPECTED_SYNCS(2));
}
+static void arm_smmu_v3_invs_test_verify(struct kunit *test,
+ struct arm_smmu_invs *invs, int num,
+ const int *ids, const int *users)
+{
+ KUNIT_EXPECT_EQ(test, invs->num_invs, num);
+ while (num--) {
+ KUNIT_EXPECT_EQ(test, invs->inv[num].id, ids[num]);
+ KUNIT_EXPECT_EQ(test, refcount_read(&invs->inv[num].users),
+ users[num]);
+ }
+}
+
+static struct arm_smmu_invs invs1 = {
+ .num_invs = 3,
+ .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
+ { .type = INV_TYPE_S2_VMID, .id = 2, },
+ { .type = INV_TYPE_S2_VMID, .id = 3, }, },
+};
+
+static struct arm_smmu_invs invs2 = {
+ .num_invs = 3,
+ .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .type = INV_TYPE_ATS, .id = 4, },
+ { .type = INV_TYPE_ATS, .id = 5, }, },
+};
+
+static struct arm_smmu_invs invs3 = {
+ .num_invs = 3,
+ .inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
+ { .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
+ { .type = INV_TYPE_ATS, .id = 6, }, },
+};
+
+static void arm_smmu_v3_invs_test(struct kunit *test)
+{
+ const int results1[2][3] = { { 1, 2, 3, }, { 1, 1, 1, }, };
+ const int results2[2][5] = { { 1, 2, 3, 4, 5, }, { 2, 1, 1, 1, 1, }, };
+ const int results3[2][3] = { { 1, 2, 3, }, { 1, 1, 1, }, };
+ const int results4[2][5] = { { 1, 2, 3, 5, 6, }, { 2, 1, 1, 1, 1, }, };
+ const int results5[2][5] = { { 1, 2, 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;
+ size_t num_trashes;
+
+ /* 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) */
+ num_trashes = 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, 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) */
+ num_trashes = 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, num_trashes, 2);
+
+ /* Test6: purge test_b (new array) */
+ test_a = arm_smmu_invs_purge(test_b, num_trashes);
+ 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) */
+ num_trashes = arm_smmu_invs_unref(test_a, &invs3, NULL);
+ KUNIT_EXPECT_EQ(test, test_a->num_invs, 0);
+ KUNIT_EXPECT_EQ(test, num_trashes, 0);
+
+ kfree(test_a);
+}
+
static struct kunit_case arm_smmu_v3_test_cases[] = {
KUNIT_CASE(arm_smmu_v3_write_ste_test_bypass_to_abort),
KUNIT_CASE(arm_smmu_v3_write_ste_test_abort_to_bypass),
@@ -590,6 +682,7 @@ static struct kunit_case arm_smmu_v3_test_cases[] = {
KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_s1_stall),
KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_clear),
KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_release),
+ KUNIT_CASE(arm_smmu_v3_invs_test),
{},
};
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 00d43080efaa8..2a8a0c76af67b 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -26,6 +26,7 @@
#include <linux/pci.h>
#include <linux/pci-ats.h>
#include <linux/platform_device.h>
+#include <linux/sort.h>
#include <linux/string_choices.h>
#include <kunit/visibility.h>
#include <uapi/linux/iommufd.h>
@@ -1015,6 +1016,239 @@ static void arm_smmu_page_response(struct device *dev, struct iopf_fault *unused
*/
}
+/* Invalidation array manipulation functions */
+static int arm_smmu_inv_cmp(const struct arm_smmu_inv *l,
+ const struct arm_smmu_inv *r)
+{
+ if (l->smmu != r->smmu)
+ return cmp_int((uintptr_t)l->smmu, (uintptr_t)r->smmu);
+ if (l->type != r->type)
+ return cmp_int(l->type, r->type);
+ return cmp_int(l->id, r->id);
+}
+
+/*
+ * Compare of two sorted arrays items. If one side is past the end of the array,
+ * return the other side to let it run out the iteration.
+ */
+static inline int arm_smmu_invs_cmp(const struct arm_smmu_invs *l, size_t l_idx,
+ const struct arm_smmu_invs *r, size_t r_idx)
+{
+ if (l_idx != l->num_invs && r_idx != r->num_invs)
+ return arm_smmu_inv_cmp(&l->inv[l_idx], &r->inv[r_idx]);
+ if (l_idx != l->num_invs)
+ return -1;
+ return 1;
+}
+
+/**
+ * arm_smmu_invs_merge() - Merge @to_merge into @invs and generate a new array
+ * @invs: the base invalidation array
+ * @to_merge: an array of invlidations to merge
+ *
+ * Return: a newly allocated array on success, or ERR_PTR
+ *
+ * This function must be locked and serialized with arm_smmu_invs_unref() and
+ * arm_smmu_invs_purge(), but do not lockdep on any lock for KUNIT test.
+ *
+ * Both @invs and @to_merge must be sorted, to ensure the returned array will be
+ * sorted as well.
+ *
+ * Caller is resposible for freeing the @invs and the returned new one.
+ *
+ * Entries marked as trash will be purged in the returned array.
+ */
+VISIBLE_IF_KUNIT
+struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
+ struct arm_smmu_invs *to_merge)
+{
+ struct arm_smmu_invs *new_invs;
+ struct arm_smmu_inv *new;
+ size_t num_trashes = 0;
+ size_t num_adds = 0;
+ size_t i, j;
+
+ for (i = j = 0; i != invs->num_invs || j != to_merge->num_invs;) {
+ int cmp = arm_smmu_invs_cmp(invs, i, to_merge, j);
+
+ /* Skip any unwanted trash entry */
+ if (cmp < 0 && !refcount_read(&invs->inv[i].users)) {
+ num_trashes++;
+ i++;
+ continue;
+ }
+
+ if (cmp < 0) {
+ /* not found in to_merge, leave alone */
+ i++;
+ } else if (cmp == 0) {
+ /* same item */
+ i++;
+ j++;
+ } else {
+ /* unique to to_merge */
+ num_adds++;
+ j++;
+ }
+ }
+
+ new_invs = arm_smmu_invs_alloc(invs->num_invs - num_trashes + num_adds);
+ if (IS_ERR(new_invs))
+ return new_invs;
+
+ new = new_invs->inv;
+ for (i = j = 0; i != invs->num_invs || j != to_merge->num_invs;) {
+ int cmp = arm_smmu_invs_cmp(invs, i, to_merge, j);
+
+ if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
+ i++;
+ continue;
+ }
+
+ if (cmp < 0) {
+ *new = invs->inv[i];
+ i++;
+ } else if (cmp == 0) {
+ *new = invs->inv[i];
+ refcount_inc(&new->users);
+ i++;
+ j++;
+ } else {
+ *new = to_merge->inv[j];
+ refcount_set(&new->users, 1);
+ j++;
+ }
+
+ if (new != new_invs->inv)
+ WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
+ new++;
+ }
+
+ WARN_ON(new != new_invs->inv + new_invs->num_invs);
+
+ return new_invs;
+}
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_merge);
+
+/**
+ * arm_smmu_invs_unref() - Find in @invs for all entries in @to_unref, decrease
+ * the user counts without deletions
+ * @invs: the base invalidation array
+ * @to_unref: an array of invlidations to decrease their user counts
+ * @flush_fn: A callback function to invoke, when an entry's user count reduces
+ * to 0
+ *
+ * Return: the number of trash entries in the array, for arm_smmu_invs_purge()
+ *
+ * This function will not fail. Any entry with users=0 will be marked as trash.
+ * All tailing trash entries in the array will be dropped. And the size of the
+ * array will be trimmed properly. All trash entries in-between will remain in
+ * the @invs until being completely deleted by the next arm_smmu_invs_merge()
+ * or an arm_smmu_invs_purge() function call.
+ *
+ * This function must be locked and serialized with arm_smmu_invs_merge() and
+ * arm_smmu_invs_purge(), but do not lockdep on any mutex for KUNIT test.
+ *
+ * Note that the final @invs->num_invs might not reflect the actual number of
+ * invalidations due to trash entries. Any reader should take the read lock to
+ * iterate each entry and check its users counter till the last entry.
+ */
+VISIBLE_IF_KUNIT
+size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
+ struct arm_smmu_invs *to_unref,
+ void (*flush_fn)(struct arm_smmu_inv *inv))
+{
+ unsigned long flags;
+ size_t num_trashes = 0;
+ size_t num_invs = 0;
+ size_t i, j;
+
+ for (i = j = 0; i != invs->num_invs || j != to_unref->num_invs;) {
+ int cmp;
+
+ /* Skip any existing trash entry */
+ if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
+ num_trashes++;
+ i++;
+ continue;
+ }
+
+ cmp = arm_smmu_invs_cmp(invs, i, to_unref, j);
+ if (cmp < 0) {
+ /* not found in to_unref, leave alone */
+ i++;
+ num_invs = i;
+ } else if (cmp == 0) {
+ /* same item */
+ if (refcount_dec_and_test(&invs->inv[i].users)) {
+ /* KUNIT test doesn't pass in a flush_fn */
+ if (flush_fn)
+ flush_fn(&invs->inv[i]);
+ num_trashes++;
+ } else {
+ num_invs = i + 1;
+ }
+ i++;
+ j++;
+ } else {
+ /* item in to_unref is not in invs or already a trash */
+ WARN_ON(true);
+ j++;
+ }
+ }
+
+ /* Exclude any tailing trash */
+ num_trashes -= invs->num_invs - num_invs;
+
+ /* The lock is required to fence concurrent ATS operations. */
+ write_lock_irqsave(&invs->rwlock, flags);
+ WRITE_ONCE(invs->num_invs, num_invs); /* Remove tailing trash entries */
+ write_unlock_irqrestore(&invs->rwlock, flags);
+
+ return num_trashes;
+}
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_unref);
+
+/**
+ * arm_smmu_invs_purge() - Purge all the trash entries in the @invs
+ * @invs: the base invalidation array
+ * @num_trashes: expected number of trash entries, typically returned by a prior
+ * arm_smmu_invs_unref() call
+ *
+ * Return: a newly allocated array on success removing all the trash entries, or
+ * NULL on failure
+ *
+ * This function must be locked and serialized with arm_smmu_invs_merge() and
+ * arm_smmu_invs_unref(), but do not lockdep on any lock for KUNIT test.
+ *
+ * Caller is resposible for freeing the @invs and the returned new one.
+ */
+VISIBLE_IF_KUNIT
+struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs,
+ size_t num_trashes)
+{
+ struct arm_smmu_invs *new_invs;
+ size_t i, j;
+
+ if (WARN_ON(invs->num_invs < num_trashes))
+ return NULL;
+
+ new_invs = arm_smmu_invs_alloc(invs->num_invs - num_trashes);
+ if (IS_ERR(new_invs))
+ return NULL;
+
+ for (i = j = 0; i != invs->num_invs; i++) {
+ if (!refcount_read(&invs->inv[i].users))
+ continue;
+ new_invs->inv[j] = invs->inv[i];
+ j++;
+ }
+
+ WARN_ON(j != new_invs->num_invs);
+ return new_invs;
+}
+EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_purge);
+
/* Context descriptor manipulation functions */
void arm_smmu_tlb_inv_asid(struct arm_smmu_device *smmu, u16 asid)
{
@@ -2462,13 +2696,21 @@ static bool arm_smmu_enforce_cache_coherency(struct iommu_domain *domain)
struct arm_smmu_domain *arm_smmu_domain_alloc(void)
{
struct arm_smmu_domain *smmu_domain;
+ struct arm_smmu_invs *new_invs;
smmu_domain = kzalloc(sizeof(*smmu_domain), GFP_KERNEL);
if (!smmu_domain)
return ERR_PTR(-ENOMEM);
+ new_invs = arm_smmu_invs_alloc(0);
+ if (IS_ERR(new_invs)) {
+ kfree(smmu_domain);
+ return ERR_CAST(new_invs);
+ }
+
INIT_LIST_HEAD(&smmu_domain->devices);
spin_lock_init(&smmu_domain->devices_lock);
+ rcu_assign_pointer(smmu_domain->invs, new_invs);
return smmu_domain;
}
--
2.43.0
Hi Nicolin,
kernel test robot noticed the following build errors:
[auto build test ERROR on soc/for-next]
[also build test ERROR on linus/master v6.18-rc3 next-20251027]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Nicolin-Chen/iommu-arm-smmu-v3-Explicitly-set-smmu_domain-stage-for-SVA/20251016-034754
base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
patch link: https://lore.kernel.org/r/345bb7703ebd19992694758b47e371900267fa0e.1760555863.git.nicolinc%40nvidia.com
patch subject: [PATCH v3 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
config: arm64-defconfig (https://download.01.org/0day-ci/archive/20251027/202510271909.iEzPjNv4-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251027/202510271909.iEzPjNv4-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202510271909.iEzPjNv4-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1062:23: error: static declaration of 'arm_smmu_invs_merge' follows non-static declaration
1062 | struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
In file included from drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:34:
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:731:23: note: previous declaration of 'arm_smmu_invs_merge' with type 'struct arm_smmu_invs *(struct arm_smmu_invs *, struct arm_smmu_invs *)'
731 | struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1157:8: error: static declaration of 'arm_smmu_invs_unref' follows non-static declaration
1157 | size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:733:8: note: previous declaration of 'arm_smmu_invs_unref' with type 'size_t(struct arm_smmu_invs *, struct arm_smmu_invs *, void (*)(struct arm_smmu_inv *))' {aka 'long unsigned int(struct arm_smmu_invs *, struct arm_smmu_invs *, void (*)(struct arm_smmu_inv *))'}
733 | size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1227:23: error: static declaration of 'arm_smmu_invs_purge' follows non-static declaration
1227 | struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:736:23: note: previous declaration of 'arm_smmu_invs_purge' with type 'struct arm_smmu_invs *(struct arm_smmu_invs *, size_t)' {aka 'struct arm_smmu_invs *(struct arm_smmu_invs *, long unsigned int)'}
736 | struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1227:23: warning: 'arm_smmu_invs_purge' defined but not used [-Wunused-function]
1227 | struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1157:8: warning: 'arm_smmu_invs_unref' defined but not used [-Wunused-function]
1157 | size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1062:23: warning: 'arm_smmu_invs_merge' defined but not used [-Wunused-function]
1062 | struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
| ^~~~~~~~~~~~~~~~~~~
vim +/arm_smmu_invs_merge +1062 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
1043
1044 /**
1045 * arm_smmu_invs_merge() - Merge @to_merge into @invs and generate a new array
1046 * @invs: the base invalidation array
1047 * @to_merge: an array of invlidations to merge
1048 *
1049 * Return: a newly allocated array on success, or ERR_PTR
1050 *
1051 * This function must be locked and serialized with arm_smmu_invs_unref() and
1052 * arm_smmu_invs_purge(), but do not lockdep on any lock for KUNIT test.
1053 *
1054 * Both @invs and @to_merge must be sorted, to ensure the returned array will be
1055 * sorted as well.
1056 *
1057 * Caller is resposible for freeing the @invs and the returned new one.
1058 *
1059 * Entries marked as trash will be purged in the returned array.
1060 */
1061 VISIBLE_IF_KUNIT
> 1062 struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
1063 struct arm_smmu_invs *to_merge)
1064 {
1065 struct arm_smmu_invs *new_invs;
1066 struct arm_smmu_inv *new;
1067 size_t num_trashes = 0;
1068 size_t num_adds = 0;
1069 size_t i, j;
1070
1071 for (i = j = 0; i != invs->num_invs || j != to_merge->num_invs;) {
1072 int cmp = arm_smmu_invs_cmp(invs, i, to_merge, j);
1073
1074 /* Skip any unwanted trash entry */
1075 if (cmp < 0 && !refcount_read(&invs->inv[i].users)) {
1076 num_trashes++;
1077 i++;
1078 continue;
1079 }
1080
1081 if (cmp < 0) {
1082 /* not found in to_merge, leave alone */
1083 i++;
1084 } else if (cmp == 0) {
1085 /* same item */
1086 i++;
1087 j++;
1088 } else {
1089 /* unique to to_merge */
1090 num_adds++;
1091 j++;
1092 }
1093 }
1094
1095 new_invs = arm_smmu_invs_alloc(invs->num_invs - num_trashes + num_adds);
1096 if (IS_ERR(new_invs))
1097 return new_invs;
1098
1099 new = new_invs->inv;
1100 for (i = j = 0; i != invs->num_invs || j != to_merge->num_invs;) {
1101 int cmp = arm_smmu_invs_cmp(invs, i, to_merge, j);
1102
1103 if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
1104 i++;
1105 continue;
1106 }
1107
1108 if (cmp < 0) {
1109 *new = invs->inv[i];
1110 i++;
1111 } else if (cmp == 0) {
1112 *new = invs->inv[i];
1113 refcount_inc(&new->users);
1114 i++;
1115 j++;
1116 } else {
1117 *new = to_merge->inv[j];
1118 refcount_set(&new->users, 1);
1119 j++;
1120 }
1121
1122 if (new != new_invs->inv)
1123 WARN_ON_ONCE(arm_smmu_inv_cmp(new - 1, new) == 1);
1124 new++;
1125 }
1126
1127 WARN_ON(new != new_invs->inv + new_invs->num_invs);
1128
1129 return new_invs;
1130 }
1131 EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_merge);
1132
1133 /**
1134 * arm_smmu_invs_unref() - Find in @invs for all entries in @to_unref, decrease
1135 * the user counts without deletions
1136 * @invs: the base invalidation array
1137 * @to_unref: an array of invlidations to decrease their user counts
1138 * @flush_fn: A callback function to invoke, when an entry's user count reduces
1139 * to 0
1140 *
1141 * Return: the number of trash entries in the array, for arm_smmu_invs_purge()
1142 *
1143 * This function will not fail. Any entry with users=0 will be marked as trash.
1144 * All tailing trash entries in the array will be dropped. And the size of the
1145 * array will be trimmed properly. All trash entries in-between will remain in
1146 * the @invs until being completely deleted by the next arm_smmu_invs_merge()
1147 * or an arm_smmu_invs_purge() function call.
1148 *
1149 * This function must be locked and serialized with arm_smmu_invs_merge() and
1150 * arm_smmu_invs_purge(), but do not lockdep on any mutex for KUNIT test.
1151 *
1152 * Note that the final @invs->num_invs might not reflect the actual number of
1153 * invalidations due to trash entries. Any reader should take the read lock to
1154 * iterate each entry and check its users counter till the last entry.
1155 */
1156 VISIBLE_IF_KUNIT
> 1157 size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
1158 struct arm_smmu_invs *to_unref,
1159 void (*flush_fn)(struct arm_smmu_inv *inv))
1160 {
1161 unsigned long flags;
1162 size_t num_trashes = 0;
1163 size_t num_invs = 0;
1164 size_t i, j;
1165
1166 for (i = j = 0; i != invs->num_invs || j != to_unref->num_invs;) {
1167 int cmp;
1168
1169 /* Skip any existing trash entry */
1170 if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
1171 num_trashes++;
1172 i++;
1173 continue;
1174 }
1175
1176 cmp = arm_smmu_invs_cmp(invs, i, to_unref, j);
1177 if (cmp < 0) {
1178 /* not found in to_unref, leave alone */
1179 i++;
1180 num_invs = i;
1181 } else if (cmp == 0) {
1182 /* same item */
1183 if (refcount_dec_and_test(&invs->inv[i].users)) {
1184 /* KUNIT test doesn't pass in a flush_fn */
1185 if (flush_fn)
1186 flush_fn(&invs->inv[i]);
1187 num_trashes++;
1188 } else {
1189 num_invs = i + 1;
1190 }
1191 i++;
1192 j++;
1193 } else {
1194 /* item in to_unref is not in invs or already a trash */
1195 WARN_ON(true);
1196 j++;
1197 }
1198 }
1199
1200 /* Exclude any tailing trash */
1201 num_trashes -= invs->num_invs - num_invs;
1202
1203 /* The lock is required to fence concurrent ATS operations. */
1204 write_lock_irqsave(&invs->rwlock, flags);
1205 WRITE_ONCE(invs->num_invs, num_invs); /* Remove tailing trash entries */
1206 write_unlock_irqrestore(&invs->rwlock, flags);
1207
1208 return num_trashes;
1209 }
1210 EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_unref);
1211
1212 /**
1213 * arm_smmu_invs_purge() - Purge all the trash entries in the @invs
1214 * @invs: the base invalidation array
1215 * @num_trashes: expected number of trash entries, typically returned by a prior
1216 * arm_smmu_invs_unref() call
1217 *
1218 * Return: a newly allocated array on success removing all the trash entries, or
1219 * NULL on failure
1220 *
1221 * This function must be locked and serialized with arm_smmu_invs_merge() and
1222 * arm_smmu_invs_unref(), but do not lockdep on any lock for KUNIT test.
1223 *
1224 * Caller is resposible for freeing the @invs and the returned new one.
1225 */
1226 VISIBLE_IF_KUNIT
> 1227 struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs,
1228 size_t num_trashes)
1229 {
1230 struct arm_smmu_invs *new_invs;
1231 size_t i, j;
1232
1233 if (WARN_ON(invs->num_invs < num_trashes))
1234 return NULL;
1235
1236 new_invs = arm_smmu_invs_alloc(invs->num_invs - num_trashes);
1237 if (IS_ERR(new_invs))
1238 return NULL;
1239
1240 for (i = j = 0; i != invs->num_invs; i++) {
1241 if (!refcount_read(&invs->inv[i].users))
1242 continue;
1243 new_invs->inv[j] = invs->inv[i];
1244 j++;
1245 }
1246
1247 WARN_ON(j != new_invs->num_invs);
1248 return new_invs;
1249 }
1250 EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_purge);
1251
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
On Mon, Oct 27, 2025 at 08:06:03PM +0800, kernel test robot wrote: > >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1062:23: error: static declaration of 'arm_smmu_invs_merge' follows non-static declaration > 1062 | struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs, > | ^~~~~~~~~~~~~~~~~~~ > In file included from drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:34: > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:731:23: note: previous declaration of 'arm_smmu_invs_merge' with type 'struct arm_smmu_invs *(struct arm_smmu_invs *, struct arm_smmu_invs *)' > 731 | struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs, > | ^~~~~~~~~~~~~~~~~~~ These should be added under "#if IS_ENABLED(CONFIG_KUNIT)" in the header. I will fix this and send a v4 today. Nicolin
Hi Nicolin,
kernel test robot noticed the following build warnings:
[auto build test WARNING on soc/for-next]
[also build test WARNING on linus/master v6.18-rc1 next-20251016]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Nicolin-Chen/iommu-arm-smmu-v3-Explicitly-set-smmu_domain-stage-for-SVA/20251016-034754
base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
patch link: https://lore.kernel.org/r/345bb7703ebd19992694758b47e371900267fa0e.1760555863.git.nicolinc%40nvidia.com
patch subject: [PATCH v3 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
config: arm64-randconfig-r123-20251017 (https://download.01.org/0day-ci/archive/20251017/202510172156.WHU485ad-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 754ebc6ebb9fb9fbee7aef33478c74ea74949853)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251017/202510172156.WHU485ad-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202510172156.WHU485ad-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c: note: in included file:
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected struct callback_head *head @@ got struct callback_head [noderef] __rcu * @@
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: expected struct callback_head *head
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: got struct callback_head [noderef] __rcu *
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: cast removes address space '__rcu' of expression
--
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c: note: in included file (through arch/arm64/include/asm/atomic.h, include/linux/atomic.h, include/asm-generic/bitops/atomic.h, ...):
arch/arm64/include/asm/cmpxchg.h:168:1: sparse: sparse: cast truncates bits from constant value (ffffffff80000000 becomes 0)
arch/arm64/include/asm/cmpxchg.h:168:1: sparse: sparse: cast truncates bits from constant value (ffffffff80000000 becomes 0)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c: note: in included file:
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected struct callback_head *head @@ got struct callback_head [noderef] __rcu * @@
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: expected struct callback_head *head
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: got struct callback_head [noderef] __rcu *
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: cast removes address space '__rcu' of expression
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected struct callback_head *head @@ got struct callback_head [noderef] __rcu * @@
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: expected struct callback_head *head
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: got struct callback_head [noderef] __rcu *
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: cast removes address space '__rcu' of expression
vim +1048 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
1045
1046 static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
1047 {
> 1048 kfree_rcu(smmu_domain->invs, rcu);
1049 kfree(smmu_domain);
1050 }
1051
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
On Fri, Oct 17, 2025 at 09:47:07PM +0800, kernel test robot wrote:
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c: note: in included file:
> >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected struct callback_head *head @@ got struct callback_head [noderef] __rcu * @@
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: expected struct callback_head *head
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: got struct callback_head [noderef] __rcu *
> >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: cast removes address space '__rcu' of expression
...
> 1045
> 1046 static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
> 1047 {
> > 1048 kfree_rcu(smmu_domain->invs, rcu);
Looks like it should be:
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
{
- kfree_rcu(smmu_domain->invs, rcu);
+ struct arm_smmu_invs *invs = rcu_dereference(smmu_domain->invs);
+
+ kfree_rcu(invs, rcu);
kfree(smmu_domain);
}
Will fix.
Thanks
Nicolin
On Fri, Oct 17, 2025 at 01:12:02PM -0700, Nicolin Chen wrote:
> On Fri, Oct 17, 2025 at 09:47:07PM +0800, kernel test robot wrote:
> > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c: note: in included file:
> > >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected struct callback_head *head @@ got struct callback_head [noderef] __rcu * @@
> > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: expected struct callback_head *head
> > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: got struct callback_head [noderef] __rcu *
> > >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: cast removes address space '__rcu' of expression
> ...
> > 1045
> > 1046 static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
> > 1047 {
> > > 1048 kfree_rcu(smmu_domain->invs, rcu);
>
> Looks like it should be:
> static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
> {
> - kfree_rcu(smmu_domain->invs, rcu);
> + struct arm_smmu_invs *invs = rcu_dereference(smmu_domain->invs);
rcu_derference_protected(,true) since we know there is no concurrency
here..
Jason
On Mon, Oct 20, 2025 at 09:10:56AM -0300, Jason Gunthorpe wrote:
> On Fri, Oct 17, 2025 at 01:12:02PM -0700, Nicolin Chen wrote:
> > On Fri, Oct 17, 2025 at 09:47:07PM +0800, kernel test robot wrote:
> > > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c: note: in included file:
> > > >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: incorrect type in argument 1 (different address spaces) @@ expected struct callback_head *head @@ got struct callback_head [noderef] __rcu * @@
> > > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: expected struct callback_head *head
> > > drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: got struct callback_head [noderef] __rcu *
> > > >> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h:1048:9: sparse: sparse: cast removes address space '__rcu' of expression
> > ...
> > > 1045
> > > 1046 static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
> > > 1047 {
> > > > 1048 kfree_rcu(smmu_domain->invs, rcu);
> >
> > Looks like it should be:
> > static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
> > {
> > - kfree_rcu(smmu_domain->invs, rcu);
> > + struct arm_smmu_invs *invs = rcu_dereference(smmu_domain->invs);
>
> rcu_derference_protected(,true) since we know there is no concurrency
> here..
Oh right, it's outside rcu_read_lock().
Thanks
Nicolin
On Wed, Oct 15, 2025 at 12:42:48PM -0700, Nicolin Chen wrote:
> +size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
> + struct arm_smmu_invs *to_unref,
> + void (*flush_fn)(struct arm_smmu_inv *inv))
> +{
> + unsigned long flags;
> + size_t num_trashes = 0;
> + size_t num_invs = 0;
> + size_t i, j;
> +
> + for (i = j = 0; i != invs->num_invs || j != to_unref->num_invs;) {
> + int cmp;
> +
> + /* Skip any existing trash entry */
> + if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
> + num_trashes++;
> + i++;
> + continue;
> + }
> +
> + cmp = arm_smmu_invs_cmp(invs, i, to_unref, j);
This should be moved upwards to "int cmp" before if.
Will fix in v4.
Nicolin
Hi Nicolin,
kernel test robot noticed the following build warnings:
[auto build test WARNING on soc/for-next]
[also build test WARNING on linus/master v6.18-rc1 next-20251016]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Nicolin-Chen/iommu-arm-smmu-v3-Explicitly-set-smmu_domain-stage-for-SVA/20251016-034754
base: https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
patch link: https://lore.kernel.org/r/345bb7703ebd19992694758b47e371900267fa0e.1760555863.git.nicolinc%40nvidia.com
patch subject: [PATCH v3 3/7] iommu/arm-smmu-v3: Introduce a per-domain arm_smmu_invs array
config: arm64-randconfig-001-20251017 (https://download.01.org/0day-ci/archive/20251017/202510170345.lO7fR7ao-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251017/202510170345.lO7fR7ao-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202510170345.lO7fR7ao-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1170:7: warning: variable 'cmp' is uninitialized when used here [-Wuninitialized]
1170 | if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
| ^~~
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c:1167:10: note: initialize the variable 'cmp' to silence this warning
1167 | int cmp;
| ^
| = 0
1 warning generated.
vim +/cmp +1170 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
1132
1133 /**
1134 * arm_smmu_invs_unref() - Find in @invs for all entries in @to_unref, decrease
1135 * the user counts without deletions
1136 * @invs: the base invalidation array
1137 * @to_unref: an array of invlidations to decrease their user counts
1138 * @flush_fn: A callback function to invoke, when an entry's user count reduces
1139 * to 0
1140 *
1141 * Return: the number of trash entries in the array, for arm_smmu_invs_purge()
1142 *
1143 * This function will not fail. Any entry with users=0 will be marked as trash.
1144 * All tailing trash entries in the array will be dropped. And the size of the
1145 * array will be trimmed properly. All trash entries in-between will remain in
1146 * the @invs until being completely deleted by the next arm_smmu_invs_merge()
1147 * or an arm_smmu_invs_purge() function call.
1148 *
1149 * This function must be locked and serialized with arm_smmu_invs_merge() and
1150 * arm_smmu_invs_purge(), but do not lockdep on any mutex for KUNIT test.
1151 *
1152 * Note that the final @invs->num_invs might not reflect the actual number of
1153 * invalidations due to trash entries. Any reader should take the read lock to
1154 * iterate each entry and check its users counter till the last entry.
1155 */
1156 VISIBLE_IF_KUNIT
1157 size_t arm_smmu_invs_unref(struct arm_smmu_invs *invs,
1158 struct arm_smmu_invs *to_unref,
1159 void (*flush_fn)(struct arm_smmu_inv *inv))
1160 {
1161 unsigned long flags;
1162 size_t num_trashes = 0;
1163 size_t num_invs = 0;
1164 size_t i, j;
1165
1166 for (i = j = 0; i != invs->num_invs || j != to_unref->num_invs;) {
1167 int cmp;
1168
1169 /* Skip any existing trash entry */
> 1170 if (cmp <= 0 && !refcount_read(&invs->inv[i].users)) {
1171 num_trashes++;
1172 i++;
1173 continue;
1174 }
1175
1176 cmp = arm_smmu_invs_cmp(invs, i, to_unref, j);
1177 if (cmp < 0) {
1178 /* not found in to_unref, leave alone */
1179 i++;
1180 num_invs = i;
1181 } else if (cmp == 0) {
1182 /* same item */
1183 if (refcount_dec_and_test(&invs->inv[i].users)) {
1184 /* KUNIT test doesn't pass in a flush_fn */
1185 if (flush_fn)
1186 flush_fn(&invs->inv[i]);
1187 num_trashes++;
1188 } else {
1189 num_invs = i + 1;
1190 }
1191 i++;
1192 j++;
1193 } else {
1194 /* item in to_unref is not in invs or already a trash */
1195 WARN_ON(true);
1196 j++;
1197 }
1198 }
1199
1200 /* Exclude any tailing trash */
1201 num_trashes -= invs->num_invs - num_invs;
1202
1203 /* The lock is required to fence concurrent ATS operations. */
1204 write_lock_irqsave(&invs->rwlock, flags);
1205 WRITE_ONCE(invs->num_invs, num_invs); /* Remove tailing trash entries */
1206 write_unlock_irqrestore(&invs->rwlock, flags);
1207
1208 return num_trashes;
1209 }
1210 EXPORT_SYMBOL_IF_KUNIT(arm_smmu_invs_unref);
1211
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.