* Re: [PATCH v7 07/10] x86/vmscape: Use static_call() for predictor flush
From: Peter Zijlstra @ 2026-03-19 21:44 UTC (permalink / raw)
To: Pawan Gupta
Cc: x86, Nikolay Borisov, H. Peter Anvin, Josh Poimboeuf,
David Kaplan, Sean Christopherson, Borislav Petkov, Dave Hansen,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, KP Singh,
Jiri Olsa, David S. Miller, David Laight, Andy Lutomirski,
Thomas Gleixner, Ingo Molnar, David Ahern, Martin KaFai Lau,
Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend,
Stanislav Fomichev, Hao Luo, Paolo Bonzini, Jonathan Corbet,
linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf, netdev,
linux-doc
In-Reply-To: <20260319213421.br6na4dulrjm6eke@desk>
On Thu, Mar 19, 2026 at 02:34:21PM -0700, Pawan Gupta wrote:
> On Thu, Mar 19, 2026 at 09:58:02PM +0100, Peter Zijlstra wrote:
> > On Thu, Mar 19, 2026 at 08:41:54AM -0700, Pawan Gupta wrote:
> > > diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
> > > index 68e2df3e3bf5..b75eda114503 100644
> > > --- a/arch/x86/kernel/cpu/bugs.c
> > > +++ b/arch/x86/kernel/cpu/bugs.c
> > > @@ -144,6 +144,17 @@ EXPORT_SYMBOL_GPL(cpu_buf_idle_clear);
> > > */
> > > DEFINE_STATIC_KEY_FALSE(switch_mm_cond_l1d_flush);
> > >
> > > +/*
> > > + * Controls CPU Fill buffer clear before VMenter. This is a subset of
> > > + * X86_FEATURE_CLEAR_CPU_BUF, and should only be enabled when KVM-only
> > > + * mitigation is required.
> > > + */
> > > +DEFINE_STATIC_KEY_FALSE(cpu_buf_vm_clear);
> > > +EXPORT_SYMBOL_GPL(cpu_buf_vm_clear);
> > > +
> > > +DEFINE_STATIC_CALL_NULL(vmscape_predictor_flush, write_ibpb);
> > > +EXPORT_STATIC_CALL_GPL(vmscape_predictor_flush);
> >
> > Does that want to be:
> >
> > EXPORT_STATIC_CALL_TRAMP_GPL(vmscape_predictor_flush);
> >
> > The distinction being that if you only export the trampoline, modules
> > can do the static_call() thing, but cannot do static_call_update().
>
> Right, modules shouldn't be updating this static_call().
>
> One caveat of not exporting the static key is that KVM uses the key to
> determine whether the mitigation is deployed or not:
>
> vcpu_enter_guest()
> {
> ...
>
> /*
> * Mark this CPU as needing a branch predictor flush before running
> * userspace. Must be done before enabling preemption to ensure it gets
> * set for the CPU that actually ran the guest, and not the CPU that it
> * may migrate to.
> */
> if (static_call_query(vmscape_predictor_flush))
> this_cpu_write(x86_predictor_flush_exit_to_user, true);
>
> With _TRAMP, KVM complains:
>
> ERROR: modpost: "__SCK__vmscape_predictor_flush" [arch/x86/kvm/kvm.ko] undefined!
Ah, tricky. Yeah, this would need to be solved differenlty. Perhaps wrap
this in a helper and export that?
Or use the below little thing and change it to
EXPORT_STATIC_CALL_FOR_MODULES(foo, "kvm"); or whatnot.
> Probably one option is to somehow make sure that the key can be set to
> __ro_after_init? I don't see a use case for modifying the static_call() after
> boot.
So we have __ro_after_init for static_branch, but we'd not done
it for static_call yet. It shouldn't be terribly difficult, just hasn't
been done. Not sure this is the moment to do so.
---
diff --git a/include/linux/static_call.h b/include/linux/static_call.h
index 78a77a4ae0ea..b610afd1ed55 100644
--- a/include/linux/static_call.h
+++ b/include/linux/static_call.h
@@ -216,6 +216,9 @@ extern long __static_call_return0(void);
#define EXPORT_STATIC_CALL_GPL(name) \
EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name)); \
EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name))
+#define EXPORT_STATIC_CALL_FOR_MODULES(name, mods) \
+ EXPORT_SYMBOL_FOR_MODULES(STATIC_CALL_KEY(name), mods); \
+ EXPORT_SYMBOL_FOR_MODULES(STATIC_CALL_TRAMP(name), mods)
/* Leave the key unexported, so modules can't change static call targets: */
#define EXPORT_STATIC_CALL_TRAMP(name) \
@@ -276,6 +279,9 @@ extern long __static_call_return0(void);
#define EXPORT_STATIC_CALL_GPL(name) \
EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name)); \
EXPORT_SYMBOL_GPL(STATIC_CALL_TRAMP(name))
+#define EXPORT_STATIC_CALL_FOR_MODULES(name, mods) \
+ EXPORT_SYMBOL_FOR_MODULES(STATIC_CALL_KEY(name), mods); \
+ EXPORT_SYMBOL_FOR_MODULES(STATIC_CALL_TRAMP(name), mods)
/* Leave the key unexported, so modules can't change static call targets: */
#define EXPORT_STATIC_CALL_TRAMP(name) \
@@ -346,6 +352,8 @@ static inline int static_call_text_reserved(void *start, void *end)
#define EXPORT_STATIC_CALL(name) EXPORT_SYMBOL(STATIC_CALL_KEY(name))
#define EXPORT_STATIC_CALL_GPL(name) EXPORT_SYMBOL_GPL(STATIC_CALL_KEY(name))
+#define EXPORT_STATIC_CALL_FOR_MODULES(name, mods) \
+ EXPORT_SYMBOL_FOR_MODULES(STATIC_CALL_KEY(name), mods)
#endif /* CONFIG_HAVE_STATIC_CALL */
^ permalink raw reply related
* [syzbot ci] Re: Virtual Swap Space
From: syzbot ci @ 2026-03-19 21:36 UTC (permalink / raw)
To: akpm, apopple, axelrasmussen, baohua, baolin.wang, bhe, byungchul,
cgroups, chengming.zhou, chrisl, corbet, david, dev.jain, gourry,
hannes, hughd, jannh, joshua.hahnjy, kasong, kernel-team,
lance.yang, lenb, liam.howlett, linux-doc, linux-kernel, linux-mm,
linux-pm, lorenzo.stoakes, matthew.brost, mhocko, muchun.song,
npache, nphamcs, pavel, peterx, peterz, pfalcato, rafael,
rakie.kim, riel, roman.gushchin, rppt, ryan.roberts, shakeel.butt,
shikemeng, surenb, tglx, vbabka, weixugc, ying.huang
Cc: syzbot, syzkaller-bugs
In-Reply-To: <20260318222953.441758-1-nphamcs@gmail.com>
syzbot ci has tested the following series
[v4] Virtual Swap Space
https://lore.kernel.org/all/20260318222953.441758-1-nphamcs@gmail.com
* [PATCH v4 01/21] mm/swap: decouple swap cache from physical swap infrastructure
* [PATCH v4 02/21] swap: rearrange the swap header file
* [PATCH v4 03/21] mm: swap: add an abstract API for locking out swapoff
* [PATCH v4 04/21] zswap: add new helpers for zswap entry operations
* [PATCH v4 05/21] mm/swap: add a new function to check if a swap entry is in swap cached.
* [PATCH v4 06/21] mm: swap: add a separate type for physical swap slots
* [PATCH v4 07/21] mm: create scaffolds for the new virtual swap implementation
* [PATCH v4 08/21] zswap: prepare zswap for swap virtualization
* [PATCH v4 09/21] mm: swap: allocate a virtual swap slot for each swapped out page
* [PATCH v4 10/21] swap: move swap cache to virtual swap descriptor
* [PATCH v4 11/21] zswap: move zswap entry management to the virtual swap descriptor
* [PATCH v4 12/21] swap: implement the swap_cgroup API using virtual swap
* [PATCH v4 13/21] swap: manage swap entry lifecycle at the virtual swap layer
* [PATCH v4 14/21] mm: swap: decouple virtual swap slot from backing store
* [PATCH v4 15/21] zswap: do not start zswap shrinker if there is no physical swap slots
* [PATCH v4 16/21] swap: do not unnecesarily pin readahead swap entries
* [PATCH v4 17/21] swapfile: remove zeromap bitmap
* [PATCH v4 18/21] memcg: swap: only charge physical swap slots
* [PATCH v4 19/21] swap: simplify swapoff using virtual swap
* [PATCH v4 20/21] swapfile: replace the swap map with bitmaps
* [PATCH v4 21/21] vswap: batch contiguous vswap free calls
and found the following issue:
possible deadlock in vswap_iter
Full report is available here:
https://ci.syzbot.org/series/f8238a2a-370e-404d-b3f7-5945b574bd63
***
possible deadlock in vswap_iter
tree: bpf-next
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git
base: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
arch: amd64
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config: https://ci.syzbot.org/builds/cf1517a6-d391-46d8-bfbe-98e6be6b93ce/config
syz repro: https://ci.syzbot.org/findings/b4e84ae7-17d4-4bf8-9c3f-4c13b10a1e52/syz_repro
============================================
WARNING: possible recursive locking detected
syzkaller #0 Not tainted
--------------------------------------------
syz.1.18/6001 is trying to acquire lock:
ffff88811fba0018 (&cluster->lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock.h:351 [inline]
ffff88811fba0018 (&cluster->lock){+.+.}-{3:3}, at: vswap_iter+0xfa/0x1b0 mm/vswap.c:274
but task is already holding lock:
ffff88811fba0018 (&cluster->lock){+.+.}-{3:3}, at: spin_lock_irq include/linux/spinlock.h:376 [inline]
ffff88811fba0018 (&cluster->lock){+.+.}-{3:3}, at: swap_cache_lock_irq+0xe2/0x190 mm/vswap.c:1529
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0
----
lock(&cluster->lock);
lock(&cluster->lock);
*** DEADLOCK ***
May be due to missing lock nesting notation
3 locks held by syz.1.18/6001:
#0: ffff8881bb523440 (&mm->mmap_lock){++++}-{4:4}, at: mmap_read_lock include/linux/mmap_lock.h:391 [inline]
#0: ffff8881bb523440 (&mm->mmap_lock){++++}-{4:4}, at: madvise_lock+0x152/0x2e0 mm/madvise.c:1789
#1: ffff88811fba0018 (&cluster->lock){+.+.}-{3:3}, at: spin_lock_irq include/linux/spinlock.h:376 [inline]
#1: ffff88811fba0018 (&cluster->lock){+.+.}-{3:3}, at: swap_cache_lock_irq+0xe2/0x190 mm/vswap.c:1529
#2: ffffffff8e55a360 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:331 [inline]
#2: ffffffff8e55a360 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:867 [inline]
#2: ffffffff8e55a360 (rcu_read_lock){....}-{1:3}, at: vswap_cgroup_record+0x41/0x440 mm/vswap.c:1909
stack backtrace:
CPU: 0 UID: 0 PID: 6001 Comm: syz.1.18 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_deadlock_bug+0x279/0x290 kernel/locking/lockdep.c:3041
check_deadlock kernel/locking/lockdep.c:3093 [inline]
validate_chain kernel/locking/lockdep.c:3895 [inline]
__lock_acquire+0x253f/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x330 kernel/locking/lockdep.c:5868
__raw_spin_lock include/linux/spinlock_api_smp.h:133 [inline]
_raw_spin_lock+0x2e/0x40 kernel/locking/spinlock.c:154
spin_lock include/linux/spinlock.h:351 [inline]
vswap_iter+0xfa/0x1b0 mm/vswap.c:274
vswap_cgroup_record+0xeb/0x440 mm/vswap.c:1910
swap_cgroup_record+0xc5/0x130 mm/vswap.c:1933
memcg1_swapout+0x358/0x9e0 mm/memcontrol-v1.c:623
__remove_mapping+0x7d4/0xa70 mm/vmscan.c:762
shrink_folio_list+0x287c/0x5160 mm/vmscan.c:1518
reclaim_folio_list+0x100/0x400 mm/vmscan.c:2198
reclaim_pages+0x45b/0x530 mm/vmscan.c:2235
madvise_cold_or_pageout_pte_range+0x1eac/0x2220 mm/madvise.c:444
walk_pmd_range mm/pagewalk.c:130 [inline]
walk_pud_range mm/pagewalk.c:224 [inline]
walk_p4d_range mm/pagewalk.c:262 [inline]
walk_pgd_range+0x1032/0x1d30 mm/pagewalk.c:303
__walk_page_range+0x14c/0x710 mm/pagewalk.c:410
walk_page_range_vma_unsafe+0x309/0x410 mm/pagewalk.c:714
madvise_pageout_page_range mm/madvise.c:622 [inline]
madvise_pageout mm/madvise.c:647 [inline]
madvise_vma_behavior+0x2951/0x43c0 mm/madvise.c:1366
madvise_walk_vmas+0x57a/0xaf0 mm/madvise.c:1721
madvise_do_behavior+0x386/0x540 mm/madvise.c:1937
do_madvise+0x1fa/0x2e0 mm/madvise.c:2030
__do_sys_madvise mm/madvise.c:2039 [inline]
__se_sys_madvise mm/madvise.c:2037 [inline]
__x64_sys_madvise+0xa6/0xc0 mm/madvise.c:2037
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f2b3459c799
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f2b35495028 EFLAGS: 00000246 ORIG_RAX: 000000000000001c
RAX: ffffffffffffffda RBX: 00007f2b34815fa0 RCX: 00007f2b3459c799
RDX: 0000000000000015 RSI: 0000000000600000 RDI: 0000200000000000
RBP: 00007f2b34632c99 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f2b34816038 R14: 00007f2b34815fa0 R15: 00007ffcfbbae8f8
</TASK>
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
^ permalink raw reply
* Re: [PATCH v7 07/10] x86/vmscape: Use static_call() for predictor flush
From: Pawan Gupta @ 2026-03-19 21:34 UTC (permalink / raw)
To: Peter Zijlstra
Cc: x86, Nikolay Borisov, H. Peter Anvin, Josh Poimboeuf,
David Kaplan, Sean Christopherson, Borislav Petkov, Dave Hansen,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, KP Singh,
Jiri Olsa, David S. Miller, David Laight, Andy Lutomirski,
Thomas Gleixner, Ingo Molnar, David Ahern, Martin KaFai Lau,
Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend,
Stanislav Fomichev, Hao Luo, Paolo Bonzini, Jonathan Corbet,
linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf, netdev,
linux-doc
In-Reply-To: <20260319205802.GJ3738786@noisy.programming.kicks-ass.net>
On Thu, Mar 19, 2026 at 09:58:02PM +0100, Peter Zijlstra wrote:
> On Thu, Mar 19, 2026 at 08:41:54AM -0700, Pawan Gupta wrote:
> > diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
> > index 68e2df3e3bf5..b75eda114503 100644
> > --- a/arch/x86/kernel/cpu/bugs.c
> > +++ b/arch/x86/kernel/cpu/bugs.c
> > @@ -144,6 +144,17 @@ EXPORT_SYMBOL_GPL(cpu_buf_idle_clear);
> > */
> > DEFINE_STATIC_KEY_FALSE(switch_mm_cond_l1d_flush);
> >
> > +/*
> > + * Controls CPU Fill buffer clear before VMenter. This is a subset of
> > + * X86_FEATURE_CLEAR_CPU_BUF, and should only be enabled when KVM-only
> > + * mitigation is required.
> > + */
> > +DEFINE_STATIC_KEY_FALSE(cpu_buf_vm_clear);
> > +EXPORT_SYMBOL_GPL(cpu_buf_vm_clear);
> > +
> > +DEFINE_STATIC_CALL_NULL(vmscape_predictor_flush, write_ibpb);
> > +EXPORT_STATIC_CALL_GPL(vmscape_predictor_flush);
>
> Does that want to be:
>
> EXPORT_STATIC_CALL_TRAMP_GPL(vmscape_predictor_flush);
>
> The distinction being that if you only export the trampoline, modules
> can do the static_call() thing, but cannot do static_call_update().
Right, modules shouldn't be updating this static_call().
One caveat of not exporting the static key is that KVM uses the key to
determine whether the mitigation is deployed or not:
vcpu_enter_guest()
{
...
/*
* Mark this CPU as needing a branch predictor flush before running
* userspace. Must be done before enabling preemption to ensure it gets
* set for the CPU that actually ran the guest, and not the CPU that it
* may migrate to.
*/
if (static_call_query(vmscape_predictor_flush))
this_cpu_write(x86_predictor_flush_exit_to_user, true);
With _TRAMP, KVM complains:
ERROR: modpost: "__SCK__vmscape_predictor_flush" [arch/x86/kvm/kvm.ko] undefined!
Probably one option is to somehow make sure that the key can be set to
__ro_after_init? I don't see a use case for modifying the static_call() after
boot.
^ permalink raw reply
* Re: [PATCH v3 3/3] ima: Add support for staging measurements for deletion
From: steven chen @ 2026-03-19 21:31 UTC (permalink / raw)
To: Mimi Zohar, Roberto Sassu, corbet, skhan, dmitry.kasatkin,
eric.snowberg, paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu, steven chen
In-Reply-To: <587e11bf4d29552bbbfc029f716146e8ebfca1eb.camel@linux.ibm.com>
On 3/17/2026 2:03 PM, Mimi Zohar wrote:
> Hi Roberto,
>
> On Wed, 2026-03-11 at 18:19 +0100, Roberto Sassu wrote:
>> From: Roberto Sassu <roberto.sassu@huawei.com>
>>
>> Introduce the ability of staging the IMA measurement list for deletion.
>> Staging means moving the current content of the measurement list to a
>> separate location, and allowing users to read and delete it. This causes
>> the measurement list to be atomically truncated before new measurements can
>> be added.
> I really like this design of atomically moving and subsequently deleting the
> measurement list. However this is a solution, not the motivation for the patch.
> Please include the motivation for the patch, before describing the solution.
>
>> Staging can be done only once at a time. In the event of kexec(),
>> staging is reverted and staged entries will be carried over to the new
>> kernel.
>> Staged measurements can be deleted entirely, or partially, with the
>> non-deleted ones added back to the IMA measurements list.
> This patch description is really long, which is an indication that the patch
> needs to be split up. Adding support for partially deleting the measurement
> list records, by prepending the remaining measurement records, should be a
> separate patch.
>
>> This allows the
>> remote attestation agents to easily separate the measurements that where
>> verified (staged and deleted) from those that weren't due to the race
>> between taking a TPM quote and reading the measurements list.
>>
>> User space is responsible to concatenate the staged IMA measurements list
>> portions (excluding the measurements added back to the IMA measurements
>> list) following the temporal order in which the operations were done,
>> together with the current measurement list. Then, it can send the collected
>> data to the remote verifiers.
> This belongs in a Documentation patch.
>
>> The benefit of staging and deleting is the ability to free precious kernel
>> memory,
> This is the motivation for the patch.
>
>> in exchange of delegating user space to reconstruct the full
>> measurement list from the chunks. No trust needs to be given to user space,
>> since the integrity of the measurement list is protected by the TPM.
> Agreed the measurement list, itself, is protected by the TPM. However, relying
> on userspace to reassemble the chunks is another concern. Support for staging
> and deleting the measurement list should be configurable. Defining a Kconfig
> should be part of this initial patch.
>
>> By default, staging the measurements list does not alter the hash table.
>> When staging and deleting are done, IMA is still able to detect collisions
>> on the staged and later deleted measurement entries, by keeping the entry
>> digests (only template data are freed).
>>
>> However, since during the measurements list serialization only the SHA1
>> digest is passed, and since there are no template data to recalculate the
>> other digests from, the hash table is currently not populated with digests
>> from staged/deleted entries after kexec().
>>
>> Introduce the new kernel option ima_flush_htable to decide whether or not
>> the digests of staged measurement entries are flushed from the hash table,
>> when they are deleted. Flushing the hash table is supported only when
>> deleting all the staged measurements, since in that case the old hash table
>> can be quickly swapped with a blank one (otherwise entries would have to be
>> removed one by one for partial deletion).
> Allowing the hash table to be deleted would be an example of another patch.
>
>> Then, introduce ascii_runtime_measurements_<algo>_staged and
>> binary_runtime_measurements_<algo>_staged interfaces to stage and delete
>> the measurements. Use 'echo A > <IMA interface>' and
>> 'echo D > <IMA interface>' to respectively stage and delete the entire
>> measurements list. Use 'echo N > <IMA interface>', with N between 1 and
>> ULONG_MAX - 1, to delete the selected staged portion of the measurements
>> list.
>>
>> The ima_measure_users counter (protected by the ima_measure_mutex mutex)
>> has been introduced to protect access to the measurements list and the
>> staged part. The open method of all the measurement interfaces has been
>> extended to allow only one writer at a time or, in alternative, multiple
>> readers. The write permission is used to stage and delete the measurements,
>> the read permission to read them. Write requires also the CAP_SYS_ADMIN
>> capability.
> Yes, this is part of the initial patch that adds support for staging the
> measurement list.
>
>> Finally, introduce the binary_lists enum and make binary_runtime_size
>> and ima_num_entries as arrays, to keep track of their values for the
>> current IMA measurements list (BINARY), current list plus staged
>> measurements (BINARY_STAGED) and the cumulative list since IMA
>> initialization (BINARY_FULL).
>>
>> Use BINARY in ima_show_measurements_count(), BINARY_STAGED in
>> ima_add_kexec_buffer() and BINARY_FULL in ima_measure_kexec_event().
>>
>> It should be noted that the BINARY_FULL counter is not passed through
>> kexec. Thus, the number of entries included in the kexec critical data
>> records refers to the entries since the previous kexec records.
>>
>> Note: This code derives from the Alt-IMA Huawei project, whose license is
>> GPL-2.0 OR MIT.
>>
>> Link: https://github.com/linux-integrity/linux/issues/1
>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> The design looks good. As I mentioned above, this patch description is quite
> long, which is an indication that the patch needs to be split up. One method of
> breaking it up would be:
>
> - (Basic) support for staging measurements for deletion (based on a Kconfig)
> - Support for removing the hash table
Great work on performance improvement and hash table redesign.
If update the following "Trim N" method patch with your current patch
lock time performance
improvement plus the hash table change, "Trim N" method can do the same
kernel
measurement list lock time as staged method do, right?
https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/
> - Support for deleting N measurement records (and pre-pending the remaining
> measurement records)
Is there any problem to bring work of "stage" step together to the
deletion step?
"Trim N" method does everything that "staged" method can do, right?
what's the "stage"
method can do but "trim N" method can't do?
in user space, if in "staged" state, no other user space agent can
access the IMA measure list, right?
Could you explain the benefit of bringing the "stage" step?
Thanks,
Steven
> - Adding documentation
>
> thanks,
>
> Mimi
^ permalink raw reply
* [PATCH v13.1] rust: interop: Add list module for C linked list interface
From: Joel Fernandes @ 2026-03-19 21:07 UTC (permalink / raw)
To: linux-kernel
Cc: Gary Guo, Danilo Krummrich, Miguel Ojeda, Boqun Feng,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Alex Gaynor, Dave Airlie, David Airlie,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Simona Vetter, Daniel Almeida, Koen Koning, Nikola Djukic,
Alexandre Courbot, Philipp Stanner, Elle Rhumsaa, Jonathan Corbet,
Alex Deucher, Christian König, Jani Nikula, Joonas Lahtinen,
Rodrigo Vivi, Tvrtko Ursulin, Huang Rui, Matthew Auld,
Matthew Brost, Lucas De Marchi, Thomas Hellström,
Helge Deller, John Hubbard, Alistair Popple, Timur Tabi,
Edwin Peer, Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
alexeyi, Eliot Courtney, dri-devel, rust-for-linux, linux-doc,
amd-gfx, intel-gfx, intel-xe, linux-fbdev, Joel Fernandes
Add a new module `kernel::interop::list` for working with C's doubly
circular linked lists. Provide low-level iteration over list nodes.
Typed iteration over actual items is provided with a `clist_create`
macro to assist in creation of the `CList` type.
Cc: Nikola Djukic <ndjukic@nvidia.com>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Acked-by: Alexandre Courbot <acourbot@nvidia.com>
Acked-by: Gary Guo <gary@garyguo.net>
Acked-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
Just resending this with changes to clippy safety lint fix (link:
https://lore.kernel.org/all/4728e901-df27-4685-a21a-d33a84946558@nvidia.com/)
MAINTAINERS | 8 +
rust/helpers/helpers.c | 1 +
rust/helpers/list.c | 17 ++
rust/kernel/interop/list.rs | 341 ++++++++++++++++++++++++++++++++++++
rust/kernel/interop/mod.rs | 9 +
rust/kernel/lib.rs | 2 +
6 files changed, 378 insertions(+)
create mode 100644 rust/helpers/list.c
create mode 100644 rust/kernel/interop/list.rs
create mode 100644 rust/kernel/interop/mod.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 4bd6b538a51f..e847099efcc2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23251,6 +23251,14 @@ T: git https://github.com/Rust-for-Linux/linux.git alloc-next
F: rust/kernel/alloc.rs
F: rust/kernel/alloc/
+RUST [INTEROP]
+M: Joel Fernandes <joelagnelf@nvidia.com>
+M: Alexandre Courbot <acourbot@nvidia.com>
+L: rust-for-linux@vger.kernel.org
+S: Maintained
+T: git https://github.com/Rust-for-Linux/linux.git interop-next
+F: rust/kernel/interop/
+
RUST [NUM]
M: Alexandre Courbot <acourbot@nvidia.com>
R: Yury Norov <yury.norov@gmail.com>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a3c42e51f00a..724fcb8240ac 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -35,6 +35,7 @@
#include "io.c"
#include "jump_label.c"
#include "kunit.c"
+#include "list.c"
#include "maple_tree.c"
#include "mm.c"
#include "mutex.c"
diff --git a/rust/helpers/list.c b/rust/helpers/list.c
new file mode 100644
index 000000000000..18095a5593c5
--- /dev/null
+++ b/rust/helpers/list.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Helpers for C circular doubly linked list implementation.
+ */
+
+#include <linux/list.h>
+
+__rust_helper void rust_helper_INIT_LIST_HEAD(struct list_head *list)
+{
+ INIT_LIST_HEAD(list);
+}
+
+__rust_helper void rust_helper_list_add_tail(struct list_head *new, struct list_head *head)
+{
+ list_add_tail(new, head);
+}
diff --git a/rust/kernel/interop/list.rs b/rust/kernel/interop/list.rs
new file mode 100644
index 000000000000..ae9692383462
--- /dev/null
+++ b/rust/kernel/interop/list.rs
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust interface for C doubly circular intrusive linked lists.
+//!
+//! This module provides Rust abstractions for iterating over C `list_head`-based
+//! linked lists. It should only be used for cases where C and Rust code share
+//! direct access to the same linked list through a C interop interface.
+//!
+//! Note: This *must not* be used by Rust components that just need a linked list
+//! primitive. Use [`kernel::list::List`] instead.
+//!
+//! # Examples
+//!
+//! ```
+//! use kernel::{
+//! bindings,
+//! interop::list::clist_create,
+//! types::Opaque,
+//! };
+//! # // Create test list with values (0, 10, 20) - normally done by C code but it is
+//! # // emulated here for doctests using the C bindings.
+//! # use core::mem::MaybeUninit;
+//! #
+//! # /// C struct with embedded `list_head` (typically will be allocated by C code).
+//! # #[repr(C)]
+//! # pub struct SampleItemC {
+//! # pub value: i32,
+//! # pub link: bindings::list_head,
+//! # }
+//! #
+//! # let mut head = MaybeUninit::<bindings::list_head>::uninit();
+//! #
+//! # let head = head.as_mut_ptr();
+//! # // SAFETY: `head` and all the items are test objects allocated in this scope.
+//! # unsafe { bindings::INIT_LIST_HEAD(head) };
+//! #
+//! # let mut items = [
+//! # MaybeUninit::<SampleItemC>::uninit(),
+//! # MaybeUninit::<SampleItemC>::uninit(),
+//! # MaybeUninit::<SampleItemC>::uninit(),
+//! # ];
+//! #
+//! # for (i, item) in items.iter_mut().enumerate() {
+//! # let ptr = item.as_mut_ptr();
+//! # // SAFETY: `ptr` points to a valid `MaybeUninit<SampleItemC>`.
+//! # unsafe { (*ptr).value = i as i32 * 10 };
+//! # // SAFETY: `&raw mut` creates a pointer valid for `INIT_LIST_HEAD`.
+//! # unsafe { bindings::INIT_LIST_HEAD(&raw mut (*ptr).link) };
+//! # // SAFETY: `link` was just initialized and `head` is a valid list head.
+//! # unsafe { bindings::list_add_tail(&mut (*ptr).link, head) };
+//! # }
+//!
+//! //
+//! /// Rust wrapper for the C struct.
+//! ///
+//! /// The list item struct in this example is defined in C code as:
+//! ///
+//! /// ```c
+//! /// struct SampleItemC {
+//! /// int value;
+//! /// struct list_head link;
+//! /// };
+//! /// ```
+//! #[repr(transparent)]
+//! pub struct Item(Opaque<SampleItemC>);
+//!
+//! impl Item {
+//! pub fn value(&self) -> i32 {
+//! // SAFETY: `Item` has same layout as `SampleItemC`.
+//! unsafe { (*self.0.get()).value }
+//! }
+//! }
+//!
+//!
+//! // Create typed [`CList`] from sentinel head.
+//! // SAFETY: `head` is valid and initialized, items are `SampleItemC` with
+//! // embedded `link` field, and `Item` is `#[repr(transparent)]` over `SampleItemC`.
+//! let list = unsafe { clist_create!(head, Item, SampleItemC, link) };
+//!
+//! // Iterate directly over typed items.
+//! let mut found_0 = false;
+//! let mut found_10 = false;
+//! let mut found_20 = false;
+//!
+//! for item in list.iter() {
+//! let val = item.value();
+//! if val == 0 { found_0 = true; }
+//! if val == 10 { found_10 = true; }
+//! if val == 20 { found_20 = true; }
+//! }
+//!
+//! assert!(found_0 && found_10 && found_20);
+//! ```
+
+use core::{
+ iter::FusedIterator,
+ marker::PhantomData, //
+};
+
+use crate::{
+ bindings,
+ types::Opaque, //
+};
+
+use pin_init::{
+ pin_data,
+ pin_init,
+ PinInit, //
+};
+
+/// FFI wrapper for a C `list_head` object used in intrusive linked lists.
+///
+/// # Invariants
+///
+/// - The underlying `list_head` is initialized with valid non-`NULL` `next`/`prev` pointers.
+#[pin_data]
+#[repr(transparent)]
+pub struct CListHead {
+ #[pin]
+ inner: Opaque<bindings::list_head>,
+}
+
+impl CListHead {
+ /// Create a `&CListHead` reference from a raw `list_head` pointer.
+ ///
+ /// # Safety
+ ///
+ /// - `ptr` must be a valid pointer to an initialized `list_head` (e.g. via
+ /// `INIT_LIST_HEAD()`), with valid non-`NULL` `next`/`prev` pointers.
+ /// - `ptr` must remain valid for the lifetime `'a`.
+ /// - The list and all linked `list_head` nodes must not be modified from
+ /// anywhere for the lifetime `'a`, unless done so via any [`CListHead`] APIs.
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *mut bindings::list_head) -> &'a Self {
+ // SAFETY:
+ // - `CListHead` has same layout as `list_head`.
+ // - `ptr` is valid and unmodified for `'a` per caller guarantees.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Get the raw `list_head` pointer.
+ #[inline]
+ pub fn as_raw(&self) -> *mut bindings::list_head {
+ self.inner.get()
+ }
+
+ /// Get the next [`CListHead`] in the list.
+ #[inline]
+ pub fn next(&self) -> &Self {
+ let raw = self.as_raw();
+ // SAFETY:
+ // - `self.as_raw()` is valid and initialized per type invariants.
+ // - The `next` pointer is valid and non-`NULL` per type invariants
+ // (initialized via `INIT_LIST_HEAD()` or equivalent).
+ unsafe { Self::from_raw((*raw).next) }
+ }
+
+ /// Check if this node is linked in a list (not isolated).
+ #[inline]
+ pub fn is_linked(&self) -> bool {
+ let raw = self.as_raw();
+ // SAFETY: `self.as_raw()` is valid per type invariants.
+ unsafe { (*raw).next != raw && (*raw).prev != raw }
+ }
+
+ /// Pin-initializer that initializes the list head.
+ pub fn new() -> impl PinInit<Self> {
+ pin_init!(Self {
+ // SAFETY: `INIT_LIST_HEAD` initializes `slot` to a valid empty list.
+ inner <- Opaque::ffi_init(|slot| unsafe { bindings::INIT_LIST_HEAD(slot) }),
+ })
+ }
+}
+
+// SAFETY: `list_head` contains no thread-bound state; it only holds
+// `next`/`prev` pointers.
+unsafe impl Send for CListHead {}
+
+// SAFETY: `CListHead` can be shared among threads as modifications are
+// not allowed at the moment.
+unsafe impl Sync for CListHead {}
+
+impl PartialEq for CListHead {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ core::ptr::eq(self, other)
+ }
+}
+
+impl Eq for CListHead {}
+
+/// Low-level iterator over `list_head` nodes.
+///
+/// An iterator used to iterate over a C intrusive linked list (`list_head`). Caller has to
+/// perform conversion of returned [`CListHead`] to an item (using [`container_of`] or similar).
+///
+/// # Invariants
+///
+/// `current` and `sentinel` are valid references into an initialized linked list.
+struct CListHeadIter<'a> {
+ /// Current position in the list.
+ current: &'a CListHead,
+ /// The sentinel head (used to detect end of iteration).
+ sentinel: &'a CListHead,
+}
+
+impl<'a> Iterator for CListHeadIter<'a> {
+ type Item = &'a CListHead;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ // Check if we've reached the sentinel (end of list).
+ if self.current == self.sentinel {
+ return None;
+ }
+
+ let item = self.current;
+ self.current = item.next();
+ Some(item)
+ }
+}
+
+impl<'a> FusedIterator for CListHeadIter<'a> {}
+
+/// A typed C linked list with a sentinel head intended for FFI use-cases where
+/// C subsystem manages a linked list that Rust code needs to read. Generally
+/// required only for special cases.
+///
+/// A sentinel head [`CListHead`] represents the entire linked list and can be used
+/// for iteration over items of type `T`, it is not associated with a specific item.
+///
+/// The const generic `OFFSET` specifies the byte offset of the `list_head` field within
+/// the struct that `T` wraps.
+///
+/// # Invariants
+///
+/// - The sentinel [`CListHead`] has valid non-`NULL` `next`/`prev` pointers.
+/// - `OFFSET` is the byte offset of the `list_head` field within the struct that `T` wraps.
+/// - All the list's `list_head` nodes have valid non-`NULL` `next`/`prev` pointers.
+#[repr(transparent)]
+pub struct CList<T, const OFFSET: usize>(CListHead, PhantomData<T>);
+
+impl<T, const OFFSET: usize> CList<T, OFFSET> {
+ /// Create a typed [`CList`] reference from a raw sentinel `list_head` pointer.
+ ///
+ /// # Safety
+ ///
+ /// - `ptr` must be a valid pointer to an initialized sentinel `list_head` (e.g. via
+ /// `INIT_LIST_HEAD()`), with valid non-`NULL` `next`/`prev` pointers.
+ /// - `ptr` must remain valid for the lifetime `'a`.
+ /// - The list and all linked nodes must not be concurrently modified for the lifetime `'a`.
+ /// - The list must contain items where the `list_head` field is at byte offset `OFFSET`.
+ /// - `T` must be `#[repr(transparent)]` over the C struct.
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *mut bindings::list_head) -> &'a Self {
+ // SAFETY:
+ // - `CList` has same layout as `CListHead` due to `#[repr(transparent)]`.
+ // - Caller guarantees `ptr` is a valid, sentinel `list_head` object.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Check if the list is empty.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ !self.0.is_linked()
+ }
+
+ /// Create an iterator over typed items.
+ #[inline]
+ pub fn iter(&self) -> CListIter<'_, T, OFFSET> {
+ let head = &self.0;
+ CListIter {
+ head_iter: CListHeadIter {
+ current: head.next(),
+ sentinel: head,
+ },
+ _phantom: PhantomData,
+ }
+ }
+}
+
+/// High-level iterator over typed list items.
+pub struct CListIter<'a, T, const OFFSET: usize> {
+ head_iter: CListHeadIter<'a>,
+ _phantom: PhantomData<&'a T>,
+}
+
+impl<'a, T, const OFFSET: usize> Iterator for CListIter<'a, T, OFFSET> {
+ type Item = &'a T;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ let head = self.head_iter.next()?;
+
+ // Convert to item using `OFFSET`.
+ //
+ // SAFETY: The pointer calculation is valid because `OFFSET` is derived
+ // from `offset_of!` per type invariants.
+ Some(unsafe { &*head.as_raw().byte_sub(OFFSET).cast::<T>() })
+ }
+}
+
+impl<'a, T, const OFFSET: usize> FusedIterator for CListIter<'a, T, OFFSET> {}
+
+/// Create a C doubly-circular linked list interface [`CList`] from a raw `list_head` pointer.
+///
+/// This macro creates a `CList<T, OFFSET>` that can iterate over items of type `$rust_type`
+/// linked via the `$field` field in the underlying C struct `$c_type`.
+///
+/// # Arguments
+///
+/// - `$head`: Raw pointer to the sentinel `list_head` object (`*mut bindings::list_head`).
+/// - `$rust_type`: Each item's rust wrapper type.
+/// - `$c_type`: Each item's C struct type that contains the embedded `list_head`.
+/// - `$field`: The name of the `list_head` field within the C struct.
+///
+/// # Safety
+///
+/// The caller must ensure:
+///
+/// - `$head` is a valid, initialized sentinel `list_head` (e.g. via `INIT_LIST_HEAD()`)
+/// pointing to a list that is not concurrently modified for the lifetime of the [`CList`].
+/// - The list contains items of type `$c_type` linked via an embedded `$field`.
+/// - `$rust_type` is `#[repr(transparent)]` over `$c_type` or has compatible layout.
+///
+/// # Examples
+///
+/// Refer to the examples in the [`crate::interop::list`] module documentation.
+#[macro_export]
+macro_rules! clist_create {
+ ($head:expr, $rust_type:ty, $c_type:ty, $($field:tt).+) => {{
+ // Compile-time check that field path is a `list_head`.
+ let _: fn(*const $c_type) -> *const $crate::bindings::list_head =
+ |p| &raw const (*p).$($field).+;
+
+ // Calculate offset and create `CList`.
+ const OFFSET: usize = ::core::mem::offset_of!($c_type, $($field).+);
+ $crate::interop::list::CList::<$rust_type, OFFSET>::from_raw($head)
+ }};
+}
+pub use clist_create;
diff --git a/rust/kernel/interop/mod.rs b/rust/kernel/interop/mod.rs
new file mode 100644
index 000000000000..b88140cf76dc
--- /dev/null
+++ b/rust/kernel/interop/mod.rs
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Infrastructure for interfacing Rust code with C kernel subsystems.
+//!
+//! This module is intended for low-level, unsafe Rust infrastructure code
+//! that interoperates between Rust and C. It is NOT for use directly in
+//! Rust drivers.
+
+pub mod list;
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index d93292d47420..bdcf632050ee 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -29,6 +29,7 @@
#![feature(lint_reasons)]
//
// Stable since Rust 1.82.0.
+#![feature(offset_of_nested)]
#![feature(raw_ref_op)]
//
// Stable since Rust 1.83.0.
@@ -107,6 +108,7 @@
#[doc(hidden)]
pub mod impl_flags;
pub mod init;
+pub mod interop;
pub mod io;
pub mod ioctl;
pub mod iommu;
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v4 09/21] mm: swap: allocate a virtual swap slot for each swapped out page
From: Peter Zijlstra @ 2026-03-19 21:03 UTC (permalink / raw)
To: Nhat Pham
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, pfalcato, rafael, rakie.kim,
roman.gushchin, rppt, ryan.roberts, shakeel.butt, shikemeng,
surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed, yuanchu,
zhengqi.arch, ziy, kernel-team, riel
In-Reply-To: <CAKEwX=MUrLtZAcmwqBau5GLnWQrjL7A_4tYrdZ4TQQaE+hsVkA@mail.gmail.com>
On Thu, Mar 19, 2026 at 11:37:19AM -0700, Nhat Pham wrote:
> On Thu, Mar 19, 2026 at 12:56 AM Peter Zijlstra <peterz@infradead.org> wrote:
> >
> > On Wed, Mar 18, 2026 at 03:29:40PM -0700, Nhat Pham wrote:
> > > diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> > > index 62cd7b35a29c9..85cb45022e796 100644
> > > --- a/include/linux/cpuhotplug.h
> > > +++ b/include/linux/cpuhotplug.h
> > > @@ -86,6 +86,7 @@ enum cpuhp_state {
> > > CPUHP_FS_BUFF_DEAD,
> > > CPUHP_PRINTK_DEAD,
> > > CPUHP_MM_MEMCQ_DEAD,
> > > + CPUHP_MM_VSWAP_DEAD,
> > > CPUHP_PERCPU_CNT_DEAD,
> > > CPUHP_RADIX_DEAD,
> > > CPUHP_PAGE_ALLOC,
> >
> > > +static int vswap_cpu_dead(unsigned int cpu)
> > > +{
> > > + struct vswap_cluster *cluster;
> > > + int order;
> > > +
> > > + rcu_read_lock();
> >
> > nit:
> > guard(rcu)();
> >
> > > + for (order = 0; order < SWAP_NR_ORDERS; order++) {
> > > + cluster = per_cpu(percpu_vswap_cluster.clusters[order], cpu);
> > > + if (cluster) {
> > > + per_cpu(percpu_vswap_cluster.clusters[order], cpu) = NULL;
> > > + spin_lock(&cluster->lock);
> >
> > This breaks on PREEMPT_RT as this is ran with IRQs disabled. This must
> > be a raw_spinlock_t.
> >
> > > + cluster->cached = false;
> > > + if (refcount_dec_and_test(&cluster->refcnt))
> > > + vswap_cluster_free(cluster);
> >
> > And this... below.
> >
> > > + spin_unlock(&cluster->lock);
> > > + }
> > > + }
> > > + rcu_read_unlock();
> > > +
> > > + return 0;
> > > +}
> >
> > > +static void vswap_cluster_free(struct vswap_cluster *cluster)
> > > +{
> > > + VM_WARN_ON(cluster->count || cluster->cached);
> > > + VM_WARN_ON(!spin_is_locked(&cluster->lock));
> >
> > This is terrible, please use:
> >
> > lockdep_assert_held(&cluster->lock);
> >
> > > + xa_lock(&vswap_cluster_map);
> >
> > This is again broken, this cannot be from a DEAD callback with IRQs
> > disabled.
> >
> > > + list_del_init(&cluster->list);
> > > + __xa_erase(&vswap_cluster_map, cluster->id);
> >
> > Strictly speaking this can end up in xas_alloc(), which is again, not
> > allowed in a DEAD callback.
>
> I see. I'll take a look at this. Thanks for pointing this out, Peter!
Oh, I think I might have confused DEAD and DYING here. DYING is the
tricky one, DEAD should be okay. Sorry about that.
^ permalink raw reply
* Re: [PATCH v7 07/10] x86/vmscape: Use static_call() for predictor flush
From: Peter Zijlstra @ 2026-03-19 20:58 UTC (permalink / raw)
To: Pawan Gupta
Cc: x86, Nikolay Borisov, H. Peter Anvin, Josh Poimboeuf,
David Kaplan, Sean Christopherson, Borislav Petkov, Dave Hansen,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, KP Singh,
Jiri Olsa, David S. Miller, David Laight, Andy Lutomirski,
Thomas Gleixner, Ingo Molnar, David Ahern, Martin KaFai Lau,
Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend,
Stanislav Fomichev, Hao Luo, Paolo Bonzini, Jonathan Corbet,
linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf, netdev,
linux-doc
In-Reply-To: <20260319-vmscape-bhb-v7-7-b76a777a98af@linux.intel.com>
On Thu, Mar 19, 2026 at 08:41:54AM -0700, Pawan Gupta wrote:
> diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
> index 68e2df3e3bf5..b75eda114503 100644
> --- a/arch/x86/kernel/cpu/bugs.c
> +++ b/arch/x86/kernel/cpu/bugs.c
> @@ -144,6 +144,17 @@ EXPORT_SYMBOL_GPL(cpu_buf_idle_clear);
> */
> DEFINE_STATIC_KEY_FALSE(switch_mm_cond_l1d_flush);
>
> +/*
> + * Controls CPU Fill buffer clear before VMenter. This is a subset of
> + * X86_FEATURE_CLEAR_CPU_BUF, and should only be enabled when KVM-only
> + * mitigation is required.
> + */
> +DEFINE_STATIC_KEY_FALSE(cpu_buf_vm_clear);
> +EXPORT_SYMBOL_GPL(cpu_buf_vm_clear);
> +
> +DEFINE_STATIC_CALL_NULL(vmscape_predictor_flush, write_ibpb);
> +EXPORT_STATIC_CALL_GPL(vmscape_predictor_flush);
Does that want to be:
EXPORT_STATIC_CALL_TRAMP_GPL(vmscape_predictor_flush);
The distinction being that if you only export the trampoline, modules
can do the static_call() thing, but cannot do static_call_update().
^ permalink raw reply
* Re: [PATCH v3 00/16] mm: expand mmap_prepare functionality and usage
From: Andrew Morton @ 2026-03-19 20:31 UTC (permalink / raw)
To: Lorenzo Stoakes (Oracle)
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
On Thu, 19 Mar 2026 18:23:24 +0000 "Lorenzo Stoakes (Oracle)" <ljs@kernel.org> wrote:
> This series expands the mmap_prepare functionality, which is intended to
> replace the deprecated f_op->mmap hook which has been the source of bugs
> and security issues for some time.
>
Thanks, I updated mm.git's mm-new branch to this version.
>
> v3:
> * Propagated tags (thanks Suren, Richard!)
> * Updated 12/16 to correctly clear the vm_area_desc data structure in
> set_desc_from_vma() as per Joshua Hahn (thanks! :)
> * Fixed type in 12/16 as per Suren (cheers!)
> * Fixed up 6/16 to use mmap_action_ioremap_full() in simple_ioremap_prepare() as
> suggested by Suren.
> * Also fixed up 6/16 to call io_remap_pfn_range_prepare() direct rather than
> mmap_action_prepare() as per Suren.
> * Also fixed up 6/16 to pass vm_len rather than vm_[start, end] to
> __simple_ioremap_prep() as per Suren (thanks for all the above! :)
> * Fixed issue in rmap lock being held - we were referencing a vma->vm_file after
> the VMA was unmapped, so UAF. Avoid that. Also do_munmap() relies on rmap lock
> NOT being held or may deadlock, so extend functionality to ensure we drop it
> when it is held on error paths.
> * Updated 'area' -> 'vma' variable in 3/16 in VMA test dup.h.
> * Fixed up reference to __compat_vma_mmap() in 12/16 commit message.
> * Updated 1/16 to no longer duplicatively apply io_remap_pfn_range_pfn().
> * Updated 1/16 to delegate I/O remap complete to remap complete logic.
> * Fixed various typos in 12/16.
> * Fixed stale comment typos in 13/16.
> * Fixed commit msg and comment typos in 14/16.
> * Removed accidental sneak peak to future functionality in 15/16 commit message
> :).
> * Fixed up field names to be identical in VMA tests + mm_types.h in 6/16,
> 15/16.
Here's how v3 altered mm.git:
drivers/hv/vmbus_drv.c | 6 +-
drivers/target/target_core_user.c | 2
include/linux/mm.h | 3 -
include/linux/uio_driver.h | 2
mm/internal.h | 22 ++++-----
mm/memory.c | 16 +++----
mm/util.c | 62 ++++++++++++++--------------
mm/vma.c | 24 +++-------
tools/testing/vma/include/dup.h | 19 ++++----
tools/testing/vma/include/stubs.h | 3 -
10 files changed, 77 insertions(+), 82 deletions(-)
--- a/drivers/hv/vmbus_drv.c~b
+++ a/drivers/hv/vmbus_drv.c
@@ -1955,8 +1955,8 @@ static int hv_mmap_ring_buffer_wrapper(s
int err;
/*
- * hv_(create|remove)_ring_sysfs implementation ensures that mmap_ring_buffer
- * is not NULL.
+ * hv_(create|remove)_ring_sysfs implementation ensures that
+ * mmap_prepare_ring_buffer is not NULL.
*/
compat_set_desc_from_vma(&desc, filp, vma);
err = channel->mmap_prepare_ring_buffer(channel, &desc);
@@ -2055,7 +2055,7 @@ static const struct kobj_type vmbus_chan
/**
* hv_create_ring_sysfs() - create "ring" sysfs entry corresponding to ring buffers for a channel.
* @channel: Pointer to vmbus_channel structure
- * @hv_mmap_ring_buffer: function pointer for initializing the function to be called on mmap
+ * @hv_mmap_prepare_ring_buffer: function pointer for initializing the function to be called on mmap
* channel's "ring" sysfs node, which is for the ring buffer of that channel.
* Function pointer is of below type:
* int (*hv_mmap_prepare_ring_buffer)(struct vmbus_channel *channel,
--- a/drivers/target/target_core_user.c~b
+++ a/drivers/target/target_core_user.c
@@ -1865,7 +1865,7 @@ static int tcmu_vma_mapped(unsigned long
{
struct tcmu_dev *udev = *vm_private_data;
- pr_debug("vma_open\n");
+ pr_debug("vma_mapped\n");
kref_get(&udev->kref);
return 0;
--- a/include/linux/mm.h~b
+++ a/include/linux/mm.h
@@ -4405,7 +4405,8 @@ static inline void mmap_action_map_kerne
int mmap_action_prepare(struct vm_area_desc *desc);
int mmap_action_complete(struct vm_area_struct *vma,
- struct mmap_action *action);
+ struct mmap_action *action,
+ bool rmap_lock_held);
/* Look up the first VMA which exactly match the interval vm_start ... vm_end */
static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm,
--- a/include/linux/uio_driver.h~b
+++ a/include/linux/uio_driver.h
@@ -97,7 +97,7 @@ struct uio_device {
* @irq_flags: flags for request_irq()
* @priv: optional private data
* @handler: the device's irq handler
- * @mmap_prepare: mmap_pepare operation for this uio device
+ * @mmap_prepare: mmap_prepare operation for this uio device
* @open: open operation for this uio device
* @release: release operation for this uio device
* @irqcontrol: disable/enable irqs when 0/1 is written to /dev/uioX
--- a/mm/internal.h~b
+++ a/mm/internal.h
@@ -1805,29 +1805,25 @@ int remap_pfn_range_prepare(struct vm_ar
int remap_pfn_range_complete(struct vm_area_struct *vma,
struct mmap_action *action);
int simple_ioremap_prepare(struct vm_area_desc *desc);
-/* No simple_ioremap_complete, is ultimately handled by remap complete. */
static inline int io_remap_pfn_range_prepare(struct vm_area_desc *desc)
{
struct mmap_action *action = &desc->action;
const unsigned long orig_pfn = action->remap.start_pfn;
+ const pgprot_t orig_pgprot = action->remap.pgprot;
const unsigned long size = action->remap.size;
const unsigned long pfn = io_remap_pfn_range_pfn(orig_pfn, size);
+ int err;
action->remap.start_pfn = pfn;
- return remap_pfn_range_prepare(desc);
-}
-
-static inline int io_remap_pfn_range_complete(struct vm_area_struct *vma,
- struct mmap_action *action)
-{
- const unsigned long size = action->remap.size;
- const unsigned long orig_pfn = action->remap.start_pfn;
- const pgprot_t orig_prot = vma->vm_page_prot;
+ action->remap.pgprot = pgprot_decrypted(orig_pgprot);
+ err = remap_pfn_range_prepare(desc);
+ if (err)
+ return err;
- action->remap.pgprot = pgprot_decrypted(orig_prot);
- action->remap.start_pfn = io_remap_pfn_range_pfn(orig_pfn, size);
- return remap_pfn_range_complete(vma, action);
+ /* Remap does the actual work. */
+ action->type = MMAP_REMAP_PFN;
+ return 0;
}
#ifdef CONFIG_MMU_NOTIFIER
--- a/mm/memory.c~b
+++ a/mm/memory.c
@@ -3207,11 +3207,10 @@ int remap_pfn_range_complete(struct vm_a
return do_remap_pfn_range(vma, start, pfn, size, prot);
}
-static int __simple_ioremap_prep(unsigned long vm_start, unsigned long vm_end,
- pgoff_t vm_pgoff, phys_addr_t start_phys,
- unsigned long size, unsigned long *pfnp)
+static int __simple_ioremap_prep(unsigned long vm_len, pgoff_t vm_pgoff,
+ phys_addr_t start_phys, unsigned long size,
+ unsigned long *pfnp)
{
- const unsigned long vm_len = vm_end - vm_start;
unsigned long pfn, pages;
/* Check that the physical memory area passed in looks valid */
@@ -3250,14 +3249,14 @@ int simple_ioremap_prepare(struct vm_are
unsigned long pfn;
int err;
- err = __simple_ioremap_prep(desc->start, desc->end, desc->pgoff,
+ err = __simple_ioremap_prep(vma_desc_size(desc), desc->pgoff,
start, size, &pfn);
if (err)
return err;
/* The I/O remap logic does the heavy lifting. */
- mmap_action_ioremap(desc, desc->start, pfn, vma_desc_size(desc));
- return mmap_action_prepare(desc);
+ mmap_action_ioremap_full(desc, pfn);
+ return io_remap_pfn_range_prepare(desc);
}
/**
@@ -3283,8 +3282,7 @@ int vm_iomap_memory(struct vm_area_struc
unsigned long pfn;
int err;
- err = __simple_ioremap_prep(vm_start, vm_end, vma->vm_pgoff, start,
- len, &pfn);
+ err = __simple_ioremap_prep(vm_len, vma->vm_pgoff, start, len, &pfn);
if (err)
return err;
--- a/mm/util.c~b
+++ a/mm/util.c
@@ -1181,6 +1181,8 @@ void compat_set_desc_from_vma(struct vm_
const struct file *file,
const struct vm_area_struct *vma)
{
+ memset(desc, 0, sizeof(*desc));
+
desc->mm = vma->vm_mm;
desc->file = (struct file *)file;
desc->start = vma->vm_start;
@@ -1224,7 +1226,7 @@ static int __compat_vma_mapped(struct fi
* @vma: The VMA to which @desc should be applied.
*
* The function assumes that you have obtained a VMA descriptor @desc from
- * compt_set_desc_from_vma(), and already executed the mmap_prepare() hook upon
+ * compat_set_desc_from_vma(), and already executed the mmap_prepare() hook upon
* it.
*
* It then performs any specified mmap actions, and invokes the vm_ops->mapped()
@@ -1249,7 +1251,8 @@ int __compat_vma_mmap(struct vm_area_des
/* Update the VMA from the descriptor. */
compat_set_vma_from_desc(vma, desc);
/* Complete any specified mmap actions. */
- err = mmap_action_complete(vma, &desc->action);
+ err = mmap_action_complete(vma, &desc->action,
+ /*rmap_lock_held=*/false);
if (err)
return err;
@@ -1397,29 +1400,31 @@ again:
}
static int mmap_action_finish(struct vm_area_struct *vma,
- struct mmap_action *action, int err)
+ struct mmap_action *action, int err,
+ bool rmap_lock_held)
{
+ if (rmap_lock_held)
+ i_mmap_unlock_write(vma->vm_file->f_mapping);
+
+ if (!err) {
+ if (action->success_hook)
+ return action->success_hook(vma);
+ return 0;
+ }
+
/*
* If an error occurs, unmap the VMA altogether and return an error. We
* only clear the newly allocated VMA, since this function is only
* invoked if we do NOT merge, so we only clean up the VMA we created.
*/
- if (err) {
- unmap_vma_locked(vma);
- if (action->error_hook) {
- /* We may want to filter the error. */
- err = action->error_hook(err);
-
- /* The caller should not clear the error. */
- VM_WARN_ON_ONCE(!err);
- }
- return err;
+ unmap_vma_locked(vma);
+ if (action->error_hook) {
+ /* We may want to filter the error. */
+ err = action->error_hook(err);
+ /* The caller should not clear the error. */
+ VM_WARN_ON_ONCE(!err);
}
-
- if (action->success_hook)
- return action->success_hook(vma);
-
- return 0;
+ return err;
}
#ifdef CONFIG_MMU
@@ -1454,13 +1459,15 @@ EXPORT_SYMBOL(mmap_action_prepare);
* mmap_action_complete - Execute VMA descriptor action.
* @vma: The VMA to perform the action upon.
* @action: The action to perform.
+ * @rmap_lock_held: Is the file rmap lock held?
*
* Similar to mmap_action_prepare().
*
* Return: 0 on success, or error, at which point the VMA will be unmapped.
*/
int mmap_action_complete(struct vm_area_struct *vma,
- struct mmap_action *action)
+ struct mmap_action *action,
+ bool rmap_lock_held)
{
int err = 0;
@@ -1471,23 +1478,19 @@ int mmap_action_complete(struct vm_area_
case MMAP_REMAP_PFN:
err = remap_pfn_range_complete(vma, action);
break;
- case MMAP_IO_REMAP_PFN:
- err = io_remap_pfn_range_complete(vma, action);
- break;
case MMAP_MAP_KERNEL_PAGES:
err = map_kernel_pages_complete(vma, action);
break;
+ case MMAP_IO_REMAP_PFN:
case MMAP_SIMPLE_IO_REMAP:
- /*
- * The simple I/O remap should have been delegated to an I/O
- * remap.
- */
+ /* Should have been delegated. */
WARN_ON_ONCE(1);
err = -EINVAL;
break;
}
- return mmap_action_finish(vma, action, err);
+ return mmap_action_finish(vma, action, err,
+ rmap_lock_held);
}
EXPORT_SYMBOL(mmap_action_complete);
#else
@@ -1509,7 +1512,8 @@ int mmap_action_prepare(struct vm_area_d
EXPORT_SYMBOL(mmap_action_prepare);
int mmap_action_complete(struct vm_area_struct *vma,
- struct mmap_action *action)
+ struct mmap_action *action,
+ bool rmap_lock_held)
{
int err = 0;
@@ -1525,7 +1529,7 @@ int mmap_action_complete(struct vm_area_
break;
}
- return mmap_action_finish(vma, action, err);
+ return mmap_action_finish(vma, action, err, rmap_lock_held);
}
EXPORT_SYMBOL(mmap_action_complete);
#endif
--- a/mm/vma.c~b
+++ a/mm/vma.c
@@ -2732,7 +2732,8 @@ static bool can_set_ksm_flags_early(stru
return false;
}
-static int call_mapped_hook(struct vm_area_struct *vma)
+static int call_mapped_hook(struct mmap_state *map,
+ struct vm_area_struct *vma)
{
const struct vm_operations_struct *vm_ops = vma->vm_ops;
void *vm_private_data = vma->vm_private_data;
@@ -2743,6 +2744,9 @@ static int call_mapped_hook(struct vm_ar
err = vm_ops->mapped(vma->vm_start, vma->vm_end, vma->vm_pgoff,
vma->vm_file, &vm_private_data);
if (err) {
+ if (map->hold_file_rmap_lock)
+ i_mmap_unlock_write(vma->vm_file->f_mapping);
+
unmap_vma_locked(vma);
return err;
}
@@ -2752,17 +2756,6 @@ static int call_mapped_hook(struct vm_ar
return 0;
}
-static void maybe_drop_file_rmap_lock(struct mmap_state *map,
- struct vm_area_struct *vma)
-{
- struct file *file;
-
- if (!map->hold_file_rmap_lock)
- return;
- file = vma->vm_file;
- i_mmap_unlock_write(file->f_mapping);
-}
-
static unsigned long __mmap_region(struct file *file, unsigned long addr,
unsigned long len, vma_flags_t vma_flags,
unsigned long pgoff, struct list_head *uf)
@@ -2814,11 +2807,10 @@ static unsigned long __mmap_region(struc
__mmap_complete(&map, vma);
if (have_mmap_prepare && allocated_new) {
- error = mmap_action_complete(vma, &desc.action);
+ error = mmap_action_complete(vma, &desc.action,
+ map.hold_file_rmap_lock);
if (!error)
- error = call_mapped_hook(vma);
-
- maybe_drop_file_rmap_lock(&map, vma);
+ error = call_mapped_hook(&map, vma);
if (error)
return error;
}
--- a/tools/testing/vma/include/dup.h~b
+++ a/tools/testing/vma/include/dup.h
@@ -470,13 +470,13 @@ struct mmap_action {
pgprot_t pgprot;
} remap;
struct {
- phys_addr_t start;
- unsigned long len;
+ phys_addr_t start_phys_addr;
+ unsigned long size;
} simple_ioremap;
struct {
unsigned long start;
struct page **pages;
- unsigned long num;
+ unsigned long nr_pages;
pgoff_t pgoff;
} map_kernel;
};
@@ -648,7 +648,7 @@ struct vm_operations_struct {
* upon first mapping a VMA.
* Context: User context. May sleep. Caller holds mmap_lock.
*/
- void (*open)(struct vm_area_struct * area);
+ void (*open)(struct vm_area_struct *vma);
/**
* @close: Called when the VMA is being removed from the MM.
* Context: User context. May sleep. Caller holds mmap_lock.
@@ -672,8 +672,8 @@ struct vm_operations_struct {
int (*mapped)(unsigned long start, unsigned long end, pgoff_t pgoff,
const struct file *file, void **vm_private_data);
/* Called any time before splitting to check if it's allowed */
- int (*may_split)(struct vm_area_struct *area, unsigned long addr);
- int (*mremap)(struct vm_area_struct *area);
+ int (*may_split)(struct vm_area_struct *vma, unsigned long addr);
+ int (*mremap)(struct vm_area_struct *vma);
/*
* Called by mprotect() to make driver-specific permission
* checks before mprotect() is finalised. The VMA must not
@@ -685,7 +685,7 @@ struct vm_operations_struct {
vm_fault_t (*huge_fault)(struct vm_fault *vmf, unsigned int order);
vm_fault_t (*map_pages)(struct vm_fault *vmf,
pgoff_t start_pgoff, pgoff_t end_pgoff);
- unsigned long (*pagesize)(struct vm_area_struct * area);
+ unsigned long (*pagesize)(struct vm_area_struct *vma);
/* notification that a previously read-only page is about to become
* writable, if an error is returned it will cause a SIGBUS */
@@ -1288,6 +1288,8 @@ static inline void compat_set_desc_from_
const struct file *file,
const struct vm_area_struct *vma)
{
+ memset(desc, 0, sizeof(*desc));
+
desc->mm = vma->vm_mm;
desc->file = (struct file *)file;
desc->start = vma->vm_start;
@@ -1342,7 +1344,8 @@ static inline int __compat_vma_mmap(stru
/* Update the VMA from the descriptor. */
compat_set_vma_from_desc(vma, desc);
/* Complete any specified mmap actions. */
- err = mmap_action_complete(vma, &desc->action);
+ err = mmap_action_complete(vma, &desc->action,
+ /*rmap_lock_held=*/false);
if (err)
return err;
--- a/tools/testing/vma/include/stubs.h~b
+++ a/tools/testing/vma/include/stubs.h
@@ -87,7 +87,8 @@ static inline int mmap_action_prepare(st
}
static inline int mmap_action_complete(struct vm_area_struct *vma,
- struct mmap_action *action)
+ struct mmap_action *action,
+ bool rmap_lock_held)
{
return 0;
}
_
^ permalink raw reply
* Re: [PATCH v3 10/10] RAS: add firmware-first CPER provider
From: kernel test robot @ 2026-03-19 19:49 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree, linux-cxl
Cc: oe-kbuild-all, Ahmed Tiba, Michael.Zhao2, robh, linux-arm-kernel,
Dmitry.Lamerov, rafael, conor, will, bp, catalin.marinas, krzk+dt,
linux-doc, mchehab+huawei, tony.luck
In-Reply-To: <20260318-topics-ahmtib01-ras_ffh_arm_internal_review-v3-10-48e6a1c249ef@arm.com>
Hi Ahmed,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 2d1373e4246da3b58e1df058374ed6b101804e07]
url: https://github.com/intel-lab-lkp/linux/commits/Ahmed-Tiba/ACPI-APEI-GHES-share-macros-via-a-private-header/20260319-115432
base: 2d1373e4246da3b58e1df058374ed6b101804e07
patch link: https://lore.kernel.org/r/20260318-topics-ahmtib01-ras_ffh_arm_internal_review-v3-10-48e6a1c249ef%40arm.com
patch subject: [PATCH v3 10/10] RAS: add firmware-first CPER provider
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20260320/202603200337.hRVhVYpU-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260320/202603200337.hRVhVYpU-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603200337.hRVhVYpU-lkp@intel.com/
All warnings (new ones prefixed by >>):
In file included from drivers/ras/cper-esource.c:26:
>> include/acpi/ghes_cper.h:105:44: warning: 'enum fixed_addresses' declared inside parameter list will not be visible outside of this definition or declaration
105 | u64 *buf_paddr, enum fixed_addresses fixmap_idx);
| ^~~~~~~~~~~~~~~
include/acpi/ghes_cper.h:108:45: warning: 'enum fixed_addresses' declared inside parameter list will not be visible outside of this definition or declaration
108 | u64 buf_paddr, enum fixed_addresses fixmap_idx);
| ^~~~~~~~~~~~~~~
include/acpi/ghes_cper.h:111:46: warning: 'enum fixed_addresses' declared inside parameter list will not be visible outside of this definition or declaration
111 | u64 *buf_paddr, enum fixed_addresses fixmap_idx);
| ^~~~~~~~~~~~~~~
include/acpi/ghes_cper.h:115:45: warning: 'enum fixed_addresses' declared inside parameter list will not be visible outside of this definition or declaration
115 | u64 buf_paddr, enum fixed_addresses fixmap_idx,
| ^~~~~~~~~~~~~~~
drivers/ras/cper-esource.c: In function 'cper_esource_ack':
drivers/ras/cper-esource.c:84:23: error: implicit declaration of function 'readq'; did you mean 'readl'? [-Werror=implicit-function-declaration]
84 | val = readq(ctx->ack.addr);
| ^~~~~
| readl
drivers/ras/cper-esource.c:87:17: error: implicit declaration of function 'writeq'; did you mean 'writel'? [-Werror=implicit-function-declaration]
87 | writeq(val, ctx->ack.addr);
| ^~~~~~
| writel
cc1: some warnings being treated as errors
vim +105 include/acpi/ghes_cper.h
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 102
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 103 int ghes_read_estatus(struct ghes *ghes,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 104 struct acpi_hest_generic_status *estatus,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 @105 u64 *buf_paddr, enum fixed_addresses fixmap_idx);
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 106 void ghes_clear_estatus(struct ghes *ghes,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 107 struct acpi_hest_generic_status *estatus,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 108 u64 buf_paddr, enum fixed_addresses fixmap_idx);
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 109 int __ghes_peek_estatus(struct ghes *ghes,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 110 struct acpi_hest_generic_status *estatus,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 111 u64 *buf_paddr, enum fixed_addresses fixmap_idx);
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 112 int __ghes_check_estatus(struct ghes *ghes,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 113 struct acpi_hest_generic_status *estatus);
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 114 int __ghes_read_estatus(struct acpi_hest_generic_status *estatus,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 115 u64 buf_paddr, enum fixed_addresses fixmap_idx,
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 116 size_t buf_len);
dacb62d12abb41 Ahmed Tiba 2026-03-18 117 int ghes_estatus_cached(struct acpi_hest_generic_status *estatus);
dacb62d12abb41 Ahmed Tiba 2026-03-18 118 void ghes_estatus_cache_add(struct acpi_hest_generic *generic,
dacb62d12abb41 Ahmed Tiba 2026-03-18 119 struct acpi_hest_generic_status *estatus);
40686d3c8f0a4d Ahmed Tiba 2026-03-18 120 int ghes_register_vendor_record_notifier(struct notifier_block *nb);
40686d3c8f0a4d Ahmed Tiba 2026-03-18 121 void ghes_unregister_vendor_record_notifier(struct notifier_block *nb);
648c11f76ec605 Ahmed Tiba 2026-03-18 122 void ghes_defer_non_standard_event(struct acpi_hest_generic_data *gdata,
648c11f76ec605 Ahmed Tiba 2026-03-18 123 int sev);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 124 int ghes_severity(int severity);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 125 bool ghes_handle_memory_failure(struct acpi_hest_generic_data *gdata,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 126 int sev, bool sync);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 127 bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 128 int sev, bool sync);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 129 void ghes_handle_aer(struct acpi_hest_generic_data *gdata);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 130 void ghes_log_hwerr(int sev, guid_t *sec_type);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 131 void __ghes_print_estatus(const char *pfx,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 132 const struct acpi_hest_generic *generic,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 133 const struct acpi_hest_generic_status *estatus);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 134 int ghes_print_estatus(const char *pfx,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 135 const struct acpi_hest_generic *generic,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 136 const struct acpi_hest_generic_status *estatus);
0eb637ae58ed14 Ahmed Tiba 2026-03-18 137 void ghes_cper_handle_status(struct device *dev,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 138 const struct acpi_hest_generic *generic,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 139 const struct acpi_hest_generic_status *estatus,
0eb637ae58ed14 Ahmed Tiba 2026-03-18 140 bool sync);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 141 void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err,
baccdf38fe1b81 Ahmed Tiba 2026-03-18 142 int severity);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 143 int cxl_cper_register_prot_err_work(struct work_struct *work);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 144 int cxl_cper_unregister_prot_err_work(struct work_struct *work);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 145 int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data *wd);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 146 void cxl_cper_post_event(enum cxl_event_type event_type,
baccdf38fe1b81 Ahmed Tiba 2026-03-18 147 struct cxl_cper_event_rec *rec);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 148 int cxl_cper_register_work(struct work_struct *work);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 149 int cxl_cper_unregister_work(struct work_struct *work);
baccdf38fe1b81 Ahmed Tiba 2026-03-18 150 int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd);
7f6d32e88c31e2 Ahmed Tiba 2026-03-18 151
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v3 10/10] RAS: add firmware-first CPER provider
From: kernel test robot @ 2026-03-19 19:36 UTC (permalink / raw)
To: Ahmed Tiba, linux-acpi, devicetree, linux-cxl
Cc: llvm, oe-kbuild-all, Ahmed Tiba, Michael.Zhao2, robh,
linux-arm-kernel, Dmitry.Lamerov, rafael, conor, will, bp,
catalin.marinas, krzk+dt, linux-doc, mchehab+huawei, tony.luck
In-Reply-To: <20260318-topics-ahmtib01-ras_ffh_arm_internal_review-v3-10-48e6a1c249ef@arm.com>
Hi Ahmed,
kernel test robot noticed the following build errors:
[auto build test ERROR on 2d1373e4246da3b58e1df058374ed6b101804e07]
url: https://github.com/intel-lab-lkp/linux/commits/Ahmed-Tiba/ACPI-APEI-GHES-share-macros-via-a-private-header/20260319-115432
base: 2d1373e4246da3b58e1df058374ed6b101804e07
patch link: https://lore.kernel.org/r/20260318-topics-ahmtib01-ras_ffh_arm_internal_review-v3-10-48e6a1c249ef%40arm.com
patch subject: [PATCH v3 10/10] RAS: add firmware-first CPER provider
config: hexagon-randconfig-001-20260320 (https://download.01.org/0day-ci/archive/20260320/202603200349.vMgMwlEs-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 4abb927bacf37f18f6359a41639a6d1b3bffffb5)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260320/202603200349.vMgMwlEs-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603200349.vMgMwlEs-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/ras/cper-esource.c:84:9: error: call to undeclared function 'readq'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
84 | val = readq(ctx->ack.addr);
| ^
>> drivers/ras/cper-esource.c:87:3: error: call to undeclared function 'writeq'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
87 | writeq(val, ctx->ack.addr);
| ^
2 errors generated.
vim +/readq +84 drivers/ras/cper-esource.c
75
76 static void cper_esource_ack(struct cper_esource *ctx)
77 {
78 u64 val;
79
80 if (!ctx->ack.present)
81 return;
82
83 if (ctx->ack.width == 64) {
> 84 val = readq(ctx->ack.addr);
85 val &= ctx->ack.preserve;
86 val |= ctx->ack.set;
> 87 writeq(val, ctx->ack.addr);
88 } else {
89 val = readl(ctx->ack.addr);
90 val &= (u32)ctx->ack.preserve;
91 val |= (u32)ctx->ack.set;
92 writel(val, ctx->ack.addr);
93 }
94 }
95
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v2 2/2] hwmon: add MP2985 driver
From: Guenter Roeck @ 2026-03-19 19:33 UTC (permalink / raw)
To: wenswang, krzk+dt, conor+dt, corbet, skhan
Cc: devicetree, linux-kernel, linux-hwmon, linux-doc
In-Reply-To: <20260319060318.643572-2-wenswang@yeah.net>
On 3/18/26 23:03, wenswang@yeah.net wrote:
> From: Wensheng Wang <wenswang@yeah.net>
>
> Add support for MPS mp2985 controller. This driver exposes
> telemetry and limit value readings and writtings.
>
> Signed-off-by: Wensheng Wang <wenswang@yeah.net>
Please check:
https://sashiko.dev/#/patchset/20260319060318.643572-1-wenswang%40yeah.net
It appears to me that the findings are real. Please have a look.
Thanks,
Guenter
> ---
> v1 -> v2:
> 1. remove duplicate entry in mp2985.rst
> 2. clamp vout value to 32767
> 3. simplify the code for obtaining PMBUS_VOUT_MODE bit value
> 4. add comment for explaining MP2985 supported vout mode
> 5. switch back to previous page after obtaining vid scale to avoid
> confusing the PMBus core
>
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/mp2985.rst | 147 +++++++++++++
> MAINTAINERS | 7 +
> drivers/hwmon/pmbus/Kconfig | 9 +
> drivers/hwmon/pmbus/Makefile | 1 +
> drivers/hwmon/pmbus/mp2985.c | 377 +++++++++++++++++++++++++++++++++
> 6 files changed, 542 insertions(+)
> create mode 100644 Documentation/hwmon/mp2985.rst
> create mode 100644 drivers/hwmon/pmbus/mp2985.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index b2ca8513cfcd..1b7007f41b39 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -183,6 +183,7 @@ Hardware Monitoring Kernel Drivers
> mp2925
> mp29502
> mp2975
> + mp2985
> mp2993
> mp5023
> mp5920
> diff --git a/Documentation/hwmon/mp2985.rst b/Documentation/hwmon/mp2985.rst
> new file mode 100644
> index 000000000000..87a39c8a300c
> --- /dev/null
> +++ b/Documentation/hwmon/mp2985.rst
> @@ -0,0 +1,147 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +Kernel driver mp2985
> +====================
> +
> +Supported chips:
> +
> + * MPS mp2985
> +
> + Prefix: 'mp2985'
> +
> +Author:
> +
> + Wensheng Wang <wenswang@yeah.net>
> +
> +Description
> +-----------
> +
> +This driver implements support for Monolithic Power Systems, Inc. (MPS)
> +MP2985 Dual Loop Digital Multi-phase Controller.
> +
> +Device compliant with:
> +
> +- PMBus rev 1.3 interface.
> +
> +The driver exports the following attributes via the 'sysfs' files
> +for input voltage:
> +
> +**in1_input**
> +
> +**in1_label**
> +
> +**in1_crit**
> +
> +**in1_crit_alarm**
> +
> +**in1_lcrit**
> +
> +**in1_lcrit_alarm**
> +
> +**in1_max**
> +
> +**in1_max_alarm**
> +
> +**in1_min**
> +
> +**in1_min_alarm**
> +
> +The driver provides the following attributes for output voltage:
> +
> +**in2_input**
> +
> +**in2_label**
> +
> +**in2_crit**
> +
> +**in2_crit_alarm**
> +
> +**in2_lcrit**
> +
> +**in2_lcrit_alarm**
> +
> +**in3_input**
> +
> +**in3_label**
> +
> +**in3_crit**
> +
> +**in3_crit_alarm**
> +
> +**in3_lcrit**
> +
> +**in3_lcrit_alarm**
> +
> +The driver provides the following attributes for input current:
> +
> +**curr1_input**
> +
> +**curr1_label**
> +
> +The driver provides the following attributes for output current:
> +
> +**curr2_input**
> +
> +**curr2_label**
> +
> +**curr2_crit**
> +
> +**curr2_crit_alarm**
> +
> +**curr2_max**
> +
> +**curr2_max_alarm**
> +
> +**curr3_input**
> +
> +**curr3_label**
> +
> +**curr3_crit**
> +
> +**curr3_crit_alarm**
> +
> +**curr3_max**
> +
> +**curr3_max_alarm**
> +
> +The driver provides the following attributes for input power:
> +
> +**power1_input**
> +
> +**power1_label**
> +
> +**power2_input**
> +
> +**power2_label**
> +
> +The driver provides the following attributes for output power:
> +
> +**power3_input**
> +
> +**power3_label**
> +
> +**power4_input**
> +
> +**power4_label**
> +
> +The driver provides the following attributes for temperature:
> +
> +**temp1_input**
> +
> +**temp1_crit**
> +
> +**temp1_crit_alarm**
> +
> +**temp1_max**
> +
> +**temp1_max_alarm**
> +
> +**temp2_input**
> +
> +**temp2_crit**
> +
> +**temp2_crit_alarm**
> +
> +**temp2_max**
> +
> +**temp2_max_alarm**
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d7241695df96..ac62dbaef34d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17920,6 +17920,13 @@ S: Maintained
> F: Documentation/hwmon/mp29502.rst
> F: drivers/hwmon/pmbus/mp29502.c
>
> +MPS MP2985 DRIVER
> +M: Wensheng Wang <wenswang@yeah.net>
> +L: linux-hwmon@vger.kernel.org
> +S: Maintained
> +F: Documentation/hwmon/mp2985.rst
> +F: drivers/hwmon/pmbus/mp2985.c
> +
> MPS MP2993 DRIVER
> M: Noah Wang <noahwang.wang@outlook.com>
> L: linux-hwmon@vger.kernel.org
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index fc1273abe357..83fe5866c083 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -447,6 +447,15 @@ config SENSORS_MP2975
> This driver can also be built as a module. If so, the module will
> be called mp2975.
>
> +config SENSORS_MP2985
> + tristate "MPS MP2985"
> + help
> + If you say yes here you get hardware monitoring support for MPS
> + MP2985 Dual Loop Digital Multi-Phase Controller.
> +
> + This driver can also be built as a module. If so, the module will
> + be called mp2985.
> +
> config SENSORS_MP2993
> tristate "MPS MP2993"
> help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index d6c86924f887..24505bbee2b0 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_MP2891) += mp2891.o
> obj-$(CONFIG_SENSORS_MP2925) += mp2925.o
> obj-$(CONFIG_SENSORS_MP29502) += mp29502.o
> obj-$(CONFIG_SENSORS_MP2975) += mp2975.o
> +obj-$(CONFIG_SENSORS_MP2985) += mp2985.o
> obj-$(CONFIG_SENSORS_MP2993) += mp2993.o
> obj-$(CONFIG_SENSORS_MP5023) += mp5023.o
> obj-$(CONFIG_SENSORS_MP5920) += mp5920.o
> diff --git a/drivers/hwmon/pmbus/mp2985.c b/drivers/hwmon/pmbus/mp2985.c
> new file mode 100644
> index 000000000000..df0934ebcaa0
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/mp2985.c
> @@ -0,0 +1,377 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2985)
> + *
> + * Copyright (C) 2026 MPS
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include "pmbus.h"
> +
> +/*
> + * Vender specific register READ_PIN_EST(0x93), READ_IIN_EST(0x8E),
> + * MFR_VR_MULTI_CONFIG_R1(0x0D) and MFR_VR_MULTI_CONFIG_R2(0x1D).
> + * The READ_PIN_EST is used to read pin telemetry, the READ_IIN_EST
> + * is used to read iin telemetry and the MFR_VR_MULTI_CONFIG_R1,
> + * MFR_VR_MULTI_CONFIG_R2 are used to obtain vid scale.
> + */
> +#define READ_PIN_EST 0x93
> +#define READ_IIN_EST 0x8E
> +#define MFR_VR_MULTI_CONFIG_R1 0x0D
> +#define MFR_VR_MULTI_CONFIG_R2 0x1D
> +
> +#define MP2985_VOUT_DIV 512
> +#define MP2985_VOUT_OVUV_UINT 195
> +#define MP2985_VOUT_OVUV_DIV 100
> +
> +#define MP2985_PAGE_NUM 2
> +
> +#define MP2985_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_PIN | \
> + PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \
> + PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \
> + PMBUS_HAVE_STATUS_VOUT | \
> + PMBUS_HAVE_STATUS_IOUT | \
> + PMBUS_HAVE_STATUS_TEMP | \
> + PMBUS_HAVE_STATUS_INPUT)
> +
> +#define MP2985_RAIL2_FUNC (PMBUS_HAVE_PIN | PMBUS_HAVE_VOUT | \
> + PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \
> + PMBUS_HAVE_TEMP | PMBUS_HAVE_IIN | \
> + PMBUS_HAVE_STATUS_VOUT | \
> + PMBUS_HAVE_STATUS_IOUT | \
> + PMBUS_HAVE_STATUS_TEMP | \
> + PMBUS_HAVE_STATUS_INPUT)
> +
> +struct mp2985_data {
> + struct pmbus_driver_info info;
> + int vout_scale[MP2985_PAGE_NUM];
> + int vid_offset[MP2985_PAGE_NUM];
> +};
> +
> +#define to_mp2985_data(x) container_of(x, struct mp2985_data, info)
> +
> +static u16 mp2985_linear_exp_transfer(u16 word, u16 expect_exponent)
> +{
> + s16 exponent, mantissa, target_exponent;
> +
> + exponent = ((s16)word) >> 11;
> + mantissa = ((s16)((word & 0x7ff) << 5)) >> 5;
> + target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11;
> +
> + if (exponent > target_exponent)
> + mantissa = mantissa << (exponent - target_exponent);
> + else
> + mantissa = mantissa >> (target_exponent - exponent);
> +
> + return (mantissa & 0x7ff) | ((expect_exponent << 11) & 0xf800);
> +}
> +
> +static int mp2985_read_byte_data(struct i2c_client *client, int page, int reg)
> +{
> + int ret;
> +
> + switch (reg) {
> + case PMBUS_VOUT_MODE:
> + /*
> + * The MP2985 does not follow standard PMBus protocol completely,
> + * and the calculation of vout in this driver is based on direct
> + * format. As a result, the format of vout is enforced to direct.
> + */
> + ret = PB_VOUT_MODE_DIRECT;
> + break;
> + default:
> + ret = -ENODATA;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int mp2985_read_word_data(struct i2c_client *client, int page, int phase,
> + int reg)
> +{
> + const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
> + struct mp2985_data *data = to_mp2985_data(info);
> + int ret;
> +
> + switch (reg) {
> + case PMBUS_READ_VOUT:
> + ret = pmbus_read_word_data(client, page, phase, reg);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * The MP2985 supports three vout mode, direct, linear11 and vid mode.
> + * In vid mode, the MP2985 vout telemetry has 49 vid step offset, but
> + * PMBUS_VOUT_OV_FAULT_LIMIT and PMBUS_VOUT_UV_FAULT_LIMIT do not take
> + * this into consideration, its resolution is 1.95mV/LSB, as a result,
> + * format[PSC_VOLTAGE_OUT] can not be set to vid directly. Adding extra
> + * vid_offset variable for vout telemetry.
> + */
> + ret = clamp_val(DIV_ROUND_CLOSEST(((ret & GENMASK(11, 0)) +
> + data->vid_offset[page]) *
> + data->vout_scale[page], MP2985_VOUT_DIV),
> + 0, 0x7FFF);
> + break;
> + case PMBUS_READ_IIN:
> + /*
> + * The MP2985 has standard PMBUS_READ_IIN register(0x89), but this is
> + * not used to read the input current of per rail. The input current
> + * is read through the vender redefined register READ_IIN_EST(0x8E).
> + */
> + ret = pmbus_read_word_data(client, page, phase, READ_IIN_EST);
> + break;
> + case PMBUS_READ_PIN:
> + /*
> + * The MP2985 has standard PMBUS_READ_PIN register(0x97), but this
> + * is not used to read the input power of per rail. The input power
> + * of per rail is read through the vender redefined register
> + * READ_PIN_EST(0x93).
> + */
> + ret = pmbus_read_word_data(client, page, phase, READ_PIN_EST);
> + break;
> + case PMBUS_VOUT_OV_FAULT_LIMIT:
> + case PMBUS_VOUT_UV_FAULT_LIMIT:
> + ret = pmbus_read_word_data(client, page, phase, reg);
> + if (ret < 0)
> + return ret;
> +
> + ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * MP2985_VOUT_OVUV_UINT,
> + MP2985_VOUT_OVUV_DIV);
> + break;
> + case PMBUS_STATUS_WORD:
> + case PMBUS_READ_VIN:
> + case PMBUS_READ_IOUT:
> + case PMBUS_READ_POUT:
> + case PMBUS_READ_TEMPERATURE_1:
> + case PMBUS_VIN_OV_FAULT_LIMIT:
> + case PMBUS_VIN_OV_WARN_LIMIT:
> + case PMBUS_VIN_UV_WARN_LIMIT:
> + case PMBUS_VIN_UV_FAULT_LIMIT:
> + case PMBUS_IOUT_OC_FAULT_LIMIT:
> + case PMBUS_IOUT_OC_WARN_LIMIT:
> + case PMBUS_OT_FAULT_LIMIT:
> + case PMBUS_OT_WARN_LIMIT:
> + ret = -ENODATA;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int mp2985_write_word_data(struct i2c_client *client, int page, int reg,
> + u16 word)
> +{
> + int ret;
> +
> + switch (reg) {
> + case PMBUS_VIN_OV_FAULT_LIMIT:
> + case PMBUS_VIN_OV_WARN_LIMIT:
> + case PMBUS_VIN_UV_WARN_LIMIT:
> + case PMBUS_VIN_UV_FAULT_LIMIT:
> + /*
> + * The PMBUS_VIN_OV_FAULT_LIMIT, PMBUS_VIN_OV_WARN_LIMIT,
> + * PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT
> + * of MP2985 is linear11 format, and the exponent is a
> + * constant value(5'b11101), so the exponent of word
> + * parameter should be converted to 5'b11101(0x1D).
> + */
> + ret = pmbus_write_word_data(client, page, reg,
> + mp2985_linear_exp_transfer(word, 0x1D));
> + break;
> + case PMBUS_VOUT_OV_FAULT_LIMIT:
> + case PMBUS_VOUT_UV_FAULT_LIMIT:
> + /*
> + * The bit0-bit11 is the limit value, and bit12-bit15
> + * should not be changed.
> + */
> + ret = pmbus_read_word_data(client, page, 0xff, reg);
> + if (ret < 0)
> + return ret;
> +
> + ret = pmbus_write_word_data(client, page, reg,
> + (ret & ~GENMASK(11, 0)) |
> + FIELD_PREP(GENMASK(11, 0),
> + DIV_ROUND_CLOSEST(word * MP2985_VOUT_OVUV_DIV,
> + MP2985_VOUT_OVUV_UINT)));
> + break;
> + case PMBUS_OT_FAULT_LIMIT:
> + case PMBUS_OT_WARN_LIMIT:
> + /*
> + * The PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT of
> + * MP2985 is linear11 format, and the exponent is a
> + * constant value(5'b00000), so the exponent of word
> + * parameter should be converted to 5'b00000.
> + */
> + ret = pmbus_write_word_data(client, page, reg,
> + mp2985_linear_exp_transfer(word, 0x00));
> + break;
> + case PMBUS_IOUT_OC_FAULT_LIMIT:
> + case PMBUS_IOUT_OC_WARN_LIMIT:
> + /*
> + * The PMBUS_IOUT_OC_FAULT_LIMIT and PMBUS_IOUT_OC_WARN_LIMIT
> + * of MP2985 is linear11 format, and the exponent can not be
> + * changed.
> + */
> + ret = pmbus_read_word_data(client, page, 0xff, reg);
> + if (ret < 0)
> + return ret;
> +
> + ret = pmbus_write_word_data(client, page, reg,
> + mp2985_linear_exp_transfer(word,
> + FIELD_GET(GENMASK(15, 11),
> + ret)));
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int
> +mp2985_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info,
> + int page)
> +{
> + struct mp2985_data *data = to_mp2985_data(info);
> + int ret;
> +
> + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
> + if (ret < 0)
> + return ret;
> +
> + ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * The MP2985 supports three vout mode. If PMBUS_VOUT_MODE
> + * bit5 is 1, it is vid mode. If PMBUS PMBUS_VOUT_MODE bit4
> + * is 1, it is linear11 mode. If PMBUS PMBUS_VOUT_MODE bit6
> + * is 1, it is direct mode. In vid mode, the MP2985 vout
> + * telemetry has 49 vid step offset.
> + */
> + if (FIELD_GET(BIT(5), ret)) {
> + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 2);
> + if (ret < 0)
> + return ret;
> +
> + ret = i2c_smbus_read_word_data(client, page == 0 ?
> + MFR_VR_MULTI_CONFIG_R1 :
> + MFR_VR_MULTI_CONFIG_R2);
> + if (ret < 0)
> + return ret;
> +
> + if (page == 0) {
> + if (FIELD_GET(BIT(4), ret))
> + data->vout_scale[page] = 2560;
> + else
> + data->vout_scale[page] = 5120;
> + } else {
> + if (FIELD_GET(BIT(3), ret))
> + data->vout_scale[page] = 2560;
> + else
> + data->vout_scale[page] = 5120;
> + }
> +
> + data->vid_offset[page] = 49;
> +
> + /*
> + * For vid mode, the MP2985 should be changed to page 2
> + * to obtain vout scale value, this may confuse the PMBus
> + * core. To avoid this, switch back to the previous page
> + * again.
> + */
> + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
> + if (ret < 0)
> + return ret;
> + } else if (FIELD_GET(BIT(4), ret)) {
> + data->vout_scale[page] = 1;
> + data->vid_offset[page] = 0;
> + } else {
> + data->vout_scale[page] = 512;
> + data->vid_offset[page] = 0;
> + }
> +
> + return 0;
> +}
> +
> +static int mp2985_identify(struct i2c_client *client, struct pmbus_driver_info *info)
> +{
> + int ret;
> +
> + ret = mp2985_identify_vout_scale(client, info, 0);
> + if (ret < 0)
> + return ret;
> +
> + return mp2985_identify_vout_scale(client, info, 1);
> +}
> +
> +static struct pmbus_driver_info mp2985_info = {
> + .pages = MP2985_PAGE_NUM,
> + .format[PSC_VOLTAGE_IN] = linear,
> + .format[PSC_CURRENT_IN] = linear,
> + .format[PSC_CURRENT_OUT] = linear,
> + .format[PSC_POWER] = linear,
> + .format[PSC_TEMPERATURE] = linear,
> + .format[PSC_VOLTAGE_OUT] = direct,
> +
> + .m[PSC_VOLTAGE_OUT] = 1,
> + .R[PSC_VOLTAGE_OUT] = 3,
> + .b[PSC_VOLTAGE_OUT] = 0,
> +
> + .func[0] = MP2985_RAIL1_FUNC,
> + .func[1] = MP2985_RAIL2_FUNC,
> + .read_word_data = mp2985_read_word_data,
> + .read_byte_data = mp2985_read_byte_data,
> + .write_word_data = mp2985_write_word_data,
> + .identify = mp2985_identify,
> +};
> +
> +static int mp2985_probe(struct i2c_client *client)
> +{
> + struct mp2985_data *data;
> +
> + data = devm_kzalloc(&client->dev, sizeof(struct mp2985_data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + memcpy(&data->info, &mp2985_info, sizeof(mp2985_info));
> +
> + return pmbus_do_probe(client, &data->info);
> +}
> +
> +static const struct i2c_device_id mp2985_id[] = {
> + {"mp2985", 0},
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, mp2985_id);
> +
> +static const struct of_device_id __maybe_unused mp2985_of_match[] = {
> + {.compatible = "mps,mp2985"},
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, mp2985_of_match);
> +
> +static struct i2c_driver mp2985_driver = {
> + .driver = {
> + .name = "mp2985",
> + .of_match_table = mp2985_of_match,
> + },
> + .probe = mp2985_probe,
> + .id_table = mp2985_id,
> +};
> +
> +module_i2c_driver(mp2985_driver);
> +
> +MODULE_AUTHOR("Wensheng Wang <wenswang@yeah.net>");
> +MODULE_DESCRIPTION("PMBus driver for MPS MP2985 device");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("PMBUS");
^ permalink raw reply
* Re: [PATCH v3] hwmon: add driver for ARCTIC Fan Controller
From: Guenter Roeck @ 2026-03-19 19:29 UTC (permalink / raw)
To: Aureo Serrano de Souza, linux-hwmon
Cc: linux, corbet, skhan, linux-doc, linux-kernel
In-Reply-To: <20260319103509.243653-1-aureo.serrano@arctic.de>
On 3/19/26 03:34, Aureo Serrano de Souza wrote:
> Add hwmon driver for the ARCTIC Fan Controller, a USB HID device
> (VID 0x3904, PID 0xF001) with 10 fan channels. Exposes fan speed in
> RPM (read-only) and PWM duty cycle (0-255, read/write) via sysfs.
>
> The device pushes IN reports at ~1 Hz containing RPM readings. PWM is
> set via OUT reports; the device applies the new duty cycle and sends
> back a 2-byte ACK (Report ID 0x02). The driver waits up to 1 s for
> the ACK using a completion. Measured device latency: max ~563 ms over
> 500 iterations. PWM control is manual-only: the device never changes
> duty cycle autonomously.
>
> raw_event() may run in hardirq context, so fan_rpm[] is protected by
> a spinlock with irq-save. pwm_duty[] and the report buffer are
> serialized by the hwmon core, which holds its lock for the duration of
> the read/write callbacks.
>
> Signed-off-by: Aureo Serrano de Souza <aureo.serrano@arctic.de>
Feedback from Sashiko:
https://sashiko.dev/#/patchset/20260319103509.243653-1-aureo.serrano%40arctic.de
Regarding DMA alignment: Please use ____cacheline_aligned to ensure that
the buffer is DMA-aligned.
Thanks,
Guenter
> ---
> Thanks to the reviewers for their feedback.
>
> Changes since v2:
> - buf[]: add __aligned(8) for DMA safety
> - ARCTIC_ACK_TIMEOUT_MS: add comment noting observed max ~563 ms
> - arctic_fan_parse_report(): replace hwmon_lock/hwmon_unlock with
> spin_lock_irqsave; hwmon_lock() may sleep and is unsafe when
> raw_event() runs in hardirq/softirq context
> - arctic_fan_raw_event(): use spin_lock_irqsave for ACK path
> - arctic_fan_write(): use spin_lock_irqsave for completion reinit
> - arctic_fan_write(): clamp val to [0, 255] before u8 cast
> - hardware teardown: register arctic_fan_hw_stop() via
> devm_add_action_or_reset() before hwmon; devm LIFO order ensures
> hwmon unregisters before hid_hw_close/stop; remove() is a no-op
> - remove priv->hwmon_dev (no longer needed)
>
> Changes since v1:
> - Use hid_dbg() instead of module_param debug flag
> - Move hid_device_id table adjacent to hid_driver struct
> - Use get_unaligned_le16() for RPM parsing
> - Remove impossible bounds/NULL checks; remove retry loop
> - Add hid_is_usb() guard
> - Do not update pwm_duty from IN reports (device is manual-only)
> - Add completion/ACK mechanism for OUT report acknowledgement
> - Add Documentation/hwmon/arctic_fan_controller.rst and MAINTAINERS
>
> diff --git a/Documentation/hwmon/arctic_fan_controller.rst b/Documentation/hwmon/arctic_fan_controller.rst
> new file mode 100644
> index 0000000000..e417f54b62
> --- /dev/null
> +++ b/Documentation/hwmon/arctic_fan_controller.rst
> @@ -0,0 +1,34 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver arctic_fan_controller
> +===================================
> +
> +Supported devices:
> +
> +* ARCTIC Fan Controller (USB HID, VID 0x3904, PID 0xF001)
> +
> +Author: Aureo Serrano de Souza <aureo.serrano@arctic.de>
> +
> +Description
> +-----------
> +
> +This driver provides hwmon support for the ARCTIC Fan Controller, a USB Custom HID
> +device with 10 fan channels. The device sends IN reports about once per second
> +containing current PWM (bytes 1–10) and RPM (bytes 11–30). PWM is set via OUT reports
> +(bytes 1–10, 0–100% per channel). Fan control is manual-only: the device does not
> +change PWM autonomously, only when it receives an OUT report from the host.
> +
> +Usage notes
> +-----------
> +
> +Since it is a USB device, hotplug is supported. The device is autodetected.
> +
> +Sysfs entries
> +-------------
> +
> +================ ===============================================================
> +fan[1-10]_input Fan speed in RPM (read-only, from device IN reports).
> +pwm[1-10] PWM duty cycle. Sysfs uses 0–255 (0%–100%); the device uses
> + 0–100% internally. Read: current duty from IN report (scaled
> + to 0–255). Write: set duty via OUT report (value 0–255).
> +================ ===============================================================
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index b2ca8513cf..c34713040e 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -42,6 +42,7 @@ Hardware Monitoring Kernel Drivers
> aht10
> amc6821
> aquacomputer_d5next
> + arctic_fan_controller
> asb100
> asc7621
> aspeed-g6-pwm-tach
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 96ea84948d..ec3112bd41 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2053,6 +2053,13 @@ S: Maintained
> F: drivers/net/arcnet/
> F: include/uapi/linux/if_arcnet.h
>
> +ARCTIC FAN CONTROLLER DRIVER
> +M: Aureo Serrano de Souza <aureo.serrano@arctic.de>
> +L: linux-hwmon@vger.kernel.org
> +S: Maintained
> +F: Documentation/hwmon/arctic_fan_controller.rst
> +F: drivers/hwmon/arctic_fan_controller.c
> +
> ARM AND ARM64 SoC SUB-ARCHITECTURES (COMMON PARTS)
> M: Arnd Bergmann <arnd@arndb.de>
> M: Krzysztof Kozlowski <krzk@kernel.org>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 328867242c..6c90a8dd40 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -388,6 +388,18 @@ config SENSORS_APPLESMC
> Say Y here if you have an applicable laptop and want to experience
> the awesome power of applesmc.
>
> +config SENSORS_ARCTIC_FAN_CONTROLLER
> + tristate "ARCTIC Fan Controller"
> + depends on USB_HID
> + help
> + If you say yes here you get support for the ARCTIC Fan Controller,
> + a USB HID device (VID 0x3904, PID 0xF001) with 10 fan channels.
> + The driver exposes fan speed (RPM) and PWM control via the hwmon
> + sysfs interface.
> +
> + This driver can also be built as a module. If so, the module
> + will be called arctic_fan_controller.
> +
> config SENSORS_ARM_SCMI
> tristate "ARM SCMI Sensors"
> depends on ARM_SCMI_PROTOCOL
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 5833c807c6..ef831c3375 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
> obj-$(CONFIG_SENSORS_AHT10) += aht10.o
> obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
> obj-$(CONFIG_SENSORS_AQUACOMPUTER_D5NEXT) += aquacomputer_d5next.o
> +obj-$(CONFIG_SENSORS_ARCTIC_FAN_CONTROLLER) += arctic_fan_controller.o
> obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
> obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
> obj-$(CONFIG_SENSORS_AS370) += as370-hwmon.o
> diff --git a/drivers/hwmon/arctic_fan_controller.c b/drivers/hwmon/arctic_fan_controller.c
> new file mode 100644
> index 0000000000..d71b323e0b
> --- /dev/null
> +++ b/drivers/hwmon/arctic_fan_controller.c
> @@ -0,0 +1,288 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Linux hwmon driver for ARCTIC Fan Controller
> + *
> + * USB Custom HID device with 10 fan channels.
> + * Exposes fan RPM (input) and PWM (0-255) via hwmon. Device pushes IN reports
> + * at ~1 Hz; no GET_REPORT. OUT reports set PWM duty (bytes 1-10, 0-100%).
> + * PWM is manual-only: the device does not change duty autonomously, only
> + * when it receives an OUT report from the host.
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/hid.h>
> +#include <linux/hwmon.h>
> +#include <linux/jiffies.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/spinlock.h>
> +#include <linux/unaligned.h>
> +
> +#define ARCTIC_VID 0x3904
> +#define ARCTIC_PID 0xF001
> +#define ARCTIC_NUM_FANS 10
> +#define ARCTIC_OUTPUT_REPORT_ID 0x01
> +#define ARCTIC_REPORT_LEN 32
> +#define ARCTIC_RPM_OFFSET 11 /* bytes 11-30: 10 x uint16 LE */
> +/* ACK report: device sends Report ID 0x02, 2 bytes (ID + status) after applying OUT report */
> +#define ARCTIC_ACK_REPORT_ID 0x02
> +#define ARCTIC_ACK_REPORT_LEN 2
> +/*
> + * Time to wait for ACK report after send.
> + * Measured over 500 iterations: max ~563 ms. Keep 1 s as margin.
> + */
> +#define ARCTIC_ACK_TIMEOUT_MS 1000
> +
> +struct arctic_fan_data {
> + struct hid_device *hdev;
> + spinlock_t in_report_lock; /* protects fan_rpm[], ack_status, in_report_received */
> + struct completion in_report_received; /* ACK (ID 0x02) received in raw_event */
> + int ack_status; /* 0 = OK, negative errno on device error */
> + u32 fan_rpm[ARCTIC_NUM_FANS];
> + u8 pwm_duty[ARCTIC_NUM_FANS]; /* 0-255 matching sysfs range; converted to 0-100 on send */
> + /* OUT report buffer; DMA-safe alignment; hwmon core serializes write callbacks */
> + u8 buf[ARCTIC_REPORT_LEN] __aligned(8);
> +};
> +
> +/*
> + * Parse RPM values from the periodic status report (10 x uint16 LE at rpm_off).
> + * pwm_duty is not updated from the report: the device is manual-only, so the
> + * host cache is the authoritative source for PWM.
> + * Called from raw_event which may run in IRQ context; must not sleep.
> + */
> +static void arctic_fan_parse_report(struct arctic_fan_data *priv, u8 *buf,
> + int len, int rpm_off)
> +{
> + unsigned long flags;
> + int i;
> +
> + if (len < rpm_off + 20)
> + return;
> +
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + for (i = 0; i < ARCTIC_NUM_FANS; i++)
> + priv->fan_rpm[i] = get_unaligned_le16(&buf[rpm_off + i * 2]);
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> +}
> +
> +/*
> + * raw_event: IN reports.
> + *
> + * Status report: Report ID 0x01, 32 bytes:
> + * byte 0 = report ID, bytes 1-10 = PWM 0-100%, bytes 11-30 = 10 x RPM uint16 LE.
> + * Device pushes these at ~1 Hz; no GET_REPORT.
> + *
> + * ACK report: Report ID 0x02, 2 bytes:
> + * byte 0 = 0x02, byte 1 = status (0x00 = OK, 0x01 = ERROR).
> + * Sent once after accepting and applying an OUT report (ID 0x01).
> + */
> +static int arctic_fan_raw_event(struct hid_device *hdev,
> + struct hid_report *report, u8 *data, int size)
> +{
> + struct arctic_fan_data *priv = hid_get_drvdata(hdev);
> + unsigned long flags;
> +
> + hid_dbg(hdev, "arctic_fan: raw_event id=%u size=%d\n", report->id, size);
> +
> + if (report->id == ARCTIC_ACK_REPORT_ID && size == ARCTIC_ACK_REPORT_LEN) {
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + priv->ack_status = data[1] == 0x00 ? 0 : -EIO;
> + complete(&priv->in_report_received);
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> + return 0;
> + }
> +
> + if (report->id != ARCTIC_OUTPUT_REPORT_ID || size != ARCTIC_REPORT_LEN) {
> + hid_dbg(hdev, "arctic_fan: raw_event id=%u size=%d ignored\n",
> + report->id, size);
> + return 0;
> + }
> +
> + arctic_fan_parse_report(priv, data, size, ARCTIC_RPM_OFFSET);
> + return 0;
> +}
> +
> +static umode_t arctic_fan_is_visible(const void *data,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + if (type == hwmon_fan && attr == hwmon_fan_input)
> + return 0444;
> + if (type == hwmon_pwm && attr == hwmon_pwm_input)
> + return 0644;
> + return 0;
> +}
> +
> +static int arctic_fan_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct arctic_fan_data *priv = dev_get_drvdata(dev);
> + unsigned long flags;
> +
> + if (type == hwmon_fan && attr == hwmon_fan_input) {
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + *val = priv->fan_rpm[channel];
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> + return 0;
> + }
> + if (type == hwmon_pwm && attr == hwmon_pwm_input) {
> + /* pwm_duty is modified only in write(), which the hwmon core serializes */
> + *val = priv->pwm_duty[channel];
> + return 0;
> + }
> + return -EINVAL;
> +}
> +
> +static int arctic_fan_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + struct arctic_fan_data *priv = dev_get_drvdata(dev);
> + unsigned long flags;
> + long t;
> + int i, ret;
> +
> + /*
> + * The hwmon core holds its lock for the duration of this callback,
> + * serializing concurrent writes. priv->buf is heap-allocated (embedded
> + * in the devm_kzalloc'd struct), satisfying usb_hcd_map_urb_for_dma().
> + */
> + priv->pwm_duty[channel] = (u8)clamp_val(val, 0, 255);
> + priv->buf[0] = ARCTIC_OUTPUT_REPORT_ID;
> + for (i = 0; i < ARCTIC_NUM_FANS; i++)
> + priv->buf[1 + i] = DIV_ROUND_CLOSEST(
> + (unsigned int)priv->pwm_duty[i] * 100, 255);
> +
> + /*
> + * Serialized by the hwmon core: only one arctic_fan_write() runs at a
> + * time, so reinit_completion() and wait_for_completion_*() below are
> + * not racy against another concurrent write.
> + * Use irqsave to match the IRQ context in which raw_event may run.
> + */
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + priv->ack_status = -ETIMEDOUT;
> + reinit_completion(&priv->in_report_received);
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> +
> + ret = hid_hw_output_report(priv->hdev, priv->buf, ARCTIC_REPORT_LEN);
> + if (ret < 0)
> + return ret;
> +
> + t = wait_for_completion_interruptible_timeout(&priv->in_report_received,
> + msecs_to_jiffies(ARCTIC_ACK_TIMEOUT_MS));
> + if (t < 0)
> + return t; /* interrupted by signal */
> + if (!t)
> + return -ETIMEDOUT;
> + return priv->ack_status; /* 0=OK, -EIO=device error */
> +}
> +
> +static const struct hwmon_ops arctic_fan_ops = {
> + .is_visible = arctic_fan_is_visible,
> + .read = arctic_fan_read,
> + .write = arctic_fan_write,
> +};
> +
> +static const struct hwmon_channel_info *arctic_fan_info[] = {
> + HWMON_CHANNEL_INFO(fan,
> + HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT,
> + HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT,
> + HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT,
> + HWMON_F_INPUT),
> + HWMON_CHANNEL_INFO(pwm,
> + HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT,
> + HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT,
> + HWMON_PWM_INPUT, HWMON_PWM_INPUT, HWMON_PWM_INPUT,
> + HWMON_PWM_INPUT),
> + NULL
> +};
> +
> +static const struct hwmon_chip_info arctic_fan_chip_info = {
> + .ops = &arctic_fan_ops,
> + .info = arctic_fan_info,
> +};
> +
> +static void arctic_fan_hw_stop(void *data)
> +{
> + struct hid_device *hdev = data;
> +
> + hid_hw_close(hdev);
> + hid_hw_stop(hdev);
> +}
> +
> +static int arctic_fan_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + struct arctic_fan_data *priv;
> + struct device *hwmon_dev;
> + int ret;
> +
> + if (!hid_is_usb(hdev))
> + return -ENODEV;
> +
> + ret = hid_parse(hdev);
> + if (ret)
> + return ret;
> +
> + priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->hdev = hdev;
> + spin_lock_init(&priv->in_report_lock);
> + init_completion(&priv->in_report_received);
> + hid_set_drvdata(hdev, priv);
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_DRIVER);
> + if (ret)
> + return ret;
> +
> + ret = hid_hw_open(hdev);
> + if (ret) {
> + hid_hw_stop(hdev);
> + return ret;
> + }
> +
> + /*
> + * Register hardware teardown before hwmon so that devm cleanup runs in
> + * LIFO order: hwmon unregistered first, then hid_hw_close/stop. This
> + * ensures no userspace sysfs write can reach an already stopped device.
> + */
> + ret = devm_add_action_or_reset(&hdev->dev, arctic_fan_hw_stop, hdev);
> + if (ret)
> + return ret;
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(&hdev->dev, "arctic_fan",
> + priv, &arctic_fan_chip_info,
> + NULL);
> + if (IS_ERR(hwmon_dev))
> + return PTR_ERR(hwmon_dev);
> +
> + hid_device_io_start(hdev);
> + return 0;
> +}
> +
> +static void arctic_fan_remove(struct hid_device *hdev)
> +{
> + /* devm cleanup (LIFO) handles hid_hw_close/stop after hwmon unregistration */
> +}
> +
> +static const struct hid_device_id arctic_fan_id_table[] = {
> + { HID_USB_DEVICE(ARCTIC_VID, ARCTIC_PID) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(hid, arctic_fan_id_table);
> +
> +static struct hid_driver arctic_fan_driver = {
> + .name = "arctic_fan",
> + .id_table = arctic_fan_id_table,
> + .probe = arctic_fan_probe,
> + .remove = arctic_fan_remove,
> + .raw_event = arctic_fan_raw_event,
> +};
> +
> +module_hid_driver(arctic_fan_driver);
> +
> +MODULE_AUTHOR("Aureo Serrano de Souza <aureo.serrano@arctic.de>");
> +MODULE_DESCRIPTION("HID hwmon driver for ARCTIC Fan Controller");
> +MODULE_LICENSE("GPL");
^ permalink raw reply
* Re: [PATCH v4 09/21] mm: swap: allocate a virtual swap slot for each swapped out page
From: Nhat Pham @ 2026-03-19 19:26 UTC (permalink / raw)
To: Peter Zijlstra
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, pfalcato, rafael, rakie.kim,
roman.gushchin, rppt, ryan.roberts, shakeel.butt, shikemeng,
surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed, yuanchu,
zhengqi.arch, ziy, kernel-team, riel
In-Reply-To: <CAKEwX=MUrLtZAcmwqBau5GLnWQrjL7A_4tYrdZ4TQQaE+hsVkA@mail.gmail.com>
On Thu, Mar 19, 2026 at 11:37 AM Nhat Pham <nphamcs@gmail.com> wrote:
>
> On Thu, Mar 19, 2026 at 12:56 AM Peter Zijlstra <peterz@infradead.org> wrote:
> >
> > On Wed, Mar 18, 2026 at 03:29:40PM -0700, Nhat Pham wrote:
> > > diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> > > index 62cd7b35a29c9..85cb45022e796 100644
> > > --- a/include/linux/cpuhotplug.h
> > > +++ b/include/linux/cpuhotplug.h
> > > @@ -86,6 +86,7 @@ enum cpuhp_state {
> > > CPUHP_FS_BUFF_DEAD,
> > > CPUHP_PRINTK_DEAD,
> > > CPUHP_MM_MEMCQ_DEAD,
> > > + CPUHP_MM_VSWAP_DEAD,
> > > CPUHP_PERCPU_CNT_DEAD,
> > > CPUHP_RADIX_DEAD,
> > > CPUHP_PAGE_ALLOC,
> >
> > > +static int vswap_cpu_dead(unsigned int cpu)
> > > +{
> > > + struct vswap_cluster *cluster;
> > > + int order;
> > > +
> > > + rcu_read_lock();
> >
> > nit:
> > guard(rcu)();
> >
> > > + for (order = 0; order < SWAP_NR_ORDERS; order++) {
> > > + cluster = per_cpu(percpu_vswap_cluster.clusters[order], cpu);
> > > + if (cluster) {
> > > + per_cpu(percpu_vswap_cluster.clusters[order], cpu) = NULL;
> > > + spin_lock(&cluster->lock);
> >
> > This breaks on PREEMPT_RT as this is ran with IRQs disabled. This must
> > be a raw_spinlock_t.
> >
> > > + cluster->cached = false;
> > > + if (refcount_dec_and_test(&cluster->refcnt))
> > > + vswap_cluster_free(cluster);
> >
> > And this... below.
> >
> > > + spin_unlock(&cluster->lock);
> > > + }
> > > + }
> > > + rcu_read_unlock();
> > > +
> > > + return 0;
> > > +}
> >
> > > +static void vswap_cluster_free(struct vswap_cluster *cluster)
> > > +{
> > > + VM_WARN_ON(cluster->count || cluster->cached);
> > > + VM_WARN_ON(!spin_is_locked(&cluster->lock));
> >
> > This is terrible, please use:
> >
> > lockdep_assert_held(&cluster->lock);
> >
> > > + xa_lock(&vswap_cluster_map);
> >
> > This is again broken, this cannot be from a DEAD callback with IRQs
> > disabled.
> >
> > > + list_del_init(&cluster->list);
> > > + __xa_erase(&vswap_cluster_map, cluster->id);
> >
> > Strictly speaking this can end up in xas_alloc(), which is again, not
> > allowed in a DEAD callback.
>
> I see. I'll take a look at this. Thanks for pointing this out, Peter!
Hmm seems like we can just defer-free on the cpu_dead path, and that
should be safe?
Right now, if a cluster is cached on a CPU, we know that it's not
cached on any other CPU, and it's not on any other partial lists.
Maybe can call_rcu() here to defer-free it. Hopefully cpu dead is rare
enough of an event that we dont have a backlog of free deferrals :)
The other alternative is workqueue (with some careful rcu handling in
the free callback), but that seems unnecessary.
^ permalink raw reply
* Re: [Intel-wired-lan] [PATCH net-next v2 02/13] wifi: cfg80211: use __rtnl_unlock in nl80211_pre_doit
From: kernel test robot @ 2026-03-19 19:22 UTC (permalink / raw)
To: Stanislav Fomichev, netdev
Cc: llvm, oe-kbuild-all, davem, edumazet, kuba, pabeni, horms, corbet,
skhan, andrew+netdev, michael.chan, pavan.chebbi,
anthony.l.nguyen, przemyslaw.kitszel, saeedm, tariqt, mbloch,
alexanderduyck, kernel-team, johannes, sd, jianbol, dtatulea, sdf,
mohsin.bashr, jacob.e.keller, willemb, skhawaja, bestswngs,
linux-doc, linux-kernel
In-Reply-To: <20260318150305.123900-3-sdf@fomichev.me>
Hi Stanislav,
kernel test robot noticed the following build errors:
[auto build test ERROR on net-next/main]
url: https://github.com/intel-lab-lkp/linux/commits/Stanislav-Fomichev/net-add-address-list-snapshot-and-reconciliation-infrastructure/20260319-122535
base: net-next/main
patch link: https://lore.kernel.org/r/20260318150305.123900-3-sdf%40fomichev.me
patch subject: [Intel-wired-lan] [PATCH net-next v2 02/13] wifi: cfg80211: use __rtnl_unlock in nl80211_pre_doit
config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260320/202603200304.JZwA4SBN-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260320/202603200304.JZwA4SBN-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603200304.JZwA4SBN-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: "__rtnl_unlock" [net/wireless/cfg80211.ko] undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v3 0/5] x86: Enable LASS support with vsyscall=xonly mode
From: Sohil Mehta @ 2026-03-19 18:46 UTC (permalink / raw)
To: Dave Hansen, x86, Andy Lutomirski, Borislav Petkov
Cc: Jonathan Corbet, Shuah Khan, Thomas Gleixner, Ingo Molnar,
H . Peter Anvin, Peter Zijlstra, Kiryl Shutsemau, Brendan Jackman,
Sean Christopherson, Nam Cao, Cedric Xing, Rick Edgecombe,
Andrew Cooper, Tony Luck, Alexander Shishkin,
Maciej Wieczor-Retman, linux-doc, linux-kernel
In-Reply-To: <20260309181029.398498-1-sohil.mehta@intel.com>
On 3/9/2026 11:10 AM, Sohil Mehta wrote:
> Linear Address Space Separation (LASS) is currently disabled [1] when
> support for vsyscall emulation is configured. This series extends LASS
> support specifically to the default mode (vsyscall=xonly).
>
x86 maintainers, this series seems to be ready to me. Are there any
additional changes you are looking for?
There are a couple of small vsyscall related improvements that are based
on this series. They are completely independent of LASS, so I haven't
included them here.
https://lore.kernel.org/lkml/20260305232136.224922-1-hpa@zytor.com/
https://lore.kernel.org/lkml/51f5dc8d-e130-4769-84e2-588553c7fde3@intel.com/
Please let me know if you would prefer that I include them at the end
and send out another version.
> Sohil Mehta (5):
> x86/vsyscall: Reorganize the page fault emulation code
> x86/traps: Consolidate user fixups in the #GP handler
> x86/vsyscall: Restore vsyscall=xonly mode under LASS
> x86/vsyscall: Disable LASS if vsyscall mode is set to EMULATE
> x86/cpu: Remove LASS restriction on vsyscall emulation
>
> .../admin-guide/kernel-parameters.txt | 4 +-
> arch/x86/entry/vsyscall/vsyscall_64.c | 91 ++++++++++++-------
> arch/x86/include/asm/vsyscall.h | 13 ++-
> arch/x86/kernel/cpu/common.c | 15 ---
> arch/x86/kernel/traps.c | 12 ++-
> arch/x86/kernel/umip.c | 3 +
> arch/x86/mm/fault.c | 2 +-
> 7 files changed, 79 insertions(+), 61 deletions(-)
>
>
> base-commit: 68400c1aaf02636a97c45ba198110b66feb270a9
^ permalink raw reply
* Re: [PATCH v4 09/21] mm: swap: allocate a virtual swap slot for each swapped out page
From: Nhat Pham @ 2026-03-19 18:37 UTC (permalink / raw)
To: Peter Zijlstra
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, pfalcato, rafael, rakie.kim,
roman.gushchin, rppt, ryan.roberts, shakeel.butt, shikemeng,
surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed, yuanchu,
zhengqi.arch, ziy, kernel-team, riel
In-Reply-To: <20260319075621.GR3738010@noisy.programming.kicks-ass.net>
On Thu, Mar 19, 2026 at 12:56 AM Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Wed, Mar 18, 2026 at 03:29:40PM -0700, Nhat Pham wrote:
> > diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
> > index 62cd7b35a29c9..85cb45022e796 100644
> > --- a/include/linux/cpuhotplug.h
> > +++ b/include/linux/cpuhotplug.h
> > @@ -86,6 +86,7 @@ enum cpuhp_state {
> > CPUHP_FS_BUFF_DEAD,
> > CPUHP_PRINTK_DEAD,
> > CPUHP_MM_MEMCQ_DEAD,
> > + CPUHP_MM_VSWAP_DEAD,
> > CPUHP_PERCPU_CNT_DEAD,
> > CPUHP_RADIX_DEAD,
> > CPUHP_PAGE_ALLOC,
>
> > +static int vswap_cpu_dead(unsigned int cpu)
> > +{
> > + struct vswap_cluster *cluster;
> > + int order;
> > +
> > + rcu_read_lock();
>
> nit:
> guard(rcu)();
>
> > + for (order = 0; order < SWAP_NR_ORDERS; order++) {
> > + cluster = per_cpu(percpu_vswap_cluster.clusters[order], cpu);
> > + if (cluster) {
> > + per_cpu(percpu_vswap_cluster.clusters[order], cpu) = NULL;
> > + spin_lock(&cluster->lock);
>
> This breaks on PREEMPT_RT as this is ran with IRQs disabled. This must
> be a raw_spinlock_t.
>
> > + cluster->cached = false;
> > + if (refcount_dec_and_test(&cluster->refcnt))
> > + vswap_cluster_free(cluster);
>
> And this... below.
>
> > + spin_unlock(&cluster->lock);
> > + }
> > + }
> > + rcu_read_unlock();
> > +
> > + return 0;
> > +}
>
> > +static void vswap_cluster_free(struct vswap_cluster *cluster)
> > +{
> > + VM_WARN_ON(cluster->count || cluster->cached);
> > + VM_WARN_ON(!spin_is_locked(&cluster->lock));
>
> This is terrible, please use:
>
> lockdep_assert_held(&cluster->lock);
>
> > + xa_lock(&vswap_cluster_map);
>
> This is again broken, this cannot be from a DEAD callback with IRQs
> disabled.
>
> > + list_del_init(&cluster->list);
> > + __xa_erase(&vswap_cluster_map, cluster->id);
>
> Strictly speaking this can end up in xas_alloc(), which is again, not
> allowed in a DEAD callback.
I see. I'll take a look at this. Thanks for pointing this out, Peter!
^ permalink raw reply
* Re: [PATCH v9 7/7] PCI/TPH: Fix get cpu steer-tag fail on ARM64 platform
From: Bjorn Helgaas @ 2026-03-19 18:32 UTC (permalink / raw)
To: Chengwen Feng
Cc: Bjorn Helgaas, Catalin Marinas, Will Deacon, Rafael J . Wysocki,
Jonathan Corbet, Shuah Khan, Huacai Chen, WANG Xuerui,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H . Peter Anvin, Juergen Gross, Boris Ostrovsky, Len Brown,
Sunil V L, Mark Rutland, Jonathan Cameron, Kees Cook, Yanteng Si,
Sean Christopherson, Kai Huang, Tom Lendacky, Thomas Huth,
Thorsten Blum, Kevin Loughlin, Zheyun Shen, Peter Zijlstra,
Pawan Gupta, Xin Li, Ahmed S . Darwish, Sohil Mehta,
Ilkka Koskinen, Robin Murphy, James Clark, Besar Wicaksono, Ma Ke,
Wei Huang, Andy Gospodarek, Somnath Kotur, punit.agrawal,
guohanjun, suzuki.poulose, ryan.roberts, chenl311, masahiroy,
wangyuquan1236, anshuman.khandual, heinrich.schuchardt,
Eric.VanTassell, wangzhou1, wanghuiqiang, liuyonglong, linux-pci,
linux-doc, linux-kernel, linux-arm-kernel, loongarch, linux-riscv,
xen-devel, linux-acpi, linux-perf-users, stable
In-Reply-To: <20260319065735.45954-8-fengchengwen@huawei.com>
Let's make the subject a little more specific, e.g.,
PCI/TPH: Pass ACPI Processor UID to Cache Locality _DSM
On Thu, Mar 19, 2026 at 02:57:35PM +0800, Chengwen Feng wrote:
> pcie_tph_get_cpu_st() is broken on ARM64:
> 1. pcie_tph_get_cpu_st() passes cpu_uid to the PCI ACPI DSM method.
> cpu_uid should be the ACPI Processor UID [1].
> 2. In BNXT, pcie_tph_get_cpu_st() is passed a cpu_uid obtained via
> cpumask_first(irq->cpu_mask) - the logical CPU ID of a CPU core,
> generated and managed by kernel (e.g., [0,255] for a system with 256
> logical CPU cores).
> 3. On ARM64 platforms, ACPI assigns Processor UID to cores listed in the
> MADT table, and this UID may not match the kernel's logical CPU ID.
> When this occurs, the mismatch results in the wrong CPU steer-tag.
> 4. On AMD x86 the logical CPU ID is identical to the ACPI Processor UID
> so the mismatch is not seen.
>
> Resolution:
> 1. Use acpi_get_cpu_uid() in pcie_tph_get_cpu_st() to translate from
> logical CPU ID to ACPI Processor UID needed for the DSM call.
> 2. Rename pcie_tpu_get_cpu_st() parameter from cpu_uid to cpu to
> reflect that it is a logical CPU_ID.
And simplify this, e.g.,
pcie_tph_get_cpu_st() uses the Query Cache Locality Features _DSM
[1] to retrieve the TPH Steering Tag for memory associated with the
CPU identified by its "cpu_uid" parameter, a Linux logical CPU ID.
The _DSM requires a ACPI Processor UID, which pcie_tph_get_cpu_st()
previously assumed was the same as the Linux logical CPU ID. This
is true on x86 but not on arm64, so pcie_tph_get_cpu_st() failed on
arm64.
Convert the Linux logical CPU ID to the ACPI Processor UID with
acpi_get_cpu_uid() before passing it to the _DSM.
If there's a specific error message from BNXT or similar that is a
symptom of this problem, it'd be nice to include that. Or if the
problem was just poor performance because the _DSM returned the wrong
Steering Tag, you could say something like:
... so pcie_tph_get_cpu_st() returned the wrong Steering Tag,
resulting in poor network performance.
> [1] According to ECN_TPH-ST_Revision_20200924
> (https://members.pcisig.com/wg/PCI-SIG/document/15470), the input
> is defined as: "If the target is a processor, then this field
> represents the ACPI Processor UID of the processor as specified in
> the MADT. If the target is a processor container, then this field
> represents the ACPI Processor UID of the processor container as
> specified in the PPTT."
>
> Fixes: d2e8a34876ce ("PCI/TPH: Add Steering Tag support")
> Cc: stable@vger.kernel.org
> Signed-off-by: Chengwen Feng <fengchengwen@huawei.com>
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
The patch looks good to me. Thanks for all your work on this!
Reviewed-by: Bjorn Helgaas <bhelgaas@google.com>
> ---
> Documentation/PCI/tph.rst | 4 ++--
> drivers/pci/tph.c | 16 +++++++++++-----
> include/linux/pci-tph.h | 4 ++--
> 3 files changed, 15 insertions(+), 9 deletions(-)
>
> diff --git a/Documentation/PCI/tph.rst b/Documentation/PCI/tph.rst
> index e8993be64fd6..b6cf22b9bd90 100644
> --- a/Documentation/PCI/tph.rst
> +++ b/Documentation/PCI/tph.rst
> @@ -79,10 +79,10 @@ To retrieve a Steering Tag for a target memory associated with a specific
> CPU, use the following function::
>
> int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type type,
> - unsigned int cpu_uid, u16 *tag);
> + unsigned int cpu, u16 *tag);
>
> The `type` argument is used to specify the memory type, either volatile
> -or persistent, of the target memory. The `cpu_uid` argument specifies the
> +or persistent, of the target memory. The `cpu` argument specifies the
> CPU where the memory is associated to.
>
> After the ST value is retrieved, the device driver can use the following
> diff --git a/drivers/pci/tph.c b/drivers/pci/tph.c
> index ca4f97be7538..b67c9ad14bda 100644
> --- a/drivers/pci/tph.c
> +++ b/drivers/pci/tph.c
> @@ -236,21 +236,27 @@ static int write_tag_to_st_table(struct pci_dev *pdev, int index, u16 tag)
> * with a specific CPU
> * @pdev: PCI device
> * @mem_type: target memory type (volatile or persistent RAM)
> - * @cpu_uid: associated CPU id
> + * @cpu: associated CPU id
> * @tag: Steering Tag to be returned
> *
> * Return the Steering Tag for a target memory that is associated with a
> - * specific CPU as indicated by cpu_uid.
> + * specific CPU as indicated by cpu.
> *
> * Return: 0 if success, otherwise negative value (-errno)
> */
> int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type mem_type,
> - unsigned int cpu_uid, u16 *tag)
> + unsigned int cpu, u16 *tag)
> {
> #ifdef CONFIG_ACPI
> struct pci_dev *rp;
> acpi_handle rp_acpi_handle;
> union st_info info;
> + u32 cpu_uid;
> + int ret;
> +
> + ret = acpi_get_cpu_uid(cpu, &cpu_uid);
> + if (ret != 0)
> + return ret;
>
> rp = pcie_find_root_port(pdev);
> if (!rp || !rp->bus || !rp->bus->bridge)
> @@ -265,9 +271,9 @@ int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type mem_type,
>
> *tag = tph_extract_tag(mem_type, pdev->tph_req_type, &info);
>
> - pci_dbg(pdev, "get steering tag: mem_type=%s, cpu_uid=%d, tag=%#04x\n",
> + pci_dbg(pdev, "get steering tag: mem_type=%s, cpu=%d, tag=%#04x\n",
> (mem_type == TPH_MEM_TYPE_VM) ? "volatile" : "persistent",
> - cpu_uid, *tag);
> + cpu, *tag);
>
> return 0;
> #else
> diff --git a/include/linux/pci-tph.h b/include/linux/pci-tph.h
> index ba28140ce670..be68cd17f2f8 100644
> --- a/include/linux/pci-tph.h
> +++ b/include/linux/pci-tph.h
> @@ -25,7 +25,7 @@ int pcie_tph_set_st_entry(struct pci_dev *pdev,
> unsigned int index, u16 tag);
> int pcie_tph_get_cpu_st(struct pci_dev *dev,
> enum tph_mem_type mem_type,
> - unsigned int cpu_uid, u16 *tag);
> + unsigned int cpu, u16 *tag);
> void pcie_disable_tph(struct pci_dev *pdev);
> int pcie_enable_tph(struct pci_dev *pdev, int mode);
> u16 pcie_tph_get_st_table_size(struct pci_dev *pdev);
> @@ -36,7 +36,7 @@ static inline int pcie_tph_set_st_entry(struct pci_dev *pdev,
> { return -EINVAL; }
> static inline int pcie_tph_get_cpu_st(struct pci_dev *dev,
> enum tph_mem_type mem_type,
> - unsigned int cpu_uid, u16 *tag)
> + unsigned int cpu, u16 *tag)
> { return -EINVAL; }
> static inline void pcie_disable_tph(struct pci_dev *pdev) { }
> static inline int pcie_enable_tph(struct pci_dev *pdev, int mode)
> --
> 2.17.1
>
^ permalink raw reply
* [PATCH v3 16/16] mm: on remap assert that input range within the proposed VMA
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
Now we have range_in_vma_desc(), update remap_pfn_range_prepare() to check
whether the input range in contained within the specified VMA, so we can
fail at prepare time if an invalid range is specified.
This covers the I/O remap mmap actions also which ultimately call into
this function, and other mmap action types either already span the full
VMA or check this already.
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
mm/memory.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/mm/memory.c b/mm/memory.c
index 53ef8ef3d04a..68cc592ff0ba 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3142,6 +3142,9 @@ int remap_pfn_range_prepare(struct vm_area_desc *desc)
const bool is_cow = vma_desc_is_cow_mapping(desc);
int err;
+ if (!range_in_vma_desc(desc, start, end))
+ return -EFAULT;
+
err = get_remap_pgoff(is_cow, start, end, desc->start, desc->end, pfn,
&desc->pgoff);
if (err)
--
2.53.0
^ permalink raw reply related
* [PATCH v3 15/16] mm: add mmap_action_map_kernel_pages[_full]()
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
A user can invoke mmap_action_map_kernel_pages() to specify that the
mapping should map kernel pages starting from desc->start of a specified
number of pages specified in an array.
In order to implement this, adjust mmap_action_prepare() to be able to
return an error code, as it makes sense to assert that the specified
parameters are valid as quickly as possible as well as updating the VMA
flags to include VMA_MIXEDMAP_BIT as necessary.
This provides an mmap_prepare equivalent of vm_insert_pages(). We
additionally update the existing vm_insert_pages() code to use
range_in_vma() and add a new range_in_vma_desc() helper function for the
mmap_prepare case, sharing the code between the two in range_is_subset().
We add both mmap_action_map_kernel_pages() and
mmap_action_map_kernel_pages_full() to allow for both partial and full VMA
mappings.
We update the documentation to reflect the new features.
Finally, we update the VMA tests accordingly to reflect the changes.
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
Documentation/filesystems/mmap_prepare.rst | 8 ++
include/linux/mm.h | 95 +++++++++++++++++++++-
include/linux/mm_types.h | 7 ++
mm/memory.c | 42 +++++++++-
mm/util.c | 6 ++
tools/testing/vma/include/dup.h | 7 ++
6 files changed, 159 insertions(+), 6 deletions(-)
diff --git a/Documentation/filesystems/mmap_prepare.rst b/Documentation/filesystems/mmap_prepare.rst
index be76ae475b9c..e810aa4134eb 100644
--- a/Documentation/filesystems/mmap_prepare.rst
+++ b/Documentation/filesystems/mmap_prepare.rst
@@ -156,5 +156,13 @@ pointer. These are:
* mmap_action_simple_ioremap() - Sets up an I/O remap from a specified
physical address and over a specified length.
+* mmap_action_map_kernel_pages() - Maps a specified array of `struct page`
+ pointers in the VMA from a specific offset.
+
+* mmap_action_map_kernel_pages_full() - Maps a specified array of `struct
+ page` pointers over the entire VMA. The caller must ensure there are
+ sufficient entries in the page array to cover the entire range of the
+ described VMA.
+
**NOTE:** The ``action`` field should never normally be manipulated directly,
rather you ought to use one of these helpers.
diff --git a/include/linux/mm.h b/include/linux/mm.h
index ef2e4dccfe8e..8aadf115278e 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2912,7 +2912,7 @@ static inline bool folio_maybe_mapped_shared(struct folio *folio)
* The caller must add any reference (e.g., from folio_try_get()) it might be
* holding itself to the result.
*
- * Returns the expected folio refcount.
+ * Returns: the expected folio refcount.
*/
static inline int folio_expected_ref_count(const struct folio *folio)
{
@@ -4364,6 +4364,45 @@ static inline void mmap_action_simple_ioremap(struct vm_area_desc *desc,
action->type = MMAP_SIMPLE_IO_REMAP;
}
+/**
+ * mmap_action_map_kernel_pages - helper for mmap_prepare hook to specify that
+ * @num kernel pages contained in the @pages array should be mapped to userland
+ * starting at virtual address @start.
+ * @desc: The VMA descriptor for the VMA requiring kernel pags to be mapped.
+ * @start: The virtual address from which to map them.
+ * @pages: An array of struct page pointers describing the memory to map.
+ * @nr_pages: The number of entries in the @pages aray.
+ */
+static inline void mmap_action_map_kernel_pages(struct vm_area_desc *desc,
+ unsigned long start, struct page **pages,
+ unsigned long nr_pages)
+{
+ struct mmap_action *action = &desc->action;
+
+ action->type = MMAP_MAP_KERNEL_PAGES;
+ action->map_kernel.start = start;
+ action->map_kernel.pages = pages;
+ action->map_kernel.nr_pages = nr_pages;
+ action->map_kernel.pgoff = desc->pgoff;
+}
+
+/**
+ * mmap_action_map_kernel_pages_full - helper for mmap_prepare hook to specify that
+ * kernel pages contained in the @pages array should be mapped to userland
+ * from @desc->start to @desc->end.
+ * @desc: The VMA descriptor for the VMA requiring kernel pags to be mapped.
+ * @pages: An array of struct page pointers describing the memory to map.
+ *
+ * The caller must ensure that @pages contains sufficient entries to cover the
+ * entire range described by @desc.
+ */
+static inline void mmap_action_map_kernel_pages_full(struct vm_area_desc *desc,
+ struct page **pages)
+{
+ mmap_action_map_kernel_pages(desc, desc->start, pages,
+ vma_desc_pages(desc));
+}
+
int mmap_action_prepare(struct vm_area_desc *desc);
int mmap_action_complete(struct vm_area_struct *vma,
struct mmap_action *action,
@@ -4381,10 +4420,59 @@ static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm,
return vma;
}
+/**
+ * range_is_subset - Is the specified inner range a subset of the outer range?
+ * @outer_start: The start of the outer range.
+ * @outer_end: The exclusive end of the outer range.
+ * @inner_start: The start of the inner range.
+ * @inner_end: The exclusive end of the inner range.
+ *
+ * Returns: %true if [inner_start, inner_end) is a subset of [outer_start,
+ * outer_end), otherwise %false.
+ */
+static inline bool range_is_subset(unsigned long outer_start,
+ unsigned long outer_end,
+ unsigned long inner_start,
+ unsigned long inner_end)
+{
+ return outer_start <= inner_start && inner_end <= outer_end;
+}
+
+/**
+ * range_in_vma - is the specified [@start, @end) range a subset of the VMA?
+ * @vma: The VMA against which we want to check [@start, @end).
+ * @start: The start of the range we wish to check.
+ * @end: The exclusive end of the range we wish to check.
+ *
+ * Returns: %true if [@start, @end) is a subset of [@vma->vm_start,
+ * @vma->vm_end), %false otherwise.
+ */
static inline bool range_in_vma(const struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
- return (vma && vma->vm_start <= start && end <= vma->vm_end);
+ if (!vma)
+ return false;
+
+ return range_is_subset(vma->vm_start, vma->vm_end, start, end);
+}
+
+/**
+ * range_in_vma_desc - is the specified [@start, @end) range a subset of the VMA
+ * described by @desc, a VMA descriptor?
+ * @desc: The VMA descriptor against which we want to check [@start, @end).
+ * @start: The start of the range we wish to check.
+ * @end: The exclusive end of the range we wish to check.
+ *
+ * Returns: %true if [@start, @end) is a subset of [@desc->start, @desc->end),
+ * %false otherwise.
+ */
+static inline bool range_in_vma_desc(const struct vm_area_desc *desc,
+ unsigned long start, unsigned long end)
+{
+ if (!desc)
+ return false;
+
+ return range_is_subset(desc->start, desc->end, start, end);
}
#ifdef CONFIG_MMU
@@ -4428,6 +4516,9 @@ int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
int vm_insert_page(struct vm_area_struct *, unsigned long addr, struct page *);
int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
struct page **pages, unsigned long *num);
+int map_kernel_pages_prepare(struct vm_area_desc *desc);
+int map_kernel_pages_complete(struct vm_area_struct *vma,
+ struct mmap_action *action);
int vm_map_pages(struct vm_area_struct *vma, struct page **pages,
unsigned long num);
int vm_map_pages_zero(struct vm_area_struct *vma, struct page **pages,
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 7538d64f8848..c46224020a46 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -815,6 +815,7 @@ enum mmap_action_type {
MMAP_REMAP_PFN, /* Remap PFN range. */
MMAP_IO_REMAP_PFN, /* I/O remap PFN range. */
MMAP_SIMPLE_IO_REMAP, /* I/O remap with guardrails. */
+ MMAP_MAP_KERNEL_PAGES, /* Map kernel page range from array. */
};
/*
@@ -833,6 +834,12 @@ struct mmap_action {
phys_addr_t start_phys_addr;
unsigned long size;
} simple_ioremap;
+ struct {
+ unsigned long start;
+ struct page **pages;
+ unsigned long nr_pages;
+ pgoff_t pgoff;
+ } map_kernel;
};
enum mmap_action_type type;
diff --git a/mm/memory.c b/mm/memory.c
index b3bcc21af20a..53ef8ef3d04a 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2484,13 +2484,14 @@ static int insert_pages(struct vm_area_struct *vma, unsigned long addr,
int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
struct page **pages, unsigned long *num)
{
- const unsigned long end_addr = addr + (*num * PAGE_SIZE) - 1;
+ const unsigned long nr_pages = *num;
+ const unsigned long end = addr + PAGE_SIZE * nr_pages;
- if (addr < vma->vm_start || end_addr >= vma->vm_end)
+ if (!range_in_vma(vma, addr, end))
return -EFAULT;
if (!(vma->vm_flags & VM_MIXEDMAP)) {
- BUG_ON(mmap_read_trylock(vma->vm_mm));
- BUG_ON(vma->vm_flags & VM_PFNMAP);
+ VM_WARN_ON_ONCE(mmap_read_trylock(vma->vm_mm));
+ VM_WARN_ON_ONCE(vma->vm_flags & VM_PFNMAP);
vm_flags_set(vma, VM_MIXEDMAP);
}
/* Defer page refcount checking till we're about to map that page. */
@@ -2498,6 +2499,39 @@ int vm_insert_pages(struct vm_area_struct *vma, unsigned long addr,
}
EXPORT_SYMBOL(vm_insert_pages);
+int map_kernel_pages_prepare(struct vm_area_desc *desc)
+{
+ const struct mmap_action *action = &desc->action;
+ const unsigned long addr = action->map_kernel.start;
+ unsigned long nr_pages, end;
+
+ if (!vma_desc_test(desc, VMA_MIXEDMAP_BIT)) {
+ VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));
+ VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
+ vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
+ }
+
+ nr_pages = action->map_kernel.nr_pages;
+ end = addr + PAGE_SIZE * nr_pages;
+ if (!range_in_vma_desc(desc, addr, end))
+ return -EFAULT;
+
+ return 0;
+}
+EXPORT_SYMBOL(map_kernel_pages_prepare);
+
+int map_kernel_pages_complete(struct vm_area_struct *vma,
+ struct mmap_action *action)
+{
+ unsigned long nr_pages;
+
+ nr_pages = action->map_kernel.nr_pages;
+ return insert_pages(vma, action->map_kernel.start,
+ action->map_kernel.pages,
+ &nr_pages, vma->vm_page_prot);
+}
+EXPORT_SYMBOL(map_kernel_pages_complete);
+
/**
* vm_insert_page - insert single page into user vma
* @vma: user vma to map to
diff --git a/mm/util.c b/mm/util.c
index 8cf59267a9ac..682d0d24e1c6 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -1446,6 +1446,8 @@ int mmap_action_prepare(struct vm_area_desc *desc)
return io_remap_pfn_range_prepare(desc);
case MMAP_SIMPLE_IO_REMAP:
return simple_ioremap_prepare(desc);
+ case MMAP_MAP_KERNEL_PAGES:
+ return map_kernel_pages_prepare(desc);
}
WARN_ON_ONCE(1);
@@ -1476,6 +1478,9 @@ int mmap_action_complete(struct vm_area_struct *vma,
case MMAP_REMAP_PFN:
err = remap_pfn_range_complete(vma, action);
break;
+ case MMAP_MAP_KERNEL_PAGES:
+ err = map_kernel_pages_complete(vma, action);
+ break;
case MMAP_IO_REMAP_PFN:
case MMAP_SIMPLE_IO_REMAP:
/* Should have been delegated. */
@@ -1497,6 +1502,7 @@ int mmap_action_prepare(struct vm_area_desc *desc)
case MMAP_REMAP_PFN:
case MMAP_IO_REMAP_PFN:
case MMAP_SIMPLE_IO_REMAP:
+ case MMAP_MAP_KERNEL_PAGES:
WARN_ON_ONCE(1); /* nommu cannot handle these. */
break;
}
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 1f123704078e..6392e7835f89 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -454,6 +454,7 @@ enum mmap_action_type {
MMAP_REMAP_PFN, /* Remap PFN range. */
MMAP_IO_REMAP_PFN, /* I/O remap PFN range. */
MMAP_SIMPLE_IO_REMAP, /* I/O remap with guardrails. */
+ MMAP_MAP_KERNEL_PAGES, /* Map kernel page range from an array. */
};
/*
@@ -472,6 +473,12 @@ struct mmap_action {
phys_addr_t start_phys_addr;
unsigned long size;
} simple_ioremap;
+ struct {
+ unsigned long start;
+ struct page **pages;
+ unsigned long nr_pages;
+ pgoff_t pgoff;
+ } map_kernel;
};
enum mmap_action_type type;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 14/16] uio: replace deprecated mmap hook with mmap_prepare in uio_info
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
The f_op->mmap interface is deprecated, so update uio_info to use its
successor, mmap_prepare.
Therefore, replace the uio_info->mmap hook with a new
uio_info->mmap_prepare hook, and update its one user, target_core_user,
to both specify this new mmap_prepare hook and also to use the new
vm_ops->mapped() hook to continue to maintain a correct udev->kref
refcount.
Then update uio_mmap() to utilise the mmap_prepare compatibility layer to
invoke this callback from the uio mmap invocation.
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
drivers/target/target_core_user.c | 26 ++++++++++++++++++--------
drivers/uio/uio.c | 10 ++++++++--
include/linux/uio_driver.h | 4 ++--
3 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
index af95531ddd35..edc2afd5f4ee 100644
--- a/drivers/target/target_core_user.c
+++ b/drivers/target/target_core_user.c
@@ -1860,6 +1860,17 @@ static struct page *tcmu_try_get_data_page(struct tcmu_dev *udev, uint32_t dpi)
return NULL;
}
+static int tcmu_vma_mapped(unsigned long start, unsigned long end, pgoff_t pgoff,
+ const struct file *file, void **vm_private_data)
+{
+ struct tcmu_dev *udev = *vm_private_data;
+
+ pr_debug("vma_mapped\n");
+
+ kref_get(&udev->kref);
+ return 0;
+}
+
static void tcmu_vma_open(struct vm_area_struct *vma)
{
struct tcmu_dev *udev = vma->vm_private_data;
@@ -1919,26 +1930,25 @@ static vm_fault_t tcmu_vma_fault(struct vm_fault *vmf)
}
static const struct vm_operations_struct tcmu_vm_ops = {
+ .mapped = tcmu_vma_mapped,
.open = tcmu_vma_open,
.close = tcmu_vma_close,
.fault = tcmu_vma_fault,
};
-static int tcmu_mmap(struct uio_info *info, struct vm_area_struct *vma)
+static int tcmu_mmap_prepare(struct uio_info *info, struct vm_area_desc *desc)
{
struct tcmu_dev *udev = container_of(info, struct tcmu_dev, uio_info);
- vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
- vma->vm_ops = &tcmu_vm_ops;
+ vma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT);
+ desc->vm_ops = &tcmu_vm_ops;
- vma->vm_private_data = udev;
+ desc->private_data = udev;
/* Ensure the mmap is exactly the right size */
- if (vma_pages(vma) != udev->mmap_pages)
+ if (vma_desc_pages(desc) != udev->mmap_pages)
return -EINVAL;
- tcmu_vma_open(vma);
-
return 0;
}
@@ -2253,7 +2263,7 @@ static int tcmu_configure_device(struct se_device *dev)
info->irqcontrol = tcmu_irqcontrol;
info->irq = UIO_IRQ_CUSTOM;
- info->mmap = tcmu_mmap;
+ info->mmap_prepare = tcmu_mmap_prepare;
info->open = tcmu_open;
info->release = tcmu_release;
diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
index 5a4998e2caf8..1e4ade78ed84 100644
--- a/drivers/uio/uio.c
+++ b/drivers/uio/uio.c
@@ -850,8 +850,14 @@ static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
goto out;
}
- if (idev->info->mmap) {
- ret = idev->info->mmap(idev->info, vma);
+ if (idev->info->mmap_prepare) {
+ struct vm_area_desc desc;
+
+ compat_set_desc_from_vma(&desc, filep, vma);
+ ret = idev->info->mmap_prepare(idev->info, &desc);
+ if (ret)
+ goto out;
+ ret = __compat_vma_mmap(&desc, vma);
goto out;
}
diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h
index 334641e20fb1..02eaac47ac44 100644
--- a/include/linux/uio_driver.h
+++ b/include/linux/uio_driver.h
@@ -97,7 +97,7 @@ struct uio_device {
* @irq_flags: flags for request_irq()
* @priv: optional private data
* @handler: the device's irq handler
- * @mmap: mmap operation for this uio device
+ * @mmap_prepare: mmap_prepare operation for this uio device
* @open: open operation for this uio device
* @release: release operation for this uio device
* @irqcontrol: disable/enable irqs when 0/1 is written to /dev/uioX
@@ -112,7 +112,7 @@ struct uio_info {
unsigned long irq_flags;
void *priv;
irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
- int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
+ int (*mmap_prepare)(struct uio_info *info, struct vm_area_desc *desc);
int (*open)(struct uio_info *info, struct inode *inode);
int (*release)(struct uio_info *info, struct inode *inode);
int (*irqcontrol)(struct uio_info *info, s32 irq_on);
--
2.53.0
^ permalink raw reply related
* [PATCH v3 13/16] drivers: hv: vmbus: replace deprecated mmap hook with mmap_prepare
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
The f_op->mmap interface is deprecated, so update the vmbus driver to use
its successor, mmap_prepare.
This updates all callbacks which referenced the function pointer
hv_mmap_ring_buffer to instead reference hv_mmap_prepare_ring_buffer,
utilising the newly introduced compat_set_desc_from_vma() and
__compat_vma_mmap() to be able to implement this change.
The UIO HV generic driver is the only user of hv_create_ring_sysfs(),
which is the only function which references
vmbus_channel->mmap_prepare_ring_buffer which, in turn, is the only
external interface to hv_mmap_prepare_ring_buffer.
This patch therefore updates this caller to use mmap_prepare instead,
which also previously used vm_iomap_memory(), so this change replaces it
with its mmap_prepare equivalent, mmap_action_simple_ioremap().
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
drivers/hv/hyperv_vmbus.h | 4 ++--
drivers/hv/vmbus_drv.c | 31 +++++++++++++++++++------------
drivers/uio/uio_hv_generic.c | 11 ++++++-----
include/linux/hyperv.h | 4 ++--
4 files changed, 29 insertions(+), 21 deletions(-)
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 7bd8f8486e85..31f576464f18 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -545,8 +545,8 @@ static inline int hv_debug_add_dev_dir(struct hv_device *dev)
/* Create and remove sysfs entry for memory mapped ring buffers for a channel */
int hv_create_ring_sysfs(struct vmbus_channel *channel,
- int (*hv_mmap_ring_buffer)(struct vmbus_channel *channel,
- struct vm_area_struct *vma));
+ int (*hv_mmap_prepare_ring_buffer)(struct vmbus_channel *channel,
+ struct vm_area_desc *desc));
int hv_remove_ring_sysfs(struct vmbus_channel *channel);
#endif /* _HYPERV_VMBUS_H */
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index bc4fc1951ae1..45625487ba36 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -1951,12 +1951,19 @@ static int hv_mmap_ring_buffer_wrapper(struct file *filp, struct kobject *kobj,
struct vm_area_struct *vma)
{
struct vmbus_channel *channel = container_of(kobj, struct vmbus_channel, kobj);
+ struct vm_area_desc desc;
+ int err;
/*
- * hv_(create|remove)_ring_sysfs implementation ensures that mmap_ring_buffer
- * is not NULL.
+ * hv_(create|remove)_ring_sysfs implementation ensures that
+ * mmap_prepare_ring_buffer is not NULL.
*/
- return channel->mmap_ring_buffer(channel, vma);
+ compat_set_desc_from_vma(&desc, filp, vma);
+ err = channel->mmap_prepare_ring_buffer(channel, &desc);
+ if (err)
+ return err;
+
+ return __compat_vma_mmap(&desc, vma);
}
static struct bin_attribute chan_attr_ring_buffer = {
@@ -2048,13 +2055,13 @@ static const struct kobj_type vmbus_chan_ktype = {
/**
* hv_create_ring_sysfs() - create "ring" sysfs entry corresponding to ring buffers for a channel.
* @channel: Pointer to vmbus_channel structure
- * @hv_mmap_ring_buffer: function pointer for initializing the function to be called on mmap of
+ * @hv_mmap_prepare_ring_buffer: function pointer for initializing the function to be called on mmap
* channel's "ring" sysfs node, which is for the ring buffer of that channel.
* Function pointer is of below type:
- * int (*hv_mmap_ring_buffer)(struct vmbus_channel *channel,
- * struct vm_area_struct *vma))
- * This has a pointer to the channel and a pointer to vm_area_struct,
- * used for mmap, as arguments.
+ * int (*hv_mmap_prepare_ring_buffer)(struct vmbus_channel *channel,
+ * struct vm_area_desc *desc))
+ * This has a pointer to the channel and a pointer to vm_area_desc,
+ * used for mmap_prepare, as arguments.
*
* Sysfs node for ring buffer of a channel is created along with other fields, however its
* visibility is disabled by default. Sysfs creation needs to be controlled when the use-case
@@ -2071,12 +2078,12 @@ static const struct kobj_type vmbus_chan_ktype = {
* Returns 0 on success or error code on failure.
*/
int hv_create_ring_sysfs(struct vmbus_channel *channel,
- int (*hv_mmap_ring_buffer)(struct vmbus_channel *channel,
- struct vm_area_struct *vma))
+ int (*hv_mmap_prepare_ring_buffer)(struct vmbus_channel *channel,
+ struct vm_area_desc *desc))
{
struct kobject *kobj = &channel->kobj;
- channel->mmap_ring_buffer = hv_mmap_ring_buffer;
+ channel->mmap_prepare_ring_buffer = hv_mmap_prepare_ring_buffer;
channel->ring_sysfs_visible = true;
return sysfs_update_group(kobj, &vmbus_chan_group);
@@ -2098,7 +2105,7 @@ int hv_remove_ring_sysfs(struct vmbus_channel *channel)
channel->ring_sysfs_visible = false;
ret = sysfs_update_group(kobj, &vmbus_chan_group);
- channel->mmap_ring_buffer = NULL;
+ channel->mmap_prepare_ring_buffer = NULL;
return ret;
}
EXPORT_SYMBOL_GPL(hv_remove_ring_sysfs);
diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c
index 3f8e2e27697f..29ec2d15ada8 100644
--- a/drivers/uio/uio_hv_generic.c
+++ b/drivers/uio/uio_hv_generic.c
@@ -154,15 +154,16 @@ static void hv_uio_rescind(struct vmbus_channel *channel)
* The ring buffer is allocated as contiguous memory by vmbus_open
*/
static int
-hv_uio_ring_mmap(struct vmbus_channel *channel, struct vm_area_struct *vma)
+hv_uio_ring_mmap_prepare(struct vmbus_channel *channel, struct vm_area_desc *desc)
{
void *ring_buffer = page_address(channel->ringbuffer_page);
if (channel->state != CHANNEL_OPENED_STATE)
return -ENODEV;
- return vm_iomap_memory(vma, virt_to_phys(ring_buffer),
- channel->ringbuffer_pagecount << PAGE_SHIFT);
+ mmap_action_simple_ioremap(desc, virt_to_phys(ring_buffer),
+ channel->ringbuffer_pagecount << PAGE_SHIFT);
+ return 0;
}
/* Callback from VMBUS subsystem when new channel created. */
@@ -183,7 +184,7 @@ hv_uio_new_channel(struct vmbus_channel *new_sc)
}
set_channel_read_mode(new_sc, HV_CALL_ISR);
- ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap);
+ ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap_prepare);
if (ret) {
dev_err(device, "sysfs create ring bin file failed; %d\n", ret);
vmbus_close(new_sc);
@@ -366,7 +367,7 @@ hv_uio_probe(struct hv_device *dev,
* or decoupled from uio_hv_generic probe. Userspace programs can make use of inotify
* APIs to make sure that ring is created.
*/
- hv_create_ring_sysfs(channel, hv_uio_ring_mmap);
+ hv_create_ring_sysfs(channel, hv_uio_ring_mmap_prepare);
hv_set_drvdata(dev, pdata);
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index dfc516c1c719..3a721b1853a4 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1015,8 +1015,8 @@ struct vmbus_channel {
/* The max size of a packet on this channel */
u32 max_pkt_size;
- /* function to mmap ring buffer memory to the channel's sysfs ring attribute */
- int (*mmap_ring_buffer)(struct vmbus_channel *channel, struct vm_area_struct *vma);
+ /* function to mmap_prepare ring buffer memory to the channel's sysfs ring attribute */
+ int (*mmap_prepare_ring_buffer)(struct vmbus_channel *channel, struct vm_area_desc *desc);
/* boolean to control visibility of sysfs for ring buffer */
bool ring_sysfs_visible;
--
2.53.0
^ permalink raw reply related
* [PATCH v3 12/16] mm: allow handling of stacked mmap_prepare hooks in more drivers
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
While the conversion of mmap hooks to mmap_prepare is underway, we will
encounter situations where mmap hooks need to invoke nested mmap_prepare
hooks.
The nesting of mmap hooks is termed 'stacking'. In order to flexibly
facilitate the conversion of custom mmap hooks in drivers which stack, we
must split up the existing __compat_vma_mmap() function into two separate
functions:
* compat_set_desc_from_vma() - This allows the setting of a vm_area_desc
object's fields to the relevant fields of a VMA.
* __compat_vma_mmap() - Once an mmap_prepare hook has been executed upon a
vm_area_desc object, this function performs any mmap actions specified by
the mmap_prepare hook and then invokes its vm_ops->mapped() hook if any
were specified.
In ordinary cases, where a file's f_op->mmap_prepare() hook simply needs
to be invoked in a stacked mmap() hook, compat_vma_mmap() can be used.
However some drivers define their own nested hooks, which are invoked in
turn by another hook.
A concrete example is vmbus_channel->mmap_ring_buffer(), which is invoked
in turn by bin_attribute->mmap():
vmbus_channel->mmap_ring_buffer() has a signature of:
int (*mmap_ring_buffer)(struct vmbus_channel *channel,
struct vm_area_struct *vma);
And bin_attribute->mmap() has a signature of:
int (*mmap)(struct file *, struct kobject *,
const struct bin_attribute *attr,
struct vm_area_struct *vma);
And so compat_vma_mmap() cannot be used here for incremental conversion of
hooks from mmap() to mmap_prepare().
There are many such instances like this, where conversion to mmap_prepare
would otherwise cascade to a huge change set due to nesting of this kind.
The changes in this patch mean we could now instead convert
vmbus_channel->mmap_ring_buffer() to
vmbus_channel->mmap_prepare_ring_buffer(), and implement something like:
struct vm_area_desc desc;
int err;
compat_set_desc_from_vma(&desc, file, vma);
err = channel->mmap_prepare_ring_buffer(channel, &desc);
if (err)
return err;
return __compat_vma_mmap(&desc, vma);
Allowing us to incrementally update this logic, and other logic like it.
Unfortunately, as part of this change, we need to be able to flexibly
assign to the VMA descriptor, so have to remove some of the const
declarations within the structure.
Also update the VMA tests to reflect the changes.
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
include/linux/fs.h | 3 +
include/linux/mm_types.h | 4 +-
mm/util.c | 112 ++++++++++++++++++++++---------
mm/vma.h | 2 +-
tools/testing/vma/include/dup.h | 114 +++++++++++++++++++++-----------
5 files changed, 162 insertions(+), 73 deletions(-)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index c390f5c667e3..0bdccfa70b44 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2058,6 +2058,9 @@ static inline bool can_mmap_file(struct file *file)
return true;
}
+void compat_set_desc_from_vma(struct vm_area_desc *desc, const struct file *file,
+ const struct vm_area_struct *vma);
+int __compat_vma_mmap(struct vm_area_desc *desc, struct vm_area_struct *vma);
int compat_vma_mmap(struct file *file, struct vm_area_struct *vma);
int __vma_check_mmap_hook(struct vm_area_struct *vma);
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 50685cf29792..7538d64f8848 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -891,8 +891,8 @@ static __always_inline bool vma_flags_empty(vma_flags_t *flags)
*/
struct vm_area_desc {
/* Immutable state. */
- const struct mm_struct *const mm;
- struct file *const file; /* May vary from vm_file in stacked callers. */
+ struct mm_struct *mm;
+ struct file *file; /* May vary from vm_file in stacked callers. */
unsigned long start;
unsigned long end;
diff --git a/mm/util.c b/mm/util.c
index 879ba62b5f0c..8cf59267a9ac 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -1163,34 +1163,40 @@ void flush_dcache_folio(struct folio *folio)
EXPORT_SYMBOL(flush_dcache_folio);
#endif
-static int __compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
+/**
+ * compat_set_desc_from_vma() - assigns VMA descriptor @desc fields from a VMA.
+ * @desc: A VMA descriptor whose fields need to be set.
+ * @file: The file object describing the file being mmap()'d.
+ * @vma: The VMA whose fields we wish to assign to @desc.
+ *
+ * This is a compatibility function to allow an mmap() hook to call
+ * mmap_prepare() hooks when drivers nest these. This function specifically
+ * allows the construction of a vm_area_desc value, @desc, from a VMA @vma for
+ * the purposes of doing this.
+ *
+ * Once the conversion of drivers is complete this function will no longer be
+ * required and will be removed.
+ */
+void compat_set_desc_from_vma(struct vm_area_desc *desc,
+ const struct file *file,
+ const struct vm_area_struct *vma)
{
- struct vm_area_desc desc = {
- .mm = vma->vm_mm,
- .file = file,
- .start = vma->vm_start,
- .end = vma->vm_end,
+ memset(desc, 0, sizeof(*desc));
- .pgoff = vma->vm_pgoff,
- .vm_file = vma->vm_file,
- .vma_flags = vma->flags,
- .page_prot = vma->vm_page_prot,
-
- .action.type = MMAP_NOTHING, /* Default */
- };
- int err;
+ desc->mm = vma->vm_mm;
+ desc->file = (struct file *)file;
+ desc->start = vma->vm_start;
+ desc->end = vma->vm_end;
- err = vfs_mmap_prepare(file, &desc);
- if (err)
- return err;
+ desc->pgoff = vma->vm_pgoff;
+ desc->vm_file = vma->vm_file;
+ desc->vma_flags = vma->flags;
+ desc->page_prot = vma->vm_page_prot;
- err = mmap_action_prepare(&desc);
- if (err)
- return err;
-
- set_vma_from_desc(vma, &desc);
- return mmap_action_complete(vma, &desc.action, /*rmap_lock_held=*/false);
+ /* Default. */
+ desc->action.type = MMAP_NOTHING;
}
+EXPORT_SYMBOL(compat_set_desc_from_vma);
static int __compat_vma_mapped(struct file *file, struct vm_area_struct *vma)
{
@@ -1211,6 +1217,50 @@ static int __compat_vma_mapped(struct file *file, struct vm_area_struct *vma)
return err;
}
+/**
+ * __compat_vma_mmap() - Similar to compat_vma_mmap(), only it allows
+ * flexibility as to how the mmap_prepare callback is invoked, which is useful
+ * for drivers which invoke nested mmap_prepare callbacks in an mmap() hook.
+ * @desc: A VMA descriptor upon which an mmap_prepare() hook has already been
+ * executed.
+ * @vma: The VMA to which @desc should be applied.
+ *
+ * The function assumes that you have obtained a VMA descriptor @desc from
+ * compat_set_desc_from_vma(), and already executed the mmap_prepare() hook upon
+ * it.
+ *
+ * It then performs any specified mmap actions, and invokes the vm_ops->mapped()
+ * hook if one is present.
+ *
+ * See the description of compat_vma_mmap() for more details.
+ *
+ * Once the conversion of drivers is complete this function will no longer be
+ * required and will be removed.
+ *
+ * Returns: 0 on success or error.
+ */
+int __compat_vma_mmap(struct vm_area_desc *desc,
+ struct vm_area_struct *vma)
+{
+ int err;
+
+ /* Perform any preparatory tasks for mmap action. */
+ err = mmap_action_prepare(desc);
+ if (err)
+ return err;
+ /* Update the VMA from the descriptor. */
+ compat_set_vma_from_desc(vma, desc);
+ /* Complete any specified mmap actions. */
+ err = mmap_action_complete(vma, &desc->action,
+ /*rmap_lock_held=*/false);
+ if (err)
+ return err;
+
+ /* Invoke vm_ops->mapped callback. */
+ return __compat_vma_mapped(desc->file, vma);
+}
+EXPORT_SYMBOL(__compat_vma_mmap);
+
/**
* compat_vma_mmap() - Apply the file's .mmap_prepare() hook to an
* existing VMA and execute any requested actions.
@@ -1218,10 +1268,10 @@ static int __compat_vma_mapped(struct file *file, struct vm_area_struct *vma)
* @vma: The VMA to apply the .mmap_prepare() hook to.
*
* Ordinarily, .mmap_prepare() is invoked directly upon mmap(). However, certain
- * stacked filesystems invoke a nested mmap hook of an underlying file.
+ * stacked drivers invoke a nested mmap hook of an underlying file.
*
- * Until all filesystems are converted to use .mmap_prepare(), we must be
- * conservative and continue to invoke these stacked filesystems using the
+ * Until all drivers are converted to use .mmap_prepare(), we must be
+ * conservative and continue to invoke these stacked drivers using the
* deprecated .mmap() hook.
*
* However we have a problem if the underlying file system possesses an
@@ -1232,20 +1282,22 @@ static int __compat_vma_mapped(struct file *file, struct vm_area_struct *vma)
* establishes a struct vm_area_desc descriptor, passes to the underlying
* .mmap_prepare() hook and applies any changes performed by it.
*
- * Once the conversion of filesystems is complete this function will no longer
- * be required and will be removed.
+ * Once the conversion of drivers is complete this function will no longer be
+ * required and will be removed.
*
* Returns: 0 on success or error.
*/
int compat_vma_mmap(struct file *file, struct vm_area_struct *vma)
{
+ struct vm_area_desc desc;
int err;
- err = __compat_vma_mmap(file, vma);
+ compat_set_desc_from_vma(&desc, file, vma);
+ err = vfs_mmap_prepare(file, &desc);
if (err)
return err;
- return __compat_vma_mapped(file, vma);
+ return __compat_vma_mmap(&desc, vma);
}
EXPORT_SYMBOL(compat_vma_mmap);
diff --git a/mm/vma.h b/mm/vma.h
index adc18f7dd9f1..a76046c39b14 100644
--- a/mm/vma.h
+++ b/mm/vma.h
@@ -300,7 +300,7 @@ static inline int vma_iter_store_gfp(struct vma_iterator *vmi,
* f_op->mmap() but which might have an underlying file system which implements
* f_op->mmap_prepare().
*/
-static inline void set_vma_from_desc(struct vm_area_struct *vma,
+static inline void compat_set_vma_from_desc(struct vm_area_struct *vma,
struct vm_area_desc *desc)
{
/*
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 1b86c34e1158..1f123704078e 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -519,8 +519,8 @@ enum vma_operation {
*/
struct vm_area_desc {
/* Immutable state. */
- const struct mm_struct *const mm;
- struct file *const file; /* May vary from vm_file in stacked callers. */
+ struct mm_struct *mm;
+ struct file *file; /* May vary from vm_file in stacked callers. */
unsigned long start;
unsigned long end;
@@ -1272,43 +1272,95 @@ static inline void vma_set_anonymous(struct vm_area_struct *vma)
}
/* Declared in vma.h. */
-static inline void set_vma_from_desc(struct vm_area_struct *vma,
+static inline void compat_set_vma_from_desc(struct vm_area_struct *vma,
struct vm_area_desc *desc);
-static inline int __compat_vma_mmap(const struct file_operations *f_op,
- struct file *file, struct vm_area_struct *vma)
+static inline void compat_set_desc_from_vma(struct vm_area_desc *desc,
+ const struct file *file,
+ const struct vm_area_struct *vma)
{
- struct vm_area_desc desc = {
- .mm = vma->vm_mm,
- .file = file,
- .start = vma->vm_start,
- .end = vma->vm_end,
+ memset(desc, 0, sizeof(*desc));
- .pgoff = vma->vm_pgoff,
- .vm_file = vma->vm_file,
- .vma_flags = vma->flags,
- .page_prot = vma->vm_page_prot,
+ desc->mm = vma->vm_mm;
+ desc->file = (struct file *)file;
+ desc->start = vma->vm_start;
+ desc->end = vma->vm_end;
- .action.type = MMAP_NOTHING, /* Default */
- };
+ desc->pgoff = vma->vm_pgoff;
+ desc->vm_file = vma->vm_file;
+ desc->vma_flags = vma->flags;
+ desc->page_prot = vma->vm_page_prot;
+
+ /* Default. */
+ desc->action.type = MMAP_NOTHING;
+}
+
+static inline unsigned long vma_pages(const struct vm_area_struct *vma)
+{
+ return (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+}
+
+static inline void unmap_vma_locked(struct vm_area_struct *vma)
+{
+ const size_t len = vma_pages(vma) << PAGE_SHIFT;
+
+ mmap_assert_write_locked(vma->vm_mm);
+ do_munmap(vma->vm_mm, vma->vm_start, len, NULL);
+}
+
+static inline int __compat_vma_mapped(struct file *file, struct vm_area_struct *vma)
+{
+ const struct vm_operations_struct *vm_ops = vma->vm_ops;
int err;
- err = f_op->mmap_prepare(&desc);
+ if (!vm_ops->mapped)
+ return 0;
+
+ err = vm_ops->mapped(vma->vm_start, vma->vm_end, vma->vm_pgoff, file,
+ &vma->vm_private_data);
if (err)
- return err;
+ unmap_vma_locked(vma);
+ return err;
+}
- err = mmap_action_prepare(&desc);
+static inline int __compat_vma_mmap(struct vm_area_desc *desc,
+ struct vm_area_struct *vma)
+{
+ int err;
+
+ /* Perform any preparatory tasks for mmap action. */
+ err = mmap_action_prepare(desc);
+ if (err)
+ return err;
+ /* Update the VMA from the descriptor. */
+ compat_set_vma_from_desc(vma, desc);
+ /* Complete any specified mmap actions. */
+ err = mmap_action_complete(vma, &desc->action,
+ /*rmap_lock_held=*/false);
if (err)
return err;
- set_vma_from_desc(vma, &desc);
- return mmap_action_complete(vma, &desc.action, /*rmap_lock_held=*/false);
+ /* Invoke vm_ops->mapped callback. */
+ return __compat_vma_mapped(desc->file, vma);
+}
+
+static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
+{
+ return file->f_op->mmap_prepare(desc);
}
static inline int compat_vma_mmap(struct file *file,
struct vm_area_struct *vma)
{
- return __compat_vma_mmap(file->f_op, file, vma);
+ struct vm_area_desc desc;
+ int err;
+
+ compat_set_desc_from_vma(&desc, file, vma);
+ err = vfs_mmap_prepare(file, &desc);
+ if (err)
+ return err;
+
+ return __compat_vma_mmap(&desc, vma);
}
@@ -1318,11 +1370,6 @@ static inline void vma_iter_init(struct vma_iterator *vmi,
mas_init(&vmi->mas, &mm->mm_mt, addr);
}
-static inline unsigned long vma_pages(struct vm_area_struct *vma)
-{
- return (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
-}
-
static inline void mmap_assert_locked(struct mm_struct *);
static inline struct vm_area_struct *find_vma_intersection(struct mm_struct *mm,
unsigned long start_addr,
@@ -1492,11 +1539,6 @@ static inline int vfs_mmap(struct file *file, struct vm_area_struct *vma)
return file->f_op->mmap(file, vma);
}
-static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
-{
- return file->f_op->mmap_prepare(desc);
-}
-
static inline void vma_set_file(struct vm_area_struct *vma, struct file *file)
{
/* Changing an anonymous vma with this is illegal */
@@ -1521,11 +1563,3 @@ static inline pgprot_t vma_get_page_prot(vma_flags_t vma_flags)
return vm_get_page_prot(vm_flags);
}
-
-static inline void unmap_vma_locked(struct vm_area_struct *vma)
-{
- const size_t len = vma_pages(vma) << PAGE_SHIFT;
-
- mmap_assert_write_locked(vma->vm_mm);
- do_munmap(vma->vm_mm, vma->vm_start, len, NULL);
-}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 11/16] staging: vme_user: replace deprecated mmap hook with mmap_prepare
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
The f_op->mmap interface is deprecated, so update driver to use its
successor, mmap_prepare.
The driver previously used vm_iomap_memory(), so this change replaces it
with its mmap_prepare equivalent, mmap_action_simple_ioremap().
Functions that wrap mmap() are also converted to wrap mmap_prepare()
instead.
Also update the documentation accordingly.
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
Documentation/driver-api/vme.rst | 2 +-
drivers/staging/vme_user/vme.c | 20 +++++------
drivers/staging/vme_user/vme.h | 2 +-
drivers/staging/vme_user/vme_user.c | 51 +++++++++++++++++------------
4 files changed, 42 insertions(+), 33 deletions(-)
diff --git a/Documentation/driver-api/vme.rst b/Documentation/driver-api/vme.rst
index c0b475369de0..7111999abc14 100644
--- a/Documentation/driver-api/vme.rst
+++ b/Documentation/driver-api/vme.rst
@@ -107,7 +107,7 @@ The function :c:func:`vme_master_read` can be used to read from and
In addition to simple reads and writes, :c:func:`vme_master_rmw` is provided to
do a read-modify-write transaction. Parts of a VME window can also be mapped
-into user space memory using :c:func:`vme_master_mmap`.
+into user space memory using :c:func:`vme_master_mmap_prepare`.
Slave windows
diff --git a/drivers/staging/vme_user/vme.c b/drivers/staging/vme_user/vme.c
index f10a00c05f12..7220aba7b919 100644
--- a/drivers/staging/vme_user/vme.c
+++ b/drivers/staging/vme_user/vme.c
@@ -735,9 +735,9 @@ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask,
EXPORT_SYMBOL(vme_master_rmw);
/**
- * vme_master_mmap - Mmap region of VME master window.
+ * vme_master_mmap_prepare - Mmap region of VME master window.
* @resource: Pointer to VME master resource.
- * @vma: Pointer to definition of user mapping.
+ * @desc: Pointer to descriptor of user mapping.
*
* Memory map a region of the VME master window into user space.
*
@@ -745,12 +745,13 @@ EXPORT_SYMBOL(vme_master_rmw);
* resource or -EFAULT if map exceeds window size. Other generic mmap
* errors may also be returned.
*/
-int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma)
+int vme_master_mmap_prepare(struct vme_resource *resource,
+ struct vm_area_desc *desc)
{
+ const unsigned long vma_size = vma_desc_size(desc);
struct vme_bridge *bridge = find_bridge(resource);
struct vme_master_resource *image;
phys_addr_t phys_addr;
- unsigned long vma_size;
if (resource->type != VME_MASTER) {
dev_err(bridge->parent, "Not a master resource\n");
@@ -758,19 +759,18 @@ int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma)
}
image = list_entry(resource->entry, struct vme_master_resource, list);
- phys_addr = image->bus_resource.start + (vma->vm_pgoff << PAGE_SHIFT);
- vma_size = vma->vm_end - vma->vm_start;
+ phys_addr = image->bus_resource.start + (desc->pgoff << PAGE_SHIFT);
if (phys_addr + vma_size > image->bus_resource.end + 1) {
dev_err(bridge->parent, "Map size cannot exceed the window size\n");
return -EFAULT;
}
- vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
-
- return vm_iomap_memory(vma, phys_addr, vma->vm_end - vma->vm_start);
+ desc->page_prot = pgprot_noncached(desc->page_prot);
+ mmap_action_simple_ioremap(desc, phys_addr, vma_size);
+ return 0;
}
-EXPORT_SYMBOL(vme_master_mmap);
+EXPORT_SYMBOL(vme_master_mmap_prepare);
/**
* vme_master_free - Free VME master window
diff --git a/drivers/staging/vme_user/vme.h b/drivers/staging/vme_user/vme.h
index 797e9940fdd1..b6413605ea49 100644
--- a/drivers/staging/vme_user/vme.h
+++ b/drivers/staging/vme_user/vme.h
@@ -151,7 +151,7 @@ ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count,
ssize_t vme_master_write(struct vme_resource *resource, void *buf, size_t count, loff_t offset);
unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask, unsigned int compare,
unsigned int swap, loff_t offset);
-int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma);
+int vme_master_mmap_prepare(struct vme_resource *resource, struct vm_area_desc *desc);
void vme_master_free(struct vme_resource *resource);
struct vme_resource *vme_dma_request(struct vme_dev *vdev, u32 route);
diff --git a/drivers/staging/vme_user/vme_user.c b/drivers/staging/vme_user/vme_user.c
index d95dd7d9190a..11e25c2f6b0a 100644
--- a/drivers/staging/vme_user/vme_user.c
+++ b/drivers/staging/vme_user/vme_user.c
@@ -446,24 +446,14 @@ static void vme_user_vm_close(struct vm_area_struct *vma)
kfree(vma_priv);
}
-static const struct vm_operations_struct vme_user_vm_ops = {
- .open = vme_user_vm_open,
- .close = vme_user_vm_close,
-};
-
-static int vme_user_master_mmap(unsigned int minor, struct vm_area_struct *vma)
+static int vme_user_vm_mapped(unsigned long start, unsigned long end, pgoff_t pgoff,
+ const struct file *file, void **vm_private_data)
{
- int err;
+ const unsigned int minor = iminor(file_inode(file));
struct vme_user_vma_priv *vma_priv;
mutex_lock(&image[minor].mutex);
- err = vme_master_mmap(image[minor].resource, vma);
- if (err) {
- mutex_unlock(&image[minor].mutex);
- return err;
- }
-
vma_priv = kmalloc_obj(*vma_priv);
if (!vma_priv) {
mutex_unlock(&image[minor].mutex);
@@ -472,22 +462,41 @@ static int vme_user_master_mmap(unsigned int minor, struct vm_area_struct *vma)
vma_priv->minor = minor;
refcount_set(&vma_priv->refcnt, 1);
- vma->vm_ops = &vme_user_vm_ops;
- vma->vm_private_data = vma_priv;
-
+ *vm_private_data = vma_priv;
image[minor].mmap_count++;
mutex_unlock(&image[minor].mutex);
-
return 0;
}
-static int vme_user_mmap(struct file *file, struct vm_area_struct *vma)
+static const struct vm_operations_struct vme_user_vm_ops = {
+ .mapped = vme_user_vm_mapped,
+ .open = vme_user_vm_open,
+ .close = vme_user_vm_close,
+};
+
+static int vme_user_master_mmap_prepare(unsigned int minor,
+ struct vm_area_desc *desc)
+{
+ int err;
+
+ mutex_lock(&image[minor].mutex);
+
+ err = vme_master_mmap_prepare(image[minor].resource, desc);
+ if (!err)
+ desc->vm_ops = &vme_user_vm_ops;
+
+ mutex_unlock(&image[minor].mutex);
+ return err;
+}
+
+static int vme_user_mmap_prepare(struct vm_area_desc *desc)
{
- unsigned int minor = iminor(file_inode(file));
+ const struct file *file = desc->file;
+ const unsigned int minor = iminor(file_inode(file));
if (type[minor] == MASTER_MINOR)
- return vme_user_master_mmap(minor, vma);
+ return vme_user_master_mmap_prepare(minor, desc);
return -ENODEV;
}
@@ -498,7 +507,7 @@ static const struct file_operations vme_user_fops = {
.llseek = vme_user_llseek,
.unlocked_ioctl = vme_user_unlocked_ioctl,
.compat_ioctl = compat_ptr_ioctl,
- .mmap = vme_user_mmap,
+ .mmap_prepare = vme_user_mmap_prepare,
};
static int vme_user_match(struct vme_dev *vdev)
--
2.53.0
^ permalink raw reply related
* [PATCH v3 10/16] stm: replace deprecated mmap hook with mmap_prepare
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
The f_op->mmap interface is deprecated, so update driver to use its
successor, mmap_prepare.
The driver previously used vm_iomap_memory(), so this change replaces it
with its mmap_prepare equivalent, mmap_action_simple_ioremap().
Also, in order to correctly maintain reference counting, add a
vm_ops->mapped callback to increment the reference count when successfully
mapped.
Reviewed-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
drivers/hwtracing/stm/core.c | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/drivers/hwtracing/stm/core.c b/drivers/hwtracing/stm/core.c
index 37584e786bb5..f48c6a8a0654 100644
--- a/drivers/hwtracing/stm/core.c
+++ b/drivers/hwtracing/stm/core.c
@@ -666,6 +666,16 @@ static ssize_t stm_char_write(struct file *file, const char __user *buf,
return count;
}
+static int stm_mmap_mapped(unsigned long start, unsigned long end, pgoff_t pgoff,
+ const struct file *file, void **vm_private_data)
+{
+ struct stm_file *stmf = file->private_data;
+ struct stm_device *stm = stmf->stm;
+
+ pm_runtime_get_sync(&stm->dev);
+ return 0;
+}
+
static void stm_mmap_open(struct vm_area_struct *vma)
{
struct stm_file *stmf = vma->vm_file->private_data;
@@ -684,12 +694,14 @@ static void stm_mmap_close(struct vm_area_struct *vma)
}
static const struct vm_operations_struct stm_mmap_vmops = {
+ .mapped = stm_mmap_mapped,
.open = stm_mmap_open,
.close = stm_mmap_close,
};
-static int stm_char_mmap(struct file *file, struct vm_area_struct *vma)
+static int stm_char_mmap_prepare(struct vm_area_desc *desc)
{
+ struct file *file = desc->file;
struct stm_file *stmf = file->private_data;
struct stm_device *stm = stmf->stm;
unsigned long size, phys;
@@ -697,10 +709,10 @@ static int stm_char_mmap(struct file *file, struct vm_area_struct *vma)
if (!stm->data->mmio_addr)
return -EOPNOTSUPP;
- if (vma->vm_pgoff)
+ if (desc->pgoff)
return -EINVAL;
- size = vma->vm_end - vma->vm_start;
+ size = vma_desc_size(desc);
if (stmf->output.nr_chans * stm->data->sw_mmiosz != size)
return -EINVAL;
@@ -712,13 +724,12 @@ static int stm_char_mmap(struct file *file, struct vm_area_struct *vma)
if (!phys)
return -EINVAL;
- pm_runtime_get_sync(&stm->dev);
-
- vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
- vm_flags_set(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP);
- vma->vm_ops = &stm_mmap_vmops;
- vm_iomap_memory(vma, phys, size);
+ desc->page_prot = pgprot_noncached(desc->page_prot);
+ vma_desc_set_flags(desc, VMA_IO_BIT, VMA_DONTEXPAND_BIT,
+ VMA_DONTDUMP_BIT);
+ desc->vm_ops = &stm_mmap_vmops;
+ mmap_action_simple_ioremap(desc, phys, size);
return 0;
}
@@ -836,7 +847,7 @@ static const struct file_operations stm_fops = {
.open = stm_char_open,
.release = stm_char_release,
.write = stm_char_write,
- .mmap = stm_char_mmap,
+ .mmap_prepare = stm_char_mmap_prepare,
.unlocked_ioctl = stm_char_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
--
2.53.0
^ permalink raw reply related
* [PATCH v3 09/16] mtdchar: replace deprecated mmap hook with mmap_prepare, clean up
From: Lorenzo Stoakes (Oracle) @ 2026-03-19 18:23 UTC (permalink / raw)
To: Andrew Morton
Cc: Jonathan Corbet, Clemens Ladisch, Arnd Bergmann,
Greg Kroah-Hartman, K . Y . Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Alexander Shishkin, Maxime Coquelin,
Alexandre Torgue, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra, Bodo Stroesser, Martin K . Petersen,
David Howells, Marc Dionne, Alexander Viro, Christian Brauner,
Jan Kara, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, linux-kernel, linux-doc, linux-hyperv, linux-stm32,
linux-arm-kernel, linux-mtd, linux-staging, linux-scsi,
target-devel, linux-afs, linux-fsdevel, linux-mm, Ryan Roberts
In-Reply-To: <cover.1773944114.git.ljs@kernel.org>
Replace the deprecated mmap callback with mmap_prepare.
Commit f5cf8f07423b ("mtd: Disable mtdchar mmap on MMU systems") commented
out the CONFIG_MMU part of this function back in 2012, so after ~14 years
it's probably reasonable to remove this altogether rather than updating
dead code.
Acked-by: Richard Weinberger <richard@nod.at>
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
---
drivers/mtd/mtdchar.c | 21 +++------------------
1 file changed, 3 insertions(+), 18 deletions(-)
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
index 55a43682c567..bf01e6ac7293 100644
--- a/drivers/mtd/mtdchar.c
+++ b/drivers/mtd/mtdchar.c
@@ -1376,27 +1376,12 @@ static unsigned mtdchar_mmap_capabilities(struct file *file)
/*
* set up a mapping for shared memory segments
*/
-static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
+static int mtdchar_mmap_prepare(struct vm_area_desc *desc)
{
#ifdef CONFIG_MMU
- struct mtd_file_info *mfi = file->private_data;
- struct mtd_info *mtd = mfi->mtd;
- struct map_info *map = mtd->priv;
-
- /* This is broken because it assumes the MTD device is map-based
- and that mtd->priv is a valid struct map_info. It should be
- replaced with something that uses the mtd_get_unmapped_area()
- operation properly. */
- if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
-#ifdef pgprot_noncached
- if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory))
- vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
-#endif
- return vm_iomap_memory(vma, map->phys, map->size);
- }
return -ENODEV;
#else
- return vma->vm_flags & VM_SHARED ? 0 : -EACCES;
+ return vma_desc_test(desc, VMA_SHARED_BIT) ? 0 : -EACCES;
#endif
}
@@ -1411,7 +1396,7 @@ static const struct file_operations mtd_fops = {
#endif
.open = mtdchar_open,
.release = mtdchar_close,
- .mmap = mtdchar_mmap,
+ .mmap_prepare = mtdchar_mmap_prepare,
#ifndef CONFIG_MMU
.get_unmapped_area = mtdchar_get_unmapped_area,
.mmap_capabilities = mtdchar_mmap_capabilities,
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox