Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH v8 0/8] mm/hmm: Add mmap lock-drop support for userfaultfd-backed mappings
From: Stanislav Kinsburskii @ 2026-07-11  3:22 UTC (permalink / raw)
  To: Andrew Morton
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <20260710151151.1e193eedd0cf2591ae392f76@linux-foundation.org>

On Fri, Jul 10, 2026 at 03:11:51PM -0700, Andrew Morton wrote:
> On Fri, 10 Jul 2026 14:26:20 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
> 
> > This series extends the HMM framework to support userfaultfd-backed memory
> > by allowing the mmap read lock to be dropped during hmm_range_fault().
> 
> Thanks.  This seems fairly mature and mostly-reviewed so I'll give it a
> spin in mm.git's mm-new branch.
> 
> Unfortunately Sashiko wasn't able to apply this or v7.  I'm not sure
> what base you were using.  Hopefully there's a reason for a v9 so we
> can retry this.
> 

I rebased this series on top of mm-new right before sending it out.
Should I have used a different branch?

Thanks,
Stanislav

> I have a few niggles, nothing major...

^ permalink raw reply

* Re: [PATCH v8 7/8] accel/amdxdna: Use hmm_range_fault_unlocked_timeout() for range population
From: Stanislav Kinsburskii @ 2026-07-11  3:19 UTC (permalink / raw)
  To: Andrew Morton
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <20260710151228.ca22e127b93ec5c6d591fb5f@linux-foundation.org>

On Fri, Jul 10, 2026 at 03:12:28PM -0700, Andrew Morton wrote:
> On Fri, 10 Jul 2026 14:27:12 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
> 
> > --- a/drivers/accel/amdxdna/aie2_ctx.c
> > +++ b/drivers/accel/amdxdna/aie2_ctx.c
> > @@ -1061,22 +1061,11 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)
> >  		return -EFAULT;
> >  	}
> >  
> > -	mapp->range.notifier_seq = mmu_interval_read_begin(&mapp->notifier);
> > -	mmap_read_lock(mm);
> > -	ret = hmm_range_fault(&mapp->range);
> > -	mmap_read_unlock(mm);
> > +	ret = hmm_range_fault_unlocked_timeout(&mapp->range,
> > +			max_t(long, timeout - jiffies, 1));
> 
> max(timeout - jiffies, 1UL)?

"ma" for sure, thank you.
I have the same quesitong here: will "max(timeout - jiffies, 1UL)"
handle negative "timeout - jiffies" values correctly?

Thanks,
Stanislav

^ permalink raw reply

* Re: [PATCH v8 5/8] drm/nouveau: Use hmm_range_fault_unlocked_timeout() for SVM faults
From: Stanislav Kinsburskii @ 2026-07-11  3:16 UTC (permalink / raw)
  To: Andrew Morton
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <20260710151222.ddb35eab9c81a8720491464a@linux-foundation.org>

On Fri, Jul 10, 2026 at 03:12:22PM -0700, Andrew Morton wrote:
> On Fri, 10 Jul 2026 14:26:58 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
> 
> > @@ -683,15 +683,11 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm,
> >  			goto out;
> >  		}
> >  
> > -		range.notifier_seq = mmu_interval_read_begin(range.notifier);
> > -		mmap_read_lock(mm);
> > -		ret = hmm_range_fault(&range);
> > -		mmap_read_unlock(mm);
> > -		if (ret) {
> > -			if (ret == -EBUSY)
> > -				continue;
> > +		ret = hmm_range_fault_unlocked_timeout(&range,
> > +						       max(timeout - jiffies,
> > +							   1L));
> 
> "1UL" here?  I'd have expected min() to warn, as it likes to do.

I'm not sure... The "timeout - jiffies" can become negative.
Won't 1UL convert both of them to "UL" and thus make the comparison
overflow?

Thanks,
Stanislav

^ permalink raw reply

* Re: [PATCH v8 4/8] mshv: Use hmm_range_fault_unlocked_timeout() for region faults
From: Stanislav Kinsburskii @ 2026-07-11  3:14 UTC (permalink / raw)
  To: Andrew Morton
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <20260710151216.0397a6f9ac5c7b4ccd274cc1@linux-foundation.org>

On Fri, Jul 10, 2026 at 03:12:16PM -0700, Andrew Morton wrote:
> On Fri, 10 Jul 2026 14:26:50 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
> 
> > MSHV currently faults movable memory regions by taking mmap_read_lock()
> > around hmm_range_fault(). That prevents the fault path from handling VMAs
> > whose fault handlers need to drop mmap_lock, such as userfaultfd-backed
> > mappings.
> > 
> > Use hmm_range_fault_unlocked_timeout() instead. Passing a timeout of 0
> > preserves MSHV's existing unbounded retry behavior while letting the HMM
> > helper own mmap_lock acquisition and refresh range->notifier_seq internally
> > before walking the range. After the fault succeeds, MSHV still takes
> > mreg_mutex and checks mmu_interval_read_retry() before installing the pages
> > into the region, so the existing invalidation synchronization is preserved.
> > 
> > Fold the small fault-and-lock helper into mshv_region_range_fault(), since
> > the remaining retry path is just the standard "fault, take the driver lock,
> > check the interval notifier sequence" pattern.
> > 
> > ...
> >
> > @@ -452,13 +412,19 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
> >  	range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
> >  	range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
> >  
> > -	do {
> > -		ret = mshv_region_hmm_fault_and_lock(region, &range);
> > -	} while (ret == -EBUSY);
> > -
> > +again:
> > +	ret = hmm_range_fault_unlocked_timeout(&range, 0);
> >  	if (ret)
> >  		goto out;
> >  
> > +	mutex_lock(&region->mreg_mutex);
> > +
> > +	if (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {
> > +		mutex_unlock(&region->mreg_mutex);
> > +		cond_resched();
> > +		goto again;
> > +	}
> > +
> 
> If the calling process has realtime scheduling policy and either a)
> we're uniprocessor or b) this process and the holder of
> interval_sub->invalidate_seq are both pinned to the same CPU then
> cond_resched() won't do anything, and this might be an infinite loop?

Yes, looks like it might.
What can be done to prevent this?

Thanks,
Stanislav


^ permalink raw reply

* Re: [PATCH v8 2/8] mm/hmm: add hmm_range_fault_unlocked_timeout() for mmap lock-drop support
From: Stanislav Kinsburskii @ 2026-07-11  3:09 UTC (permalink / raw)
  To: Andrew Morton
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <20260710151209.157d7c80b03dc56d73b5884a@linux-foundation.org>

On Fri, Jul 10, 2026 at 03:12:09PM -0700, Andrew Morton wrote:
> On Fri, 10 Jul 2026 14:26:35 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:
> 
> > hmm_range_fault() requires the caller to hold the mmap read lock for the
> > duration of the call. This is incompatible with mappings whose fault
> > handler may release the mmap lock, notably userfaultfd-managed regions,
> > where handle_mm_fault() can return VM_FAULT_RETRY or VM_FAULT_COMPLETED
> > after dropping the lock. Drivers that need to populate device page tables
> > for such mappings have no way to do so today.
> > 
> > Add hmm_range_fault_unlocked_timeout() for callers that do not need to hold
> > mmap_lock across any work outside the HMM fault itself. The helper takes
> > mmap_read_lock_killable() internally, calls the common HMM fault
> > implementation, and releases the lock before returning if it is still held.
> > The timeout is specified in jiffies; passing 0 retries indefinitely, while
> > a non-zero timeout makes the helper return -EBUSY when the retry budget
> > expires.
> > 
> > When handle_mm_fault() drops mmap_lock, or when the range is invalidated,
> > hmm_range_fault_unlocked_timeout() refreshes range->notifier_seq and
> > retries the walk internally. If the lock was dropped, the retry deadline is
> > also restarted because a lock-dropping fault handler made progress.
> > Ordinary -EBUSY retries keep the existing deadline, preserving the caller's
> > timeout policy for repeated mmu-notifier invalidations.
> > 
> > The caller only needs to perform the usual post-success
> > mmu_interval_read_retry() check while holding its update lock before
> > consuming the pfns. If mmap_lock acquisition is interrupted or a fatal
> > signal is pending during retry handling, -EINTR is returned instead.
> > 
> > The common implementation conditionally sets FAULT_FLAG_ALLOW_RETRY and
> > FAULT_FLAG_KILLABLE only for hmm_range_fault_unlocked_timeout(). The
> > existing hmm_range_fault() path still passes no locked state, does not
> > allow handle_mm_fault() to drop mmap_lock, and remains a thin wrapper
> > preserving the existing API contract for current callers.
> > 
> > The previous refactor that moved page fault handling out of the page-table
> > walk callbacks is what makes this change small. Faults now run after
> > walk_page_range() has unwound, with only mmap_lock held, so dropping it
> > does not interact with the walker's pte spinlock or hugetlb_vma_lock.
> > Hugetlb regions therefore participate in the unlocked path uniformly with
> > PTE- and PMD-level mappings; no special case is required.
> > 
> > Documentation/mm/hmm.rst is updated with a description of the new API and
> > the recommended caller pattern.
> > 
> > ...
> >
> 
> A trivial thing:
> 
> > +int hmm_range_fault_unlocked_timeout(struct hmm_range *range,
> > +				     unsigned long timeout)
> > +{
> > +	struct mm_struct *mm = range->notifier->mm;
> > +	unsigned long deadline = 0;
> > +	bool locked = false;
> 
> This could be local to the do loop and it needn't be initialized.
> 

Unfortunately, it can’t, because its state is mutated in
hmm_range_fault_locked() and the resulting state needs to be preserved
across iterations of the loop as deadline reset depends on it.

Thanks,
Stanislav

> > +	int ret;
> > +
> > +	do {
> > +		if (fatal_signal_pending(current))
> > +			return -EINTR;
> > +
> > +		if (timeout) {
> > +			/*
> > +			 * If the previous fault dropped mmap_lock, then the fault
> > +			 * handler made progress. Restart the retry timeout in that
> > +			 * case, but keep the existing deadline for ordinary -EBUSY
> > +			 * retries.
> > +			 */
> > +			if (!locked)
> > +				deadline = jiffies + timeout;
> > +
> > +			if (time_after(jiffies, deadline))
> > +				return -EBUSY;
> > +		}
> > +
> > +		range->notifier_seq =
> > +			mmu_interval_read_begin(range->notifier);
> > +
> > +		ret = mmap_read_lock_killable(mm);
> > +		if (ret)
> > +			return ret;
> > +
> > +		locked = true;
> > +		ret = hmm_range_fault_locked(range, &locked);
> > +		if (locked)
> > +			mmap_read_unlock(mm);
> > +	} while (ret == -EBUSY);
> > +
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL(hmm_range_fault_unlocked_timeout);
> > +
> 

^ permalink raw reply

* Re: [PATCH 1/4] Docs/ABI/damon: fix typo in intervals_goal sysfs path
From: SJ Park @ 2026-07-11  0:21 UTC (permalink / raw)
  To: SJ Park; +Cc: Song Hu, damon, linux-mm, linux-kernel, linux-doc, stable
In-Reply-To: <20260710141738.24789-1-sj@kernel.org>

On Fri, 10 Jul 2026 07:17:37 -0700 SJ Park <sj@kernel.org> wrote:

> On Fri, 10 Jul 2026 12:47:34 +0800 Song Hu <husong@kylinos.cn> wrote:
> 
> > The ABI document spells the DAMON sysfs directory as "intrvals_goal"
> > (missing 'e') in four What: entries, but the kernel creates it as
> > "intervals_goal" (mm/damon/sysfs.c).  Following the documented path
> > therefore yields a non-existent directory.
> 
> Nice catch!
> 
> > 
> > Fixes: e2b23dc62369 ("Docs/ABI/damon: document intervals auto-tuning ABI")
> > Cc: stable@vger.kernel.org

By the way, hotfixes and non-hotfixes usually take different trains to the
mainline.  Having those in single series therefore makes maintainer works
difficult.  I understand this is not a hotfix but just somewhat worthy to
eventually be backported to stable kernels.  So no problem for this.

But, from the next time, please clarify or use different series for Cc: stable@
patches.


Thanks,
SJ

[...]

^ permalink raw reply

* RE: [PATCH v2 01/19] dt-bindings: crypto: add Rambus CryptoManager Hub
From: Ousherovitch, Alex @ 2026-07-10 23:14 UTC (permalink / raw)
  To: Conor Dooley, Krishnamoorthy, Saravanakrishnan
  Cc: Albert Ou, Conor Dooley, David S. Miller, Herbert Xu,
	Jonathan Corbet, Krzysztof Kozlowski, Palmer Dabbelt,
	Paul Walmsley, Rob Herring, Shuah Khan, Alexandre Ghiti,
	devicetree@vger.kernel.org, Wittenauer, Joel,
	linux-api@vger.kernel.org, linux-crypto@vger.kernel.org,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-riscv@lists.infradead.org,
	Shuah Khan, Nguyen, Thi
In-Reply-To: <20260710-siding-unmatched-5e066fbe4c01@wendy>

On Fri, Jul 10, 2026 at 1:59 AM, Conor Dooley <conor.dooley@microchip.com> wrote:

> This company no longer exists, you should probably introduce a rambus
> vendor prefix instead.

Cryptography Research, Inc. does still exist -- it's now a wholly-owned
subsidiary of Rambus (our co-maintainer is @cryptography.com). The
prefix names the IP originator, which is consistent with existing
subsidiary/acquired-vendor prefixes in the tree (e.g. al = Annapurna
Labs under Amazon, mstar noted as acquired by MediaTek, fsl, cavium,
xlnx). We'd prefer to keep "cri" on that basis, and can annotate the
description as "Cryptography Research, Inc. (a Rambus company)" to make
the ownership explicit. Happy to switch if you feel strongly.

> This property seems like it could be replaced by having a reg entry
> for each mailbox.

Agreed -- v3 will make each mailbox a subnode with its own reg window
and drop cri,mbx-instances.

> This looks like it should be deducible from a device-specific
> compatible. [slots/strides]

These aren't fixed per silicon -- they're the per-mailbox layout of the
VCQ rings in host DMA memory, chosen at platform integration and
programmed by the driver into the mailbox QUEUE/SLOTS/STRIDE registers.
They can differ per mailbox on the same silicon, so a compatible can't
encode them. v3 will keep them as optional, defaulted properties on the
per-mailbox subnodes.

> This whole subnode thing seems like it is only required because you
> don't have device-specific compatibles [cores].

Core presence is actually discoverable at runtime from the CORE_ENABLE
register, so v3 will drop the per-core child nodes entirely and probe
for enabled cores -- no per-variant compatible needed.

> this could probably be handled via reg-names? [affinity]

Yes -- v3 will express affinity per mailbox (a "role" of a specific core
type for a dedicated mailbox, or "generic" for the round-robin pool),
which is the subnode analog of your reg-names idea. One caveat: this
cleanly covers 1:1 core-to-mailbox dedication plus a shared pool; a
mailbox dedicated to several specific cores would need multiple role
tokens.

Thanks -- this restructures nicely.

^ permalink raw reply

* Re: [PATCH v7 03/12] sysfs: Add preferred CPU file
From: Yury Norov @ 2026-07-10 23:09 UTC (permalink / raw)
  To: Shrikanth Hegde
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <adc1793e-694f-4531-ae61-b6b3eef568c5@linux.ibm.com>

On Fri, Jul 10, 2026 at 10:30:43PM +0530, Shrikanth Hegde wrote:
> Hi Yury, thanks for taking a look.
> 
> On 7/10/26 9:11 PM, Yury Norov wrote:
> > On Fri, Jul 10, 2026 at 03:26:39AM +0530, Shrikanth Hegde wrote:
> > > Add "preferred" file in /sys/devices/system/cpu
> > > 
> > > This offers
> > > - User can quickly check which CPUs are marked as preferred at this
> > >    moment.
> > > - Userspace algorithms irqbalance could use this mask to send irq into
> > >    preferred CPUs.
> > > 
> > > For example:
> > > cat /sys/devices/system/cpu/online
> > > 0-719
> > > cat /sys/devices/system/cpu/preferred
> > > 0-599        <<< Implies 0-599 are preferred for workloads and 600-719
> > >                   should be avoided at this moment.
> > > 
> > > cat /sys/devices/system/cpu/preferred
> > > 0-719        <<< All CPUs are usable. There is no preference.
> > > 
> > > Signed-off-by: Shrikanth Hegde <sshegde@linux.ibm.com>
> > > ---
> > >   Documentation/ABI/testing/sysfs-devices-system-cpu | 11 +++++++++++
> > >   drivers/base/cpu.c                                 |  8 ++++++++
> > >   2 files changed, 19 insertions(+)
> > > 
> > > diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu
> > > index 82d10d556cc8..ac1dbb209cc7 100644
> > > --- a/Documentation/ABI/testing/sysfs-devices-system-cpu
> > > +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu
> > > @@ -806,3 +806,14 @@ Date:		Nov 2022
> > >   Contact:	Linux kernel mailing list <linux-kernel@vger.kernel.org>
> > >   Description:
> > >   		(RO) the list of CPUs that can be brought online.
> > > +
> > > +What:		/sys/devices/system/cpu/preferred
> > > +Date:		July 2026
> > > +Contact:	Linux kernel mailing list <linux-kernel@vger.kernel.org>
> > > +Description:
> > > +		(RO) the list of preferred CPUs at this moment.
> > > +		These are the only CPUs meant to be used at the moment.
> > > +		Using CPU outside of the list could lead to more
> > > +		contention of underlying physical CPU resource. Dynamically
> > > +		changes based on steal time. With CONFIG_PREFERRED_CPU=n it
> > > +		is same as active CPUs. See sched-arch.rst for more details.
> > 
> > This should mention that it's about paravirtualization.
> 
> Ok. I will rephrase it.
> 
> > 
> > > diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c
> > > index 19d288a3c80c..4ac990efee7c 100644
> > > --- a/drivers/base/cpu.c
> > > +++ b/drivers/base/cpu.c
> > > @@ -391,6 +391,13 @@ static int cpu_uevent(const struct device *dev, struct kobj_uevent_env *env)
> > >   }
> > >   #endif
> > > +static ssize_t preferred_show(struct device *dev,
> > > +			      struct device_attribute *attr, char *buf)
> > > +{
> > > +	return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpu_preferred_mask));
> > > +}
> > > +static DEVICE_ATTR_RO(preferred);
> > > +
> > >   const struct bus_type cpu_subsys = {
> > >   	.name = "cpu",
> > >   	.dev_name = "cpu",
> > > @@ -532,6 +539,7 @@ static struct attribute *cpu_root_attrs[] = {
> > >   #ifdef CONFIG_GENERIC_CPU_AUTOPROBE
> > >   	&dev_attr_modalias.attr,
> > >   #endif
> > > +	&dev_attr_preferred.attr,
> > 
> >   #ifdef CONFIG_PREFERRED_CPUS ?
> 
> Not needed no? It will print active CPUs.

If I didn't enable preferred CPUs, I'll be pretty surprised having
them in my statistics.

^ permalink raw reply

* Re: [PATCH v7 06/12] sched/core: Push current task from non preferred CPU
From: Yury Norov @ 2026-07-10 23:02 UTC (permalink / raw)
  To: Shrikanth Hegde
  Cc: linux-kernel, mingo, peterz, juri.lelli, vincent.guittot,
	yury.norov, kprateek.nayak, iii, corbet, tglx, gregkh, pbonzini,
	seanjc, vschneid, huschle, rostedt, dietmar.eggemann, maddy,
	srikar, hdanton, chleroy, vineeth, frederic, arighi, pauld,
	christian.loehle, tj, tommaso.cucinotta, maz, rafael, rdunlap,
	kernellwp, linux-doc
In-Reply-To: <20260709215648.1246821-7-sshegde@linux.ibm.com>

On Fri, Jul 10, 2026 at 03:26:42AM +0530, Shrikanth Hegde wrote:
> Actively push out task running on a non-preferred CPU. Since the task is
> running on the CPU, need to stop the cpu and push the task out.
> However, if the task is pinned only to non-preferred CPUs, it will continue
> running there. This will help in maintaining the userspace affinities
> unlike CPU hotplug or isolated cpusets.
> 
> Though code is similar to  __balance_push_cpu_stop and quite close to
> push_cpu_stop, it is being kept separate as it provides a cleaner
> implementation with CONFIG_PREFERRED_CPU.
> 
> Add push_task_work_done flag to protect work buffer.
> Works only with FAIR class.
> 
> For now, only current running task is pushed out. This keeps the code
> simpler. In future optimization maybe done to move all the queued
> task on the rq.
> 
> Signed-off-by: Shrikanth Hegde <sshegde@linux.ibm.com>
> ---
> v6->v7:
> - Moved is_migration_disabled
> - removed fair class check 
> 
>  kernel/sched/core.c  | 78 ++++++++++++++++++++++++++++++++++++++++++++
>  kernel/sched/sched.h |  8 +++++
>  2 files changed, 86 insertions(+)
> 
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 9e8eec4451b6..74c93a88bf84 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -5774,6 +5774,9 @@ void sched_tick(void)
>  	unsigned long hw_pressure;
>  	u64 resched_latency;
>  
> +	if (!cpu_preferred(cpu))
> +		sched_push_current_non_preferred_cpu(rq);
> +
>  	if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE))
>  		arch_scale_freq_tick();
>  
> @@ -11292,3 +11295,78 @@ void sched_change_end(struct sched_change_ctx *ctx)
>  		p->sched_class->prio_changed(rq, p, ctx->prio);
>  	}
>  }
> +
> +#ifdef CONFIG_PREFERRED_CPU
> +static DEFINE_PER_CPU(struct cpu_stop_work, npc_push_task_work);
> +
> +static int sched_non_preferred_cpu_push_stop(void *arg)
> +{
> +	struct task_struct *p = arg;
> +	struct rq *rq = this_rq();
> +	struct rq_flags rf;
> +	int cpu;
> +
> +	/* sanity checks and clear */
> +	if (cpu_preferred(rq->cpu) || is_migration_disabled(p)) {
> +		scoped_guard(rq_lock, rq)
> +			rq->push_task_work_done = false;
> +		put_task_struct(p);
> +		return 0;
> +	}
> +
> +	raw_spin_lock_irq(&p->pi_lock);
> +
> +	/* This could take rq lock. So call it before rq lock is taken */
> +	cpu = select_fallback_rq(rq->cpu, p);
> +	rq_lock(rq, &rf);
> +	rq->push_task_work_done = false;
> +	update_rq_clock(rq);
> +
> +	context_unsafe_alias(rq);
> +
> +	if (task_rq(p) == rq && task_on_rq_queued(p))
> +		rq = __migrate_task(rq, &rf, p, cpu);
> +
> +	rq_unlock(rq, &rf);
> +	raw_spin_unlock_irq(&p->pi_lock);
> +	put_task_struct(p);
> +
> +	return 0;
> +}
> +
> +/*
> + * Push the current task running on non-preferred CPU(npc).
> + * Using this non preferred CPU will lead to more vCPU preemptions
> + * in the host. So it is better not to use this CPU.
> + *
> + * Since task is running, call a stopper to push the task out. This is
> + * similar to how task moves during hotplug. In select_fallback_rq a
> + * preferred CPU will be chosen and henceforth task shouldn't come back to
> + * this CPU again.
> + *
> + * Works for FAIR class only
> + *
> + * If task is affined only non-preferred CPUs, it can't be moved out
> + */
> +void sched_push_current_non_preferred_cpu(struct rq *rq)
> +{
> +	struct task_struct *push_task = rq->curr;
> +
> +	/* Don't push the task if task's affinity doesn't allow */
> +	if (!task_can_sched_on_preferred(rq->cpu, push_task))
> +		return;

Shouldn't you protect it with the rq lock against races with affinity or
policy changes? The task_can_sched_on_preferred() checks p->sched_class
and p->cpus_ptr.

> +
> +	/* There is already a stopper thread. Don't race with it. */
> +	if (rq->push_task_work_done)
> +		return;
> +
> +	/* sched_tick runs with interrupts disabled. */
> +	get_task_struct(push_task);
> +
> +	scoped_guard(rq_lock, rq)
> +		rq->push_task_work_done = true;
> +
> +	stop_one_cpu_nowait(rq->cpu, sched_non_preferred_cpu_push_stop,
> +			    push_task, this_cpu_ptr(&npc_push_task_work));
> +}
> +#endif
> diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
> index 6de6366f2faa..80c02e2c09eb 100644
> --- a/kernel/sched/sched.h
> +++ b/kernel/sched/sched.h
> @@ -1277,6 +1277,8 @@ struct rq {
>  
>  	struct list_head cfs_tasks;
>  
> +	bool			push_task_work_done;
> +
>  	struct sched_avg	avg_rt;
>  	struct sched_avg	avg_dl;
>  #ifdef CONFIG_HAVE_SCHED_AVG_IRQ
> @@ -4242,4 +4244,10 @@ static inline bool task_can_sched_on_preferred(int cpu, struct task_struct *p)
>  	return cpumask_intersects(p->cpus_ptr, cpu_preferred_mask);
>  }
>  
> +#ifdef CONFIG_PREFERRED_CPU
> +void sched_push_current_non_preferred_cpu(struct rq *rq);
> +#else	/* !CONFIG_PREFERRED_CPU */
> +static inline void sched_push_current_non_preferred_cpu(struct rq *rq) { }
> +#endif
> +
>  #endif /* _KERNEL_SCHED_SCHED_H */
> -- 
> 2.47.3

^ permalink raw reply

* Re: [PATCH v3 07/11] vfio/pci: Add CONFIG_VFIO_PCI_CXL with bind-time CXL Type-2 acquisition
From: Alex Williamson @ 2026-07-10 22:23 UTC (permalink / raw)
  To: mhonap
  Cc: djbw, jgg, jic23, dave.jiang, ankita, alejandro.lucero-palau,
	alison.schofield, dave, dmatlack, gourry, ira.weiny, cjia, kjaju,
	vsethi, zhiw, kvm, linux-cxl, linux-doc, linux-kernel,
	linux-kselftest, alex
In-Reply-To: <20260625165407.1769572-8-mhonap@nvidia.com>

On Thu, 25 Jun 2026 22:24:03 +0530
<mhonap@nvidia.com> wrote:

> From: Manish Honap <mhonap@nvidia.com>
> 
> Wire vfio-pci-core to acquire CXL Type-2 device state at PCI bind
> and release it at PCI unbind, mirroring the existing vfio_pci_zdev_*
> integration model.

I don't follow, zpci has the following matching hooks:

 - vfio_pci_zdev_open_device
 - vfio_pci_zdev_close_device

Plus it also has:

 - vfio_pci_info_zdev_add_caps

What about those suggest "zdev state" is acquired at bind and released
on unbind?

>  Four lifecycle hooks are introduced —
> vfio_pci_cxl_acquire / _release / _open / _close — with !-config
> stubs that return -ENODEV / 0 / 0 / no-op respectively so vfio-pci
> behaviour is unchanged when CONFIG_VFIO_PCI_CXL=n.
> 
> vfio_pci_cxl_acquire() implements the bind sequence:
> 
>   - pcie_is_cxl() and CXL Device DVSEC discovery (-ENODEV if absent
>     or if MEM_CAPABLE clear — caller falls back to plain vfio-pci)
>   - devm_cxl_dev_state_create() with struct vfio_pci_cxl_state
>     embedding cxl_dev_state at offset 0 (required by the 7-arg
>     macro's static_assert in include/cxl/cxl.h)
>   - pci_enable_device_mem(), cxl_pci_setup_regs(), cxl_get_hdm_info()
>     (rejecting hdm_count != 1), cxl_regblock_get_bar_info(),
>     cxl_await_range_active()

The cover letter claims otherwise:

  "- cxl_await_range_active stays in cxl-core probe; not exported, vfio
  does not call it."

It's exported in 2/ and called below.

>   - devm_cxl_passthrough_create() to snapshot the DVSEC body, HDM
>     block, and CM cap-array shadows owned by cxl-core
>   - pci_disable_device() — clears PCI_COMMAND_MASTER but NOT
>     PCI_COMMAND_MEMORY, so cxl-core MMIO accesses from the next step
>     still succeed
>   - devm_cxl_probe_mem() to register the cxl_memdev, enumerate the
>     endpoint port, and attach the firmware-committed autoregion
>   - request_mem_region() + memremap_wb() of the autoregion's HPA so
>     the HDM VFIO region can serve guest accesses through it

How does this interact with:

 - The device making use of low power states while idle
 - Repeatability per tenant instance
 - Protection of tenant data per instance

The culmination of all of these, plus the basic housekeeping of
maintaining the lightest touch on the device, including keeping the
device in the minimum state of functionality outside of an actual user,
is why I would expect to perform acquire/release as part of open/close.

Could a low power transition invalidate the state established by
acquire, leading to the issue Richard encountered?

Also, on the direct calls to cxl functions, I thought one of our goals
was to avoid vfio-pci statically pulling in CXL module dependencies.
To achieve that, it seems like at some point we need to detect that we
have a CXL device (pcie_is_cxl(pdev)), do a request_module() to load
vfio-cxl, where the init function would register callback ops with
vfio-pci-core and each dependent device would acquire a reference to
the vfio-cxl module.

> diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
> index 89165b769e5c..541c1911e090 100644
> --- a/include/linux/vfio_pci_core.h
> +++ b/include/linux/vfio_pci_core.h
> @@ -142,6 +142,13 @@ struct vfio_pci_core_device {
>  	struct notifier_block	nb;
>  	struct rw_semaphore	memory_lock;
>  	struct list_head	dmabufs;
> +	/*
> +	 * Opaque pointer to struct vfio_pci_cxl_state (defined in
> +	 * drivers/vfio/pci/cxl/vfio_cxl_priv.h).  Set by
> +	 * vfio_pci_cxl_acquire() at PCI bind; NULL on non-CXL devices
> +	 * and when CONFIG_VFIO_PCI_CXL=n.
> +	 */
> +	void			*cxl;

Use a forward declaration rather than void, that avoids half your
comment.  The remainder of the comment is just explaining the obvious
parts of the code, unnecessary.  Thanks,

Alex

^ permalink raw reply

* Re: [PATCH v3 05/11] vfio: UAPI for CXL Type-2 device passthrough
From: Alex Williamson @ 2026-07-10 22:23 UTC (permalink / raw)
  To: mhonap
  Cc: djbw, jgg, jic23, dave.jiang, ankita, alejandro.lucero-palau,
	alison.schofield, dave, dmatlack, gourry, ira.weiny, cjia, kjaju,
	vsethi, zhiw, kvm, linux-cxl, linux-doc, linux-kernel,
	linux-kselftest, alex
In-Reply-To: <20260625165407.1769572-6-mhonap@nvidia.com>

On Thu, 25 Jun 2026 22:24:01 +0530
<mhonap@nvidia.com> wrote:
> diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h
> index 5de618a3a5ee..3707d53c4de5 100644
> --- a/include/uapi/linux/vfio.h
> +++ b/include/uapi/linux/vfio.h
> @@ -215,6 +215,7 @@ struct vfio_device_info {
>  #define VFIO_DEVICE_FLAGS_FSL_MC (1 << 6)	/* vfio-fsl-mc device */
>  #define VFIO_DEVICE_FLAGS_CAPS	(1 << 7)	/* Info supports caps */
>  #define VFIO_DEVICE_FLAGS_CDX	(1 << 8)	/* vfio-cdx device */
> +#define VFIO_DEVICE_FLAGS_CXL	(1 << 9)	/* vfio-cxl Type-2 device */

Would we define a different flag for type-1/3 if we ever found a need
to expose them through vfio?

>  	__u32	num_regions;	/* Max region index + 1 */
>  	__u32	num_irqs;	/* Max IRQ index + 1 */
>  	__u32   cap_offset;	/* Offset within info struct of first cap */
> @@ -257,6 +258,36 @@ struct vfio_device_info_cap_pci_atomic_comp {
>  	__u32 reserved;
>  };
>  
> +/*
> + * VFIO_DEVICE_INFO capability for CXL Type-2 passthrough devices.
> + * Present when VFIO_DEVICE_FLAGS_CXL is set on vfio_device_info::flags.
> + *
> + * @flags: VFIO_CXL_CAP_HOST_FIRMWARE_COMMITTED indicates the host CXL
> + *	subsystem committed the endpoint HDM decoder.
> + * @hdm_region_idx: VFIO region index for the HDM memory region
> + *	(subtype VFIO_REGION_SUBTYPE_CXL).
> + * @comp_reg_region_idx: VFIO region index for the CXL Component
> + *	Register shadow (subtype VFIO_REGION_SUBTYPE_CXL_COMP_REGS).

These regions are self describing via the noted CXL subtypes, what's
the purpose of double reporting them here?

> + * @comp_reg_bar: PCI BAR index that contains the CXL component
> + *	register block.  Get-region-info on this BAR returns a
> + *	VFIO_REGION_INFO_CAP_SPARSE_MMAP that excludes the CXL block.
> + * @comp_reg_offset: byte offset of the CXL component register block
> + *	within @comp_reg_bar.
> + * @comp_reg_size: byte size of the CXL component register block.

Why don't we describe all of these via a capability on the relevant
region info?

Does that leave this device level capability describing the device as
type-2 (by existence), with only a flags field to declare HDM as
firmware committed, for future compatibility should we support non-fw
committed?  Thanks,

Alex

^ permalink raw reply

* Re: [PATCH v3 08/11] vfio/pci/cxl: Add HDM + COMP_REGS regions and DVSEC clipping shim
From: Alex Williamson @ 2026-07-10 22:23 UTC (permalink / raw)
  To: mhonap
  Cc: djbw, jgg, jic23, dave.jiang, ankita, alejandro.lucero-palau,
	alison.schofield, dave, dmatlack, gourry, ira.weiny, cjia, kjaju,
	vsethi, zhiw, kvm, linux-cxl, linux-doc, linux-kernel,
	linux-kselftest, alex
In-Reply-To: <20260625165407.1769572-9-mhonap@nvidia.com>

On Thu, 25 Jun 2026 22:24:04 +0530
<mhonap@nvidia.com> wrote:
> diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c
> index a10ed733f0e3..b9f30a33515a 100644
> --- a/drivers/vfio/pci/vfio_pci_config.c
> +++ b/drivers/vfio/pci/vfio_pci_config.c
> @@ -1898,8 +1898,15 @@ ssize_t vfio_pci_config_rw_single(struct vfio_pci_core_device *vdev,
>  	/*
>  	 * Chop accesses into aligned chunks containing no more than a
>  	 * single capability.  Caller increments to the next chunk.
> +	 *
> +	 * For CXL Type-2 devices also clip at the CXL Device DVSEC body
> +	 * boundary so the generic perm-bits path handles the DVSEC
> +	 * header bytes and the CXL hook handles the body bytes; without
> +	 * this clip a 32-bit access at dvsec + 0x08 would span the
> +	 * generic Header2 word and the CXL CAPABILITY word.
>  	 */
>  	count = min(count, vfio_pci_cap_remaining_dword(vdev, *ppos));
> +	count = min(count, vfio_pci_cxl_config_boundary(vdev, *ppos));
>  	if (count >= 4 && !(*ppos % 4))
>  		count = 4;
>  	else if (count >= 2 && !(*ppos % 2))
> @@ -1909,6 +1916,30 @@ ssize_t vfio_pci_config_rw_single(struct vfio_pci_core_device *vdev,
>  
>  	ret = count;
>  
> +	/*
> +	 * Give the CXL Type-2 hook first claim on this access: if the
> +	 * range lies inside the CXL Device DVSEC body, forward it to
> +	 * cxl-core's register-virtualization helpers instead of the
> +	 * standard perm-bits path.  -ENOENT means "not for me; use the
> +	 * default path"; any other negative value is a hard error.
> +	 */
> +	if (vdev->cxl) {
> +		__le32 le_val = 0;
> +		ssize_t cxl_ret;
> +
> +		if (iswrite && copy_from_user(&le_val, buf, count))
> +			return -EFAULT;
> +		cxl_ret = vfio_pci_cxl_config_rw(vdev, *ppos, count, &le_val,
> +						 iswrite);
> +		if (cxl_ret >= 0) {
> +			if (!iswrite && copy_to_user(buf, &le_val, count))
> +				return -EFAULT;
> +			return cxl_ret;
> +		}
> +		if (cxl_ret != -ENOENT)
> +			return cxl_ret;
> +	}
> +

I think the solution here is just to set the .readfn and .writefn for
PCI_EXT_CAP_ID_DVSEC to dvsec specific handlers, rather than the raw
write and direct read handlers.  The new handlers would detect whether
the reference is to the CXL DVSEC body, possibly via ranges stored in
vdev->cxl, and either call through to CXL handlers via cxl_ops
(previously suggested), or fall through to the raw/direct handlers.

>  	cap_id = vdev->pci_config_map[*ppos];
>  
>  	if (cap_id == PCI_CAP_ID_INVALID) {
> diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
> index 05ab4ae59157..2d2dae278d1e 100644
> --- a/drivers/vfio/pci/vfio_pci_core.c
> +++ b/drivers/vfio/pci/vfio_pci_core.c
> @@ -501,6 +501,23 @@ static void vfio_pci_core_map_bars(struct vfio_pci_core_device *vdev)
>  		if (!pci_resource_len(pdev, i))
>  			continue;
>  
> +		/*
> +		 * cxl-core already holds request_mem_region() on the CXL
> +		 * component register sub-range of this BAR.  Skip the
> +		 * full-BAR request so we do not collide with that
> +		 * sub-region; vfio still owns the BAR via the driver
> +		 * binding and the iomap below succeeds without a region
> +		 * claim.
> +		 */
> +		if (vdev->cxl && bar == vfio_pci_cxl_get_component_reg_bar(vdev)) {
> +			vdev->barmap[bar] = pci_iomap(pdev, bar, 0);
> +			if (!vdev->barmap[bar]) {
> +				pci_dbg(pdev, "Failed to iomap region %d\n", bar);
> +				vdev->barmap[bar] = IOMEM_ERR_PTR(-ENOMEM);
> +			}
> +			continue;
> +		}
> +
>  		if (pci_request_selected_regions(pdev, 1 << bar, "vfio")) {
>  			pci_dbg(pdev, "Failed to reserve region %d\n", bar);
>  			vdev->barmap[bar] = IOMEM_ERR_PTR(-EBUSY);
> @@ -701,7 +718,10 @@ void vfio_pci_core_disable(struct vfio_pci_core_device *vdev)
>  		if (IS_ERR_OR_NULL(vdev->barmap[bar]))
>  			continue;
>  		pci_iounmap(pdev, vdev->barmap[bar]);
> -		pci_release_selected_regions(pdev, 1 << bar);
> +		/* Mirror the asymmetric setup-time skip in map_bars(). */
> +		if (!(vdev->cxl &&
> +		      i == vfio_pci_cxl_get_component_reg_bar(vdev)))
> +			pci_release_selected_regions(pdev, 1 << bar);

It would be much less ugly to create
vfio_pci_{request,release}_selected_region() wrappers that mask whether
the region is actually requested or released than to disrupt the code
flow like this.  Likewise below, think about creating wrappers that do
the right thing for cxl and are no-ops otherwise.  For example, embed
the vdev->cxl test into the function to cleanup the callers.  Thanks,

Alex

>  		vdev->barmap[bar] = NULL;
>  	}
>  
> @@ -1051,6 +1071,16 @@ static int vfio_pci_ioctl_get_info(struct vfio_pci_core_device *vdev,
>  	info.num_regions = VFIO_PCI_NUM_REGIONS + vdev->num_regions;
>  	info.num_irqs = VFIO_PCI_NUM_IRQS;
>  
> +	if (vdev->cxl) {
> +		ret = vfio_pci_cxl_get_info(vdev, &caps);
> +		if (ret) {
> +			pci_warn(vdev->pdev,
> +				 "Failed to add CXL info capability\n");
> +			return ret;
> +		}
> +		info.flags |= VFIO_DEVICE_FLAGS_CXL;
> +	}
> +
>  	ret = vfio_pci_info_zdev_add_caps(vdev, &caps);
>  	if (ret && ret != -ENODEV) {
>  		pci_warn(vdev->pdev,
> @@ -1093,6 +1123,12 @@ int vfio_pci_ioctl_get_region_info(struct vfio_device *core_vdev,
>  	struct pci_dev *pdev = vdev->pdev;
>  	int i, ret;
>  
> +	if (vdev->cxl) {
> +		ret = vfio_pci_cxl_get_region_info(vdev, info, caps);
> +		if (ret != -ENOTTY)
> +			return ret;
> +	}
> +
>  	switch (info->index) {
>  	case VFIO_PCI_CONFIG_REGION_INDEX:
>  		info->offset = VFIO_PCI_INDEX_TO_OFFSET(info->index);

^ permalink raw reply

* [PATCH] arm64: errata: Mitigate AmpereOne erratum AC03_CPU_57 and AC04_CPU_29
From: D Scott Phillips @ 2026-07-10 22:21 UTC (permalink / raw)
  To: Marc Zyngier, Oliver Upton, Catalin Marinas, Will Deacon,
	Jonathan Corbet, Shuah Khan, Joey Gouly, Steffen Eiden,
	Suzuki K Poulose, Zenghui Yu
  Cc: Mark Rutland, Zeng Heng, Wei Xu, Vladimir Murzin, Lucas Wei,
	Kuninori Morimoto, Sascha Bischoff, Yicong Yang, Yeoreum Yun,
	linux-arm-kernel, linux-doc, linux-kernel, kvmarm

On AmpereOne, deactivating a physical interrupt through ICC_DIR_EL1 or
ICC_EOIRx_EL1 (depending on EOImode) which is not active, but is the
highest priority pending interrupt causes the cpu to lose the interrupt
pending state and also prevents the delivery of future interrupts.

Work around this in the vgic, avoiding the cpu issue.

Signed-off-by: D Scott Phillips <scott@os.amperecomputing.com>
---

Hi Marc, we've tracked down the nested virt hang reported
previously[1] to a cpu erratum in AmpereOne[2].

Here I'm just sort of parroting the change you had posted for
debugging. I'm not familiar with the vgic logic well enough to know if
this change is sufficient and doesn't have some unintended
consequences. I guess read this more are as "I've tested this and I'm
not seeing it hang now."

Sorry for sending this half-baked, I didn't want to further delay the
errata details on my ongoing vgic education. I'm happy to take any
advice you can give, otherwise I'll continue familiarizing myself and
will hopefully later have a patch which I can actually claim I think is
correct.

[1]: https://lore.kernel.org/linux-arm-kernel/87ecjybz30.wl-maz@kernel.org/

[2]: https://amperecomputing.com/products/developer-errata

The updates with AC03_CPU_57 and AC04_CPU_29 have not yet been
published at the time I'm writing this. They should be coming
soon. I've reproduced the full entries from those two coming documents
collapsed together below:

| {AC03_CPU_57, AC04_CPU_29}: Deactivation of the non-active, highest
| priority pending interrupt prevents further interrupt delivery.
|
| Functional Unit: CPU
|
| Category: 4
|
| Affected Version(s): AmpereOne AC03 A0, AmpereOne AC03 B0
| Affected Version(s): AmpereOne AC04 A0, AmpereOne AC04_1 A0
|
| Fixed Version(s): Open
|
| Overview:
|
| If software directly deactivates a physical interrupt which is not
| in the active state, and the interrupt is also currently the highest
| priority pending interrupt, then interrupt delivery will cease on
| that PE. Deactivation can happen either through ICC_EOIRx_EL1 if
| ICC_CTLR_EL1.EOIMode==0, or through ICC_DIR_EL1 if
| ICC_CTLR_EL1.EOIMode==1. Deactivation of virtual interrupts that are
| redirected through ICV_ registers will not cause this issue, even
| when the virtual interrupt deactivation triggers a physical
| interrupt deactivation through ICH_LR<n>_EL2.HW=1.
|
| This has been observed with Nested Virtualization starting with
| Linux-KVM v6.19.
|
| Impact:
|
| Physical interrupts will not be delivered after the deactivation of
| the non-active, highest priority pending interrupt. A core may
| appear to be hung.
|
| Workaround:
|
| Software must only deactivate interrupts which are currently active


 Documentation/arch/arm64/silicon-errata.rst |  4 ++++
 arch/arm64/Kconfig                          | 17 +++++++++++++++++
 arch/arm64/kernel/cpu_errata.c              | 15 +++++++++++++++
 arch/arm64/kvm/vgic/vgic-v3.c               |  4 +++-
 arch/arm64/tools/cpucaps                    |  1 +
 5 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index 014aa1c215a16..89130404ce572 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -55,10 +55,14 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | Ampere         | AmpereOne       | AC03_CPU_38     | AMPERE_ERRATUM_AC03_CPU_38  |
 +----------------+-----------------+-----------------+-----------------------------+
+| Ampere         | AmpereOne       | AC03_CPU_57     | AMPERE_ERRATUM_AC03_CPU_57  |
++----------------+-----------------+-----------------+-----------------------------+
 | Ampere         | AmpereOne AC04  | AC04_CPU_10     | AMPERE_ERRATUM_AC03_CPU_38  |
 +----------------+-----------------+-----------------+-----------------------------+
 | Ampere         | AmpereOne AC04  | AC04_CPU_23     | AMPERE_ERRATUM_AC04_CPU_23  |
 +----------------+-----------------+-----------------+-----------------------------+
+| Ampere         | AmpereOne AC04  | AC04_CPU_29     | AMPERE_ERRATUM_AC03_CPU_57  |
++----------------+-----------------+-----------------+-----------------------------+
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A510     | #2457168        | ARM64_ERRATUM_2457168       |
 +----------------+-----------------+-----------------+-----------------------------+
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b3afe0688919b..ee5421283d8df 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -436,6 +436,23 @@ config AMPERE_ERRATUM_AC03_CPU_38
 
 	  If unsure, say Y.
 
+config AMPERE_ERRATUM_AC03_CPU_57
+	bool "AmpereOne: AC03_CPU_57: Deactivation of the non-active, highest priority pending interrupt prevents further interrupt delivery."
+	default y
+	help
+	  This option adds an alternative code sequence to work around Ampere
+	  errata AC03_CPU_57 and AC04_CPU_29 on AmpereOne.
+
+	  Deactivating a physical interrupt through ICC_DIR_EL1 or
+	  ICC_EOIR1_EL1 (depending on EOImode) which is not active, but is the
+	  highest priority pending interrupt causes the cpu to lose the
+	  interrupt pending state and also prevents the delivery of future
+	  interrupts.
+
+	  The workaround is for KVM to not deactivate interrupts for nested vgics.
+
+	  If unsure, say Y.
+
 config AMPERE_ERRATUM_AC04_CPU_23
         bool "AmpereOne: AC04_CPU_23:  Failure to synchronize writes to HCR_EL2 may corrupt address translations."
 	default y
diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index 1995e1198648e..9b03dccd55e09 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -631,6 +631,14 @@ static const struct midr_range erratum_ac03_cpu_38_list[] = {
 };
 #endif
 
+#ifdef CONFIG_AMPERE_ERRATUM_AC03_CPU_57
+static const struct midr_range erratum_ac03_cpu_57_list[] = {
+	MIDR_ALL_VERSIONS(MIDR_AMPERE1),
+	MIDR_ALL_VERSIONS(MIDR_AMPERE1A),
+	{},
+};
+#endif
+
 #ifdef CONFIG_AMPERE_ERRATUM_AC04_CPU_23
 static const struct midr_range erratum_ac04_cpu_23_list[] = {
 	MIDR_ALL_VERSIONS(MIDR_AMPERE1A),
@@ -987,6 +995,13 @@ const struct arm64_cpu_capabilities arm64_errata[] = {
 		ERRATA_MIDR_RANGE_LIST(erratum_ac03_cpu_38_list),
 	},
 #endif
+#ifdef CONFIG_AMPERE_ERRATUM_AC03_CPU_57
+	{
+		.desc = "AmpereOne erratum AC03_CPU_57",
+		.capability = ARM64_WORKAROUND_AMPERE_AC03_CPU_57,
+		ERRATA_MIDR_RANGE_LIST(erratum_ac03_cpu_57_list),
+	},
+#endif
 #ifdef CONFIG_AMPERE_ERRATUM_AC04_CPU_23
 	{
 		.desc = "AmpereOne erratum AC04_CPU_23",
diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c
index 9e841e7afd4a7..8f1d10872360c 100644
--- a/arch/arm64/kvm/vgic/vgic-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-v3.c
@@ -275,7 +275,9 @@ void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val)
 		lr = vgic_v3_compute_lr(vcpu, irq) & ~ICH_LR_ACTIVE_BIT;
 	}
 
-	if (lr & ICH_LR_HW)
+	if ((lr & ICH_LR_HW) &&
+	    !(cpus_have_final_cap(ARM64_WORKAROUND_AMPERE_AC03_CPU_57) &&
+	      vgic_state_is_nested(vcpu)))
 		vgic_v3_deactivate_phys(FIELD_GET(ICH_LR_PHYS_ID_MASK, lr));
 
 	vgic_v3_fold_lr(vcpu, lr);
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 9b85a84f6fd49..7a63f3f7c982b 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -109,6 +109,7 @@ WORKAROUND_2658417
 WORKAROUND_4193714
 WORKAROUND_4311569
 WORKAROUND_AMPERE_AC03_CPU_38
+WORKAROUND_AMPERE_AC03_CPU_57
 WORKAROUND_AMPERE_AC04_CPU_23
 WORKAROUND_TRBE_OVERWRITE_FILL_MODE
 WORKAROUND_TSB_FLUSH_FAILURE
-- 
2.52.0


^ permalink raw reply related

* Re: [PATCH v8 8/8] drm/gpusvm: Use hmm_range_fault_unlocked_timeout() for range faults
From: Andrew Morton @ 2026-07-10 22:12 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178371883977.900500.2198446134676328631.stgit@skinsburskii>

On Fri, 10 Jul 2026 14:27:19 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:

> +	err = hmm_range_fault_unlocked_timeout(&hmm_range,
> +					       max(timeout - jiffies, 1L));

1UL again?

^ permalink raw reply

* Re: [PATCH v8 7/8] accel/amdxdna: Use hmm_range_fault_unlocked_timeout() for range population
From: Andrew Morton @ 2026-07-10 22:12 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178371883276.900500.12789147320642521200.stgit@skinsburskii>

On Fri, 10 Jul 2026 14:27:12 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:

> --- a/drivers/accel/amdxdna/aie2_ctx.c
> +++ b/drivers/accel/amdxdna/aie2_ctx.c
> @@ -1061,22 +1061,11 @@ static int aie2_populate_range(struct amdxdna_gem_obj *abo)
>  		return -EFAULT;
>  	}
>  
> -	mapp->range.notifier_seq = mmu_interval_read_begin(&mapp->notifier);
> -	mmap_read_lock(mm);
> -	ret = hmm_range_fault(&mapp->range);
> -	mmap_read_unlock(mm);
> +	ret = hmm_range_fault_unlocked_timeout(&mapp->range,
> +			max_t(long, timeout - jiffies, 1));

max(timeout - jiffies, 1UL)?

^ permalink raw reply

* Re: [PATCH v8 5/8] drm/nouveau: Use hmm_range_fault_unlocked_timeout() for SVM faults
From: Andrew Morton @ 2026-07-10 22:12 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178371881847.900500.8789369230260725500.stgit@skinsburskii>

On Fri, 10 Jul 2026 14:26:58 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:

> @@ -683,15 +683,11 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm,
>  			goto out;
>  		}
>  
> -		range.notifier_seq = mmu_interval_read_begin(range.notifier);
> -		mmap_read_lock(mm);
> -		ret = hmm_range_fault(&range);
> -		mmap_read_unlock(mm);
> -		if (ret) {
> -			if (ret == -EBUSY)
> -				continue;
> +		ret = hmm_range_fault_unlocked_timeout(&range,
> +						       max(timeout - jiffies,
> +							   1L));

"1UL" here?  I'd have expected min() to warn, as it likes to do.

^ permalink raw reply

* Re: [PATCH v8 4/8] mshv: Use hmm_range_fault_unlocked_timeout() for region faults
From: Andrew Morton @ 2026-07-10 22:12 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178371881034.900500.5214601525971121683.stgit@skinsburskii>

On Fri, 10 Jul 2026 14:26:50 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:

> MSHV currently faults movable memory regions by taking mmap_read_lock()
> around hmm_range_fault(). That prevents the fault path from handling VMAs
> whose fault handlers need to drop mmap_lock, such as userfaultfd-backed
> mappings.
> 
> Use hmm_range_fault_unlocked_timeout() instead. Passing a timeout of 0
> preserves MSHV's existing unbounded retry behavior while letting the HMM
> helper own mmap_lock acquisition and refresh range->notifier_seq internally
> before walking the range. After the fault succeeds, MSHV still takes
> mreg_mutex and checks mmu_interval_read_retry() before installing the pages
> into the region, so the existing invalidation synchronization is preserved.
> 
> Fold the small fault-and-lock helper into mshv_region_range_fault(), since
> the remaining retry path is just the standard "fault, take the driver lock,
> check the interval notifier sequence" pattern.
> 
> ...
>
> @@ -452,13 +412,19 @@ static int mshv_region_range_fault(struct mshv_mem_region *region,
>  	range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
>  	range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
>  
> -	do {
> -		ret = mshv_region_hmm_fault_and_lock(region, &range);
> -	} while (ret == -EBUSY);
> -
> +again:
> +	ret = hmm_range_fault_unlocked_timeout(&range, 0);
>  	if (ret)
>  		goto out;
>  
> +	mutex_lock(&region->mreg_mutex);
> +
> +	if (mmu_interval_read_retry(range.notifier, range.notifier_seq)) {
> +		mutex_unlock(&region->mreg_mutex);
> +		cond_resched();
> +		goto again;
> +	}
> +

If the calling process has realtime scheduling policy and either a)
we're uniprocessor or b) this process and the holder of
interval_sub->invalidate_seq are both pinned to the same CPU then
cond_resched() won't do anything, and this might be an infinite loop?

^ permalink raw reply

* Re: [PATCH v8 2/8] mm/hmm: add hmm_range_fault_unlocked_timeout() for mmap lock-drop support
From: Andrew Morton @ 2026-07-10 22:12 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178371879503.900500.7148019929226548795.stgit@skinsburskii>

On Fri, 10 Jul 2026 14:26:35 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:

> hmm_range_fault() requires the caller to hold the mmap read lock for the
> duration of the call. This is incompatible with mappings whose fault
> handler may release the mmap lock, notably userfaultfd-managed regions,
> where handle_mm_fault() can return VM_FAULT_RETRY or VM_FAULT_COMPLETED
> after dropping the lock. Drivers that need to populate device page tables
> for such mappings have no way to do so today.
> 
> Add hmm_range_fault_unlocked_timeout() for callers that do not need to hold
> mmap_lock across any work outside the HMM fault itself. The helper takes
> mmap_read_lock_killable() internally, calls the common HMM fault
> implementation, and releases the lock before returning if it is still held.
> The timeout is specified in jiffies; passing 0 retries indefinitely, while
> a non-zero timeout makes the helper return -EBUSY when the retry budget
> expires.
> 
> When handle_mm_fault() drops mmap_lock, or when the range is invalidated,
> hmm_range_fault_unlocked_timeout() refreshes range->notifier_seq and
> retries the walk internally. If the lock was dropped, the retry deadline is
> also restarted because a lock-dropping fault handler made progress.
> Ordinary -EBUSY retries keep the existing deadline, preserving the caller's
> timeout policy for repeated mmu-notifier invalidations.
> 
> The caller only needs to perform the usual post-success
> mmu_interval_read_retry() check while holding its update lock before
> consuming the pfns. If mmap_lock acquisition is interrupted or a fatal
> signal is pending during retry handling, -EINTR is returned instead.
> 
> The common implementation conditionally sets FAULT_FLAG_ALLOW_RETRY and
> FAULT_FLAG_KILLABLE only for hmm_range_fault_unlocked_timeout(). The
> existing hmm_range_fault() path still passes no locked state, does not
> allow handle_mm_fault() to drop mmap_lock, and remains a thin wrapper
> preserving the existing API contract for current callers.
> 
> The previous refactor that moved page fault handling out of the page-table
> walk callbacks is what makes this change small. Faults now run after
> walk_page_range() has unwound, with only mmap_lock held, so dropping it
> does not interact with the walker's pte spinlock or hugetlb_vma_lock.
> Hugetlb regions therefore participate in the unlocked path uniformly with
> PTE- and PMD-level mappings; no special case is required.
> 
> Documentation/mm/hmm.rst is updated with a description of the new API and
> the recommended caller pattern.
> 
> ...
>

A trivial thing:

> +int hmm_range_fault_unlocked_timeout(struct hmm_range *range,
> +				     unsigned long timeout)
> +{
> +	struct mm_struct *mm = range->notifier->mm;
> +	unsigned long deadline = 0;
> +	bool locked = false;

This could be local to the do loop and it needn't be initialized.

> +	int ret;
> +
> +	do {
> +		if (fatal_signal_pending(current))
> +			return -EINTR;
> +
> +		if (timeout) {
> +			/*
> +			 * If the previous fault dropped mmap_lock, then the fault
> +			 * handler made progress. Restart the retry timeout in that
> +			 * case, but keep the existing deadline for ordinary -EBUSY
> +			 * retries.
> +			 */
> +			if (!locked)
> +				deadline = jiffies + timeout;
> +
> +			if (time_after(jiffies, deadline))
> +				return -EBUSY;
> +		}
> +
> +		range->notifier_seq =
> +			mmu_interval_read_begin(range->notifier);
> +
> +		ret = mmap_read_lock_killable(mm);
> +		if (ret)
> +			return ret;
> +
> +		locked = true;
> +		ret = hmm_range_fault_locked(range, &locked);
> +		if (locked)
> +			mmap_read_unlock(mm);
> +	} while (ret == -EBUSY);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL(hmm_range_fault_unlocked_timeout);
> +


^ permalink raw reply

* Re: [PATCH v8 0/8] mm/hmm: Add mmap lock-drop support for userfaultfd-backed mappings
From: Andrew Morton @ 2026-07-10 22:11 UTC (permalink / raw)
  To: Stanislav Kinsburskii
  Cc: airlied, akhilesh, corbet, dakr, david, decui, haiyangz, jgg,
	kees, kys, leon, liam, lizhi.hou, ljs, longli, lyude,
	maarten.lankhorst, mamin506, mhocko, mripard, nouveau, ogabbay,
	oleg, rppt, shuah, simona, skhan, surenb, tzimmermann, vbabka,
	wei.liu, dri-devel, linux-mm, linux-doc, linux-hyperv,
	linux-kernel, linux-kselftest, linux-rdma
In-Reply-To: <178371866223.900500.12312667138651735591.stgit@skinsburskii>

On Fri, 10 Jul 2026 14:26:20 -0700 Stanislav Kinsburskii <skinsburskii@gmail.com> wrote:

> This series extends the HMM framework to support userfaultfd-backed memory
> by allowing the mmap read lock to be dropped during hmm_range_fault().

Thanks.  This seems fairly mature and mostly-reviewed so I'll give it a
spin in mm.git's mm-new branch.

Unfortunately Sashiko wasn't able to apply this or v7.  I'm not sure
what base you were using.  Hopefully there's a reason for a v9 so we
can retry this.

I have a few niggles, nothing major...

^ permalink raw reply

* Re: [PATCH] docs: custom.css: don't limit randering to old 800px monitors
From: Mauro Carvalho Chehab @ 2026-07-10 22:10 UTC (permalink / raw)
  To: Jonathan Corbet
  Cc: Linux Doc Mailing List, linux-kernel, Clinton Phillips,
	Daniel Lundberg Pedersen, Hans Verkuil, Mauro Carvalho Chehab,
	Petr Vorel, Randy Dunlap, Rito Rhymes, Shuah Khan, linux-media
In-Reply-To: <87a4ryk4fc.fsf@trenco.lwn.net>

On Fri, 10 Jul 2026 13:52:55 -0600
Jonathan Corbet <corbet@lwn.net> wrote:

> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> writes:
> 
> > On Fri, 10 Jul 2026 09:27:23 -0600
> > Jonathan Corbet <corbet@lwn.net> wrote:
> >  
> >> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> writes:
> >>   
> >> > Right now, base.css style imposes a maximum limit of 800 horizontal
> >> > pixels to be compatible with very old SVGA monitors.
> >> >
> >> > Remove such artificial limit, letting the output to be adjusted to
> >> > the browser windows size.
> >> >
> >> > Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> >> > ---
> >> >  Documentation/sphinx-static/custom.css | 2 ++
> >> >  1 file changed, 2 insertions(+)
> >> >
> >> > diff --git a/Documentation/sphinx-static/custom.css b/Documentation/sphinx-static/custom.css
> >> > index 5aa0a1ed9864..1055db7dc1dd 100644
> >> > --- a/Documentation/sphinx-static/custom.css
> >> > +++ b/Documentation/sphinx-static/custom.css
> >> > @@ -3,6 +3,8 @@
> >> >   * CSS tweaks for the Alabaster theme
> >> >   */
> >> >  
> >> > +div.body {  max-width: none; }
> >> > +    
> >> 
> >> 800px is clearly a dumb limit, I have no problem changing that.  Going
> >> to arbitrary width doesn't seem good for readability, though.  What do
> >> you think about, instead, setting a limit in a resolution-independent
> >> say, to (say) 60em?  
> >
> > 60em also seems too small, considering the size of tables we have on
> > media. Some tables have one column for each bit, plus one or two other
> > columns, so the table would easily have up to 34 columns. After adding
> > long fourcc codes there and V4L macro names, it can easily be very big,
> > in terms of "em" measures.  
> 
> I did say "say" :)  I don't feel the need to argue too much about the
> exact value.  I do believe, though, that excessively wide columns are
> not good human factors in general.

If one gets a big enough "em" to fit the largest tables and ascii artwork,
I'm ok using "em" but one would need to double check what's the bigger
one, which would require some time and someone would need to periodically
review it.

My feeling is that, on media, the bigger tables are the pixformat ones,
but maybe the biggest one is somewhere else.

Most (if not all) artwork fits on 80 columns, but I vaguely remember
some that were bigger (can't remember if they were changed to fit on
80 cols).

Probably a way to define a limit that covers artwork would be to run a script
to get the max column size for .rst files. Not perfect because of indentation,
on codeblocks, but it could work as a hint. Unfortunately, this won't work
for tables using flat-table (which is used on ~235 files - most on media,
but ~20 files elsewhere).

> 
> > If you don't want to let it become too big(*), then perhaps it could
> > be something like:
> >
> > 	div.body {  max-width: 80%; }  
> 
> I like that even less...I tend to use relatively narrow browser windows
> so that I can fit more of them on the screen...it shouldn't waste that
> space unnecessarily.

Agreed.

> > (*) personally, I don't see any issues on it. My monitor's ratio is
> >     32/9. Quite the opposite, with such ultra-wide screen, I don't like
> >     sites that have fixed max-width limits, as they end limiting my
> >     personal taste for no good reason.  
> 
> Well, I won't fight about it (too much :).  I definitely agree that the
> 800px value makes little sense.
> 
> jon



Thanks,
Mauro

^ permalink raw reply

* Re: [PATCH v3 08/11] vfio/pci/cxl: Add HDM + COMP_REGS regions and DVSEC clipping shim
From: Dan Williams (nvidia) @ 2026-07-10 22:09 UTC (permalink / raw)
  To: mhonap, djbw, alex, jgg, jic23, dave.jiang, ankita,
	alejandro.lucero-palau, alison.schofield, dave, dmatlack, gourry,
	ira.weiny
  Cc: cjia, kjaju, vsethi, zhiw, mhonap, kvm, linux-cxl, linux-doc,
	linux-kernel, linux-kselftest
In-Reply-To: <20260625165407.1769572-9-mhonap@nvidia.com>

mhonap@ wrote:
> From: Manish Honap <mhonap@nvidia.com>
> 
> Complete the vfio-pci-core integration of CXL Type-2 device
> passthrough by exposing two VFIO regions to userspace, wiring DVSEC
> config-space accesses through cxl-core's register-virtualization
> helpers, and reserving the CXL component register block from BAR
> mmap and BAR resource claim.
> 
> HDM region (VFIO_REGION_SUBTYPE_CXL):
>   - mmappable view of the device's firmware-committed HPA range
>   - mmap fault handler calls vmf_insert_pfn() from the physical HPA
>     so the guest gets the same backing memory the host sees
>   - pread/pwrite go through the memremap_wb() kva captured at
>     bind time by vfio_cxl_map_hdm()
> 
> COMP_REGS region (VFIO_REGION_SUBTYPE_CXL_COMP_REGS):
>   - pread/pwrite only, dword-aligned (-EINVAL on misalignment)
>   - thin transport: each dword dispatches by offset to
>     cxl_passthrough_cm_rw() (CM cap-array snapshot) or
>     cxl_passthrough_hdm_rw() (HDM Decoder block).  No shadow buffer
>     on the vfio side; all per-field semantics live in cxl-core.
> 
> DVSEC config-space access:
>   - vfio_pci_cxl_config_boundary() clips a chunk at the CXL Device
>     DVSEC body edge in vfio_pci_config_rw_single() so the generic
>     perm-bits path handles the DVSEC header bytes and the CXL hook
>     handles the body bytes.  The clipping shim is used instead of
>     re-pointing the ecap_perms[] readfn/writefn (which would mutate
>     a module-init static and race across multiple CXL devices).
>   - vfio_pci_cxl_config_rw() forwards clipped accesses to
>     cxl_passthrough_dvsec_rw(); cxl-core enforces the per-field
>     write semantics (LOCK/RWO, CONTROL/RWL, STATUS/RW1C,
>     RANGE1/HwInit, RANGE2/RsvdZ).
> 
> GET_INFO / GET_REGION_INFO:
>   - VFIO_DEVICE_INFO_CAP_CXL advertises the two region indices, the
>     component BAR layout, and HOST_FIRMWARE_COMMITTED.
>   - GET_REGION_INFO on the component BAR returns a sparse-mmap cap
>     that excludes [comp_reg_offset, comp_reg_offset+comp_reg_size).
> 
> BAR resource handling:
>   - cxl-core holds request_mem_region() on the CXL component
>     register sub-range from devm_cxl_probe_mem(), so vfio_pci-core's
>     pci_request_selected_regions() on the full BAR would collide.
>     map_bars() skips the request for the component BAR (still iomaps
>     it; vfio holds the BAR via driver binding); disable() mirrors
>     the asymmetric skip.
>   - mmap of the component BAR refuses any range overlapping the CXL
>     sub-range via vfio_pci_cxl_mmap_overlaps_comp_regs().
> 
> vfio_pci_cxl_open() now registers both VFIO regions; close()
> unregisters them.  Raw BAR rw redirect into the CXL sub-range is
> intentionally not implemented: VMMs use the COMP_REGS region
> directly.

I jumped ahead in the review to see how passthrough.c was being
used.

This patch is doing a lot which raises the risk that individual
proposals within it are going to raise questions.

> Signed-off-by: Manish Honap <mhonap@nvidia.com>
> ---
>  drivers/vfio/pci/cxl/vfio_cxl_core.c | 521 ++++++++++++++++++++++++++-
>  drivers/vfio/pci/vfio_pci_config.c   |  31 ++
>  drivers/vfio/pci/vfio_pci_core.c     |  44 ++-
>  drivers/vfio/pci/vfio_pci_priv.h     |  72 ++++
>  drivers/vfio/pci/vfio_pci_rdwr.c     |  17 +
>  5 files changed, 679 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/vfio/pci/cxl/vfio_cxl_core.c b/drivers/vfio/pci/cxl/vfio_cxl_core.c
> index 42cd00bbe869..8a00b776d7c7 100644
> --- a/drivers/vfio/pci/cxl/vfio_cxl_core.c
> +++ b/drivers/vfio/pci/cxl/vfio_cxl_core.c
> @@ -123,12 +123,24 @@ static int vfio_cxl_probe_regs(struct vfio_pci_cxl_state *cxl)
>  	if (rc)
>  		return rc;
>  
> +	/*
> +	 * The CXL Component Register block is a fixed 64 KiB area (CXL r4.0
> +	 * §8.2.3).  cxl_pci_setup_regs() records the remaining BAR length
> +	 * after the regblock offset in reg_map.max_size, which is an upper
> +	 * bound, not the spec-defined size.  Bail if the BAR does not have
> +	 * room for a full component register block at the recorded offset,
> +	 * and publish the spec size so the UAPI, sparse-mmap exclusion, and
> +	 * COMP_REGS region all agree on the same window.
> +	 */
> +	if (cxlds->reg_map.max_size < CXL_COMPONENT_REG_BLOCK_SIZE)
> +		return -ENXIO;

This feels like a generic check that should not be vfio specific...

Indeed it is an existing check in cxl_decode_regblock() that already
makes this assertion.

>  	cxl->info.hdm_count               = hdm_count;
>  	cxl->info.hdm_reg_offset          = hdm_off;
>  	cxl->info.hdm_reg_size            = hdm_size;
>  	cxl->info.comp_reg_bir            = bir;
>  	cxl->info.comp_reg_offset         = bar_off;
> -	cxl->info.comp_reg_size           = cxlds->reg_map.max_size;
> +	cxl->info.comp_reg_size           = CXL_COMPONENT_REG_BLOCK_SIZE;
>  	cxl->info.host_firmware_committed = true;
>  
>  	/*
> @@ -354,16 +366,515 @@ void vfio_pci_cxl_release(struct vfio_pci_core_device *vdev)
>  	vdev->cxl = NULL;
>  }
>  
> +static int vfio_pci_cxl_register_hdm(struct vfio_pci_core_device *vdev);
> +static int vfio_pci_cxl_register_comp_regs(struct vfio_pci_core_device *vdev);
> +
>  int vfio_pci_cxl_open(struct vfio_pci_core_device *vdev)
>  {
> +	struct vfio_pci_cxl_state *cxl = vdev->cxl;
> +	int rc;
> +
> +	if (!cxl)
> +		return 0;	/* plain vfio-pci device */
> +
> +	rc = vfio_pci_cxl_register_comp_regs(vdev);
> +	if (rc) {
> +		pci_warn(vdev->pdev,
> +			 "vfio-cxl: COMP_REGS region register failed (%d)\n",
> +			 rc);
> +		return rc;
> +	}
> +
> +	rc = vfio_pci_cxl_register_hdm(vdev);
> +	if (rc) {
> +		pci_warn(vdev->pdev,
> +			 "vfio-cxl: HDM region register failed (%d)\n", rc);
> +		/*
> +		 * COMP_REGS already registered above.  vfio core does not
> +		 * call close_device() when open_device() returns an error,
> +		 * so roll back the COMP_REGS dynamic region here to avoid
> +		 * a leaked half-registered open state.
> +		 */
> +		vfio_pci_cxl_close(vdev);
> +		return rc;
> +	}
> +	return 0;
> +}
> +
> +void vfio_pci_cxl_close(struct vfio_pci_core_device *vdev)
> +{
> +	struct vfio_pci_cxl_state *cxl = vdev->cxl;
> +	unsigned int i;
> +
> +	if (!cxl)
> +		return;
> +
> +	for (i = vdev->num_regions; i > 0; i--) {
> +		struct vfio_pci_region *r = &vdev->region[i - 1];
> +
> +		if (r->data != cxl)
> +			break;
> +		if (r->ops->release)
> +			r->ops->release(vdev, r);
> +		vdev->num_regions--;
> +	}
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* HDM region: mmappable view of the device's HPA range               */
> +/* ------------------------------------------------------------------ */
> +
> +static vm_fault_t hdm_region_fault(struct vm_fault *vmf)
> +{
> +	struct vm_area_struct *vma = vmf->vma;
> +	struct vfio_pci_cxl_state *cxl = vma->vm_private_data;
> +	unsigned long off = (vmf->address - vma->vm_start) +
> +			    (vma->vm_pgoff << PAGE_SHIFT);
> +	phys_addr_t pa;
> +
> +	if (!cxl || !cxl->info.hpa_size)
> +		return VM_FAULT_SIGBUS;
> +	if (off >= cxl->info.hpa_size)
> +		return VM_FAULT_SIGBUS;
> +
> +	pa = cxl->info.hpa_base + off;
> +	return vmf_insert_pfn(vma, vmf->address, PHYS_PFN(pa));

It seems unfortunate that this reimplements vfio_pci_mmap_ops without
huge mapping support. If CXL is going to be a first class citizen in
vfio then why not extend vfio_pci_core_device with the concept that
devices can have HDM ranges, or some scheme to address large mappings
from day one.

[..]
> diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
> index 05ab4ae59157..2d2dae278d1e 100644
> --- a/drivers/vfio/pci/vfio_pci_core.c
> +++ b/drivers/vfio/pci/vfio_pci_core.c
> @@ -501,6 +501,23 @@ static void vfio_pci_core_map_bars(struct vfio_pci_core_device *vdev)
>  		if (!pci_resource_len(pdev, i))
>  			continue;
>  
> +		/*
> +		 * cxl-core already holds request_mem_region() on the CXL
> +		 * component register sub-range of this BAR.  Skip the
> +		 * full-BAR request so we do not collide with that
> +		 * sub-region; vfio still owns the BAR via the driver
> +		 * binding and the iomap below succeeds without a region
> +		 * claim.
> +		 */

Rather than working around a problem in the CXL core why not fix it?
This is the second time someone has run across a conflict with
devm_cxl_iomap_block() when trying to reuse CXL code.

devm_cxl_iomap_block() was a response to folks wanting /dev/mem access
to CXL BAR space. When VFIO owns the whole BAR it should be blocking
/dev/mem access to enforce going through VFIO.

I think this looks like a flag on cxl_register_map that only cxl_pci
sets to indicate the legacy special mode of doing piecemeal resource
requests of just the component registers. External CXL drivers likely
just want to own the whole BAR and not workaround an internal detail
like this.

^ permalink raw reply

* Re: [PATCH v3 06/11] cxl: Add register-virtualization helpers for vfio Type-2 passthrough
From: Dan Williams (nvidia) @ 2026-07-10 21:56 UTC (permalink / raw)
  To: mhonap, djbw, alex, jgg, jic23, dave.jiang, ankita,
	alejandro.lucero-palau, alison.schofield, dave, dmatlack, gourry,
	ira.weiny
  Cc: cjia, kjaju, vsethi, zhiw, mhonap, kvm, linux-cxl, linux-doc,
	linux-kernel, linux-kselftest
In-Reply-To: <20260625165407.1769572-7-mhonap@nvidia.com>

mhonap@ wrote:
> From: Manish Honap <mhonap@nvidia.com>
> 
> vfio-pci needs the CXL Device DVSEC body, the HDM Decoder Capability
> block, and the CXL.cache/mem cap-array prefix to be virtualized
> toward a KVM guest in a CXL-spec-compliant way.
> 
> Introduce a narrow helper API owned by cxl-core:
> 
>   struct cxl_passthrough *
>   devm_cxl_passthrough_create(struct device *dev,
>                               struct cxl_dev_state *cxlds);
> 
>   int cxl_passthrough_dvsec_rw(struct cxl_passthrough *p, u32 off,
>                                u32 *val, size_t sz, bool write);
>   int cxl_passthrough_hdm_rw(struct cxl_passthrough *p, u32 off,
>                              u32 *val, bool write);
>   int cxl_passthrough_cm_rw(struct cxl_passthrough *p, u32 off,
>                             u32 *val, bool write);
> 
> Each helper takes a per-device mutex covering the DVSEC + HDM shadows
> (the CM cap-array snapshot is immutable after create) and dispatches
> by offset to a hand-written write handler against CXL r4.0 §8.1.3
> (DVSEC: LOCK is RWO, CONTROL/CONTROL2 are RWL gated on CONFIG_LOCK,
> STATUS/STATUS2 are RW1C, RANGE1 is HwInit, RANGE2 is RsvdZ) and
> §8.2.4.20 (HDM: GLOBAL_CTRL RW, decoder CTRL implements
> COMMIT/COMMITTED, decoder BASE/SIZE RWL gated on COMMITTED or
> LOCK_ON_COMMIT, cap header HwInit).
> 
> Writes to the CM cap-array are silently discarded because the
> cap-array headers are RO per CXL r4.0 §8.2.4; the write parameter is
> kept on the rw API to make the drop policy explicit at the call site.
> 
> The shadows are snapshotted at create time: the DVSEC body from PCI
> config space dword-at-a-time, the CM cap-array and HDM block from
> the cxl-core MMIO mapping at cxlds->reg_map.base.  This preserves
> firmware-committed values so the guest reads what the host BIOS
> committed, while writes update the shadow per the per-field write
> semantics above.
> 
> The file is gated by the hidden Kconfig CXL_VFIO_PASSTHROUGH so the
> passthrough code stays out of cxl_core when no vfio consumer is configured.
> 
> Scope: firmware-committed, single-decoder, no-interleave Type-2
> passthrough.  Multi-decoder, interleave, and hotplug are
> out-of-scope and rejected at create time (-EOPNOTSUPP for
> hdm_count != 1).

Hi Manish, many style comments below, but one hard concern to highlight
is that some component capability registers require 64-bit access.

> 
> Signed-off-by: Manish Honap <mhonap@nvidia.com>
> ---
>  drivers/cxl/Kconfig            |   7 +
>  drivers/cxl/core/Makefile      |   1 +
>  drivers/cxl/core/passthrough.c | 590 +++++++++++++++++++++++++++++++++

Likely this should be called vfio.c because nothing in this file is
relevant to the rest of the CXL core. However, it is not even clear
there is value in it living in drivers/cxl/.

A lot of this code is a VFIO backend implemnentation detail, not
anything that CXL core needs. It also seems to have little if any
dependency on any core symbols. Does it really belong in
drivers/cxl/core/?

>  include/cxl/passthrough.h      | 121 +++++++
>  4 files changed, 719 insertions(+)
>  create mode 100644 drivers/cxl/core/passthrough.c
>  create mode 100644 include/cxl/passthrough.h
> 
> diff --git a/drivers/cxl/Kconfig b/drivers/cxl/Kconfig
> index 80aeb0d556bd..7c874d486a9c 100644
> --- a/drivers/cxl/Kconfig
> +++ b/drivers/cxl/Kconfig
> @@ -19,6 +19,13 @@ menuconfig CXL_BUS
>  
>  if CXL_BUS
>  
> +config CXL_VFIO_PASSTHROUGH
> +	bool
> +	# Hidden symbol selected by VFIO_PCI_CXL to pull
> +	# drivers/cxl/core/passthrough.c into cxl_core when a vfio
> +	# Type-2 passthrough consumer is configured.  Keep silent: no
> +	# help text, no default, no user-visible prompt.

No need for a paragraph of documentation to explain why this prompt has
no documention. If probably just be called CXL_VFIO.

> +
>  config CXL_PCI
>  	tristate "PCI manageability"
>  	default CXL_BUS
> diff --git a/drivers/cxl/core/Makefile b/drivers/cxl/core/Makefile
> index ce7213818d3c..0cc80bd35a88 100644
> --- a/drivers/cxl/core/Makefile
> +++ b/drivers/cxl/core/Makefile
> @@ -22,3 +22,4 @@ cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o
>  cxl_core-$(CONFIG_CXL_RAS) += ras.o
>  cxl_core-$(CONFIG_CXL_RAS) += ras_rch.o
>  cxl_core-$(CONFIG_CXL_ATL) += atl.o
> +cxl_core-$(CONFIG_CXL_VFIO_PASSTHROUGH) += passthrough.o
> diff --git a/drivers/cxl/core/passthrough.c b/drivers/cxl/core/passthrough.c
> new file mode 100644
> index 000000000000..b89829586024
> --- /dev/null
> +++ b/drivers/cxl/core/passthrough.c
> @@ -0,0 +1,590 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright(c) 2026 NVIDIA Corporation. All rights reserved.
> + *
> + * vfio-pci Type-2 device passthrough — CXL register virtualization.
> + *
> + * Owns the CXL spec-defined virtualization semantics for the
> + *   - CXL Device DVSEC capability body  (CXL r4.0 §8.1.3)
> + *   - HDM Decoder Capability block      (CXL r4.0 §8.2.4.20)
> + *   - CXL.cache/mem (CM) cap-array      (CXL r4.0 §8.2.4)
> + *
> + * vfio-pci is the only caller.  This file is NOT a generic emulation
> + * framework: every register the guest may touch has a hand-written
> + * write handler against the spec.  Reads serve from a shadow
> + * snapshotted at create time; writes update the shadow per the spec
> + * attribute mode for that field.
> + *
> + * Scope: firmware-committed, single-decoder, no-interleave Type-2
> + * passthrough.  Multi-decoder, interleave, and hotplug are
> + * out-of-scope and rejected at create time.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/device.h>
> +#include <linux/export.h>
> +#include <linux/io.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pci_ids.h>
> +#include <linux/pci_regs.h>
> +#include <linux/slab.h>
> +#include <linux/types.h>
> +#include <linux/unaligned.h>
> +
> +#include <uapi/cxl/cxl_regs.h>
> +
> +#include <cxlpci.h>
> +#include <cxlmem.h>
> +#include <cxl/cxl.h>
> +#include <cxl/passthrough.h>
> +
> +#include "core.h"
> +
> +/* DVSEC CXL Device body offsets — relative to DVSEC capability start.
> + * Body begins at PCI_DVSEC_CXL_CAP (0x0a); preceding bytes are the PCI
> + * ext-cap header and DVSEC headers handled by the generic vfio
> + * perm-bits path.
> + */
> +#define DVSEC_OFF_CAPABILITY		PCI_DVSEC_CXL_CAP	/* 0x0a, u16 */
> +#define DVSEC_OFF_CONTROL		PCI_DVSEC_CXL_CTRL	/* 0x0c, u16 */
> +#define DVSEC_OFF_STATUS		0x0e			/* u16 */
> +#define DVSEC_OFF_CONTROL2		0x10			/* u16 */
> +#define DVSEC_OFF_STATUS2		0x12			/* u16 */
> +#define DVSEC_OFF_LOCK			0x14			/* u16 */
> +#define DVSEC_OFF_RANGE1_SIZE_HI	0x18			/* u32 */
> +#define DVSEC_OFF_RANGE1_SIZE_LO	0x1c
> +#define DVSEC_OFF_RANGE1_BASE_HI	0x20
> +#define DVSEC_OFF_RANGE1_BASE_LO	0x24
> +#define DVSEC_OFF_RANGE2_SIZE_HI	0x28
> +#define DVSEC_OFF_RANGE2_SIZE_LO	0x2c
> +#define DVSEC_OFF_RANGE2_BASE_HI	0x30
> +#define DVSEC_OFF_RANGE2_BASE_LO	0x34
> +#define DVSEC_BODY_END			0x38

Please do not duplicate the existing definitions in
include/uapi/linux/pci_regs.h.

> +
> +#define DVSEC_LOCK_CONFIG_LOCK		BIT(0)

> +
> +/* HDM Decoder Capability block offsets — relative to HDM block base.
> + * Decoder N register set starts at 0x10 + N * 0x20.
> + */
> +#define HDM_OFF_CAP_HEADER		0x00
> +#define HDM_OFF_GLOBAL_CTRL		0x04
> +#define HDM_DEC_BASE			0x10
> +#define HDM_DEC_STRIDE			0x20
> +#define HDM_DEC_OFF_BASE_LO(n)		(HDM_DEC_BASE + (n) * HDM_DEC_STRIDE + 0x00)
> +#define HDM_DEC_OFF_BASE_HI(n)		(HDM_DEC_BASE + (n) * HDM_DEC_STRIDE + 0x04)
> +#define HDM_DEC_OFF_SIZE_LO(n)		(HDM_DEC_BASE + (n) * HDM_DEC_STRIDE + 0x08)
> +#define HDM_DEC_OFF_SIZE_HI(n)		(HDM_DEC_BASE + (n) * HDM_DEC_STRIDE + 0x0c)
> +#define HDM_DEC_OFF_CTRL(n)		(HDM_DEC_BASE + (n) * HDM_DEC_STRIDE + 0x10)
> +
> +/* HDM Decoder CTRL bits per CXL r4.0 §8.2.4.20.5. */
> +#define HDM_CTRL_LOCK_ON_COMMIT		BIT(8)
> +#define HDM_CTRL_COMMIT			BIT(9)
> +#define HDM_CTRL_COMMITTED		BIT(10)
> +#define HDM_CTRL_ERR_NOT_COMMITTED	BIT(11)

Please do not duplicate the definitions in drivers/cxl/cxl.h?

If you move this file to drivers/vfio/ then also move the definitions to
include/cxl/.

> +struct cxl_passthrough {

This structure should be called cxl_vfio so that any grep for "vfio" in
drivers/cxl/ brings up this code. Otherwise "passthrough" is not a term
I would expect someone to understand is immediately vfio related. Even
drivers/vfio/ barely uses the term.

> +	struct cxl_dev_state *cxlds;
> +
> +	/* DVSEC body shadow.  Byte-indexed by (off - PCI_DVSEC_CXL_CAP).
> +	 * Allocated rounded up to a dword so dword reads at the tail
> +	 * never overrun.
> +	 */
> +	u8 *dvsec_shadow;
> +	u16 dvsec_size;			/* full DVSEC cap length, incl. headers */
> +	bool dvsec_config_locked;
> +
> +	/* HDM block shadow.  Byte-indexed; size = hdm_reg_size. */
> +	u8 *hdm_shadow;
> +	resource_size_t hdm_reg_size;
> +
> +	/* CM cap-array snapshot.  Dword-indexed by (off / 4) where off
> +	 * is the byte offset from CXL_CM_OFFSET.  Read-only after create.
> +	 */
> +	__le32 *cm_snapshot;
> +	size_t cm_snapshot_dwords;
> +
> +	/* Covers dvsec_shadow + dvsec_config_locked + hdm_shadow.
> +	 * cm_snapshot is immutable after create; no lock needed.  Leaf-
> +	 * level: no entry point holding this mutex calls into cxl-bus or
> +	 * vfio.
> +	 */
> +	struct mutex lock;
> +};
> +
> +/* ------------------------------------------------------------------ */
> +/* Snapshot helpers                                                    */
> +/* ------------------------------------------------------------------ */
> +
> +/* Read the DVSEC body bytes [PCI_DVSEC_CXL_CAP, dvsec_size) from PCI
> + * config space into the shadow.
> + *
> + * The body starts at PCI_DVSEC_CXL_CAP (0x0a), which is word-aligned but
> + * NOT dword-aligned, and CXL r4.0 §8.1.3 places six 16-bit descriptors
> + * (CAPABILITY through LOCK) at offsets 0x0a..0x14 before any 32-bit
> + * field.  Strict-alignment PCIe host bridges (e.g. ARM64 ECAM) reject
> + * misaligned dword config accesses with PCIBIOS_BAD_REGISTER_NUMBER;
> + * snapshot at the natural granularity of the body's 16-bit descriptors
> + * (2-byte stride) so every offset in the range is naturally aligned.
> + */
> +static int snapshot_dvsec_body(struct cxl_passthrough *p)
> +{
> +	struct pci_dev *pdev = to_pci_dev(p->cxlds->dev);
> +	u16 dvsec = p->cxlds->cxl_dvsec;
> +	u16 off;
> +	u16 word;
> +	int rc;
> +
> +	for (off = PCI_DVSEC_CXL_CAP; off < p->dvsec_size; off += 2) {
> +		rc = pci_read_config_word(pdev, dvsec + off, &word);
> +		if (rc)
> +			return -EIO;
> +		put_unaligned_le16(word, p->dvsec_shadow +
> +				   (off - PCI_DVSEC_CXL_CAP));
> +	}
> +	return 0;
> +}
> +
> +/* Read the CM cap-array prefix [CXL_CM_OFFSET, hdm_reg_offset) from
> + * MMIO into cm_snapshot, and the HDM block [hdm_reg_offset,
> + * hdm_reg_offset + hdm_reg_size) into hdm_shadow.
> + *
> + * @base is a short-lived kva for the component register block,
> + * established by the caller via ioremap() against cxlds->reg_map.resource.
> + * cxl_setup_regs() drops its own ioremap (clears reg_map.base) after the
> + * cap-array probe completes, so this function cannot rely on
> + * cxlds->reg_map.base being valid; the caller passes a fresh mapping
> + * here and releases it once snapshot data has been copied into the
> + * in-memory shadows.

This commentary is point in time research about how the CXL core
behaves. I do not see a reason for devm_cxl_vfio_create() to document
the semantics of cxl_setup_regs().

If you want to add documentation that explains how cxl_setup_regs()
works that belongs on cxl_setup_regs() directly.

Lastly cxl_setup_regs() is a convenience function for the cxl_pci driver
that wants to parse the component register block into per capability
sparse mappings. The only functionality this implementation wants is the
base resource address, but that might change with the next comment.

> + */
> +static void snapshot_cm_and_hdm(struct cxl_passthrough *p,
> +				void __iomem *base,
> +				resource_size_t hdm_off)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < p->cm_snapshot_dwords; i++)
> +		p->cm_snapshot[i] = cpu_to_le32(readl(base + CXL_CM_OFFSET +
> +						      i * 4));

Unfortunately there are some registers that are 64-bit only registers.

The spec says: "A 64-bit register shall be accessed as an 8-byte
quantity. Partial reads are not permitted."

That means if a device has a link capability structure, a CHMU, or a
CPMU then this implementation will violate that requirement. Maybe the
implemetnation indeed needs to limit the component register blocks that
are supported.

> +
> +	for (i = 0; i < p->hdm_reg_size / 4; i++)
> +		put_unaligned_le32(readl(base + hdm_off + i * 4),
> +				   p->hdm_shadow + i * 4);
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* devres                                                              */
> +/* ------------------------------------------------------------------ */
> +
> +static void cxl_passthrough_release(struct device *dev, void *res)
> +{
> +	struct cxl_passthrough *p = *(struct cxl_passthrough **)res;
> +
> +	kfree(p->dvsec_shadow);
> +	kfree(p->hdm_shadow);
> +	kfree(p->cm_snapshot);
> +	mutex_destroy(&p->lock);
> +	kfree(p);
> +}
> +
> +struct cxl_passthrough *
> +devm_cxl_passthrough_create(struct device *dev, struct cxl_dev_state *cxlds)
> +{
> +	struct cxl_passthrough **dres;
> +	struct cxl_passthrough *p;
> +	struct pci_dev *pdev;
> +	resource_size_t hdm_off, hdm_size;
> +	size_t dvsec_shadow_size;
> +	u8 hdm_count;
> +	u32 hdr;
> +	int rc;
> +
> +	/*
> +	 * cxl_setup_regs() releases its short-lived ioremap before returning,
> +	 * so reg_map.base is NULL by the time we run.  Validate the persistent
> +	 * fields (resource address and size) instead; the local ioremap
> +	 * established further below covers the snapshot reads.
> +	 */

This is the cxl_setup_regs() documentation again.


> +	if (!dev || !cxlds || !cxlds->dev || !cxlds->cxl_dvsec ||
> +	    !cxlds->reg_map.resource || !cxlds->reg_map.max_size)
> +		return ERR_PTR(-EINVAL);
> +
> +	pdev = to_pci_dev(cxlds->dev);
> +
> +	rc = cxl_get_hdm_info(cxlds, &hdm_count, &hdm_off, &hdm_size);
> +	if (rc)
> +		return ERR_PTR(rc);
> +	if (hdm_count != 1 || !hdm_size || hdm_off <= CXL_CM_OFFSET ||
> +	    !IS_ALIGNED(hdm_size, 4))
> +		return ERR_PTR(-EOPNOTSUPP);
> +
> +	p = kzalloc_obj(*p, GFP_KERNEL);
> +	if (!p)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mutex_init(&p->lock);
> +	p->cxlds = cxlds;
> +	p->hdm_reg_size = hdm_size;
> +
> +	/* DVSEC body length from PCI ext-cap header. */
> +	rc = pci_read_config_dword(pdev, cxlds->cxl_dvsec + PCI_DVSEC_HEADER1,
> +				   &hdr);
> +	if (rc) {
> +		rc = -EIO;
> +		goto err;
> +	}
> +	p->dvsec_size = PCI_DVSEC_HEADER1_LEN(hdr);
> +	if (p->dvsec_size < DVSEC_BODY_END) {
> +		rc = -EINVAL;
> +		goto err;
> +	}
> +
> +	dvsec_shadow_size = round_up(p->dvsec_size - PCI_DVSEC_CXL_CAP, 4);
> +	p->dvsec_shadow = kzalloc(dvsec_shadow_size, GFP_KERNEL);
> +	if (!p->dvsec_shadow) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	p->cm_snapshot_dwords = (hdm_off - CXL_CM_OFFSET) / 4;
> +	p->cm_snapshot = kcalloc(p->cm_snapshot_dwords, sizeof(__le32),
> +				 GFP_KERNEL);
> +	if (!p->cm_snapshot) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	p->hdm_shadow = kzalloc(hdm_size, GFP_KERNEL);
> +	if (!p->hdm_shadow) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +
> +	rc = snapshot_dvsec_body(p);
> +	if (rc)
> +		goto err;
> +
> +	{
> +		void __iomem *base;

Just declare this at the top and skip the new scope, or just make this a
helper function.

> +
> +		/*
> +		 * Bind-time-only ioremap.  cxl_setup_regs() has already
> +		 * released the cxl-core ioremap (see comment on the entry
> +		 * gate).  Take a fresh, short-lived mapping for the
> +		 * snapshot, then release it; all subsequent reads serve
> +		 * from the in-memory shadows.
> +		 */

More redundant explanation of the cxl_setup_pci_regs() semantics.

> +		base = ioremap(cxlds->reg_map.resource,
> +			       cxlds->reg_map.max_size);
> +		if (!base) {
> +			rc = -ENOMEM;
> +			goto err;
> +		}
> +		snapshot_cm_and_hdm(p, base, hdm_off);
> +		iounmap(base);
> +	}
> +
> +	dres = devres_alloc(cxl_passthrough_release, sizeof(*dres),
> +			    GFP_KERNEL);
> +	if (!dres) {
> +		rc = -ENOMEM;
> +		goto err;
> +	}
> +	*dres = p;
> +	devres_add(dev, dres);

This is just:

rc = devm_add_action_or_reset(dev, cxl_passthrough_release, p);
if (rc)
	return ERR_PTR(rc);

> +	return p;
> +
> +err:
> +	kfree(p->dvsec_shadow);
> +	kfree(p->cm_snapshot);
> +	kfree(p->hdm_shadow);
> +	mutex_destroy(&p->lock);
> +	kfree(p);
> +	return ERR_PTR(rc);
> +}
> +EXPORT_SYMBOL_NS_GPL(devm_cxl_passthrough_create, "CXL");
> +
> +/* ------------------------------------------------------------------ */
> +/* DVSEC write semantics                                               */
> +/* ------------------------------------------------------------------ */
> +
> +static u16 dvsec_shadow_get_u16(struct cxl_passthrough *p, u16 off)
> +{
> +	return get_unaligned_le16(p->dvsec_shadow + (off - PCI_DVSEC_CXL_CAP));
> +}
> +
> +static void dvsec_shadow_set_u16(struct cxl_passthrough *p, u16 off, u16 val)
> +{
> +	put_unaligned_le16(val, p->dvsec_shadow + (off - PCI_DVSEC_CXL_CAP));
> +}
> +
> +/* Apply a write to a single DVSEC field at @off, with the field's
> + * native width (2 for descriptors, 4 for RANGE entries).  @width is
> + * the field's spec width; @new is the merged value to apply.  Caller
> + * holds p->lock.
> + */
> +static void dvsec_apply_write(struct cxl_passthrough *p, u16 off, size_t width,
> +			      u32 new)
> +{
> +	u16 cur16;
> +
> +	switch (off) {
> +	case DVSEC_OFF_CAPABILITY:
> +		/* HwInit — drop. */
> +		return;
> +	case DVSEC_OFF_CONTROL:
> +	case DVSEC_OFF_CONTROL2:
> +		/* RWL — gated on CONFIG_LOCK. */
> +		if (p->dvsec_config_locked)
> +			return;
> +		dvsec_shadow_set_u16(p, off, (u16)new);
> +		return;
> +	case DVSEC_OFF_STATUS:
> +	case DVSEC_OFF_STATUS2:
> +		/* RW1C — clear bits where the guest wrote 1. */
> +		cur16 = dvsec_shadow_get_u16(p, off);
> +		dvsec_shadow_set_u16(p, off, cur16 & ~(u16)new);
> +		return;
> +	case DVSEC_OFF_LOCK:
> +		/* RWO — first 1-write latches CONFIG_LOCK; subsequent
> +		 * writes are ignored.
> +		 */
> +		cur16 = dvsec_shadow_get_u16(p, off);
> +		if (cur16 & DVSEC_LOCK_CONFIG_LOCK)
> +			return;
> +		if (new & DVSEC_LOCK_CONFIG_LOCK) {
> +			dvsec_shadow_set_u16(p, off,
> +					     cur16 | DVSEC_LOCK_CONFIG_LOCK);
> +			p->dvsec_config_locked = true;
> +		}
> +		return;
> +	case DVSEC_OFF_RANGE1_SIZE_HI:
> +	case DVSEC_OFF_RANGE1_SIZE_LO:
> +	case DVSEC_OFF_RANGE1_BASE_HI:
> +	case DVSEC_OFF_RANGE1_BASE_LO:
> +		/* HwInit — drop. */
> +		return;
> +	case DVSEC_OFF_RANGE2_SIZE_HI:
> +	case DVSEC_OFF_RANGE2_SIZE_LO:
> +	case DVSEC_OFF_RANGE2_BASE_HI:
> +	case DVSEC_OFF_RANGE2_BASE_LO:
> +		/* RsvdZ — drop. */
> +		return;
> +	default:
> +		/* Reserved offsets inside the modelled body: drop. */
> +		(void)width;
> +		return;
> +	}
> +}
> +
> +/* Map a byte offset @off inside the DVSEC body to the natural-width
> + * field that contains it: returns the field's base offset (16-bit
> + * aligned for descriptors, 32-bit aligned for RANGE entries) and width.
> + * Returns false if @off lies outside any modelled field.
> + */
> +static bool dvsec_field_at(u16 off, u16 *field_off, size_t *width)
> +{
> +	if (off >= DVSEC_OFF_CAPABILITY && off < DVSEC_OFF_RANGE1_SIZE_HI) {
> +		*field_off = ALIGN_DOWN(off, 2);
> +		*width = 2;
> +		return true;
> +	}
> +	if (off >= DVSEC_OFF_RANGE1_SIZE_HI && off < DVSEC_BODY_END) {
> +		*field_off = ALIGN_DOWN(off, 4);
> +		*width = 4;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +int cxl_passthrough_dvsec_rw(struct cxl_passthrough *p, u32 off, u32 *val,
> +			     size_t sz, bool write)
> +{
> +	u8 *shadow;
> +	u16 field_off;
> +	size_t field_width;
> +	u32 cur, merged;
> +	u32 sub_shift;
> +	u32 width_mask;
> +
> +	if (!p || !val)
> +		return -EINVAL;
> +	if (sz != 1 && sz != 2 && sz != 4)
> +		return -EINVAL;
> +	if (off < PCI_DVSEC_CXL_CAP || off + sz > p->dvsec_size)
> +		return -EINVAL;
> +
> +	guard(mutex)(&p->lock);
> +
> +	shadow = p->dvsec_shadow + (off - PCI_DVSEC_CXL_CAP);
> +
> +	if (!write) {
> +		switch (sz) {
> +		case 1:
> +			*val = *shadow;
> +			break;
> +		case 2:
> +			*val = get_unaligned_le16(shadow);
> +			break;
> +		case 4:
> +			*val = get_unaligned_le32(shadow);
> +			break;
> +		}
> +		return 0;
> +	}
> +
> +	if (!dvsec_field_at(off, &field_off, &field_width))
> +		return 0;	/* outside any modelled field: drop */
> +
> +	/* Read-modify-merge the field at its natural width. */
> +	if (field_width == 2)
> +		cur = dvsec_shadow_get_u16(p, field_off);
> +	else
> +		cur = get_unaligned_le32(p->dvsec_shadow +
> +					 (field_off - PCI_DVSEC_CXL_CAP));
> +
> +	width_mask = (sz == 4) ? 0xffffffff : (sz == 2 ? 0xffff : 0xff);
> +	sub_shift = (off - field_off) * 8;
> +	merged = cur & ~(width_mask << sub_shift);
> +	merged |= (*val & width_mask) << sub_shift;
> +
> +	dvsec_apply_write(p, field_off, field_width, merged);
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_passthrough_dvsec_rw, "CXL");

Hmm, no other "CXL" namespace consumer would ever care about this
mechanism.  The export goes away if this file moves drivers/vfio/. If
there is a good argument to stay in drivers/cxl/ then it should be
EXPORT_SYMBOL_FOR_MODULES.

> +
> +/* ------------------------------------------------------------------ */
> +/* HDM write semantics                                                 */
> +/* ------------------------------------------------------------------ */
> +
> +static u32 hdm_shadow_get(struct cxl_passthrough *p, u32 off)
> +{
> +	return get_unaligned_le32(p->hdm_shadow + off);
> +}
> +
> +static void hdm_shadow_set(struct cxl_passthrough *p, u32 off, u32 val)
> +{
> +	put_unaligned_le32(val, p->hdm_shadow + off);
> +}
> +
> +/* Decoder index for a per-decoder register offset. */
> +static u32 hdm_decoder_of(u32 off)
> +{
> +	return (off - HDM_DEC_BASE) / HDM_DEC_STRIDE;
> +}
> +
> +static u32 hdm_decoder_field(u32 off)
> +{
> +	return (off - HDM_DEC_BASE) % HDM_DEC_STRIDE;
> +}
> +
> +static void hdm_decoder_ctrl_write(struct cxl_passthrough *p, u32 off, u32 val)
> +{
> +	u32 cur = hdm_shadow_get(p, off);
> +	u32 next;
> +
> +	/* Once COMMITTED, only the COMMIT toggle is honoured.  Releasing
> +	 * COMMIT clears COMMITTED and Lock-on-Commit per CXL r4.0
> +	 * §8.2.4.20.5.
> +	 */
> +	if (cur & HDM_CTRL_COMMITTED) {
> +		next = (cur & ~HDM_CTRL_COMMIT) | (val & HDM_CTRL_COMMIT);
> +		if (!(val & HDM_CTRL_COMMIT)) {
> +			next &= ~HDM_CTRL_COMMITTED;
> +			next &= ~HDM_CTRL_LOCK_ON_COMMIT;

No, when the decoder is locked its commit bit becomes read-only. Only
reset can clear lock-on-commit.


> +		}
> +		hdm_shadow_set(p, off, next);
> +		return;
> +	}
> +
> +	next = val & ~(HDM_CTRL_COMMITTED | HDM_CTRL_ERR_NOT_COMMITTED);
> +	if (val & HDM_CTRL_COMMIT)
> +		next |= HDM_CTRL_COMMITTED;
> +	hdm_shadow_set(p, off, next);
> +}
> +
> +static void hdm_decoder_basesize_write(struct cxl_passthrough *p, u32 off,
> +				       u32 val)
> +{
> +	u32 n = hdm_decoder_of(off);
> +	u32 ctrl = hdm_shadow_get(p, HDM_DEC_OFF_CTRL(n));
> +
> +	/* RWL — BASE/SIZE locked when the decoder is committed or
> +	 * lock-on-commit has been latched.
> +	 */
> +	if (ctrl & (HDM_CTRL_COMMITTED | HDM_CTRL_LOCK_ON_COMMIT))
> +		return;
> +	hdm_shadow_set(p, off, val);
> +}
> +
> +int cxl_passthrough_hdm_rw(struct cxl_passthrough *p, u32 off, u32 *val,
> +			   bool write)
> +{
> +	u32 field;
> +
> +	if (!p || !val)
> +		return -EINVAL;
> +	if (!IS_ALIGNED(off, 4) || off + 4 > p->hdm_reg_size)
> +		return -EINVAL;
> +
> +	guard(mutex)(&p->lock);
> +
> +	if (!write) {
> +		*val = hdm_shadow_get(p, off);
> +		return 0;
> +	}
> +
> +	switch (off) {
> +	case HDM_OFF_CAP_HEADER:
> +		/* HwInit — drop. */
> +		return 0;
> +	case HDM_OFF_GLOBAL_CTRL:
> +		/* RW — shadow. */
> +		hdm_shadow_set(p, off, *val);
> +		return 0;
> +	}
> +
> +	if (off < HDM_DEC_BASE)
> +		return 0;	/* gap before per-decoder regs: drop */
> +
> +	field = hdm_decoder_field(off);
> +	switch (field) {
> +	case 0x00: case 0x04:	/* BASE_LO / BASE_HI */
> +	case 0x08: case 0x0c:	/* SIZE_LO / SIZE_HI */
> +		hdm_decoder_basesize_write(p, off, *val);
> +		return 0;
> +	case 0x10:		/* CTRL */
> +		hdm_decoder_ctrl_write(p, off, *val);
> +		return 0;
> +	default:
> +		/* TARGET_LIST_{LO,HI} and other per-decoder bytes are
> +		 * accepted as plain RW shadow for the firmware-committed
> +		 * scope; multi-decoder / interleave behaviour is
> +		 * out-of-scope.
> +		 */
> +		hdm_shadow_set(p, off, *val);
> +		return 0;
> +	}
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_passthrough_hdm_rw, "CXL");


> +
> +/* ------------------------------------------------------------------ */
> +/* CM cap-array snapshot                                               */
> +/* ------------------------------------------------------------------ */
> +
> +int cxl_passthrough_cm_rw(struct cxl_passthrough *p, u32 off, u32 *val,
> +			  bool write)
> +{
> +	if (!p || !val)
> +		return -EINVAL;
> +	if (!IS_ALIGNED(off, 4) || off / 4 >= p->cm_snapshot_dwords)
> +		return -EINVAL;
> +
> +	if (write)
> +		return 0;	/* cap-array headers are RO; drop. */
> +
> +	*val = le32_to_cpu(p->cm_snapshot[off / 4]);
> +	return 0;
> +}
> +EXPORT_SYMBOL_NS_GPL(cxl_passthrough_cm_rw, "CXL");
> diff --git a/include/cxl/passthrough.h b/include/cxl/passthrough.h
> new file mode 100644
> index 000000000000..43214b0d34f6
> --- /dev/null
> +++ b/include/cxl/passthrough.h

This file becomes private to drivers/vfio if it moves.

Otherwise it needs s/passthrough/vfio/ as mentioned earlier.

> @@ -0,0 +1,121 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright(c) 2026 NVIDIA Corporation. All rights reserved.
> + *
> + * CXL register virtualization helpers for vfio-pci Type-2 passthrough.
> + *
> + * See Documentation/driver-api/vfio-pci-cxl.rst for the ownership
> + * contract.  In short: cxl-core owns the per-device DVSEC body, HDM
> + * Decoder block, and CM cap-array shadows; vfio-pci is a transport
> + * that forwards guest reads and writes through the helpers below.
> + *
> + * The helpers are not a generic emulation framework.  Each register
> + * is hand-coded against CXL r4.0 §8.1.3 and §8.2.4.20.  Adding a new
> + * field is "add a case", not "add a mode".
> + */
> +#ifndef __CXL_PASSTHROUGH_H__
> +#define __CXL_PASSTHROUGH_H__
> +
> +#include <linux/types.h>
> +
> +struct cxl_dev_state;
> +struct cxl_passthrough;
> +struct device;
> +
> +/**
> + * devm_cxl_passthrough_create - snapshot a Type-2 device's DVSEC + HDM +
> + * CM cap-array shadows and return the opaque handle the rw helpers
> + * operate on.
> + *
> + * @dev: device whose devres lifetime bounds the returned handle.
> + * @cxlds: CXL device state with cxlds->cxl_dvsec populated and
> + *	   cxlds->reg_map.resource and cxlds->reg_map.max_size describing
> + *	   the component register block.  cxlds->reg_map.base is NOT
> + *	   required; cxl_pci_setup_regs() releases its short-lived
> + *	   ioremap before returning, so this helper takes a local
> + *	   bind-time ioremap against cxlds->reg_map.resource for the
> + *	   duration of the snapshot.

More out of place cxl_pci_setup_regs() documentation...

> + *
> + * On success the returned handle is bound to @dev's devres so unwind
> + * happens automatically when @dev is unbound.  The handle must not be
> + * freed by the caller.
> + *
> + * Return: a valid &struct cxl_passthrough on success, ERR_PTR(-errno)
> + * on failure.
> + */
> +struct cxl_passthrough *
> +devm_cxl_passthrough_create(struct device *dev, struct cxl_dev_state *cxlds);
> +
> +/**
> + * cxl_passthrough_dvsec_rw - read or write the CXL Device DVSEC body shadow.
> + *
> + * @p: handle from devm_cxl_passthrough_create().
> + * @off: byte offset from the start of the DVSEC capability.  Must be
> + *	 >= PCI_DVSEC_CXL_CAP and (off + sz) must lie inside the DVSEC.
> + *	 Accesses to the PCI ext-cap header bytes (off < PCI_DVSEC_CXL_CAP)
> + *	 are the caller's responsibility; they belong on the generic
> + *	 perm-bits path, not here.
> + * @val: pointer to a u32 holding the read result or the write value.
> + *	 The low @sz bytes of *val are the payload; upper bytes ignored
> + *	 for writes and zero for reads.
> + * @sz: 1, 2, or 4.  Other values return -EINVAL.
> + * @write: false for read, true for write.
> + *
> + * Reads serve from the shadow.  Writes update the shadow per the spec
> + * attribute mode for the addressed field (LOCK is RWO, CONTROL/CONTROL2
> + * are RWL gated on CONFIG_LOCK, STATUS/STATUS2 are RW1C, RANGE1/2 are
> + * HwInit, Reserved/RsvdZ silently consumed).
> + *
> + * Known limitation: a 4-byte write whose @off straddles a 16-bit DVSEC
> + * field boundary (CONTROL/STATUS at 0x0c/0x0e, CONTROL2/STATUS2 at
> + * 0x10/0x12) applies only the field containing the first byte of the
> + * access; the adjacent 16-bit field is not updated by the same write.
> + * Standard CXL register-access patterns issue separate 2-byte accesses
> + * to CONTROL, STATUS, CONTROL2 and STATUS2, so this corner case is
> + * documented rather than handled.
> + *
> + * Return: 0 on success; -EINVAL on out-of-range or bad size.
> + */
> +int cxl_passthrough_dvsec_rw(struct cxl_passthrough *p, u32 off, u32 *val,
> +			     size_t sz, bool write);
> +
> +/**
> + * cxl_passthrough_hdm_rw - read or write the HDM Decoder block shadow.
> + *
> + * @p: handle from devm_cxl_passthrough_create().
> + * @off: byte offset from the HDM block base; must be 4-byte aligned and
> + *	 (off + 4) <= hdm_reg_size.  Sub-dword access is not supported on
> + *	 HDM registers per CXL r4.0 §8.2.4.
> + * @val: pointer to a u32 holding the read result or the write value.
> + * @write: false for read, true for write.
> + *
> + * Reads serve from the shadow.  Writes implement the per-decoder
> + * COMMIT/COMMITTED handshake (CTRL) and the RWL gating on BASE/SIZE
> + * imposed by COMMITTED|LOCK_ON_COMMIT.  GLOBAL_CTRL is RW; the cap
> + * header is HwInit (writes dropped); other offsets in the per-decoder
> + * stride are RW shadow.
> + *
> + * Return: 0 on success; -EINVAL on misalignment or out-of-range.
> + */
> +int cxl_passthrough_hdm_rw(struct cxl_passthrough *p, u32 off, u32 *val,
> +			   bool write);
> +
> +/**
> + * cxl_passthrough_cm_rw - read or write the CXL.cache/mem cap-array snapshot.
> + *
> + * @p: handle from devm_cxl_passthrough_create().
> + * @off: byte offset from CXL_CM_OFFSET (the start of the CM cap-array
> + *	 header in the component register block); must be 4-byte aligned
> + *	 and (off + 4) <= cm_snapshot_size.
> + * @val: pointer to a u32 holding the read result; ignored on write.
> + * @write: false for read.  Writes to the cap-array are silently dropped
> + *	   (the array headers are RO per CXL r4.0 §8.2.4); the @write
> + *	   parameter is present only to keep the API symmetric with the
> + *	   other rw helpers and to make the drop policy explicit at the
> + *	   call site.
> + *
> + * Return: 0 on success; -EINVAL on misalignment or out-of-range.
> + */
> +int cxl_passthrough_cm_rw(struct cxl_passthrough *p, u32 off, u32 *val,
> +			  bool write);
> +
> +#endif /* __CXL_PASSTHROUGH_H__ */
> -- 
> 2.25.1



^ permalink raw reply

* [PATCH net-next v4 15/15] ixd: add devlink support
From: Tony Nguyen @ 2026-07-10 21:53 UTC (permalink / raw)
  To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
  Cc: Amritha Nambiar, larysa.zaremba, przemyslaw.kitszel,
	aleksander.lobakin, sridhar.samudrala, anjali.singhai,
	michal.swiatkowski, maciej.fijalkowski, emil.s.tantilov,
	madhu.chittim, joshua.a.hay, jacob.e.keller,
	jayaprakash.shanmugam, jiri, horms, corbet, richardcochran,
	linux-doc, Bharath R
In-Reply-To: <20260710215313.1475803-1-anthony.l.nguyen@intel.com>

From: Amritha Nambiar <amritha.nambiar@intel.com>

Enable initial support for the devlink interface with the ixd driver. The
ixd hardware is a single function PCIe device. So, the PCIe adapter gets
its own devlink instance to manage device-wide resources or configuration.

$ devlink dev show
pci/0000:83:00.6

$ devlink dev info pci/0000:83:00.6
pci/0000:83:00.6:
  driver ixd
  serial_number 00-a0-c9-ff-ff-23-45-67
  versions:
      fixed:
        device.type MEV
      running:
        virtchnl 2.0

Signed-off-by: Amritha Nambiar <amritha.nambiar@intel.com>
Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Tested-by: Bharath R <Bharath.r@intel.com>
Signed-off-by: Larysa Zaremba <larysa.zaremba@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 Documentation/networking/devlink/index.rst   |  1 +
 Documentation/networking/devlink/ixd.rst     | 30 ++++++
 drivers/net/ethernet/intel/ixd/Kconfig       |  1 +
 drivers/net/ethernet/intel/ixd/Makefile      |  1 +
 drivers/net/ethernet/intel/ixd/ixd.h         |  1 +
 drivers/net/ethernet/intel/ixd/ixd_devlink.c | 97 ++++++++++++++++++++
 drivers/net/ethernet/intel/ixd/ixd_devlink.h | 50 ++++++++++
 drivers/net/ethernet/intel/ixd/ixd_lib.c     |  3 +
 drivers/net/ethernet/intel/ixd/ixd_main.c    | 14 ++-
 9 files changed, 195 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/networking/devlink/ixd.rst
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_devlink.c
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_devlink.h

diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
index 4745148fecf4..e371be30b99e 100644
--- a/Documentation/networking/devlink/index.rst
+++ b/Documentation/networking/devlink/index.rst
@@ -88,6 +88,7 @@ parameters, info versions, and other features it supports.
    ionic
    iosm
    ixgbe
+   ixd
    kvaser_pciefd
    kvaser_usb
    mlx4
diff --git a/Documentation/networking/devlink/ixd.rst b/Documentation/networking/devlink/ixd.rst
new file mode 100644
index 000000000000..17b63c8425aa
--- /dev/null
+++ b/Documentation/networking/devlink/ixd.rst
@@ -0,0 +1,30 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===================
+ixd devlink support
+===================
+
+This document describes the devlink features implemented by the ``ixd``
+device driver.
+
+Info versions
+=============
+
+The ``ixd`` driver reports the following versions
+
+.. list-table:: devlink info versions implemented
+    :widths: 5 5 5 90
+
+    * - Name
+      - Type
+      - Example
+      - Description
+    * - ``device.type``
+      - fixed
+      - MEV
+      - The hardware type for this device
+    * - ``virtchnl``
+      - running
+      - 2.0
+      - 2-digit version number (major.minor) of the communication channel
+        (virtchnl) used by the device.
diff --git a/drivers/net/ethernet/intel/ixd/Kconfig b/drivers/net/ethernet/intel/ixd/Kconfig
index 24510c50070e..34181c59dcdc 100644
--- a/drivers/net/ethernet/intel/ixd/Kconfig
+++ b/drivers/net/ethernet/intel/ixd/Kconfig
@@ -7,6 +7,7 @@ config IXD
 	select LIBETH
 	select LIBIE_CP
 	select LIBIE_PCI
+	select NET_DEVLINK
 	help
 	  This driver supports Intel(R) Control Plane PCI Function
 	  of Intel E2100 and later IPUs and FNICs.
diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile
index 90abf231fb16..03760a2580b9 100644
--- a/drivers/net/ethernet/intel/ixd/Makefile
+++ b/drivers/net/ethernet/intel/ixd/Makefile
@@ -8,5 +8,6 @@ obj-$(CONFIG_IXD) += ixd.o
 ixd-y := ixd_main.o
 ixd-y += ixd_ctlq.o
 ixd-y += ixd_dev.o
+ixd-y += ixd_devlink.o
 ixd-y += ixd_lib.o
 ixd-y += ixd_virtchnl.o
diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h
index 8e3cd5bc5a84..343c5053045f 100644
--- a/drivers/net/ethernet/intel/ixd/ixd.h
+++ b/drivers/net/ethernet/intel/ixd/ixd.h
@@ -23,6 +23,7 @@ struct ixd_adapter {
 		struct delayed_work init_work;
 		u8 reset_retries;
 		u8 vc_retries;
+		bool success;
 	} init_task;
 	struct delayed_work mbx_task;
 	struct libie_ctlq_xn_manager *xnm;
diff --git a/drivers/net/ethernet/intel/ixd/ixd_devlink.c b/drivers/net/ethernet/intel/ixd/ixd_devlink.c
new file mode 100644
index 000000000000..23ab11226978
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_devlink.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025, Intel Corporation. */
+
+#include "ixd.h"
+#include "ixd_devlink.h"
+
+#define IXD_DEVLINK_INFO_LEN	128
+
+/**
+ * ixd_fill_dsn - Get the serial number for the ixd device
+ * @adapter: adapter to query
+ * @buf: storage buffer for the info request
+ */
+static void ixd_fill_dsn(struct ixd_adapter *adapter, char *buf)
+{
+	u8 dsn[8];
+
+	/* Copy the DSN into an array in Big Endian format */
+	put_unaligned_be64(pci_get_dsn(adapter->cp_ctx.mmio_info.pdev), dsn);
+
+	snprintf(buf, IXD_DEVLINK_INFO_LEN, "%8phD", dsn);
+}
+
+/**
+ * ixd_fill_device_name - Get the name of the underlying hardware
+ * @adapter: adapter to query
+ * @buf: storage buffer for the info request
+ * @buf_size: size of the storage buffer
+ */
+static void ixd_fill_device_name(struct ixd_adapter *adapter, char *buf,
+				 size_t buf_size)
+{
+	if (adapter->caps.device_type == cpu_to_le32(VIRTCHNL2_MEV_DEVICE))
+		snprintf(buf, buf_size, "%s", "MEV");
+	else
+		snprintf(buf, buf_size, "%s", "UNKNOWN");
+}
+
+/**
+ * ixd_devlink_info_get - .info_get devlink handler
+ * @devlink: devlink instance structure
+ * @req: the devlink info request
+ * @extack: extended netdev ack structure
+ *
+ * Callback for the devlink .info_get operation. Reports information about the
+ * device.
+ *
+ * Return: zero on success or an error code on failure.
+ */
+static int ixd_devlink_info_get(struct devlink *devlink,
+				struct devlink_info_req *req,
+				struct netlink_ext_ack *extack)
+{
+	struct ixd_adapter *adapter = devlink_priv(devlink);
+	char buf[IXD_DEVLINK_INFO_LEN];
+	int err;
+
+	ixd_fill_dsn(adapter, buf);
+	err = devlink_info_serial_number_put(req, buf);
+	if (err)
+		return err;
+
+	ixd_fill_device_name(adapter, buf, IXD_DEVLINK_INFO_LEN);
+	err = devlink_info_version_fixed_put(req, "device.type", buf);
+	if (err)
+		return err;
+
+	snprintf(buf, sizeof(buf), "%u.%u",
+		 adapter->vc_ver.major, adapter->vc_ver.minor);
+
+	return devlink_info_version_running_put(req, "virtchnl", buf);
+}
+
+static const struct devlink_ops ixd_devlink_ops = {
+	.info_get = ixd_devlink_info_get,
+};
+
+/**
+ * ixd_adapter_alloc - Allocate devlink and return adapter pointer
+ * @dev: the device to allocate for
+ *
+ * Allocate a devlink instance for this device and return the private area as
+ * the adapter structure.
+ *
+ * Return: adapter structure on success, NULL on failure
+ */
+struct ixd_adapter *ixd_adapter_alloc(struct device *dev)
+{
+	struct devlink *devlink;
+
+	devlink = devlink_alloc(&ixd_devlink_ops, sizeof(struct ixd_adapter),
+				dev);
+	if (!devlink)
+		return NULL;
+
+	return devlink_priv(devlink);
+}
diff --git a/drivers/net/ethernet/intel/ixd/ixd_devlink.h b/drivers/net/ethernet/intel/ixd/ixd_devlink.h
new file mode 100644
index 000000000000..b23a1b37aebc
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_devlink.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2025, Intel Corporation. */
+
+#ifndef _IXD_DEVLINK_H_
+#define _IXD_DEVLINK_H_
+
+#include <net/devlink.h>
+
+#include "ixd.h"
+
+struct ixd_adapter *ixd_adapter_alloc(struct device *dev);
+
+/**
+ * ixd_devlink_free - teardown the devlink
+ * @adapter: the adapter structure to free
+ *
+ */
+static inline void ixd_devlink_free(struct ixd_adapter *adapter)
+{
+	struct devlink *devlink = priv_to_devlink(adapter);
+
+	devlink_free(devlink);
+}
+
+/**
+ * ixd_devlink_unregister - Unregister devlink for this adapter.
+ * @adapter: the adapter structure to cleanup
+ *
+ * Init task must be completed or cancelled beforehand.
+ */
+static inline void ixd_devlink_unregister(struct ixd_adapter *adapter)
+{
+	if (!adapter->init_task.success)
+		return;
+
+	devlink_unregister(priv_to_devlink(adapter));
+}
+
+/**
+ * ixd_devlink_register - Register devlink interface for this adapter
+ * @adapter: pointer to ixd adapter structure to be associated with devlink
+ *
+ * Register the devlink instance associated with this adapter
+ */
+static inline void ixd_devlink_register(struct ixd_adapter *adapter)
+{
+	devlink_register(priv_to_devlink(adapter));
+}
+
+#endif /* _IXD_DEVLINK_H_ */
diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c
index ce870aba143d..6f2b39cd985c 100644
--- a/drivers/net/ethernet/intel/ixd/ixd_lib.c
+++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c
@@ -3,6 +3,7 @@
 
 #include "ixd.h"
 #include "ixd_ctlq.h"
+#include "ixd_devlink.h"
 #include "ixd_virtchnl.h"
 
 #define IXD_DFLT_MBX_Q_LEN 64
@@ -154,6 +155,8 @@ void ixd_init_task(struct work_struct *work)
 
 	if (!ixd_vc_dev_init(adapter)) {
 		adapter->init_task.vc_retries = 0;
+		adapter->init_task.success = true;
+		ixd_devlink_register(adapter);
 		return;
 	}
 
diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c
index eb8e410300c2..973bc480e25a 100644
--- a/drivers/net/ethernet/intel/ixd/ixd_main.c
+++ b/drivers/net/ethernet/intel/ixd/ixd_main.c
@@ -4,6 +4,7 @@
 #include "ixd.h"
 #include "ixd_ctlq.h"
 #include "ixd_lan_regs.h"
+#include "ixd_devlink.h"
 
 MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver");
 MODULE_IMPORT_NS("LIBIE_CP");
@@ -21,6 +22,8 @@ static void ixd_remove(struct pci_dev *pdev)
 	/* Do not mix removal with (re)initialization */
 	cancel_delayed_work_sync(&adapter->init_task.init_work);
 
+	ixd_devlink_unregister(adapter);
+
 	/* Leave the device clean on exit */
 	if (adapter->xnm)
 		libie_ctlq_xn_shutdown(adapter->xnm);
@@ -28,6 +31,7 @@ static void ixd_remove(struct pci_dev *pdev)
 	ixd_deinit_dflt_mbx(adapter);
 
 	libie_pci_unmap_all_mmio_regions(&adapter->cp_ctx.mmio_info);
+	ixd_devlink_free(adapter);
 }
 
 /**
@@ -95,7 +99,7 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 	if (WARN_ON(ent->device != IXD_DEV_ID_CPF))
 		return -EINVAL;
 
-	adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL);
+	adapter = ixd_adapter_alloc(&pdev->dev);
 	if (!adapter)
 		return -ENOMEM;
 
@@ -104,13 +108,13 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	err = libie_pci_init_dev(pdev);
 	if (err)
-		return err;
+		goto free_adapter;
 
 	pci_set_drvdata(pdev, adapter);
 
 	err = ixd_iomap_regions(adapter);
 	if (err)
-		return err;
+		goto free_adapter;
 
 	INIT_DELAYED_WORK(&adapter->init_task.init_work,
 			  ixd_init_task);
@@ -121,6 +125,10 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 			   msecs_to_jiffies(500));
 
 	return 0;
+
+free_adapter:
+	ixd_devlink_free(adapter);
+	return err;
 }
 
 static const struct pci_device_id ixd_pci_tbl[] = {
-- 
2.47.1


^ permalink raw reply related

* [PATCH net-next v4 14/15] ixd: add the core initialization
From: Tony Nguyen @ 2026-07-10 21:53 UTC (permalink / raw)
  To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
  Cc: Larysa Zaremba, anthony.l.nguyen, przemyslaw.kitszel,
	aleksander.lobakin, sridhar.samudrala, anjali.singhai,
	michal.swiatkowski, maciej.fijalkowski, emil.s.tantilov,
	madhu.chittim, joshua.a.hay, jacob.e.keller,
	jayaprakash.shanmugam, jiri, horms, corbet, richardcochran,
	linux-doc, Bharath R
In-Reply-To: <20260710215313.1475803-1-anthony.l.nguyen@intel.com>

From: Larysa Zaremba <larysa.zaremba@intel.com>

As the mailbox is setup, initialize the core. This makes use of the send
and receive mailbox message framework for virtchnl communication between
the driver and device Control Plane (CP).

To start with, driver confirms the virtchnl version with the CP. Once that
is done, it requests and gets the required capabilities and resources
needed such as max vectors, queues, vports etc.

Use a unified way of handling the virtchnl messages, where a single
function handles all related memory management and the caller only provides
the callbacks to fill the send buffer and to handle the response.

Place generic control queue message handling separately to facilitate the
addition of protocols other than virtchannel in the future.

Co-developed-by: Amritha Nambiar <amritha.nambiar@intel.com>
Signed-off-by: Amritha Nambiar <amritha.nambiar@intel.com>
Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Tested-by: Bharath R <Bharath.r@intel.com>
Signed-off-by: Larysa Zaremba <larysa.zaremba@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ixd/Makefile       |   2 +
 drivers/net/ethernet/intel/ixd/ixd.h          |  10 +
 drivers/net/ethernet/intel/ixd/ixd_ctlq.c     | 146 ++++++++++++++
 drivers/net/ethernet/intel/ixd/ixd_ctlq.h     |  34 ++++
 drivers/net/ethernet/intel/ixd/ixd_lib.c      |  30 ++-
 drivers/net/ethernet/intel/ixd/ixd_main.c     |   3 +
 drivers/net/ethernet/intel/ixd/ixd_virtchnl.c | 178 ++++++++++++++++++
 drivers/net/ethernet/intel/ixd/ixd_virtchnl.h |  12 ++
 8 files changed, 414 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_ctlq.c
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_ctlq.h
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_virtchnl.c
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_virtchnl.h

diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile
index 164b2c86952f..90abf231fb16 100644
--- a/drivers/net/ethernet/intel/ixd/Makefile
+++ b/drivers/net/ethernet/intel/ixd/Makefile
@@ -6,5 +6,7 @@
 obj-$(CONFIG_IXD) += ixd.o
 
 ixd-y := ixd_main.o
+ixd-y += ixd_ctlq.o
 ixd-y += ixd_dev.o
 ixd-y += ixd_lib.o
+ixd-y += ixd_virtchnl.o
diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h
index c86c2b05c5b4..8e3cd5bc5a84 100644
--- a/drivers/net/ethernet/intel/ixd/ixd.h
+++ b/drivers/net/ethernet/intel/ixd/ixd.h
@@ -10,19 +10,29 @@
  * struct ixd_adapter - Data structure representing a CPF
  * @cp_ctx: Control plane communication context
  * @init_task: Delayed initialization after reset
+ * @mbx_task: Control queue Rx handling
  * @xnm: virtchnl transaction manager
  * @asq: Send control queue info
  * @arq: Receive control queue info
+ * @vc_ver: Negotiated virtchnl version
+ * @caps: Negotiated virtchnl capabilities
  */
 struct ixd_adapter {
 	struct libie_ctlq_ctx cp_ctx;
 	struct {
 		struct delayed_work init_work;
 		u8 reset_retries;
+		u8 vc_retries;
 	} init_task;
+	struct delayed_work mbx_task;
 	struct libie_ctlq_xn_manager *xnm;
 	struct libie_ctlq_info *asq;
 	struct libie_ctlq_info *arq;
+	struct {
+		u32 major;
+		u32 minor;
+	} vc_ver;
+	struct virtchnl2_get_capabilities caps;
 };
 
 /**
diff --git a/drivers/net/ethernet/intel/ixd/ixd_ctlq.c b/drivers/net/ethernet/intel/ixd/ixd_ctlq.c
new file mode 100644
index 000000000000..6d658be0695d
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_ctlq.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2025 Intel Corporation */
+
+#include "ixd.h"
+#include "ixd_ctlq.h"
+#include "ixd_virtchnl.h"
+
+/**
+ * ixd_ctlq_clean_sq - Clean the send control queue after sending the message
+ * @adapter: The adapter that sent the messages
+ * @force: Clean regardless of send status
+ *
+ * Free the libie send resources after sending the message and handling
+ * the response.
+ */
+void ixd_ctlq_clean_sq(struct ixd_adapter *adapter, bool force)
+{
+	struct libie_ctlq_xn_clean_params params = {
+		.ctlq = adapter->asq,
+		.force = force,
+		.num_msgs = adapter->asq->ring_len,
+		.rel_tx_buf = kfree,
+	};
+
+	libie_ctlq_xn_send_clean(&params);
+}
+
+/**
+ * ixd_ctlq_init_sparams - Initialize control queue send parameters
+ * @adapter: The adapter with initialized mailbox
+ * @sparams: Parameters to initialize
+ * @msg_buf: DMA-mappable pointer to the message being sent
+ * @msg_size: Message size
+ */
+static void ixd_ctlq_init_sparams(struct ixd_adapter *adapter,
+				  struct libie_ctlq_xn_send_params *sparams,
+				  void *msg_buf, size_t msg_size)
+{
+	*sparams = (struct libie_ctlq_xn_send_params) {
+		.rel_tx_buf = kfree,
+		.xnm = adapter->xnm,
+		.ctlq = adapter->asq,
+		.timeout_ms = IXD_CTLQ_TIMEOUT,
+		.send_buf = (struct kvec) {
+			.iov_base = msg_buf,
+			.iov_len = msg_size,
+		},
+	};
+}
+
+/**
+ * ixd_ctlq_do_req - Perform a standard virtchnl request
+ * @adapter: The adapter with initialized mailbox
+ * @req: virtchnl request description
+ *
+ * Return: %0 if a message was sent and received a response
+ * that was successfully handled by the custom callback,
+ * negative error otherwise.
+ */
+int ixd_ctlq_do_req(struct ixd_adapter *adapter, const struct ixd_ctlq_req *req)
+{
+	u8 onstack_send_buff[LIBIE_CP_TX_COPYBREAK] __aligned_largest = {};
+	struct libie_ctlq_xn_send_params send_params = {};
+	struct kvec *recv_mem;
+	void *send_buff;
+	int err;
+
+	send_buff = libie_cp_can_send_onstack(req->send_size) ?
+		    &onstack_send_buff : kzalloc(req->send_size, GFP_KERNEL);
+	if (!send_buff)
+		return -ENOMEM;
+
+	ixd_ctlq_init_sparams(adapter, &send_params, send_buff,
+			      req->send_size);
+
+	send_params.chnl_opcode = req->opcode;
+
+	if (req->send_buff_init)
+		req->send_buff_init(adapter, send_buff, req->ctx);
+
+	ixd_ctlq_clean_sq(adapter, false);
+	err = libie_ctlq_xn_send(&send_params);
+	if (err)
+		return err;
+
+	recv_mem = &send_params.recv_mem;
+	if (req->recv_process)
+		err = req->recv_process(adapter, recv_mem->iov_base,
+					recv_mem->iov_len, req->ctx);
+
+	libie_ctlq_release_rx_buf(recv_mem);
+
+	return err;
+}
+
+/**
+ * ixd_ctlq_handle_msg - Default control queue message handler
+ * @ctx: Control plane communication context
+ * @msg: Message received
+ */
+static void ixd_ctlq_handle_msg(struct libie_ctlq_ctx *ctx,
+				struct libie_ctlq_msg *msg)
+{
+	struct ixd_adapter *adapter = pci_get_drvdata(ctx->mmio_info.pdev);
+
+	if (ixd_vc_can_handle_msg(msg))
+		ixd_vc_recv_event_msg(adapter, msg);
+	else
+		dev_dbg_ratelimited(ixd_to_dev(adapter),
+				    "Received an unsupported opcode 0x%x from the CP\n",
+				    msg->chnl_opcode);
+
+	libie_ctlq_release_rx_buf(&msg->recv_mem);
+}
+
+/**
+ * ixd_ctlq_recv_mb_msg - Receive a potential message over mailbox periodically
+ * @adapter: The adapter with initialized mailbox
+ */
+static void ixd_ctlq_recv_mb_msg(struct ixd_adapter *adapter)
+{
+	struct libie_ctlq_xn_recv_params xn_params = {
+		.xnm = adapter->xnm,
+		.ctlq = adapter->arq,
+		.ctlq_msg_handler = ixd_ctlq_handle_msg,
+		.budget = LIBIE_CTLQ_MAX_XN_ENTRIES,
+	};
+
+	libie_ctlq_xn_recv(&xn_params);
+}
+
+/**
+ * ixd_ctlq_rx_task - Periodically check for mailbox responses and events
+ * @work: work handle
+ */
+void ixd_ctlq_rx_task(struct work_struct *work)
+{
+	struct ixd_adapter *adapter;
+
+	adapter = container_of(work, struct ixd_adapter, mbx_task.work);
+
+	queue_delayed_work(system_unbound_wq, &adapter->mbx_task,
+			   msecs_to_jiffies(300));
+
+	ixd_ctlq_recv_mb_msg(adapter);
+}
diff --git a/drivers/net/ethernet/intel/ixd/ixd_ctlq.h b/drivers/net/ethernet/intel/ixd/ixd_ctlq.h
new file mode 100644
index 000000000000..8839f9f8f6d5
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_ctlq.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2025 Intel Corporation */
+
+#ifndef _IXD_CTLQ_H_
+#define _IXD_CTLQ_H_
+
+#include <linux/net/intel/virtchnl2.h>
+
+#define IXD_CTLQ_TIMEOUT 2000
+
+/**
+ * struct ixd_ctlq_req - Standard virtchnl request description
+ * @opcode: protocol opcode, only virtchnl2 is needed for now
+ * @send_size: required length of the send buffer
+ * @send_buff_init: function to initialize the allocated send buffer
+ * @recv_process: function to handle the CP response
+ * @ctx: additional context for callbacks
+ */
+struct ixd_ctlq_req {
+	enum virtchnl2_op opcode;
+	size_t send_size;
+	void (*send_buff_init)(struct ixd_adapter *adapter, void *send_buff,
+			       void *ctx);
+	int (*recv_process)(struct ixd_adapter *adapter, void *recv_buff,
+			    size_t recv_size, void *ctx);
+	void *ctx;
+};
+
+void ixd_ctlq_clean_sq(struct ixd_adapter *adapter, bool force);
+int ixd_ctlq_do_req(struct ixd_adapter *adapter,
+		    const struct ixd_ctlq_req *req);
+void ixd_ctlq_rx_task(struct work_struct *work);
+
+#endif /* _IXD_CTLQ_H_ */
diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c
index afc413d3650f..ce870aba143d 100644
--- a/drivers/net/ethernet/intel/ixd/ixd_lib.c
+++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c
@@ -2,6 +2,8 @@
 /* Copyright (C) 2025 Intel Corporation */
 
 #include "ixd.h"
+#include "ixd_ctlq.h"
+#include "ixd_virtchnl.h"
 
 #define IXD_DFLT_MBX_Q_LEN 64
 
@@ -67,6 +69,11 @@ static void ixd_adapter_fill_dflt_ctlqs(struct ixd_adapter *adapter)
  */
 void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter)
 {
+	cancel_delayed_work_sync(&adapter->mbx_task);
+
+	if (adapter->asq)
+		ixd_ctlq_clean_sq(adapter, true);
+
 	if (adapter->xnm)
 		libie_ctlq_xn_deinit(adapter->xnm, &adapter->cp_ctx);
 
@@ -108,6 +115,8 @@ int ixd_init_dflt_mbx(struct ixd_adapter *adapter)
 		return -ENOENT;
 	}
 
+	queue_delayed_work(system_unbound_wq, &adapter->mbx_task, 0);
+
 	return 0;
 }
 
@@ -136,8 +145,27 @@ void ixd_init_task(struct work_struct *work)
 
 	adapter->init_task.reset_retries = 0;
 	err = ixd_init_dflt_mbx(adapter);
-	if (err)
+	if (err) {
 		dev_err(ixd_to_dev(adapter),
 			"Failed to initialize the default mailbox: %pe\n",
 			ERR_PTR(err));
+		return;
+	}
+
+	if (!ixd_vc_dev_init(adapter)) {
+		adapter->init_task.vc_retries = 0;
+		return;
+	}
+
+	libie_ctlq_xn_shutdown(adapter->xnm);
+	ixd_trigger_reset(adapter);
+	ixd_deinit_dflt_mbx(adapter);
+	if (++adapter->init_task.vc_retries > 5) {
+		dev_err(ixd_to_dev(adapter),
+			"Failed to establish mailbox communications with the hardware\n");
+		return;
+	}
+
+	queue_delayed_work(system_unbound_wq, &adapter->init_task.init_work,
+			   msecs_to_jiffies(500));
 }
diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c
index 35d0ff5c8d98..eb8e410300c2 100644
--- a/drivers/net/ethernet/intel/ixd/ixd_main.c
+++ b/drivers/net/ethernet/intel/ixd/ixd_main.c
@@ -2,6 +2,7 @@
 /* Copyright (C) 2025 Intel Corporation */
 
 #include "ixd.h"
+#include "ixd_ctlq.h"
 #include "ixd_lan_regs.h"
 
 MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver");
@@ -19,6 +20,7 @@ static void ixd_remove(struct pci_dev *pdev)
 
 	/* Do not mix removal with (re)initialization */
 	cancel_delayed_work_sync(&adapter->init_task.init_work);
+
 	/* Leave the device clean on exit */
 	if (adapter->xnm)
 		libie_ctlq_xn_shutdown(adapter->xnm);
@@ -112,6 +114,7 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	INIT_DELAYED_WORK(&adapter->init_task.init_work,
 			  ixd_init_task);
+	INIT_DELAYED_WORK(&adapter->mbx_task, ixd_ctlq_rx_task);
 
 	ixd_trigger_reset(adapter);
 	queue_delayed_work(system_unbound_wq, &adapter->init_task.init_work,
diff --git a/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c
new file mode 100644
index 000000000000..66049d1b1d15
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2025 Intel Corporation */
+
+#include "ixd.h"
+#include "ixd_ctlq.h"
+#include "ixd_virtchnl.h"
+
+/**
+ * ixd_vc_recv_event_msg - Handle virtchnl event message
+ * @adapter: The adapter handling the message
+ * @ctlq_msg: Message received
+ */
+void ixd_vc_recv_event_msg(struct ixd_adapter *adapter,
+			   struct libie_ctlq_msg *ctlq_msg)
+{
+	int payload_size = ctlq_msg->data_len;
+	struct virtchnl2_event *v2e;
+
+	if (payload_size < sizeof(*v2e)) {
+		dev_warn_ratelimited(ixd_to_dev(adapter),
+				     "Failed to receive valid payload for event msg (op 0x%X len %u)\n",
+				     ctlq_msg->chnl_opcode,
+				     payload_size);
+		return;
+	}
+
+	v2e = (struct virtchnl2_event *)ctlq_msg->recv_mem.iov_base;
+
+	dev_dbg(ixd_to_dev(adapter), "Got event 0x%X from the CP\n",
+		le32_to_cpu(v2e->event));
+}
+
+/**
+ * ixd_vc_can_handle_msg - Decide if an event has to be handled by virtchnl code
+ * @ctlq_msg: Message received
+ *
+ * Return: %true if virtchnl code can handle the event, %false otherwise
+ */
+bool ixd_vc_can_handle_msg(struct libie_ctlq_msg *ctlq_msg)
+{
+	return ctlq_msg->chnl_opcode == VIRTCHNL2_OP_EVENT;
+}
+
+/**
+ * ixd_handle_caps - Handle VIRTCHNL2_OP_GET_CAPS response
+ * @adapter: The adapter for which the capabilities are being updated
+ * @recv_buff: Buffer containing the response
+ * @recv_size: Response buffer size
+ * @ctx: unused
+ *
+ * Return: %0 if the response format is correct and was handled as expected,
+ * negative error otherwise.
+ */
+static int ixd_handle_caps(struct ixd_adapter *adapter, void *recv_buff,
+			   size_t recv_size, void *ctx)
+{
+	if (recv_size < sizeof(adapter->caps))
+		return -EBADMSG;
+
+	adapter->caps = *(typeof(adapter->caps) *)recv_buff;
+
+	return 0;
+}
+
+/**
+ * ixd_req_vc_caps - Request and save device capability
+ * @adapter: The adapter to get the capabilities for
+ *
+ * Return: success or error if sending the get capability message fails
+ */
+static int ixd_req_vc_caps(struct ixd_adapter *adapter)
+{
+	const struct ixd_ctlq_req req = {
+		.opcode = VIRTCHNL2_OP_GET_CAPS,
+		.send_size = sizeof(struct virtchnl2_get_capabilities),
+		.ctx = NULL,
+		.send_buff_init = NULL,
+		.recv_process = ixd_handle_caps,
+	};
+
+	return ixd_ctlq_do_req(adapter, &req);
+}
+
+/**
+ * ixd_get_vc_ver - Get version info from adapter
+ *
+ * Return: filled in virtchannel2 version info, ready for sending
+ */
+static struct virtchnl2_version_info ixd_get_vc_ver(void)
+{
+	return (struct virtchnl2_version_info) {
+		.major = cpu_to_le32(VIRTCHNL2_VERSION_MAJOR_2),
+		.minor = cpu_to_le32(VIRTCHNL2_VERSION_MINOR_0),
+	};
+}
+
+static void ixd_fill_vc_ver(struct ixd_adapter *adapter, void *send_buff,
+			    void *ctx)
+{
+	*(struct virtchnl2_version_info *)send_buff = ixd_get_vc_ver();
+}
+
+/**
+ * ixd_handle_vc_ver - Handle VIRTCHNL2_OP_VERSION response
+ * @adapter: The adapter for which the version is being updated
+ * @recv_buff: Buffer containing the response
+ * @recv_size: Response buffer size
+ * @ctx: Unused
+ *
+ * Return: %0 if the response format is correct and was handled as expected,
+ * negative error otherwise.
+ */
+static int ixd_handle_vc_ver(struct ixd_adapter *adapter, void *recv_buff,
+			     size_t recv_size, void *ctx)
+{
+	struct virtchnl2_version_info need_ver = ixd_get_vc_ver();
+	struct virtchnl2_version_info *recv_ver;
+
+	if (recv_size < sizeof(need_ver))
+		return -EBADMSG;
+
+	recv_ver = recv_buff;
+	if (le32_to_cpu(need_ver.major) > le32_to_cpu(recv_ver->major))
+		return -EOPNOTSUPP;
+
+	adapter->vc_ver.major = le32_to_cpu(recv_ver->major);
+	adapter->vc_ver.minor = le32_to_cpu(recv_ver->minor);
+
+	return 0;
+}
+
+/**
+ * ixd_req_vc_version - Request and save Virtchannel2 version
+ * @adapter: The adapter to get the version for
+ *
+ * Return: success or error if sending fails or the response was not as expected
+ */
+static int ixd_req_vc_version(struct ixd_adapter *adapter)
+{
+	const struct ixd_ctlq_req req = {
+		.opcode = VIRTCHNL2_OP_VERSION,
+		.send_size = sizeof(struct virtchnl2_version_info),
+		.ctx = NULL,
+		.send_buff_init = ixd_fill_vc_ver,
+		.recv_process = ixd_handle_vc_ver,
+	};
+
+	return ixd_ctlq_do_req(adapter, &req);
+}
+
+/**
+ * ixd_vc_dev_init - virtchnl device core initialization
+ * @adapter: device information
+ *
+ * Return: %0 on success or error if any step of the initialization fails
+ */
+int ixd_vc_dev_init(struct ixd_adapter *adapter)
+{
+	int err;
+
+	err = ixd_req_vc_version(adapter);
+	if (err) {
+		dev_warn(ixd_to_dev(adapter),
+			 "Getting virtchnl version failed, error=%pe\n",
+			 ERR_PTR(err));
+		return err;
+	}
+
+	err = ixd_req_vc_caps(adapter);
+	if (err) {
+		dev_warn(ixd_to_dev(adapter),
+			 "Getting virtchnl capabilities failed, error=%pe\n",
+			 ERR_PTR(err));
+		return err;
+	}
+
+	return err;
+}
diff --git a/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h
new file mode 100644
index 000000000000..1a53da8b545c
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_virtchnl.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2025 Intel Corporation */
+
+#ifndef _IXD_VIRTCHNL_H_
+#define _IXD_VIRTCHNL_H_
+
+int ixd_vc_dev_init(struct ixd_adapter *adapter);
+bool ixd_vc_can_handle_msg(struct libie_ctlq_msg *ctlq_msg);
+void ixd_vc_recv_event_msg(struct ixd_adapter *adapter,
+			   struct libie_ctlq_msg *ctlq_msg);
+
+#endif /* _IXD_VIRTCHNL_H_ */
-- 
2.47.1


^ permalink raw reply related

* [PATCH net-next v4 13/15] ixd: add reset checks and initialize the mailbox
From: Tony Nguyen @ 2026-07-10 21:53 UTC (permalink / raw)
  To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
  Cc: Larysa Zaremba, anthony.l.nguyen, przemyslaw.kitszel,
	aleksander.lobakin, sridhar.samudrala, anjali.singhai,
	michal.swiatkowski, maciej.fijalkowski, emil.s.tantilov,
	madhu.chittim, joshua.a.hay, jacob.e.keller,
	jayaprakash.shanmugam, jiri, horms, corbet, richardcochran,
	linux-doc, Aleksandr Loktionov, Bharath R
In-Reply-To: <20260710215313.1475803-1-anthony.l.nguyen@intel.com>

From: Larysa Zaremba <larysa.zaremba@intel.com>

At the end of the probe, trigger hard reset, initialize and schedule the
after-reset task. If the reset is complete in a pre-determined time,
initialize the default mailbox, through which other resources will be
negotiated.

Co-developed-by: Amritha Nambiar <amritha.nambiar@intel.com>
Signed-off-by: Amritha Nambiar <amritha.nambiar@intel.com>
Reviewed-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Bharath R <Bharath.r@intel.com>
Signed-off-by: Larysa Zaremba <larysa.zaremba@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ixd/Kconfig        |   1 +
 drivers/net/ethernet/intel/ixd/Makefile       |   2 +
 drivers/net/ethernet/intel/ixd/ixd.h          |  28 +++-
 drivers/net/ethernet/intel/ixd/ixd_dev.c      |  89 +++++++++++
 drivers/net/ethernet/intel/ixd/ixd_lan_regs.h |  40 +++++
 drivers/net/ethernet/intel/ixd/ixd_lib.c      | 143 ++++++++++++++++++
 drivers/net/ethernet/intel/ixd/ixd_main.c     |  34 ++++-
 7 files changed, 328 insertions(+), 9 deletions(-)
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_dev.c
 create mode 100644 drivers/net/ethernet/intel/ixd/ixd_lib.c

diff --git a/drivers/net/ethernet/intel/ixd/Kconfig b/drivers/net/ethernet/intel/ixd/Kconfig
index f5594efe292c..24510c50070e 100644
--- a/drivers/net/ethernet/intel/ixd/Kconfig
+++ b/drivers/net/ethernet/intel/ixd/Kconfig
@@ -5,6 +5,7 @@ config IXD
 	tristate "Intel(R) Control Plane Function Support"
 	depends on PCI_MSI
 	select LIBETH
+	select LIBIE_CP
 	select LIBIE_PCI
 	help
 	  This driver supports Intel(R) Control Plane PCI Function
diff --git a/drivers/net/ethernet/intel/ixd/Makefile b/drivers/net/ethernet/intel/ixd/Makefile
index 3849bc240600..164b2c86952f 100644
--- a/drivers/net/ethernet/intel/ixd/Makefile
+++ b/drivers/net/ethernet/intel/ixd/Makefile
@@ -6,3 +6,5 @@
 obj-$(CONFIG_IXD) += ixd.o
 
 ixd-y := ixd_main.o
+ixd-y += ixd_dev.o
+ixd-y += ixd_lib.o
diff --git a/drivers/net/ethernet/intel/ixd/ixd.h b/drivers/net/ethernet/intel/ixd/ixd.h
index 1b918c5d31cd..c86c2b05c5b4 100644
--- a/drivers/net/ethernet/intel/ixd/ixd.h
+++ b/drivers/net/ethernet/intel/ixd/ixd.h
@@ -4,14 +4,25 @@
 #ifndef _IXD_H_
 #define _IXD_H_
 
-#include <linux/net/intel/libie/pci.h>
+#include <linux/net/intel/libie/controlq.h>
 
 /**
  * struct ixd_adapter - Data structure representing a CPF
- * @hw: Device access data
+ * @cp_ctx: Control plane communication context
+ * @init_task: Delayed initialization after reset
+ * @xnm: virtchnl transaction manager
+ * @asq: Send control queue info
+ * @arq: Receive control queue info
  */
 struct ixd_adapter {
-	struct libie_mmio_info hw;
+	struct libie_ctlq_ctx cp_ctx;
+	struct {
+		struct delayed_work init_work;
+		u8 reset_retries;
+	} init_task;
+	struct libie_ctlq_xn_manager *xnm;
+	struct libie_ctlq_info *asq;
+	struct libie_ctlq_info *arq;
 };
 
 /**
@@ -22,7 +33,16 @@ struct ixd_adapter {
  */
 static inline struct device *ixd_to_dev(struct ixd_adapter *adapter)
 {
-	return &adapter->hw.pdev->dev;
+	return &adapter->cp_ctx.mmio_info.pdev->dev;
 }
 
+void ixd_ctlq_reg_init(struct ixd_adapter *adapter,
+		       struct libie_ctlq_reg *ctlq_reg_tx,
+		       struct libie_ctlq_reg *ctlq_reg_rx);
+void ixd_trigger_reset(struct ixd_adapter *adapter);
+bool ixd_check_reset_complete(struct ixd_adapter *adapter);
+void ixd_init_task(struct work_struct *work);
+int ixd_init_dflt_mbx(struct ixd_adapter *adapter);
+void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter);
+
 #endif /* _IXD_H_ */
diff --git a/drivers/net/ethernet/intel/ixd/ixd_dev.c b/drivers/net/ethernet/intel/ixd/ixd_dev.c
new file mode 100644
index 000000000000..cdd5477cc1f4
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_dev.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2025 Intel Corporation */
+
+#include "ixd.h"
+#include "ixd_lan_regs.h"
+
+/**
+ * ixd_ctlq_reg_init - Initialize default mailbox registers
+ * @adapter: PCI device driver-specific private data
+ * @ctlq_reg_tx: Transmit queue registers info to be filled
+ * @ctlq_reg_rx: Receive queue registers info to be filled
+ */
+void ixd_ctlq_reg_init(struct ixd_adapter *adapter,
+		       struct libie_ctlq_reg *ctlq_reg_tx,
+		       struct libie_ctlq_reg *ctlq_reg_rx)
+{
+	struct libie_mmio_info *mmio_info = &adapter->cp_ctx.mmio_info;
+	*ctlq_reg_tx = (struct libie_ctlq_reg) {
+		.head = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQH),
+		.tail = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQT),
+		.len = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQLEN),
+		.addr_high = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQBAH),
+		.addr_low = libie_pci_get_mmio_addr(mmio_info, PF_FW_ATQBAL),
+		.len_mask = PF_FW_ATQLEN_ATQLEN_M,
+		.len_ena_mask = PF_FW_ATQLEN_ATQENABLE_M,
+		.head_mask = PF_FW_ATQH_ATQH_M,
+	};
+
+	*ctlq_reg_rx = (struct libie_ctlq_reg) {
+		.head = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQH),
+		.tail = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQT),
+		.len = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQLEN),
+		.addr_high = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQBAH),
+		.addr_low = libie_pci_get_mmio_addr(mmio_info, PF_FW_ARQBAL),
+		.len_mask = PF_FW_ARQLEN_ARQLEN_M,
+		.len_ena_mask = PF_FW_ARQLEN_ARQENABLE_M,
+		.head_mask = PF_FW_ARQH_ARQH_M,
+	};
+}
+
+static const struct ixd_reset_reg ixd_reset_reg = {
+	.rstat  = PFGEN_RSTAT,
+	.rstat_m = PFGEN_RSTAT_PFR_STATE_M,
+	.rstat_ok_v = 0b01,
+	.rtrigger = PFGEN_CTRL,
+	.rtrigger_m = PFGEN_CTRL_PFSWR,
+};
+
+/**
+ * ixd_trigger_reset - Trigger PFR reset
+ * @adapter: the device with mapped reset register
+ */
+void ixd_trigger_reset(struct ixd_adapter *adapter)
+{
+	void __iomem *addr;
+	u32 reg_val;
+
+	addr = libie_pci_get_mmio_addr(&adapter->cp_ctx.mmio_info,
+				       ixd_reset_reg.rtrigger);
+	reg_val = readl(addr);
+	writel(reg_val | ixd_reset_reg.rtrigger_m, addr);
+}
+
+/**
+ * ixd_check_reset_complete - Check if the PFR reset is completed
+ * @adapter: CPF being reset
+ *
+ * Return: %true if the register read indicates reset has been finished,
+ *	   %false otherwise
+ */
+bool ixd_check_reset_complete(struct ixd_adapter *adapter)
+{
+	u32 reg_val, reset_status;
+	void __iomem *addr;
+
+	addr = libie_pci_get_mmio_addr(&adapter->cp_ctx.mmio_info,
+				       ixd_reset_reg.rstat);
+	reg_val = readl(addr);
+	reset_status = reg_val & ixd_reset_reg.rstat_m;
+
+	/* 0xFFFFFFFF might be read if the other side hasn't cleared
+	 * the register for us yet.
+	 */
+	if (reg_val != GENMASK(31, 0) &&
+	    reset_status == ixd_reset_reg.rstat_ok_v)
+		return true;
+
+	return false;
+}
diff --git a/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h
index fbb88929d0de..58e58c75981b 100644
--- a/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h
+++ b/drivers/net/ethernet/intel/ixd/ixd_lan_regs.h
@@ -11,9 +11,33 @@
 #define PF_FW_MBX_REG_LEN		4096
 #define PF_FW_MBX			0x08400000
 
+#define PF_FW_ARQBAL			(PF_FW_MBX)
+#define PF_FW_ARQBAH			(PF_FW_MBX + 0x4)
+#define PF_FW_ARQLEN			(PF_FW_MBX + 0x8)
+#define PF_FW_ARQLEN_ARQLEN_M		GENMASK(12, 0)
+#define PF_FW_ARQLEN_ARQENABLE_S	31
+#define PF_FW_ARQLEN_ARQENABLE_M	BIT(PF_FW_ARQLEN_ARQENABLE_S)
+#define PF_FW_ARQH_ARQH_M		GENMASK(12, 0)
+#define PF_FW_ARQH			(PF_FW_MBX + 0xC)
+#define PF_FW_ARQT			(PF_FW_MBX + 0x10)
+
+#define PF_FW_ATQBAL			(PF_FW_MBX + 0x14)
+#define PF_FW_ATQBAH			(PF_FW_MBX + 0x18)
+#define PF_FW_ATQLEN			(PF_FW_MBX + 0x1C)
+#define PF_FW_ATQLEN_ATQLEN_M		GENMASK(9, 0)
+#define PF_FW_ATQLEN_ATQENABLE_S	31
+#define PF_FW_ATQLEN_ATQENABLE_M	BIT(PF_FW_ATQLEN_ATQENABLE_S)
+#define PF_FW_ATQH_ATQH_M		GENMASK(9, 0)
+#define PF_FW_ATQH			(PF_FW_MBX + 0x20)
+#define PF_FW_ATQT			(PF_FW_MBX + 0x24)
+
 /* Reset registers */
 #define PFGEN_RTRIG_REG_LEN		2048
 #define PFGEN_RTRIG			0x08407000	/* Device resets */
+#define PFGEN_RSTAT			0x08407008	/* PFR status */
+#define PFGEN_RSTAT_PFR_STATE_M		GENMASK(1, 0)
+#define PFGEN_CTRL			0x0840700C	/* PFR trigger */
+#define PFGEN_CTRL_PFSWR		BIT(0)
 
 /**
  * struct ixd_bar_region - BAR region description
@@ -25,4 +49,20 @@ struct ixd_bar_region {
 	resource_size_t size;
 };
 
+/**
+ * struct ixd_reset_reg - structure for reset registers
+ * @rstat: offset of status in register
+ * @rstat_m: status mask
+ * @rstat_ok_v: value that indicates PFR completed status
+ * @rtrigger: offset of reset trigger in register
+ * @rtrigger_m: reset trigger mask
+ */
+struct ixd_reset_reg {
+	u32	rstat;
+	u32	rstat_m;
+	u32	rstat_ok_v;
+	u32	rtrigger;
+	u32	rtrigger_m;
+};
+
 #endif /* _IXD_LAN_REGS_H_ */
diff --git a/drivers/net/ethernet/intel/ixd/ixd_lib.c b/drivers/net/ethernet/intel/ixd/ixd_lib.c
new file mode 100644
index 000000000000..afc413d3650f
--- /dev/null
+++ b/drivers/net/ethernet/intel/ixd/ixd_lib.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2025 Intel Corporation */
+
+#include "ixd.h"
+
+#define IXD_DFLT_MBX_Q_LEN 64
+
+/**
+ * ixd_init_ctlq_create_info - Initialize control queue info for creation
+ * @info: destination
+ * @type: type of the queue to create
+ * @ctlq_reg: register assigned to the control queue
+ */
+static void ixd_init_ctlq_create_info(struct libie_ctlq_create_info *info,
+				      enum virtchnl2_queue_type type,
+				      const struct libie_ctlq_reg *ctlq_reg)
+{
+	*info = (struct libie_ctlq_create_info) {
+		.type = type,
+		.id = -1,
+		.reg = *ctlq_reg,
+		.len = IXD_DFLT_MBX_Q_LEN,
+	};
+}
+
+/**
+ * ixd_init_libie_xn_params - Initialize xn transaction manager creation info
+ * @params: destination
+ * @adapter: adapter info struct
+ * @ctlqs: list of the managed queues to create
+ * @num_queues: length of the queue list
+ */
+static void ixd_init_libie_xn_params(struct libie_ctlq_xn_init_params *params,
+				     struct ixd_adapter *adapter,
+				      struct libie_ctlq_create_info *ctlqs,
+				      uint num_queues)
+{
+	*params = (struct libie_ctlq_xn_init_params){
+		.cctlq_info = ctlqs,
+		.ctx = &adapter->cp_ctx,
+		.num_qs = num_queues,
+	};
+}
+
+/**
+ * ixd_adapter_fill_dflt_ctlqs - Find default control queues and store them
+ * @adapter: adapter info struct
+ */
+static void ixd_adapter_fill_dflt_ctlqs(struct ixd_adapter *adapter)
+{
+	guard(spinlock)(&adapter->cp_ctx.ctlqs_lock);
+	struct libie_ctlq_info *cq;
+
+	list_for_each_entry(cq, &adapter->cp_ctx.ctlqs, list) {
+		if (cq->qid != -1)
+			continue;
+		if (cq->type == LIBIE_CTLQ_TYPE_RX)
+			adapter->arq = cq;
+		else if (cq->type == LIBIE_CTLQ_TYPE_TX)
+			adapter->asq = cq;
+	}
+}
+
+/**
+ * ixd_deinit_dflt_mbx - Deinitialize default mailbox
+ * @adapter: adapter info struct
+ */
+void ixd_deinit_dflt_mbx(struct ixd_adapter *adapter)
+{
+	if (adapter->xnm)
+		libie_ctlq_xn_deinit(adapter->xnm, &adapter->cp_ctx);
+
+	adapter->arq = NULL;
+	adapter->asq = NULL;
+	adapter->xnm = NULL;
+}
+
+/**
+ * ixd_init_dflt_mbx - Setup default mailbox parameters and make request
+ * @adapter: adapter info struct
+ *
+ * Return: %0 on success, negative errno code on failure
+ */
+int ixd_init_dflt_mbx(struct ixd_adapter *adapter)
+{
+	struct libie_ctlq_create_info ctlqs_info[2];
+	struct libie_ctlq_xn_init_params xn_params;
+	struct libie_ctlq_reg ctlq_reg_tx;
+	struct libie_ctlq_reg ctlq_reg_rx;
+	int err;
+
+	ixd_ctlq_reg_init(adapter, &ctlq_reg_tx, &ctlq_reg_rx);
+	ixd_init_ctlq_create_info(&ctlqs_info[0], LIBIE_CTLQ_TYPE_TX,
+				  &ctlq_reg_tx);
+	ixd_init_ctlq_create_info(&ctlqs_info[1], LIBIE_CTLQ_TYPE_RX,
+				  &ctlq_reg_rx);
+	ixd_init_libie_xn_params(&xn_params, adapter, ctlqs_info,
+				 ARRAY_SIZE(ctlqs_info));
+	err = libie_ctlq_xn_init(&xn_params);
+	if (err)
+		return err;
+	adapter->xnm = xn_params.xnm;
+
+	ixd_adapter_fill_dflt_ctlqs(adapter);
+
+	if (!adapter->asq || !adapter->arq) {
+		ixd_deinit_dflt_mbx(adapter);
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/**
+ * ixd_init_task - Initialize after reset
+ * @work: init work struct
+ */
+void ixd_init_task(struct work_struct *work)
+{
+	struct ixd_adapter *adapter;
+	int err;
+
+	adapter = container_of(work, struct ixd_adapter,
+			       init_task.init_work.work);
+
+	if (!ixd_check_reset_complete(adapter)) {
+		if (++adapter->init_task.reset_retries < 10)
+			queue_delayed_work(system_unbound_wq,
+					   &adapter->init_task.init_work,
+					   msecs_to_jiffies(500));
+		else
+			dev_err(ixd_to_dev(adapter),
+				"Device reset failed. The driver was unable to contact the device's firmware. Check that the FW is running.\n");
+		return;
+	}
+
+	adapter->init_task.reset_retries = 0;
+	err = ixd_init_dflt_mbx(adapter);
+	if (err)
+		dev_err(ixd_to_dev(adapter),
+			"Failed to initialize the default mailbox: %pe\n",
+			ERR_PTR(err));
+}
diff --git a/drivers/net/ethernet/intel/ixd/ixd_main.c b/drivers/net/ethernet/intel/ixd/ixd_main.c
index 75ee53152e61..35d0ff5c8d98 100644
--- a/drivers/net/ethernet/intel/ixd/ixd_main.c
+++ b/drivers/net/ethernet/intel/ixd/ixd_main.c
@@ -5,6 +5,7 @@
 #include "ixd_lan_regs.h"
 
 MODULE_DESCRIPTION("Intel(R) Control Plane Function Device Driver");
+MODULE_IMPORT_NS("LIBIE_CP");
 MODULE_IMPORT_NS("LIBIE_PCI");
 MODULE_LICENSE("GPL");
 
@@ -16,7 +17,15 @@ static void ixd_remove(struct pci_dev *pdev)
 {
 	struct ixd_adapter *adapter = pci_get_drvdata(pdev);
 
-	libie_pci_unmap_all_mmio_regions(&adapter->hw);
+	/* Do not mix removal with (re)initialization */
+	cancel_delayed_work_sync(&adapter->init_task.init_work);
+	/* Leave the device clean on exit */
+	if (adapter->xnm)
+		libie_ctlq_xn_shutdown(adapter->xnm);
+	ixd_trigger_reset(adapter);
+	ixd_deinit_dflt_mbx(adapter);
+
+	libie_pci_unmap_all_mmio_regions(&adapter->cp_ctx.mmio_info);
 }
 
 /**
@@ -51,7 +60,7 @@ static int ixd_iomap_regions(struct ixd_adapter *adapter)
 	};
 
 	for (int i = 0; i < ARRAY_SIZE(regions); i++) {
-		struct libie_mmio_info *mmio_info = &adapter->hw;
+		struct libie_mmio_info *mmio_info = &adapter->cp_ctx.mmio_info;
 		bool map_ok;
 
 		map_ok = libie_pci_map_mmio_region(mmio_info,
@@ -81,11 +90,15 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 	struct ixd_adapter *adapter;
 	int err;
 
+	if (WARN_ON(ent->device != IXD_DEV_ID_CPF))
+		return -EINVAL;
+
 	adapter = devm_kzalloc(&pdev->dev, sizeof(*adapter), GFP_KERNEL);
 	if (!adapter)
 		return -ENOMEM;
-	adapter->hw.pdev = pdev;
-	INIT_LIST_HEAD(&adapter->hw.mmio_list);
+
+	adapter->cp_ctx.mmio_info.pdev = pdev;
+	INIT_LIST_HEAD(&adapter->cp_ctx.mmio_info.mmio_list);
 
 	err = libie_pci_init_dev(pdev);
 	if (err)
@@ -93,7 +106,18 @@ static int ixd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 
 	pci_set_drvdata(pdev, adapter);
 
-	return ixd_iomap_regions(adapter);
+	err = ixd_iomap_regions(adapter);
+	if (err)
+		return err;
+
+	INIT_DELAYED_WORK(&adapter->init_task.init_work,
+			  ixd_init_task);
+
+	ixd_trigger_reset(adapter);
+	queue_delayed_work(system_unbound_wq, &adapter->init_task.init_work,
+			   msecs_to_jiffies(500));
+
+	return 0;
 }
 
 static const struct pci_device_id ixd_pci_tbl[] = {
-- 
2.47.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