Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH v8 06/17] iio: core: create local __iio_chan_prefix_emit() for reuse
From: Andy Shevchenko @ 2026-07-14 11:46 UTC (permalink / raw)
  To: rodrigo.alencar
  Cc: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening,
	Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva
In-Reply-To: <20260714-ad9910-iio-driver-v8-6-36939c3c07d2@analog.com>

On Tue, Jul 14, 2026 at 12:02:46PM +0100, Rodrigo Alencar via B4 Relay wrote:

> Move logic to create a channel prefix for naming attribute files into a
> separate __iio_chan_prefix_emit() function for reuse. The dev pointer is
> passed to __iio_device_attr_init() so that dev_err() replaces WARN().
> The new helper is going to be used to expose channel relationships as
> sysfs attributes.

...

> +static ssize_t __iio_chan_prefix_emit(struct device *dev,
> +				      const struct iio_chan_spec *chan,
> +				      enum iio_shared_by shared_by,
> +				      char *buf, size_t len)
> +{
> +	const char *type = iio_chan_type_name_spec[chan->type];
> +	const char *dir = iio_direction[chan->output];
> +	struct seq_buf s;
> +
> +	seq_buf_init(&s, buf, len);
> +
> +	switch (shared_by) {
> +	case IIO_SHARED_BY_ALL:
> +		break;
> +	case IIO_SHARED_BY_DIR:
> +		seq_buf_printf(&s, "%s", dir);
> +		break;
> +	case IIO_SHARED_BY_TYPE:
> +		seq_buf_printf(&s, "%s_%s", dir, type);
> +		if (chan->differential)
> +			seq_buf_printf(&s, "-%s", type);
> +		break;
> +	case IIO_SEPARATE:

> +		seq_buf_printf(&s, "%s_%s", dir, type);
> +		if (chan->indexed) {
> +			seq_buf_printf(&s, "%d", chan->channel);
> +			if (chan->differential)
> +				seq_buf_printf(&s, "-%s%d", type, chan->channel2);
> +		} else if (chan->differential) {
> +			dev_err(dev, "Differential channels must be indexed\n");
> +			return -EINVAL;
> +		}
> +
> +		if (chan->modified) {
> +			if (chan->differential) {
> +				dev_err(dev, "Differential channels can not have modifier\n");
> +				return -EINVAL;
> +			}
> +			seq_buf_printf(&s, "_%s", iio_modifier_names[chan->channel2]);
> +		}
> +
> +		if (chan->extend_name)
> +			seq_buf_printf(&s, "_%s", chan->extend_name);
> +		break;

Can we actually do the validation first without dirtying the output?

		/* Validate differential channel settings */
		if (chan->differential) {
			if (!chan->indexed) {
				dev_err(dev, "Differential channels must be indexed\n");
				return -EINVAL;
			}
			if (chan->modified) {
				dev_err(dev, "Differential channels can not have modifier\n");
				return -EINVAL;
			}
		}

		seq_buf_printf(&s, "%s_%s", dir, type);

		if (chan->indexed) {
			seq_buf_printf(&s, "%d", chan->channel);

		if (chan->differential)
			seq_buf_printf(&s, "-%s%d", type, chan->channel2);

		if (chan->modified)
			seq_buf_printf(&s, "_%s", iio_modifier_names[chan->channel2]);

		if (chan->extend_name)
			seq_buf_printf(&s, "_%s", chan->extend_name);
		break;


> +	}
> +
> +	return seq_buf_has_overflowed(&s) ? -EOVERFLOW : s.len;
> +}

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH net-next V6 4/4] devlink: Apply eswitch mode boot defaults
From: Jiri Pirko @ 2026-07-14 12:02 UTC (permalink / raw)
  To: Mark Bloch
  Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc
In-Reply-To: <20260714061731.531849-5-mbloch@nvidia.com>

Tue, Jul 14, 2026 at 08:17:30AM +0200, mbloch@nvidia.com wrote:
>Apply parsed devlink_eswitch_mode= defaults after devlink registration
>and after successful reload.
>
>Mark the default mode as pending when a devlink instance is allocated.
>Before devl_unlock() releases the instance lock, apply a pending default
>when the instance is registered.
>
>Clear the pending state before calling into the driver so the boot
>default remains a one-shot operation even if the mode change fails.
>
>For successful reloads that performed DRIVER_REINIT, devlink_reload()
>already holds the devlink instance lock and the driver has completed
>reload_up(). Clear the pending state and apply the default directly from
>the reload path.
>
>Treat an explicit user eswitch mode request as consuming the pending
>default mode.
>

Looks fine to me now. One nit below:


[..]

>+void devlink_default_esw_mode_instance_init(struct devlink *devlink)


Forgotten "instance" here. With this fixed, feel free to add my
Reviewed-by: Jiri Pirko <jiri@nvidia.com>

[..]

^ permalink raw reply

* [RFC v2 1/3] mm/lru_gen: add AGING counter and proactive aging helper
From: Zicheng Wang @ 2026-07-14 12:15 UTC (permalink / raw)
  To: akpm, yuanchu
  Cc: tj, hannes, mkoutny, corbet, kasong, qi.zheng, shakeel.butt,
	baohua, axelrasmussen, weixugc, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, roman.gushchin, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, willy, denghaojie, baoquan.he,
	kaleshsingh, tjmercier, tao.wangtao, zhangji1, wangzhen5,
	Zicheng Wang
In-Reply-To: <20260714121529.2237261-1-wangzicheng@honor.com>

Add the two pieces the rest of the series builds on: an AGING node
stat counter (the observation an aging policy pairs with
workingset_refault to spot over-aging) and a reusable helper,
lru_gen_age_memcg(), that ages a memcg by advancing max_seq without
reclaim (the control primitive memory.aging wraps).

The counter counts every pass in inc_max_seq(), regardless of caller.
The helper mirrors lru_gen_seq_write()'s reclaim_state /
memalloc_noreclaim / mm_walk setup; swappiness comes from
get_swappiness() so anon is not rotated without swap, and a racing
lruvec is skipped (-EAGAIN) without blocking other nodes.

Gated by CONFIG_LRU_GEN.  No user-visible change yet.

Signed-off-by: Zicheng Wang <wangzicheng@honor.com>
---
 include/linux/mmzone.h |  3 ++
 mm/internal.h          |  4 +++
 mm/memcontrol.c        |  9 +++++
 mm/vmscan.c            | 80 ++++++++++++++++++++++++++++++++++++++++++
 mm/vmstat.c            |  3 ++
 5 files changed, 99 insertions(+)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 9adb2ad21da5..ab5137c272ca 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -323,6 +323,9 @@ enum node_stat_item {
 	PGSCAN_ANON,
 	PGSCAN_FILE,
 	PGREFILL,
+#ifdef CONFIG_LRU_GEN
+	AGING,
+#endif
 #ifdef CONFIG_HUGETLB_PAGE
 	NR_HUGETLB,
 #endif
diff --git a/mm/internal.h b/mm/internal.h
index 5a2ddcf68e0b..96add6cd4627 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -634,6 +634,10 @@ extern void reclaim_throttle(pg_data_t *pgdat, enum vmscan_throttle_state reason
 int user_proactive_reclaim(char *buf,
 			   struct mem_cgroup *memcg, pg_data_t *pgdat);
 
+#ifdef CONFIG_LRU_GEN
+int lru_gen_age_memcg(struct mem_cgroup *memcg, unsigned long nr_gens);
+#endif
+
 /*
  * in mm/rmap.c:
  */
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index d978e18b9b2d..e42f97e004b6 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -420,6 +420,9 @@ static const unsigned int memcg_node_stat_items[] = {
 	PGSCAN_ANON,
 	PGSCAN_FILE,
 	PGREFILL,
+#ifdef CONFIG_LRU_GEN
+	AGING,
+#endif
 #ifdef CONFIG_HUGETLB_PAGE
 	NR_HUGETLB,
 #endif
@@ -1606,6 +1609,9 @@ static const struct memory_stat memory_stats[] = {
 #ifdef CONFIG_NUMA_BALANCING
 	{ "pgpromote_success",		PGPROMOTE_SUCCESS	},
 #endif
+#ifdef CONFIG_LRU_GEN
+	{ "aging",		AGING	},
+#endif
 };
 
 /* The actual unit of the state item, not the same as the output unit */
@@ -1655,6 +1661,9 @@ static int memcg_page_state_output_unit(int item)
 	case PGSCAN_KHUGEPAGED:
 	case PGSCAN_PROACTIVE:
 	case PGREFILL:
+#ifdef CONFIG_LRU_GEN
+	case AGING:
+#endif
 #ifdef CONFIG_NUMA_BALANCING
 	case PGPROMOTE_SUCCESS:
 #endif
diff --git a/mm/vmscan.c b/mm/vmscan.c
index b3e555561417..805e29c499c8 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -3987,6 +3987,9 @@ static bool inc_max_seq(struct lruvec *lruvec, unsigned long seq, int swappiness
 	WRITE_ONCE(lrugen->timestamps[next], jiffies);
 	/* make sure preceding modifications appear */
 	smp_store_release(&lrugen->max_seq, lrugen->max_seq + 1);
+
+	/* Count every aging pass for over-aging diagnosis. */
+	mod_lruvec_state(lruvec, AGING, 1);
 unlock:
 	lruvec_unlock_irq(lruvec);
 
@@ -5855,6 +5858,83 @@ static int __init init_lru_gen(void)
 };
 late_initcall(init_lru_gen);
 
+/**
+ * lru_gen_age_memcg - Proactively age a memcg by N generations
+ * @memcg: target memcg
+ * @nr_gens: number of generations to age (1..MAX_NR_GENS)
+ *
+ * Advances max_seq without page reclaim on all nodes of @memcg, using
+ * the same lock/walk protocol as lru_gen_seq_write().  Swappiness comes
+ * from get_swappiness() so anon rotation is skipped without swap.
+ * to the next node.
+ *
+ * Return: 0 on success, -EINVAL on bad input, -EAGAIN if a lruvec did
+ * not advance, -ENOMEM on mm_walk failure, -EINTR on signal.
+ */
+int lru_gen_age_memcg(struct mem_cgroup *memcg, unsigned long nr_gens)
+{
+	struct scan_control sc = {
+		.may_writepage = true,
+		.may_unmap = true,
+		.may_swap = true,
+		.reclaim_idx = MAX_NR_ZONES - 1,
+		.gfp_mask = GFP_KERNEL,
+		.proactive = true,
+	};
+	int nid, swappiness;
+	unsigned long i;
+	int ret = 0;
+	unsigned int flags;
+	struct blk_plug plug;
+	struct lruvec *lruvec;
+
+	if (!memcg)
+		return -EINVAL;
+
+	if (nr_gens == 0 || nr_gens > MAX_NR_GENS)
+		return -EINVAL;
+
+	set_task_reclaim_state(current, &sc.reclaim_state);
+	flags = memalloc_noreclaim_save();
+	blk_start_plug(&plug);
+	if (!set_mm_walk(NULL, true)) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	for_each_node_state(nid, N_MEMORY) {
+		if (signal_pending(current)) {
+			ret = -EINTR;
+			goto done;
+		}
+
+		lruvec = get_lruvec(memcg, nid);
+		if (!lruvec)
+			continue;
+
+		swappiness = get_swappiness(lruvec, &sc);
+
+		for (i = 0; i < nr_gens; i++) {
+			DEFINE_MAX_SEQ(lruvec);
+
+			if (!try_to_inc_max_seq(lruvec, max_seq,
+						swappiness, false)) {
+				ret = -EAGAIN;
+				break;
+			}
+		}
+
+		cond_resched();
+	}
+
+done:
+	clear_mm_walk();
+	blk_finish_plug(&plug);
+	memalloc_noreclaim_restore(flags);
+	set_task_reclaim_state(current, NULL);
+	return ret;
+}
+
 #else /* !CONFIG_LRU_GEN */
 
 static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
diff --git a/mm/vmstat.c b/mm/vmstat.c
index f534972f517d..78ea093428c7 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1290,6 +1290,9 @@ const char * const vmstat_text[] = {
 	[I(PGSCAN_ANON)]			= "pgscan_anon",
 	[I(PGSCAN_FILE)]			= "pgscan_file",
 	[I(PGREFILL)]				= "pgrefill",
+#ifdef CONFIG_LRU_GEN
+	[I(AGING)]			= "aging",
+#endif
 #ifdef CONFIG_HUGETLB_PAGE
 	[I(NR_HUGETLB)]				= "nr_hugetlb",
 #endif
-- 
2.25.1


^ permalink raw reply related

* [RFC v2 0/3] mm/mglru: proactive aging via memory.aging
From: Zicheng Wang @ 2026-07-14 12:15 UTC (permalink / raw)
  To: akpm, yuanchu
  Cc: tj, hannes, mkoutny, corbet, kasong, qi.zheng, shakeel.butt,
	baohua, axelrasmussen, weixugc, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, roman.gushchin, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, willy, denghaojie, baoquan.he,
	kaleshsingh, tjmercier, tao.wangtao, zhangji1, wangzhen5,
	Zicheng Wang

MGLRU inverts the reclaim order when anonymous memory is faulted in
bulk: anonymous pages sit in the young generations while file pages
sit in the oldest two, so reclaim evicts hot file pages before cold
anonymous pages.  swappiness cannot correct it.  This series adds a
per-cgroup proactive-aging operation, memory.aging, with the
observability to drive it as a closed loop, so userspace can rebalance
a cgroup's generations before reclaim runs.

The problem
-----------

MGLRU places pages by access path, not hotness.  On the current tree
the entry rules in lru_gen_folio_seq() give:

  - faulted-in pages (anonymous pages; the fault path sets the active
    mark) land at the 2nd youngest generation (max_seq - 1);
  - file pages from read()/fadvise() land in the oldest two
    generations: non-workingset at min_seq, workingset at min_seq + 1.

Pictorially, for a full four-generation window::

       youngest                          oldest
       max_seq   max_seq-1   min_seq+1   min_seq
       -------------------------------------------
 anon    .          X           .           .     (faulted in)
 file    .          .           X           X     (read/fadvise)
                                            ^
                              reclaim scans from here -> evicts file first

Reclaim scans from min_seq, so file pages are evicted before the
anonymous pages two generations younger, a hot/cold inversion.

swappiness does not fix it: it only selects which type to scan, not
the ordering within a type, so reclaim can still evict hot pages.
swappiness=201 (SWAPPINESS_ANON_ONLY) just flips the inversion to the
other side: no value recovers a correct hot/cold order, because the
order comes from the access path, not access recency [1].  This is not
Android-specific; the same file over-reclaim is shown on servers and
worked around there by forcing an age iteration before reclaim.

Why userspace-driven
--------------------

The benefit is workload-dependent: file-cache-bound servers gain from
aging, anon-bound servers do not, so no kernel default is correct for
all.  The kernel also cannot know when to age: on Android the right
moment is the foreground-to-background transition, when the app's pages
are cold but their PTE accessed bits are still accurate from foreground
execution, a framework concept.  The kernel therefore provides the
mechanism; userspace runs the loop.

Design: observation and control
-------------------------------

The series gives userspace one control primitive and the observation to
use it.  All three pieces are per-cgroup and gated by CONFIG_LRU_GEN,
and together they form a closed loop:

  control   memory.aging          write nr_gens to advance max_seq without
                                  reclaim; the aging counterpart of
                                  memory.reclaim
  observe   nr_oldest_anon/file   pages in the oldest generation of each
                                  type: what the next reclaim can evict
            aging                 number of aging passes, paired with
                                  workingset_refault to spot over-aging

A policy reads nr_oldest_* to see whether anon is sheltered, ages with
memory.aging, then checks the aging counter and workingset_refault.
A typical flow ages to rebalance, then reclaims::

    echo 2 > /sys/fs/cgroup/foo/memory.aging
    echo "100M" > /sys/fs/cgroup/foo/memory.reclaim

Why cgroup v2
-------------

memory.reclaim already lives in cgroup v2, but it is broken when
anonymous pages are sheltered in the young generations: reclaim cannot
reach anon until aging advances it.  A useful memory.reclaim therefore
requires aging first.  Aging is not a new MGLRU detail leaked into
cgroup v2; it is the other half of the proactive-reclaim operation
cgroup v2 already exposes.  Putting it anywhere else (sysfs) cuts one
operation across two ABIs and two addressing models.

memory.aging is gated by CONFIG_LRU_GEN and exposes one primitive
(advancing the generation), not internal data structures; if MGLRU is
removed, the file goes with it.

Alternatives considered
-----------------------

1) sysfs (/sys/kernel/mm/lru_gen/) fights sysfs's one-value-per-file,
stateless model: the command is multi-argument and per-(memcg,node),
the read is parameterized per memcg, and css-id addressing is hostile
to cgroup paths.  It also splits aging from memory.reclaim.

2) in-kernel automatic aging is workload-dependent (memcached regresses)
and needs a trigger the kernel cannot know: the Android
foreground-to-background moment is a framework concept.  MGLRU had this
once: `should_run_aging' fired on a generation-balance heuristic::

    if (young * MIN_NR_GENS > total)
        return true;
    if (old * (MIN_NR_GENS + 2) < total)
        return true;


3) extending memory.reclaim to run the debugfs aging command
(`+ memcg_id node_id seq`) overloads a reclaim file with a non-reclaim
operation and pulls MGLRU's debugfs command syntax into a generic
cgroup v2 ABI. But Aging is reclaim's counterpart, not one of its modes.

Results
-------
N39 (Device-X1 8 GB/SM7750, Device-X2 12 GB/SM8750).  Production telemetry,
~300 users, 30K+ device-hours:
    keep-alive          +15.4% (8 GB) / +10.2% (12 GB)
    IME dismiss latency -47.2%
    launch/exit jank >50ms   eliminated
    one aging pass      342 us median, never on a UX thread

AN90 (a new shipping product, separate from N39):
    scroll jank >50 ms  -22%, severe jank -26%
    cold-launch stalls >= 3 s  eliminated
    workingset_refault (anon + file) and direct reclaim both down

Server (Intel i7-14700F, 20C/28T, 32 GB DDR5-5600, 1 TB NVMe,
Ubuntu 24.04; same root cause, aging triggered on the
reclaim path when anon in the oldest gen ratio < 10%):
    fio random-read     +31.8%
    ripgrep             +15.8%
    memcached           -1.2%   (anon-bound; expected no gain)

N39 and AN90 are different products.

Why RFC
-------

v1 [2] put the control in procfs/debugfs and was NAKed for the
cgroup-v2 venue.  This version keeps aging in cgroup v2, next to
memory.reclaim, and argues why above.  Discussed at LSF/MM 2026 [3].

[1] swappiness=0/201 hot/cold inversion, and why the straightforward
    fix is deferred:
    https://lore.kernel.org/linux-mm/7829b070df1b405dbc97dd6a028d8c8a@honor.com/
[2] v1: https://lore.kernel.org/linux-mm/20251128025315.3520689-1-wangzicheng@honor.com/
[3] LSF/MM 2026 slides:
    https://docs.google.com/presentation/d/1hUogz6InyLn13c8CjHuvEIzE4rT7saVRUV6xpWZoNfQ/

Changes since v1
----------------

- Dropped the debugfs -> procfs move; aging is now a cgroup v2 file.
- Added the AGING counter and nr_oldest_anon/file observability.
- Corrected the generation-placement description to the current tree
  (faults at the 2nd youngest, file in the oldest two).

Zicheng Wang (3):
  mm/lru_gen: add AGING counter and proactive aging helper
  mm: memcontrol: add memory.aging cgroup v2 file
  mm/lru_gen: expose oldest-generation page counts in memory.stat

 Documentation/admin-guide/cgroup-v2.rst       |  53 +++++++++++
 Documentation/admin-guide/mm/multigen_lru.rst |  13 +++
 include/linux/mmzone.h                        |   3 +
 mm/internal.h                                 |  19 +++++
 mm/memcontrol.c                               |  63 +++++++++++
 mm/vmscan.c                                   | 109 +++++++++++++++++++++
 mm/vmstat.c                                   |   3 +
 7 files changed, 263 insertions(+)

--
2.25.1


^ permalink raw reply

* [RFC v2 3/3] mm/lru_gen: expose oldest-generation page counts in memory.stat
From: Zicheng Wang @ 2026-07-14 12:15 UTC (permalink / raw)
  To: akpm, yuanchu
  Cc: tj, hannes, mkoutny, corbet, kasong, qi.zheng, shakeel.butt,
	baohua, axelrasmussen, weixugc, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, roman.gushchin, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, willy, denghaojie, baoquan.he,
	kaleshsingh, tjmercier, tao.wangtao, zhangji1, wangzhen5,
	Zicheng Wang
In-Reply-To: <20260714121529.2237261-1-wangzicheng@honor.com>

Add nr_oldest_anon and nr_oldest_file to memory.stat: pages in the
oldest generation (min_seq) of each type, aggregated over the cgroup
subtree and all N_MEMORY nodes.  This is the observation half of the
proactive-aging loop - it shows a policy how much anon/file memory the
next reclaim can evict directly, so it can decide whether aging is
needed.

Computed on demand from lrugen->nr_pages; no per-page maintenance, no
hot-path cost.  Paired with the AGING counter and workingset_refault_*
to confirm an aging pass took effect and was not too aggressive.

Gated by CONFIG_LRU_GEN; reported in pages, matching the lru_gen dump.

Signed-off-by: Zicheng Wang <wangzicheng@honor.com>
---
 Documentation/admin-guide/cgroup-v2.rst | 18 ++++++++++++++++
 mm/internal.h                           |  2 ++
 mm/memcontrol.c                         | 24 +++++++++++++++++++++
 mm/vmscan.c                             | 28 +++++++++++++++++++++++++
 4 files changed, 72 insertions(+)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index f42ac6dddbf6..a071175d11be 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1658,6 +1658,24 @@ The following nested keys are defined.
 		the value for the foo counter, since the foo counter is type-based, not
 		list-based.
 
+	  nr_oldest_anon, nr_oldest_file
+		Number of pages in the oldest generation of the anonymous and
+		file types. Only present when CONFIG_LRU_GEN is enabled.
+
+		With MGLRU, page reclaim starts from the oldest generation
+		(min_seq) of each type, so these counts show how much anonymous
+		and file memory the next reclaim pass (or a proactive
+		memory.reclaim) can directly evict. They are aggregated across
+		this cgroup's subtree and all NUMA nodes, and are reported in
+		pages, matching the per-generation breakdown in the ``lru_gen``
+		debugfs file.
+
+		Proactive-aging policy can use them together with the ``aging``
+		counter and ``workingset_refault_*``: a large ``nr_oldest_file``
+		with near-zero ``nr_oldest_anon`` indicates file cache piling up
+		in the oldest generation while anonymous pages stay young, which
+		is the condition ``memory.aging`` is meant to rebalance.
+
 	  slab_reclaimable
 		Part of "slab" that might be reclaimed, such as
 		dentries and inodes.
diff --git a/mm/internal.h b/mm/internal.h
index 96add6cd4627..959d376c02a6 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -636,6 +636,8 @@ int user_proactive_reclaim(char *buf,
 
 #ifdef CONFIG_LRU_GEN
 int lru_gen_age_memcg(struct mem_cgroup *memcg, unsigned long nr_gens);
+void lru_gen_nr_oldest_pages(struct lruvec *lruvec,
+			     unsigned long *nr_anon, unsigned long *nr_file);
 #endif
 
 /*
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index c6a969b56878..016194cabd60 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -1733,6 +1733,30 @@ static void memcg_stat_format(struct mem_cgroup *memcg, struct seq_buf *s)
 		}
 	}
 
+#ifdef CONFIG_LRU_GEN
+	/* Oldest-generation (min_seq) anon/file pages over this cgroup's
+	 * subtree and all N_MEMORY nodes; see lru_gen_nr_oldest_pages().
+	 */
+	{
+		struct mem_cgroup *mi;
+		int nid;
+		unsigned long oldest_anon = 0, oldest_file = 0;
+
+		for_each_mem_cgroup_tree(mi, memcg) {
+			for_each_node_state(nid, N_MEMORY) {
+				unsigned long nr_anon, nr_file;
+				struct lruvec *lruvec = mem_cgroup_lruvec(mi, NODE_DATA(nid));
+
+				lru_gen_nr_oldest_pages(lruvec, &nr_anon, &nr_file);
+				oldest_anon += nr_anon;
+				oldest_file += nr_file;
+			}
+		}
+		seq_buf_printf(s, "nr_oldest_anon %lu\n", oldest_anon);
+		seq_buf_printf(s, "nr_oldest_file %lu\n", oldest_file);
+	}
+#endif
+
 	/* Accumulated memory events */
 	seq_buf_printf(s, "pgscan %lu\n",
 		       memcg_page_state(memcg, PGSCAN_KSWAPD) +
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 805e29c499c8..3800f0c4f1f5 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5935,6 +5935,34 @@ int lru_gen_age_memcg(struct mem_cgroup *memcg, unsigned long nr_gens)
 	return ret;
 }
 
+/**
+ * lru_gen_nr_oldest_pages - pages in the oldest generation of a lruvec
+ * @lruvec: target lruvec
+ * @nr_anon: filled with oldest-generation anonymous page count
+ * @nr_file: filled with oldest-generation file page count
+ *
+ * Reclaim starts from the oldest generation (min_seq) of each type, so these
+ * count what the next reclaim can evict directly.  Computed from
+ * lrugen->nr_pages[min_seq type][zone]; eventually consistent, clamped to 0.
+ */
+void lru_gen_nr_oldest_pages(struct lruvec *lruvec,
+			     unsigned long *nr_anon, unsigned long *nr_file)
+{
+	int type, zone;
+	struct lru_gen_folio *lrugen = &lruvec->lrugen;
+	unsigned long size[ANON_AND_FILE] = {};
+
+	for (type = 0; type < ANON_AND_FILE; type++) {
+		int gen = lru_gen_from_seq(READ_ONCE(lrugen->min_seq[type]));
+
+		for (zone = 0; zone < MAX_NR_ZONES; zone++)
+			size[type] += max(READ_ONCE(lrugen->nr_pages[gen][type][zone]), 0L);
+	}
+
+	*nr_anon = size[LRU_GEN_ANON];
+	*nr_file = size[LRU_GEN_FILE];
+}
+
 #else /* !CONFIG_LRU_GEN */
 
 static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc)
-- 
2.25.1


^ permalink raw reply related

* [RFC v2 2/3] mm: memcontrol: add memory.aging cgroup v2 file
From: Zicheng Wang @ 2026-07-14 12:15 UTC (permalink / raw)
  To: akpm, yuanchu
  Cc: tj, hannes, mkoutny, corbet, kasong, qi.zheng, shakeel.butt,
	baohua, axelrasmussen, weixugc, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, roman.gushchin, muchun.song, cgroups, linux-mm,
	linux-kernel, linux-doc, willy, denghaojie, baoquan.he,
	kaleshsingh, tjmercier, tao.wangtao, zhangji1, wangzhen5,
	Zicheng Wang
In-Reply-To: <20260714121529.2237261-1-wangzicheng@honor.com>

Add a write-only memory.aging that triggers MGLRU aging (max_seq
advancement) without reclaim - the aging counterpart of memory.reclaim.
Userspace ages to rebalance the anon/file distribution across
generations, then reclaims.  This is the control half of the loop;
the observability it needs lands in the next patch.

Useful for workloads such as the Android app lifecycle, where anon
concentrates in the 2nd-youngest generation while file fills the
oldest two and gets over-reclaimed.

  echo 2 > /sys/fs/cgroup/foo/memory.aging
  echo "100M" > /sys/fs/cgroup/foo/memory.reclaim

Gated by CONFIG_LRU_GEN.  Concurrent writers race on the lruvec lock;
a caller that did not advance the window gets -EAGAIN.

Signed-off-by: Zicheng Wang <wangzicheng@honor.com>
---
 Documentation/admin-guide/cgroup-v2.rst       | 35 +++++++++++++++++++
 Documentation/admin-guide/mm/multigen_lru.rst | 13 +++++++
 mm/memcontrol.c                               | 30 ++++++++++++++++
 3 files changed, 78 insertions(+)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 6efd0095ed99..f42ac6dddbf6 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1437,6 +1437,41 @@ The following nested keys are defined.
 	The valid range for swappiness is [0-200, max], setting
 	swappiness=max exclusively reclaims anonymous memory.
 
+  memory.aging
+	A write-only file which exists for all cgroups when
+	CONFIG_LRU_GEN is enabled.
+
+	This interface triggers MGLRU aging (generation advancement)
+	without page reclaim. It is useful for proactively rebalancing
+	the distribution of pages across generations before invoking
+	memory.reclaim, particularly for workloads (such as the Android
+	application lifecycle) where anonymous pages concentrate in
+	the youngest generations while file pages fill up the oldest
+	generation and get over-reclaimed.
+
+	The single argument is the number of generations to age, in
+	the range [1, MAX_NR_GENS]. MAX_NR_GENS is currently 4.
+
+	Example::
+
+	  echo 2 > memory.aging
+
+	Each aging pass advances both anonymous and file generations
+	simultaneously, matching MGLRU's max_seq semantics. There is
+	no way to age only one type - this is a property of MGLRU,
+	not of this interface.
+
+	Proactive aging should be followed by memory.reclaim to
+	actually free pages::
+
+	  echo 2 > /sys/fs/cgroup/foo/memory.aging
+	  echo "100M" > /sys/fs/cgroup/foo/memory.reclaim
+
+	Returns -EAGAIN if this call did not advance the generation
+	window (typically because a concurrent caller on the same
+	cgroup got there first). Returns -EINVAL if the argument is
+	missing, zero, or out of range.
+
   memory.peak
 	A read-write single value file which exists on non-root cgroups.
 
diff --git a/Documentation/admin-guide/mm/multigen_lru.rst b/Documentation/admin-guide/mm/multigen_lru.rst
index 9cb54b4ff5d9..fd8b4286014e 100644
--- a/Documentation/admin-guide/mm/multigen_lru.rst
+++ b/Documentation/admin-guide/mm/multigen_lru.rst
@@ -161,3 +161,16 @@ cold pages because of the overestimation, it retries on the next
 server according to the ranking result obtained from the working set
 estimation step. This less forceful approach limits the impacts on the
 existing jobs.
+
+Proactive aging
+---------------
+For workloads where it is useful to rebalance the distribution of
+anonymous and file pages across generations before reclaiming (for
+example, the Android application lifecycle, where an application
+that has just been backgrounded has most of its anonymous pages
+clustered in the youngest generations while file pages dominate
+the oldest), the ``memory.aging`` cgroup v2 file can be used to
+advance the generation counter without eviction. The file returns
+-EAGAIN if no advance happened, so callers can correlate with the
+``aging`` counter to verify how many passes actually took
+effect. See Documentation/admin-guide/cgroup-v2.rst.
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index e42f97e004b6..c6a969b56878 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -4896,6 +4896,29 @@ static ssize_t memory_reclaim(struct kernfs_open_file *of, char *buf,
 	return nbytes;
 }
 
+#ifdef CONFIG_LRU_GEN
+static ssize_t memory_aging(struct kernfs_open_file *of, char *buf,
+			    size_t nbytes, loff_t off)
+{
+	struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
+	unsigned long nr_gens;
+	int ret;
+
+	ret = kstrtoul(buf, 0, &nr_gens);
+	if (ret)
+		return ret;
+
+	if (nr_gens == 0 || nr_gens > MAX_NR_GENS)
+		return -EINVAL;
+
+	ret = lru_gen_age_memcg(memcg, nr_gens);
+	if (ret)
+		return ret;
+
+	return nbytes;
+}
+#endif
+
 static struct cftype memory_files[] = {
 	{
 		.name = "current",
@@ -4967,6 +4990,13 @@ static struct cftype memory_files[] = {
 		.flags = CFTYPE_NS_DELEGATABLE,
 		.write = memory_reclaim,
 	},
+#ifdef CONFIG_LRU_GEN
+	{
+		.name = "aging",
+		.flags = CFTYPE_NS_DELEGATABLE,
+		.write = memory_aging,
+	},
+#endif
 	{ }	/* terminate */
 };
 
-- 
2.25.1


^ permalink raw reply related

* Re: What's cooking in zh_CN (Jul 2026)
From: Weijie Yuan @ 2026-07-14 12:16 UTC (permalink / raw)
  To: Alex Shi
  Cc: Dongliang Mu, Dongliang Mu, linux-doc, Alex Shi, Yanteng Si,
	Ben Guo, Gary Guo, Yan Zhu, Doehyun Baek, Jiandong Qiu
In-Reply-To: <b664685e-6785-44ae-8005-443032508096@gmail.com>

I took a look about patchwork, it is basically like a mailing-list
version issue tracker / pull-request dashboard.

It can manage patch backlog, assign patches, record CI results, and mark
patches as accepted, rejected, superseded, or under review. and record
trailers. Well, pretty much like a mini version of Gerrit or SourceHut.

But I think it's a little complicated for our needs, as it offers many
features that we might not actually need, which involves learning costs
and maintenance costs.

Just now, while going through patchwork of other lists a bit, I didn't
seem to find a way to filter out Chinese patches from the linux-doc
list, if we were to use it.

Dongliang, is the dashboard you want exactly like the one I sent in this
email, which is read-only? Or perhaps wish there are other functions as
well. For example, you can mark the patch series you have reviewed.

I can't find what IMA is btw.

[1] An exmaple of marking status of a patch in SourceHut
    https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/70509

^ permalink raw reply

* Re: [PATCH v7 11/12] virt/steal_monitor: Act on steal time periodically and decide on preferred CPUs
From: Yury Norov @ 2026-07-14 12:31 UTC (permalink / raw)
  To: Shrikanth Hegde
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <9da4fd45-0a0f-495d-8207-816ee88c5937@linux.ibm.com>

On Mon, Jul 13, 2026 at 11:18:59AM +0530, Shrikanth Hegde wrote:

... 

> > > +requeue_work:
> > > +	/* maintain design constructs always */
> > > +	WARN_ON_ONCE(cpumask_empty(cpu_preferred_mask));
> > > +	WARN_ON_ONCE(!cpumask_subset(cpu_preferred_mask, cpu_active_mask));
> > 
> > cpu_read_lock here? And again, you should do something to restore
> > integrity. WARN_ON is not enough. The simplest and safest thing you
> > can do is to unload the driver. You definitely shouldn't schedule a
> > new work against the broken cpu_preferred_mask.
> 
> How about not requeue the work if it broken. Add a pr_err and return.
> That makes driver pretty much nop until rmmod.
> 
>         /* maintain design constructs always */
>         if (cpumask_empty(cpu_preferred_mask)) {
>                 pr_err("empty cpu_preferred_mask, stop steal_monitor work");
>                 return;
>         }
> 
>         if (!cpumask_subset(cpu_preferred_mask, cpu_active_mask)) {
>                 pr_err("preferred: %*pbl is not a subset of active: %*pbl, stop steal_monitor work\n",
>                        pr_cpuamsk_args(cpu_preferred_mask), pr_cpuamsk_args(cpu_active_mask));
> 		return;
> 	}
 
That would keep broken preferred cpumask working in the kernel.

Imagine, it becomes empty on Friday night. That severely hurts 
the scheduling on the VMs. So, your approach requires the VM
admin to come and manually rmmod the driver. This is not how
he wanted to spend the evening, I guess. :)

So, ether unload the driver, or actively recover the invariant.

Thanks,
Yury

^ permalink raw reply

* Re: [PATCH v7 10/12] virt/steal_monitor: Provide functions for managing steal values
From: Yury Norov @ 2026-07-14 12:47 UTC (permalink / raw)
  To: Shrikanth Hegde
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <b4b7591c-8edd-4f45-9e03-37d7353ae5cf@linux.ibm.com>

On Mon, Jul 13, 2026 at 10:43:15AM +0530, Shrikanth Hegde wrote:
> Hi Yury,
> 
> On 7/11/26 1:30 AM, Yury Norov wrote:

...

> > I think it should return status: if the function can't disable CPUs
> > now, it would be a good hint for the caller that it would be useless
> > to call it again.
> > 
> > You may keep status in struct steal_monitor like:
> > 
> >          if (steal_ratio > sm_core_ctx.high_threshold)  {
> >                  if (sm_core_ctx->status | CANT_DECREASE) {
> >                          pr_something();
> >                  else
> >                          sm_core_ctx->status = decrease();
> > 
> > It would be a good hint to user that he has the driver misconfigured,
> > and save the driver extra work. Same for increase().
> > 
> 
> I thought about the extra work in function, but doesn't happen too often IMO.

10ms is the shortest possible interval, 100 times in a second.

> Also, it is specially not a misconfiguration for increase.
> So i have kept it stateless for the below reason.
> 
> - Under typical operation of this driver, user will enable it once.
> - Once enabled, user will use their VM as usual.
> - Majority of the time the steal time will be less.
> - workload are bursty in nature.

Not necessarily.

> - Occasionally many VM will have high utilization and there will be steal time.
>   This lasts for sometime.
> - After workload completes, steal time goes low again.
> - Cycle could repeat after extended low steal time duration.
> 
> So when the steal time is low, though driver is enabled, doesn't mean it
> is mis-configured. Just that there is contention and driver has nothing to
> do. So, adding print there could easily consume the console.
> 
> Similarly, there could situations, where decrease cannot happen though there is
> high steal time, Though they are corner cases.

OK, that makes sense. If one needs to fine-tune the driver's
thresholds, he'd monitor the preferred cpumasks statistics.

> For example,
> - one small/few VMs have not enabled the driver. steal time could be high, but this
>   VM has already down to one core. It can't decrease any further.
> - Though all VMs have enabled the feature, but task running is not FAIR class. Though
>   steal time shows high.
> 
> Hitting only one core or all cores isn't necessarily a misconfiguration.
> It is a possible behavior during severe contention or complete idle system.
> 
> we need to continuously monitor steal time so that it can expand/contract the
> based on current situation. If we stop calling the functions, natural expand/contract
> will not happen. There is no interrupt which arrives due to high/low steal time where
> we can kick start the driver again. Also it is a difficult ask for user to keep enabling
> or disabling the driver.
> 
> Since this can be called at minimal once in 10ms, I guess we can incur the additional
> overheads to keep the logic simple and stateless. What do you think?

OK, it's your design.

> PS: I will remove that additional SM_DIR as you suggested in other reply. That keeps
> it all stateless.

^ permalink raw reply

* Re: [PATCH net-next V6 3/4] devlink: Parse eswitch mode boot defaults
From: Jiri Pirko @ 2026-07-14 12:58 UTC (permalink / raw)
  To: Mark Bloch
  Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc
In-Reply-To: <20260714061731.531849-4-mbloch@nvidia.com>

Tue, Jul 14, 2026 at 08:17:29AM +0200, mbloch@nvidia.com wrote:
>Add devlink_eswitch_mode= kernel command line parsing for a default
>eswitch mode.
>
>The supported syntax selects either all devlink handles or one explicit
>comma-separated handle list:
>
>  devlink_eswitch_mode=*=<mode>
>
>  devlink_eswitch_mode=<handle>[,<handle>...]=<mode>
>
>where <mode> is one of legacy, switchdev or switchdev_inactive. All
>selected handles receive the same mode. Assigning different modes to
>different handle lists in the same parameter value is not supported.
>
>Store the parsed selector and mode in devlink core so the default can be
>applied by a downstream patch.
>
>Document the devlink_eswitch_mode= syntax and duplicate handle handling.
>
>Signed-off-by: Mark Bloch <mbloch@nvidia.com>

Reviewed-by: Jiri Pirko <jiri@nvidia.com>

^ permalink raw reply

* Re: [PATCH net-next V6 2/4] devlink: Factor out eswitch mode setting
From: Jiri Pirko @ 2026-07-14 12:58 UTC (permalink / raw)
  To: Mark Bloch
  Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc
In-Reply-To: <20260714061731.531849-3-mbloch@nvidia.com>

Tue, Jul 14, 2026 at 08:17:28AM +0200, mbloch@nvidia.com wrote:
>Move the common eswitch mode set checks into a small helper and use it
>from the netlink eswitch set command. This makes the same validation
>available to the devlink core path that applies eswitch mode defaults.
>
>Signed-off-by: Mark Bloch <mbloch@nvidia.com>

Reviewed-by: Jiri Pirko <jiri@nvidia.com>

^ permalink raw reply

* Re: [PATCH net-next 9/9] net: document RFC6724 rule 5.5 implementation
From: Jonathan Corbet @ 2026-07-14 13:01 UTC (permalink / raw)
  To: David 'equinox' Lamparter, Paolo Abeni, Jakub Kicinski,
	Ido Schimmel
  Cc: David Ahern, David S. Miller, Eric Dumazet, Simon Horman,
	Shuah Khan, Fernando Fernandez Mancera, Lorenzo Colitti,
	Maciej Żenczykowski, Patrick Rohr, netdev, linux-doc,
	linux-kselftest, David 'equinox' Lamparter
In-Reply-To: <20260714094030.136317-10-equinox@diac24.net>

David 'equinox' Lamparter <equinox@diac24.net> writes:

> RFC6724 rule 5.5 is anything but obvious, especially if trying to do it
> well.  (RFC8028 and its errata kinda proves the point.)
>
> This documents what exactly the Linux kernel does for RFC6724 rule 5.5,
> especially what the routing table needs to look like for it to work.
>
> Signed-off-by: David 'equinox' Lamparter <equinox@diac24.net>
> ---
>  Documentation/networking/ipv6-addrsel.rst | 75 +++++++++++++++++++++++
>  MAINTAINERS                               |  1 +
>  2 files changed, 76 insertions(+)
>  create mode 100644 Documentation/networking/ipv6-addrsel.rst

You need to add this new document to the index.rst file or it won't be
part of the docs build...you should have seen a warning when you built
the docs.

> diff --git a/Documentation/networking/ipv6-addrsel.rst b/Documentation/networking/ipv6-addrsel.rst
> new file mode 100644
> index 000000000000..bed032e69570
> --- /dev/null
> +++ b/Documentation/networking/ipv6-addrsel.rst
> @@ -0,0 +1,75 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +====================================
> +IPv6 source address selection trivia
> +====================================
> +
> +
> +RFC6724 rule 5.5 support
> +------------------------

Please stick with the section markup guidelines in
Documentation/doc-guide/sphinx.rst. 

> +RFC6724 rule 5.5 is a very short paragraph in a complex RFC that has turned
> +out quite tricky, but also immensely useful in multihoming scenarios.  For
> +reference, it says:
> +
> +::

You'll likely get more visually pleasing results if you just leave out
the "::" here; you don't need literal formatting.

> +   Rule 5.5: Prefer addresses in a prefix advertised by the next-hop.
> +   If SA or SA's prefix is assigned by the selected next-hop that will
> +   be used to send to D and SB or SB's prefix is assigned by a different
> +   next-hop, then prefer SA.  Similarly, if SB or SB's prefix is
> +   assigned by the next-hop that will be used to send to D and SA or
> +   SA's prefix is assigned by a different next-hop, then prefer SB.
> +
> +The way this works on Linux is as follows:
> +
> +- prior to any source address selection happening, when receiving a RA, more
> +  than the installation of a default route (or ::/128 route) needs to happen:
> +  for each PIO, a source-specific (subtree) route is *additionally* installed.
> +  The effect of this is that *after* a source address has been selected, one
> +  of the routers that advertised it will remain in use (this is *not* RFC 6724
> +  related, but rather RFC 8028.)  At the same time, these extra routes serve
> +  to remember which router advertised what.
> +
> +- per usual, a route lookup for the IPv6 destination address in consideration
> +  is done first.  This is passed around in kernel as a dst_entry.
> +
> +- the source address selection code iterates through the various rules in
> +  RFC 6724.
> +
> +- if/when rule 5.5 is reached, first of all, there is a check if *any* source
> +  specific routes exist in the routing table.  If there are none, the entire
> +  code for 5.5 is skipped because it cannot have any effect, but is not free
> +  to execute (can involve multiple routing lookups.)  **In applications that
> +  use a lot of unbound (e.g. UDP) sockets, installing subtree routes should
> +  therefore be avoided to not incur this cost on each source address selection
> +  pass.**  Alternatively, applications should bind their sockets to a specific
> +  source address such that the selection code is never hit.
> +
> +- if subtree routes do exist, the source address selection code now repeats
> +  the routing lookup done before source address selection is entered, except
> +  with the source address under consideration filled in.  This lookup will hit
> +  the subtree routes that were installed (see first item), giving a fresh
> +  dst_entry.  If the new dst_entry matches the original dst_entry, that means
> +  the original router has in fact sent RAs with PIOs for this source address,
> +  so it is preferred.  Otherwise it is not.
> +
> +
> +There are a few caveats to consider:
> +
> +- the kernel currently does not create the subtree routes mentioned in the
> +  first item.  This is a separate work item, partially done at the time of
> +  writing this.  But this can equally well be performed in userspace processing
> +  of RAs, e.g. NetworkManager or plain static configuration.
> +
> +- since addresses can also be acquired from DHCPv6, even RA/PIO combinations
> +  that didn't result in the creation of any addresses (e.g. A=0) should have
> +  subtree routes added.  Those routes *may* be relevant for DHCPv6-generated
> +  addresses.
> +
> +- the "announce check" lookup does not backtrack.  Only the destination prefix
> +  that provided the "unspecific" (::/128) match is checked for source prefixes
> +  to see what routers advertised what.  This means that for e.g. RIOs, subtree
> +  routes also have to be created.  (Backtracking for this case would further
> +  increase the cost of source address selection, for a pretty rare situation
> +  that has an easy fix/workaround.)
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f3218abefd0c..4edf48362a07 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18912,6 +18912,7 @@ F:	Documentation/netlink/specs/rt-addr.yaml
>  F:	Documentation/netlink/specs/rt-neigh.yaml
>  F:	Documentation/netlink/specs/rt-route.yaml
>  F:	Documentation/netlink/specs/rt-rule.yaml
> +F:	Documentation/networking/ipv6-addrsel.rst

It seems weird to add this one file here - I wonder why there isn't just
an entry for Documentation/networking ?

Thanks,

jon

^ permalink raw reply

* Re: [PATCH v7 02/12] cpumask: Introduce cpu_preferred_mask
From: Yury Norov @ 2026-07-14 13:01 UTC (permalink / raw)
  To: Shrikanth Hegde
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <df198506-9c6e-4e79-bf3b-798668322708@linux.ibm.com>

On Tue, Jul 14, 2026 at 12:00:51PM +0530, Shrikanth Hegde wrote:
> Hi Yury,

...

> > > diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt
> > > index 88c594c6d7fc..ed02e4431230 100644
> > > --- a/kernel/Kconfig.preempt
> > > +++ b/kernel/Kconfig.preempt
> > > @@ -192,3 +192,6 @@ config SCHED_CLASS_EXT
> > >   	  For more information:
> > >   	    Documentation/scheduler/sched-ext.rst
> > >   	    https://github.com/sched-ext/scx
> > > +
> > > +config PREFERRED_CPU
> > > +	bool
> > 
> > This still should depend on PARAVIRT and SMP. And maybe to enforce it
> > even stronger, your driver should fail to build if PREFERRED_CPU is
> > disabled. Imagine a scenario when someone makes PREFERRED_CPU
> > depending on some other config, but doesn't modify your driver. That
> > way you'll build the STEAL_MONITOR successfully, but because
> > PREFERRED_CPU is off, you'll end up with non-working functionality at
> > best, or corrupted cpu_active_mask at worst.
> > 
> 
> Sorry, i may not understand all the intricacies of kconfigs.
> But, Since driver selects PREFERRED_CPU, and PREFERRED_CPU can't be enabled
> individually, driver again can't depend on PREFERRED_CPU right?
> 
> As per previous discussion, it is probably better that driver selects PREFERRED_CPU.
> Keeping them both independent and selectable brings too many variations.
> No?
> 
> I guess you meant below.
> 
> In kernel/Kconfig.preempt:
> config PREFERRED_CPU
> 	bool
> 	depends on SMP && PARAVIRT
> 
> Driver's Kconfig (this is there already)
> config VIRT_STEAL_GOVERNOR
> 	tristate "Virtual Steal Time Governor"
> 	depends on SMP && PARAVIRT
> 	select PREFERRED_CPU

It's just another precaution. In a hypothetical case of making
preferred CPUs config extended in future, one could keep the steal
governor configuration consistent to avoid this situation:

config PREFERRED_CPU
	depends on SMP && PARAVIRT && NEW_DEPENDENCY
	bool

config VIRT_STEAL_GOVERNOR
	tristate "Virtual Steal Time Governor"
	depends on SMP && PARAVIRT      # NEW_DEPENDENCY dependency missed
	select PREFERRED_CPU            # Selection doesn't happen

In sm_core.c:

  #if !IS_ENABLED(CONFIG_PREFERED_CPU)
  # error "Preferred CPUs is the requirement"
  #endif

As said, it's only precaution for hypothetical case, but it
makes you nice for those working on preferred CPUs in future.

Thanks,
Yury

^ permalink raw reply

* [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86

This RFC extends RISC-V CBQRI/resctrl support to external requesters
behind a RISC-V IOMMU.

The RISC-V IOMMU QoS ID extension associates a resource-control ID
(RCID) and monitoring ID (MCID) with IOMMU-related memory requests [2].
For a translated device request, the IOMMU obtains the IDs from the
device context translation attributes (DC.ta) and supplies them to the
I/O bridge after a successful translation. The bridge and downstream
CBQRI-controlled resources can then apply their existing allocation and
monitoring policy to the request.

The series is based directly on Drew Fustini's v4 RISC-V Ssqosid and
CBQRI resctrl series [1]. That dependency provides CPU-side QoS ID
plumbing and CBQRI-backed resctrl support. This series extends the same
resctrl identity to device requests translated by a RISC-V IOMMU.

The RISC-V names map to the existing resctrl identifiers as follows:

    resctrl CLOSID  ->  RISC-V RCID
    resctrl RMID    ->  RISC-V MCID

Userspace first creates a resctrl resource group. The new "devices"
file then assigns an external requester to that group:

    userspace
       |
       | mkdir /sys/fs/resctrl/<group>
       |
       | echo "iommu_group:<id>" > /sys/fs/resctrl/<group>/devices
       v
    generic resctrl devices file
       |
       | (closid, rmid)
       v
    RISC-V CBQRI IOMMU binding backend
       |
       | (rcid, mcid)
       v
    RISC-V IOMMU DC.ta.{RCID,MCID}

The "devices" file is an assignment and membership interface only. It
does not describe a new resctrl resource, schema, or monitoring domain;
resource policy remains in the existing schemata and info files.

Common IOMMU helpers:

The userspace ABI names an IOMMU group by the numeric ID already used by
/sys/kernel/iommu_groups. Patch 1 adds iommu_group_get_by_id() as a
narrow, reference-counted lookup for that existing identifier.

Updating a group also needs stronger iteration semantics than separate
iommu_group_for_each_dev() calls can provide. All devices must be
validated before any device context is changed, and group membership
must not change between validation and update. Patch 2 therefore adds
iommu_group_update_devices():

    lock group->mutex
      for each device: check()          /* may fail, no state changed */
      for each device: update()         /* non-failing callback */
    unlock group->mutex

The helper contains no resctrl or QoS policy. It only provides an
all-or-none validation boundary for a group-wide update while keeping
IOMMU group internals private. The RISC-V IOMMU QoS code is its first
caller.

RISC-V IOMMU programming:

For translated device requests, the per-device-context QoS IDs are
programmed in DC.ta. Active contexts are invalidated after an update so
cached translations do not retain stale IDs. BARE mode cannot safely
represent per-IOMMU-group assignment because only the per-IOMMU
iommu_qosid global default is available, so group assignment rejects
BARE mode.

The RISC-V IOMMU also has a per-IOMMU iommu_qosid register. The series
exposes it as:

    /sys/class/iommu/<iommu>/qosid

This sysfs attribute programs the global RCID and MCID used for
IOMMU-originated DDT, CQ, FQ, PQ, and MSI accesses. In BARE mode,
device-originated requests also use these IDs. It is not a resctrl group
assignment interface.

Open questions for this RFC:

  * whether a common resctrl "devices" file is the right ABI for
    associating external requesters with a resource group;
  * whether iommu_group_get_by_id() is an acceptable narrow common
    helper for resolving the numeric IOMMU group ID passed through that
    ABI; and
  * whether iommu_group_update_devices() provides the right common
    contract for checked group-wide updates.

[1] Drew Fustini's CBQRI v4 series:
https://lore.kernel.org/linux-riscv/20260706-dfustini-atl-sc-cbqri-dt-v4-0-e75c20201d64@kernel.org/
[2] RISC-V IOMMU QoS ID extension:
https://docs.riscv.org/reference/cbqri/qos_iommu.html

Zhanpeng Zhang (7):
  iommu: Add group lookup by ID
  iommu: Add checked group device update helper
  resctrl: Add a devices file for external requester assignment
  iommu/riscv: Program QoS IDs for assigned groups
  iommu/riscv: Expose global QoS IDs in sysfs
  riscv_cbqri: Assign IOMMU groups to resource groups
  selftests/iommu: Add RISC-V IOMMU QoS smoke test

 .../ABI/testing/sysfs-class-iommu-riscv-iommu |  27 +
 Documentation/filesystems/resctrl.rst         |  26 +
 MAINTAINERS                                   |  12 +
 arch/Kconfig                                  |   6 +
 arch/riscv/include/asm/qos.h                  |  28 +
 drivers/iommu/iommu.c                         | 140 +++-
 drivers/iommu/riscv/iommu-bits.h              |  15 +
 drivers/iommu/riscv/iommu.c                   | 419 ++++++++++-
 drivers/iommu/riscv/iommu.h                   |  22 +-
 drivers/resctrl/Kconfig                       |   6 +
 drivers/resctrl/Makefile                      |   1 +
 drivers/resctrl/cbqri_iommu.c                 | 276 ++++++++
 fs/resctrl/rdtgroup.c                         | 206 +++++-
 include/linux/iommu.h                         |  30 +
 include/linux/resctrl.h                       |  45 ++
 tools/testing/selftests/iommu/Makefile        |   2 +
 .../selftests/iommu/iommu_qos_smoke.sh        | 649 ++++++++++++++++++
 17 files changed, 1886 insertions(+), 24 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
 create mode 100644 drivers/resctrl/cbqri_iommu.c
 create mode 100755 tools/testing/selftests/iommu/iommu_qos_smoke.sh


base-commit: ab754eba620f5a66e7277b7b2d0e706aa7743e8a
-- 
2.50.1 (Apple Git-155)

^ permalink raw reply

* [RFC PATCH 1/7] iommu: Add group lookup by ID
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
the numeric ID used in /sys/kernel/iommu_groups.

An ID lookup must keep the group object alive without also keeping an
otherwise empty group active. Embed the devices kobject in struct
iommu_group so its address remains valid until the parent group is
released, and return a reference on the parent kobject to ID lookup
callers. Add iommu_group_put_by_id() to release that reference and
iommu_group_is_active() to detect when the devices kobject has become
inactive.

Serialize lookup against group teardown with iommu_group_kset_mutex and
only return groups whose devices kobject still has a live reference.
This prevents a concurrent lookup from dereferencing a stale child
kobject while allowing external users to discard bindings to empty
groups.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
 include/linux/iommu.h |  17 +++++++
 2 files changed, 113 insertions(+), 11 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index e8f13dcebbde..da269d10f6bf 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -40,6 +40,7 @@
 #include "iommu-priv.h"
 
 static struct kset *iommu_group_kset;
+static DEFINE_MUTEX(iommu_group_kset_mutex);
 static DEFINE_IDA(iommu_group_ida);
 static DEFINE_IDA(iommu_global_pasid_ida);
 
@@ -52,7 +53,8 @@ enum { IOMMU_PASID_ARRAY_DOMAIN = 0, IOMMU_PASID_ARRAY_HANDLE = 1 };
 
 struct iommu_group {
 	struct kobject kobj;
-	struct kobject *devices_kobj;
+	/* Embedded so it remains addressable until the parent group is released. */
+	struct kobject devices_kobj;
 	struct list_head devices;
 	struct xarray pasid_array;
 	struct mutex mutex;
@@ -729,7 +731,7 @@ static void __iommu_group_free_device(struct iommu_group *group,
 {
 	struct device *dev = grp_dev->dev;
 
-	sysfs_remove_link(group->devices_kobj, grp_dev->name);
+	sysfs_remove_link(&group->devices_kobj, grp_dev->name);
 	sysfs_remove_link(&dev->kobj, "iommu_group");
 
 	trace_remove_device_from_group(group->id, dev);
@@ -1058,6 +1060,14 @@ static const struct kobj_type iommu_group_ktype = {
 	.release = iommu_group_release,
 };
 
+static void iommu_group_devices_release(struct kobject *kobj)
+{
+}
+
+static const struct kobj_type iommu_group_devices_ktype = {
+	.release = iommu_group_devices_release,
+};
+
 /**
  * iommu_group_alloc - Allocate a new group
  *
@@ -1091,17 +1101,22 @@ struct iommu_group *iommu_group_alloc(void)
 	}
 	group->id = ret;
 
+	mutex_lock(&iommu_group_kset_mutex);
 	ret = kobject_init_and_add(&group->kobj, &iommu_group_ktype,
 				   NULL, "%d", group->id);
 	if (ret) {
 		kobject_put(&group->kobj);
+		mutex_unlock(&iommu_group_kset_mutex);
 		return ERR_PTR(ret);
 	}
 
-	group->devices_kobj = kobject_create_and_add("devices", &group->kobj);
-	if (!group->devices_kobj) {
+	kobject_init(&group->devices_kobj, &iommu_group_devices_ktype);
+	ret = kobject_add(&group->devices_kobj, &group->kobj, "devices");
+	if (ret) {
+		kobject_put(&group->devices_kobj);
 		kobject_put(&group->kobj); /* triggers .release & free */
-		return ERR_PTR(-ENOMEM);
+		mutex_unlock(&iommu_group_kset_mutex);
+		return ERR_PTR(ret);
 	}
 
 	/*
@@ -1114,15 +1129,18 @@ struct iommu_group *iommu_group_alloc(void)
 	ret = iommu_group_create_file(group,
 				      &iommu_group_attr_reserved_regions);
 	if (ret) {
-		kobject_put(group->devices_kobj);
+		kobject_put(&group->devices_kobj);
+		mutex_unlock(&iommu_group_kset_mutex);
 		return ERR_PTR(ret);
 	}
 
 	ret = iommu_group_create_file(group, &iommu_group_attr_type);
 	if (ret) {
-		kobject_put(group->devices_kobj);
+		kobject_put(&group->devices_kobj);
+		mutex_unlock(&iommu_group_kset_mutex);
 		return ERR_PTR(ret);
 	}
+	mutex_unlock(&iommu_group_kset_mutex);
 
 	pr_debug("Allocated group %d\n", group->id);
 
@@ -1286,7 +1304,7 @@ static struct group_device *iommu_group_alloc_device(struct iommu_group *group,
 		goto err_remove_link;
 	}
 
-	ret = sysfs_create_link_nowarn(group->devices_kobj,
+	ret = sysfs_create_link_nowarn(&group->devices_kobj,
 				       &dev->kobj, device->name);
 	if (ret) {
 		if (ret == -EEXIST && i >= 0) {
@@ -1431,7 +1449,7 @@ struct iommu_group *iommu_group_get(struct device *dev)
 	struct iommu_group *group = dev->iommu_group;
 
 	if (group)
-		kobject_get(group->devices_kobj);
+		kobject_get(&group->devices_kobj);
 
 	return group;
 }
@@ -1446,7 +1464,7 @@ EXPORT_SYMBOL_GPL(iommu_group_get);
  */
 struct iommu_group *iommu_group_ref_get(struct iommu_group *group)
 {
-	kobject_get(group->devices_kobj);
+	kobject_get(&group->devices_kobj);
 	return group;
 }
 EXPORT_SYMBOL_GPL(iommu_group_ref_get);
@@ -1461,10 +1479,77 @@ EXPORT_SYMBOL_GPL(iommu_group_ref_get);
 void iommu_group_put(struct iommu_group *group)
 {
 	if (group)
-		kobject_put(group->devices_kobj);
+		kobject_put(&group->devices_kobj);
 }
 EXPORT_SYMBOL_GPL(iommu_group_put);
 
+/**
+ * iommu_group_get_by_id - Lookup an IOMMU group by its sysfs ID
+ * @id: group ID matching /sys/kernel/iommu_groups/<id>
+ *
+ * Return a group with a reference on its parent kobject, or NULL if no active
+ * group exists for @id. The caller must release the returned group with
+ * iommu_group_put_by_id(). Keeping this reference does not keep an empty
+ * group's devices kobject active.
+ */
+struct iommu_group *iommu_group_get_by_id(int id)
+{
+	struct kobject *group_kobj;
+	struct iommu_group *group = NULL;
+	char name[12];
+
+	if (!iommu_group_kset || id < 0)
+		return NULL;
+
+	snprintf(name, sizeof(name), "%d", id);
+	mutex_lock(&iommu_group_kset_mutex);
+	group_kobj = kset_find_obj(iommu_group_kset, name);
+	if (!group_kobj)
+		goto unlock;
+
+	group = container_of(group_kobj, struct iommu_group, kobj);
+	if (!kobject_get_unless_zero(&group->devices_kobj)) {
+		kobject_put(group_kobj);
+		group = NULL;
+		goto unlock;
+	}
+
+	kobject_put(&group->devices_kobj);
+unlock:
+	mutex_unlock(&iommu_group_kset_mutex);
+	return group;
+}
+EXPORT_SYMBOL_GPL(iommu_group_get_by_id);
+
+/**
+ * iommu_group_put_by_id - Release a group returned by ID lookup
+ * @group: group returned by iommu_group_get_by_id()
+ */
+void iommu_group_put_by_id(struct iommu_group *group)
+{
+	if (group)
+		kobject_put(&group->kobj);
+}
+EXPORT_SYMBOL_GPL(iommu_group_put_by_id);
+
+/**
+ * iommu_group_is_active - Test whether a referenced group can accept devices
+ * @group: referenced IOMMU group
+ *
+ * Return true while the devices kobject still has a live reference. Once the
+ * group loses its last device and external device reference, it cannot become
+ * active again.
+ */
+bool iommu_group_is_active(struct iommu_group *group)
+{
+	if (!group || !kobject_get_unless_zero(&group->devices_kobj))
+		return false;
+
+	kobject_put(&group->devices_kobj);
+	return true;
+}
+EXPORT_SYMBOL_GPL(iommu_group_is_active);
+
 /**
  * iommu_group_id - Return ID for a group
  * @group: the group to ID
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index d20aa6f6863a..e771b4a92f5b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -989,6 +989,9 @@ extern void iommu_group_remove_device(struct device *dev);
 extern int iommu_group_for_each_dev(struct iommu_group *group, void *data,
 				    int (*fn)(struct device *, void *));
 extern struct iommu_group *iommu_group_get(struct device *dev);
+struct iommu_group *iommu_group_get_by_id(int id);
+void iommu_group_put_by_id(struct iommu_group *group);
+bool iommu_group_is_active(struct iommu_group *group);
 extern struct iommu_group *iommu_group_ref_get(struct iommu_group *group);
 extern void iommu_group_put(struct iommu_group *group);
 
@@ -1401,6 +1404,20 @@ static inline struct iommu_group *iommu_group_get(struct device *dev)
 	return NULL;
 }
 
+static inline struct iommu_group *iommu_group_get_by_id(int id)
+{
+	return NULL;
+}
+
+static inline void iommu_group_put_by_id(struct iommu_group *group)
+{
+}
+
+static inline bool iommu_group_is_active(struct iommu_group *group)
+{
+	return false;
+}
+
 static inline void iommu_group_put(struct iommu_group *group)
 {
 }
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [RFC PATCH 2/7] iommu: Add checked group device update helper
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

Some group-wide operations must validate every member before changing
any device. Separate iommu_group_for_each_dev() calls cannot provide
that guarantee because group membership may change between traversals.

Add iommu_group_update_devices() to keep the group membership mutex held
across a validation pass and a non-failing update pass. This provides
all-or-none validation without exposing IOMMU group internals to
callers.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 drivers/iommu/iommu.c | 33 +++++++++++++++++++++++++++++++++
 include/linux/iommu.h | 13 +++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index da269d10f6bf..9a6c4a7e7df6 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -1436,6 +1436,39 @@ int iommu_group_for_each_dev(struct iommu_group *group, void *data,
 }
 EXPORT_SYMBOL_GPL(iommu_group_for_each_dev);
 
+/**
+ * iommu_group_update_devices - Check and update every device in a group
+ * @group: the group
+ * @data: caller data passed to both callbacks
+ * @check: validates whether one device can be updated
+ * @update: updates one device after every check has succeeded
+ *
+ * Keep group membership stable while first checking every device and then
+ * applying an update which cannot fail. No device is updated if a check fails.
+ */
+int iommu_group_update_devices(struct iommu_group *group, void *data,
+			       int (*check)(struct device *, void *),
+			       void (*update)(struct device *, void *))
+{
+	struct group_device *device;
+	int ret = 0;
+
+	mutex_lock(&group->mutex);
+	for_each_group_device(group, device) {
+		ret = check(device->dev, data);
+		if (ret)
+			goto unlock;
+	}
+
+	for_each_group_device(group, device)
+		update(device->dev, data);
+
+unlock:
+	mutex_unlock(&group->mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_group_update_devices);
+
 /**
  * iommu_group_get - Return the group for a device and increment reference
  * @dev: get the group that this device belongs to
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e771b4a92f5b..befba0683e06 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -988,6 +988,9 @@ extern int iommu_group_add_device(struct iommu_group *group,
 extern void iommu_group_remove_device(struct device *dev);
 extern int iommu_group_for_each_dev(struct iommu_group *group, void *data,
 				    int (*fn)(struct device *, void *));
+int iommu_group_update_devices(struct iommu_group *group, void *data,
+			       int (*check)(struct device *, void *),
+			       void (*update)(struct device *, void *));
 extern struct iommu_group *iommu_group_get(struct device *dev);
 struct iommu_group *iommu_group_get_by_id(int id);
 void iommu_group_put_by_id(struct iommu_group *group);
@@ -1399,6 +1402,16 @@ static inline int iommu_group_for_each_dev(struct iommu_group *group,
 	return -ENODEV;
 }
 
+static inline int iommu_group_update_devices(struct iommu_group *group,
+					     void *data,
+					     int (*check)(struct device *,
+							  void *),
+					     void (*update)(struct device *,
+							    void *))
+{
+	return -ENODEV;
+}
+
 static inline struct iommu_group *iommu_group_get(struct device *dev)
 {
 	return NULL;
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [RFC PATCH 3/7] resctrl: Add a devices file for external requester assignment
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

Do not overload the resctrl tasks file with architecture-specific
non-PID tokens. The tasks ABI remains a list of task IDs, while the new
devices file carries external requesters assigned to a resctrl group.

Add architecture hooks for assigning and showing those external
objects, and reject group removal, reparenting, or pseudo-lock setup
while devices are still assigned. The teardown path performs a
best-effort reset to the default group.

The devices file is an assignment interface only. It does not describe
a new resource schema or domain; resource allocation and monitoring
policy remain described by the existing schemata and info files.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 Documentation/filesystems/resctrl.rst |  26 ++++
 arch/Kconfig                          |   6 +
 fs/resctrl/rdtgroup.c                 | 206 +++++++++++++++++++++++++-
 include/linux/resctrl.h               |  45 ++++++
 4 files changed, 280 insertions(+), 3 deletions(-)

diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index e4b66af55ffb..fce61d019114 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -581,6 +581,32 @@ All groups contain the following files:
 	idle tasks. Instead, a CPU's idle task is always considered as a
 	member of the group owning the CPU.
 
+"devices":
+	On architectures that support external requester assignment through
+	resctrl, reading this file shows the devices or device groups assigned
+	to this resource group. Writing an architecture-specific device token
+	moves that external requester to the group. Multiple tokens can be
+	separated by commas and are processed sequentially. A failure aborts the
+	write, but requesters moved before the failure remain in their new groups.
+
+	On RISC-V, an IOMMU group is identified by the following token::
+
+		 iommu_group:<group-id>
+
+	Each assigned IOMMU group is reported on a separate line when the file is
+	read. Writing the token to the root control group's devices file restores
+	the IOMMU group to the reserved default QoS IDs. Default assignments are
+	not listed in the root devices file.
+
+	This file only controls external requester membership. Resource
+	allocation and monitoring policy remains described by schemata and
+	info files.
+
+	Resource groups with assigned devices cannot be removed or reparented.
+	Move devices to another group first.
+
+	Failures will be logged to /sys/fs/resctrl/info/last_cmd_status.
+
 "cpus":
 	Reading this file shows a bitmask of the logical CPUs owned by
 	this group. Writing a mask to this file will add and remove
diff --git a/arch/Kconfig b/arch/Kconfig
index fa7507ac8e13..36a4f4fbb164 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -1615,6 +1615,12 @@ config ARCH_HAS_CPU_RESCTRL
 	  monitoring and control interfaces provided by the 'resctrl'
 	  filesystem (see RESCTRL_FS).
 
+config ARCH_HAS_RESCTRL_DEVICES
+	bool
+	help
+	  An architecture selects this option to indicate that external
+	  device objects can be attached to resctrl resource groups.
+
 config HAVE_ARCH_COMPILER_H
 	bool
 	help
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index af2cbab14497..2e424c911049 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -99,13 +99,32 @@ void rdt_last_cmd_puts(const char *s)
 	seq_buf_puts(&last_cmd_status, s);
 }
 
+void resctrl_last_cmd_puts(const char *s)
+{
+	rdt_last_cmd_puts(s);
+}
+
+static void rdt_last_cmd_vprintf(const char *fmt, va_list ap)
+{
+	lockdep_assert_held(&rdtgroup_mutex);
+	seq_buf_vprintf(&last_cmd_status, fmt, ap);
+}
+
 void rdt_last_cmd_printf(const char *fmt, ...)
 {
 	va_list ap;
 
 	va_start(ap, fmt);
-	lockdep_assert_held(&rdtgroup_mutex);
-	seq_buf_vprintf(&last_cmd_status, fmt, ap);
+	rdt_last_cmd_vprintf(fmt, ap);
+	va_end(ap);
+}
+
+void resctrl_last_cmd_printf(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	rdt_last_cmd_vprintf(fmt, ap);
 	va_end(ap);
 }
 
@@ -766,6 +785,151 @@ static int rdtgroup_move_task(pid_t pid, struct rdtgroup *rdtgrp,
 	return ret;
 }
 
+static bool rdtgroup_effective_ids(struct rdtgroup *r,
+				   struct resctrl_group_ids *ids)
+{
+	if (!r || !ids)
+		return false;
+
+	if (r->type == RDTMON_GROUP)
+		ids->closid = r->mon.parent->closid;
+	else if (r->type == RDTCTRL_GROUP)
+		ids->closid = r->closid;
+	else
+		return false;
+
+	ids->rmid = r->mon.rmid;
+	return true;
+}
+
+#ifdef CONFIG_ARCH_HAS_RESCTRL_DEVICES
+static void show_rdt_devices(struct rdtgroup *r, struct seq_file *s)
+{
+	struct resctrl_group_ids ids;
+
+	if (rdtgroup_effective_ids(r, &ids))
+		resctrl_arch_devices_show(s, ids);
+}
+
+static int rdtgroup_reset_all_devices(struct rdtgroup *to)
+{
+	struct resctrl_group_ids default_ids;
+
+	if (!to || !rdtgroup_effective_ids(to, &default_ids))
+		return 0;
+
+	return resctrl_arch_devices_reset_all(default_ids);
+}
+
+static bool rdtgroup_arch_devices_assigned(struct rdtgroup *rdtgrp)
+{
+	struct resctrl_group_ids ids;
+
+	if (!rdtgroup_effective_ids(rdtgrp, &ids))
+		return false;
+
+	return resctrl_arch_devices_assigned(ids);
+}
+
+static bool rdtgroup_child_arch_devices_assigned(struct rdtgroup *rdtgrp)
+{
+	struct rdtgroup *crgrp;
+
+	list_for_each_entry(crgrp, &rdtgrp->mon.crdtgrp_list, mon.crdtgrp_list) {
+		if (rdtgroup_arch_devices_assigned(crgrp))
+			return true;
+	}
+
+	return false;
+}
+
+static int rdtgroup_reject_assigned_devices(struct rdtgroup *rdtgrp,
+					    bool include_children,
+					    const char *operation)
+{
+	if (!rdtgroup_arch_devices_assigned(rdtgrp) &&
+	    (!include_children || !rdtgroup_child_arch_devices_assigned(rdtgrp)))
+		return 0;
+
+	rdt_last_cmd_printf("Move devices out before %s group\n", operation);
+	return -EBUSY;
+}
+
+static ssize_t rdtgroup_devices_write(struct kernfs_open_file *of,
+				      char *buf, size_t nbytes, loff_t off)
+{
+	struct resctrl_group_ids ids;
+	struct rdtgroup *rdtgrp;
+	char *tok;
+	int ret = 0;
+
+	rdtgrp = rdtgroup_kn_lock_live(of->kn);
+	if (!rdtgrp) {
+		rdtgroup_kn_unlock(of->kn);
+		return -ENOENT;
+	}
+	rdt_last_cmd_clear();
+
+	if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED ||
+	    rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP) {
+		ret = -EINVAL;
+		rdt_last_cmd_puts("Pseudo-locking in progress\n");
+		goto unlock;
+	}
+
+	if (!rdtgroup_effective_ids(rdtgrp, &ids)) {
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	while ((tok = strsep(&buf, ","))) {
+		tok = strim(tok);
+		if (!*tok) {
+			rdt_last_cmd_puts("Device list parsing error\n");
+			ret = -EINVAL;
+			break;
+		}
+
+		ret = resctrl_arch_devices_write(tok, ids);
+		if (ret)
+			break;
+	}
+
+unlock:
+	rdtgroup_kn_unlock(of->kn);
+
+	return ret ?: nbytes;
+}
+
+static int rdtgroup_devices_show(struct kernfs_open_file *of,
+				 struct seq_file *s, void *v)
+{
+	struct rdtgroup *rdtgrp;
+	int ret = 0;
+
+	rdtgrp = rdtgroup_kn_lock_live(of->kn);
+	if (rdtgrp)
+		show_rdt_devices(rdtgrp, s);
+	else
+		ret = -ENOENT;
+	rdtgroup_kn_unlock(of->kn);
+
+	return ret;
+}
+#else
+static inline int rdtgroup_reset_all_devices(struct rdtgroup *to)
+{
+	return 0;
+}
+
+static inline int rdtgroup_reject_assigned_devices(struct rdtgroup *rdtgrp,
+						   bool include_children,
+						   const char *operation)
+{
+	return 0;
+}
+#endif
+
 static ssize_t rdtgroup_tasks_write(struct kernfs_open_file *of,
 				    char *buf, size_t nbytes, loff_t off)
 {
@@ -1491,6 +1655,11 @@ static ssize_t rdtgroup_mode_write(struct kernfs_open_file *of,
 		rdtgrp->mode = RDT_MODE_EXCLUSIVE;
 	} else if (IS_ENABLED(CONFIG_RESCTRL_FS_PSEUDO_LOCK) &&
 		   !strcmp(buf, "pseudo-locksetup")) {
+		ret = rdtgroup_reject_assigned_devices(rdtgrp, true,
+						       "entering pseudo-locksetup");
+		if (ret)
+			goto out;
+
 		ret = rdtgroup_locksetup_enter(rdtgrp);
 		if (ret)
 			goto out;
@@ -2067,6 +2236,16 @@ static struct rftype res_common_files[] = {
 		.seq_show	= rdtgroup_tasks_show,
 		.fflags		= RFTYPE_BASE,
 	},
+#ifdef CONFIG_ARCH_HAS_RESCTRL_DEVICES
+	{
+		.name		= "devices",
+		.mode		= 0644,
+		.kf_ops		= &rdtgroup_kf_single_ops,
+		.write		= rdtgroup_devices_write,
+		.seq_show	= rdtgroup_devices_show,
+		.fflags		= RFTYPE_BASE,
+	},
+#endif
 	{
 		.name		= "mon_hw_id",
 		.mode		= 0444,
@@ -3061,6 +3240,8 @@ static void rmdir_all_sub(void)
 	/* Move all tasks to the default resource group */
 	rdt_move_group_tasks(NULL, &rdtgroup_default, NULL);
 
+	WARN_ON_ONCE(rdtgroup_reset_all_devices(&rdtgroup_default));
+
 	list_for_each_entry_safe(rdtgrp, tmp, &rdt_all_groups, rdtgroup_list) {
 		/* Free any child rmids */
 		free_all_child_rdtgrp(rdtgrp);
@@ -3970,6 +4151,11 @@ static int rdtgroup_rmdir_mon(struct rdtgroup *rdtgrp, cpumask_var_t tmpmask)
 	struct rdtgroup *prdtgrp = rdtgrp->mon.parent;
 	u32 closid, rmid;
 	int cpu;
+	int ret;
+
+	ret = rdtgroup_reject_assigned_devices(rdtgrp, false, "removing");
+	if (ret)
+		return ret;
 
 	/* Give any tasks back to the parent group */
 	rdt_move_group_tasks(rdtgrp, prdtgrp, tmpmask);
@@ -4020,6 +4206,11 @@ static int rdtgroup_rmdir_ctrl(struct rdtgroup *rdtgrp, cpumask_var_t tmpmask)
 {
 	u32 closid, rmid;
 	int cpu;
+	int ret;
+
+	ret = rdtgroup_reject_assigned_devices(rdtgrp, true, "removing");
+	if (ret)
+		return ret;
 
 	/* Give any tasks back to the default group */
 	rdt_move_group_tasks(rdtgrp, &rdtgroup_default, tmpmask);
@@ -4093,7 +4284,10 @@ static int rdtgroup_rmdir(struct kernfs_node *kn)
 	    rdtgrp != &rdtgroup_default) {
 		if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP ||
 		    rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
-			ret = rdtgroup_ctrl_remove(rdtgrp);
+			ret = rdtgroup_reject_assigned_devices(rdtgrp, true,
+							       "removing");
+			if (!ret)
+				ret = rdtgroup_ctrl_remove(rdtgrp);
 		} else {
 			ret = rdtgroup_rmdir_ctrl(rdtgrp, tmpmask);
 		}
@@ -4210,6 +4404,12 @@ static int rdtgroup_rename(struct kernfs_node *kn,
 		goto out;
 	}
 
+	if (rdtgrp->mon.parent != new_prdtgrp) {
+		ret = rdtgroup_reject_assigned_devices(rdtgrp, false, "reparenting");
+		if (ret)
+			goto out;
+	}
+
 	/*
 	 * Allocate the cpumask for use in mongrp_reparent() to avoid the
 	 * possibility of failing to allocate it after kernfs_rename() has
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 73ff522448a0..a4b5c2d5e814 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -8,6 +8,8 @@
 #include <linux/pid.h>
 #include <linux/resctrl_types.h>
 
+struct seq_file;
+
 #ifdef CONFIG_ARCH_HAS_CPU_RESCTRL
 #include <asm/resctrl.h>
 #endif
@@ -18,6 +20,49 @@
 
 #define RESCTRL_PICK_ANY_CPU		-1
 
+/**
+ * struct resctrl_group_ids - resctrl control and monitoring IDs
+ * @closid: resource control class ID
+ * @rmid: resource monitoring ID
+ */
+struct resctrl_group_ids {
+	u32 closid;
+	u32 rmid;
+};
+
+void resctrl_last_cmd_puts(const char *s);
+void resctrl_last_cmd_printf(const char *fmt, ...) __printf(1, 2);
+
+#ifdef CONFIG_ARCH_HAS_RESCTRL_DEVICES
+int resctrl_arch_devices_write(char *tok, struct resctrl_group_ids ids);
+void resctrl_arch_devices_show(struct seq_file *s,
+			       struct resctrl_group_ids ids);
+bool resctrl_arch_devices_assigned(struct resctrl_group_ids ids);
+int resctrl_arch_devices_reset_all(struct resctrl_group_ids default_ids);
+#else
+static inline int resctrl_arch_devices_write(char *tok,
+					     struct resctrl_group_ids ids)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void resctrl_arch_devices_show(struct seq_file *s,
+					     struct resctrl_group_ids ids)
+{
+}
+
+static inline bool resctrl_arch_devices_assigned(struct resctrl_group_ids ids)
+{
+	return false;
+}
+
+static inline int
+resctrl_arch_devices_reset_all(struct resctrl_group_ids default_ids)
+{
+	return 0;
+}
+#endif
+
 #ifdef CONFIG_PROC_CPU_RESCTRL
 
 int proc_resctrl_show(struct seq_file *m,
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [RFC PATCH 4/7] iommu/riscv: Program QoS IDs for assigned groups
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

Program RCID and MCID for RISC-V IOMMU groups through the device context
TA fields. The resctrl group assignment is per device group, so reject
BARE mode where only the per-IOMMU iommu_qosid global default is
available.

Validate every group member, firmware ID, device context, field value,
and QoS ID capability before changing hardware. Then update all members
through the checked IOMMU group helper so a validation failure leaves the
group unchanged.

Serialize DC.ta changes with context setup under qosid_lock. Change only
the RCID and MCID fields with ordinary accesses so fixed DDT mappings are
not subject to atomic LR/SC operations, invalidate active device contexts
after an update, and clear the IDs when a device is released.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 arch/riscv/include/asm/qos.h     |  16 +++
 drivers/iommu/riscv/iommu-bits.h |  15 +++
 drivers/iommu/riscv/iommu.c      | 200 ++++++++++++++++++++++++++++++-
 drivers/iommu/riscv/iommu.h      |   3 +
 4 files changed, 232 insertions(+), 2 deletions(-)

diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
index cf19e8438bb9..daa758d4efff 100644
--- a/arch/riscv/include/asm/qos.h
+++ b/arch/riscv/include/asm/qos.h
@@ -2,7 +2,23 @@
 #ifndef _ASM_RISCV_QOS_H
 #define _ASM_RISCV_QOS_H
 
+#include <linux/errno.h>
 #include <linux/percpu-defs.h>
+#include <linux/types.h>
+
+struct iommu_group;
+
+#ifdef CONFIG_RISCV_IOMMU
+int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
+				u32 mcid);
+#else
+static inline int riscv_iommu_group_set_qosid(struct iommu_group *group,
+					      u32 rcid, u32 mcid)
+{
+	return -EOPNOTSUPP;
+}
+
+#endif
 
 #ifdef CONFIG_RISCV_ISA_SSQOSID
 
diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index f2ef9bd3cde9..782de5c92727 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -63,6 +63,7 @@
 #define RISCV_IOMMU_CAPABILITIES_PD8		BIT_ULL(38)
 #define RISCV_IOMMU_CAPABILITIES_PD17		BIT_ULL(39)
 #define RISCV_IOMMU_CAPABILITIES_PD20		BIT_ULL(40)
+#define RISCV_IOMMU_CAPABILITIES_QOSID		BIT_ULL(41)
 #define RISCV_IOMMU_CAPABILITIES_NL		BIT_ULL(42)
 #define RISCV_IOMMU_CAPABILITIES_S		BIT_ULL(43)
 
@@ -274,6 +275,14 @@ enum riscv_iommu_hpmevent_id {
 #define RISCV_IOMMU_TR_RESPONSE_SZ	BIT_ULL(9)
 #define RISCV_IOMMU_TR_RESPONSE_PPN	RISCV_IOMMU_PPN_FIELD
 
+/* 6.27 IOMMU QoS IDs for IOMMU-initiated requests (32bits) */
+#define RISCV_IOMMU_REG_IOMMU_QOSID	0x0270
+#define RISCV_IOMMU_IOMMU_QOSID_RCID	GENMASK(11, 0)
+#define RISCV_IOMMU_IOMMU_QOSID_MCID	GENMASK(27, 16)
+
+#define RISCV_IOMMU_IOMMU_QOSID_RCID_SHIFT	0
+#define RISCV_IOMMU_IOMMU_QOSID_MCID_SHIFT	16
+
 /* 5.27 Interrupt cause to vector (64bits) */
 #define RISCV_IOMMU_REG_ICVEC		0x02F8
 #define RISCV_IOMMU_ICVEC_CIV		GENMASK_ULL(3, 0)
@@ -371,6 +380,12 @@ enum riscv_iommu_dc_iohgatp_modes {
 
 /* Translation attributes fields */
 #define RISCV_IOMMU_DC_TA_PSCID		GENMASK_ULL(31, 12)
+/*
+ * QoS IDs for translated device requests and IOMMU accesses with a
+ * device context (when capabilities.QOSID == 1).
+ */
+#define RISCV_IOMMU_DC_TA_RCID		GENMASK_ULL(51, 40)
+#define RISCV_IOMMU_DC_TA_MCID		GENMASK_ULL(63, 52)
 
 /* First-stage context fields */
 #define RISCV_IOMMU_DC_FSC_PPN		RISCV_IOMMU_ATP_PPN_FIELD
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index cec3ddd7ab10..deab646bb1ea 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -48,6 +48,8 @@
 static DEFINE_IDA(riscv_iommu_pscids);
 #define RISCV_IOMMU_MAX_PSCID		(BIT(20) - 1)
 
+static const struct iommu_ops riscv_iommu_ops;
+
 /* Device resource-managed allocations */
 struct riscv_iommu_devres {
 	void *addr;
@@ -1091,6 +1093,28 @@ static void riscv_iommu_iotlb_inval(struct riscv_iommu_domain *domain,
 }
 
 #define RISCV_IOMMU_FSC_BARE 0
+#define RISCV_IOMMU_DC_TA_QOSID \
+	(RISCV_IOMMU_DC_TA_RCID | RISCV_IOMMU_DC_TA_MCID)
+
+static u64 riscv_iommu_qosid_ta(u32 rcid, u32 mcid)
+{
+	return FIELD_PREP(RISCV_IOMMU_DC_TA_RCID, rcid) |
+	       FIELD_PREP(RISCV_IOMMU_DC_TA_MCID, mcid);
+}
+
+static void riscv_iommu_dc_update_qosid(struct riscv_iommu_device *iommu,
+					struct riscv_iommu_dc *dc,
+					u32 rcid, u32 mcid)
+{
+	u64 qos_ta = riscv_iommu_qosid_ta(rcid, mcid);
+	u64 ta;
+
+	lockdep_assert_held(&iommu->qosid_lock);
+	ta = READ_ONCE(dc->ta);
+	ta = (ta & ~RISCV_IOMMU_DC_TA_QOSID) | qos_ta;
+	WRITE_ONCE(dc->ta, ta);
+}
+
 /*
  * This function sends IOTINVAL commands as required by the RISC-V
  * IOMMU specification (Section 6.3.1 and 6.3.2 in 1.0 spec version)
@@ -1202,12 +1226,23 @@ static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
 	 * is stored as DC_TC_V bit (both sharing the same location at BIT(0)).
 	 */
 	for (i = 0; i < fwspec->num_ids; i++) {
+		u64 dc_ta;
+		u64 ta_mask = RISCV_IOMMU_PC_TA_PSCID;
+
 		dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
 		tc = READ_ONCE(dc->tc);
-		tc |= ta & RISCV_IOMMU_DC_TC_V;
+		dc_ta = ta;
+		if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) {
+			dc_ta |= READ_ONCE(dc->ta) &
+				 (RISCV_IOMMU_DC_TA_RCID |
+				  RISCV_IOMMU_DC_TA_MCID);
+			ta_mask |= RISCV_IOMMU_DC_TA_RCID |
+				   RISCV_IOMMU_DC_TA_MCID;
+		}
+		tc |= dc_ta & RISCV_IOMMU_DC_TC_V;
 
 		WRITE_ONCE(dc->fsc, fsc);
-		WRITE_ONCE(dc->ta, ta & RISCV_IOMMU_PC_TA_PSCID);
+		WRITE_ONCE(dc->ta, dc_ta & ta_mask);
 		/* Update device context, write TC.V as the last step. */
 		dma_wmb();
 		WRITE_ONCE(dc->tc, tc);
@@ -1474,13 +1509,174 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev)
 	return &iommu->iommu;
 }
 
+static void riscv_iommu_qosid_invalidate_did(struct riscv_iommu_device *iommu,
+					     unsigned int did)
+{
+	struct riscv_iommu_command cmd;
+
+	riscv_iommu_cmd_iodir_inval_ddt(&cmd);
+	riscv_iommu_cmd_iodir_set_did(&cmd, did);
+	riscv_iommu_cmd_send(iommu, &cmd);
+}
+
 static void riscv_iommu_release_device(struct device *dev)
 {
 	struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+	struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+	bool sync_required = false;
+	unsigned int i;
+
+	if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) {
+		mutex_lock(&iommu->qosid_lock);
+		for (i = 0; fwspec && i < fwspec->num_ids; i++) {
+			struct riscv_iommu_dc *dc;
+			u64 tc;
+
+			dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
+			if (!dc)
+				continue;
+
+			tc = READ_ONCE(dc->tc);
+			riscv_iommu_dc_update_qosid(iommu, dc, 0, 0);
+			if (!(tc & RISCV_IOMMU_DC_TC_V))
+				continue;
+
+			dma_wmb();
+			riscv_iommu_qosid_invalidate_did(iommu, fwspec->ids[i]);
+			riscv_iommu_iodir_iotinval(iommu, false, dc->iohgatp,
+						   dc, NULL);
+			sync_required = true;
+		}
+
+		if (sync_required)
+			riscv_iommu_cmd_sync(iommu,
+					     RISCV_IOMMU_IOTINVAL_TIMEOUT);
+		mutex_unlock(&iommu->qosid_lock);
+	}
 
 	kfree_rcu_mightsleep(info);
 }
 
+struct riscv_iommu_qosid_hw_ctx {
+	u32 rcid;
+	u32 mcid;
+	bool has_devices;
+	bool has_qosid;
+	bool reset;
+};
+
+static int riscv_iommu_qosid_validate_dev(struct device *dev, void *data)
+{
+	struct riscv_iommu_qosid_hw_ctx *ctx = data;
+	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+	struct riscv_iommu_device *iommu;
+	unsigned int i;
+
+	ctx->has_devices = true;
+
+	if (!dev->iommu || !dev->iommu->iommu_dev ||
+	    dev->iommu->iommu_dev->ops != &riscv_iommu_ops)
+		return -EOPNOTSUPP;
+
+	if (!fwspec || !fwspec->num_ids)
+		return -ENODEV;
+
+	iommu = dev_to_iommu(dev);
+
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return ctx->reset ? 0 : -EOPNOTSUPP;
+
+	ctx->has_qosid = true;
+
+	/*
+	 * IOMMU group QoS is a per-device assignment. BARE mode only has the
+	 * per-IOMMU iommu_qosid register, which is a global default rather
+	 * than a safe target for moving an individual group between resctrl
+	 * groups.
+	 */
+	if (iommu->ddt_mode <= RISCV_IOMMU_DDTP_IOMMU_MODE_BARE)
+		return -EOPNOTSUPP;
+
+	for (i = 0; i < fwspec->num_ids; i++) {
+		if (!riscv_iommu_get_dc(iommu, fwspec->ids[i]))
+			return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void riscv_iommu_qosid_apply_dev(struct device *dev, void *data)
+{
+	struct riscv_iommu_qosid_hw_ctx *ctx = data;
+	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+	struct riscv_iommu_device *iommu;
+	bool sync_required = false;
+	unsigned int i;
+
+	iommu = dev_to_iommu(dev);
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return;
+
+	mutex_lock(&iommu->qosid_lock);
+	for (i = 0; i < fwspec->num_ids; i++) {
+		struct riscv_iommu_dc *dc;
+		bool dc_is_valid;
+		u64 tc;
+
+		dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
+		if (WARN_ON_ONCE(!dc))
+			continue;
+
+		tc = READ_ONCE(dc->tc);
+		dc_is_valid = tc & RISCV_IOMMU_DC_TC_V;
+
+		riscv_iommu_dc_update_qosid(iommu, dc, ctx->rcid, ctx->mcid);
+		dev_dbg(dev, "set QoS ID DC.ta did=%u rcid=%u mcid=%u\n",
+			fwspec->ids[i], ctx->rcid, ctx->mcid);
+
+		if (dc_is_valid) {
+			dma_wmb();
+			riscv_iommu_qosid_invalidate_did(iommu, fwspec->ids[i]);
+			riscv_iommu_iodir_iotinval(iommu, false, dc->iohgatp,
+						   dc, NULL);
+			sync_required = true;
+		}
+	}
+
+	if (sync_required)
+		riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
+	mutex_unlock(&iommu->qosid_lock);
+}
+
+int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid, u32 mcid)
+{
+	struct riscv_iommu_qosid_hw_ctx hw = {
+		.rcid = rcid,
+		.mcid = mcid,
+		.reset = !rcid && !mcid,
+	};
+	int ret;
+
+	if (rcid > FIELD_MAX(RISCV_IOMMU_DC_TA_RCID) ||
+	    mcid > FIELD_MAX(RISCV_IOMMU_DC_TA_MCID))
+		return -ERANGE;
+
+	ret = iommu_group_update_devices(group, &hw,
+					 riscv_iommu_qosid_validate_dev,
+					 riscv_iommu_qosid_apply_dev);
+	if (ret)
+		return ret;
+	if (!hw.has_devices)
+		return -ENODATA;
+	if (!hw.has_qosid && !hw.reset)
+		return -EOPNOTSUPP;
+
+	pr_debug("set qosid: group=%d rcid=%u mcid=%u\n",
+		 iommu_group_id(group), rcid, mcid);
+	return 0;
+}
+
 static const struct iommu_ops riscv_iommu_ops = {
 	.of_xlate = riscv_iommu_of_xlate,
 	.identity_domain = &riscv_iommu_identity_domain,
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 46df79dd5495..2c57625637bf 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -19,6 +19,9 @@
 
 struct riscv_iommu_device;
 
+int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
+				u32 mcid);
+
 struct riscv_iommu_queue {
 	atomic_t prod;				/* unbounded producer allocation index */
 	atomic_t head;				/* unbounded shadow ring buffer consumer index */
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [RFC PATCH 5/7] iommu/riscv: Expose global QoS IDs in sysfs
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

The RISC-V IOMMU QoS extension provides iommu_qosid as a per-IOMMU
global default tag. It is used for IOMMU-originated DDT, CQ, FQ, PQ, and
MSI accesses, and for device-originated requests when DDTP is in BARE
mode.

Initialize iommu_qosid to RCID 0 and MCID 0 when the hardware advertises
QOSID support. Preserve reserved and WPRI bits with read-modify-write,
and use register readback to reject values which the WARL fields do not
retain.

Add a qosid attribute to the RISC-V IOMMU class device. Reading returns
the current RCID and MCID values. Writing the documented
'rcid=<rcid> mcid=<mcid>' form updates both fields while preserving the
other register bits.

Keep this interface separate from resctrl group QoS. The sysfs attribute
controls the IOMMU-wide default, while resctrl device assignment programs
per-device DC.ta in translated modes.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 .../ABI/testing/sysfs-class-iommu-riscv-iommu |  27 +++
 MAINTAINERS                                   |  10 ++
 drivers/iommu/riscv/iommu.c                   | 159 +++++++++++++++++-
 drivers/iommu/riscv/iommu.h                   |   9 +-
 4 files changed, 202 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu

diff --git a/Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu b/Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
new file mode 100644
index 000000000000..b0cd68997f17
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
@@ -0,0 +1,27 @@
+What:		/sys/class/iommu/<iommu>/qosid
+Date:		June 2026
+KernelVersion:	6.18
+Contact:	Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
+Description:
+		The RISC-V IOMMU global default QoS IDs for this IOMMU.
+		The file is present only when the IOMMU reports the QOSID
+		capability.
+
+		Reading the file returns the RCID and MCID fields from the
+		iommu_qosid register:
+
+		  rcid=<rcid> mcid=<mcid>
+
+		Writing the file updates the RCID and MCID fields while
+		preserving reserved/WPRI bits:
+
+		  rcid=<rcid> mcid=<mcid>
+
+		Writes fail with ERANGE when either value cannot be represented
+		by the IOMMU. A successful write is verified by reading the WARL
+		fields back from the register.
+
+		The iommu_qosid register is a per-IOMMU global default. It
+		tags IOMMU-originated DDT, CQ, FQ, PQ and MSI accesses, and
+		in BARE mode device-originated requests. It does not assign
+		per-device or per-IOMMU-group QoS IDs in translated modes.
diff --git a/MAINTAINERS b/MAINTAINERS
index 0b5d38b772e0..c59be02c8f02 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23279,6 +23279,16 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git
 F:	Documentation/devicetree/bindings/iommu/riscv,iommu.yaml
 F:	drivers/iommu/riscv/
 
+RISC-V IOMMU QoS
+M:	Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
+R:	Tomasz Jeznach <tomasz.jeznach@linux.dev>
+R:	Drew Fustini <fustini@kernel.org>
+R:	yunhui cui <cuiyunhui@bytedance.com>
+L:	iommu@lists.linux.dev
+L:	linux-riscv@lists.infradead.org
+S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
+
 RISC-V MICROCHIP SUPPORT
 M:	Conor Dooley <conor.dooley@microchip.com>
 M:	Daire McNamara <daire.mcnamara@microchip.com>
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index deab646bb1ea..e85da9eef58e 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1658,6 +1658,7 @@ int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid, u32 mcid)
 	};
 	int ret;
 
+	/* Resctrl IDs are bounded by the system's reported controller counts. */
 	if (rcid > FIELD_MAX(RISCV_IOMMU_DC_TA_RCID) ||
 	    mcid > FIELD_MAX(RISCV_IOMMU_DC_TA_MCID))
 		return -ERANGE;
@@ -1688,9 +1689,154 @@ static const struct iommu_ops riscv_iommu_ops = {
 	.release_device	= riscv_iommu_release_device,
 };
 
+static int riscv_iommu_set_default_qosid(struct riscv_iommu_device *iommu,
+					 u32 rcid, u32 mcid)
+{
+	u32 old_qosid;
+	u32 qosid;
+	int ret = 0;
+
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return -EOPNOTSUPP;
+
+	if (rcid > FIELD_MAX(RISCV_IOMMU_IOMMU_QOSID_RCID) ||
+	    mcid > FIELD_MAX(RISCV_IOMMU_IOMMU_QOSID_MCID))
+		return -ERANGE;
+
+	/*
+	 * iommu_qosid is a per-IOMMU global default. It tags IOMMU-originated
+	 * DDT/CQ/FQ/PQ and MSI accesses, and in BARE mode device-originated
+	 * requests. Per-device group QoS is still handled separately through
+	 * DC.ta.
+	 */
+	mutex_lock(&iommu->qosid_lock);
+	old_qosid = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IOMMU_QOSID);
+	qosid = old_qosid & ~(RISCV_IOMMU_IOMMU_QOSID_RCID |
+				RISCV_IOMMU_IOMMU_QOSID_MCID);
+	qosid |= FIELD_PREP(RISCV_IOMMU_IOMMU_QOSID_RCID, rcid) |
+		 FIELD_PREP(RISCV_IOMMU_IOMMU_QOSID_MCID, mcid);
+	riscv_iommu_writel(iommu, RISCV_IOMMU_REG_IOMMU_QOSID, qosid);
+
+	qosid = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IOMMU_QOSID);
+	if (FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_RCID, qosid) != rcid ||
+	    FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_MCID, qosid) != mcid) {
+		riscv_iommu_writel(iommu, RISCV_IOMMU_REG_IOMMU_QOSID,
+				   old_qosid);
+		ret = -ERANGE;
+	}
+	mutex_unlock(&iommu->qosid_lock);
+	if (ret)
+		return ret;
+
+	dev_dbg(iommu->dev, "set global QoS IDs rcid=%u mcid=%u\n",
+		(u32)FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_RCID, qosid),
+		(u32)FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_MCID, qosid));
+
+	return ret;
+}
+
+static int riscv_iommu_get_default_qosid(struct riscv_iommu_device *iommu,
+					 u32 *rcid, u32 *mcid)
+{
+	u32 qosid;
+
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return -EOPNOTSUPP;
+
+	qosid = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IOMMU_QOSID);
+	if (rcid)
+		*rcid = FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_RCID, qosid);
+	if (mcid)
+		*mcid = FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_MCID, qosid);
+
+	return 0;
+}
+
+static int riscv_iommu_init_default_qosid(struct riscv_iommu_device *iommu)
+{
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return 0;
+
+	/* Avoid probing the live WARL fields with all-ones while in BARE mode. */
+	return riscv_iommu_set_default_qosid(iommu, 0, 0);
+}
+
+static struct riscv_iommu_device *dev_to_riscv_iommu(struct device *dev)
+{
+	struct iommu_device *iommu = dev_to_iommu_device(dev);
+
+	return iommu ? container_of(iommu, struct riscv_iommu_device, iommu) : NULL;
+}
+
+static ssize_t qosid_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct riscv_iommu_device *iommu = dev_to_riscv_iommu(dev);
+	u32 rcid, mcid;
+	int ret;
+
+	if (!iommu)
+		return -ENODEV;
+
+	ret = riscv_iommu_get_default_qosid(iommu, &rcid, &mcid);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "rcid=%u mcid=%u\n", rcid, mcid);
+}
+
+static ssize_t qosid_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct riscv_iommu_device *iommu = dev_to_riscv_iommu(dev);
+	char *args, *key, *value;
+	char *input;
+	u32 rcid, mcid;
+	int ret = -EINVAL;
+
+	if (!iommu)
+		return -ENODEV;
+
+	input = kstrdup(buf, GFP_KERNEL);
+	if (!input)
+		return -ENOMEM;
+
+	args = strim(input);
+	args = next_arg(args, &key, &value);
+	if (!value || strcmp(key, "rcid") || kstrtou32(value, 10, &rcid))
+		goto out;
+
+	args = next_arg(args, &key, &value);
+	if (!value || strcmp(key, "mcid") || kstrtou32(value, 10, &mcid) ||
+	    *skip_spaces(args))
+		goto out;
+
+	ret = riscv_iommu_set_default_qosid(iommu, rcid, mcid);
+out:
+	kfree(input);
+	return ret ? ret : count;
+}
+
+static DEVICE_ATTR_RW(qosid);
+
+static struct attribute *riscv_iommu_attrs[] = {
+	&dev_attr_qosid.attr,
+	NULL,
+};
+
+static const struct attribute_group riscv_iommu_group = {
+	.attrs = riscv_iommu_attrs,
+};
+
+static const struct attribute_group *riscv_iommu_groups[] = {
+	&riscv_iommu_group,
+	NULL,
+};
+
 static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
 {
 	u64 ddtp;
+	int ret;
 
 	/*
 	 * Make sure the IOMMU is switched off or in pass-through mode during
@@ -1721,6 +1867,10 @@ static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
 			return -EINVAL;
 	}
 
+	ret = riscv_iommu_init_default_qosid(iommu);
+	if (ret)
+		return ret;
+
 	/*
 	 * Distribute interrupt vectors, always use first vector for CIV.
 	 * At least one interrupt is required. Read back and verify.
@@ -1753,10 +1903,12 @@ void riscv_iommu_remove(struct riscv_iommu_device *iommu)
 
 int riscv_iommu_init(struct riscv_iommu_device *iommu)
 {
+	const struct attribute_group **sysfs_groups = NULL;
 	int rc;
 
 	RISCV_IOMMU_QUEUE_INIT(&iommu->cmdq, CQ);
 	RISCV_IOMMU_QUEUE_INIT(&iommu->fltq, FQ);
+	mutex_init(&iommu->qosid_lock);
 
 	rc = riscv_iommu_init_check(iommu);
 	if (rc)
@@ -1788,8 +1940,11 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu)
 	if (rc)
 		goto err_queue_disable;
 
-	rc = iommu_device_sysfs_add(&iommu->iommu, NULL, NULL, "riscv-iommu@%s",
-				    dev_name(iommu->dev));
+	if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID)
+		sysfs_groups = riscv_iommu_groups;
+
+	rc = iommu_device_sysfs_add(&iommu->iommu, NULL, sysfs_groups,
+				    "riscv-iommu@%s", dev_name(iommu->dev));
 	if (rc) {
 		dev_err_probe(iommu->dev, rc, "cannot register sysfs interface\n");
 		goto err_iodir_off;
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 2c57625637bf..13ea67b7e42d 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -12,8 +12,12 @@
 #define _RISCV_IOMMU_H_
 
 #include <linux/iommu.h>
-#include <linux/types.h>
 #include <linux/iopoll.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#ifdef CONFIG_RISCV_IOMMU_32BIT
+#include <linux/spinlock.h>
+#endif
 
 #include "iommu-bits.h"
 
@@ -50,6 +54,9 @@ struct riscv_iommu_device {
 	u64 caps;
 	u32 fctl;
 
+	/* Serializes QoS updates to iommu_qosid and device contexts. */
+	struct mutex qosid_lock;
+
 	/* available interrupt numbers, MSI or WSI */
 	unsigned int irqs[RISCV_IOMMU_INTR_COUNT];
 	unsigned int irqs_count;
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [RFC PATCH 6/7] riscv_cbqri: Assign IOMMU groups to resource groups
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

Allow the resctrl devices file to accept iommu_group:<id> tokens on
RISC-V and map the target resource group's closid and rmid values to
RCID and MCID.

Record non-default assignments in an RCU-protected binding list rather
than inferring membership from hardware IDs, which may be shared. Keep a
parent reference obtained by numeric group lookup so the binding does not
keep an empty group's devices kobject active, and prune bindings after
their group becomes inactive.

Publish an explicit UPDATING state while hardware changes. Paging-domain
attachment rejects that transient state. Static FSC=Bare transitions
preserve the current IDs so a mandatory release-domain attachment cannot
fail. After the checked group update succeeds, publish the packed IDs. A
validation failure restores the previous active state without partially
changing hardware.

Moving an IOMMU group to the default resource group resets its hardware
state and removes the software binding. Device contexts created later for
an assigned group inherit the IDs through the RCU lookup path.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 MAINTAINERS                   |   1 +
 arch/riscv/include/asm/qos.h  |  12 ++
 drivers/iommu/riscv/iommu.c   |  70 +++++++--
 drivers/iommu/riscv/iommu.h   |  16 +-
 drivers/resctrl/Kconfig       |   6 +
 drivers/resctrl/Makefile      |   1 +
 drivers/resctrl/cbqri_iommu.c | 276 ++++++++++++++++++++++++++++++++++
 7 files changed, 369 insertions(+), 13 deletions(-)
 create mode 100644 drivers/resctrl/cbqri_iommu.c

diff --git a/MAINTAINERS b/MAINTAINERS
index c59be02c8f02..162ad5a1780f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23288,6 +23288,7 @@ L:	iommu@lists.linux.dev
 L:	linux-riscv@lists.infradead.org
 S:	Maintained
 F:	Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
+F:	drivers/resctrl/cbqri_iommu.c
 
 RISC-V MICROCHIP SUPPORT
 M:	Conor Dooley <conor.dooley@microchip.com>
diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
index daa758d4efff..84a93739c6ff 100644
--- a/arch/riscv/include/asm/qos.h
+++ b/arch/riscv/include/asm/qos.h
@@ -8,6 +8,18 @@
 
 struct iommu_group;
 
+#ifdef CONFIG_RISCV_CBQRI_IOMMU
+int riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group,
+					u32 *rcid, u32 *mcid);
+#else
+static inline int
+riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group, u32 *rcid,
+				    u32 *mcid)
+{
+	return -ENOENT;
+}
+#endif
+
 #ifdef CONFIG_RISCV_IOMMU
 int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
 				u32 mcid);
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index e85da9eef58e..1a7d8a582959 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1188,16 +1188,50 @@ static void riscv_iommu_iodir_iotinval(struct riscv_iommu_device *iommu,
  * device is not quiesced might be disruptive, potentially causing
  * interim translation faults.
  */
-static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
-				     struct device *dev, u64 fsc, u64 ta)
+static int riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
+				    struct device *dev, u64 fsc, u64 ta)
 {
 	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
 	struct riscv_iommu_dc *dc;
 	struct riscv_iommu_command cmd;
 	bool sync_required = false;
+	u64 group_qos_ta = 0;
+	bool has_group_qos;
+	u32 group_rcid;
+	u32 group_mcid;
 	u64 tc;
+	int ret = 0;
+	int qos_ret;
 	int i;
 
+	/* Serialize DC.ta updates with resctrl IOMMU group QoS changes. */
+	mutex_lock(&iommu->qosid_lock);
+
+	qos_ret = riscv_resctrl_iommu_group_get_qosid(dev->iommu_group,
+						      &group_rcid, &group_mcid);
+	if (qos_ret == -EAGAIN) {
+		/* Static domain transitions must not fail during device release. */
+		if (fsc != RISCV_IOMMU_FSC_BARE) {
+			ret = -EBUSY;
+			goto unlock;
+		}
+		qos_ret = -ENOENT;
+	}
+	has_group_qos = !qos_ret;
+	if (has_group_qos) {
+		if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) ||
+		    iommu->ddt_mode <= RISCV_IOMMU_DDTP_IOMMU_MODE_BARE) {
+			ret = -EOPNOTSUPP;
+			goto unlock;
+		}
+		if (group_rcid > FIELD_MAX(RISCV_IOMMU_DC_TA_RCID) ||
+		    group_mcid > FIELD_MAX(RISCV_IOMMU_DC_TA_MCID)) {
+			ret = -ERANGE;
+			goto unlock;
+		}
+		group_qos_ta = riscv_iommu_qosid_ta(group_rcid, group_mcid);
+	}
+
 	for (i = 0; i < fwspec->num_ids; i++) {
 		dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
 		tc = READ_ONCE(dc->tc);
@@ -1233,11 +1267,12 @@ static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
 		tc = READ_ONCE(dc->tc);
 		dc_ta = ta;
 		if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) {
-			dc_ta |= READ_ONCE(dc->ta) &
-				 (RISCV_IOMMU_DC_TA_RCID |
-				  RISCV_IOMMU_DC_TA_MCID);
-			ta_mask |= RISCV_IOMMU_DC_TA_RCID |
-				   RISCV_IOMMU_DC_TA_MCID;
+			if (has_group_qos)
+				dc_ta |= group_qos_ta;
+			else
+				dc_ta |= READ_ONCE(dc->ta) &
+					 RISCV_IOMMU_DC_TA_QOSID;
+			ta_mask |= RISCV_IOMMU_DC_TA_QOSID;
 		}
 		tc |= dc_ta & RISCV_IOMMU_DC_TC_V;
 
@@ -1259,6 +1294,9 @@ static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
 	}
 
 	riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
+unlock:
+	mutex_unlock(&iommu->qosid_lock);
+	return ret;
 }
 
 /*
@@ -1324,6 +1362,7 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
 	struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
 	struct pt_iommu_riscv_64_hw_info pt_info;
 	u64 fsc, ta;
+	int ret;
 
 	pt_iommu_riscv_64_hw_info(&domain->riscvpt, &pt_info);
 
@@ -1338,7 +1377,11 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
 	if (riscv_iommu_bond_link(domain, dev))
 		return -ENOMEM;
 
-	riscv_iommu_iodir_update(iommu, dev, fsc, ta);
+	ret = riscv_iommu_iodir_update(iommu, dev, fsc, ta);
+	if (ret) {
+		riscv_iommu_bond_unlink(domain, dev);
+		return ret;
+	}
 	riscv_iommu_bond_unlink(info->domain, dev);
 	info->domain = domain;
 
@@ -1413,9 +1456,12 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
 {
 	struct riscv_iommu_device *iommu = dev_to_iommu(dev);
 	struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+	int ret;
 
 	/* Make device context invalid, translation requests will fault w/ #258 */
-	riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, 0);
+	ret = riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, 0);
+	if (ret)
+		return ret;
 	riscv_iommu_bond_unlink(info->domain, dev);
 	info->domain = NULL;
 
@@ -1435,8 +1481,12 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
 {
 	struct riscv_iommu_device *iommu = dev_to_iommu(dev);
 	struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+	u64 ta = RISCV_IOMMU_PC_TA_V;
+	int ret;
 
-	riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, RISCV_IOMMU_PC_TA_V);
+	ret = riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, ta);
+	if (ret)
+		return ret;
 	riscv_iommu_bond_unlink(info->domain, dev);
 	info->domain = NULL;
 
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 13ea67b7e42d..e7804ec65647 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -11,13 +11,11 @@
 #ifndef _RISCV_IOMMU_H_
 #define _RISCV_IOMMU_H_
 
+#include <linux/errno.h>
 #include <linux/iommu.h>
 #include <linux/iopoll.h>
 #include <linux/mutex.h>
 #include <linux/types.h>
-#ifdef CONFIG_RISCV_IOMMU_32BIT
-#include <linux/spinlock.h>
-#endif
 
 #include "iommu-bits.h"
 
@@ -26,6 +24,18 @@ struct riscv_iommu_device;
 int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
 				u32 mcid);
 
+#ifdef CONFIG_RISCV_CBQRI_IOMMU
+int riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group,
+					u32 *rcid, u32 *mcid);
+#else
+static inline int
+riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group, u32 *rcid,
+				    u32 *mcid)
+{
+	return -ENOENT;
+}
+#endif
+
 struct riscv_iommu_queue {
 	atomic_t prod;				/* unbounded producer allocation index */
 	atomic_t head;				/* unbounded shadow ring buffer consumer index */
diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
index b7db6ff9d054..4e10c813e647 100644
--- a/drivers/resctrl/Kconfig
+++ b/drivers/resctrl/Kconfig
@@ -60,3 +60,9 @@ endif
 config RISCV_CBQRI_RESCTRL_FS
 	bool
 	default y if RISCV_CBQRI && RESCTRL_FS
+
+config RISCV_CBQRI_IOMMU
+	bool
+	depends on RISCV_CBQRI_RESCTRL_FS && RISCV_IOMMU
+	select ARCH_HAS_RESCTRL_DEVICES
+	default y
diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
index c8339113ef1f..148b2e28c6a3 100644
--- a/drivers/resctrl/Makefile
+++ b/drivers/resctrl/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_RISCV_CBQRI)			+= cbqri.o
 cbqri-y						+= cbqri_devices.o
 cbqri-$(CONFIG_RISCV_CBQRI_RESCTRL_FS)		+= cbqri_resctrl.o
 cbqri-$(CONFIG_RISCV_CBQRI_CAPACITY)		+= cbqri_capacity.o
+cbqri-$(CONFIG_RISCV_CBQRI_IOMMU)			+= cbqri_iommu.o
diff --git a/drivers/resctrl/cbqri_iommu.c b/drivers/resctrl/cbqri_iommu.c
new file mode 100644
index 000000000000..4086c32546bd
--- /dev/null
+++ b/drivers/resctrl/cbqri_iommu.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/capability.h>
+#include <linux/errno.h>
+#include <linux/iommu.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/rculist.h>
+#include <linux/rcupdate.h>
+#include <linux/resctrl.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include <asm/qos.h>
+
+#define IOMMU_GROUP_TOKEN	"iommu_group:"
+
+/*
+ * RISC-V IOMMU RCID and MCID fields are 12 bits each, so an active binding
+ * cannot contain U64_MAX.
+ */
+#define QOS_IOMMU_BINDING_UPDATING	U64_MAX
+
+/* Records the resctrl QoS ID assignment for one IOMMU group. */
+struct qos_iommu_group_binding {
+	struct list_head node;
+	struct rcu_head rcu;
+	struct iommu_group *group;
+	int group_id;
+	/* Packed IDs, or QOS_IOMMU_BINDING_UPDATING during a hardware update. */
+	u64 state;
+};
+
+/*
+ * resctrl owns IOMMU group membership bookkeeping. Do not infer membership by
+ * reading RCID/MCID back from hardware: the same IDs may be shared by multiple
+ * resctrl users. Writers are serialized by rdtgroup_mutex. IOMMU attach paths
+ * read the list under RCU so newly attached devices can inherit an existing
+ * group assignment without taking locks in the opposite order to resctrl.
+ */
+static LIST_HEAD(qos_iommu_bindings);
+
+static u64 qos_iommu_group_ids_pack(struct resctrl_group_ids ids)
+{
+	return (u64)ids.closid << 32 | ids.rmid;
+}
+
+static struct resctrl_group_ids qos_iommu_group_ids_unpack(u64 ids)
+{
+	return (struct resctrl_group_ids) {
+		.closid = upper_32_bits(ids),
+		.rmid = lower_32_bits(ids),
+	};
+}
+
+static bool qos_iommu_group_ids_match(u64 packed, struct resctrl_group_ids ids)
+{
+	return packed != QOS_IOMMU_BINDING_UPDATING &&
+	       packed == qos_iommu_group_ids_pack(ids);
+}
+
+static bool qos_iommu_group_ids_default(struct resctrl_group_ids ids)
+{
+	return ids.closid == RESCTRL_RESERVED_CLOSID &&
+	       ids.rmid == RESCTRL_RESERVED_RMID;
+}
+
+static struct qos_iommu_group_binding *
+qos_iommu_group_find_binding(int group_id)
+{
+	struct qos_iommu_group_binding *binding;
+
+	list_for_each_entry(binding, &qos_iommu_bindings, node) {
+		if (binding->group_id == group_id)
+			return binding;
+	}
+
+	return NULL;
+}
+
+static void qos_iommu_group_free_binding(struct qos_iommu_group_binding *binding)
+{
+	list_del_rcu(&binding->node);
+	iommu_group_put_by_id(binding->group);
+	kfree_rcu(binding, rcu);
+}
+
+static void qos_iommu_group_prune_inactive(void)
+{
+	struct qos_iommu_group_binding *binding;
+	struct qos_iommu_group_binding *tmp;
+
+	list_for_each_entry_safe(binding, tmp, &qos_iommu_bindings, node) {
+		if (!iommu_group_is_active(binding->group))
+			qos_iommu_group_free_binding(binding);
+	}
+}
+
+static int qos_iommu_group_set(struct iommu_group *group,
+			       struct resctrl_group_ids ids)
+{
+	/* RISC-V IOMMU QoS maps closid->rcid and rmid->mcid 1:1. */
+	return riscv_iommu_group_set_qosid(group, ids.closid, ids.rmid);
+}
+
+int resctrl_arch_devices_write(char *tok, struct resctrl_group_ids ids)
+{
+	struct qos_iommu_group_binding *binding;
+	struct qos_iommu_group_binding *new_binding = NULL;
+	struct iommu_group *group;
+	u64 old_state = QOS_IOMMU_BINDING_UPDATING;
+	bool default_ids;
+	int group_id;
+	int ret;
+
+	if (strncmp(tok, IOMMU_GROUP_TOKEN, strlen(IOMMU_GROUP_TOKEN))) {
+		resctrl_last_cmd_printf("Unsupported device token %s\n", tok);
+		return -EOPNOTSUPP;
+	}
+
+	tok += strlen(IOMMU_GROUP_TOKEN);
+	if (kstrtoint(tok, 10, &group_id) || group_id < 0) {
+		resctrl_last_cmd_printf("IOMMU group parsing error %s%s\n",
+					IOMMU_GROUP_TOKEN, tok);
+		return -EINVAL;
+	}
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		resctrl_last_cmd_puts("No permission to move IOMMU group\n");
+		return -EPERM;
+	}
+
+	default_ids = qos_iommu_group_ids_default(ids);
+	qos_iommu_group_prune_inactive();
+	binding = qos_iommu_group_find_binding(group_id);
+	if (!binding) {
+		group = iommu_group_get_by_id(group_id);
+		if (!group) {
+			resctrl_last_cmd_printf("No IOMMU group %d\n", group_id);
+			return -ENOENT;
+		}
+
+		if (default_ids) {
+			ret = qos_iommu_group_set(group, ids);
+			iommu_group_put_by_id(group);
+			if (ret)
+				resctrl_last_cmd_printf("Failed to reset IOMMU group %d QoS (%d)\n",
+							group_id, ret);
+			return ret;
+		}
+
+		new_binding = kzalloc_obj(*new_binding, GFP_KERNEL);
+		if (!new_binding) {
+			iommu_group_put_by_id(group);
+			resctrl_last_cmd_puts("Unable to allocate IOMMU group QoS binding\n");
+			return -ENOMEM;
+		}
+		binding = new_binding;
+		binding->group = group;
+		binding->group_id = group_id;
+		binding->state = QOS_IOMMU_BINDING_UPDATING;
+		list_add_tail_rcu(&binding->node, &qos_iommu_bindings);
+	} else {
+		group = binding->group;
+		old_state = READ_ONCE(binding->state);
+		WRITE_ONCE(binding->state, QOS_IOMMU_BINDING_UPDATING);
+	}
+
+	ret = qos_iommu_group_set(group, ids);
+	if (ret) {
+		if (new_binding)
+			qos_iommu_group_free_binding(binding);
+		else
+			WRITE_ONCE(binding->state, old_state);
+		resctrl_last_cmd_printf("Failed to move IOMMU group %d QoS (%d)\n",
+					group_id, ret);
+		return ret;
+	}
+
+	if (default_ids)
+		qos_iommu_group_free_binding(binding);
+	else
+		WRITE_ONCE(binding->state, qos_iommu_group_ids_pack(ids));
+
+	return 0;
+}
+
+int riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group, u32 *rcid,
+					u32 *mcid)
+{
+	struct qos_iommu_group_binding *binding;
+	int ret = -ENOENT;
+
+	if (!group)
+		return -ENODEV;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(binding, &qos_iommu_bindings, node) {
+		struct resctrl_group_ids ids;
+		u64 state;
+
+		if (binding->group != group)
+			continue;
+		state = READ_ONCE(binding->state);
+		if (state == QOS_IOMMU_BINDING_UPDATING) {
+			ret = -EAGAIN;
+			break;
+		}
+
+		ids = qos_iommu_group_ids_unpack(state);
+		if (rcid)
+			*rcid = ids.closid;
+		if (mcid)
+			*mcid = ids.rmid;
+		ret = 0;
+		break;
+	}
+	rcu_read_unlock();
+
+	return ret;
+}
+
+void resctrl_arch_devices_show(struct seq_file *s, struct resctrl_group_ids ids)
+{
+	struct qos_iommu_group_binding *binding;
+
+	qos_iommu_group_prune_inactive();
+	list_for_each_entry(binding, &qos_iommu_bindings, node) {
+		if (qos_iommu_group_ids_match(READ_ONCE(binding->state), ids))
+			seq_printf(s, "iommu_group:%d\n", binding->group_id);
+	}
+}
+
+bool resctrl_arch_devices_assigned(struct resctrl_group_ids ids)
+{
+	struct qos_iommu_group_binding *binding;
+
+	qos_iommu_group_prune_inactive();
+	list_for_each_entry(binding, &qos_iommu_bindings, node) {
+		if (qos_iommu_group_ids_match(READ_ONCE(binding->state), ids))
+			return true;
+	}
+
+	return false;
+}
+
+int resctrl_arch_devices_reset_all(struct resctrl_group_ids default_ids)
+{
+	struct qos_iommu_group_binding *binding;
+	struct qos_iommu_group_binding *tmp;
+	u64 old_state;
+	int first_ret = 0;
+	int ret;
+
+	qos_iommu_group_prune_inactive();
+	list_for_each_entry_safe(binding, tmp, &qos_iommu_bindings, node) {
+		old_state = READ_ONCE(binding->state);
+		WRITE_ONCE(binding->state, QOS_IOMMU_BINDING_UPDATING);
+		ret = qos_iommu_group_set(binding->group, default_ids);
+		if (ret) {
+			WRITE_ONCE(binding->state, old_state);
+			if (!first_ret)
+				first_ret = ret;
+			resctrl_last_cmd_printf("Failed to reset IOMMU group %d QoS (%d)\n",
+						binding->group_id, ret);
+			continue;
+		}
+
+		qos_iommu_group_free_binding(binding);
+	}
+
+	return first_ret;
+}
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [RFC PATCH 7/7] selftests/iommu: Add RISC-V IOMMU QoS smoke test
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
  To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
  Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
	james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
	cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
	linux-kselftest, x86
In-Reply-To: <20260714130657.46963-1-zhangzhanpeng.jasper@bytedance.com>

Add a RISC-V IOMMU QoS smoke test for the resctrl devices assignment ABI
and the per-IOMMU global qosid sysfs attribute.

Probe for a usable IOMMU group with a non-default child-group assignment
without disturbing groups already assigned by the system. Cover valid
assignment and reset, devices-file readback, malformed and missing group
IDs, strict decimal parsing, tasks-file rejection, group removal and
pseudo-lock protection, and cleanup before child-group removal.

When the global qosid attribute is present, cover its readback format,
valid rewrite and restore, malformed input, missing separators, range
checking, and integer overflow. Keep child-group coverage optional so an
environment without allocatable resctrl resources can still exercise
the root IOMMU QoS paths.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
 MAINTAINERS                                   |   1 +
 tools/testing/selftests/iommu/Makefile        |   2 +
 .../selftests/iommu/iommu_qos_smoke.sh        | 649 ++++++++++++++++++
 3 files changed, 652 insertions(+)
 create mode 100755 tools/testing/selftests/iommu/iommu_qos_smoke.sh

diff --git a/MAINTAINERS b/MAINTAINERS
index 162ad5a1780f..9facf8ac79a2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23289,6 +23289,7 @@ L:	linux-riscv@lists.infradead.org
 S:	Maintained
 F:	Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
 F:	drivers/resctrl/cbqri_iommu.c
+F:	tools/testing/selftests/iommu/iommu_qos_smoke.sh
 
 RISC-V MICROCHIP SUPPORT
 M:	Conor Dooley <conor.dooley@microchip.com>
diff --git a/tools/testing/selftests/iommu/Makefile b/tools/testing/selftests/iommu/Makefile
index 84abeb2f0949..2d22a10c17fc 100644
--- a/tools/testing/selftests/iommu/Makefile
+++ b/tools/testing/selftests/iommu/Makefile
@@ -7,4 +7,6 @@ TEST_GEN_PROGS :=
 TEST_GEN_PROGS += iommufd
 TEST_GEN_PROGS += iommufd_fail_nth
 
+TEST_PROGS += iommu_qos_smoke.sh
+
 include ../lib.mk
diff --git a/tools/testing/selftests/iommu/iommu_qos_smoke.sh b/tools/testing/selftests/iommu/iommu_qos_smoke.sh
new file mode 100755
index 000000000000..1a81bca39460
--- /dev/null
+++ b/tools/testing/selftests/iommu/iommu_qos_smoke.sh
@@ -0,0 +1,649 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-only
+# Smoke test for RISC-V IOMMU QoS resctrl integration.
+#
+# This intentionally tests the IOMMU group QoS control path:
+#   resctrl/devices -> iommu_group:<id> parser -> IOMMU group QoS assignment
+#   explicit reset to the default group before resctrl group removal
+# and the per-IOMMU global default QoS ID sysfs path used by BARE mode.
+
+KSFT_PASS=0
+KSFT_FAIL=1
+KSFT_SKIP=4
+
+RESCTRL=/sys/fs/resctrl
+RESCTRL_FS=resctrl
+IOMMU_GROUPS=/sys/kernel/iommu_groups
+LAST_STATUS=$RESCTRL/info/last_cmd_status
+ROOT_DEVICES=$RESCTRL/devices
+IOMMU_CLASS=/sys/class/iommu
+
+pass_count=0
+fail_count=0
+skip_count=0
+group=""
+child_group=""
+qosid_file=""
+tmp_dir=""
+lock_dir=""
+lock_taken=0
+mounted_by_test=0
+qosid_rcid=""
+qosid_mcid=""
+
+log()
+{
+	printf '%s\n' "$*"
+}
+
+pass()
+{
+	pass_count=$((pass_count + 1))
+	log "PASS: $*"
+}
+
+fail()
+{
+	fail_count=$((fail_count + 1))
+	log "FAIL: $*"
+}
+
+skip()
+{
+	log "SKIP: $*"
+	exit $KSFT_SKIP
+}
+
+skip_optional()
+{
+	skip_count=$((skip_count + 1))
+	log "SKIP: $*"
+}
+
+cleanup()
+{
+	if [ -n "$child_group" ] && [ -d "$child_group" ]; then
+		if [ -n "$group" ]; then
+			printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" 2>/dev/null || true
+		fi
+		rmdir "$child_group" 2>/dev/null || true
+	fi
+
+	if [ "$mounted_by_test" = "1" ]; then
+		umount "$RESCTRL" 2>/dev/null || true
+	fi
+
+	if [ "$lock_taken" = "1" ] && [ -n "$lock_dir" ]; then
+		rmdir "$lock_dir" 2>/dev/null || true
+	fi
+
+	if [ -n "$tmp_dir" ]; then
+		rm -rf "$tmp_dir"
+	fi
+}
+
+need_root()
+{
+	[ "$(id -u)" = "0" ] || skip "requires root"
+}
+
+init_tmp_dir()
+{
+	tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/iommu_qos_smoke.XXXXXX") ||
+		skip "failed to create temporary directory"
+}
+
+take_global_lock()
+{
+	lock_dir=${TMPDIR:-/tmp}/iommu_qos_smoke.lock
+
+	if mkdir "$lock_dir" 2>"$tmp_dir/lock.err"; then
+		lock_taken=1
+		pass "acquired global IOMMU QoS test lock"
+		return
+	fi
+
+	skip "another IOMMU QoS smoke test instance is running"
+}
+
+ensure_resctrl_mounted()
+{
+	if ! grep -qw resctrl /proc/filesystems; then
+		skip "resctrl filesystem is not available"
+	fi
+
+	mkdir -p "$RESCTRL" 2>/dev/null || true
+
+	if grep -qs " $RESCTRL resctrl " /proc/mounts; then
+		pass "resctrl already mounted"
+		return
+	fi
+
+	if mount -t resctrl "$RESCTRL_FS" "$RESCTRL" 2>"$tmp_dir/mount.err"; then
+		mounted_by_test=1
+		pass "mounted resctrl"
+	else
+		log "mount error: $(cat "$tmp_dir/mount.err" 2>/dev/null)"
+		skip "failed to mount resctrl"
+	fi
+}
+
+ensure_devices_file()
+{
+	[ -e "$ROOT_DEVICES" ] || skip "resctrl devices file is not available"
+	[ -w "$ROOT_DEVICES" ] || skip "$ROOT_DEVICES is not writable"
+}
+
+create_child_group()
+{
+	child_group=$RESCTRL/iommu_qos_test_$$
+
+	if mkdir "$child_group" 2>"$tmp_dir/mkdir.err"; then
+		pass "created child resctrl group"
+		return
+	fi
+
+	log "mkdir error: $(cat "$tmp_dir/mkdir.err" 2>/dev/null)"
+	child_group=""
+	skip "failed to create a child resctrl group"
+}
+
+iommu_group_is_assigned()
+{
+	candidate=$1
+
+	find "$RESCTRL" -type f -name devices \
+		-exec grep -qx "iommu_group:$candidate" {} \; -print \
+		2>/dev/null | grep -q .
+}
+
+pick_iommu_group()
+{
+	populated=0
+	unsupported=0
+
+	[ -d "$IOMMU_GROUPS" ] || skip "no IOMMU groups sysfs directory"
+
+	for path in "$IOMMU_GROUPS"/*; do
+		[ -d "$path" ] || continue
+		case "${path##*/}" in
+		*[!0-9]*|'')
+			continue
+			;;
+		esac
+
+		[ -d "$path/devices" ] || continue
+		if ! find "$path/devices" -mindepth 1 -maxdepth 1 2>/dev/null |
+			grep -q .; then
+			continue
+		fi
+
+		populated=$((populated + 1))
+		candidate=${path##*/}
+		if iommu_group_is_assigned "$candidate"; then
+			log "skip IOMMU group $candidate with an existing resctrl assignment"
+			continue
+		fi
+
+		if printf '%s\n' "iommu_group:$candidate" > "$child_group/devices" \
+				2>"$tmp_dir/group_$candidate.err"; then
+			group=$candidate
+			if ! printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+					2>"$tmp_dir/group_${candidate}_reset.err"; then
+				log "reset error:"
+				cat "$tmp_dir/group_${candidate}_reset.err" 2>/dev/null
+				fail "failed to restore probed IOMMU group $group"
+				return 1
+			fi
+			pass "selected group $group using non-default QoS assignment"
+			return 0
+		fi
+
+		if last_status_is_iommu_qos_unsupported; then
+			unsupported=$((unsupported + 1))
+			continue
+		fi
+
+		log "group $candidate write error:"
+		cat "$tmp_dir/group_$candidate.err" 2>/dev/null
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "IOMMU group $candidate failed QoS capability probing"
+	done
+
+	[ "$populated" -gt 0 ] || skip "no populated IOMMU group found"
+	if [ "$unsupported" -eq "$populated" ]; then
+		skip "no populated IOMMU group supports QoS ID programming"
+	fi
+
+	return 1
+}
+
+last_status_contains()
+{
+	pattern=$1
+
+	[ -f "$LAST_STATUS" ] || return 1
+	grep -qi "$pattern" "$LAST_STATUS"
+}
+
+last_status_is_iommu_qos_unsupported()
+{
+	[ -f "$LAST_STATUS" ] || return 1
+	grep -Eiq \
+		'IOMMU group .* QoS \(-95\)|IOMMU QoSID.*not supported|not supported' \
+		"$LAST_STATUS"
+}
+
+pick_missing_iommu_group()
+{
+	max=0
+
+	for path in "$IOMMU_GROUPS"/*; do
+		[ -d "$path" ] || continue
+		id=${path##*/}
+		case "$id" in
+		*[!0-9]*|'')
+			continue
+			;;
+		esac
+		if [ "$id" -gt "$max" ]; then
+			max=$id
+		fi
+	done
+
+	if [ "$max" -ge 2147483647 ]; then
+		skip "cannot choose a missing IOMMU group id"
+	fi
+
+	missing=$((max + 1))
+	while [ -e "$IOMMU_GROUPS/$missing" ]; do
+		if [ "$missing" -ge 2147483647 ]; then
+			skip "cannot choose a missing IOMMU group id"
+		fi
+		missing=$((missing + 1))
+	done
+}
+
+pick_qosid_file()
+{
+	for file in "$IOMMU_CLASS"/*/qosid; do
+		[ -e "$file" ] || continue
+		qosid_file=$file
+		return 0
+	done
+
+	return 1
+}
+
+parse_qosid()
+{
+	line=$1
+
+	case "$line" in
+	rcid=[0-9]*" "mcid=[0-9]*)
+		qosid_rcid=${line%% *}
+		qosid_rcid=${qosid_rcid#rcid=}
+		qosid_mcid=${line##* }
+		qosid_mcid=${qosid_mcid#mcid=}
+		;;
+	*)
+		return 1
+		;;
+	esac
+
+	case "$qosid_rcid" in
+	*[!0-9]*|'')
+		return 1
+		;;
+	esac
+	case "$qosid_mcid" in
+	*[!0-9]*|'')
+		return 1
+		;;
+	esac
+
+	return 0
+}
+
+test_malformed_group_rejected()
+{
+	if printf '%s\n' 'iommu_group:bad' > "$ROOT_DEVICES" \
+			2>"$tmp_dir/badtoken.err"; then
+		fail "malformed IOMMU group token was accepted"
+		return
+	fi
+
+	if last_status_contains "IOMMU group parsing error"; then
+		pass "malformed IOMMU group token is rejected with useful status"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "malformed IOMMU group rejection status is unclear"
+	fi
+}
+
+test_trailing_separator_rejected()
+{
+	if printf '%s\n' "iommu_group:$group," > "$ROOT_DEVICES" \
+			2>"$tmp_dir/trailing_separator.err"; then
+		fail "IOMMU group token with trailing separator was accepted"
+		return
+	fi
+
+	if last_status_contains "Device list parsing error"; then
+		pass "trailing device-list separator is rejected"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "trailing separator rejection status is unclear"
+	fi
+}
+
+test_missing_group_rejected()
+{
+	pick_missing_iommu_group
+
+	if printf '%s\n' "iommu_group:$missing" > "$ROOT_DEVICES" \
+			2>"$tmp_dir/missing.err"; then
+		fail "missing IOMMU group was accepted"
+		return
+	fi
+
+	if last_status_contains "No IOMMU group $missing"; then
+		pass "missing IOMMU group is rejected with useful status"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "missing IOMMU group rejection status is unclear"
+	fi
+}
+
+test_non_decimal_group_rejected()
+{
+	if printf '%s\n' "iommu_group:0x$group" > "$ROOT_DEVICES" \
+			2>"$tmp_dir/non_decimal.err"; then
+		fail "non-decimal IOMMU group ID was accepted"
+		return
+	fi
+
+	if last_status_contains "IOMMU group parsing error"; then
+		pass "non-decimal IOMMU group ID is rejected"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "non-decimal IOMMU group rejection status is unclear"
+	fi
+}
+
+test_tasks_rejects_iommu_token()
+{
+	if printf '%s\n' "iommu_group:$group" > "$RESCTRL/tasks" \
+			2>"$tmp_dir/tasks_token.err"; then
+		fail "tasks accepted an IOMMU group token"
+		return
+	fi
+
+	if last_status_contains "Task list parsing error pid iommu_group:$group"; then
+		pass "tasks rejects IOMMU group tokens"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "tasks rejection status is unclear"
+	fi
+}
+
+test_global_qosid_sysfs()
+{
+	if ! pick_qosid_file; then
+		skip_optional "no RISC-V IOMMU qosid sysfs file is available"
+		return
+	fi
+
+	if ! line=$(cat "$qosid_file" 2>"$tmp_dir/qosid_read.err"); then
+		log "read error: $(cat "$tmp_dir/qosid_read.err" 2>/dev/null)"
+		fail "failed to read IOMMU global qosid"
+		return
+	fi
+
+	if parse_qosid "$line"; then
+		pass "read IOMMU global qosid from sysfs"
+	else
+		log "qosid content: $line"
+		fail "IOMMU global qosid has unexpected format"
+		return
+	fi
+
+	if printf '%s\n' "$line" > "$qosid_file" \
+			2>"$tmp_dir/qosid_rewrite.err"; then
+		pass "IOMMU global qosid accepts readback format"
+	else
+		log "write error: $(cat "$tmp_dir/qosid_rewrite.err" 2>/dev/null)"
+		fail "failed to rewrite IOMMU global qosid readback"
+		return
+	fi
+
+	if printf '%s\n' "bad" > "$qosid_file" 2>"$tmp_dir/qosid_bad.err"; then
+		fail "IOMMU global qosid accepted malformed input"
+		return
+	fi
+	pass "IOMMU global qosid rejects malformed input"
+
+	if printf '%s\n' "rcid=0mcid=1" > "$qosid_file" \
+			2>"$tmp_dir/qosid_separator.err"; then
+		fail "IOMMU global qosid accepted missing token separator"
+		return
+	fi
+	pass "IOMMU global qosid requires a token separator"
+
+	if printf '%s\n' "rcid=4096 mcid=0" > "$qosid_file" \
+			2>"$tmp_dir/qosid_range.err"; then
+		fail "IOMMU global qosid accepted out-of-range RCID"
+		return
+	fi
+	pass "IOMMU global qosid rejects out-of-range RCID"
+
+	if printf '%s\n' "rcid=4294967296 mcid=0" > "$qosid_file" \
+			2>"$tmp_dir/qosid_overflow.err"; then
+		fail "IOMMU global qosid accepted overflowed RCID"
+		return
+	fi
+	pass "IOMMU global qosid rejects overflowed RCID"
+
+	if printf '%s\n' "rcid=$qosid_rcid mcid=$qosid_mcid" > "$qosid_file" \
+			2>"$tmp_dir/qosid_restore.err"; then
+		pass "IOMMU global qosid accepts current RCID/MCID"
+	else
+		log "write error: $(cat "$tmp_dir/qosid_restore.err" 2>/dev/null)"
+		fail "failed to write current IOMMU global qosid"
+		return
+	fi
+
+	if line=$(cat "$qosid_file" 2>/dev/null) &&
+			[ "$line" = "rcid=$qosid_rcid mcid=$qosid_mcid" ]; then
+		pass "IOMMU global qosid readback matches restored value"
+	else
+		log "qosid content: $line"
+		fail "IOMMU global qosid readback mismatch"
+	fi
+}
+
+test_valid_group_write()
+{
+	if printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+		2>"$tmp_dir/valid.err"; then
+		pass "write iommu_group:$group to root resctrl devices"
+	else
+		log "write error: $(cat "$tmp_dir/valid.err" 2>/dev/null)"
+		fail "valid IOMMU group write failed"
+		return
+	fi
+
+	if last_status_contains ^ok; then
+		pass "last_cmd_status reports ok"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "last_cmd_status is not ok after valid write"
+	fi
+
+	# The root control group represents the default QoS IDs. The kernel
+	# applies the reset but intentionally does not keep a software binding
+	# for default assignments, so the root devices file should stay empty.
+	if grep -qx "iommu_group:$group" "$ROOT_DEVICES"; then
+		log "devices content:"
+		cat "$ROOT_DEVICES" 2>/dev/null
+		fail "root devices unexpectedly shows default iommu_group:$group"
+	else
+		pass "root devices omits default iommu_group:$group"
+	fi
+
+}
+
+test_child_group_write()
+{
+	if printf '%s\n' "iommu_group:$group" > "$child_group/devices" \
+			2>"$tmp_dir/child.err"; then
+		pass "write iommu_group:$group to child resctrl group"
+	else
+		log "write error: $(cat "$tmp_dir/child.err" 2>/dev/null)"
+		fail "valid child IOMMU group write failed"
+		return
+	fi
+
+	if last_status_contains ^ok; then
+		pass "last_cmd_status reports ok after child group write"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "last_cmd_status is not ok after child group write"
+	fi
+
+	if grep -qx "iommu_group:$group" "$child_group/devices"; then
+		pass "child devices shows iommu_group:$group after set/get"
+	else
+		log "child devices content:"
+		cat "$child_group/devices" 2>/dev/null
+		fail "child devices does not show iommu_group:$group"
+	fi
+
+	if grep -qx "iommu_group:$group" "$ROOT_DEVICES"; then
+		log "root devices content:"
+		cat "$ROOT_DEVICES" 2>/dev/null
+		fail "root devices still shows iommu_group:$group after child move"
+	else
+		pass "root devices no longer shows iommu_group:$group after child move"
+	fi
+
+}
+
+test_child_pseudo_locksetup_rejected()
+{
+	[ -d "$child_group" ] || return
+
+	if [ ! -e "$child_group/mode" ]; then
+		skip_optional "resctrl mode file is not available"
+		return
+	fi
+
+	if printf '%s\n' "pseudo-locksetup" > "$child_group/mode" \
+			2>"$tmp_dir/pseudo_locksetup.err"; then
+		printf '%s\n' "shareable" > "$child_group/mode" 2>/dev/null || true
+		fail "pseudo-locksetup accepted a group with assigned devices"
+		return
+	fi
+
+	if last_status_contains \
+			"Move devices out before entering pseudo-locksetup group"; then
+		pass "pseudo-locksetup rejects group with assigned devices"
+	elif last_status_contains "Unknown or unsupported mode"; then
+		skip_optional "pseudo-locksetup mode is not supported"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "pseudo-locksetup rejection status is unclear"
+	fi
+}
+
+test_child_group_rmdir()
+{
+	[ -d "$child_group" ] || return
+
+	if rmdir "$child_group" 2>"$tmp_dir/rmdir_busy.err"; then
+		fail "rmdir child resctrl group with IOMMU group was accepted"
+		# The child group no longer exists, so cleanup() cannot reset this.
+		if ! printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+				2>"$tmp_dir/rmdir_unexpected_move_root.err"; then
+			log "move error after unexpected rmdir:"
+			cat "$tmp_dir/rmdir_unexpected_move_root.err" 2>/dev/null
+		fi
+		child_group=""
+		return
+	fi
+
+	if last_status_contains "Move devices out before removing group"; then
+		pass "rmdir rejects child group while IOMMU group is assigned"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "rmdir rejection status is unclear"
+	fi
+
+	if printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+			2>"$tmp_dir/rmdir_move_root.err"; then
+		pass "move iommu_group:$group back to root before rmdir"
+	else
+		log "move error: $(cat "$tmp_dir/rmdir_move_root.err" 2>/dev/null)"
+		fail "failed to move iommu_group:$group back to root"
+		return
+	fi
+
+	if rmdir "$child_group" 2>"$tmp_dir/rmdir.err"; then
+		pass "rmdir child resctrl group after moving IOMMU group out"
+	else
+		log "rmdir error: $(cat "$tmp_dir/rmdir.err" 2>/dev/null)"
+		fail "failed to rmdir child resctrl group after moving IOMMU group out"
+		return
+	fi
+	child_group=""
+
+	if last_status_contains ^ok; then
+		pass "last_cmd_status reports ok after child group rmdir"
+	else
+		log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+		fail "last_cmd_status is not ok after child group rmdir"
+	fi
+
+	if grep -qx "iommu_group:$group" "$ROOT_DEVICES"; then
+		log "root devices content:"
+		cat "$ROOT_DEVICES" 2>/dev/null
+		fail "root devices still shows iommu_group:$group after default move"
+	else
+		pass "root devices does not show iommu_group:$group after default move"
+	fi
+
+}
+
+trap cleanup EXIT
+need_root
+init_tmp_dir
+take_global_lock
+ensure_resctrl_mounted
+ensure_devices_file
+create_child_group
+if ! pick_iommu_group; then
+	log "FAIL: $fail_count failure(s), $pass_count pass(es)"
+	exit $KSFT_FAIL
+fi
+test_malformed_group_rejected
+test_trailing_separator_rejected
+test_missing_group_rejected
+test_non_decimal_group_rejected
+test_tasks_rejects_iommu_token
+test_global_qosid_sysfs
+test_valid_group_write
+test_child_group_write
+test_child_pseudo_locksetup_rejected
+test_child_group_rmdir
+
+if [ "$fail_count" -gt 0 ]; then
+	log "FAIL: $fail_count failure(s), $pass_count pass(es)"
+	exit $KSFT_FAIL
+fi
+
+if [ "$skip_count" -gt 0 ]; then
+	log "PASS: $pass_count checks passed, $skip_count optional check(s) skipped"
+else
+	log "PASS: $pass_count checks passed"
+fi
+exit $KSFT_PASS
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* Re: [PATCH v7 11/12] virt/steal_monitor: Act on steal time periodically and decide on preferred CPUs
From: Shrikanth Hegde @ 2026-07-14 13:08 UTC (permalink / raw)
  To: Yury Norov
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <alYsGQhAyknF4X5b@yury>

Hi Yury.

On 7/14/26 6:01 PM, Yury Norov wrote:
> On Mon, Jul 13, 2026 at 11:18:59AM +0530, Shrikanth Hegde wrote:
> 
> ...
> 
>>>> +requeue_work:
>>>> +	/* maintain design constructs always */
>>>> +	WARN_ON_ONCE(cpumask_empty(cpu_preferred_mask));
>>>> +	WARN_ON_ONCE(!cpumask_subset(cpu_preferred_mask, cpu_active_mask));
>>>
>>> cpu_read_lock here? And again, you should do something to restore
>>> integrity. WARN_ON is not enough. The simplest and safest thing you
>>> can do is to unload the driver. You definitely shouldn't schedule a
>>> new work against the broken cpu_preferred_mask.
>>
>> How about not requeue the work if it broken. Add a pr_err and return.
>> That makes driver pretty much nop until rmmod.
>>
>>          /* maintain design constructs always */
>>          if (cpumask_empty(cpu_preferred_mask)) {
>>                  pr_err("empty cpu_preferred_mask, stop steal_monitor work");
>>                  return;
>>          }
>>
>>          if (!cpumask_subset(cpu_preferred_mask, cpu_active_mask)) {
>>                  pr_err("preferred: %*pbl is not a subset of active: %*pbl, stop steal_monitor work\n",
>>                         pr_cpuamsk_args(cpu_preferred_mask), pr_cpuamsk_args(cpu_active_mask));
>> 		return;
>> 	}
>   
> That would keep broken preferred cpumask working in the kernel.
> 
> Imagine, it becomes empty on Friday night. That severely hurts
> the scheduling on the VMs. So, your approach requires the VM
> admin to come and manually rmmod the driver. This is not how
> he wanted to spend the evening, I guess. :)
> 
> So, ether unload the driver, or actively recover the invariant.

yes, i will restore it to the active mask and not requeue the work
and add a pr_err.

> 

Can we unload the driver after init completes?

This is happening in work function after module is loaded.
This invariant maybe broken after a few days driver was loaded.

> Thanks,
> Yury


^ permalink raw reply

* Re: What's cooking in zh_CN (Jul 2026)
From: Dongliang Mu @ 2026-07-14 13:14 UTC (permalink / raw)
  To: Weijie Yuan, Alex Shi
  Cc: Dongliang Mu, linux-doc, Alex Shi, Yanteng Si, Ben Guo, Gary Guo,
	Yan Zhu, Doehyun Baek, Jiandong Qiu
In-Reply-To: <alYomSIiGeUtQI1I@wyuan.org>


On 7/14/26 8:16 PM, Weijie Yuan wrote:
> I took a look about patchwork, it is basically like a mailing-list
> version issue tracker / pull-request dashboard.
>
> It can manage patch backlog, assign patches, record CI results, and mark
> patches as accepted, rejected, superseded, or under review. and record
> trailers. Well, pretty much like a mini version of Gerrit or SourceHut.
>
> But I think it's a little complicated for our needs, as it offers many
> features that we might not actually need, which involves learning costs
> and maintenance costs.
>
> Just now, while going through patchwork of other lists a bit, I didn't
> seem to find a way to filter out Chinese patches from the linux-doc
> list, if we were to use it.
>
> Dongliang, is the dashboard you want exactly like the one I sent in this
> email, which is read-only? Or perhaps wish there are other functions as
> well. For example, you can mark the patch series you have reviewed.


Actually I just need a list of patches which I need to review, which I 
reviewed before and be resubmitted, and which is applied.


>
> I can't find what IMA is btw.


https://ima.qq.com/

It is closed-source software from Tencent. This software cannot solve 
our problem.

I just put all the related linux-doc patches into the knowledge base, 
and ask in NLP about the activities in the linux-doc mailing list


Dongliang Mu


>
> [1] An exmaple of marking status of a patch in SourceHut
>      https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/70509


^ permalink raw reply

* Re: [PATCH v7 10/12] virt/steal_monitor: Provide functions for managing steal values
From: Shrikanth Hegde @ 2026-07-14 13:15 UTC (permalink / raw)
  To: Yury Norov
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <alYv7QGbsdLaMPH3@yury>

Hi Yury.

On 7/14/26 6:17 PM, Yury Norov wrote:
> On Mon, Jul 13, 2026 at 10:43:15AM +0530, Shrikanth Hegde wrote:
>> Hi Yury,
>>
>> On 7/11/26 1:30 AM, Yury Norov wrote:
> 
> ...
> 
>>> I think it should return status: if the function can't disable CPUs
>>> now, it would be a good hint for the caller that it would be useless
>>> to call it again.
>>>
>>> You may keep status in struct steal_monitor like:
>>>
>>>           if (steal_ratio > sm_core_ctx.high_threshold)  {
>>>                   if (sm_core_ctx->status | CANT_DECREASE) {
>>>                           pr_something();
>>>                   else
>>>                           sm_core_ctx->status = decrease();
>>>
>>> It would be a good hint to user that he has the driver misconfigured,
>>> and save the driver extra work. Same for increase().
>>>
>>
>> I thought about the extra work in function, but doesn't happen too often IMO.
> 
> 10ms is the shortest possible interval, 100 times in a second.

Even 10ms is too short i think. I am thinking to keep it 100ms. So
some meaningful number in terms of steal time accumulates.

10ms means only 1 tick on HZ=100 (which is a valid config even today).
100ms means at least 10 ticks.

That also reduces overhead concerns a bit as well.

> 
>> Also, it is specially not a misconfiguration for increase.
>> So i have kept it stateless for the below reason.
>>
>> - Under typical operation of this driver, user will enable it once.
>> - Once enabled, user will use their VM as usual.
>> - Majority of the time the steal time will be less.
>> - workload are bursty in nature.
> 
> Not necessarily.
> 
>> - Occasionally many VM will have high utilization and there will be steal time.
>>    This lasts for sometime.
>> - After workload completes, steal time goes low again.
>> - Cycle could repeat after extended low steal time duration.
>>
>> So when the steal time is low, though driver is enabled, doesn't mean it
>> is mis-configured. Just that there is contention and driver has nothing to
>> do. So, adding print there could easily consume the console.
>>
>> Similarly, there could situations, where decrease cannot happen though there is
>> high steal time, Though they are corner cases.
> 
> OK, that makes sense. If one needs to fine-tune the driver's
> thresholds, he'd monitor the preferred cpumasks statistics.
> 
>> For example,
>> - one small/few VMs have not enabled the driver. steal time could be high, but this
>>    VM has already down to one core. It can't decrease any further.
>> - Though all VMs have enabled the feature, but task running is not FAIR class. Though
>>    steal time shows high.
>>
>> Hitting only one core or all cores isn't necessarily a misconfiguration.
>> It is a possible behavior during severe contention or complete idle system.
>>
>> we need to continuously monitor steal time so that it can expand/contract the
>> based on current situation. If we stop calling the functions, natural expand/contract
>> will not happen. There is no interrupt which arrives due to high/low steal time where
>> we can kick start the driver again. Also it is a difficult ask for user to keep enabling
>> or disabling the driver.
>>
>> Since this can be called at minimal once in 10ms, I guess we can incur the additional
>> overheads to keep the logic simple and stateless. What do you think?
> 
> OK, it's your design.
> 

Ok.

I will keep it stateless for now.
If the need arises, we can bring in more towards that.

>> PS: I will remove that additional SM_DIR as you suggested in other reply. That keeps
>> it all stateless.


^ permalink raw reply

* Re: [RFC PATCH] firmware: scmi: Make SCMI arch independent
From: Ulf Hansson @ 2026-07-14 13:16 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: Michal Simek, linux-kernel, monstr, git, vincent.guittot,
	Souvik.Chakravarty, Alex Shi, Andy Shevchenko,
	Bartosz Golaszewski, Brian Masney, David Lechner, Dongliang Mu,
	Fabio Estevam, Frank Li, Guenter Roeck, Jonathan Cameron,
	Jonathan Corbet, Len Brown, Liam Girdwood, Linus Walleij,
	Lukasz Luba, Mark Brown, Michael Turquette, Nuno Sá,
	Pavel Machek, Peng Fan, Pengutronix Kernel Team, Philipp Zabel,
	Rafael J. Wysocki, Sascha Hauer, Shuah Khan, Stephen Boyd,
	Sudeep Holla, Ulf Hansson, Viresh Kumar, Yanteng Si, arm-scmi,
	open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	linux-arm-kernel, open list:COMMON CLK FRAMEWORK,
	open list:DOCUMENTATION, open list:GPIO SUBSYSTEM,
	open list:HARDWARE MONITORING,
	open list:IIO SUBSYSTEM AND DRIVERS, open list:ENERGY MODEL
In-Reply-To: <ak_Eu_eQKalPMwo-@pluto>

On Thu, Jul 9, 2026 at 5:56 PM Cristian Marussi
<cristian.marussi@arm.com> wrote:
>
> On Thu, Jul 09, 2026 at 03:27:00PM +0200, Michal Simek wrote:
> > On heterogenious systems like AMD/Xilinx FPGA there is a need to talk to
> > SCMI server from different architectures than ARM that's why remove
> > ARM/ARM64 Kconfig dependency with also remove ARM from description and
> > rename folder to reflect it.
>
> While I understand dropping the dependency on ARM (I always wanted to do
> that and test if it worked at all on some otehr archs with QEMU), I am
> not sure about the whole renaming party ? why is needed just for
> cosmetic reasons ? it is at the end an arm originated protocol so I dont
> see it as a being wrong to be named as such even though used by other
> archs...I have not really strong opinion on this...

This is certainly not my call to make, but I have to admit that I
fully agree with the above.

Usually we don't rename files that indicate a legacy platform name,
just because a new company/platform wants to make use of it.

>
> ... my concern really is ... wont this full scale rename simply generate
> a lot of un-needed churn for future fixes and/or backporting ?

We have some git magic to overcome most of these problems, but it's
not always straightforward, I think.

>
> Thanks,
> Cristian

Kind regards
Uffe

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox