Linux virtualization list
 help / color / mirror / Atom feed
* [PATCH v8 02/11] cpumask: Introduce cpu_preferred_mask
From: Shrikanth Hegde @ 2026-07-20 17:22 UTC (permalink / raw)
  To: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet
  Cc: sshegde, 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,
	jgross, virtualization
In-Reply-To: <20260720172250.2257582-1-sshegde@linux.ibm.com>

Provide cpu_preferred_mask infrastructure. Define get/set macros
which could be used to get/set CPU state as preferred.

PREFERRED_CPU config will be selected by the driver which handles
steal time values. It is going to set/clear preferred CPU state.
This driver will be called steal_governor and it is introduced in
subsequent patches. It periodically samples the steal time and
decides on preferred CPU state.

A CPU is set to preferred when it becomes active. Later it may be
marked as non-preferred depending on steal time values with
steal_governor being enabled.

Always maintain design construct of preferred is subset of active.
i.e. preferred ⊆ active ⊆ online ⊆ present ⊆ possible

With PREFERRED_CPU=n, ensure set_cpu_preferred is a nop and get
method returns the active state in that case.

Signed-off-by: Shrikanth Hegde <sshegde@linux.ibm.com>
---
 include/linux/cpumask.h | 24 ++++++++++++++++++++++++
 kernel/Kconfig.preempt  |  4 ++++
 kernel/cpu.c            |  6 ++++++
 kernel/sched/core.c     |  5 +++++
 4 files changed, 39 insertions(+)

diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h
index d3cda0544954..34d08a3d80e1 100644
--- a/include/linux/cpumask.h
+++ b/include/linux/cpumask.h
@@ -122,12 +122,20 @@ extern struct cpumask __cpu_enabled_mask;
 extern struct cpumask __cpu_present_mask;
 extern struct cpumask __cpu_active_mask;
 extern struct cpumask __cpu_dying_mask;
+
+#ifdef CONFIG_PREFERRED_CPU
+extern struct cpumask __cpu_preferred_mask;
+#else
+#define __cpu_preferred_mask __cpu_active_mask
+#endif
+
 #define cpu_possible_mask ((const struct cpumask *)&__cpu_possible_mask)
 #define cpu_online_mask   ((const struct cpumask *)&__cpu_online_mask)
 #define cpu_enabled_mask   ((const struct cpumask *)&__cpu_enabled_mask)
 #define cpu_present_mask  ((const struct cpumask *)&__cpu_present_mask)
 #define cpu_active_mask   ((const struct cpumask *)&__cpu_active_mask)
 #define cpu_dying_mask    ((const struct cpumask *)&__cpu_dying_mask)
+#define cpu_preferred_mask ((const struct cpumask *)&__cpu_preferred_mask)
 
 extern atomic_t __num_online_cpus;
 extern unsigned int __num_possible_cpus;
@@ -1164,6 +1172,12 @@ void init_cpu_possible(const struct cpumask *src);
 #define set_cpu_active(cpu, active)	assign_cpu((cpu), &__cpu_active_mask, (active))
 #define set_cpu_dying(cpu, dying)	assign_cpu((cpu), &__cpu_dying_mask, (dying))
 
+#ifdef CONFIG_PREFERRED_CPU
+#define set_cpu_preferred(cpu, preferred) assign_cpu((cpu), &__cpu_preferred_mask, (preferred))
+#else
+#define set_cpu_preferred(cpu, preferred) do { } while (0)
+#endif
+
 void set_cpu_online(unsigned int cpu, bool online);
 void set_cpu_possible(unsigned int cpu, bool possible);
 
@@ -1258,6 +1272,11 @@ static __always_inline bool cpu_dying(unsigned int cpu)
 	return cpumask_test_cpu(cpu, cpu_dying_mask);
 }
 
+static __always_inline bool cpu_preferred(unsigned int cpu)
+{
+	return cpumask_test_cpu(cpu, cpu_preferred_mask);
+}
+
 #else
 
 #define num_online_cpus()	1U
@@ -1296,6 +1315,11 @@ static __always_inline bool cpu_dying(unsigned int cpu)
 	return false;
 }
 
+static __always_inline bool cpu_preferred(unsigned int cpu)
+{
+	return cpu == 0;
+}
+
 #endif /* NR_CPUS > 1 */
 
 #define cpu_is_offline(cpu)	unlikely(!cpu_online(cpu))
diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt
index 88c594c6d7fc..de789b274ba3 100644
--- a/kernel/Kconfig.preempt
+++ b/kernel/Kconfig.preempt
@@ -192,3 +192,7 @@ config SCHED_CLASS_EXT
 	  For more information:
 	    Documentation/scheduler/sched-ext.rst
 	    https://github.com/sched-ext/scx
+
+config PREFERRED_CPU
+	bool
+	depends on SMP && PARAVIRT
diff --git a/kernel/cpu.c b/kernel/cpu.c
index b3c8553d7bd6..376d297a6292 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -3103,6 +3103,11 @@ EXPORT_SYMBOL(__cpu_dying_mask);
 atomic_t __num_online_cpus __read_mostly;
 EXPORT_SYMBOL(__num_online_cpus);
 
+#ifdef CONFIG_PREFERRED_CPU
+struct cpumask __cpu_preferred_mask __read_mostly;
+EXPORT_SYMBOL_GPL(__cpu_preferred_mask);
+#endif
+
 void init_cpu_present(const struct cpumask *src)
 {
 	cpumask_copy(&__cpu_present_mask, src);
@@ -3160,6 +3165,7 @@ void __init boot_cpu_init(void)
 	/* Mark the boot cpu "present", "online" etc for SMP and UP case */
 	set_cpu_online(cpu, true);
 	set_cpu_active(cpu, true);
+	set_cpu_preferred(cpu, true);
 	set_cpu_present(cpu, true);
 	set_cpu_possible(cpu, true);
 
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 2e7cde033a31..a45f7c308329 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -8690,6 +8690,9 @@ int sched_cpu_activate(unsigned int cpu)
 	 */
 	sched_set_rq_online(rq, cpu);
 
+	/* preferred is subset of active and follows its state */
+	set_cpu_preferred(cpu, true);
+
 	return 0;
 }
 
@@ -8703,6 +8706,8 @@ int sched_cpu_deactivate(unsigned int cpu)
 	if (ret)
 		return ret;
 
+	set_cpu_preferred(cpu, false);
+
 	/*
 	 * Remove CPU from nohz.idle_cpus_mask to prevent participating in
 	 * load balancing when not active
-- 
2.47.3


^ permalink raw reply related

* [PATCH v8 00/11] sched, steal_governor: Introduce cpu_preferred_mask and steal-driven vCPU backoff
From: Shrikanth Hegde @ 2026-07-20 17:22 UTC (permalink / raw)
  To: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet
  Cc: sshegde, 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,
	jgross, virtualization

This patch series represents the result of multiple iterations, 
redesigns and community feedback. What started as an arch-specific RFC
has evolved into a scheduler mechanism paired with a virtualization
driver.

Special thanks to Yury Norov for the rigorous reviews that greatly 
improved the series and to everyone who have provided their review
comments so far. Really appreciated! _/\_

I have put a detailed context around problem statement, design, best
practises and performance numbers below. This cover-letter is a good
starting point for anyone looking into this solution without the pain of
browsing through all the previous patches/videos.

Apologies in advance if any review comments are missed or any
missing implementation for the new driver. If so would be purely
accidental, not in any way intentional.

Background and Problem Statement
================================

As hardware scales, the density of physical CPUs (pCPUs) per server is
increasing across many architectures. On these massive systems, deploying
a single bare-metal OS for general workloads becomes increasingly difficult
to manage if not impossible. The natural shift is to deploy
Virtual Machines(VMs). For example, on IBM PowerPC architecture customers
frequently deploy Shared Processor LPARs (SPLPARs) to maximize hardware ROI.

Typical enterprise workloads are combination of bursty and long running;
their average CPU utilization is low, but they require high core counts
during peak transactions. To accommodate this, customers often
use CPU overcommit strategies i.e. configuring VMs with a large number
of virtual CPUs (vCPUs) while backing them with a smaller, shared pool
of physical CPUs (pCPUs). This achieves a high server consolidation
and excellent cost efficiency.

However, when multiple such VMs have high utilization simultaneously,
the shared pCPU pool becomes contended. The hypervisor is forced to preempt
one vCPU to run another to maintain fairness. It maybe schedule vCPU of same
VM or different VM.  If a vCPU is preempted while holding a lock or
irq disabled section, overall forward progress collapses. There are some
mitigation strategies such as yielding the vCPU to lock-holder, but they
don't cover all the cases. In addition there are hidden costs such as cache,
tlb misses, cost of vCPU preemption, host scheduling overheads etc.

Under heavy contention, the most effective mitigation strategy is for
the guests/VMs to voluntarily fold its workload onto a smaller subset
of its vCPUs. By demanding fewer pCPUs, the VMs reduces overall host 
contention, which decreases vCPU preemption and improves total throughput
for the system. 

Limitations of Existing Approaches
==================================

CPU Hotplug, Isolated cpusets, cpuset: 
- This is a heavy and administrative operation that requires topology rebuild.
  Crucially, it breaks userspace CPU affinities. 

Explicit task affinity:
- Very difficult to manage for the users, if not impossible.

We need a fast, co-operative backoff mechanism inside the kernel that can
dynamically react to contention without violating user/task affinity
contracts. Since reacting to the contention is agnostic to the user
it cannot violate user affinity contracts.

When there is high contention, fold the workload and use limited vCPUs
and when there is no contention, use all the vCPUs again. This natural
expansion/contraction gives the best possible performance to the users
based on the underlying contention.

Proposed Architecture
=====================

Current design is built on basis that contention is effectively
quantified by steal time as seen in guest kernel.
Steal time is already a well established construct today in
para-virtualization world.  All major archs support this feature.
It is indication of the contention of physical CPU. It scales according
to the amount of contention. Today it is used by administrative users
for changing the VM configurations. During high contention the steal
time shows up in each guest based on its configuration. The proposed
solution works well when all VMs honor the hint and work in co-operative
manner. Note there is still no inter-guest communication to achieve this
co-operation. Read the section on best practises on how to get the
best out of this solution.

This series introduces a dynamic vCPU backoff mechanism.
It is separated into a core scheduler mechanism and a loadable
virtualization policy module.

Layer A: The Scheduler Mechanism (preferred CPUs)
=================================================

Series introduces a new CPU state called preferred. It indicates that
vCPU can be safely used and using that vCPU won't increase contention
for underlying physical CPUs. This state info is made available via
cpu_preferred_mask, which is strictly maintained as a subset of
cpu_active_mask.

The scheduler uses this mask as a hint to fold workloads onto preferred
CPUs using a few mechanisms.

1, Wakeup: is_cpu_allowed() checks if CPU is preferred. If not calls
   select_fallback_rq, which selects a preferred CPU if tasks's affinity
   permits.

2. The Tick (Push): During sched_tick(), if the current CPU is non-preferred,
   the scheduler actively pushes the running task onto a preferred CPU
   using a stopper thread. 

3. Load Balance: sched_balance_rq restricts its domain span to
   cpu_preferred_mask, preventing tasks from being pulled toward
   non-preferred CPUs.

Design Constraint: The scheduler strictly respects user affinities.
If a task is pinned exclusively to non-preferred CPUs, it will remain there.
The kernel will not break user/task affinity contracts.

Layer B: The Policy Engine (virt/steal_governor)
================================================

The core scheduler should not dictate virtualization policy.
Therefore, the policy is isolated into a new driver: steal_governor.
(Can be selected by CONFIG_STEAL_GOVERNOR)
This module latches onto that concept that contention is quantified by
steal time. It periodically samples the steal time values across the
system and depending on high/low steal values, takes appropriate action.

When it sees high steal times, i.e. steal time exceeds high_threshold
(default 5%), driver reduces the preferred CPUs by 1 core. 
When it sees Low Steal Times, i.e.  steal time drops below low_threshold
(default 2%), driver increases the preferred CPUs by 1 core.

This creates a dynamic, self-maintained stepwise loop. The guest automatically
shrinks its pCPU footprint when the host is saturated, and expands it when
the noise clears while requiring zero cross-VM communication.

Policy Design Constraints:
- Ensure at least one core is kept as preferred.
- Ensure preferred is always subset of active.

Best Practises
==============
1. Ensure all the VM run kernel which has the patches.

2. Keep CONFIG_STEAL_GOVERNOR=m. Build it as module, but don't load it by
   default. When the administrative user enables it in one VM, he/she
   will likely enable it in all VMs. Also module parameters can
   only be changed at module load. Having it as module also allows one
   to disable it to remove additional overhead it brings.

3. Keep the interval_ms=500 to 5000. I.e. between 500ms to 5second.
   Though parameters allows slightly higher range. 

4. Fine tune low and high threshold depending on your platform for best
   results. Even where is no contention, very small steal values
   might show up. So it might be better to keep low threshold higher
   than 0.

Baseline and Revision History
==============================

tip/sched/core at 
commit: '04998aa54848 ("sched/eevdf: Delayed dequeue task can't preempt")'

For a detailed talk on the problem and discussion on this issue, one can also
refer to the OSPM26 talk[1]. 

[1]: https://youtu.be/adxUKFPlOp0
[2]: https://www.ibm.com/support/pages/ibm-power-virtualization-best-practices-guide
[3]: https://www.ibm.com/docs/en/linux-on-systems?topic=bad-daytrader


v7->v8:
- Rename to STEAL_GOVERNOR from STEAL_MONITOR.
- Remove additional defaults.c and move it to core.c (Yury Norov)
- Remove SM_DIR gating for direction control. (Yury Norov)
- Enforce design constraint and restore the state if not met (Yury
  Norov)
- Drop nohz_full tick enable patch.
- Move Kconfig patch as the last patch for enablement. (Yury Norov)
- Use disable_delayed_work_sync to avoid race condition during
  module unload. (Yury Norov)
- Add same kconfig dependency and fail to compile the driver (Yury Norov)
- Make low < high comparison during module init instead as they
  are dependent parameters (Sashiko)
- Update sysfs file helper section (Yury Norov)
- Make preferred sysfs file available only with CONFIG_PREFERRED_CPU=y
  (Yury Norov)
- A few documentation and comments fixes. (Randy Dunlap)
- Fix possible race in sched_push_current_non_preferred_cpu (Yury Norov)
- Move is_migration_disabled check just before actual migration.
- Make 100ms as minimal interval_ms from 10ms.
- Make helper functions static and remove from header file as there
  are no other callers.
- Collapse helper functions and periodic work into one patch.

Short summary on previous versions:
v6->v7:
- Consolidate new driver code to 4-5 patches.
- deffer the arch specific interface.
- Use possible CPUs instead of active for steal value calculations.
- Simplify is_cpu_allowed.
- Make module parameters fixed at module load
- Define CONFIG_STEAL_MONITOR and Make it select CONFIG_PREFERRED_CPU

v5->v6:
- Drop the optimization of caching the preferred state
  in select_fallback_rq
- Drop wakeup patch

v4->v5:
- Move the computation of steal time and decide on preferred CPU state
  to a driver. i.e new driver called STEAL_MONITOR

v3->v4:
- Make preferred subset of active instead of online. 
- Dropped RT patch and Defer sched_ext. Support only FAIR class.

v2->v3:
- Introduce a new config CONFIG_PREFERRED_CPU

v1->v2:
- A new name - Preferred CPUs and cpu_preferred_mask
- Arch independent code. Everything happens in scheduler.
- Steal time computation is gated with sched feature STEAL_MONITOR

RFC v3-> RFC v4:
- Introduced computation of steal time in arch/powerpc.

RFC PATCH v1:
- push task mechanism.
- No steal time computation. Manual sysfs hint for preferred CPUs 

v1: https://lore.kernel.org/all/236f4925-dd3c-41ef-be04-47708c9ce129@linux.ibm.com/
v2: https://lore.kernel.org/all/20260407191950.643549-1-sshegde@linux.ibm.com/#t
v3: https://lore.kernel.org/all/20260514152204.481115-1-sshegde@linux.ibm.com/#r
v4: https://lore.kernel.org/all/20260617174139.155540-1-sshegde@linux.ibm.com/#t
v5: https://lore.kernel.org/all/20260625124648.802832-1-sshegde@linux.ibm.com/
v6: https://lore.kernel.org/all/20260701141654.500125-1-sshegde@linux.ibm.com/#t
v7: https://lore.kernel.org/all/20260709215648.1246821-1-sshegde@linux.ibm.com/
Even earlier version:
https://lore.kernel.org/all/236f4925-dd3c-41ef-be04-47708c9ce129@linux.ibm.com/ 

========================================
Performance Numbers (powerpc, x86, s390)
========================================

PowerPC:
===================
VM1: 60VP/30EC and VM2: 30VP/20EC
Shared physical CPU pool size: 50 Cores. Each core is SMT8.
(VP - Virtual Core, EC - Entitles Core) -  PowerVM terminologies of SPLPAR[2]

Default parameter values: 1000ms, 200 low threshold, 500 high threshold
Both the VMs are running the same workload. Total throughput/time of VM1+VM2
is being mentioned in all cases.

Hackbench
              baseline    steal_governor        steal_governor
                             disabled               enabled
======================================================================

10 groups        5.20   |    5.40 (-3.85%)  |     4.65 (+10.58%)
20 groups       11.39   |   12.01 (-5.44%)  |     7.09 (+37.75%)
40 groups       20.32   |   19.80 (+2.56%)  |    11.31 (+44.34%)
10 groups(-p)    2.37   |    2.26 (+4.64%)  |     2.06 (+13.08%)
20 groups(-p)    3.34   |    3.28 (+1.80%)  |     3.20 (+4.19%)
40 groups(-p)    4.46   |    4.83 (-8.30%)  |     4.26 (+4.48%)
Remarks: Net improvement with steal_governor specially high load points.

schbench ( -L -n 0 -r 30 -s 0)
              baseline    steal_governor          steal_governor
                             disabled                 enabled
======================================================================
-m 1 -t 128     2475162 |    2621246 (+5.90%)  |     2527299 (+2.11%)
-m 1 -t 256     1467350 |    1470032 (+0.18%)  |      1492372 (+1.71%)
-m 1 -t 512     1408813 |    1454687 (+3.26%)  |      1437605 (+2.04%)
Remarks: Effectively means no-improvements or regressions

kernbench	baseline    steal_governor     steal_governor
(elapsed time)	               disabled            enabled
======================================================================
-j nr_cpus	231      |      235 (-1.7%) |    199 (+14%)
Remarks: Net improvement in elapsed time.

Daytrader - A real life work which is a proxy for trading based
on db2[3]
              baseline      steal_governor   steal_governor
                              disabled          enabled
======================================================================
Load@30%	1x	|	0.96x	|	 1.53x			
Load@60%	1x	|	0.94x	|	 1.41x
Remarks: Good improvement seen at different load points.
When there is no steal time (such as dedicated LPAR, or only VM2
is running) throughput was same with steal_governor enabled/disabled
which indicates minimal overhead of steal_governor. 


Data from x86,s390 KVM which Ilya Leoshkevich carried out during OSPM26
time. *This was based on v2*. Idea is still the name, numbers are
expected to be better in v8 as some of the overhead has been removed.
Note: Other variations of the benchmark shows no observable
difference.

x86:
====
cascade-lake: 32 threads = 16 cores
Benchmark      #VMs    #CPUs/VM  ΔRPS     (%std)
===============================================
hackbench         8          16  90.73% ± 9.97%
hackbench         4          24  52.67% ± 7.43%
hackbench         4          16  37.96% ± 11.19%
hackbench         4          32  37.82% ± 4.38%
hackbench        12           8  36.90% ± 4.74%
hackbench         8           8  35.30% ± 3.61%
pgbench          16           4  31.77% ± 2.44%
hackbench         2          24  25.85% ± 8.63%
hackbench        16           8  24.87% ± 3.46%
pgbench          16           8  21.83% ± 2.20%
pgbench          12           8  21.35% ± 2.15%
pgbench           8           8  18.46% ± 1.01%
hackbench         2          32  15.56% ± 4.53%
pgbench          12           4  14.28% ± 2.04%
hackbench        16           4  14.07% ± 2.90%
hackbench        12           4  9.60% ± 3.49%
[...]
pgbench           4           8  -1.16% ± 3.60%
hackbench         4           4  -1.80% ± 9.55%
sysbench         12           4  -2.19% ± 0.78%
pgbench           4          24  -2.43% ± 4.38%
pgbench           4          32  -3.21% ± 0.79%
sysbench         16           4  -3.22% ± 1.09%

S390:
=====
z16: 16 threads = 8 cores (SMT-2)
Benchmark      #VMs    #CPUs/VM  ΔRPS    (std%)
===============================================
pgbench           2           8  73.50% ± 35.91%
pgbench          16           4  61.30% ± 4.09%
hackbench        16           4  54.11% ± 4.38%
hackbench        12           4  36.34% ± 4.63%
pgbench          12           4  34.83% ± 2.57%
hackbench         8           4  29.75% ± 5.86%
hackbench         8           8  25.98% ± 5.09%
pgbench           2           4  23.31% ± 33.44%
pgbench           2          16  19.95% ± 17.12%
hackbench         4           8  19.43% ± 9.33%
pgbench           8           4  19.32% ± 4.50%
[...]
schbench          8           8  -0.79% ± 0.33%
sysbench          8           8  -0.81% ± 0.39%
hackbench         4          16  -1.11% ± 5.82%
sysbench          8           4  -1.62% ± 0.49%
sysbench         16           4  -2.70% ± 0.58%
schbench         16           4  -2.73% ± 0.91%
sysbench         12           4  -2.91% ± 0.61%
hackbench         2          24  -4.99% ± 3.31%

Summary:
- Many improvement across archs specially with real life workloads.
- No major regressions observed.
- Overhead of steal_governor looks minimal when there is no steal time.
- Overhead when STEAL_GOVERNOR=n is negligible.

Testing and Validation
======================

Apart from performance, To ensure the robustness of the preferred
CPU masking and push mechanisms, the following scenarios were tested:
- CPU Hotplug: bringing CPUs up/down change the preferred mask
  accordingly under no-contention and contention.
- Housekeeping cores: Verified with different combinations of
  nohz_full=<beginning, middle, end set of CPUs> to ensure that
  policy engine restricts to first housekeeping core in extreme cases.
- User Affinity: Confirmed that tasks explicitly pinned to non-preferred
  CPUs via taskset remain on their assigned CPUs.
- Affine Move: Confirmed the affinity move using "taskset -cp" happens
  on all combinations of non-preferred, non-preferred under contention.
- Affinity and hotplug: It works as expected. I.e affinity gets
  reset if all the CPUs of p->cpus_ptr go offline even if they are
  non-preferred CPUs.
- Extreme load and running threads: for example 4800 stress-ng threads
  on 480 CPU system and it still packs to preferred CPUs.

Known Limitations & Future Work
===============================

To keep this initial implementation clean and minimal, a few optimizations
have been deferred:

- Push all tasks on rq: Currently, the stopper thread only pushes the current
  running task off a non-preferred CPU. Future optimizations may look into
  migrating all queued tasks on that runqueue.

- Sched Classes: This feature currently only works for the FAIR
  class. Real-time (RT) and sched_ext classes are deferred for now,
  as there is no need for it.

- Arch specific hints from HW and framework for it as been deferred to
  the future.

- NUMA Splicing: The steal_governor currently removes last active core
  based on CPU number. It does not yet do complex NUMA-aware splicing,
  expecting that CPUs are spread out uniformly across nodes in
  most cases.

Shrikanth Hegde (11):
  sched/docs: Document cpu_preferred_mask and Preferred CPU concept
  cpumask: Introduce cpu_preferred_mask
  sysfs: Add preferred CPU file
  sched/core: Try to use a preferred CPU in is_cpu_allowed
  sched/fair: Load balance only among preferred CPUs
  sched/core: Push current task from non preferred CPU
  sched/debug: Add migration stats due to non preferred CPUs
  virt: Introduce steal governor driver
  virt/steal_governor: Add control knobs for handling steal values
  virt/steal_governor: Implement steal_governor policy loop
  virt/steal_governor: Enable the driver

 .../ABI/testing/sysfs-devices-system-cpu      |  14 +
 Documentation/driver-api/index.rst            |   1 +
 Documentation/driver-api/steal-governor.rst   | 117 ++++++++
 Documentation/scheduler/sched-arch.rst        |  58 ++++
 MAINTAINERS                                   |   9 +
 drivers/base/cpu.c                            |  12 +
 drivers/virt/Kconfig                          |   2 +
 drivers/virt/Makefile                         |   1 +
 drivers/virt/steal_governor/Kconfig           |  18 ++
 drivers/virt/steal_governor/Makefile          |   6 +
 drivers/virt/steal_governor/core.c            | 277 ++++++++++++++++++
 drivers/virt/steal_governor/core.h            |  30 ++
 include/linux/cpumask.h                       |  24 ++
 include/linux/sched.h                         |   1 +
 kernel/Kconfig.preempt                        |   4 +
 kernel/cpu.c                                  |   6 +
 kernel/sched/core.c                           | 100 ++++++-
 kernel/sched/debug.c                          |   1 +
 kernel/sched/fair.c                           |  11 +-
 kernel/sched/sched.h                          |  20 ++
 20 files changed, 704 insertions(+), 8 deletions(-)
 create mode 100644 Documentation/driver-api/steal-governor.rst
 create mode 100644 drivers/virt/steal_governor/Kconfig
 create mode 100644 drivers/virt/steal_governor/Makefile
 create mode 100644 drivers/virt/steal_governor/core.c
 create mode 100644 drivers/virt/steal_governor/core.h

-- 
2.47.3


^ permalink raw reply

* [PATCH v8 01/11] sched/docs: Document cpu_preferred_mask and Preferred CPU concept
From: Shrikanth Hegde @ 2026-07-20 17:22 UTC (permalink / raw)
  To: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet
  Cc: sshegde, 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,
	jgross, virtualization, kernel test robot
In-Reply-To: <20260720172250.2257582-1-sshegde@linux.ibm.com>

Add documentation for new cpumask called cpu_preferred_mask. This could
help users in understanding what this mask is and the concept behind it.

Document how to enable it and implementation aspects of it.

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202606180717.yNM0yb41-lkp@intel.com/
Signed-off-by: Shrikanth Hegde <sshegde@linux.ibm.com>
---
 Documentation/scheduler/sched-arch.rst | 58 ++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/Documentation/scheduler/sched-arch.rst b/Documentation/scheduler/sched-arch.rst
index ed07efea7d02..1ce0f92bc3f2 100644
--- a/Documentation/scheduler/sched-arch.rst
+++ b/Documentation/scheduler/sched-arch.rst
@@ -62,6 +62,64 @@ Your cpu_idle routines need to obey the following rules:
 arch/x86/kernel/process.c has examples of both polling and
 sleeping idle functions.
 
+Preferred CPUs
+==============
+
+In virtualised environments it is possible to overcommit CPU resources. i.e.
+the sum of virtual CPUs (vCPUs) of all VMs is greater than number of physical
+CPUs (pCPUs). Under such conditions when all or many VMs have high utilization,
+hypervisor won't be able to satisfy the CPU requirement and has to context
+switch within or across VMs. The hypervisor needs to preempt one vCPU to run
+another. This is called vCPU preemption. This is more expensive compared to
+task context switch within a vCPU.
+
+In such cases it is better that combined vCPU ask from all VMs is reduced
+by not using some of the vCPUs in each VM. vCPUs where workload can be safely
+scheduled which won't increase any contention for pCPU are called as
+"Preferred CPUs".
+
+Main design construct is preferred CPUs are always a subset of active CPUs.
+In most cases preferred CPUs will be same as active CPUs, when there is pCPU
+contention, Preferred CPUs will reduce based on the amount of steal time.
+When the pCPU contention goes away as indicated by steal time, Preferred CPUs
+will become same as active CPUs again. This is done by loading the
+steal_governor driver available at drivers/virt/steal_governor.
+
+For scheduling decisions such as wakeup, pushing the task etc, needs this
+CPU state info. This is maintained in cpu_preferred_mask.
+vCPUs which are not in cpu_preferred_mask should be treated as vCPUs which
+should not be used at this moment provided it doesn't break user affinity.
+
+This is achieved by:
+
+1. Selecting a preferred CPU at wakeup using fallback mechanism.
+2. Push the task away from non-preferred CPU at tick.
+3. Only select preferred CPUs for load balance.
+
+/sys/devices/system/cpu/preferred prints the current cpu_preferred_mask in
+cpulist format.
+
+Notes:
+
+1. This feature is available under CONFIG_PREFERRED_CPU. It is selected
+   by steal_governor driver (CONFIG_STEAL_GOVERNOR). On enabling the
+   driver, CPU preferred state can change based on steal time. Without that
+   driver, preferred CPUs is same as active CPUs.
+
+2. This feature works for FAIR class only.
+
+3. A task pinned, which can't be moved to preferred CPUs will continue
+   to run based on its affinity. But no load balancing happens.
+
+4. Decision to use/not use is driven by kernel. Hence it shouldn't
+   break user affinities. One of the main reasons why CPU hotplug
+   or Isolated cpuset partitions was not a solution.
+
+5. This feature works best only when all the VMs enable the feature as
+   it is a co-operative scheme. If a specific VM doesn't enable this feature
+   it may end up with more CPUs than others, still should lead to better
+   performance when seen from system view.
+   Users who enable this driver must ensure it is enabled in all VMs.
 
 Possible arch/ problems
 =======================
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH v4 6/8] media: virtio: Add virtio_media_driver
From: Brian Daniels @ 2026-07-20 15:48 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Michael S. Tsirkin, Mauro Carvalho Chehab, acourbot, adelva,
	aesteve, changyeon, daniel.almeida, eperezma, gnurou,
	gurchetansingh, hverkuil, jasowang, linux-kernel, linux-media,
	nicolas.dufresne, virtualization, xuanzhuo
In-Reply-To: <20260712085726.19198fda@foz.lan>

On Sun, Jul 12, 2026 at 2:57 AM Mauro Carvalho Chehab
<mchehab+huawei@kernel.org> wrote:
>
> On Thu, 25 Jun 2026 16:18:48 -0400
> Brian Daniels <briandaniels@google.com> wrote:
>
> > > > From: Alexandre Courbot <gnurou@gmail.com>
> > > >
> > > > virtio_media_driver.c provides the expected driver hooks, and support
> > > > for mmapping and polling.
> > > >
> > > > Signed-off-by: Alexandre Courbot <gnurou@gmail.com>
> > > > Co-developed-by: Brian Daniels <briandaniels@google.com>
> > > > Signed-off-by: Brian Daniels <briandaniels@google.com>
> > > > ---
> > > >  drivers/media/virtio/virtio_media_driver.c | 959 +++++++++++++++++++++
> > > >  1 file changed, 959 insertions(+)
> > > >  create mode 100644 drivers/media/virtio/virtio_media_driver.c
> > > >
> > > > diff --git a/drivers/media/virtio/virtio_media_driver.c b/drivers/media/virtio/virtio_media_driver.c
> > > > new file mode 100644
> > > > index 000000000..d6363c673
> > > > --- /dev/null
> > > > +++ b/drivers/media/virtio/virtio_media_driver.c
> > > > @@ -0,0 +1,959 @@
> > > > +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+
> > > > +
> > > > +/*
> > > > + * Virtio-media driver.
> > > > + *
> > > > + * Copyright (c) 2024-2025 Google LLC.
> > > > + */
> > > > +
> > > > +#include <linux/delay.h>
> > > > +#include <linux/device.h>
> > > > +#include <linux/dev_printk.h>
> > > > +#include <linux/mm.h>
> > > > +#include <linux/mutex.h>
> > > > +#include <linux/scatterlist.h>
> > > > +#include <linux/types.h>
> > > > +#include <linux/videodev2.h>
> > > > +#include <linux/vmalloc.h>
> > > > +#include <linux/wait.h>
> > > > +#include <linux/workqueue.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/moduleparam.h>
> > > > +#include <linux/virtio.h>
> > > > +#include <linux/virtio_config.h>
> > > > +#include <linux/virtio_ids.h>
> > > > +
> > > > +#include <media/frame_vector.h>
> > > > +#include <media/v4l2-dev.h>
> > > > +#include <media/v4l2-event.h>
> > > > +#include <media/videobuf2-memops.h>
> > > > +#include <media/v4l2-device.h>
> > > > +#include <media/v4l2-ioctl.h>
> > > > +
> > > > +#include "protocol.h"
> > > > +#include "session.h"
> > > > +#include "virtio_media.h"
> > > > +
> > > > +#define VIRTIO_MEDIA_NUM_EVENT_BUFS 16
> > > > +
> > > > +/* ID of the SHM region into which MMAP buffer will be mapped. */
> > > > +#define VIRTIO_MEDIA_SHM_MMAP 0
> > > > +
> > > > +/*
> > > > + * Name of the driver to expose to user-space.
> > > > + *
> > > > + * This is configurable because v4l2-compliance has workarounds specific to
> > > > + * some drivers. When proxying these directly from the host, this allows it to
> > > > + * apply them as needed.
> > > > + */
> > > > +char *virtio_media_driver_name;
> > > > +module_param_named(driver_name, virtio_media_driver_name, charp, 0660);
> > >
> > >
> > > Um. What? Not how it should be handled.
> >
> > I can remove this module param. I didn't end up using this when compliance testing.
> > Instead, I patched v4l-utils:
> > https://lore.kernel.org/all/20260528163448.4031965-1-briandaniels@google.com/
> >
> > Let me know if you think the v4l-utils patch is a good approach, otherwise let
> > me know how you'd prefer to address the v4l2-compliance driver-specific workounds
> > when they're being proxied with virtio-media.
>
> This kind of discussion should happen on a separate PR for v4l2-compliance,
> c/c to the proper developers and maintainers of it.

I've removed the driver name module parameter in v5. Let's continue
the compliance testing discussion on the v4l-utils patch:
https://lore.kernel.org/all/20260528163448.4031965-1-briandaniels@google.com/

> >
> > > > +
> > > > +/*
> > > > + * Whether USERPTR buffers are allowed.
> > > > + *
> > > > + * This is disabled by default as USERPTR buffers are dangerous, but the option
> > > > + * is left to enable them if desired.
> > > > + */
> > > > +bool virtio_media_allow_userptr;
> > > > +module_param_named(allow_userptr, virtio_media_allow_userptr, bool, 0660);
> > >
> > >
> > > is this kind of thing common?
>
> There is one old media device that has it (saa7134).
>
> >
> > To be honest, I don't really know. I'm also not that familiar with the USERPTR
> > issues. I see a few references online about their use being discouraged due to
> > possible race conditions, perhaps that was the original motivation for this
> > parameter (I'm not the original author of this driver).
> >
> > I'm open to alternatives, feel free to let me know if you have a preference.
>
> We tend to not implement USERPTR on newer drivers. I suggest you
> to place the logic with regards to V4L2_MEMORY_USERPTR on a separate
> patch for further discussions.

Will do in v5.

> >
> > > > +
> > > > +/**
> > > > + * virtio_media_session_alloc - Allocate a new session.
> > > > + * @vv: virtio-media device the session belongs to.
> > > > + * @id: ID of the session.
> > > > + * @nonblocking_dequeue: whether dequeuing of buffers should be blocking or
> > > > + * not.
> > > > + *
> > > > + * The ``id`` and ``list`` fields must still be set by the caller.
> > >
> > > still in what sense?
> >
> > Based on the code below, I'm not so sure that the caller is responsible for
> > setting these values. They seem to be initialized in the function.
> >
> > Perhaps Alexandre Courbot (the original author) would know more. Unless he
> > says otherwise though I'm inclined to remove this comment.

Removed in v5

> > > > + */
> > > > +static struct virtio_media_session *
> > > > +virtio_media_session_alloc(struct virtio_media *vv, u32 id,
> > > > +                    struct file *file)
> > > > +{
> > > > + struct virtio_media_session *session;
> > > > + int i;
> > > > + int ret;
> > > > +
> > > > + session = kzalloc_obj(*session, GFP_KERNEL);
> > > > + if (!session)
> > > > +         goto err_session;
> > > > +
> > > > + session->shadow_buf = kzalloc(VIRTIO_SHADOW_BUF_SIZE, GFP_KERNEL);
> > > > + if (!session->shadow_buf)
> > > > +         goto err_shadow_buf;
> > > > +
> > > > + ret = sg_alloc_table(&session->command_sgs, DESC_CHAIN_MAX_LEN,
> > > > +                      GFP_KERNEL);
> > > > + if (ret)
> > > > +         goto err_payload_sgs;
> > > > +
> > > > + session->id = id;
> > > > + session->nonblocking_dequeue = file->f_flags & O_NONBLOCK;
> > > > +
> > > > + INIT_LIST_HEAD(&session->list);
> > > > + v4l2_fh_init(&session->fh, &vv->video_dev);
> > > > + virtio_media_session_fh_add(session, file);
> > > > +
> > > > + for (i = 0; i <= VIRTIO_MEDIA_LAST_QUEUE; i++)
> > > > +         INIT_LIST_HEAD(&session->queues[i].pending_dqbufs);
> > > > + mutex_init(&session->queues_lock);
> > > > +
> > > > + init_waitqueue_head(&session->dqbuf_wait);
> > > > +
> > > > + mutex_lock(&vv->sessions_lock);
> > > > + list_add_tail(&session->list, &vv->sessions);
> > > > + mutex_unlock(&vv->sessions_lock);
> > > > +
> > > > + return session;
> > > > +
> > > > +err_payload_sgs:
> > > > + kfree(session->shadow_buf);
> > > > +err_shadow_buf:
> > > > + kfree(session);
> > > > +err_session:
> > > > + return ERR_PTR(-ENOMEM);
> > > > +}
> > > > +
> > > > +/**
> > > > + * virtio_media_session_free - Free all resources of a session.
> > > > + * @vv: virtio-media device the session belongs to.
> > > > + * @session: session to destroy.
> > > > + *
> > > > + * All the resources of @sesssion, as well as the backing memory of @session
> > > > + * itself, are freed.
> > >
> > > why @ here and `` above? And typo in the name.
> >
> > The `@` here was an attempt to follow the guide here for referencing function
> > parameters:
> > https://docs.kernel.org/doc-guide/kernel-doc.html#highlights-and-cross-references
>
> Yes. This is part of Linux Kernel kernel-doc markup: when referring to
> struct fields, you should use @field (or ``field`` if one wants to place an
> asterisk on it, like ``*field``).
>
> >
> > That being said, I don't believe this file is 100% consistent with that. I will
> > spend some time cleaning up the comments throughout this patch set to get them
> > consistent for v5. Thanks!
>
> Please use it on a consistent way along the driver.

I went through and cleaned up all of the files in v5 so they should
use kernel-doc consistently now.

> Thanks,
> Mauro

^ permalink raw reply

* [PATCH v2 15/15] Documentation/gpu: remove completed drm_simple_encoder_init() todo
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

All drm_simple_encoder_init() users have been removed, so drop the
completed todo item.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 Documentation/gpu/todo.rst | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index 14cf37590fc7..b7351467dc74 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -29,21 +29,6 @@ refactorings already and are an expert in the specific area
 Subsystem-wide refactorings
 ===========================
 
-Open-code drm_simple_encoder_init()
------------------------------------
-
-The helper drm_simple_encoder_init() was supposed to simplify encoder
-initialization. Instead it only added an intermediate layer between atomic
-modesetting and the DRM driver.
-
-The task here is to remove drm_simple_encoder_init(). Search for a driver
-that calls drm_simple_encoder_init() and inline the helper. The driver will
-also need its own instance of drm_encoder_funcs.
-
-Contact: Thomas Zimmermann, respective driver maintainer
-
-Level: Easy
-
 Replace struct drm_simple_display_pipe with regular atomic helpers
 ------------------------------------------------------------------
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 14/15] drm/drm_simple: remove deprecated drm_simple_encoder_init function
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

All driver users of drm_simple_encoder_init() have been converted to
drm_encoder_init(). Drop the helper and open-code its remaining internal
use in drm_simple_display_pipe_init() to prevent new users.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/drm_simple_kms_helper.c | 13 ++-----------
 include/drm/drm_simple_kms_helper.h     |  4 ----
 2 files changed, 2 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c
index 8e1d07b9f1e3..7878b9d7d524 100644
--- a/drivers/gpu/drm/drm_simple_kms_helper.c
+++ b/drivers/gpu/drm/drm_simple_kms_helper.c
@@ -20,16 +20,6 @@ static const struct drm_encoder_funcs drm_simple_encoder_funcs_cleanup = {
 	.destroy = drm_encoder_cleanup,
 };
 
-int drm_simple_encoder_init(struct drm_device *dev,
-			    struct drm_encoder *encoder,
-			    int encoder_type)
-{
-	return drm_encoder_init(dev, encoder,
-				&drm_simple_encoder_funcs_cleanup,
-				encoder_type, NULL);
-}
-EXPORT_SYMBOL(drm_simple_encoder_init);
-
 void *__drmm_simple_encoder_alloc(struct drm_device *dev, size_t size,
 				  size_t offset, int encoder_type)
 {
@@ -363,7 +353,8 @@ int drm_simple_display_pipe_init(struct drm_device *dev,
 		return ret;
 
 	encoder->possible_crtcs = drm_crtc_mask(crtc);
-	ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_NONE);
+	ret = drm_encoder_init(dev, encoder, &drm_simple_encoder_funcs_cleanup,
+			       DRM_MODE_ENCODER_NONE, NULL);
 	if (ret || !connector)
 		return ret;
 
diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h
index cb672ce0e856..c95f86ff355f 100644
--- a/include/drm/drm_simple_kms_helper.h
+++ b/include/drm/drm_simple_kms_helper.h
@@ -68,10 +68,6 @@ int drm_simple_display_pipe_init(struct drm_device *dev,
 			const uint64_t *format_modifiers,
 			struct drm_connector *connector);
 
-int drm_simple_encoder_init(struct drm_device *dev,
-			    struct drm_encoder *encoder,
-			    int encoder_type);
-
 void *__drmm_simple_encoder_alloc(struct drm_device *dev, size_t size,
 				  size_t offset, int encoder_type);
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 13/15] drm/meson: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/meson/meson_encoder_cvbs.c | 11 ++++++++---
 drivers/gpu/drm/meson/meson_encoder_dsi.c  | 11 ++++++++---
 drivers/gpu/drm/meson/meson_encoder_hdmi.c | 11 ++++++++---
 3 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c
index 22cacb1660c4..cdb84d2283f8 100644
--- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c
+++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c
@@ -17,8 +17,8 @@
 #include <drm/drm_bridge_connector.h>
 #include <drm/drm_device.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "meson_registers.h"
 #include "meson_vclk.h"
@@ -218,6 +218,10 @@ static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = {
 	.atomic_create_state = drm_atomic_helper_bridge_create_state,
 };
 
+static const struct drm_encoder_funcs meson_encoder_cvbs_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 int meson_encoder_cvbs_probe(struct meson_drm *priv)
 {
 	struct drm_device *drm = priv->drm;
@@ -257,8 +261,9 @@ int meson_encoder_cvbs_probe(struct meson_drm *priv)
 	meson_encoder_cvbs->priv = priv;
 
 	/* Encoder */
-	ret = drm_simple_encoder_init(priv->drm, &meson_encoder_cvbs->encoder,
-				      DRM_MODE_ENCODER_TVDAC);
+	ret = drm_encoder_init(priv->drm, &meson_encoder_cvbs->encoder,
+			       &meson_encoder_cvbs_funcs,
+			       DRM_MODE_ENCODER_TVDAC, NULL);
 	if (ret)
 		return dev_err_probe(priv->dev, ret,
 				     "Failed to init CVBS encoder\n");
diff --git a/drivers/gpu/drm/meson/meson_encoder_dsi.c b/drivers/gpu/drm/meson/meson_encoder_dsi.c
index 3e422b612f74..faa309cb97a6 100644
--- a/drivers/gpu/drm/meson/meson_encoder_dsi.c
+++ b/drivers/gpu/drm/meson/meson_encoder_dsi.c
@@ -10,10 +10,10 @@
 #include <linux/of_graph.h>
 
 #include <drm/drm_atomic_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
 #include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_probe_helper.h>
 
 #include "meson_drv.h"
@@ -99,6 +99,10 @@ static const struct drm_bridge_funcs meson_encoder_dsi_bridge_funcs = {
 	.atomic_create_state = drm_atomic_helper_bridge_create_state,
 };
 
+static const struct drm_encoder_funcs meson_encoder_dsi_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 int meson_encoder_dsi_probe(struct meson_drm *priv)
 {
 	struct meson_encoder_dsi *meson_encoder_dsi;
@@ -133,8 +137,9 @@ int meson_encoder_dsi_probe(struct meson_drm *priv)
 	meson_encoder_dsi->priv = priv;
 
 	/* Encoder */
-	ret = drm_simple_encoder_init(priv->drm, &meson_encoder_dsi->encoder,
-				      DRM_MODE_ENCODER_DSI);
+	ret = drm_encoder_init(priv->drm, &meson_encoder_dsi->encoder,
+			       &meson_encoder_dsi_funcs, DRM_MODE_ENCODER_DSI,
+			       NULL);
 	if (ret)
 		return dev_err_probe(priv->dev, ret,
 				     "Failed to init DSI encoder\n");
diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c
index 0c7a72cb514a..c4355c5cc340 100644
--- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c
+++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c
@@ -23,8 +23,8 @@
 #include <drm/drm_bridge_connector.h>
 #include <drm/drm_device.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include <linux/media-bus-format.h>
 #include <linux/videodev2.h>
@@ -369,6 +369,10 @@ static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = {
 	.atomic_create_state = drm_atomic_helper_bridge_create_state,
 };
 
+static const struct drm_encoder_funcs meson_encoder_hdmi_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 int meson_encoder_hdmi_probe(struct meson_drm *priv)
 {
 	struct meson_encoder_hdmi *meson_encoder_hdmi;
@@ -407,8 +411,9 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv)
 	meson_encoder_hdmi->priv = priv;
 
 	/* Encoder */
-	ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder,
-				      DRM_MODE_ENCODER_TMDS);
+	ret = drm_encoder_init(priv->drm, &meson_encoder_hdmi->encoder,
+			       &meson_encoder_hdmi_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
 	if (ret) {
 		dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n");
 		goto err_put_node;

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 12/15] drm/arm/komeda: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/arm/display/komeda/komeda_crtc.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c b/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c
index e8cb782a6f8e..719568d9f7c2 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c
@@ -11,9 +11,9 @@
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_print.h>
 #include <drm/drm_vblank.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_bridge.h>
 
 #include "komeda_dev.h"
@@ -635,6 +635,10 @@ static int komeda_attach_bridge(struct device *dev,
 	return err;
 }
 
+static const struct drm_encoder_funcs komeda_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static int komeda_crtc_add(struct komeda_kms_dev *kms,
 			   struct komeda_crtc *kcrtc)
 {
@@ -658,7 +662,8 @@ static int komeda_crtc_add(struct komeda_kms_dev *kms,
 	 * bridge
 	 */
 	kcrtc->encoder.possible_crtcs = drm_crtc_mask(crtc);
-	err = drm_simple_encoder_init(base, encoder, DRM_MODE_ENCODER_TMDS);
+	err = drm_encoder_init(base, encoder, &komeda_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
 	if (err)
 		return err;
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 11/15] drm/hisilicon/kirin: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
index 15042365dec0..62c5bd3277da 100644
--- a/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
+++ b/drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c
@@ -20,11 +20,11 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_mipi_dsi.h>
 #include <drm/drm_of.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "dw_dsi_reg.h"
 
@@ -687,6 +687,10 @@ static int dsi_encoder_atomic_check(struct drm_encoder *encoder,
 	return 0;
 }
 
+static const struct drm_encoder_funcs dw_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs dw_encoder_helper_funcs = {
 	.atomic_check	= dsi_encoder_atomic_check,
 	.mode_valid	= dsi_encoder_mode_valid,
@@ -708,7 +712,8 @@ static int dw_drm_encoder_init(struct device *dev,
 	}
 
 	encoder->possible_crtcs = crtc_mask;
-	ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
+	ret = drm_encoder_init(drm_dev, encoder, &dw_encoder_funcs,
+			       DRM_MODE_ENCODER_DSI, NULL);
 	if (ret) {
 		DRM_ERROR("failed to init dsi encoder\n");
 		return ret;

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 10/15] drm/renesas/shmobile: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c b/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c
index 1a2b9b68af6f..2dc477c7eda6 100644
--- a/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c
+++ b/drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c
@@ -21,6 +21,7 @@
 #include <drm/drm_bridge_connector.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_framebuffer.h>
@@ -29,7 +30,6 @@
 #include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_vblank.h>
 
 #include <video/videomode.h>
@@ -436,6 +436,10 @@ static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
 	.mode_fixup = shmob_drm_encoder_mode_fixup,
 };
 
+static const struct drm_encoder_funcs shmob_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 /* -----------------------------------------------------------------------------
  * Encoder
  */
@@ -448,8 +452,8 @@ int shmob_drm_encoder_create(struct shmob_drm_device *sdev)
 
 	encoder->possible_crtcs = 1;
 
-	ret = drm_simple_encoder_init(&sdev->ddev, encoder,
-				      DRM_MODE_ENCODER_DPI);
+	ret = drm_encoder_init(&sdev->ddev, encoder, &shmob_encoder_funcs,
+			       DRM_MODE_ENCODER_DPI, NULL);
 	if (ret < 0)
 		return ret;
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 09/15] drm/mediatek: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/mediatek/mtk_dsi.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c
index 3f3f56eed3f9..7cd136bd9605 100644
--- a/drivers/gpu/drm/mediatek/mtk_dsi.c
+++ b/drivers/gpu/drm/mediatek/mtk_dsi.c
@@ -21,12 +21,12 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_mipi_dsi.h>
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "mtk_ddp_comp.h"
 #include "mtk_disp_drv.h"
@@ -913,12 +913,16 @@ void mtk_dsi_ddp_stop(struct device *dev)
 	mtk_dsi_poweroff(dsi);
 }
 
+static const struct drm_encoder_funcs mtk_dsi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static int mtk_dsi_encoder_init(struct drm_device *drm, struct mtk_dsi *dsi)
 {
 	int ret;
 
-	ret = drm_simple_encoder_init(drm, &dsi->encoder,
-				      DRM_MODE_ENCODER_DSI);
+	ret = drm_encoder_init(drm, &dsi->encoder, &mtk_dsi_encoder_funcs,
+			       DRM_MODE_ENCODER_DSI, NULL);
 	if (ret) {
 		drm_err(drm, "Failed to encoder init to drm\n");
 		return ret;

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 08/15] drm/imx: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/imx/dc/dc-kms.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
index 0f8cfaf4c4d1..a9adcfc68b84 100644
--- a/drivers/gpu/drm/imx/dc/dc-kms.c
+++ b/drivers/gpu/drm/imx/dc/dc-kms.c
@@ -17,7 +17,6 @@
 #include <drm/drm_mode_config.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_vblank.h>
 
 #include "dc-de.h"
@@ -30,6 +29,10 @@ static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
 	.atomic_commit = drm_atomic_helper_commit,
 };
 
+static const struct drm_encoder_funcs dc_kms_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
 					int crtc_index)
 {
@@ -55,7 +58,8 @@ static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
 	}
 
 	encoder = &dc_drm->encoder[crtc_index];
-	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
+	ret = drm_encoder_init(drm, encoder, &dc_kms_encoder_funcs,
+			       DRM_MODE_ENCODER_NONE, NULL);
 	if (ret) {
 		dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
 			crtc->index, ret);

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 07/15] drm/tidss: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/tidss/tidss_encoder.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c
index 698f8d964ca0..10dbcc6cdf6a 100644
--- a/drivers/gpu/drm/tidss/tidss_encoder.c
+++ b/drivers/gpu/drm/tidss/tidss_encoder.c
@@ -9,11 +9,11 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_of.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "tidss_crtc.h"
 #include "tidss_drv.h"
@@ -81,6 +81,10 @@ static const struct drm_bridge_funcs tidss_bridge_funcs = {
 	.atomic_destroy_state		= drm_atomic_helper_bridge_destroy_state,
 };
 
+static const struct drm_encoder_funcs tidss_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 int tidss_encoder_create(struct tidss_device *tidss,
 			 struct drm_bridge *next_bridge,
 			 u32 encoder_type, u32 possible_crtcs)
@@ -95,8 +99,8 @@ int tidss_encoder_create(struct tidss_device *tidss,
 	if (IS_ERR(t_enc))
 		return PTR_ERR(t_enc);
 
-	ret = drm_simple_encoder_init(&tidss->ddev, &t_enc->encoder,
-				      encoder_type);
+	ret = drm_encoder_init(&tidss->ddev, &t_enc->encoder,
+			       &tidss_encoder_funcs, encoder_type, NULL);
 	if (ret)
 		return ret;
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 06/15] drm/virtio: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.
Also check the return value from drm_encoder_init() to avoid silent
failures.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/virtio/virtgpu_display.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c
index 44ffffec550f..67023d91d40b 100644
--- a/drivers/gpu/drm/virtio/virtgpu_display.c
+++ b/drivers/gpu/drm/virtio/virtgpu_display.c
@@ -28,11 +28,11 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_damage_helper.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_vblank.h>
 #include <drm/drm_vblank_helper.h>
 
@@ -232,6 +232,10 @@ static enum drm_mode_status virtio_gpu_conn_mode_valid(struct drm_connector *con
 	return MODE_BAD;
 }
 
+static const struct drm_encoder_funcs virtio_gpu_enc_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs virtio_gpu_enc_helper_funcs = {
 	.mode_set   = virtio_gpu_enc_mode_set,
 	.enable     = virtio_gpu_enc_enable,
@@ -306,7 +310,11 @@ static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index)
 	if (vgdev->has_edid)
 		drm_connector_attach_edid_property(connector);
 
-	drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL);
+	ret = drm_encoder_init(dev, encoder, &virtio_gpu_enc_funcs,
+			       DRM_MODE_ENCODER_VIRTUAL, NULL);
+	if (ret)
+		return ret;
+
 	drm_encoder_helper_add(encoder, &virtio_gpu_enc_helper_funcs);
 	encoder->possible_crtcs = 1 << index;
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 05/15] drm/kmb: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/kmb/kmb_dsi.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/kmb/kmb_dsi.c b/drivers/gpu/drm/kmb/kmb_dsi.c
index 59d0e856392f..13adba96bc78 100644
--- a/drivers/gpu/drm/kmb/kmb_dsi.c
+++ b/drivers/gpu/drm/kmb/kmb_dsi.c
@@ -14,8 +14,8 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_mipi_dsi.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 
@@ -1427,6 +1427,10 @@ struct kmb_dsi *kmb_dsi_init(struct platform_device *pdev)
 	return kmb_dsi;
 }
 
+static const struct drm_encoder_funcs kmb_dsi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 int kmb_dsi_encoder_init(struct drm_device *dev, struct kmb_dsi *kmb_dsi)
 {
 	struct drm_encoder *encoder;
@@ -1437,7 +1441,8 @@ int kmb_dsi_encoder_init(struct drm_device *dev, struct kmb_dsi *kmb_dsi)
 	encoder->possible_crtcs = 1;
 	encoder->possible_clones = 0;
 
-	ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_DSI);
+	ret = drm_encoder_init(dev, encoder, &kmb_dsi_encoder_funcs,
+			       DRM_MODE_ENCODER_DSI, NULL);
 	if (ret) {
 		dev_err(kmb_dsi->dev, "Failed to init encoder %d\n", ret);
 		return ret;

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 04/15] drm/fsl-dcu: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
index 84eff7519e32..c2b788bfa8f9 100644
--- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
+++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c
@@ -11,14 +11,18 @@
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "fsl_dcu_drm_drv.h"
 #include "fsl_tcon.h"
 
+static const struct drm_encoder_funcs fsl_dcu_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev,
 			       struct drm_crtc *crtc)
 {
@@ -31,8 +35,8 @@ int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev,
 	if (fsl_dev->tcon)
 		fsl_tcon_bypass_enable(fsl_dev->tcon);
 
-	ret = drm_simple_encoder_init(fsl_dev->drm, encoder,
-				      DRM_MODE_ENCODER_LVDS);
+	ret = drm_encoder_init(fsl_dev->drm, encoder, &fsl_dcu_encoder_funcs,
+			       DRM_MODE_ENCODER_LVDS, NULL);
 	if (ret < 0)
 		return ret;
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 03/15] drm/tegra: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.
Also check the return value from drm_encoder_init() to avoid silent
failures.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/tegra/dsi.c | 16 +++++++++++++---
 drivers/gpu/drm/tegra/rgb.c | 15 +++++++++++++--
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c
index e7fdd8c7ac12..3f818c195e9a 100644
--- a/drivers/gpu/drm/tegra/dsi.c
+++ b/drivers/gpu/drm/tegra/dsi.c
@@ -20,11 +20,11 @@
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_debugfs.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_file.h>
 #include <drm/drm_mipi_dsi.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "dc.h"
 #include "drm.h"
@@ -1055,6 +1055,10 @@ tegra_dsi_encoder_atomic_check(struct drm_encoder *encoder,
 	return err;
 }
 
+static const struct drm_encoder_funcs tegra_dsi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
 	.disable = tegra_dsi_encoder_disable,
 	.enable = tegra_dsi_encoder_enable,
@@ -1078,8 +1082,14 @@ static int tegra_dsi_init(struct host1x_client *client)
 					 &tegra_dsi_connector_helper_funcs);
 		dsi->output.connector.dpms = DRM_MODE_DPMS_OFF;
 
-		drm_simple_encoder_init(drm, &dsi->output.encoder,
-					DRM_MODE_ENCODER_DSI);
+		err = drm_encoder_init(drm, &dsi->output.encoder,
+				       &tegra_dsi_encoder_funcs,
+				       DRM_MODE_ENCODER_DSI, NULL);
+		if (err) {
+			drm_err(drm, "failed to initialize encoder: %d\n", err);
+			return err;
+		}
+
 		drm_encoder_helper_add(&dsi->output.encoder,
 				       &tegra_dsi_encoder_helper_funcs);
 
diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c
index e67fbb2362e6..bc1c93c7554c 100644
--- a/drivers/gpu/drm/tegra/rgb.c
+++ b/drivers/gpu/drm/tegra/rgb.c
@@ -9,7 +9,8 @@
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge_connector.h>
-#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_print.h>
 
 #include "drm.h"
 #include "dc.h"
@@ -194,6 +195,10 @@ tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
 	return err;
 }
 
+static const struct drm_encoder_funcs tegra_rgb_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs tegra_rgb_encoder_helper_funcs = {
 	.disable = tegra_rgb_encoder_disable,
 	.enable = tegra_rgb_encoder_enable,
@@ -305,7 +310,13 @@ int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc)
 	if (!dc->rgb)
 		return -ENODEV;
 
-	drm_simple_encoder_init(drm, &output->encoder, DRM_MODE_ENCODER_LVDS);
+	err = drm_encoder_init(drm, &output->encoder, &tegra_rgb_encoder_funcs,
+			       DRM_MODE_ENCODER_LVDS, NULL);
+	if (err) {
+		drm_err(drm, "failed to initialize encoder: %d\n", err);
+		return err;
+	}
+
 	drm_encoder_helper_add(&output->encoder,
 			       &tegra_rgb_encoder_helper_funcs);
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 02/15] drm/xlnx/zynqmp_dpsub: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.
Also check the return value from drm_encoder_init() to avoid silent
failures.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/xlnx/zynqmp_kms.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c
index d5f922450565..ac9197e026af 100644
--- a/drivers/gpu/drm/xlnx/zynqmp_kms.c
+++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c
@@ -29,8 +29,8 @@
 #include <drm/drm_managed.h>
 #include <drm/drm_mode_config.h>
 #include <drm/drm_plane.h>
+#include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_vblank.h>
 
 #include <linux/clk.h>
@@ -417,6 +417,10 @@ static const struct drm_driver zynqmp_dpsub_drm_driver = {
 	.minor				= 0,
 };
 
+static const struct drm_encoder_funcs zynqmp_dpsub_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub)
 {
 	struct drm_encoder *encoder = &dpsub->drm->encoder;
@@ -436,7 +440,13 @@ static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub)
 
 	/* Create the encoder and attach the bridge. */
 	encoder->possible_crtcs |= drm_crtc_mask(&dpsub->drm->crtc);
-	drm_simple_encoder_init(&dpsub->drm->dev, encoder, DRM_MODE_ENCODER_NONE);
+	ret = drm_encoder_init(&dpsub->drm->dev, encoder,
+			       &zynqmp_dpsub_encoder_funcs,
+			       DRM_MODE_ENCODER_NONE, NULL);
+	if (ret) {
+		drm_err(&dpsub->drm->dev, "failed to initialize encoder\n");
+		return ret;
+	}
 
 	ret = drm_bridge_attach(encoder, dpsub->bridge, NULL,
 				DRM_BRIDGE_ATTACH_NO_CONNECTOR);

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 01/15] drm/exynos: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva
In-Reply-To: <20260720-drm_simple_encoder_init-v2-0-5020b630668a@gmail.com>

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

Open-code drm_simple_encoder_init() by calling drm_encoder_init()
directly and providing driver-local drm_encoder_funcs.
Also check the return value from drm_encoder_init() to avoid silent
failures.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
 drivers/gpu/drm/exynos/exynos_dp.c       | 13 +++++++++++--
 drivers/gpu/drm/exynos/exynos_drm_dpi.c  | 13 +++++++++++--
 drivers/gpu/drm/exynos/exynos_drm_dsi.c  | 11 +++++++++--
 drivers/gpu/drm/exynos/exynos_drm_vidi.c | 13 +++++++++++--
 drivers/gpu/drm/exynos/exynos_hdmi.c     | 14 ++++++++++++--
 5 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c
index b80540328150..957382223956 100644
--- a/drivers/gpu/drm/exynos/exynos_dp.c
+++ b/drivers/gpu/drm/exynos/exynos_dp.c
@@ -24,11 +24,11 @@
 #include <drm/drm_bridge.h>
 #include <drm/drm_bridge_connector.h>
 #include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_of.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/exynos_drm.h>
 
 #include "exynos_drm_crtc.h"
@@ -79,6 +79,10 @@ static void exynos_dp_nop(struct drm_encoder *encoder)
 	/* do nothing */
 }
 
+static const struct drm_encoder_funcs exynos_dp_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = {
 	.mode_set = exynos_dp_mode_set,
 	.enable = exynos_dp_nop,
@@ -95,7 +99,12 @@ static int exynos_dp_bind(struct device *dev, struct device *master, void *data)
 
 	dp->drm_dev = drm_dev;
 
-	drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+	ret = drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		drm_err(drm_dev, "Failed to initialize encoder\n");
+		return ret;
+	}
 
 	drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs);
 
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dpi.c b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
index 0dc36df6ada3..de4a80158746 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dpi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dpi.c
@@ -12,10 +12,10 @@
 #include <linux/regulator/consumer.h>
 
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include <video/of_videomode.h>
 #include <video/videomode.h>
@@ -140,6 +140,10 @@ static void exynos_dpi_disable(struct drm_encoder *encoder)
 	}
 }
 
+static const struct drm_encoder_funcs exynos_dpi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs exynos_dpi_encoder_helper_funcs = {
 	.mode_set = exynos_dpi_mode_set,
 	.enable = exynos_dpi_enable,
@@ -194,7 +198,12 @@ int exynos_dpi_bind(struct drm_device *dev, struct drm_encoder *encoder)
 {
 	int ret;
 
-	drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS);
+	ret = drm_encoder_init(dev, encoder, &exynos_dpi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		drm_err(dev, "failed to create encoder ret = %d\n", ret);
+		return ret;
+	}
 
 	drm_encoder_helper_add(encoder, &exynos_dpi_encoder_helper_funcs);
 
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
index c4d098ab7863..6b7561ac9bb0 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c
@@ -13,7 +13,7 @@
 
 #include <drm/bridge/samsung-dsim.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_encoder.h>
 
 #include "exynos_drm_crtc.h"
 #include "exynos_drm_drv.h"
@@ -22,6 +22,10 @@ struct exynos_dsi {
 	struct drm_encoder encoder;
 };
 
+static const struct drm_encoder_funcs exynos_drm_dsi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static irqreturn_t exynos_dsi_te_irq_handler(struct samsung_dsim *dsim)
 {
 	struct exynos_dsi *dsi = dsim->priv;
@@ -79,7 +83,10 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, void *data
 	struct drm_device *drm_dev = data;
 	int ret;
 
-	drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+	ret = drm_encoder_init(drm_dev, encoder, &exynos_drm_dsi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret)
+		return ret;
 
 	ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
 	if (ret < 0)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_vidi.c b/drivers/gpu/drm/exynos/exynos_drm_vidi.c
index 67bbf9b8bc0e..077571eae9d6 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_vidi.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_vidi.c
@@ -13,10 +13,10 @@
 
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_vblank.h>
 #include <drm/exynos_drm.h>
 
@@ -403,6 +403,10 @@ static void exynos_vidi_disable(struct drm_encoder *encoder)
 {
 }
 
+static const struct drm_encoder_funcs exynos_vidi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
 static const struct drm_encoder_helper_funcs exynos_vidi_encoder_helper_funcs = {
 	.mode_set = exynos_vidi_mode_set,
 	.enable = exynos_vidi_enable,
@@ -445,7 +449,12 @@ static int vidi_bind(struct device *dev, struct device *master, void *data)
 		return PTR_ERR(ctx->crtc);
 	}
 
-	drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+	ret = drm_encoder_init(drm_dev, encoder, &exynos_vidi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		drm_err(drm_dev, "failed to initialize encoder ret = %d\n", ret);
+		return ret;
+	}
 
 	drm_encoder_helper_add(encoder, &exynos_vidi_encoder_helper_funcs);
 
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
index 09b2cabb236f..45aeda94b74d 100644
--- a/drivers/gpu/drm/exynos/exynos_hdmi.c
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
@@ -36,9 +36,9 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_bridge.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
 
 #include "exynos_drm_crtc.h"
 #include "regs-hdmi.h"
@@ -1575,6 +1575,11 @@ static void hdmi_disable(struct drm_encoder *encoder)
 	mutex_unlock(&hdata->mutex);
 }
 
+static const struct drm_encoder_funcs exynos_hdmi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+
 static const struct drm_encoder_helper_funcs exynos_hdmi_encoder_helper_funcs = {
 	.mode_fixup	= hdmi_mode_fixup,
 	.enable		= hdmi_enable,
@@ -1862,7 +1867,12 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 
 	hdata->phy_clk.enable = hdmiphy_clk_enable;
 
-	drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
+	ret = drm_encoder_init(drm_dev, encoder, &exynos_hdmi_encoder_funcs,
+			       DRM_MODE_ENCODER_TMDS, NULL);
+	if (ret) {
+		drm_err(drm_dev, "failed to initialize encoder ret = %d\n", ret);
+		return ret;
+	}
 
 	drm_encoder_helper_add(encoder, &exynos_hdmi_encoder_helper_funcs);
 

-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 00/15] drm/drm_simple: remove drm_simple_encoder_init
From: Diogo Silva @ 2026-07-20 15:40 UTC (permalink / raw)
  To: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Michal Simek, Thierry Reding,
	Mikko Perttunen, Jonathan Hunter, Stefan Agner, Alison Wang,
	Anitha Chrisanthus, David Airlie, Gerd Hoffmann, Dmitry Osipenko,
	Gurchetan Singh, Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Chun-Kuang Hu, Philipp Zabel, Matthias Brugger,
	AngeloGioacchino Del Regno, Geert Uytterhoeven, Xinliang Liu,
	Sumit Semwal, Yongqin Liu, John Stultz, Liviu Dudau,
	Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
	Jonathan Corbet, Shuah Khan
  Cc: dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc, Diogo Silva

The simple KMS helpers are deprecated because they only add an
intermediate layer between drivers and atomic modesetting.

This series open-codes all remaining drm_simple_encoder_init() users by
calling drm_encoder_init() directly and providing driver-local
drm_encoder_funcs where needed. After the driver conversions, the helper
is removed and the completed DRM todo item is dropped.

Signed-off-by: Diogo Silva <diogompaissilva@gmail.com>
---
Changes in v2:
- Change logging to consistently use drm_err()
- Link to v1: https://patch.msgid.link/20260719-drm_simple_encoder_init-v1-0-a78c509e3062@gmail.com

To: Jingoo Han <jingoohan1@gmail.com>
To: Inki Dae <inki.dae@samsung.com>
To: Seung-Woo Kim <sw0312.kim@samsung.com>
To: Kyungmin Park <kyungmin.park@samsung.com>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Krzysztof Kozlowski <krzk@kernel.org>
To: Peter Griffin <peter.griffin@linaro.org>
To: Alim Akhtar <alim.akhtar@samsung.com>
To: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
To: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: Michal Simek <michal.simek@amd.com>
To: Thierry Reding <thierry.reding@kernel.org>
To: Mikko Perttunen <mperttunen@nvidia.com>
To: Jonathan Hunter <jonathanh@nvidia.com>
To: Stefan Agner <stefan@agner.ch>
To: Alison Wang <alison.wang@nxp.com>
To: Anitha Chrisanthus <anitha.chrisanthus@intel.com>
To: David Airlie <airlied@redhat.com>
To: Gerd Hoffmann <kraxel@redhat.com>
To: Dmitry Osipenko <dmitry.osipenko@collabora.com>
To: Gurchetan Singh <gurchetansingh@chromium.org>
To: Chia-I Wu <olvaffe@gmail.com>
To: Jyri Sarha <jyri.sarha@iki.fi>
To: Liu Ying <victor.liu@nxp.com>
To: Frank Li <Frank.Li@nxp.com>
To: Sascha Hauer <s.hauer@pengutronix.de>
To: Pengutronix Kernel Team <kernel@pengutronix.de>
To: Fabio Estevam <festevam@gmail.com>
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
To: Geert Uytterhoeven <geert+renesas@glider.be>
To: Xinliang Liu <xinliang.liu@linaro.org>
To: Sumit Semwal <sumit.semwal@linaro.org>
To: Yongqin Liu <yongqin.liu@linaro.org>
To: John Stultz <jstultz@google.com>
To: Liviu Dudau <liviu.dudau@arm.com>
To: Neil Armstrong <neil.armstrong@linaro.org>
To: Kevin Hilman <khilman@baylibre.com>
To: Jerome Brunet <jbrunet@baylibre.com>
To: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-samsung-soc@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-tegra@vger.kernel.org
Cc: virtualization@lists.linux.dev
Cc: imx@lists.linux.dev
Cc: linux-mediatek@lists.infradead.org
Cc: linux-renesas-soc@vger.kernel.org
Cc: linux-amlogic@lists.infradead.org
Cc: linux-doc@vger.kernel.org

---
Diogo Silva (15):
      drm/exynos: remove dependency on DRM simple helpers
      drm/xlnx/zynqmp_dpsub: remove dependency on DRM simple helpers
      drm/tegra: remove dependency on DRM simple helpers
      drm/fsl-dcu: remove dependency on DRM simple helpers
      drm/kmb: remove dependency on DRM simple helpers
      drm/virtio: remove dependency on DRM simple helpers
      drm/tidss: remove dependency on DRM simple helpers
      drm/imx: remove dependency on DRM simple helpers
      drm/mediatek: remove dependency on DRM simple helpers
      drm/renesas/shmobile: remove dependency on DRM simple helpers
      drm/hisilicon/kirin: remove dependency on DRM simple helpers
      drm/arm/komeda: remove dependency on DRM simple helpers
      drm/meson: remove dependency on DRM simple helpers
      drm/drm_simple: remove deprecated drm_simple_encoder_init function
      Documentation/gpu: remove completed drm_simple_encoder_init() todo

 Documentation/gpu/todo.rst                        | 15 ---------------
 drivers/gpu/drm/arm/display/komeda/komeda_crtc.c  |  9 +++++++--
 drivers/gpu/drm/drm_simple_kms_helper.c           | 13 ++-----------
 drivers/gpu/drm/exynos/exynos_dp.c                | 13 +++++++++++--
 drivers/gpu/drm/exynos/exynos_drm_dpi.c           | 13 +++++++++++--
 drivers/gpu/drm/exynos/exynos_drm_dsi.c           | 11 +++++++++--
 drivers/gpu/drm/exynos/exynos_drm_vidi.c          | 13 +++++++++++--
 drivers/gpu/drm/exynos/exynos_hdmi.c              | 14 ++++++++++++--
 drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c         | 10 +++++++---
 drivers/gpu/drm/hisilicon/kirin/dw_drm_dsi.c      |  9 +++++++--
 drivers/gpu/drm/imx/dc/dc-kms.c                   |  8 ++++++--
 drivers/gpu/drm/kmb/kmb_dsi.c                     |  9 +++++++--
 drivers/gpu/drm/mediatek/mtk_dsi.c                | 10 +++++++---
 drivers/gpu/drm/meson/meson_encoder_cvbs.c        | 11 ++++++++---
 drivers/gpu/drm/meson/meson_encoder_dsi.c         | 11 ++++++++---
 drivers/gpu/drm/meson/meson_encoder_hdmi.c        | 11 ++++++++---
 drivers/gpu/drm/renesas/shmobile/shmob_drm_crtc.c | 10 +++++++---
 drivers/gpu/drm/tegra/dsi.c                       | 16 +++++++++++++---
 drivers/gpu/drm/tegra/rgb.c                       | 15 +++++++++++++--
 drivers/gpu/drm/tidss/tidss_encoder.c             | 10 +++++++---
 drivers/gpu/drm/virtio/virtgpu_display.c          | 12 ++++++++++--
 drivers/gpu/drm/xlnx/zynqmp_kms.c                 | 14 ++++++++++++--
 include/drm/drm_simple_kms_helper.h               |  4 ----
 23 files changed, 183 insertions(+), 78 deletions(-)
---
base-commit: e1113c37ba4f166e37dec74a729e6dfd8cba1687
change-id: 20260718-drm_simple_encoder_init-d069a4cc7f8b

Best regards,
--  
Diogo Silva <diogompaissilva@gmail.com>


^ permalink raw reply

* Re: [PATCH v4 2/8] media: virtio: Add virtio-media driver structs and function declarations
From: Brian Daniels @ 2026-07-20 15:30 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: Mauro Carvalho Chehab, acourbot, adelva, aesteve, changyeon,
	daniel.almeida, eperezma, gnurou, gurchetansingh, hverkuil,
	jasowang, linux-kernel, linux-media, nicolas.dufresne,
	virtualization, xuanzhuo
In-Reply-To: <20260718132340-mutt-send-email-mst@kernel.org>

On Sat, Jul 18, 2026 at 1:27 PM Michael S. Tsirkin <mst@redhat.com> wrote:
>
> On Fri, Jul 17, 2026 at 03:46:36PM -0400, Brian Daniels wrote:
> > On Mon, Jun 22, 2026 at 5:08 PM Michael S. Tsirkin <mst@redhat.com> wrote:
> > >
> > > On Mon, Jun 22, 2026 at 04:43:37PM -0400, Brian Daniels wrote:
> > > > From: Alexandre Courbot <gnurou@gmail.com>
> > > >
> > > > Add the structs and function declarations for the new virtio-media drvier.
> > > >
> > > > Signed-off-by: Alexandre Courbot <gnurou@gmail.com>
> > > > Co-developed-by: Brian Daniels <briandaniels@google.com>
> > > > Signed-off-by: Brian Daniels <briandaniels@google.com>
> > > > ---
> > > >  drivers/media/virtio/virtio_media.h | 95 +++++++++++++++++++++++++++++
> > > >  1 file changed, 95 insertions(+)
> > > >  create mode 100644 drivers/media/virtio/virtio_media.h
> > > >
> > > > diff --git a/drivers/media/virtio/virtio_media.h b/drivers/media/virtio/virtio_media.h
> > > > new file mode 100644
> > > > index 000000000..52809d4e9
> > > > --- /dev/null
> > > > +++ b/drivers/media/virtio/virtio_media.h
> > > > @@ -0,0 +1,95 @@
> > > > +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0+ */
> > > > +
> > > > +/*
> > > > + * Virtio-media structures & functions declarations.
> > > > + *
> > > > + * Copyright (c) 2024-2025 Google LLC.
> > > > + */
> > > > +
> > > > +#ifndef __VIRTIO_MEDIA_H
> > > > +#define __VIRTIO_MEDIA_H
> > > > +
> > > > +#include <linux/virtio_config.h>
> > > > +#include <media/v4l2-device.h>
> > > > +
> > > > +#include "protocol.h"
> > > > +
> > > > +#define DESC_CHAIN_MAX_LEN SG_MAX_SINGLE_ALLOC
> > > > +
> > > > +#define VIRTIO_MEDIA_DEFAULT_DRIVER_NAME "virtio-media"
> > > > +
> > > > +extern char *virtio_media_driver_name;
> > > > +extern bool virtio_media_allow_userptr;
> > > > +
> > > > +/**
> > > > + * struct virtio_media - Virtio-media device.
> > > > + * @v4l2_dev: v4l2_device for the media device.
> > > > + * @video_dev: video_device for the media device.
> > > > + * @virtio_dev: virtio device for the media device.
> > > > + * @commandq: virtio command queue.
> > > > + * @eventq: virtio event queue.
> > > > + * @eventq_work: work to run when events are received on @eventq.
> > > > + * @mmap_region: region into which MMAP buffers are mapped by the host.
> > > > + * @event_buffer: buffer for event descriptors.
> > > > + * @sessions: list of active sessions on the device.
> > > > + * @sessions_lock: protects @sessions and ``virtio_media_session::list``.
> > > > + * @events_lock: prevents concurrent processing of events.
> > > > + * @cmd: union of the device commands "open" and "munmap". The other
> > > > + *       commands are handled by @struct virtio_media_session
> > > > + * @resp: union of responses.to device commands "open" and "munmap". The
> > > > + *        other responses are handled by @struct virtio_media_session
> > > > + * @vlock: serializes access to the command queue.
> > > > + * @wq: waitqueue for host responses on the command queue.
> > > > + */
> > > > +struct virtio_media {
> > > > +     struct v4l2_device v4l2_dev;
> > > > +     struct video_device video_dev;
> > > > +
> > > > +     struct virtio_device *virtio_dev;
> > > > +     struct virtqueue *commandq;
> > > > +     struct virtqueue *eventq;
> > > > +     struct work_struct eventq_work;
> > > > +
> > > > +     struct virtio_shm_region mmap_region;
> > > > +
> > > > +     void *event_buffer;
> > > > +
> > > > +     struct list_head sessions;
> > > > +     struct mutex sessions_lock; /* protects sessions list */
> > > > +
> > > > +     struct mutex events_lock; /* prevents concurrent event processing */
> > > > +
> > > > +     union {
> > > > +             struct virtio_media_cmd_open open;
> > > > +             struct virtio_media_cmd_munmap munmap;
> > > > +     } cmd;
> > > > +
> > > > +     union {
> > > > +             struct virtio_media_resp_open open;
> > > > +             struct virtio_media_resp_munmap munmap;
> > > > +     } resp;
> > >
> > >
> > > You need DMA alignment padding for these things.
> >
> > Each member of the above unions is already padded to 64-bit alignment,
> > is that sufficient?
> >
> > If not, could you tell me what else is necessary?
>
> Read up "What memory is DMA'able?" and following chapters in
> Documentation/core-api/dma-api-howto.rst

Great thanks!

> > > Which one can only see when I reads the actual driver 8 patches down.
> > > Which is why it's not a sensible way to split patches.
> > >
> > > A sensible way is to have a driver then add functionality
> > > in logical pieces gradually.
> > >
> >
> > Appreciate the feedback. There was a previous comment requesting to
> > split the patch into c/h file pairs, but I can try reformatting it as
> > you suggest for v5.
> >
> > > > +
> > > > +     struct mutex vlock; /* serializes command queue access */
> > > > +     wait_queue_head_t wq;
> > > > +};
> > > > +
> > > > +static inline struct virtio_media *
> > > > +to_virtio_media(struct video_device *video_dev)
> > > > +{
> > > > +     return container_of(video_dev, struct virtio_media, video_dev);
> > > > +}
> > > > +
> > > > +/* virtio_media_driver.c */
> > > > +
> > > > +int virtio_media_send_command(struct virtio_media *vv, struct scatterlist **sgs,
> > > > +                           const size_t out_sgs, const size_t in_sgs,
> > > > +                           size_t minimum_resp_len, size_t *resp_len);
> > > > +void virtio_media_process_events(struct virtio_media *vv);
> > > > +
> > > > +/* virtio_media_ioctls.c */
> > > > +
> > > > +long virtio_media_device_ioctl(struct file *file, unsigned int cmd,
> > > > +                            unsigned long arg);
> > > > +extern const struct v4l2_ioctl_ops virtio_media_ioctl_ops;
> > > > +
> > > > +#endif // __VIRTIO_MEDIA_H
> > > > --
> > > > 2.55.0.rc0.799.gd6f94ed593-goog
> > >
>

^ permalink raw reply

* Re: [PATCH 01/15] drm/exynos: remove dependency on DRM simple helpers
From: Thomas Zimmermann @ 2026-07-20 15:27 UTC (permalink / raw)
  To: Diogo Silva
  Cc: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Michal Simek, Thierry Reding, Mikko Perttunen,
	Jonathan Hunter, Stefan Agner, Alison Wang, Anitha Chrisanthus,
	David Airlie, Gerd Hoffmann, Dmitry Osipenko, Gurchetan Singh,
	Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, Chun-Kuang Hu,
	Philipp Zabel, Matthias Brugger, AngeloGioacchino Del Regno,
	Geert Uytterhoeven, Xinliang Liu, Sumit Semwal, Yongqin Liu,
	John Stultz, Liviu Dudau, Neil Armstrong, Kevin Hilman,
	Jerome Brunet, Martin Blumenstingl, Jonathan Corbet, Shuah Khan,
	dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc
In-Reply-To: <CAJpoHp73S2_cavgV3aVwC5PS8H1GRqTdDV-Xu8n6nXavBnV2=g@mail.gmail.com>

Hi

Am 20.07.26 um 17:15 schrieb Diogo Silva:
> Hey,
>
>> Again, you rather want drm_err() with drm_dev here.
>>
>> I've briefly looked over the series and many patches seem affected.
>> Please prefer drm_ logging functions and DRM devices over the plain
>> device equivalents.
> Thanks. I will submit a v2 with consistent usage of drm_err().

Thanks

>
> Also, is there any ongoing task or would it be useful to submit patches
> changing the usage of DRM_DEV_* functions with drm_* equivalents?
> Or from dev_* to drm_* when possible ?

Possibly. DRM_DEV_ logging is supposed to be replaced with drm_ logging 
helpers. That would be a simple follow-up series.  Whether to prefer 
drm_ over dev_ logging depends on the context and the driver maintainer.

Best regards
Thomas


>
> BR,
> Diogo

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, (HRB 36809, AG Nürnberg)



^ permalink raw reply

* Re: [PATCH 01/15] drm/exynos: remove dependency on DRM simple helpers
From: Diogo Silva @ 2026-07-20 15:15 UTC (permalink / raw)
  To: Thomas Zimmermann
  Cc: Jingoo Han, Inki Dae, Seung-Woo Kim, Kyungmin Park, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
	Laurent Pinchart, Tomi Valkeinen, Maarten Lankhorst,
	Maxime Ripard, Michal Simek, Thierry Reding, Mikko Perttunen,
	Jonathan Hunter, Stefan Agner, Alison Wang, Anitha Chrisanthus,
	David Airlie, Gerd Hoffmann, Dmitry Osipenko, Gurchetan Singh,
	Chia-I Wu, Jyri Sarha, Liu Ying, Frank Li, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, Chun-Kuang Hu,
	Philipp Zabel, Matthias Brugger, AngeloGioacchino Del Regno,
	Geert Uytterhoeven, Xinliang Liu, Sumit Semwal, Yongqin Liu,
	John Stultz, Liviu Dudau, Neil Armstrong, Kevin Hilman,
	Jerome Brunet, Martin Blumenstingl, Jonathan Corbet, Shuah Khan,
	dri-devel, linux-arm-kernel, linux-samsung-soc, linux-kernel,
	linux-tegra, virtualization, imx, linux-mediatek,
	linux-renesas-soc, linux-amlogic, linux-doc
In-Reply-To: <4af254a4-beb7-4f77-830f-42d5bacb10a4@suse.de>

Hey,

> Again, you rather want drm_err() with drm_dev here.
>
> I've briefly looked over the series and many patches seem affected.
> Please prefer drm_ logging functions and DRM devices over the plain
> device equivalents.

Thanks. I will submit a v2 with consistent usage of drm_err().

Also, is there any ongoing task or would it be useful to submit patches
changing the usage of DRM_DEV_* functions with drm_* equivalents?
Or from dev_* to drm_* when possible ?

BR,
Diogo

^ permalink raw reply

* [RFC PATCH 2/4] vhost: host kernel support for SQ/CQ polling
From: rom.wang @ 2026-07-20 14:10 UTC (permalink / raw)
  To: mst, jasowangio, eperezma, stefanha
  Cc: virtualization, linux-kernel, linux-scsi, kvm, qemu-devel,
	Yufeng Wang
In-Reply-To: <20260720141040.181946-1-r4o5m6e8o@163.com>

From: Yufeng Wang <wangyufeng@kylinos.cn>

Implement SQ/CQ doorbell polling in the vhost kernel backend:

  - vhost.c: per-VQ poll thread (kthread) that polls sq->idx for
    submissions and processes completions via poll_complete callback;
    need_resched() cooperative spin; round-robin CPU binding with
    modulo to prevent overflow; sqcq_poll enabled after poll_task is
    valid (NULL deref fix); get_user fault retry (3 attempts, 10ms
    backoff); EMA overflow-safe formula
  - vhost.h: vhost_virtqueue extensions (sq/cq pointers, poll thread
    state, EMA fields)
  - scsi.c: vhost_scsi_poll_complete() writes cq->idx and signals
    Guest; SET_ENDPOINT error path stops started poll threads (fallback);
    10s window diagnostic output
  - vhost.h UAPI: VHOST_SET_VRING_SQCQ_ADDR ioctl
  - vhost_types.h UAPI: struct vhost_vring_sqcq_addr

Signed-off-by: Yufeng Wang <wangyufeng@kylinos.cn>
---
 Documentation/compare-sqcq-results.sh         | 239 +++++++
 Documentation/run-sqcq-compare.sh             | 182 ++++++
 Documentation/virtio-sqcq-poll-spec.rst       | 617 ++++++++++++++++++
 .../virtio-sqcq-poll-virtio-spec.rst          | 355 ++++++++++
 build-riscv-kernel.sh                         |  26 +
 drivers/vhost/scsi.c                          | 187 +++++-
 drivers/vhost/vhost.c                         | 595 ++++++++++++++++-
 drivers/vhost/vhost.h                         |  29 +-
 include/uapi/linux/vhost.h                    |   4 +
 include/uapi/linux/vhost_types.h              |   6 +
 10 files changed, 2221 insertions(+), 19 deletions(-)
 create mode 100644 Documentation/compare-sqcq-results.sh
 create mode 100644 Documentation/run-sqcq-compare.sh
 create mode 100644 Documentation/virtio-sqcq-poll-spec.rst
 create mode 100644 Documentation/virtio-sqcq-poll-virtio-spec.rst
 create mode 100644 build-riscv-kernel.sh

diff --git a/Documentation/compare-sqcq-results.sh b/Documentation/compare-sqcq-results.sh
new file mode 100644
index 0000000000000..85bb927fe91e5
--- /dev/null
+++ b/Documentation/compare-sqcq-results.sh
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+"""compare-sqcq-results.py - Generate A/B comparison report for SQ/CQ polling vs baseline.
+
+Usage:
+    python3 compare-sqcq-results.py <baseline_dir> <sqcq_dir>
+    python3 compare-sqcq-results.py   # auto-detect latest results
+"""
+import json, sys, os, glob, re
+from collections import OrderedDict
+
+def find_latest_dirs():
+    """Find the most recent baseline and sqcq-poll result directories."""
+    base = "perf-results"
+    if not os.path.isdir(base):
+        print(f"Error: '{base}/' not found. Run run-sqcq-compare.sh first.")
+        sys.exit(1)
+
+    bl_dirs = sorted(glob.glob(os.path.join(base, "baseline_*")))
+    sq_dirs = sorted(glob.glob(os.path.join(base, "sqcq-poll_*")))
+
+    if not bl_dirs:
+        print("Error: No baseline_* directory found in perf-results/")
+        sys.exit(1)
+    if not sq_dirs:
+        print("Error: No sqcq-poll_* directory found in perf-results/")
+        sys.exit(1)
+
+    return bl_dirs[-1], sq_dirs[-1]
+
+
+def parse_fio_json(filepath):
+    """Extract key metrics from a fio JSON output file."""
+    with open(filepath) as f:
+        d = json.load(f)
+
+    jobs = d.get("jobs", [])
+    if not jobs:
+        return None
+
+    j = jobs[0]
+    rd = j.get("read", {})
+    wr = j.get("write", {})
+
+    # Determine which direction has data
+    if rd.get("iops", 0) > 0:
+        data = rd
+    else:
+        data = wr
+
+    iops = data.get("iops", 0)
+    bw_mib = data.get("bw", 0) / 1024  # KiB/s -> MiB/s
+
+    # Latency
+    lat_ns = data.get("lat_ns", {})
+    avg_us = lat_ns.get("mean", 0) / 1000
+    min_us = lat_ns.get("min", 0) / 1000
+    max_us = lat_ns.get("max", 0) / 1000
+    stddev_us = lat_ns.get("stddev", 0) / 1000
+
+    # Completion latency percentiles
+    clat = data.get("clat_ns", {})
+    pct = clat.get("percentile", {}) if isinstance(clat, dict) else {}
+    p50 = pct.get("50.000000", 0) / 1000
+    p95 = pct.get("95.000000", 0) / 1000
+    p99 = pct.get("99.000000", 0) / 1000
+    p999 = pct.get("99.900000", 0) / 1000
+
+    return {
+        "iops": iops,
+        "bw_mib": bw_mib,
+        "lat_avg_us": avg_us,
+        "lat_min_us": min_us,
+        "lat_max_us": max_us,
+        "lat_stddev_us": stddev_us,
+        "p50_us": p50,
+        "p95_us": p95,
+        "p99_us": p99,
+        "p999_us": p999,
+    }
+
+
+def pct_change(old, new):
+    """Calculate percentage change. Returns None if division by zero."""
+    if old == 0:
+        return None
+    return (new - old) / old * 100
+
+
+def fmt_pct(val):
+    """Format a percentage change value."""
+    if val is None:
+        return "N/A"
+    sign = "+" if val > 0 else ""
+    return f"{sign}{val:.1f}%"
+
+
+def fmt_int(val):
+    """Format integer with comma separators."""
+    return f"{val:,.0f}"
+
+
+def fmt_f1(val):
+    """Format float with 1 decimal."""
+    return f"{val:.1f}"
+
+
+def main():
+    if len(sys.argv) >= 3:
+        bl_dir = sys.argv[1]
+        sq_dir = sys.argv[2]
+    else:
+        bl_dir, sq_dir = find_latest_dirs()
+
+    print(f"Baseline:   {bl_dir}")
+    print(f"SQ/CQ Poll: {sq_dir}")
+    print()
+
+    # Collect all test labels
+    bl_files = {os.path.basename(f).replace("_best.json", ""): f
+                for f in glob.glob(os.path.join(bl_dir, "*_best.json"))}
+    sq_files = {os.path.basename(f).replace("_best.json", ""): f
+                for f in glob.glob(os.path.join(sq_dir, "*_best.json"))}
+
+    all_labels = sorted(set(bl_files.keys()) | set(sq_files.keys()))
+
+    if not all_labels:
+        print("Error: No *_best.json files found.")
+        sys.exit(1)
+
+    # Define metric display columns
+    metrics = [
+        ("IOPS",        "iops",        fmt_int,  True),
+        ("BW(MiB/s)",   "bw_mib",      fmt_f1,   True),
+        ("Avg(us)",     "lat_avg_us",  fmt_f1,   False),
+        ("P50(us)",     "p50_us",      fmt_f1,   False),
+        ("P95(us)",     "p95_us",      fmt_f1,   False),
+        ("P99(us)",     "p99_us",      fmt_f1,   False),
+        ("P99.9(us)",   "p999_us",     fmt_f1,   False),
+    ]
+
+    # Per-label comparison
+    results = []
+    for label in all_labels:
+        bl_data = parse_fio_json(bl_files[label]) if label in bl_files else None
+        sq_data = parse_fio_json(sq_files[label]) if label in sq_files else None
+
+        entry = {"label": label, "baseline": bl_data, "sqcq": sq_data}
+        results.append(entry)
+
+    # Print grouped by workload type
+    groups = OrderedDict()
+    for label in all_labels:
+        # Extract workload type from label: e.g. "4k-randread-od1-nj1" -> "4k-randread"
+        parts = label.rsplit("-od", 1)
+        wl = parts[0] if len(parts) > 1 else label
+        if wl not in groups:
+            groups[wl] = []
+        groups[wl].append(label)
+
+    print("=" * 100)
+    print("  SQ/CQ POLL vs BASELINE COMPARISON REPORT")
+    print("=" * 100)
+
+    for wl, labels in groups.items():
+        print(f"\n{'─' * 100}")
+        print(f"  Workload: {wl}")
+        print(f"{'─' * 100}")
+
+        for label in labels:
+            r = next(x for x in results if x["label"] == label)
+            bl = r["baseline"]
+            sq = r["sqcq"]
+
+            # Extract iodepth/numjobs from label
+            m = re.search(r'od(\d+)-nj(\d+)', label)
+            od = m.group(1) if m else "?"
+            nj = m.group(2) if m else "?"
+
+            print(f"\n  iodepth={od}, numjobs={nj}")
+            print(f"  {'Metric':<14} {'Baseline':>12} {'SQ/CQ Poll':>12} {'Change':>10}")
+            print(f"  {'─'*14} {'─'*12} {'─'*12} {'─'*10}")
+
+            for metric_name, metric_key, fmt_fn, higher_is_better in metrics:
+                bl_val = bl[metric_key] if bl else 0
+                sq_val = sq[metric_key] if sq else 0
+                change = pct_change(bl_val, sq_val)
+
+                # For latency, negative change is improvement
+                line = f"  {metric_name:<14} {fmt_fn(bl_val):>12} {fmt_fn(sq_val):>12}"
+
+                if change is not None:
+                    marker = ""
+                    if higher_is_better and change > 2:
+                        marker = " *"
+                    elif not higher_is_better and change < -2:
+                        marker = " *"
+                    line += f" {fmt_pct(change):>10}{marker}"
+                else:
+                    line += f" {'N/A':>10}"
+
+                print(line)
+
+    # Summary table - just IOPS
+    print(f"\n{'=' * 100}")
+    print("  IOPS SUMMARY (all scenarios)")
+    print(f"{'=' * 100}")
+    print(f"\n  {'Test':<30} {'Baseline':>10} {'SQ/CQ':>10} {'Change':>10}")
+    print(f"  {'─'*30} {'─'*10} {'─'*10} {'─'*10}")
+
+    for r in results:
+        bl_iops = r["baseline"]["iops"] if r["baseline"] else 0
+        sq_iops = r["sqcq"]["iops"] if r["sqcq"] else 0
+        change = pct_change(bl_iops, sq_iops)
+
+        marker = " **" if change and change > 5 else ""
+        print(f"  {r['label']:<30} {bl_iops:>10,.0f} {sq_iops:>10,.0f} {fmt_pct(change):>10}{marker}")
+
+    # Latency summary
+    print(f"\n{'=' * 100}")
+    print("  P99 LATENCY SUMMARY (all scenarios)")
+    print(f"{'=' * 100}")
+    print(f"\n  {'Test':<30} {'Base(p99)':>12} {'SQ/CQ(p99)':>12} {'Change':>10}")
+    print(f"  {'─'*30} {'─'*12} {'─'*12} {'─'*10}")
+
+    for r in results:
+        bl_p99 = r["baseline"]["p99_us"] if r["baseline"] else 0
+        sq_p99 = r["sqcq"]["p99_us"] if r["sqcq"] else 0
+        change = pct_change(bl_p99, sq_p99)
+
+        marker = " **" if change and change < -5 else ""
+        print(f"  {r['label']:<30} {bl_p99:>12.1f} {sq_p99:>12.1f} {fmt_pct(change):>10}{marker}")
+
+    print(f"\n  *  = notable improvement")
+    print(f"  ** = significant improvement (>5% IOPS gain or >5% latency reduction)")
+    print()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/Documentation/run-sqcq-compare.sh b/Documentation/run-sqcq-compare.sh
new file mode 100644
index 0000000000000..3a5928bfaef2a
--- /dev/null
+++ b/Documentation/run-sqcq-compare.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+# run-sqcq-compare.sh - A/B comparison for SQ/CQ polling vs baseline
+#
+# Usage:
+#   1. Boot Guest WITHOUT  VIRTIO_F_SQCQ_POLL:  ./run-sqcq-compare.sh baseline
+#   2. Boot Guest WITH     VIRTIO_F_SQCQ_POLL:  ./run-sqcq-compare.sh sqcq-poll
+#
+# Results saved to: perf-results/<mode>_<timestamp>/
+#
+# IMPORTANT: CPU pinning uses --cpus_allowed (NOT taskset).
+#            Verify with: ps -eo pid,comm,psr | grep fio
+#
+set -euo pipefail
+
+MODE=${1:?"Usage: $0 <baseline|sqcq-poll>"}
+DEV=${2:-/dev/sda}
+PIN_CPU=7
+RUNTIME=60
+RUNS=3
+
+TIMESTAMP=$(date +%Y%m%d-%H%M%S)
+OUTDIR="perf-results/${MODE}_${TIMESTAMP}"
+mkdir -p "$OUTDIR"
+
+# Verify CPU pinning mapping: PIN_CPU -> hwq -> single Host vq
+echo "=== CPU/mq mapping verification ==="
+echo "PIN_CPU=$PIN_CPU"
+for d in /sys/block/$(basename $DEV)/mq/*/; do
+    dir=$(basename "$d")
+    cpus=$(cat "$d/cpu_list" 2>/dev/null || echo "?")
+    echo "  hwq $dir: CPU $cpus"
+done
+echo ""
+
+echo "=== Mode: $MODE | Device: $DEV | CPU pin: $PIN_CPU | Runs: $RUNS ==="
+echo "=== Output: $OUTDIR ==="
+echo ""
+
+# Collect Host dmesg marker (for correlating with Host-side vhost stats)
+dmesg > "$OUTDIR/guest-dmesg-before.log" 2>/dev/null || true
+
+# Test matrix: label rw bs iodepth numjobs
+TESTS=(
+    "4k-randread-od1-nj1    randread   4k  1  1"
+    "4k-randread-od32-nj1   randread   4k  32 1"
+    "4k-randread-od32-nj4   randread   4k  32 4"
+    "4k-randread-od32-nj8   randread   4k  32 8"
+    "4k-randwrite-od1-nj1   randwrite  4k  1  1"
+    "4k-randwrite-od32-nj1  randwrite  4k  32 1"
+    "4k-randwrite-od32-nj4  randwrite  4k  32 4"
+    "4k-randwrite-od32-nj8  randwrite  4k  32 8"
+    "128k-seqread-od1-nj1   read       128k 1  1"
+    "128k-seqread-od32-nj1  read       128k 32 1"
+    "128k-seqread-od32-nj4  read       128k 32 4"
+    "128k-seqread-od32-nj8  read       128k 32 8"
+    "128k-seqwrite-od1-nj1  write      128k 1  1"
+    "128k-seqwrite-od32-nj1 write      128k 32 1"
+    "128k-seqwrite-od32-nj4 write      128k 32 4"
+    "128k-seqwrite-od32-nj8 write      128k 32 8"
+)
+
+for entry in "${TESTS[@]}"; do
+    read -r LABEL RW BS OD NJ <<< "$entry"
+
+    echo "=== $LABEL (iodepth=$OD, numjobs=$NJ) ==="
+
+    BEST_IOPS=0
+    BEST_FILE=""
+
+    for run in $(seq 1 $RUNS); do
+        OUTFILE="${OUTDIR}/${LABEL}_run${run}.json"
+
+        # For numjobs=1, pin to single CPU; for numjobs>1, let fio distribute
+        PIN_ARGS=""
+        if [ "$NJ" -eq 1 ]; then
+            PIN_ARGS="--cpus_allowed=$PIN_CPU --cpus_allowed_policy=shared"
+        fi
+
+        echo "  run $run/$RUNS ..."
+        fio --ioengine=libaio \
+            --direct=1 \
+            --rw="$RW" \
+            --bs="$BS" \
+            --iodepth=$OD \
+            --numjobs=$NJ \
+            $PIN_ARGS \
+            --time_based \
+            --runtime=$RUNTIME \
+            --group_reporting \
+            --percentile_list=50,95,99,99.9 \
+            --filename="$DEV" \
+            --name="$LABEL" \
+            --output-format=json \
+            --output="$OUTFILE"
+
+        # Extract IOPS from JSON
+        IOPS=$(python3 -c "
+import json, sys
+with open('$OUTFILE') as f:
+    d = json.load(f)
+jobs = d.get('jobs', [])
+if jobs:
+    rd = jobs[0].get('read', {})
+    wr = jobs[0].get('write', {})
+    iops = rd.get('iops', 0) + wr.get('iops', 0)
+    print(f'{iops:.0f}')
+else:
+    print('0')
+" 2>/dev/null || echo "0")
+
+        echo "    IOPS: $IOPS"
+
+        # Track best run
+        if [ "$IOPS" -gt "$BEST_IOPS" ]; then
+            BEST_IOPS=$IOPS
+            BEST_FILE=$OUTFILE
+        fi
+    done
+
+    # Copy best run as the canonical result
+    if [ -n "$BEST_FILE" ]; then
+        cp "$BEST_FILE" "${OUTDIR}/${LABEL}_best.json"
+    fi
+
+    echo ""
+done
+
+# Collect dmesg after
+dmesg > "$OUTDIR/guest-dmesg-after.log" 2>/dev/null || true
+
+# Generate summary
+echo "=== Generating summary ==="
+python3 - <<'PYEOF' "$OUTDIR"
+import json, sys, os, glob
+
+outdir = sys.argv[1]
+print(f"\n{'='*80}")
+print(f"  RESULTS: {os.path.basename(outdir)}")
+print(f"{'='*80}\n")
+
+header = f"{'Test':<30} {'IOPS':>10} {'p50(us)':>10} {'p99(us)':>10} {'p99.9(us)':>10} {'BW(MiB/s)':>10} {'lat_avg(us)':>12}"
+print(header)
+print("-" * len(header))
+
+for best in sorted(glob.glob(os.path.join(outdir, "*_best.json"))):
+    label = os.path.basename(best).replace("_best.json", "")
+    try:
+        with open(best) as f:
+            d = json.load(f)
+        jobs = d.get("jobs", [])
+        if not jobs:
+            continue
+
+        j = jobs[0]
+        rd = j.get("read", {})
+        wr = j.get("write", {})
+        iops = rd.get("iops", 0) + wr.get("iops", 0)
+        bw = (rd.get("bw", 0) + wr.get("bw", 0)) / 1024  # KiB/s -> MiB/s
+
+        lat_ns = rd.get("lat_ns", {}) or wr.get("lat_ns", {})
+        avg_us = lat_ns.get("mean", 0) / 1000
+
+        # Percentiles from clat_ns (completion latency)
+        clat = rd.get("clat_ns", {}) or wr.get("clat_ns", {})
+        pct = clat.get("percentile", {}) if isinstance(clat, dict) else {}
+        p50 = pct.get("50.000000", 0) / 1000
+        p99 = pct.get("99.000000", 0) / 1000
+        p999 = pct.get("99.900000", 0) / 1000
+
+        print(f"{label:<30} {iops:>10,.0f} {p50:>10.1f} {p99:>10.1f} {p999:>10.1f} {bw:>10.1f} {avg_us:>12.1f}")
+    except Exception as e:
+        print(f"{label:<30} ERROR: {e}")
+
+print()
+PYEOF
+
+echo ""
+echo "=== Done. Results in $OUTDIR ==="
+echo "=== After running both modes, compare with: ==="
+echo "    diff <(python3 summarize.py perf-results/baseline_*) <(python3 summarize.py perf-results/sqcq-poll_*)"
+echo ""
+echo "=== Or paste both summary tables side-by-side for comparison ==="
diff --git a/Documentation/virtio-sqcq-poll-spec.rst b/Documentation/virtio-sqcq-poll-spec.rst
new file mode 100644
index 0000000000000..523bb3e3a1963
--- /dev/null
+++ b/Documentation/virtio-sqcq-poll-spec.rst
@@ -0,0 +1,617 @@
+Virtio SQ/CQ Polling Mode — 技术规格书
+==========================================
+
+Host Kernel vhost 端已实现。Guest Kernel 端已实现。QEMU 端已实现。
+
+1. 概述
+-------
+
+传统 virtio 使用 MMIO kick (Guest→Device) 和 MSI-X 中断 (Device→Guest) 通知,
+每次涉及 VM exit。SQ/CQ polling 借鉴 io_uring SQPOLL 模型,在 split ring 旁引入
+SQ/CQ 门铃结构,由轮询线程检测变更替代中断,消除 VM exit。
+
+- Guest→Device:Guest 更新 SQ.idx,Device 轮询线程检测
+- Device→Guest:Device 更新 CQ.idx,Guest 轮询线程检测
+
+SQ/CQ 门铃仅做通知信号,实际数据仍通过 desc/avail/used ring 传递。
+通过 Feature Bit 协商,未协商时零开销。仅支持 split ring。
+
+1.1 地址传递架构
+~~~~~~~~~~~~~~~
+
+::
+
+  Guest Kernel          QEMU (userspace)       Host Kernel (vhost)
+  PCI config space      GPA → HVA 转换           VHOST_SET_VRING_SQCQ_ADDR
+  (写入 GPA)            ioctl (传入 HVA)            (接收 HVA)
+
+1.2 通知路径
+~~~~~~~~~~~~
+
+::
+
+  活跃路径(零开销):
+    Guest 提交 → SQ.idx → Device 轮询检测(消除 VM exit)
+    Device 完成 → CQ.idx → Guest 轮询检测(消除 VM exit)
+
+  休眠唤醒路径:
+    Guest 提交 → SQ.idx → MMIO kick → QEMU → eventfd → vhost 唤醒
+    Device 完成 → CQ.idx → eventfd_signal → QEMU → MSI-X → Guest 唤醒
+
+
+2. Feature 协商
+---------------
+
+``VIRTIO_F_SQCQ_POLL = 42``(``include/uapi/linux/virtio_config.h``)。
+
+``VIRTIO_TRANSPORT_F_END`` 从 42 更新为 43。
+该 bit 属于 transport feature range (28..43),需同时被 virtio_ring 和 virtio_pci 接受。
+vhost 端将该 feature 加入 ``VHOST_FEATURES`` 宏,在 ``VHOST_GET_FEATURES`` ioctl 中对 Guest 可见。
+
+**协商流程:**
+
+1. Device 设置 bit 42 → Guest 接受 → finalize_features
+2. Guest 在 find_vqs 阶段分配 SQ/CQ 结构
+3. Guest 在 vp_active_vq 阶段将 SQ/CQ DMA 地址写入 PCI config space
+4. QEMU 读 GPA → 转 HVA → ``VHOST_SET_VRING_SQCQ_ADDR`` ioctl → vhost
+5. vhost 在 ``VHOST_SCSI_SET_ENDPOINT`` 时启动轮询线程
+
+任一方不支持则回退传统中断模式。
+
+
+3. 数据结构
+-----------
+
+3.1 SQ/CQ 门铃
+~~~~~~~~~~~~~~~
+
+定义于 ``include/uapi/linux/virtio_ring.h``:
+
+.. code-block:: c
+
+    struct vring_sq {
+        __virtio64 idx;             /* Producer position: Guest 更新 */
+        __u8 sq_need_wakeup;        /* 1 = Device 应被 kick 唤醒 */
+        __u8 reserved[55];
+    } __attribute__((aligned(128)));  /* 128 bytes, cache-line 对齐 */
+
+    struct vring_cq {
+        __virtio64 idx;             /* Producer position: Device 更新 */
+        __u8 cq_need_wakeup;        /* 1 = Guest 应被中断唤醒 */
+        __u8 reserved[55];
+    } __attribute__((aligned(128)));  /* 128 bytes, cache-line 对齐 */
+
+- ``sq->idx`` 对应 ``avail->idx``,``cq->idx`` 对应 ``used->idx``
+- 128 字节对齐覆盖 ARM Neoverse N2/V2、Apple M 系列等 128 字节 cache line 的 CPU,
+  消除 SQ 与 CQ 之间的 false sharing
+- ``sq/cq_need_wakeup`` 为 8-bit 直接赋值字段,用于 NEED_WAKEUP 协议(见第 6 节)
+
+3.2 PCI Config Space 寄存器
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+追加到 ``struct virtio_pci_modern_common_cfg`` 末尾(offset 62 之后),
+定义于 ``include/uapi/linux/virtio_pci.h``:
+
+.. code-block:: c
+
+    #define VIRTIO_PCI_COMMON_SQE_LO    64
+    #define VIRTIO_PCI_COMMON_SQE_HI    68
+    #define VIRTIO_PCI_COMMON_CQE_LO    72
+    #define VIRTIO_PCI_COMMON_CQE_HI    76
+
+    __le32 sqe_ring_lo;    /* offset 64: SQ DMA 地址低 32 位 */
+    __le32 sqe_ring_hi;    /* offset 68: SQ DMA 地址高 32 位 */
+    __le32 cqe_ring_lo;    /* offset 72: CQ DMA 地址低 32 位 */
+    __le32 cqe_ring_hi;    /* offset 76: CQ DMA 地址高 32 位 */
+
+受 queue_select 约束。UAPI 已定义,Guest 已实现写入,QEMU 已实现读取。
+
+3.3 vhost UAPI
+~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+    // include/uapi/linux/vhost.h
+    #define VHOST_SET_VRING_SQCQ_ADDR _IOW(VHOST_VIRTIO, 0x27, \
+                        struct vhost_vring_sqcq_addr)
+
+    // include/uapi/linux/vhost_types.h
+    struct vhost_vring_sqcq_addr {
+        unsigned int index;
+        __u64 sq_user_addr;    /* SQ doorbell HVA */
+        __u64 cq_user_addr;    /* CQ doorbell HVA */
+    };
+
+QEMU 调用时机:在 ``VHOST_SET_VRING_ADDR`` 之后、``VHOST_SCSI_SET_ENDPOINT`` 之前。
+
+3.4 写入时序
+~~~~~~~~~~~~
+
+Guest(vp_active_vq 中):queue_select → desc/avail/used 地址 → sqe/cqe_ring_lo/hi 地址
+QEMU:组合 GPA → address_space_map 转 HVA → VHOST_SET_VRING_SQCQ_ADDR ioctl
+
+
+4. 字段读写所有权
+-----------------
+
+.. list-table::
+   :header-rows: 1
+   :widths: 20 20 20 25 15
+
+   * - 字段
+     - 写入方
+     - 读取方
+     - 内存序
+     - 对应
+
+   * - sq->idx
+     - Guest
+     - Device
+     - Guest: smp_store_release; Device: get_user + smp_rmb
+     - avail->idx
+
+   * - sq->sq_need_wakeup
+     - Device
+     - Guest
+     - Device: smp_wmb + put_user + smp_mb (double-check)
+     - NEED_WAKEUP
+
+   * - cq->idx
+     - Device
+     - Guest
+     - Device: smp_wmb before put_user; Guest: smp_load_acquire
+     - used->idx
+
+   * - cq->cq_need_wakeup
+     - Guest
+     - Device
+     - Guest: smp_store_release; Device: get_user + smp_rmb
+     - NEED_WAKEUP
+
+
+5. 核心协议
+-----------
+
+5.1 Guest 提交请求 (SQ 方向)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+    // virtqueue_kick() 中 sqcq_poll 模式
+    smp_store_release(&vring->sq->idx, avail_idx_shadow);
+    need_wakeup = smp_load_acquire(&vring->sq->sq_need_wakeup);
+    if (need_wakeup)
+        virtqueue_notify(vq);  // MMIO write,仅在 Device 休眠时
+
+**关键:** Guest 仍写 ``avail->idx``(``virtqueue_add_split()`` 完成)。
+SQ idx 是额外通知信号,让 Device 快速检测(读一个 cache line vs 读 avail ring 两次)。
+Device 的 ``handle_kick()`` 通过 ``avail->idx`` 定位 descriptor。
+
+**virtqueue_kick_prepare() 守卫:** sqcq_poll 模式下直接返回 ``true``,
+不检查 ``used->flags``(vhost 不维护该 flag)。
+
+5.2 Device 处理请求 (vhost 轮询线程)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+独立 kthread ``vhost_sqcq_poll_thread``。直接调用 ``handle_kick`` 处理提交,
+通过 ``poll_complete`` 回调处理完成,单线程闭环。
+
+主循环伪代码(drivers/vhost/vhost.c):
+
+.. code-block:: c
+
+    while (!poll_stop):
+        // 0. 处理 pending completion
+        if (atomic_read(&vq->completion_pending) && vq->poll_complete):
+            atomic_set(&vq->completion_pending, 0)
+            vq->poll_complete(vq)  // copy resp, add used, 写 cq->idx, 检查 cq_need_wakeup
+
+        // 1. 读 SQ idx
+        ret = get_user(sq_idx_val, &vq->sq->idx)
+        smp_rmb()  // acquire
+        new_sq_idx = (u16)sq_idx_val
+
+        // 2. 有新提交:同步调用 handle_kick
+        if (new_sq_idx != vq->last_avail_idx):
+            vq->handle_kick(&vq->poll.work)
+
+        // 3. 自适应休眠(EMA 动态超时)
+        should_sleep = (last_avail_idx == last_used_idx)
+        if (!should_sleep && ema_ns > 0)
+            should_sleep = (active_io_vqs > 1) && (ema_us > SQCQ_SLEEP_THRESHOLD_US)
+
+        if (should_sleep):
+            timeout = ema_based_or_fallback(SQCQ_IDLE_TIMEOUT_US_MAX)
+            // 设置 NEED_WAKEUP + double-check idx
+            smp_wmb(); put_user(1, &sq->sq_need_wakeup); smp_mb()
+            if (idx changed): put_user(0, &sq->sq_need_wakeup); continue
+            // 注册 kick eventfd,带超时等待
+            kick_register(vq)
+            wait_event_interruptible_timeout(poll_wait,
+                poll_stop || kick_received || completion_pending, timeout)
+            was_kicked = READ_ONCE(kick_received)
+            kick_unregister(vq)
+            put_user(0, &sq->sq_need_wakeup)
+        else:
+            cpu_relax()
+
+5.3 I/O 完成路径
+~~~~~~~~~~~~~~~~~
+
+Poll 模式下 completion 不经过 vhost_worker:
+
+1. **TCM 完成**(中断/workqueue 上下文):``llist_add`` + ``atomic_set(completion_pending, 1)`` + 条件 ``wake_up``
+2. **Poll thread 步骤 0**(poll thread 上下文):``poll_complete`` 回调
+3. **poll_complete 内部**:``llist_del_all`` → ``mutex_lock`` → copy response + ``vhost_add_used`` → ``mutex_unlock`` → ``put_user(cq->idx)`` → 检查 ``cq_need_wakeup`` → ``eventfd_signal``
+
+设计要点:单线程闭环(handle_kick 和 poll_complete 顺序执行,共享 mutex 不会死锁);
+CQ idx 单写者(仅 poll_complete);条件 wake_up(仅在 poll thread 非 RUNNING 时)。
+
+5.4 vhost_enable/disable_notify 在 poll 模式下
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- ``vhost_enable_notify()`` → ``return false``
+- ``vhost_disable_notify()`` → ``return``
+
+通知控制由 sq_need_wakeup / cq_need_wakeup 承担,used ring flags 不再有效。
+Guest 端对称:``virtqueue_kick_prepare()`` 直接返回 true,``disable_cb/enable_cb`` 操作 avail->flags 但 vhost 忽略。
+
+5.5 Guest 接收完成 (CQ 方向)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Guest 轮询线程(``virtio_sqcq_poll_thread``),每 device 一个线程。
+
+主循环要点:
+
+1. ``while (cq_poll_has_work())`` drain 该 vq 所有完成,callback 后 ``do_softirq()``
+2. 有工作时重置 ``last_io_ts = ktime_get()``,不 yield,快速轮询
+3. 无工作时基于 ``last_io_ts`` 时间窗口自旋(``busy_spin_us`` = 1ms)
+4. 自旋中使用 ``need_resched()`` + ``cpu_relax()`` 协作让出(io_uring SQPOLL 模型)
+5. spin 窗口过期后设置所有 vq 的 ``cq_need_wakeup`` + double-check ``more_used()``
+6. 无新工作则 ``schedule_timeout(idle_timeout_us)`` 休眠(1ms)
+7. 醒来后清除所有 vq 的 NEED_WAKEUP,不重置 spin 窗口(超时唤醒不 re-arm spin)
+
+**CQ 完成检测:** 直接使用 ``more_used(vq)`` 检查 used ring,不再轮询 ``cq->idx``。
+``cq->idx`` 仅由 Device 写入,保留用于 NEED_WAKEUP 协议的 ``cq_need_wakeup`` 字段,
+不再作为完成检测的 fast path(消除了 cq->idx 与 used ring 之间的竞态)。
+
+**CQ 访问器函数** (virtio_ring.c):
+
+- ``cq_poll_has_work(vq)``:``more_used(vq)`` — 直接检查 used ring
+- ``cq_set_need_wakeup(vq)``:``smp_store_release(&cq->cq_need_wakeup, 1)``
+- ``cq_clear_need_wakeup(vq)``:``WRITE_ONCE(cq->cq_need_wakeup, 0)``
+- ``cq_recheck(vq)``:``smp_mb()`` + ``more_used(vq)``
+
+5.6 Device 唤醒路径 (kick eventfd 桥接)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+  Guest MMIO kick → QEMU eventfd write → eventfd wq →
+  vhost_sqcq_kick_wakeup() → kick_received=true + wake_up →
+  poll thread → clear sq_need_wakeup → process sq->idx
+
+休眠前通过 ``vhost_sqcq_kick_register()`` 注册到 kick eventfd wait queue。
+关键:``vfs_poll`` 可能因 stale counter 返回 EPOLLIN,必须验证 ``sq->idx``
+确实有新工作才触发回调,否则所有 poll thread busy-loop。
+
+QEMU 端无需额外操作,现有 kick eventfd 路径即可。
+
+
+6. NEED_WAKEUP 协议(核心,必须严格遵守)
+-----------------------------------------
+
+双向对称,SQ 和 CQ 各一套。避免死锁和丢失唤醒的核心机制。
+
+6.1 SQ 方向
+~~~~~~~~~~~
+
+**Device 休眠前:**
+
+.. code-block:: pseudo
+
+    smp_wmb()
+    sq->sq_need_wakeup = 1
+    smp_mb()
+    new_sq_idx = sq->idx           // double-check
+    if (new_sq_idx != last_avail):
+        sq->sq_need_wakeup = 0     // 取消休眠
+        goto process
+    wait_event(kick_received)
+    sq->sq_need_wakeup = 0         // 醒来后清除
+
+**Guest 提交后:**
+
+.. code-block:: pseudo
+
+    smp_store_release(&sq->idx, new_idx)
+    need_wakeup = smp_load_acquire(&sq->sq_need_wakeup)
+    if (need_wakeup)
+        virtqueue_notify(vq)       // MMIO kick
+
+6.2 CQ 方向
+~~~~~~~~~~~
+
+    **Guest 休眠前:**
+
+.. code-block:: pseudo
+
+    smp_store_release(&cq->cq_need_wakeup, 1)
+    smp_mb()
+    if (more_used(vq)):             // double-check used ring
+        cq->cq_need_wakeup = 0     // 取消休眠
+        goto process
+    schedule_timeout(idle_timeout_us)
+    cq->cq_need_wakeup = 0         // 醒来后清除
+
+**Device 完成后(两阶段):**
+
+1. release_cmd:``llist_add`` + ``atomic_set(completion_pending, 1)`` + 条件 ``wake_up``
+2. poll_complete:copy resp → add used → ``put_user(cq->idx)`` → 检查 ``cq_need_wakeup`` → ``eventfd_signal``
+
+6.3 为什么需要 Double-check
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+设置 NEED_WAKEUP → 重读 idx 之间存在竞态:Guest 可能在 Device 读 idx 后、
+sleep 前提交。Device 在 sleep 前重读 idx,如果变了则取消休眠。
+此时 Guest 的 kick 可能已到也可能未到,但 Device 已在处理新工作,不影响正确性。
+
+6.4 内存序总结
+~~~~~~~~~~~~~~
+
+- idx 更新:写入方 store-release(Guest: ``smp_store_release``;Device: ``smp_wmb`` + ``put_user``)
+- idx 读取:Guest 使用 ``more_used()`` 直接检查 used ring(不再轮询 ``cq->idx``)
+- sq_need_wakeup 读取:Guest 使用 ``smp_load_acquire``(替代 ``smp_mb`` + ``READ_ONCE``)
+- NEED_WAKEUP 设置后 ``smp_mb()``,然后重读 used ring / sq->idx
+- NEED_WAKEUP 清除在 sleep 唤醒后立即执行
+- x86 上屏障为 no-op(TSO),ARM/RISC-V 上生成实际屏障指令
+
+
+7. QEMU 实现指南
+----------------
+
+7.1 Feature 协商
+~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+    virtio_add_feature(&vdev->host_features, VIRTIO_F_SQCQ_POLL);
+
+7.2 GPA → HVA 转换
+~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+    hwaddr sq_gpa = (sqe_ring_hi << 32) | sqe_ring_lo;
+    void *sq_hva = address_space_map(&address_space_memory, sq_gpa, sizeof(struct vring_sq), false);
+    // ... cq 类似
+    struct vhost_vring_sqcq_addr addr = { .index = idx, .sq_user_addr = sq_hva, .cq_user_addr = cq_hva };
+    ioctl(vhost_fd, VHOST_SET_VRING_SQCQ_ADDR, &addr);
+
+7.3 ioctl 调用顺序
+~~~~~~~~~~~~~~~~~~
+
+启动:
+
+::
+
+  1. VHOST_SET_VRING_ADDR (desc/avail/used)
+  2. VHOST_SET_VRING_SQCQ_ADDR (sq/cq) — 新增
+  3. VHOST_SET_VRING_KICK
+  4. VHOST_SET_VRING_CALL
+  5. VHOST_SCSI_SET_ENDPOINT — 触发轮询线程启动
+
+停止:
+
+::
+
+  1. VHOST_SCSI_CLEAR_ENDPOINT — 自动停止轮询线程
+  2. VHOST_SET_VRING_KICK (null_fd)
+  3. VHOST_SET_VRING_CALL (null_fd)
+
+7.4 call eventfd 处理
+~~~~~~~~~~~~~~~~~~~~~
+
+收到 eventfd signal 说明 Guest 设置了 cq_need_wakeup,需注入 MSI-X 中断。
+使用现有 ``virtio_irq()`` / ``virtio_notify_irqfd()`` 即可。
+
+即使启用 polling,Device 仍需保留中断能力(CQ NEED_WAKEUP 唤醒、config change、fallback)。
+
+7.5 内存序
+~~~~~~~~~~
+
+QEMU 用户态需使用 ``atomic_store_release`` / ``atomic_load_acquire`` 或 C11 ``stdatomic.h``。
+
+
+8. Host Kernel vhost 端实现
+----------------------------
+
+8.1 vhost_virtqueue 扩展
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+    struct vhost_virtqueue {
+        /* ... 现有字段 ... */
+        /* SQ/CQ polling mode */
+        bool sqcq_poll;
+        struct vring_sq __user *sq;
+        struct vring_cq __user *cq;
+        struct task_struct *poll_task;
+        bool poll_stop;
+        wait_queue_head_t poll_wait;
+        struct completion poll_started, poll_stopped;
+        bool kick_received;
+        atomic_t completion_pending;
+        void (*poll_complete)(struct vhost_virtqueue *vq);
+        wait_queue_head_t *kick_wqh;
+        wait_queue_entry_t kick_wait;
+        poll_table kick_pt;
+        /* 10s 统计窗口和 EMA */
+        u64 ema_latency_ns;     /* EMA of cmd processing latency */
+        u64 ema_interval_ns;    /* EMA of cmd-to-cmd interval */
+        /* ... lat_*/stat_* 统计字段 ... */
+    };
+
+8.2 ioctl 处理
+~~~~~~~~~~~~~~
+
+``vhost_vring_set_sqcq_addr()``:copy_from_user → 验证地址/对齐 → access_ok(backend 已配置时)→ 存储 sq/cq。
+另含自动启动逻辑:当 private_data && sq && cq && !sqcq_poll && poll_complete && feature 时调 ``vhost_sqcq_poll_start()``。
+
+8.3 独立轮询线程
+~~~~~~~~~~~~~~~~~
+
+kthread ``vhost-sqcq-<pid>-<vq_offset>``,通过 ``kthread_use_mm(dev->mm)`` 绑定 QEMU 地址空间。
+直接调用 ``handle_kick`` 处理提交,``poll_complete`` 处理完成,单线程闭环。
+
+API:``vhost_sqcq_poll_start(vq)`` / ``vhost_sqcq_poll_stop(vq)``。
+start 时先创建 kthread 并等待 ``poll_started`` completion,然后才设置 ``sqcq_poll = true``,
+确保 ``vhost_signal()`` 看到 sqcq_poll 时 ``poll_task`` 已有效(消除 NULL deref 窗口)。
+stop 后清除 poll_task、sqcq_poll、sq、cq,flush 残留 completion。
+
+8.4 策略常量与 CPU 绑核
+~~~~~~~~~~~~~~~~~~~~~~~
+
+策略常量(编译期固定,``#define``):
+
+.. code-block:: c
+
+    #define SQCQ_IDLE_TIMEOUT_US_MAX    1000U   /* sleep 时长上界 (us) */
+    #define SQCQ_SLEEP_EMA_MULT         2       /* sleep 时长 = N * ema_latency */
+    #define SQCQ_SLEEP_THRESHOLD_US     20U     /* 快于此时长的后端选择 spin */
+    #define SQCQ_SPIN_EMA_MULT          2       /* inflight spin 窗口 = N * ema_latency */
+    #define SQCQ_ACTIVE_TIMEOUT_NS      1000000U    /* 无 inflight 固定自旋窗口 1ms */
+    #define SQCQ_STALE_TIMEOUT_NS       1000000000ULL /* sq_was_active 兜底清除 1s */
+
+**CPU 绑核**:round-robin(``sqcq_poll_cpu_rr`` 全局原子计数器 ``% num_online_cpus()``),
+分散到不同在线 CPU。计数器取模确保重启后不会溢出到 fallback(全绑 cpu0)。
+
+**协作式自旋**(io_uring SQPOLL 模型):两条自旋路径均使用
+``need_resched()`` / ``cond_resched()`` 协作让出——自旋时若调度器标记需要让出,
+主动 ``cond_resched()``,避免饿死同核其他任务。线程保持 TASK_RUNNING(轮询
+``sq->idx``),不进入 sleep 路径、不设 ``sq_need_wakeup``,从而消除 Guest kick
+(VM exit)。仅在无 inflight 且超过 1ms 无新提交时才真正 sleep。
+
+**TCM 完成 CPU 导向**:``sqcq_poll_active_cpu_mask`` 记录忙 poll 线程占用的 CPU
+(跟着 ``sq_was_active`` 翻转)。``vhost_sqcq_pick_completion_cpu()`` 从空闲 CPU
+中选一个给 TCM 完成,避开忙 poll 核;全占满时回退到 next-CPU。
+
+8.5 vhost_signal / notify
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- ``vhost_signal()``:poll 模式下设 completion_pending + 条件 wake_up。SCSI completion 不经过此路径
+- ``vhost_enable_notify()``:poll 模式下 return false
+- ``vhost_disable_notify()``:poll 模式下 return
+- ``vhost_dev_stop()``:仅对 sqcq_poll vq 加锁停止
+
+8.6 vhost-scsi 集成
+~~~~~~~~~~~~~~~~~~~
+
+``set_endpoint`` 中对 I/O VQs(index >= VHOST_SCSI_VQ_IO):如果 feature + sq + cq 则 start。
+``clear_endpoint`` 中先 stop 再 clear backend。
+
+8.7 call_ctx.ctx NULL 检查
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``poll_complete`` 中 ``eventfd_signal(call_ctx.ctx)`` 前做 NULL 检查。
+QEMU 必须在 SET_ENDPOINT 之前完成 SET_VRING_CALL 设置。
+
+8.8 空闲策略与 EMA
+~~~~~~~~~~~~~~~~~~
+
+系统维护两个独立的 EMA:
+- ``ema_latency_ns``:cmd 处理延迟(submit → process_completions)的 EMA,每次 ``poll_complete`` 后更新 ``old - old/8 + avg/8``(溢出安全)
+- ``ema_interval_ns``:cmd-to-cmd 间隔的 EMA,每次检测到 sq->idx 变化时更新 ``old - old/8 + new/8``
+
+**无 inflight 自旋**(io_uring SQPOLL 模型):固定 ``SQCQ_ACTIVE_TIMEOUT_NS``(1ms)
+窗口 + ``cond_resched()`` 协作让出。线程持续轮询 ``sq->idx`` 接住下一条提交,
+不进入 sleep 路径、不设 ``sq_need_wakeup`` → 消除 Guest kick(VM exit)。
+仅在超过 1ms 无新提交时才 sleep。
+
+**有 inflight 自旋**:``spin_ns = SQCQ_SPIN_EMA_MULT * ema_latency_ns``,
+``cond_resched()`` 协作让出。超出窗口或多 VQ + 高延迟时 sleep。
+
+Sleep 动态超时:``timeout = ema_latency / (1000 / SQCQ_SLEEP_EMA_MULT)``,clamp 到 [10us, SQCQ_IDLE_TIMEOUT_US_MAX]。
+多 VQ 休眠决策:有 inflight 时仅在 ``sqcq_active_io_vqs > 1`` 且 ``ema_latency_us > SQCQ_SLEEP_THRESHOLD_US`` 时休眠。
+
+8.9 统计与诊断
+~~~~~~~~~~~~~~
+
+10s 统计窗口,打印 cq/fail/sq_hits/enters/wkick/wcomp/avg/kick_pct/wait_pct/cqw_pct/spin_pct/wake_avg/wake_n/interval/ema_lat/inflight 到 dmesg。
+Stuck 检测:wait_event 超时或窗口内无完成但有 inflight 时告警。
+
+9. Guest Kernel 端实现
+-----------------------
+
+9.1 SQ/CQ DMA 分配
+~~~~~~~~~~~~~~~~~~
+
+``virtio_alloc_sqcq()``:``dma_alloc_coherent()`` 一次分配 256 bytes(sq+cq 各 128 字节),
+在 ``vring_create_virtqueue_split()`` 中分配,``vring_free`` 时释放。
+
+9.2 地址传递
+~~~~~~~~~~~~
+
+``vp_modern_queue_address_sqcq()``:在 ``vp_active_vq()`` 中,通过 PCI common config
+的 queue_select + sqe/cqe_ring_lo/hi 写入 64 位 DMA 地址。
+
+9.3 提交路径
+~~~~~~~~~~~~
+
+``virtqueue_kick()``:``smp_store_release(&sq->idx, shadow)`` → 如果 NEED_WAKEUP 则 MMIO kick。
+``virtqueue_kick_prepare()``:sqqcq_poll 下直接 return true。
+
+9.4 CQ 轮询线程
+~~~~~~~~~~~~~~~~~
+
+独立 kthread ``virtio-sqcq/<device_name>``,每 device 一个线程(``struct virtio_sqcq_poll_data``)。
+
+**CQ 访问器函数** (virtio_ring.c):
+
+- ``cq_poll_has_work(vq)``:``smp_load_acquire(&cq->idx) != last_cq_head``
+- ``cq_poll_consume(vq)``:snapshot cq->idx(callback 前)
+- ``cq_set_need_wakeup(vq)``:``smp_store_release(&cq->cq_need_wakeup, 1)``
+- ``cq_clear_need_wakeup(vq)``:``WRITE_ONCE(cq->cq_need_wakeup, 0)``
+- ``cq_recheck(vq)``:``smp_mb()`` + ``smp_load_acquire(&cq->idx) != last_cq_head``
+
+**线程生命周期:** ``vp_modern_find_vqs()`` 中启动(``virtio_start_sqcq_poll(vdev, 1000, 1000)``),
+注册所有 poll 活跃的 vq。``vp_del_vqs()`` 中停止(``virtio_stop_sqcq_poll`` 内部释放所有 vq node)。
+
+**启动参数:** ``idle_timeout_us=1000``(1ms sleep 超时),``busy_spin_us=1000``(1ms spin 预算)。
+不绑核(调度器自由分配),无指数退避(固定 1ms sleep)。
+
+**config 空间大小检查:** ``vp_check_common_size()`` 验证 common config 至少 80 字节。
+
+
+10. 与现有机制的关系
+---------------------
+
+- **EVENT_IDX:** 可协商但对 polling vq 无效,被 sq/cq_need_wakeup 替代
+- **virtio-net NAPI:** 可共存,NAPI 可直接检查 CQ
+- **blk-mq iopoll:** 互补(iopoll 同步快路径,SQ/CQ 替代异步中断路径)
+- **Live Migration:** reset 时回退中断模式,迁移后 Guest 重新协商
+
+
+11. 文件清单
+------------
+
+**Guest Kernel:** ``virtio_config.h`` (feature bit), ``virtio_ring.h`` (vring_sq/cq),
+``virtio_pci.h`` (PCI offsets), ``virtio_ring.c`` (分配/SQ提交/CQ accessor),
+``virtio_sqcq_poll.c`` (CQ 轮询线程), ``virtio_pci_modern.c`` (feature/queue激活/线程生命周期),
+``virtio_pci_common.c`` (vp_del_vqs), ``virtio_pci_modern_dev.c`` (PCI config 映射)
+
+**Host Kernel vhost:** ``vhost.h`` (ioctl), ``vhost_types.h`` (sqcq_addr struct),
+``vhost.h/vhost.c`` (轮询线程/ioctl/signal/生命周期), ``scsi.c`` (vhost-scsi 集成)
+
+**QEMU:** ``virtio-pci.c`` (feature/config), ``virtio-pci-modern.c`` (BAR/GPA→HVA)
+
+
+12. 验证方案
+------------
+
+**功能:** QEMU 带 VIRTIO_F_SQCQ_POLL + virtio-scsi → Guest 读写验证正确性。
+
+**性能:** fio 对比中断模式 vs polling,观察 IOPS/延迟/VM exit 次数。
+
+**正确性:** 24h+ stress test;idle/wakeup 验证;NEED_WAKEUP 边界条件;ARM/RISC-V 内存序验证。
diff --git a/Documentation/virtio-sqcq-poll-virtio-spec.rst b/Documentation/virtio-sqcq-poll-virtio-spec.rst
new file mode 100644
index 0000000000000..26ae41860a8e5
--- /dev/null
+++ b/Documentation/virtio-sqcq-poll-virtio-spec.rst
@@ -0,0 +1,355 @@
+SQ/CQ Doorbell Polling
+======================
+
+This section describes the SQ/CQ doorbell polling mechanism
+(``VIRTIO_F_SQCQ_POLL``), which allows the driver and device to use
+shared-memory doorbells instead of MMIO writes (notifications) and
+interrupts, eliminating VM exits on both submission and completion paths.
+
+This feature is only supported with split virtqueues. A driver or
+device that negotiates this feature MUST NOT also negotiate
+``VIRTIO_F_RING_PACKED``.
+
+
+Feature bit
+-----------
+
+``VIRTIO_F_SQCQ_POLL (42)`` — SQ/CQ Doorbell Polling.
+
+This feature is in the transport feature range (28..43). Both
+``virtio_ring`` and ``virtio_pci`` MUST accept this feature for it
+to be negotiated.
+
+
+Data Structures
+---------------
+
+SQ Doorbell
+~~~~~~~~~~~
+
+The SQ (Submission Queue) doorbell is a cache-line aligned structure
+written by the driver and read by the device:
+
+.. code-block:: c
+
+    struct vring_sq {
+        le64 idx;
+        u8   sq_need_wakeup;
+        u8   reserved[55];
+    };
+
+The structure MUST be aligned to the cache line size of the platform
+(minimum 64 bytes; 128 bytes recommended for platforms with 128-byte
+cache lines such as ARM Neoverse N2/V2).
+
+``idx`` corresponds to ``avail->idx`` and is written by the driver
+using ``smp_store_release()``.
+
+``sq_need_wakeup`` is a single-byte flag written by the device,
+indicating whether the device's poll thread is sleeping and needs to
+be woken by an MMIO write (kick).
+
+CQ Doorbell
+~~~~~~~~~~~
+
+The CQ (Completion Queue) doorbell is a cache-line aligned structure
+written by the device and read by the driver:
+
+.. code-block:: c
+
+    struct vring_cq {
+        le64 idx;
+        u8   cq_need_wakeup;
+        u8   reserved[55];
+    };
+
+The structure MUST be aligned to the same alignment as ``vring_sq``.
+
+``idx`` corresponds to ``used->idx`` and is written by the device.
+
+``cq_need_wakeup`` is a single-byte flag written by the driver,
+indicating whether the driver's poll thread is sleeping and needs to
+be woken by an interrupt.
+
+
+PCI Configuration
+-----------------
+
+The SQ and CQ doorbell DMA addresses are passed through the PCI
+common configuration space. Four new registers are appended to
+``struct virtio_pci_modern_common_cfg``:
+
+==============  ========  ========================================
+Offset          Field     Description
+==============  ========  ========================================
+64              sqe_ring_lo  SQ doorbell DMA address, low 32 bits
+68              sqe_ring_hi  SQ doorbell DMA address, high 32 bits
+72              cqe_ring_lo  CQ doorbell DMA address, low 32 bits
+76              cqe_ring_hi  CQ doorbell DMA address, high 32 bits
+==============  ========  ========================================
+
+These registers are scoped by ``queue_select``. The driver writes
+the SQ and CQ doorbell DMA addresses for each virtqueue during
+``vp_active_vq``.
+
+The device (QEMU) translates the guest physical address to a host
+virtual address and passes it to the vhost backend via
+``VHOST_SET_VRING_SQCQ_ADDR`` ioctl.
+
+
+Allocation
+----------
+
+The driver allocates SQ and CQ doorbells as a single
+``dma_alloc_coherent()`` call (contiguous physical memory):
+
+.. code-block:: c
+
+    size_t total = sizeof(struct vring_sq) + sizeof(struct vring_cq);
+    void *buf = dma_alloc_coherent(dev, total, &dma_addr, GFP_KERNEL);
+    vq->sq = buf;
+    vq->cq = buf + sizeof(struct vring_sq);
+
+Control and event virtqueues MUST NOT use SQ/CQ polling. Only request
+virtqueues (index >= VIRTIO_SCSI_VQ_BASE for virtio-scsi) negotiate
+this feature.
+
+
+Submission Path (Driver → Device)
+---------------------------------
+
+When the driver has new requests available:
+
+1. The driver writes descriptors to the avail ring as usual
+2. The driver updates ``sq->idx`` using ``smp_store_release()``
+3. The driver reads ``sq_need_wakeup`` using ``smp_load_acquire()``
+
+If ``sq_need_wakeup`` is 0, the device's poll thread is actively
+spinning and will detect the new ``sq->idx`` value without any
+further action.
+
+If ``sq_need_wakeup`` is 1, the device's poll thread is sleeping.
+The driver MUST perform a traditional MMIO write (kick) to wake it.
+
+.. code-block:: pseudo
+
+    smp_store_release(&sq->idx, avail_idx_shadow)
+    need_wakeup = smp_load_acquire(&sq->sq_need_wakeup)
+    if (need_wakeup):
+        mmio_kick(vq)
+
+The ``virtqueue_kick_prepare()`` function MUST return ``true``
+unconditionally when SQ/CQ polling is active, bypassing the
+``used->flags`` check.
+
+
+Completion Path (Device → Driver)
+---------------------------------
+
+Device Side
+~~~~~~~~~~~
+
+When the device processes completions:
+
+1. The device writes completion entries to the used ring as usual
+2. The device's poll thread writes ``cq->idx`` to signal completion
+
+The device's poll thread is a dedicated kernel thread that processes
+both submissions (via ``handle_kick``) and completions (via
+``poll_complete`` callback) in a single-threaded loop.
+
+Driver Side
+~~~~~~~~~~~
+
+The driver's poll thread (one per device) detects completions by
+checking ``more_used()`` — directly comparing ``used->idx`` with
+``last_used_idx``:
+
+.. code-block:: pseudo
+
+    while more_used(vq):
+        consume_and_callback(vq)
+        do_softirq()
+
+Note: The driver polls ``used->idx`` directly rather than ``cq->idx``,
+because ``cq->idx`` may lag behind ``used->idx`` (the device writes
+the used ring entries before updating ``cq->idx``). Polling
+``used->idx`` ensures the lowest-latency completion detection.
+
+``cq->idx`` is retained for the ``cq_need_wakeup`` sleep/wake
+protocol (see below) but is not polled as a fast-path completion
+detector.
+
+
+NEED_WAKEUP Protocol
+--------------------
+
+The NEED_WAKEUP protocol prevents lost wakeups when either side
+transitions from polling to sleeping. It is symmetric: SQ uses
+``sq_need_wakeup`` and CQ uses ``cq_need_wakeup``.
+
+SQ NEED_WAKEUP (Device sleeping)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before the device's poll thread sleeps:
+
+1. Set ``sq_need_wakeup = 1`` (via ``put_user``)
+2. Execute ``smp_mb()``
+3. Re-read ``sq->idx`` — if it changed, cancel sleep
+
+After waking (via eventfd or timeout):
+- Clear ``sq_need_wakeup = 0``
+
+The driver checks ``sq_need_wakeup`` after writing ``sq->idx`` and
+performs an MMIO kick only if the flag is set.
+
+CQ NEED_WAKEUP (Driver sleeping)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before the driver's poll thread sleeps:
+
+1. Set ``cq_need_wakeup = 1`` (via ``smp_store_release``)
+2. Execute ``smp_mb()``
+3. Re-check ``more_used()`` — if work found, cancel sleep
+
+After waking (via eventfd/interrupt or timeout):
+- Clear ``cq_need_wakeup = 0``
+
+The device checks ``cq_need_wakeup`` after writing ``cq->idx`` and
+sends an eventfd signal only if the flag is set.
+
+
+Memory Ordering
+---------------
+
+The following memory ordering rules MUST be observed:
+
+====================  ============================  ============================
+Field                 Writer                       Reader
+====================  ============================  ============================
+``sq->idx``           ``smp_store_release``         ``get_user`` + ``smp_rmb``
+``sq_need_wakeup``    ``put_user`` + ``smp_wmb``    ``smp_load_acquire``
+``cq->idx``           ``put_user`` (with ``smp_wmb``) Not polled (see note)
+``cq_need_wakeup``    ``smp_store_release``         ``get_user`` + ``smp_rmb``
+``used->idx``         Device (via ``vhost_add_used``) ``more_used()`` (plain read)
+====================  ============================  ============================
+
+Note: ``cq->idx`` is written by the device but is not polled by the
+driver's fast path. The driver uses ``more_used()`` (checking
+``used->idx``) for completion detection, which is the authoritative
+source of completions.
+
+On x86 (TSO memory model), all barriers are no-ops. On ARM64 and
+RISC-V, ``smp_store_release`` generates ``stlr`` and
+``smp_load_acquire`` generates ``ldar``, which are lighter than full
+memory barriers (``dmb ish``).
+
+
+Poll Thread Behavior
+--------------------
+
+Device Poll Thread (vhost)
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One kernel thread per virtqueue. Each thread:
+
+1. Polls ``sq->idx`` for new submissions and ``completion_pending``
+   for completions
+2. When active: busy-spins using ``need_resched()`` + ``cpu_relax()``
+   for cooperative yielding (io_uring SQPOLL pattern)
+3. Spin budget: fixed 1ms (``SQCQ_ACTIVE_TIMEOUT_NS``) for no-inflight,
+   or ``2 × ema_latency`` for has-inflight
+4. After spin budget expires: sets ``sq_need_wakeup``, sleeps via
+   ``wait_event_interruptible_timeout``
+5. Sleep timeout: adaptive, based on EMA latency, clamped to [10us, 1ms]
+
+CPU affinity: round-robin across online CPUs
+(``atomic_counter % num_online_cpus()``).
+
+Driver Poll Thread
+~~~~~~~~~~~~~~~~~~
+
+One kernel thread per device (serves all registered virtqueues). The
+thread:
+
+1. Iterates all registered virtqueues, polls ``more_used()`` for each
+2. When active: busy-spins within a time budget (``busy_spin_us = 1ms``)
+   from ``last_io_ts``, using ``need_resched()`` + ``cpu_relax()``
+3. After spin budget expires: sets ``cq_need_wakeup`` on all VQs,
+   double-checks ``more_used()``, sleeps via ``schedule_timeout``
+4. Sleep timeout: fixed 1ms (``idle_timeout_us = 1000``)
+5. ``do_softirq()`` called after each completion callback (no hardware
+   interrupts means softirqs must be explicitly flushed)
+
+Parameters (configurable at thread creation):
+
+- ``idle_timeout_us``: sleep timeout in microseconds (default: 1000)
+- ``busy_spin_us``: spin budget in microseconds (default: 1000)
+
+
+Feature Negotiation
+-------------------
+
+1. The device offers ``VIRTIO_F_SQCQ_POLL`` in its feature bits
+2. The driver accepts the feature during ``finalize_features``
+3. The driver allocates SQ/CQ doorbells in ``find_vqs``
+4. The driver writes SQ/CQ DMA addresses to PCI config space
+5. The device (vhost) receives addresses and starts poll threads
+6. ``virtio_features_ok()`` MUST reject
+   ``VIRTIO_F_SQCQ_POLL | VIRTIO_F_RING_PACKED``
+
+If either side does not support the feature, traditional MMIO kick +
+MSI-X interrupt mode is used with zero overhead.
+
+
+Mutual Exclusion
+----------------
+
+``VIRTIO_F_SQCQ_POLL`` and ``VIRTIO_F_RING_PACKED`` MUST NOT be
+negotiated simultaneously. The driver's ``virtio_features_ok()``
+function rejects this combination.
+
+This restriction exists because the SQ/CQ doorbell implementation
+accesses split-ring-specific fields (``avail_idx_shadow``,
+``vring->used->idx``) unconditionally in ``virtqueue_notify()``.
+Packed ring support is a future enhancement.
+
+
+Relationship to Existing Mechanisms
+-----------------------------------
+
+- **EVENT_IDX** (``VIRTIO_RING_F_EVENT_IDX``): may be negotiated but
+  has no effect on polling virtqueues. Notification suppression is
+  handled by ``sq_need_wakeup`` / ``cq_need_wakeup`` instead.
+
+- **blk-mq iopoll**: complementary. iopoll handles synchronous
+  polling from the submitting task; SQ/CQ replaces the asynchronous
+  interrupt path.
+
+- **vhost-vDPA**: vDPA provides its own doorbell mmap mechanism.
+  SQ/CQ polling is specific to the vhost-scsi kernel backend and
+  does not require vDPA hardware support.
+
+
+Performance Characteristics
+---------------------------
+
+Benchmark results (fio, 4K random I/O, arm64 with 8 vCPUs):
+
+============================  ============  ===========  =========
+Test                          Baseline      SQ/CQ Poll   Change
+============================  ============  ===========  =========
+randread od1 nj1              22,427 IOPS   28,289 IOPS  +26.1%
+randread od32 nj1             89,910 IOPS   75,665 IOPS  -15.8%
+randread od32 nj4             186,763 IOPS  379,549 IOPS +103.2%
+randread od32 nj8             199,967 IOPS  550,633 IOPS +175.4%
+randwrite od1 nj1             21,912 IOPS   27,261 IOPS  +24.4%
+randwrite od32 nj1            85,349 IOPS   81,389 IOPS   -4.6%
+randwrite od32 nj4            190,443 IOPS  355,811 IOPS  +86.8%
+randwrite od32 nj8            196,552 IOPS  566,640 IOPS +188.3%
+============================  ============  ===========  =========
+
+Multi-queue workloads (nj4/nj8) see 87-188% improvement from
+eliminated VM exits on both submission and completion paths.
+Single-queue high-queue-depth workloads (od32 nj1) show a
+minor regression due to polling overhead vs. the VM exit savings.
diff --git a/build-riscv-kernel.sh b/build-riscv-kernel.sh
new file mode 100644
index 0000000000000..2ace46278369e
--- /dev/null
+++ b/build-riscv-kernel.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# 设置变量
+export ARCH=riscv
+export CROSS_COMPILE=riscv64-linux-gnu-
+export KERNEL_DIR=$(pwd)
+export OUTPUT_DIR="${KERNEL_DIR}/output"
+
+# 清理
+make mrproper
+
+# 配置
+make defconfig
+# 或使用特定配置
+# cp ${CONFIG_FILE} .config
+
+# 编译内核
+echo "开始编译内核..."
+make -j$(nproc) || exit 1
+
+# 编译设备树
+echo "编译设备树..."
+make dtbs || exit 1
+
+echo "编译完成!"
+echo "内核镜像: ${KERNEL_DIR}/arch/riscv/boot/Image"
diff --git a/drivers/vhost/scsi.c b/drivers/vhost/scsi.c
index 1c22880e72267..4d65205fcb23e 100644
--- a/drivers/vhost/scsi.c
+++ b/drivers/vhost/scsi.c
@@ -142,6 +142,7 @@ struct vhost_scsi_cmd {
 	struct llist_node tvc_completion_list;
 	/* Used to track inflight cmd */
 	struct vhost_scsi_inflight *inflight;
+	ktime_t tvc_submit_ts;
 };
 
 struct vhost_scsi_nexus {
@@ -234,6 +235,7 @@ struct vhost_scsi_virtqueue {
 
 	struct vhost_work completion_work;
 	struct llist_head completion_list;
+	ktime_t stats_window;
 };
 
 struct vhost_scsi {
@@ -490,9 +492,18 @@ static void vhost_scsi_release_cmd(struct se_cmd *se_cmd)
 		struct vhost_scsi_virtqueue *svq =  container_of(cmd->tvc_vq,
 					struct vhost_scsi_virtqueue, vq);
 
-		llist_add(&cmd->tvc_completion_list, &svq->completion_list);
-		if (!vhost_vq_work_queue(&svq->vq, &svq->completion_work))
-			vhost_scsi_drop_cmds(svq);
+			llist_add(&cmd->tvc_completion_list, &svq->completion_list);
+			if (READ_ONCE(svq->vq.sqcq_poll)) {
+				smp_wmb();
+				atomic_set(&svq->vq.completion_pending, 1);
+				smp_mb();
+				if (!READ_ONCE(svq->vq.poll_stop) &&
+				    READ_ONCE(svq->vq.poll_task) &&
+				    !task_is_running(svq->vq.poll_task))
+					wake_up(&svq->vq.poll_wait);
+			} else if (!vhost_vq_work_queue(&svq->vq, &svq->completion_work)) {
+				vhost_scsi_drop_cmds(svq);
+			}
 	}
 }
 
@@ -678,15 +689,11 @@ static int vhost_scsi_copy_sgl_to_iov(struct vhost_scsi_cmd *cmd)
 	return 0;
 }
 
-/* Fill in status and signal that we are done processing this command
- *
- * This is scheduled in the vhost work queue so we are called with the owner
- * process mm and can access the vring.
+/* Process completion_list: copy responses, add used entries, release cmds.
+ * Returns true if any completions were signalled.
  */
-static void vhost_scsi_complete_cmd_work(struct vhost_work *work)
+static bool vhost_scsi_process_completions(struct vhost_scsi_virtqueue *svq)
 {
-	struct vhost_scsi_virtqueue *svq = container_of(work,
-				struct vhost_scsi_virtqueue, completion_work);
 	struct virtio_scsi_cmd_resp v_rsp;
 	struct vhost_scsi_cmd *cmd, *t;
 	struct llist_node *llnode;
@@ -726,6 +733,9 @@ static void vhost_scsi_complete_cmd_work(struct vhost_work *work)
 			signal = true;
 
 			vhost_add_used(cmd->tvc_vq, cmd->tvc_vq_desc, 0);
+			cmd->tvc_vq->lat_scsi_ns +=
+				ktime_to_ns(ktime_sub(ktime_get(), cmd->tvc_submit_ts));
+			cmd->tvc_vq->stat_completions++;
 		} else
 			pr_err("Faulted on virtio_scsi_cmd_resp\n");
 
@@ -737,10 +747,70 @@ static void vhost_scsi_complete_cmd_work(struct vhost_work *work)
 
 	mutex_unlock(&svq->vq.mutex);
 
+	return signal;
+}
+
+/* Worker-path completion (non-poll mode only) */
+static void vhost_scsi_complete_cmd_work(struct vhost_work *work)
+{
+	struct vhost_scsi_virtqueue *svq = container_of(work,
+				struct vhost_scsi_virtqueue, completion_work);
+	struct vhost_virtqueue *vq = &svq->vq;
+	bool signal;
+
+	signal = vhost_scsi_process_completions(svq);
+
 	if (signal)
 		vhost_signal(&svq->vs->dev, &svq->vq);
+
+	if (vq->stat_completions > 0) {
+		u64 now = ktime_get_ns();
+
+		if (!svq->stats_window)
+			svq->stats_window = ns_to_ktime(now);
+
+		if (now - ktime_to_ns(svq->stats_window) >= 10 * NSEC_PER_SEC) {
+			int vi;
+
+			for (vi = 0; vi < svq->vs->dev.nvqs; vi++)
+				if (svq->vs->dev.vqs[vi] == vq)
+					break;
+			pr_info("vhost-baseline: vq[%d] cmd=%llu avg_lat=%lluns\n",
+			       vi, vq->stat_completions,
+			       div64_u64(vq->lat_scsi_ns, vq->stat_completions));
+			svq->stats_window = ns_to_ktime(now);
+			vq->lat_scsi_ns = 0;
+			vq->stat_completions = 0;
+		}
+	}
 }
 
+/* Poll-thread-path completion: write cq->idx and signal Guest directly */
+static void vhost_scsi_poll_complete(struct vhost_virtqueue *vq)
+{
+	struct vhost_scsi_virtqueue *svq = container_of(vq,
+				struct vhost_scsi_virtqueue, vq);
+	bool signal;
+
+	signal = vhost_scsi_process_completions(svq);
+
+	if (signal && svq->vq.cq) {
+		__virtio64 cq_idx_val;
+		__u8 cq_need_wakeup;
+
+		smp_mb();
+		cq_idx_val = (__force __virtio64)READ_ONCE(svq->vq.last_used_idx);
+		put_user(cq_idx_val, &svq->vq.cq->idx);
+		smp_mb(); /* Ensure cq->idx is visible before reading cq_need_wakeup */
+		if (!get_user(cq_need_wakeup, &svq->vq.cq->cq_need_wakeup)) {
+			smp_rmb();
+			if (cq_need_wakeup && svq->vq.call_ctx.ctx)
+				eventfd_signal(svq->vq.call_ctx.ctx);
+		}
+	}
+}
+
+
 static struct vhost_scsi_cmd *
 vhost_scsi_get_cmd(struct vhost_virtqueue *vq, u64 scsi_tag)
 {
@@ -1060,9 +1130,25 @@ static void vhost_scsi_target_queue_cmd(struct vhost_scsi_nexus *nexus,
 	}
 
 	se_cmd->tag = 0;
+	cmd->tvc_submit_ts = ktime_get();
 	target_init_cmd(se_cmd, nexus->tvn_se_sess, &cmd->tvc_sense_buf[0],
 			lun, exp_data_len, vhost_scsi_to_tcm_attr(task_attr),
-			data_dir, TARGET_SCF_ACK_KREF);
+			data_dir,
+			TARGET_SCF_ACK_KREF |
+			(READ_ONCE(cmd->tvc_vq->sqcq_poll) ?
+					TARGET_SCF_USE_CPUID : 0));
+
+	/* Poll mode: steer TCM completion to next CPU to avoid
+	 * starvation behind poll thread. Skip if fabric has
+	 * custom cmd_compl_affinity (-2=UNBOUND or specific CPU).
+	 */
+	if (READ_ONCE(cmd->tvc_vq->sqcq_poll) && num_online_cpus() > 1) {
+		struct se_wwn *wwn = se_cmd->se_sess->se_tpg->se_tpg_wwn;
+
+		if (!wwn || wwn->cmd_compl_affinity == SE_COMPL_AFFINITY_CPUID)
+			se_cmd->cpuid =
+				vhost_sqcq_pick_completion_cpu(smp_processor_id());
+	}
 
 	if (target_submit_prep(se_cmd, cdb, sg_ptr,
 			       cmd->tvc_sgl_count, NULL, 0, sg_prot_ptr,
@@ -1346,7 +1432,6 @@ vhost_scsi_handle_vq(struct vhost_scsi *vs, struct vhost_virtqueue *vq)
 		ret = vhost_scsi_chk_size(vq, &vc);
 		if (ret)
 			goto err;
-
 		ret = vhost_scsi_get_req(vq, &vc, &tpg);
 		if (ret)
 			goto err;
@@ -2070,6 +2155,15 @@ vhost_scsi_set_endpoint(struct vhost_scsi *vs,
 			mutex_lock(&vq->mutex);
 			vhost_vq_set_backend(vq, vs_tpg);
 			vhost_vq_init_access(vq);
+			if (i >= VHOST_SCSI_VQ_IO &&
+			    vhost_has_feature(vq, VIRTIO_F_SQCQ_POLL) &&
+			    vq->sq && vq->cq) {
+				ret = vhost_sqcq_poll_start(vq);
+				if (ret) {
+					mutex_unlock(&vq->mutex);
+					goto stop_poll_threads;
+				}
+			}
 			mutex_unlock(&vq->mutex);
 		}
 		ret = 0;
@@ -2086,6 +2180,19 @@ vhost_scsi_set_endpoint(struct vhost_scsi *vs,
 	vs->vs_tpg = vs_tpg;
 	goto out;
 
+stop_poll_threads:
+	/* VQ at index i failed to start. Stop poll threads for all
+	 * previously started VQs (indices i-1 down to VHOST_SCSI_VQ_IO).
+	 */
+	for (i = i - 1; i >= VHOST_SCSI_VQ_IO; i--) {
+		vq = &vs->vqs[i].vq;
+		mutex_lock(&vq->mutex);
+		vhost_sqcq_poll_stop(vq);
+		vhost_vq_set_backend(vq, NULL);
+		mutex_unlock(&vq->mutex);
+	}
+	/* Reset i for full vq cmds cleanup */
+	i = vs->dev.nvqs;
 destroy_vq_cmds:
 	for (i--; i >= VHOST_SCSI_VQ_IO; i--) {
 		if (!vhost_vq_get_backend(&vs->vqs[i].vq))
@@ -2163,9 +2270,17 @@ vhost_scsi_clear_endpoint(struct vhost_scsi *vs,
 	/* Prevent new cmds from starting and accessing the tpgs/sessions */
 	for (i = 0; i < vs->dev.nvqs; i++) {
 		vq = &vs->vqs[i].vq;
-		mutex_lock(&vq->mutex);
-		vhost_vq_set_backend(vq, NULL);
-		mutex_unlock(&vq->mutex);
+		if (!READ_ONCE(vq->sqcq_poll)) {
+			mutex_lock(&vq->mutex);
+			vhost_vq_set_backend(vq, NULL);
+			mutex_unlock(&vq->mutex);
+		} else {
+			mutex_lock(&vq->mutex);
+			vhost_sqcq_poll_stop(vq);
+			vhost_vq_set_backend(vq, NULL);
+			mutex_unlock(&vq->mutex);
+			vhost_sqcq_poll_flush(vq);
+		}
 	}
 	/* Make sure cmds are not running before tearing them down. */
 	vhost_scsi_flush(vs);
@@ -2221,6 +2336,7 @@ static int vhost_scsi_set_features(struct vhost_scsi *vs, u64 features)
 	bool is_log, was_log;
 	int i;
 
+
 	if (features & ~VHOST_SCSI_FEATURES)
 		return -EOPNOTSUPP;
 
@@ -2321,6 +2437,7 @@ static int vhost_scsi_open(struct inode *inode, struct file *f)
 		vhost_work_init(&svq->completion_work,
 				vhost_scsi_complete_cmd_work);
 		svq->vq.handle_kick = vhost_scsi_handle_kick;
+		svq->vq.poll_complete = vhost_scsi_poll_complete;
 	}
 	vhost_dev_init(&vs->dev, vqs, nvqs, UIO_MAXIOV,
 		       VHOST_SCSI_WEIGHT, 0, true, NULL);
@@ -2415,6 +2532,45 @@ vhost_scsi_ioctl(struct file *f,
 		if (copy_from_user(&features, featurep, sizeof features))
 			return -EFAULT;
 		return vhost_scsi_set_features(vs, features);
+	case VHOST_GET_FEATURES_ARRAY: {
+		u64 count, copied;
+		u64 farr[VIRTIO_FEATURES_U64S] = {};
+
+		if (get_user(count, (u64 __user *)argp))
+			return -EFAULT;
+		copied = min_t(u64, count, VIRTIO_FEATURES_U64S);
+		farr[0] = VHOST_SCSI_FEATURES;
+		if (copy_to_user((u64 __user *)(argp + sizeof(u64)),
+				 farr, copied * sizeof(u64)))
+			return -EFAULT;
+		if (count > copied &&
+		    clear_user((u64 __user *)(argp + sizeof(u64) +
+					     copied * sizeof(u64)),
+			       (count - copied) * sizeof(u64)))
+			return -EFAULT;
+		return 0;
+	}
+	case VHOST_SET_FEATURES_ARRAY: {
+		u64 count, copied, all_features[VIRTIO_FEATURES_U64S] = {};
+		int i;
+
+		if (get_user(count, (u64 __user *)argp))
+			return -EFAULT;
+		copied = min_t(u64, count, VIRTIO_FEATURES_U64S);
+		if (copy_from_user(all_features,
+				   (u64 __user *)(argp + sizeof(u64)),
+				   copied * sizeof(u64)))
+			return -EFAULT;
+		for (i = copied; i < count; i++) {
+			u64 tmp;
+			if (get_user(tmp, (u64 __user *)(argp +
+					sizeof(u64) + i * sizeof(u64))))
+				return -EFAULT;
+			if (tmp)
+				return -EOPNOTSUPP;
+		}
+		return vhost_scsi_set_features(vs, all_features[0]);
+	}
 	case VHOST_NEW_WORKER:
 	case VHOST_FREE_WORKER:
 	case VHOST_ATTACH_VRING_WORKER:
@@ -2424,6 +2580,7 @@ vhost_scsi_ioctl(struct file *f,
 		mutex_unlock(&vs->dev.mutex);
 		return r;
 	default:
+
 		mutex_lock(&vs->dev.mutex);
 		r = vhost_dev_ioctl(&vs->dev, ioctl, argp);
 		/* TODO: flush backend after dev ioctl. */
diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c
index 2f2c45d208832..68fb53c40b2e6 100644
--- a/drivers/vhost/vhost.c
+++ b/drivers/vhost/vhost.c
@@ -42,6 +42,20 @@ static int max_iotlb_entries = 2048;
 module_param(max_iotlb_entries, int, 0444);
 MODULE_PARM_DESC(max_iotlb_entries,
 	"Maximum number of iotlb entries. (default: 2048)");
+#define SQCQ_IDLE_TIMEOUT_US_MAX	1000U
+#define SQCQ_SLEEP_EMA_MULT		2
+#define SQCQ_SPIN_EMA_MULT		2
+#define SQCQ_ACTIVE_TIMEOUT_NS		1000000U	/* cap active-VQ clear at 1 ms */
+#define SQCQ_STALE_TIMEOUT_NS		1000000000ULL	/* backstop EMA/active clear at 1 s */
+
+static atomic_t sqcq_poll_cpu_rr = ATOMIC_INIT(-1);
+static atomic_t sqcq_active_io_vqs = ATOMIC_INIT(0);
+/* CPUs with a BUSY poll thread (actively processing cmds); TCM completion
+ * steers away.  Idle (sleeping) poll threads don't occupy their CPU, so
+ * their bit is cleared via the sq_was_active transitions in the poll loop.
+ */
+static cpumask_t sqcq_poll_active_cpu_mask;
+
 static bool fork_from_owner_default = VHOST_FORK_OWNER_TASK;
 
 #ifdef CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL
@@ -390,6 +404,20 @@ static void vhost_vq_reset(struct vhost_dev *dev,
 	vhost_disable_cross_endian(vq);
 	vhost_reset_is_le(vq);
 	vq->busyloop_timeout = 0;
+	WRITE_ONCE(vq->sqcq_poll, false);
+	vq->sq = NULL;
+	vq->cq = NULL;
+	vq->poll_task = NULL;
+	WRITE_ONCE(vq->poll_stop, false);
+	init_waitqueue_head(&vq->poll_wait);
+	init_completion(&vq->poll_started);
+	init_completion(&vq->poll_stopped);
+	WRITE_ONCE(vq->kick_received, false);
+	atomic_set(&vq->completion_pending, 0);
+	vq->kick_wqh = NULL;
+	vq->stat_completions = 0;
+	vq->lat_scsi_ns = 0;
+	vq->poll_cpu = -1;
 	vq->umem = NULL;
 	vq->iotlb = NULL;
 	rcu_assign_pointer(vq->worker, NULL);
@@ -1168,6 +1196,15 @@ void vhost_dev_stop(struct vhost_dev *dev)
 {
 	int i;
 
+	for (i = 0; i < dev->nvqs; ++i) {
+		if (!READ_ONCE(dev->vqs[i]->sqcq_poll))
+			continue;
+		mutex_lock(&dev->vqs[i]->mutex);
+		vhost_sqcq_poll_stop(dev->vqs[i]);
+		mutex_unlock(&dev->vqs[i]->mutex);
+		vhost_sqcq_poll_flush(dev->vqs[i]);
+	}
+
 	for (i = 0; i < dev->nvqs; ++i) {
 		if (dev->vqs[i]->kick && dev->vqs[i]->handle_kick)
 			vhost_poll_stop(&dev->vqs[i]->poll);
@@ -2051,6 +2088,535 @@ static long vhost_vring_set_num(struct vhost_dev *d,
 	return 0;
 }
 
+static int vhost_sqcq_kick_wakeup(wait_queue_entry_t *wait, unsigned int mode,
+				   int sync, void *key)
+{
+	struct vhost_virtqueue *vq = container_of(wait,
+		struct vhost_virtqueue, kick_wait);
+
+	if (!(key_to_poll(key) & EPOLLIN))
+		return 0;
+
+	WRITE_ONCE(vq->kick_received, true);
+	wake_up(&vq->poll_wait);
+	return 0;
+}
+
+static void vhost_sqcq_kick_ptable_func(struct file *file,
+					 wait_queue_head_t *wqh,
+					 poll_table *pt)
+{
+	struct vhost_virtqueue *vq = container_of(pt,
+		struct vhost_virtqueue, kick_pt);
+
+	vq->kick_wqh = wqh;
+	add_wait_queue(wqh, &vq->kick_wait);
+}
+
+static void vhost_sqcq_kick_register(struct vhost_virtqueue *vq)
+{
+	init_waitqueue_func_entry(&vq->kick_wait, vhost_sqcq_kick_wakeup);
+	vq->kick_wqh = NULL;
+
+	if (vq->kick) {
+		__poll_t mask;
+
+		init_poll_funcptr(&vq->kick_pt, vhost_sqcq_kick_ptable_func);
+		mask = vfs_poll(vq->kick, &vq->kick_pt);
+		/* eventfd counter is never consumed; verify stale EPOLLIN
+		 * against sq->idx before acting; real kicks wake via
+		 * the registered wait queue.
+		 */
+		if (mask & EPOLLIN) {
+			__virtio64 sq_val;
+
+			if (vq->sq && !get_user(sq_val, &vq->sq->idx)) {
+				smp_rmb();
+				if ((__force u16)sq_val != vq->last_avail_idx)
+					vhost_sqcq_kick_wakeup(
+						&vq->kick_wait, 0, 0,
+						poll_to_key(mask));
+			}
+		}
+	}
+}
+
+static void vhost_sqcq_kick_unregister(struct vhost_virtqueue *vq)
+{
+	if (vq->kick_wqh) {
+		remove_wait_queue(vq->kick_wqh, &vq->kick_wait);
+		vq->kick_wqh = NULL;
+	}
+	WRITE_ONCE(vq->kick_received, false);
+}
+
+static int vhost_sqcq_poll_thread(void *data)
+{
+	struct vhost_virtqueue *vq = data;
+	struct vhost_dev *dev = vq->dev;
+	u16 new_sq_idx;
+	__virtio64 sq_idx_val;
+	__u8 sq_need_wakeup_val = 0;
+	ktime_t ts2;
+	ktime_t last_io_ts = 0;
+	ktime_t stats_window = ktime_get();
+	u64 stats_cq = 0;
+	u64 stats_lat = 0;
+	u64 stats_inflight_sum = 0;
+	u64 stats_inflight_n = 0;
+	u64 stats_multi_cnt = 0;
+	u64 stats_sleep_cnt = 0;
+	u64 stats_wake_kick = 0;
+	u64 stats_wake_complete = 0;
+	u64 stats_wake_timeout = 0;
+	u64 ema_ns_val;
+	u64 spin_ns_val;
+	unsigned long _timeout_jiffies;
+	u64 _timeout_us;
+
+	kthread_use_mm(dev->mm);
+	complete(&vq->poll_started);
+
+	vq->stat_completions = 0;
+	vq->lat_scsi_ns = 0;
+	vq->ema_latency_ns = 0;
+	vq->ema_interval_ns = 0;
+	ts2 = ktime_get();
+
+	while (!READ_ONCE(vq->poll_stop)) {
+
+		stats_inflight_sum += (u16)(vq->last_avail_idx - vq->last_used_idx);
+		stats_inflight_n++;
+		stats_multi_cnt += ((atomic_read(&sqcq_active_io_vqs) > 1) ? 1 : 0);
+
+		// 10's output windows.
+		if (ktime_to_ns(ktime_sub(ktime_get(), stats_window)) >=
+			    (s64)10 * NSEC_PER_SEC) {
+			if (stats_cq > 0) {
+				int vi;
+
+				for (vi = 0; vi < dev->nvqs; vi++)
+					if (dev->vqs[vi] == vq)
+						break;
+				pr_info("vhost-sqcq: vq[%d] cq=%llu avg=%lluns ema_lat=%lluns interval=%lluns avg_inflight=%llu multi=%llu%% sleep=%llu wk_kick=%llu%% wk_cmp=%llu%% wk_to=%llu%%\n",
+				       vi, stats_cq,
+				       div64_u64(stats_lat, stats_cq),
+				       vq->ema_latency_ns,
+				       vq->ema_interval_ns,
+				       stats_inflight_n > 0 ?
+				       div64_u64(stats_inflight_sum, stats_inflight_n) : 0,
+				       div64_u64(stats_multi_cnt*100, stats_inflight_n),
+				       stats_sleep_cnt,
+				       stats_sleep_cnt > 0 ?
+				       div64_u64(stats_wake_kick*100, stats_sleep_cnt) : 0,
+				       stats_sleep_cnt > 0 ?
+				       div64_u64(stats_wake_complete*100, stats_sleep_cnt) : 0,
+				       stats_sleep_cnt > 0 ?
+				       div64_u64(stats_wake_timeout*100, stats_sleep_cnt) : 0);
+			}
+			stats_window = ktime_get();
+			stats_cq = 0;
+			stats_lat = 0;
+			stats_inflight_sum = 0;
+			stats_inflight_n = 0;
+			stats_multi_cnt = 0;
+			stats_sleep_cnt = 0;
+			stats_wake_kick = 0;
+			stats_wake_complete = 0;
+			stats_wake_timeout = 0;
+		}
+
+		/* Backstop cleanup: drop sq_was_active and reset EMAs once a VQ
+		 * has been idle (no submission) for a fixed interval.
+		 */
+		if (vq->sq_was_active && last_io_ts > 0 &&
+		    ktime_to_ns(ktime_sub(ktime_get(), last_io_ts)) >
+			    (s64)SQCQ_STALE_TIMEOUT_NS) {
+			vq->sq_was_active = false;
+			atomic_dec(&sqcq_active_io_vqs);
+			if (vq->poll_cpu >= 0)
+				cpumask_clear_cpu(vq->poll_cpu, &sqcq_poll_active_cpu_mask);
+			WRITE_ONCE(vq->ema_interval_ns, 0);
+			WRITE_ONCE(vq->ema_latency_ns, 0);
+		}
+
+		/* complete*/
+		if (atomic_read(&vq->completion_pending) && vq->poll_complete) {
+			smp_rmb();
+			atomic_set(&vq->completion_pending, 0);
+			vq->poll_complete(vq);
+			if (vq->stat_completions > 0) {
+				u64 avg = div64_u64(vq->lat_scsi_ns,
+						    vq->stat_completions);
+
+				if (vq->ema_latency_ns == 0)
+					vq->ema_latency_ns = avg;
+				else
+					vq->ema_latency_ns =
+						vq->ema_latency_ns -
+						vq->ema_latency_ns / 8 +
+						avg / 8;
+				stats_cq += vq->stat_completions;
+				stats_lat += vq->lat_scsi_ns;
+				vq->lat_scsi_ns = 0;
+				vq->stat_completions = 0;
+			}
+		}
+
+		if (get_user(sq_idx_val, &vq->sq->idx)) {
+			int fault_retries = 3;
+
+			while (fault_retries-- > 0) {
+				if (!get_user(sq_idx_val, &vq->sq->idx))
+					break;
+				schedule_timeout_idle(1);
+			}
+			if (fault_retries < 0)
+				schedule_timeout_idle(HZ / 100);
+			continue;
+		}
+		/* Acquire: see all prior Guest writes (descriptors). */
+		smp_rmb();
+		new_sq_idx = (__force u16)sq_idx_val;
+
+		if (new_sq_idx != vq->last_avail_idx) {
+			vq->handle_kick(&vq->poll.work);
+			ts2 = ktime_get();
+			if (last_io_ts > 0) {
+				u64 _interval = ktime_to_ns(ktime_sub(ts2, last_io_ts));
+				if (vq->ema_interval_ns == 0)
+					vq->ema_interval_ns = _interval;
+				else
+					vq->ema_interval_ns =
+						vq->ema_interval_ns -
+						vq->ema_interval_ns / 8 +
+						_interval / 8;
+			}
+			last_io_ts = ts2;
+			if (!vq->sq_was_active) {
+				vq->sq_was_active = true;
+				atomic_inc(&sqcq_active_io_vqs);
+				if (vq->poll_cpu >= 0)
+					cpumask_set_cpu(vq->poll_cpu, &sqcq_poll_active_cpu_mask);
+			}
+			sq_need_wakeup_val = 0;
+			smp_mb();
+			put_user(sq_need_wakeup_val, &vq->sq->sq_need_wakeup);
+		}
+
+
+		if (vq->last_avail_idx == vq->last_used_idx) {
+			/* Spin for a fixed window to catch next submission via
+			 * sq->idx polling; need_resched+cond_resched prevents
+			 * starving other tasks on this CPU.
+			 */
+			spin_ns_val = SQCQ_ACTIVE_TIMEOUT_NS;
+			if (last_io_ts > 0 && ktime_to_ns(ktime_sub(
+			    ktime_get(), last_io_ts)) < spin_ns_val) {
+				if (need_resched())
+					cond_resched();
+				cpu_relax();
+				continue;
+			}
+		/* Clear sq_was_active to keep active_io_vqs and
+		 * cpu mask fresh. Has-inflight path keeps the
+		 * flag for TCM completion steering.
+		 */
+			if (vq->sq_was_active) {
+				vq->sq_was_active = false;
+				atomic_dec(&sqcq_active_io_vqs);
+				if (vq->poll_cpu >= 0)
+					cpumask_clear_cpu(vq->poll_cpu, &sqcq_poll_active_cpu_mask);
+			}
+		} else {
+		/* Spin within SQCQ_SPIN_EMA_MULT * ema_latency (with
+		 * cooperative yield), then sleep.  cond_resched handles
+		 * multi-VQ fairness without a separate yield heuristic.
+		 */
+			ema_ns_val = READ_ONCE(vq->ema_latency_ns);
+			spin_ns_val = ema_ns_val > 0 ?
+				(u64)SQCQ_SPIN_EMA_MULT * ema_ns_val :
+				2 * NSEC_PER_USEC;  /* fallback when EMA unknown */
+
+			if (ktime_to_ns(ktime_sub(
+			    ktime_get(), last_io_ts)) < spin_ns_val) {
+				if (need_resched())
+					cond_resched();
+				cpu_relax();
+				continue;
+			}
+		}
+
+		ema_ns_val = READ_ONCE(vq->ema_latency_ns);
+
+		if (ema_ns_val == 0) {
+			_timeout_jiffies = usecs_to_jiffies(
+				SQCQ_IDLE_TIMEOUT_US_MAX);
+		} else {
+			_timeout_us = div64_u64(
+				ema_ns_val,
+				(u64)NSEC_PER_USEC / SQCQ_SLEEP_EMA_MULT);
+
+			_timeout_us = clamp(_timeout_us,
+				10ULL,
+				(u64)SQCQ_IDLE_TIMEOUT_US_MAX);
+			_timeout_jiffies = usecs_to_jiffies(
+				_timeout_us);
+			if (_timeout_jiffies == 0)
+				_timeout_jiffies = 1;
+		}
+
+		/* Set NEED_WAKEUP, then double-check sq->idx before sleeping. */
+		smp_wmb();
+		sq_need_wakeup_val = 1;
+		put_user(sq_need_wakeup_val,
+			&vq->sq->sq_need_wakeup);
+		smp_mb();
+
+		if (get_user(sq_idx_val, &vq->sq->idx)) {
+			int fault_retries = 3;
+
+			while (fault_retries-- > 0) {
+				if (!get_user(sq_idx_val, &vq->sq->idx))
+					break;
+				schedule_timeout_idle(1);
+			}
+			if (fault_retries < 0)
+				schedule_timeout_idle(HZ / 100);
+			continue;
+		}
+		smp_rmb();
+		new_sq_idx = (__force u16)sq_idx_val;
+
+		if (new_sq_idx != vq->last_avail_idx) {
+			sq_need_wakeup_val = 0;
+			smp_mb();
+			put_user(sq_need_wakeup_val,
+				&vq->sq->sq_need_wakeup);
+			continue;
+		}
+
+		++stats_sleep_cnt;
+		vhost_sqcq_kick_register(vq);
+		wait_event_interruptible_timeout(
+			vq->poll_wait,
+			READ_ONCE(vq->poll_stop) ||
+			READ_ONCE(vq->kick_received) ||
+			atomic_read(&vq->completion_pending),
+			_timeout_jiffies);
+		/* Wake reason stats: snapshot before unregister clears kick_received */
+		{
+			bool _kicked = READ_ONCE(vq->kick_received);
+			bool _completed = atomic_read(&vq->completion_pending);
+
+			if (_kicked)
+				stats_wake_kick++;
+			if (_completed)
+				stats_wake_complete++;
+			if (!_kicked && !_completed)
+				stats_wake_timeout++;
+		}
+		vhost_sqcq_kick_unregister(vq);
+
+		sq_need_wakeup_val = 0;
+		put_user(sq_need_wakeup_val,
+			&vq->sq->sq_need_wakeup);
+	}
+
+	complete(&vq->poll_stopped);
+	kthread_unuse_mm(dev->mm);
+	return 0;
+}
+
+int vhost_sqcq_poll_start(struct vhost_virtqueue *vq)
+{
+	if (!vq->sq || !vq->cq || !vq->handle_kick)
+		return -EINVAL;
+
+	vhost_poll_stop(&vq->poll);
+
+	WRITE_ONCE(vq->poll_stop, false);
+	reinit_completion(&vq->poll_started);
+	reinit_completion(&vq->poll_stopped);
+
+	int vi;
+
+	for (vi = 0; vi < vq->dev->nvqs; vi++)
+		if (vq->dev->vqs[vi] == vq)
+			break;
+	vq->poll_task = kthread_run(vhost_sqcq_poll_thread, vq,
+				    "vhost-sqcq-%d-%d",
+				    task_pid_nr(current),
+				    vi);
+	if (IS_ERR(vq->poll_task)) {
+		int ret = PTR_ERR(vq->poll_task);
+
+		vq->poll_task = NULL;
+		if (vq->kick)
+			vhost_poll_start(&vq->poll, vq->kick);
+		return ret;
+	}
+
+	wait_for_completion(&vq->poll_started);
+
+	/* Enable sqcq mode only after poll_task is valid and the
+	 * thread has signaled readiness.  Before this point,
+	 * vhost_signal() routes completions via eventfd (old path).
+	 */
+	smp_mb();
+	WRITE_ONCE(vq->sqcq_poll, true);
+
+	if (vq->poll_task) {
+		int cpu;
+		int start = atomic_inc_return(&sqcq_poll_cpu_rr);
+		int target_cpu = -1;
+		int online = num_online_cpus();
+
+		if (online > 0)
+			start = start % online;
+		else
+			start = 0;
+
+		for_each_online_cpu(cpu) {
+			if (start-- <= 0) {
+				target_cpu = cpu;
+				break;
+			}
+		}
+		if (target_cpu < 0) {
+			for_each_online_cpu(cpu) {
+				target_cpu = cpu;
+				break;
+			}
+		}
+
+		if (target_cpu >= 0 && target_cpu < nr_cpu_ids &&
+		    cpu_online(target_cpu)) {
+			int vi;
+
+			set_cpus_allowed_ptr(vq->poll_task, cpumask_of(target_cpu));
+			for (vi = 0; vi < vq->dev->nvqs; vi++)
+				if (vq->dev->vqs[vi] == vq)
+					break;
+			pr_info("vhost-sqcq: vq[%d] poll thread bound to cpu%d\n",
+				vi, target_cpu);
+			vq->poll_cpu = target_cpu;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(vhost_sqcq_poll_start);
+
+void vhost_sqcq_poll_stop(struct vhost_virtqueue *vq)
+{
+	if (!vq->poll_task)
+		return;
+
+	WRITE_ONCE(vq->poll_stop, true);
+	smp_mb();
+	wake_up_process(vq->poll_task);
+	wake_up(&vq->poll_wait);
+	wait_for_completion(&vq->poll_stopped);
+	vq->poll_task = NULL;
+	WRITE_ONCE(vq->sqcq_poll, false);
+	if (vq->poll_cpu >= 0) {
+		cpumask_clear_cpu(vq->poll_cpu, &sqcq_poll_active_cpu_mask);
+		vq->poll_cpu = -1;
+	}
+	smp_mb();
+
+	if (vq->kick)
+		vhost_poll_start(&vq->poll, vq->kick);
+	vq->sq = NULL;
+	vq->cq = NULL;
+}
+EXPORT_SYMBOL_GPL(vhost_sqcq_poll_stop);
+
+/* Flush residual completions after stop.  Must be called outside vq->mutex. */
+void vhost_sqcq_poll_flush(struct vhost_virtqueue *vq)
+{
+	if (WARN_ON_ONCE(!vq->poll_complete))
+		return;
+
+	atomic_set(&vq->completion_pending, 0);
+	vq->poll_complete(vq);
+}
+EXPORT_SYMBOL_GPL(vhost_sqcq_poll_flush);
+
+/* Pick a CPU for TCM completion: prefer a free CPU (no poll thread),
+ * searching from hint_cpu+1 with wrap. Fallback to next-online
+ * if every CPU has a poll thread.
+ */
+int vhost_sqcq_pick_completion_cpu(int hint_cpu)
+{
+	int first, cpu, target = -1;
+
+	first = cpumask_next(hint_cpu, cpu_online_mask);
+	if (first >= nr_cpu_ids)
+		first = cpumask_first(cpu_online_mask);
+
+	cpu = first;
+	do {
+		if (!cpumask_test_cpu(cpu, &sqcq_poll_active_cpu_mask)) {
+			target = cpu;
+			break;
+		}
+		cpu = cpumask_next(cpu, cpu_online_mask);
+		if (cpu >= nr_cpu_ids)
+			cpu = cpumask_first(cpu_online_mask);
+	} while (cpu != first);
+
+	if (target < 0)
+		target = first;
+
+	return target;
+}
+EXPORT_SYMBOL_GPL(vhost_sqcq_pick_completion_cpu);
+
+static long vhost_vring_set_sqcq_addr(struct vhost_dev *d,
+				       struct vhost_virtqueue *vq,
+				       void __user *argp)
+{
+	struct vhost_vring_sqcq_addr a;
+
+	if (copy_from_user(&a, argp, sizeof(a)))
+		return -EFAULT;
+
+	if ((u64)(unsigned long)a.sq_user_addr != a.sq_user_addr ||
+	    (u64)(unsigned long)a.cq_user_addr != a.cq_user_addr)
+		return -EFAULT;
+
+	if (a.sq_user_addr & 7 || a.cq_user_addr & 7)
+		return -EINVAL;
+
+	if (vq->private_data) {
+		if (!access_ok((void __user *)(unsigned long)a.sq_user_addr,
+			       sizeof(struct vring_sq)) ||
+		    !access_ok((void __user *)(unsigned long)a.cq_user_addr,
+			       sizeof(struct vring_cq)))
+			return -EINVAL;
+	}
+
+	vq->sq = (struct vring_sq __user *)(unsigned long)a.sq_user_addr;
+	vq->cq = (struct vring_cq __user *)(unsigned long)a.cq_user_addr;
+
+	/* SET_ENDPOINT may run before SET_FEATURES/SQCQ_ADDR, so poll-start
+	 * can fail there; this is the canonical trigger. Skip VQs without
+	 * poll_complete (ctrl/event VQs) — they stay interrupt-driven.
+	 */
+	if (vq->private_data && vq->sq && vq->cq &&
+	    !READ_ONCE(vq->sqcq_poll) &&
+	    vq->poll_complete &&
+	    vhost_has_feature(vq, VIRTIO_F_SQCQ_POLL)) {
+		long ret = vhost_sqcq_poll_start(vq);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static long vhost_vring_set_addr(struct vhost_dev *d,
 				 struct vhost_virtqueue *vq,
 				 void __user *argp)
@@ -2244,6 +2810,9 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg
 		if (copy_to_user(argp, &s, sizeof(s)))
 			r = -EFAULT;
 		break;
+	case VHOST_SET_VRING_SQCQ_ADDR:
+		r = vhost_vring_set_sqcq_addr(d, vq, argp);
+		break;
 	default:
 		r = -ENOIOCTLCMD;
 	}
@@ -2256,7 +2825,7 @@ long vhost_vring_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *arg
 	if (filep)
 		fput(filep);
 
-	if (pollstart && vq->handle_kick)
+	if (pollstart && vq->handle_kick && !READ_ONCE(vq->sqcq_poll))
 		r = vhost_poll_start(&vq->poll, vq->kick);
 
 	mutex_unlock(&vq->mutex);
@@ -2938,8 +3507,12 @@ int vhost_get_vq_desc_n(struct vhost_virtqueue *vq,
 		*ndesc = c;
 
 	/* Assume notifications from guest are disabled at this point,
-	 * if they aren't we would need to update avail_event index. */
-	BUG_ON(!(vq->used_flags & VRING_USED_F_NO_NOTIFY));
+	 * if they aren't we would need to update avail_event index.
+	 * In SQCQ poll mode, vhost_disable_notify is a no-op so
+	 * VRING_USED_F_NO_NOTIFY is never set; skip the check.
+	 */
+	WARN_ON_ONCE(!vq->sqcq_poll &&
+		     !(vq->used_flags & VRING_USED_F_NO_NOTIFY));
 	return head;
 }
 EXPORT_SYMBOL_GPL(vhost_get_vq_desc_n);
@@ -3167,6 +3740,16 @@ static bool vhost_notify(struct vhost_dev *dev, struct vhost_virtqueue *vq)
 /* This actually signals the guest, using eventfd. */
 void vhost_signal(struct vhost_dev *dev, struct vhost_virtqueue *vq)
 {
+	if (READ_ONCE(vq->sqcq_poll) && vq->cq) {
+		atomic_set(&vq->completion_pending, 1);
+		if (!READ_ONCE(vq->poll_stop)) {
+			smp_mb();
+			if (!task_is_running(vq->poll_task))
+				wake_up(&vq->poll_wait);
+		}
+		return;
+	}
+
 	/* Signal the Guest tell them we used something up. */
 	if (vq->call_ctx.ctx && vhost_notify(dev, vq))
 		eventfd_signal(vq->call_ctx.ctx);
@@ -3213,6 +3796,9 @@ EXPORT_SYMBOL_GPL(vhost_vq_avail_empty);
 /* OK, now we need to know about added descriptors. */
 bool vhost_enable_notify(struct vhost_dev *dev, struct vhost_virtqueue *vq)
 {
+	if (READ_ONCE(vq->sqcq_poll))
+		return false;
+
 	int r;
 
 	if (!(vq->used_flags & VRING_USED_F_NO_NOTIFY))
@@ -3249,6 +3835,9 @@ EXPORT_SYMBOL_GPL(vhost_enable_notify);
 /* We don't need to be notified again. */
 void vhost_disable_notify(struct vhost_dev *dev, struct vhost_virtqueue *vq)
 {
+	if (READ_ONCE(vq->sqcq_poll))
+		return;
+
 	int r;
 
 	if (vq->used_flags & VRING_USED_F_NO_NOTIFY)
diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h
index 4fe99765c5c73..b9c15961f5a18 100644
--- a/drivers/vhost/vhost.h
+++ b/drivers/vhost/vhost.h
@@ -111,6 +111,7 @@ struct vhost_virtqueue {
 
 	/* The routine to call when the Guest pings us, or timeout. */
 	vhost_work_fn_t handle_kick;
+	void (*poll_complete)(struct vhost_virtqueue *vq);
 
 	/* Last available index we saw.
 	 * Values are limited to 0x7fff, and the high bit is used as
@@ -164,6 +165,27 @@ struct vhost_virtqueue {
 	bool user_be;
 #endif
 	u32 busyloop_timeout;
+
+	/* SQ/CQ polling mode */
+	bool sqcq_poll;
+	struct vring_sq __user *sq;
+	struct vring_cq __user *cq;
+	struct task_struct *poll_task;
+	bool poll_stop;
+	wait_queue_head_t poll_wait;
+	struct completion poll_started;
+	struct completion poll_stopped;
+	bool kick_received;		/* Set when kick eventfd wakes us */
+	atomic_t completion_pending;	/* Set by vhost_signal, poll thread atomically clears */
+	wait_queue_head_t *kick_wqh;	/* kick eventfd wait queue head */
+	wait_queue_entry_t kick_wait;	/* Wait entry on kick eventfd wq */
+	poll_table kick_pt;		/* poll_table for kick eventfd */
+	u64		stat_completions;
+	u64		lat_scsi_ns;
+	u64		ema_interval_ns;
+	u64		ema_latency_ns;
+	bool		sq_was_active;
+	int		poll_cpu;	/* CPU this VQ's poll thread is pinned to, -1 = unpinned */
 };
 
 struct vhost_msg_node {
@@ -256,6 +278,10 @@ void vhost_add_used_and_signal_n(struct vhost_dev *, struct vhost_virtqueue *,
 void vhost_signal(struct vhost_dev *, struct vhost_virtqueue *);
 void vhost_disable_notify(struct vhost_dev *, struct vhost_virtqueue *);
 bool vhost_vq_avail_empty(struct vhost_dev *, struct vhost_virtqueue *);
+int vhost_sqcq_poll_start(struct vhost_virtqueue *vq);
+void vhost_sqcq_poll_stop(struct vhost_virtqueue *vq);
+void vhost_sqcq_poll_flush(struct vhost_virtqueue *vq);
+int vhost_sqcq_pick_completion_cpu(int hint_cpu);
 bool vhost_enable_notify(struct vhost_dev *, struct vhost_virtqueue *);
 
 int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log,
@@ -294,7 +320,8 @@ void vhost_iotlb_map_free(struct vhost_iotlb *iotlb,
 	VIRTIO_RING_F_EVENT_IDX, \
 	VHOST_F_LOG_ALL, \
 	VIRTIO_F_ANY_LAYOUT, \
-	VIRTIO_F_VERSION_1
+	VIRTIO_F_VERSION_1, \
+	VIRTIO_F_SQCQ_POLL
 
 static inline u64 vhost_features_u64(const int *features, int size, int idx)
 {
diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h
index c57674a6aa0db..dacb0e1fdc4bc 100644
--- a/include/uapi/linux/vhost.h
+++ b/include/uapi/linux/vhost.h
@@ -123,6 +123,10 @@
 #define VHOST_SET_BACKEND_FEATURES _IOW(VHOST_VIRTIO, 0x25, __u64)
 #define VHOST_GET_BACKEND_FEATURES _IOR(VHOST_VIRTIO, 0x26, __u64)
 
+/* Set SQ/CQ doorbell addresses for polling mode */
+#define VHOST_SET_VRING_SQCQ_ADDR _IOW(VHOST_VIRTIO, 0x27, \
+					struct vhost_vring_sqcq_addr)
+
 /* VHOST_NET specific defines */
 
 /* Attach virtio net ring to a raw socket, or tap device.
diff --git a/include/uapi/linux/vhost_types.h b/include/uapi/linux/vhost_types.h
index 1c39cc5f5a31b..6514c1b5be20b 100644
--- a/include/uapi/linux/vhost_types.h
+++ b/include/uapi/linux/vhost_types.h
@@ -47,6 +47,12 @@ struct vhost_vring_addr {
 	__u64 log_guest_addr;
 };
 
+struct vhost_vring_sqcq_addr {
+	unsigned int index;
+	__u64 sq_user_addr;	/* SQ doorbell HVA */
+	__u64 cq_user_addr;	/* CQ doorbell HVA */
+};
+
 struct vhost_worker_state {
 	/*
 	 * For VHOST_NEW_WORKER the kernel will return the new vhost_worker id.
-- 
2.34.1


^ permalink raw reply related

* [RFC PATCH 4/4] virtio: add SQ/CQ polling mode support for vhost-scsi
From: rom.wang @ 2026-07-20 14:10 UTC (permalink / raw)
  To: mst, jasowangio, eperezma, stefanha
  Cc: virtualization, linux-kernel, linux-scsi, kvm, qemu-devel,
	Yufeng Wang
In-Reply-To: <20260720141040.181946-1-r4o5m6e8o@163.com>

From: Yufeng Wang <wangyufeng@kylinos.cn>

Implement the QEMU-side bridge layer for virtio SQ/CQ polling mode,
which replaces interrupt-based virtio notifications with io_uring-style
doorbell polling to eliminate VM exits in high-throughput scenarios.

UAPI headers: sync VIRTIO_F_SQCQ_POLL (bit 42), PCI common config
offsets (64-76), struct vring_sq/vring_cq, struct vhost_vring_sqcq_addr,
and VHOST_SET_VRING_SQCQ_ADDR ioctl (0x27) from Linux kernel.

VirtIO core: add sq/cq fields to VRing, with getter/setter functions.

PCI transport: handle SQE/CQE register read/write in common config,
gate SQ/CQ address forwarding on VIRTIO_F_SQCQ_POLL negotiation in
Q_ENABLE handler.

Vhost backend: add vhost_set_vring_sqcq_addr_op to VhostOps, implement
kernel backend via VHOST_SET_VRING_SQCQ_ADDR ioctl.

Vhost core: map SQ/CQ GPAs to HVAs in vhost_virtqueue_start() after
VHOST_SET_VRING_ADDR, call the new ioctl before VHOST_SET_VRING_KICK;
unmap in do_vhost_virtqueue_stop().

vhost-scsi: advertise VIRTIO_F_SQCQ_POLL in kernel_feature_bits[].

Signed-off-by: Yufeng Wang <wangyufeng@kylinos.cn>
---
 hw/scsi/vhost-scsi.c                          |  4 ++
 hw/virtio/vhost-backend.c                     |  7 +++
 hw/virtio/vhost.c                             | 54 +++++++++++++++++++
 hw/virtio/virtio-pci.c                        | 31 +++++++++++
 hw/virtio/virtio.c                            | 18 +++++++
 include/hw/virtio/vhost-backend.h             |  4 ++
 include/hw/virtio/vhost.h                     |  6 +++
 include/hw/virtio/virtio-pci.h                |  2 +
 include/hw/virtio/virtio.h                    |  3 ++
 include/standard-headers/linux/vhost_types.h  |  7 +++
 .../standard-headers/linux/virtio_config.h    |  5 +-
 include/standard-headers/linux/virtio_pci.h   |  4 ++
 include/standard-headers/linux/virtio_ring.h  | 30 +++++++++++
 linux-headers/linux/vhost.h                   |  4 ++
 14 files changed, 178 insertions(+), 1 deletion(-)

diff --git a/hw/scsi/vhost-scsi.c b/hw/scsi/vhost-scsi.c
index 699863c..bb7f3e8 100644
--- a/hw/scsi/vhost-scsi.c
+++ b/hw/scsi/vhost-scsi.c
@@ -40,6 +40,7 @@ static const int kernel_feature_bits[] = {
     VIRTIO_F_RING_RESET,
     VIRTIO_F_IN_ORDER,
     VIRTIO_F_NOTIFICATION_DATA,
+    VIRTIO_F_SQCQ_POLL,
     VHOST_INVALID_FEATURE_BIT
 };
 
@@ -361,6 +362,9 @@ static const Property vhost_scsi_properties[] = {
     DEFINE_PROP_BIT64("hotplug", VHostSCSICommon, host_features,
                                                   VIRTIO_SCSI_F_HOTPLUG,
                                                   false),
+    DEFINE_PROP_BIT64("sqcq_poll", VHostSCSICommon, host_features,
+                                                   VIRTIO_F_SQCQ_POLL,
+                                                   true),
     DEFINE_PROP_BOOL("migratable", VHostSCSICommon, migratable, false),
     DEFINE_PROP_BOOL("worker_per_virtqueue", VirtIOSCSICommon,
                      conf.worker_per_virtqueue, false),
diff --git a/hw/virtio/vhost-backend.c b/hw/virtio/vhost-backend.c
index 4367db0..a71b30b 100644
--- a/hw/virtio/vhost-backend.c
+++ b/hw/virtio/vhost-backend.c
@@ -115,6 +115,12 @@ static int vhost_kernel_set_vring_addr(struct vhost_dev *dev,
     return vhost_kernel_call(dev, VHOST_SET_VRING_ADDR, addr);
 }
 
+static int vhost_kernel_set_vring_sqcq_addr(struct vhost_dev *dev,
+                                            struct vhost_vring_sqcq_addr *addr)
+{
+    return vhost_kernel_call(dev, VHOST_SET_VRING_SQCQ_ADDR, addr);
+}
+
 static int vhost_kernel_set_vring_endian(struct vhost_dev *dev,
                                          struct vhost_vring_state *ring)
 {
@@ -368,6 +374,7 @@ const VhostOps kernel_ops = {
         .vhost_set_log_base = vhost_kernel_set_log_base,
         .vhost_set_mem_table = vhost_kernel_set_mem_table,
         .vhost_set_vring_addr = vhost_kernel_set_vring_addr,
+        .vhost_set_vring_sqcq_addr = vhost_kernel_set_vring_sqcq_addr,
         .vhost_set_vring_endian = vhost_kernel_set_vring_endian,
         .vhost_set_vring_num = vhost_kernel_set_vring_num,
         .vhost_set_vring_base = vhost_kernel_set_vring_base,
diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
index 31e9704..8c82f98 100644
--- a/hw/virtio/vhost.c
+++ b/hw/virtio/vhost.c
@@ -22,6 +22,7 @@
 #include "qemu/memfd.h"
 #include "qemu/log.h"
 #include "standard-headers/linux/vhost_types.h"
+#include "standard-headers/linux/virtio_ring.h"
 #include "hw/virtio/virtio-bus.h"
 #include "hw/mem/memory-device.h"
 #include "migration/blocker.h"
@@ -1337,6 +1338,49 @@ int vhost_virtqueue_start(struct vhost_dev *dev,
         goto fail_alloc;
     }
 
+    /* SQ/CQ polling: map and pass to vhost if feature negotiated */
+    vq->sq = NULL;
+    vq->cq = NULL;
+    vq->sq_phys = virtio_queue_get_sq_addr(vdev, idx);
+    vq->cq_phys = virtio_queue_get_cq_addr(vdev, idx);
+
+    if (vq->sq_phys && vq->cq_phys &&
+        dev->vhost_ops->vhost_set_vring_sqcq_addr &&
+        virtio_has_feature(vdev->guest_features, VIRTIO_F_SQCQ_POLL)) {
+        struct vhost_vring_sqcq_addr sqcq_addr;
+
+        l = sizeof(struct vring_sq);
+        vq->sq = vhost_memory_map(dev, vq->sq_phys, &l, false);
+        if (!vq->sq) {
+            r = -ENOMEM;
+            goto fail_alloc;
+        }
+        vq->sq_size = l;
+
+        l = sizeof(struct vring_cq);
+        vq->cq = vhost_memory_map(dev, vq->cq_phys, &l, false);
+        if (!vq->cq) {
+            r = -ENOMEM;
+            vhost_memory_unmap(dev, vq->sq, vq->sq_size, 0, 0);
+            goto fail_alloc;
+        }
+        vq->cq_size = l;
+
+        memset(&sqcq_addr, 0, sizeof(sqcq_addr));
+        sqcq_addr.index = vhost_vq_index;
+        sqcq_addr.sq_user_addr = (uint64_t)(unsigned long)vq->sq;
+        sqcq_addr.cq_user_addr = (uint64_t)(unsigned long)vq->cq;
+        r = dev->vhost_ops->vhost_set_vring_sqcq_addr(dev, &sqcq_addr);
+        if (r < 0) {
+            VHOST_OPS_DEBUG(r, "vhost_set_vring_sqcq_addr failed");
+            vhost_memory_unmap(dev, vq->cq, vq->cq_size, 0, 0);
+            vhost_memory_unmap(dev, vq->sq, vq->sq_size, 0, 0);
+            vq->sq = NULL;
+            vq->cq = NULL;
+            goto fail_alloc;
+        }
+    }
+
     file.fd = event_notifier_get_fd(virtio_queue_get_host_notifier(vvq));
     r = dev->vhost_ops->vhost_set_vring_kick(dev, &file);
     if (r) {
@@ -1425,6 +1469,16 @@ static int do_vhost_virtqueue_stop(struct vhost_dev *dev,
                                                 vhost_vq_index);
     }
 
+    /* Unmap SQ/CQ if mapped */
+    if (vq->sq) {
+        vhost_memory_unmap(dev, vq->sq, vq->sq_size, 0, 0);
+        vq->sq = NULL;
+    }
+    if (vq->cq) {
+        vhost_memory_unmap(dev, vq->cq, vq->cq_size, 0, 0);
+        vq->cq = NULL;
+    }
+
     vhost_memory_unmap(dev, vq->used, virtio_queue_get_used_size(vdev, idx),
                        1, virtio_queue_get_used_size(vdev, idx));
     vhost_memory_unmap(dev, vq->avail, virtio_queue_get_avail_size(vdev, idx),
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index b273eb2..c88cba3 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -1623,6 +1623,18 @@ static uint64_t virtio_pci_common_read(void *opaque, hwaddr addr,
     case VIRTIO_PCI_COMMON_Q_RESET:
         val = proxy->vqs[vdev->queue_sel].reset;
         break;
+    case VIRTIO_PCI_COMMON_SQE_LO:
+        val = proxy->vqs[vdev->queue_sel].sq[0];
+        break;
+    case VIRTIO_PCI_COMMON_SQE_HI:
+        val = proxy->vqs[vdev->queue_sel].sq[1];
+        break;
+    case VIRTIO_PCI_COMMON_CQE_LO:
+        val = proxy->vqs[vdev->queue_sel].cq[0];
+        break;
+    case VIRTIO_PCI_COMMON_CQE_HI:
+        val = proxy->vqs[vdev->queue_sel].cq[1];
+        break;
     default:
         val = 0;
     }
@@ -1727,6 +1739,13 @@ static void virtio_pci_common_write(void *opaque, hwaddr addr,
                        proxy->vqs[vdev->queue_sel].avail[0],
                        ((uint64_t)proxy->vqs[vdev->queue_sel].used[1]) << 32 |
                        proxy->vqs[vdev->queue_sel].used[0]);
+            if (virtio_has_feature(vdev->guest_features, VIRTIO_F_SQCQ_POLL)) {
+                virtio_queue_set_sqcq(vdev, vdev->queue_sel,
+                    ((uint64_t)proxy->vqs[vdev->queue_sel].sq[1] << 32) |
+                    proxy->vqs[vdev->queue_sel].sq[0],
+                    ((uint64_t)proxy->vqs[vdev->queue_sel].cq[1] << 32) |
+                    proxy->vqs[vdev->queue_sel].cq[0]);
+            }
             proxy->vqs[vdev->queue_sel].enabled = 1;
             proxy->vqs[vdev->queue_sel].reset = 0;
             virtio_queue_enable(vdev, vdev->queue_sel);
@@ -1752,6 +1771,18 @@ static void virtio_pci_common_write(void *opaque, hwaddr addr,
     case VIRTIO_PCI_COMMON_Q_USEDHI:
         proxy->vqs[vdev->queue_sel].used[1] = val;
         break;
+    case VIRTIO_PCI_COMMON_SQE_LO:
+        proxy->vqs[vdev->queue_sel].sq[0] = val;
+        break;
+    case VIRTIO_PCI_COMMON_SQE_HI:
+        proxy->vqs[vdev->queue_sel].sq[1] = val;
+        break;
+    case VIRTIO_PCI_COMMON_CQE_LO:
+        proxy->vqs[vdev->queue_sel].cq[0] = val;
+        break;
+    case VIRTIO_PCI_COMMON_CQE_HI:
+        proxy->vqs[vdev->queue_sel].cq[1] = val;
+        break;
     case VIRTIO_PCI_COMMON_Q_RESET:
         if (val == 1) {
             proxy->vqs[vdev->queue_sel].reset = 1;
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c
index 3dc9423..42e39d9 100644
--- a/hw/virtio/virtio.c
+++ b/hw/virtio/virtio.c
@@ -111,6 +111,8 @@ typedef struct VRing
     hwaddr desc;
     hwaddr avail;
     hwaddr used;
+    hwaddr sq;
+    hwaddr cq;
     VRingMemoryRegionCaches *caches;
 } VRing;
 
@@ -2400,6 +2402,12 @@ void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc,
     virtio_init_region_cache(vdev, n);
 }
 
+void virtio_queue_set_sqcq(VirtIODevice *vdev, int n, hwaddr sq, hwaddr cq)
+{
+    vdev->vq[n].vring.sq = sq;
+    vdev->vq[n].vring.cq = cq;
+}
+
 void virtio_queue_set_num(VirtIODevice *vdev, int n, int num)
 {
     /* Don't allow guest to flip queue between existent and
@@ -3661,6 +3669,16 @@ hwaddr virtio_queue_get_used_addr(VirtIODevice *vdev, int n)
     return vdev->vq[n].vring.used;
 }
 
+hwaddr virtio_queue_get_sq_addr(VirtIODevice *vdev, int n)
+{
+    return vdev->vq[n].vring.sq;
+}
+
+hwaddr virtio_queue_get_cq_addr(VirtIODevice *vdev, int n)
+{
+    return vdev->vq[n].vring.cq;
+}
+
 hwaddr virtio_queue_get_desc_size(VirtIODevice *vdev, int n)
 {
     return sizeof(VRingDesc) * vdev->vq[n].vring.num;
diff --git a/include/hw/virtio/vhost-backend.h b/include/hw/virtio/vhost-backend.h
index ff94fa1..911cd15 100644
--- a/include/hw/virtio/vhost-backend.h
+++ b/include/hw/virtio/vhost-backend.h
@@ -45,6 +45,7 @@ struct vhost_memory;
 struct vhost_vring_file;
 struct vhost_vring_state;
 struct vhost_vring_addr;
+struct vhost_vring_sqcq_addr;
 struct vhost_vring_worker;
 struct vhost_worker_state;
 struct vhost_scsi_target;
@@ -71,6 +72,8 @@ typedef int (*vhost_set_mem_table_op)(struct vhost_dev *dev,
                                       struct vhost_memory *mem);
 typedef int (*vhost_set_vring_addr_op)(struct vhost_dev *dev,
                                        struct vhost_vring_addr *addr);
+typedef int (*vhost_set_vring_sqcq_addr_op)(struct vhost_dev *dev,
+                                            struct vhost_vring_sqcq_addr *addr);
 typedef int (*vhost_set_vring_endian_op)(struct vhost_dev *dev,
                                          struct vhost_vring_state *ring);
 typedef int (*vhost_set_vring_num_op)(struct vhost_dev *dev,
@@ -178,6 +181,7 @@ typedef struct VhostOps {
     vhost_set_log_base_op vhost_set_log_base;
     vhost_set_mem_table_op vhost_set_mem_table;
     vhost_set_vring_addr_op vhost_set_vring_addr;
+    vhost_set_vring_sqcq_addr_op vhost_set_vring_sqcq_addr;
     vhost_set_vring_endian_op vhost_set_vring_endian;
     vhost_set_vring_num_op vhost_set_vring_num;
     vhost_set_vring_base_op vhost_set_vring_base;
diff --git a/include/hw/virtio/vhost.h b/include/hw/virtio/vhost.h
index 08bbb4d..65056a0 100644
--- a/include/hw/virtio/vhost.h
+++ b/include/hw/virtio/vhost.h
@@ -27,6 +27,8 @@ struct vhost_virtqueue {
     void *desc;
     void *avail;
     void *used;
+    void *sq;           /* Mapped SQ doorbell */
+    void *cq;           /* Mapped CQ doorbell */
     int num;
     unsigned long long desc_phys;
     unsigned desc_size;
@@ -34,6 +36,10 @@ struct vhost_virtqueue {
     unsigned avail_size;
     unsigned long long used_phys;
     unsigned used_size;
+    unsigned long long sq_phys;  /* GPA of SQ doorbell */
+    unsigned long long cq_phys;  /* GPA of CQ doorbell */
+    unsigned sq_size;   /* Size of mapped SQ region */
+    unsigned cq_size;   /* Size of mapped CQ region */
     EventNotifier masked_notifier;
     EventNotifier error_notifier;
     EventNotifier masked_config_notifier;
diff --git a/include/hw/virtio/virtio-pci.h b/include/hw/virtio/virtio-pci.h
index 6397529..37103c7 100644
--- a/include/hw/virtio/virtio-pci.h
+++ b/include/hw/virtio/virtio-pci.h
@@ -122,6 +122,8 @@ typedef struct VirtIOPCIQueue {
   uint32_t desc[2];
   uint32_t avail[2];
   uint32_t used[2];
+  uint32_t sq[2];
+  uint32_t cq[2];
 } VirtIOPCIQueue;
 
 struct VirtIOPCIProxy {
diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h
index 27cd98d..29f21f4 100644
--- a/include/hw/virtio/virtio.h
+++ b/include/hw/virtio/virtio.h
@@ -361,6 +361,7 @@ int virtio_queue_get_max_num(VirtIODevice *vdev, int n);
 int virtio_get_num_queues(VirtIODevice *vdev);
 void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc,
                             hwaddr avail, hwaddr used);
+void virtio_queue_set_sqcq(VirtIODevice *vdev, int n, hwaddr sq, hwaddr cq);
 void virtio_queue_update_rings(VirtIODevice *vdev, int n);
 void virtio_init_region_cache(VirtIODevice *vdev, int n);
 void virtio_queue_set_align(VirtIODevice *vdev, int n, int align);
@@ -408,6 +409,8 @@ bool virtio_queue_enabled_legacy(VirtIODevice *vdev, int n);
 bool virtio_queue_enabled(VirtIODevice *vdev, int n);
 hwaddr virtio_queue_get_avail_addr(VirtIODevice *vdev, int n);
 hwaddr virtio_queue_get_used_addr(VirtIODevice *vdev, int n);
+hwaddr virtio_queue_get_sq_addr(VirtIODevice *vdev, int n);
+hwaddr virtio_queue_get_cq_addr(VirtIODevice *vdev, int n);
 hwaddr virtio_queue_get_desc_size(VirtIODevice *vdev, int n);
 hwaddr virtio_queue_get_avail_size(VirtIODevice *vdev, int n);
 hwaddr virtio_queue_get_used_size(VirtIODevice *vdev, int n);
diff --git a/include/standard-headers/linux/vhost_types.h b/include/standard-headers/linux/vhost_types.h
index 79b53a9..ea96feb 100644
--- a/include/standard-headers/linux/vhost_types.h
+++ b/include/standard-headers/linux/vhost_types.h
@@ -151,6 +151,13 @@ struct vhost_scsi_target {
 	unsigned short reserved;
 };
 
+/* SQ/CQ polling mode */
+struct vhost_vring_sqcq_addr {
+	unsigned int index;
+	__u64 sq_user_addr;   /* SQ doorbell HVA */
+	__u64 cq_user_addr;   /* CQ doorbell HVA */
+};
+
 /* VHOST_VDPA specific definitions */
 
 struct vhost_vdpa_config {
diff --git a/include/standard-headers/linux/virtio_config.h b/include/standard-headers/linux/virtio_config.h
index 45be0fa..d73938e 100644
--- a/include/standard-headers/linux/virtio_config.h
+++ b/include/standard-headers/linux/virtio_config.h
@@ -52,7 +52,7 @@
  * rest are per-device feature bits.
  */
 #define VIRTIO_TRANSPORT_F_START	28
-#define VIRTIO_TRANSPORT_F_END		42
+#define VIRTIO_TRANSPORT_F_END		43
 
 #ifndef VIRTIO_CONFIG_NO_LEGACY
 /* Do we get callbacks when the ring is completely used, even if we've
@@ -118,4 +118,7 @@
  */
 #define VIRTIO_F_ADMIN_VQ		41
 
+/* SQ/CQ polling mode for io_uring-style doorbell polling */
+#define VIRTIO_F_SQCQ_POLL		42
+
 #endif /* _LINUX_VIRTIO_CONFIG_H */
diff --git a/include/standard-headers/linux/virtio_pci.h b/include/standard-headers/linux/virtio_pci.h
index 4c82513..cc585b6 100644
--- a/include/standard-headers/linux/virtio_pci.h
+++ b/include/standard-headers/linux/virtio_pci.h
@@ -235,6 +235,10 @@ struct virtio_pci_cfg_cap {
 #define VIRTIO_PCI_COMMON_Q_RESET	58
 #define VIRTIO_PCI_COMMON_ADM_Q_IDX	60
 #define VIRTIO_PCI_COMMON_ADM_Q_NUM	62
+#define VIRTIO_PCI_COMMON_SQE_LO	64
+#define VIRTIO_PCI_COMMON_SQE_HI	68
+#define VIRTIO_PCI_COMMON_CQE_LO	72
+#define VIRTIO_PCI_COMMON_CQE_HI	76
 
 #endif /* VIRTIO_PCI_NO_MODERN */
 
diff --git a/include/standard-headers/linux/virtio_ring.h b/include/standard-headers/linux/virtio_ring.h
index 22f6eb8..520b758 100644
--- a/include/standard-headers/linux/virtio_ring.h
+++ b/include/standard-headers/linux/virtio_ring.h
@@ -245,4 +245,34 @@ struct vring_packed_desc {
 	uint16_t flags;
 };
 
+/*
+ * struct vring_sq - Submission Queue doorbell for polling mode.
+ * Placed alongside the standard virtio split ring to enable
+ * kick-less notification from guest to host.
+ *
+ * @idx: Producer index (guest updates, corresponds to avail->idx)
+ * @sq_need_wakeup: device requests guest to kick when sleeping
+ * @reserved: Reserved for future use, pad to cache-line
+ */
+struct vring_sq {
+	__virtio64 idx;
+	uint8_t sq_need_wakeup;
+	uint8_t reserved[55];
+} __attribute__((aligned(128)));
+
+/*
+ * struct vring_cq - Completion Queue doorbell for polling mode.
+ * Placed alongside the standard virtio split ring to enable
+ * interrupt-less notification from host to guest.
+ *
+ * @idx: Producer index (device updates, corresponds to used->idx)
+ * @cq_need_wakeup: guest requests host to signal when sleeping
+ * @reserved: Reserved for future use, pad to cache-line
+ */
+struct vring_cq {
+	__virtio64 idx;
+	uint8_t cq_need_wakeup;
+	uint8_t reserved[55];
+} __attribute__((aligned(128)));
+
 #endif /* _LINUX_VIRTIO_RING_H */
diff --git a/linux-headers/linux/vhost.h b/linux-headers/linux/vhost.h
index c57674a..746d71f 100644
--- a/linux-headers/linux/vhost.h
+++ b/linux-headers/linux/vhost.h
@@ -123,6 +123,10 @@
 #define VHOST_SET_BACKEND_FEATURES _IOW(VHOST_VIRTIO, 0x25, __u64)
 #define VHOST_GET_BACKEND_FEATURES _IOR(VHOST_VIRTIO, 0x26, __u64)
 
+/* Set SQ/CQ doorbell addresses for polling mode */
+#define VHOST_SET_VRING_SQCQ_ADDR _IOW(VHOST_VIRTIO, 0x27, \
+				struct vhost_vring_sqcq_addr)
+
 /* VHOST_NET specific defines */
 
 /* Attach virtio net ring to a raw socket, or tap device.
-- 
2.34.1


^ permalink raw reply related


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