* Re: [PATCH v4 11/13] ima: Support staging and deleting N measurements entries
From: steven chen @ 2026-04-01 17:48 UTC (permalink / raw)
To: Roberto Sassu, corbet, skhan, zohar, 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: <af6aa732b85af36e07e4a82b29170e80b13dc7c4.camel@huaweicloud.com>
On 3/27/2026 10:02 AM, Roberto Sassu wrote:
> On Thu, 2026-03-26 at 16:19 -0700, steven chen wrote:
>> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
>>> From: Roberto Sassu <roberto.sassu@huawei.com>
>>>
>>> Add support for sending a value N between 1 and ULONG_MAX to the staging
>>> interface. This value represents the number of measurements that should be
>>> deleted from the current measurements list.
>>>
>>> This staging method allows the remote attestation agents to easily separate
>>> the measurements that were verified (staged and deleted) from those that
>>> weren't due to the race between taking a TPM quote and reading the
>>> measurements list.
>>>
>>> In order to minimize the locking time of ima_extend_list_mutex, deleting
>>> N entries is realized by staging the entire current measurements list
>>> (with the lock), by determining the N-th staged entry (without the lock),
>>> and by splicing the entries in excess back to the current measurements list
>>> (with the lock). Finally, the N entries are deleted (without the lock).
>>>
>>> Flushing the hash table is not supported for N entries, since it would
>>> require removing the N entries one by one from the hash table under the
>>> ima_extend_list_mutex lock, which would increase the locking time.
>>>
>>> The ima_extend_list_mutex lock is necessary in ima_dump_measurement_list()
>>> because ima_queue_staged_delete_partial() uses __list_cut_position() to
>>> modify ima_measurements_staged, for which no RCU-safe variant exists. For
>>> the staging with prompt flavor alone, list_replace_rcu() could have been
>>> used instead, but since both flavors share the same kexec serialization
>>> path, the mutex is required regardless.
>>>
>>> Link: https://github.com/linux-integrity/linux/issues/1
>>> Suggested-by: Steven Chen <chenste@linux.microsoft.com>
>>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
>>> ---
>>> security/integrity/ima/Kconfig | 3 ++
>>> security/integrity/ima/ima.h | 1 +
>>> security/integrity/ima/ima_fs.c | 22 +++++++++-
>>> security/integrity/ima/ima_queue.c | 70 ++++++++++++++++++++++++++++++
>>> 4 files changed, 95 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
>>> index e714726f3384..6ddb4e77bff5 100644
>>> --- a/security/integrity/ima/Kconfig
>>> +++ b/security/integrity/ima/Kconfig
>>> @@ -341,6 +341,9 @@ config IMA_STAGING
>>> It allows user space to stage the measurements list for deletion and
>>> to delete the staged measurements after confirmation.
>>>
>>> + Or, alternatively, it allows user space to specify N measurements
>>> + entries to be deleted.
>>> +
>>> On kexec, staging is reverted and staged measurements are prepended
>>> to the current measurements list when measurements are copied to the
>>> secondary kernel.
>>> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
>>> index 699b735dec7d..de0693fce53c 100644
>>> --- a/security/integrity/ima/ima.h
>>> +++ b/security/integrity/ima/ima.h
>>> @@ -319,6 +319,7 @@ struct ima_template_desc *lookup_template_desc(const char *name);
>>> bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
>>> int ima_queue_stage(void);
>>> int ima_queue_staged_delete_all(void);
>>> +int ima_queue_staged_delete_partial(unsigned long req_value);
>>> int ima_restore_measurement_entry(struct ima_template_entry *entry);
>>> int ima_restore_measurement_list(loff_t bufsize, void *buf);
>>> int ima_measurements_show(struct seq_file *m, void *v);
>>> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
>>> index 39d9128e9f22..eb3f343c1138 100644
>>> --- a/security/integrity/ima/ima_fs.c
>>> +++ b/security/integrity/ima/ima_fs.c
>>> @@ -28,6 +28,7 @@
>>> * Requests:
>>> * 'A\n': stage the entire measurements list
>>> * 'D\n': delete all staged measurements
>>> + * '[1, ULONG_MAX]\n' delete N measurements entries
>>> */
>>> #define STAGED_REQ_LENGTH 21
>>>
>>> @@ -319,6 +320,7 @@ static ssize_t ima_measurements_staged_write(struct file *file,
>>> size_t datalen, loff_t *ppos)
>>> {
>>> char req[STAGED_REQ_LENGTH];
>>> + unsigned long req_value;
>>> int ret;
>>>
>>> if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
>>> @@ -346,7 +348,25 @@ static ssize_t ima_measurements_staged_write(struct file *file,
>>> ret = ima_queue_staged_delete_all();
>>> break;
>>> default:
>>> - ret = -EINVAL;
>>> + if (ima_flush_htable) {
>>> + pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + ret = kstrtoul(req, 10, &req_value);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + if (req_value == 0) {
>>> + pr_debug("Must delete at least one entry\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + ret = ima_queue_stage();
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + ret = ima_queue_staged_delete_partial(req_value);
>> The default processing is "Trim N" idea plus performance improvement.
>>
>> Here do everything in one time. And this is what I said in v3.
>>
>> [PATCH v3 1/3] ima: Remove ima_h_table structure
>> <https://lore.kernel.org/linux-integrity/c61aeaa79929a98cb3a6d30835972891fac3570f.camel@linux.ibm.com/T/#t>
> In your approach you do:
>
> lock ima_extend_measure_list_mutex
> scan entries until N
> cut list staged -> trim
> unlock ima_extend_measure_list_mutex
>
>
> In my approach I do:
> lock ima_extend_measure_list_mutex
> list replace active -> staged
> unlock ima_extend_measure_list_mutex
>
> scan entries until N
>
> lock ima_extend_measure_list_mutex
> cut list staged -> trim
> splice staged ->active
> unlock ima_extend_measure_list_mutex
>
> So, I guess if you refer to less user space locking time, you mean one
> lock/unlock and one list replace + list splice less in your solution.
>
> In exchange, you propose to hold the lock in the kernel while scanning
> N. I think it is a significant increase of kernel locking time vs a
> negligible increase of user space locking time (in the kernel, all
> processes need to wait for the ima_extend_measure_list_mutex to be
> released, in user space it is just the agent waiting).
>
> Roberto
Please the version 5:
[PATCH v5 0/3] Trim N entries of IMA event logs
<https://lore.kernel.org/linux-integrity/20260401172956.4581-1-chenste@linux.microsoft.com/T/#t>
Scanning N is moved out of the lock period.
"Trim N" proposal has less lock time than "Staging and deleting" proposal.
"Trim N" proposal has much less code than "Staging and deleting" proposal.
"Trim N" proposal user space operation is more simple than "Staging and
deleting".
Steven
>> The important two parts of trimming is "trim N" and performance improvement.
>>
>> The performance improvement include two parts:
>> hash table staging
>> active log list staging
>>
>> And I think "Trim N" plus performance improvement is the right direction
>> to go.
>> Lots of code for two steps "stage and trim" "stage" part can be removed.
>>
>> Also race condition may happen if not holding the list all time in user
>> space
>> during attestation period: from stage, read list, attestation and trimming.
>>
>> So in order to improve the above user space lock time, "Trim T:N" can be
>> used
>> not to hold list long in user space during attestation.
>>
>> For Trim T:N, T represent total log trimmed since system boot up. Please
>> refer to
>> https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/T/#t
>>
>> Thanks,
>>
>> Steven
>>> }
>>>
>>> if (ret < 0)
>>> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
>>> index f5c18acfbc43..4fb557d61a88 100644
>>> --- a/security/integrity/ima/ima_queue.c
>>> +++ b/security/integrity/ima/ima_queue.c
>>> @@ -371,6 +371,76 @@ int ima_queue_staged_delete_all(void)
>>> return 0;
>>> }
>>>
>>> +int ima_queue_staged_delete_partial(unsigned long req_value)
>>> +{
>>> + unsigned long req_value_copy = req_value;
>>> + unsigned long size_to_remove = 0, num_to_remove = 0;
>>> + struct list_head *cut_pos = NULL;
>>> + LIST_HEAD(ima_measurements_trim);
>>> + struct ima_queue_entry *qe;
>>> + int ret = 0;
>>> +
>>> + /*
>>> + * Safe walk (no concurrent write), not under ima_extend_list_mutex
>>> + * for performance reasons.
>>> + */
>>> + list_for_each_entry(qe, &ima_measurements_staged, later) {
>>> + size_to_remove += get_binary_runtime_size(qe->entry);
>>> + num_to_remove++;
>>> +
>>> + if (--req_value_copy == 0) {
>>> + /* qe->later always points to a valid list entry. */
>>> + cut_pos = &qe->later;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + /* Nothing to remove, undoing staging. */
>>> + if (req_value_copy > 0) {
>>> + size_to_remove = 0;
>>> + num_to_remove = 0;
>>> + ret = -ENOENT;
>>> + }
>>> +
>>> + mutex_lock(&ima_extend_list_mutex);
>>> + if (list_empty(&ima_measurements_staged)) {
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> + return -ENOENT;
>>> + }
>>> +
>>> + if (cut_pos != NULL)
>>> + /*
>>> + * ima_dump_measurement_list() does not modify the list,
>>> + * cut_pos remains the same even if it was computed before
>>> + * the lock.
>>> + */
>>> + __list_cut_position(&ima_measurements_trim,
>>> + &ima_measurements_staged, cut_pos);
>>> +
>>> + atomic_long_sub(num_to_remove, &ima_num_entries[BINARY_STAGED]);
>>> + atomic_long_add(atomic_long_read(&ima_num_entries[BINARY_STAGED]),
>>> + &ima_num_entries[BINARY]);
>>> + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
>>> +
>>> + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
>>> + binary_runtime_size[BINARY_STAGED] -= size_to_remove;
>>> + binary_runtime_size[BINARY] +=
>>> + binary_runtime_size[BINARY_STAGED];
>>> + binary_runtime_size[BINARY_STAGED] = 0;
>>> + }
>>> +
>>> + /*
>>> + * Splice (prepend) any remaining non-deleted staged entries to the
>>> + * active list (RCU not needed, there cannot be concurrent readers).
>>> + */
>>> + list_splice(&ima_measurements_staged, &ima_measurements);
>>> + INIT_LIST_HEAD(&ima_measurements_staged);
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> +
>>> + ima_queue_delete(&ima_measurements_trim, false);
>>> + return ret;
>>> +}
>>> +
>>> static void ima_queue_delete(struct list_head *head, bool flush_htable)
>>> {
>>> struct ima_queue_entry *qe, *qe_tmp;
^ permalink raw reply
* Re: [PATCH 12/33] rust: macros: update `extract_if` MSRV TODO comment
From: Miguel Ojeda @ 2026-04-01 17:45 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Nathan Chancellor, Nicolas Schier, Danilo Krummrich,
Andreas Hindborg, Catalin Marinas, Will Deacon, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Courbot, David Airlie,
Simona Vetter, Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet, Boqun Feng,
Björn Roy Baron, Benno Lossin, Trevor Gross, rust-for-linux,
linux-kbuild, Lorenzo Stoakes, Vlastimil Babka, Liam R . Howlett,
Uladzislau Rezki, linux-block, moderated for non-subscribers,
Alexandre Ghiti, linux-riscv, nouveau, dri-devel, Rae Moar,
linux-kselftest, kunit-dev, Nick Desaulniers, Bill Wendling,
Justin Stitt, llvm, linux-kernel, Shuah Khan, linux-doc
In-Reply-To: <DHHVSX66206Y.3E7I9QUNTCJ8I@garyguo.net>
On Wed, Apr 1, 2026 at 4:18 PM Gary Guo <gary@garyguo.net> wrote:
>
> When I write the comment the intention is to enable the unstable feature and
> switch.
Yeah, that is what I meant as the alternative in the commit message.
I am OK with either.
(By the way, I wondered why you mentioned 1.85 in the comment, I guess
it was supposed to be 1.86 instead originally, i.e. "above" as in >
1.86)
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH 09/33] rust: kbuild: make `--remap-path-prefix` workaround conditional
From: Miguel Ojeda @ 2026-04-01 17:39 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Nathan Chancellor, Nicolas Schier, Danilo Krummrich,
Andreas Hindborg, Catalin Marinas, Will Deacon, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Courbot, David Airlie,
Simona Vetter, Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet, Boqun Feng,
Björn Roy Baron, Benno Lossin, Trevor Gross, rust-for-linux,
linux-kbuild, Lorenzo Stoakes, Vlastimil Babka, Liam R . Howlett,
Uladzislau Rezki, linux-block, moderated for non-subscribers,
Alexandre Ghiti, linux-riscv, nouveau, dri-devel, Rae Moar,
linux-kselftest, kunit-dev, Nick Desaulniers, Bill Wendling,
Justin Stitt, llvm, linux-kernel, Shuah Khan, linux-doc
In-Reply-To: <DHHVLQAOMLWB.3FHHSYKNM5TNP@garyguo.net>
On Wed, Apr 1, 2026 at 4:08 PM Gary Guo <gary@garyguo.net> wrote:
>
> Okay, I see what the comments mean now. Perhaps squash this to the previous
> commit?
This one was mostly to ensure the workaround was not needed anymore,
i.e. it is more "optional" than the other.
In fact, we may want to just not have neither of the patches, i.e. we
could just remove the workaround given the timelines of the branches
-- please see my reply on the previous one on this.
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH 08/33] rust: kbuild: simplify `--remap-path-prefix` workaround
From: Miguel Ojeda @ 2026-04-01 17:36 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Nathan Chancellor, Nicolas Schier, Danilo Krummrich,
Andreas Hindborg, Catalin Marinas, Will Deacon, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Courbot, David Airlie,
Simona Vetter, Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet, Boqun Feng,
Björn Roy Baron, Benno Lossin, Trevor Gross, rust-for-linux,
linux-kbuild, Lorenzo Stoakes, Vlastimil Babka, Liam R . Howlett,
Uladzislau Rezki, linux-block, moderated for non-subscribers,
Alexandre Ghiti, linux-riscv, nouveau, dri-devel, Rae Moar,
linux-kselftest, kunit-dev, Nick Desaulniers, Bill Wendling,
Justin Stitt, llvm, linux-kernel, Shuah Khan, linux-doc
In-Reply-To: <DHHVEPJHLGDW.1E6KDP9BUFG5U@garyguo.net>
On Wed, Apr 1, 2026 at 3:59 PM Gary Guo <gary@garyguo.net> wrote:
>
> I'm not sure that I parse this. You do remove the filter-out completely below?
(I see you saw the other commit)
> Looks like this is going to conflict with rust-fixes (which adds the
> --remap-path-scope). Perhaps worth doing a back merge?
It would be only a couple lines conflicting, so it should be fine.
Having said that, when I was doing this, I wondered if we should even
consider keeping the workaround. In other words, locally for
`rust-next`, the "normal" commit would be to remove the workaround
entirely because there the flag doesn't exist to begin with (i.e. the
workaround should have been removed back when the revert landed).
Then, when conflict happens in linux-next, we can just keep the
addition of the flag from your commit -- the rest can say as-is, i.e.
no workaround needed, because you only enable both flags in a version
(1.95.0) where there is no need for the workaround (which was for <
1.87.0).
It is also why I added the second commit here, i.e. the "make it
conditional", because I was testing that indeed we didn't need the
workaround anymore.
So it may just simpler to do that. What I thought was that perhaps the
workaround is good even if we ourselves don't pass the flag, e.g.
someone else may be passing it. But the chances are very low,
restricted to a couple versions, and the error is obvious and at build
time anyway.
Cheers,
Miguel
^ permalink raw reply
* [PATCH v5 3/3] ima: add new critical data record to measure log trim
From: steven chen @ 2026-04-01 17:29 UTC (permalink / raw)
To: linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve, chenste,
gregorylumen, nramas, sushring, linux-doc
In-Reply-To: <20260401172956.4581-1-chenste@linux.microsoft.com>
Add a new critical data record to measure the trimming event when
ima event records are deleted since system boot up.
If all IMA event logs are saved in the userspace, use this log to get total
numbers of records deleted since system boot up at that point.
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
security/integrity/ima/ima_fs.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 8e26e0f34311..38d0a49b587f 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -43,6 +43,7 @@ static int valid_policy = 1;
#define IMA_LOG_TRIM_REQ_NUM_LENGTH 15
#define IMA_LOG_TRIM_REQ_TOTAL_LENGTH 32
+#define IMA_LOG_TRIM_EVENT_LEN 256
atomic_long_t ima_number_entries = ATOMIC_LONG_INIT(0);
static long trimcount;
/* mutex protects atomicity of trimming measurement list
@@ -52,6 +53,22 @@ static long trimcount;
static DEFINE_MUTEX(ima_measure_lock);
static long ima_measure_users;
+static void ima_measure_trim_event(void)
+{
+ char ima_log_trim_event[IMA_LOG_TRIM_EVENT_LEN];
+ struct timespec64 ts;
+ u64 time_ns;
+ int n;
+
+ ktime_get_real_ts64(&ts);
+ time_ns = (u64)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+ n = scnprintf(ima_log_trim_event, IMA_LOG_TRIM_EVENT_LEN,
+ "time= %llu; number= %lu;", time_ns, trimcount);
+
+ ima_measure_critical_data("ima_log_trim", "trim ima event logs",
+ ima_log_trim_event, n, false, NULL, 0);
+}
+
static ssize_t ima_show_htable_value(char __user *buf, size_t count,
loff_t *ppos, atomic_long_t *val)
{
@@ -436,6 +453,9 @@ static ssize_t ima_log_trim_write(struct file *file,
if (ret < 0)
goto out;
+ if (ret > 0)
+ ima_measure_trim_event();
+
trimcount += ret;
ret = datalen;
--
2.43.0
^ permalink raw reply related
* [PATCH v5 2/3] ima: trim N IMA event log records
From: steven chen @ 2026-04-01 17:29 UTC (permalink / raw)
To: linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve, chenste,
gregorylumen, nramas, sushring, linux-doc
In-Reply-To: <20260401172956.4581-1-chenste@linux.microsoft.com>
Trim N entries of the IMA event logs. Do not clean the hash table.
The values saved in hash table were already used.
Provide a userspace interface ima_trim_log:
When read this interface, it returns total number T of entries trimmed
since system boot up.
When write to this interface need to provide two numbers T:N to let
kernel to trim N entries of IMA event logs.
Kernel measurement list lock time performance improvement by not
clean the hash table.
when kernel get log trim request T:N
- Get the T, compare with the total trimmed number
- if equal, then do trim N and change T to T+N
- else return error
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
.../admin-guide/kernel-parameters.txt | 4 +
security/integrity/ima/ima.h | 4 +-
security/integrity/ima/ima_fs.c | 198 +++++++++++++++++-
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_queue.c | 96 +++++++++
5 files changed, 296 insertions(+), 8 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e92c0056e4e0..cd1a1d0bf0e2 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2197,6 +2197,10 @@
Use the canonical format for the binary runtime
measurements, instead of host native format.
+ ima_flush_htable [IMA]
+ Flush the measurement list hash table when trim all
+ or a part of it for deletion.
+
ima_hash= [IMA]
Format: { md5 | sha1 | rmd160 | sha256 | sha384
| sha512 | ... }
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index e3d71d8d56e3..5cbee3a295a0 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -243,11 +243,13 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
const void *payload, size_t plen,
unsigned long flags, bool create);
#endif
-
+extern atomic_long_t ima_number_entries;
#ifdef CONFIG_IMA_KEXEC
void ima_measure_kexec_event(const char *event_name);
+long ima_delete_event_log(long req_val);
#else
static inline void ima_measure_kexec_event(const char *event_name) {}
+static inline long ima_delete_event_log(long req_val) { return 0; }
#endif
/*
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 87045b09f120..8e26e0f34311 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -21,6 +21,9 @@
#include <linux/rcupdate.h>
#include <linux/parser.h>
#include <linux/vmalloc.h>
+#include <linux/ktime.h>
+#include <linux/timekeeping.h>
+#include <linux/ima.h>
#include "ima.h"
@@ -38,6 +41,17 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup);
static int valid_policy = 1;
+#define IMA_LOG_TRIM_REQ_NUM_LENGTH 15
+#define IMA_LOG_TRIM_REQ_TOTAL_LENGTH 32
+atomic_long_t ima_number_entries = ATOMIC_LONG_INIT(0);
+static long trimcount;
+/* mutex protects atomicity of trimming measurement list
+ * and also protects atomicity the measurement list read
+ * write operation.
+ */
+static DEFINE_MUTEX(ima_measure_lock);
+static long ima_measure_users;
+
static ssize_t ima_show_htable_value(char __user *buf, size_t count,
loff_t *ppos, atomic_long_t *val)
{
@@ -64,8 +78,7 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
-
+ return ima_show_htable_value(buf, count, ppos, &ima_number_entries);
}
static const struct file_operations ima_measurements_count_ops = {
@@ -202,16 +215,77 @@ static const struct seq_operations ima_measurments_seqops = {
.show = ima_measurements_show
};
+/*
+ * _ima_measurements_open - open the IMA measurements file
+ * @inode: inode of the file being opened
+ * @file: file being opened
+ * @seq_ops: sequence operations for the file
+ *
+ * Returns 0 on success, or negative error code.
+ * Implements mutual exclusion between readers and writer
+ * of the measurements file. Multiple readers are allowed,
+ * but writer get exclusive access only no other readers/writers.
+ * Readers is not allowed when there is a writer.
+ */
+static int _ima_measurements_open(struct inode *inode, struct file *file,
+ const struct seq_operations *seq_ops)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+ int ret;
+
+ if (write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ mutex_lock(&ima_measure_lock);
+ if ((write && ima_measure_users != 0) ||
+ (!write && ima_measure_users < 0)) {
+ mutex_unlock(&ima_measure_lock);
+ return -EBUSY;
+ }
+
+ ret = seq_open(file, seq_ops);
+ if (ret < 0) {
+ mutex_unlock(&ima_measure_lock);
+ return ret;
+ }
+
+ if (write)
+ ima_measure_users--;
+ else
+ ima_measure_users++;
+
+ mutex_unlock(&ima_measure_lock);
+ return ret;
+}
+
static int ima_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_measurments_seqops);
+ return _ima_measurements_open(inode, file, &ima_measurments_seqops);
+}
+
+static int ima_measurements_release(struct inode *inode, struct file *file)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+ int ret;
+
+ mutex_lock(&ima_measure_lock);
+ ret = seq_release(inode, file);
+ if (!ret) {
+ if (!write)
+ ima_measure_users--;
+ else
+ ima_measure_users++;
+ }
+
+ mutex_unlock(&ima_measure_lock);
+ return ret;
}
static const struct file_operations ima_measurements_ops = {
.open = ima_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ima_measurements_release,
};
void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
@@ -279,14 +353,114 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_ascii_measurements_seqops);
+ return _ima_measurements_open(inode, file, &ima_ascii_measurements_seqops);
}
static const struct file_operations ima_ascii_measurements_ops = {
.open = ima_ascii_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ima_measurements_release,
+};
+
+static int ima_log_trim_open(struct inode *inode, struct file *file)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+
+ if (!write && capable(CAP_SYS_ADMIN))
+ return 0;
+ else if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return _ima_measurements_open(inode, file, &ima_measurments_seqops);
+}
+
+static ssize_t ima_log_trim_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
+{
+ char tmpbuf[IMA_LOG_TRIM_REQ_NUM_LENGTH];
+ ssize_t len;
+
+ len = scnprintf(tmpbuf, sizeof(tmpbuf), "%li\n", trimcount);
+ return simple_read_from_buffer(buf, size, ppos, tmpbuf, len);
+}
+
+static ssize_t ima_log_trim_write(struct file *file,
+ const char __user *buf, size_t datalen, loff_t *ppos)
+{
+ char tmpbuf[IMA_LOG_TRIM_REQ_TOTAL_LENGTH];
+ char *p = tmpbuf;
+ long count, ret, val = 0, max = LONG_MAX;
+
+ if (*ppos > 0 || datalen > IMA_LOG_TRIM_REQ_TOTAL_LENGTH || datalen < 2) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(tmpbuf, buf, datalen) != 0) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ p = tmpbuf;
+
+ while (*p && *p != ':') {
+ if (!isdigit((unsigned char)*p))
+ return -EINVAL;
+
+ /* digit value */
+ int d = *p - '0';
+
+ /* overflow check: val * 10 + d > max -> (val > (max - d) / 10) */
+ if (val > (max - d) / 10)
+ return -ERANGE;
+
+ val = val * 10 + d;
+ p++;
+ }
+
+ if (*p != ':')
+ return -EINVAL;
+
+ /* verify trim count matches */
+ if (val != trimcount)
+ return -EINVAL;
+
+ p++; /* skip ':' */
+ ret = kstrtoul(p, 0, &count);
+
+ if (ret < 0)
+ goto out;
+
+ ret = ima_delete_event_log(count);
+
+ if (ret < 0)
+ goto out;
+
+ trimcount += ret;
+
+ ret = datalen;
+out:
+ return ret;
+}
+
+static int ima_log_trim_release(struct inode *inode, struct file *file)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+
+ if (!write && capable(CAP_SYS_ADMIN))
+ return 0;
+ else if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return ima_measurements_release(inode, file);
+}
+
+static const struct file_operations ima_log_trim_ops = {
+ .open = ima_log_trim_open,
+ .read = ima_log_trim_read,
+ .write = ima_log_trim_write,
+ .llseek = generic_file_llseek,
+ .release = ima_log_trim_release
};
static ssize_t ima_read_policy(char *path)
@@ -528,6 +702,18 @@ int __init ima_fs_init(void)
goto out;
}
+ if (IS_ENABLED(CONFIG_IMA_LOG_TRIMMING)) {
+ dentry = securityfs_create_file("ima_trim_log",
+ S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP,
+ ima_dir, NULL, &ima_log_trim_ops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+ }
+
+ trimcount = 0;
+
dentry = securityfs_create_file("runtime_measurements_count",
S_IRUSR | S_IRGRP, ima_dir, NULL,
&ima_measurements_count_ops);
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 7362f68f2d8b..bee997683e03 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -41,7 +41,7 @@ void ima_measure_kexec_event(const char *event_name)
int n;
buf_size = ima_get_binary_runtime_size();
- len = atomic_long_read(&ima_htable.len);
+ len = atomic_long_read(&ima_number_entries);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 590637e81ad1..07225e19b9b5 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -22,6 +22,14 @@
#define AUDIT_CAUSE_LEN_MAX 32
+bool ima_flush_htable;
+static int __init ima_flush_htable_setup(char *str)
+{
+ ima_flush_htable = true;
+ return 1;
+}
+__setup("ima_flush_htable", ima_flush_htable_setup);
+
/* pre-allocated array of tpm_digest structures to extend a PCR */
static struct tpm_digest *digests;
@@ -114,6 +122,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
list_add_tail_rcu(&qe->later, &ima_measurements);
atomic_long_inc(&ima_htable.len);
+ atomic_long_inc(&ima_number_entries);
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
@@ -220,6 +229,93 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
return result;
}
+/**
+ * ima_delete_event_log - delete IMA event entry
+ * @num_records: number of records to delete
+ *
+ * delete num_records entries off the measurement list.
+ * Returns num_records, or negative error code.
+ */
+long ima_delete_event_log(long num_records)
+{
+ long len, cur = num_records, tmp_len = 0;
+ struct ima_queue_entry *qe, *qe_tmp;
+ LIST_HEAD(ima_measurements_to_delete);
+ struct list_head *list_ptr;
+
+ if (!IS_ENABLED(CONFIG_IMA_LOG_TRIMMING))
+ return -EOPNOTSUPP;
+
+ if (num_records <= 0)
+ return num_records;
+
+ list_ptr = &ima_measurements;
+
+ len = atomic_long_read(&ima_number_entries);
+
+ if (num_records <= len) {
+ list_for_each_entry(qe, list_ptr, later) {
+ if (cur > 0) {
+ tmp_len += get_binary_runtime_size(qe->entry);
+ --cur;
+ }
+ if (cur == 0) {
+ qe_tmp = qe;
+ break;
+ }
+ }
+ }
+ else {
+ return -ENOENT;
+ }
+
+
+ mutex_lock(&ima_extend_list_mutex);
+ len = atomic_long_read(&ima_number_entries);
+
+ if (num_records == len) {
+ list_replace(&ima_measurements, &ima_measurements_to_delete);
+ INIT_LIST_HEAD(&ima_measurements);
+ atomic_long_set(&ima_number_entries, 0);
+ list_ptr = &ima_measurements_to_delete;
+ }
+ else {
+ __list_cut_position(&ima_measurements_to_delete, &ima_measurements,
+ &qe_tmp->later);
+ atomic_long_sub(num_records, &ima_number_entries);
+ if (IS_ENABLED(CONFIG_IMA_KEXEC))
+ binary_runtime_size -= tmp_len;
+ }
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (ima_flush_htable)
+ synchronize_rcu();
+
+ list_for_each_entry_safe(qe, qe_tmp, &ima_measurements_to_delete, later) {
+ /*
+ * Ok because after list delete qe is only accessed by
+ * ima_lookup_digest_entry().
+ */
+ for (int i = 0; i < qe->entry->template_desc->num_fields; i++) {
+ kfree(qe->entry->template_data[i].data);
+ qe->entry->template_data[i].data = NULL;
+ qe->entry->template_data[i].len = 0;
+ }
+
+ list_del(&qe->later);
+
+ /* No leak if !ima_flush_htable, referenced by ima_htable. */
+ if (ima_flush_htable) {
+ kfree(qe->entry->digests);
+ kfree(qe->entry);
+ kfree(qe);
+ }
+ }
+
+ return num_records;
+}
+
int ima_restore_measurement_entry(struct ima_template_entry *entry)
{
int result = 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v5 1/3] ima: make ima event log trimming configurable
From: steven chen @ 2026-04-01 17:29 UTC (permalink / raw)
To: linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve, chenste,
gregorylumen, nramas, sushring, linux-doc
In-Reply-To: <20260401172956.4581-1-chenste@linux.microsoft.com>
Make ima event log trimming function configurable.
Suggested-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
security/integrity/ima/Kconfig | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 976e75f9b9ba..322964ae4772 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -332,4 +332,16 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
If set to the default value of 0, an extra half page of memory for those
additional measurements will be allocated.
+config IMA_LOG_TRIMMING
+ bool "IMA Event Log Trimming"
+ default n
+ help
+ Say Y here if you want support for IMA Event Log Trimming.
+ This creates the file /sys/kernel/security/integrity/ima/ima_trim_log.
+ Userspace
+ - writes to this file to trigger IMA event log trimming
+ - reads this file to get number of entried trimming last time
+
+ If unsure, say N.
+
endif
--
2.43.0
^ permalink raw reply related
* [PATCH v5 0/3] Trim N entries of IMA event logs
From: steven chen @ 2026-04-01 17:29 UTC (permalink / raw)
To: linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve, chenste,
gregorylumen, nramas, sushring, linux-doc
The Integrity Measurement Architecture (IMA) maintains a measurement list
—a record of system events used for integrity verification. The IMA event
logs are the entries within this measurement list, each representing a
specific event or measurement that contributes to the system's integrity
assessment.
This update introduces the ability to trim, or remove, N entries from the
current measurement list. Trimming involves deleting N entries from the
list. This action atomically truncates the measurement list, ensuring that
no new measurements can be added until the operation is complete.
Importantly, only one writer can initiate this trimming process at a time,
maintaining consistency and preventing race conditions.
A userspace interface, ima_trim_log, has been provided for this purpose.
When this interface is read, it returns the total number T of entries
trimmed since system boot up. This value T need to be preserved across
kexec soft reboots. By writing two number T:N to this interface, userspace
can request the kernel to trim N entries from the IMA event logs.
To maintain a complete record, userspace is responsible for concatenating
and storing the logs before initiating trimming. Userspace can then send
the collected data to remote verifiers for validation. After receiving
confirmation from the remote verifiers, userspace may instruct the kernel
to proceed with trimming the IMA event logs accordingly.
The primary benefit of this solution is the ability to free valuable
kernel memory by delegating the task of reconstructing the full
measurement list from log chunks to userspace. Trust is not required in
userspace for the integrity of the measurement list, as its integrity is
cryptographically protected by the Trusted Platform Module (TPM).
Multiple readers are allowed to access the ima_trim_log interface
concurrently, while only one writer can trigger log trimming at any time.
During trimming, readers do not see the list and cannot access it while
deletion is in progress, ensuring atomicity.
Introduce the new kernel option ima_flush_htable to decide whether or not
the digests of measurement entries are flushed from the hash table (from
reference [2]).
The ima_measure_users counter (protected by the ima_measure_lock mutex) has
been introduced to protect access to the measurement list 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/delete the measurements, the read permission
to read them. Write requires also the CAP_SYS_ADMIN capability (from
reference [2]). This ima_measure_users needs to be preserved across kexec
soft reboots
The total trimmed number T and the ima_measure_users both need to be
preserved across kexec soft reboot and new patch will be added for this
purpose in next version.
New IMA log trim event is added when trimming finish.
The time required for trimming is minimal, and IMA event logs are briefly
on hold during this process, preventing read or add operations. This short
interruption has no impact on the overall functionality of IMA.
A new critical data record "ima_log_trim" is added in this solution. This
record logs the trim event with number of entries deleted total T since
system start and time when this happened. User space can get the total
number T of entries trimmed by checking "ima_log_trim" event in the
measurement list.
The following are how user space to use the measurement list and
ima_log_trim interface
1. get the total numer trimmed T through "ima_log_trim" interface
2. get the PCR quote
3. read the measurement list file, close the file, send for verification
4. wait for response from verifier, until get the good response from
verifier with number N that matched the PCR quote got in step 2
5. get the number N from the above message
6. write the T:N to the ima_log_trim interface when no conflict
when kernel get log trim request T:N
Get the T, compare with the total trimmed number
if equal, then do trim N and change T to T+N
else return error
Using above way to trim the log, the time for user space to hold the list
will be trimming T:N operation itself at the step 6. User space agent
race condition is solved too in this way.
References:
-----------
[1] [PATCH 0/1] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20251202232857.8211-1-chenste@linux.microsoft.com/T/#t
[2] [RFC][PATCH] ima: Add support for staging measurements for deletion
https://lore.kernel.org/linux-integrity/207fd6d7-53c-57bb-36d8-13a0902052d1@linux.microsoft.com/T/#t
[3] [PATCH v2 0/1] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20251210235314.3341-1-chenste@linux.microsoft.com/T/#t
[4] [PATCH v3 0/3] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20260106020713.3994-1-chenste@linux.microsoft.com/T/#t
[5] [PATCH v4 0/3] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/T/#t
Change Log v5:
- lock time performance improvement
- Keep hash table unchanged because log already use the hash value
- Updated patch descriptions as necessary.
Change Log v4:
- Incorporated feedback from Roberto on v3 series.
- Update "ima_log_trim" interface definition
When read this interface, return total number of records trimmed T
need to write T:N to this interface to trim N records
- Update user space use case on how to trim IMA event logs
- Updated patch descriptions as necessary.
Change Log v3:
- Incorporated feedback from Mimi on v2 series.
- split patch into multiple patches
- lock time performance improvement
- Updated patch descriptions as necessary.
Change Log v2:
- Incorporated feedback from the Roberto on v1 series.
- Adapted code from Roberto's RFC [Reference 2]
- Add IMA log trim event log to record trim event
- Updated patch descriptions as necessary
steven chen (3):
ima: make ima event log trimming configurable
ima: trim N IMA event log records
ima: add new critical data record to measure log trim
.../admin-guide/kernel-parameters.txt | 4 +
security/integrity/ima/Kconfig | 12 +
security/integrity/ima/ima.h | 4 +-
security/integrity/ima/ima_fs.c | 218 +++++++++++++++++-
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_queue.c | 96 ++++++++
6 files changed, 328 insertions(+), 8 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH v6] hwmon: add driver for ARCTIC Fan Controller
From: Guenter Roeck @ 2026-04-01 17:34 UTC (permalink / raw)
To: Aureo Serrano de Souza, linux-hwmon
Cc: linux, corbet, skhan, linux-doc, linux-kernel
In-Reply-To: <20260401153949.77488-1-aureo.serrano@arctic.de>
On 4/1/26 08:39, 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[] is also protected by this spinlock
> because reset_resume() clears it outside the hwmon core lock. The OUT
> report buffer is built and write_pending is armed under the same lock so
> that no reset_resume() can race with the pwm_duty[] snapshot. priv->buf
> is exclusively accessed by write(), which the hwmon core serializes.
>
> Signed-off-by: Aureo Serrano de Souza <aureo.serrano@arctic.de>
This must be a resend because it does not appear to be corrupted.
Please mention that in the future.
Sashiko still has a couple of comments:
https://sashiko.dev/#/patchset/20260401153949.77488-1-aureo.serrano%40arctic.de
Ignore the cache line comment. The concern about the redundant hid_device_io_stop()
seems real, though. Please check.
Thanks,
Guenter
> ---
> Thanks to Guenter Roeck and Thomas Weißschuh for the reviews.
>
> Changes since v5:
> - arctic_fan_probe(): switch from devm_hwmon_device_register_with_info()
> to hwmon_device_register_with_info(); store the returned pointer in
> priv->hwmon_dev for explicit teardown in remove()
> - arctic_fan_remove(): call hwmon_device_unregister(priv->hwmon_dev)
> before hid_device_io_stop/hid_hw_close/hid_hw_stop; this closes the
> use-after-free window where a concurrent sysfs write could call
> hid_hw_output_report() on an already-stopped device; matches the
> removal pattern used by nzxt-smart2 and aquacomputer_d5next
> - arctic_fan_write(): expand write_pending comment to document the
> residual theoretical late-ACK race (unfixable without a correlation
> ID in the device ACK report) and its practical impossibility (observed
> max ACK latency ~563 ms, timeout 1 s; a delay > 1 s indicates a
> non-functional device)
> - arctic_fan_reset_resume(), arctic_fan_read(), arctic_fan_write():
> extend in_report_lock coverage to pwm_duty[]; reset_resume() clears
> pwm_duty[] outside the hwmon core lock, so all paths that read or
> write pwm_duty[] now hold in_report_lock to prevent a data race
> during resume
> - arctic_fan_write(): build the OUT report buffer inside in_report_lock
> so reset_resume() cannot clear pwm_duty[] between the pwm_duty[]
> snapshot and the buffer write; this makes the lock coverage complete
>
> Changes since v4:
> - arctic_fan_write(): switch to wait_for_completion_timeout() (non-
> interruptible); eliminates the signal-interrupted write case of the
> late-ACK race that write_pending could not fully prevent
> - arctic_fan_write(): guard pwm_duty[channel] commit with
> ack_status == 0 check; a device error ACK (status 0x01) no longer
> silently poisons the cached duty used in future OUT reports
> - arctic_fan_probe()/remove(): replace devm_add_action_or_reset() +
> no-op remove() with explicit hid_device_io_stop/hid_hw_close/
> hid_hw_stop in remove(); devm_add_action_or_reset() was called after
> hdev->driver = NULL, causing a NULL deref in hid_hw_close() on unbind
> - add reset_resume callback: device resets PWM to hardware defaults on
> power loss during suspend; driver now clears cached pwm_duty[] on
> reset-resume so stale pre-suspend values are not re-sent as if valid
> - Documentation/hwmon/arctic_fan_controller.rst: document suspend/
> resume behaviour and the updated pwm[1-10] read semantics
>
> Changes since v3:
> - buf[]: upgrade from __aligned(8) to ____cacheline_aligned so the
> DMA buffer occupies its own cache line, preventing false sharing with
> adjacent fan_rpm[]/pwm_duty[] fields on non-coherent architectures
> - arctic_fan_write(): add write_pending flag (protected by
> in_report_lock) so raw_event() delivers ACKs only while a write is
> in flight
> - arctic_fan_write(): commit pwm_duty[channel] only after the device
> ACKs the command; a failed or timed-out write no longer leaves a
> stale value in the cached duty state
> - arctic_fan_probe(): start IO (hid_device_io_start) before registering
> with hwmon; previously a sysfs write arriving between hwmon
> registration and io_start could send an OUT report whose ACK would be
> discarded by the HID core, causing a spurious timeout
> - Documentation/hwmon/arctic_fan_controller.rst: document that cached
> PWM values start at 0 (hardware state unknown at probe) and that each
> OUT report carries all 10 channel values
>
> Changes since v2:
> - buf[]: add __aligned(8) for DMA safety
> - ARCTIC_ACK_TIMEOUT_MS: restore 1000 ms; note 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
> - 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..b5be88ae46
> --- /dev/null
> +++ b/Documentation/hwmon/arctic_fan_controller.rst
> @@ -0,0 +1,56 @@
> +.. 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 RPM values (bytes 11-30, 10 x uint16 LE).
> +Fan speed control is manual-only: the device does not change PWM
> +autonomously; it only applies a new duty cycle when it receives an OUT
> +report from the host.
> +
> +After the device applies an OUT report, it sends back a 2-byte ACK IN
> +report (Report ID 0x02, byte 1 = 0x00 on success) confirming the command
> +was applied.
> +
> +Usage notes
> +-----------
> +
> +Since it is a USB device, hotplug is supported. The device is autodetected.
> +
> +The device does not support GET_REPORT, so the driver cannot read back the
> +current hardware PWM state at probe time. The cached PWM values (readable
> +via pwm[1-10]) start at 0 and reflect only values that have been
> +successfully written. Because each OUT report carries all 10 channel values,
> +writing a single channel also sends the cached values for all other channels.
> +Users should set all channels to the desired values before relying on the
> +cached state.
> +
> +On system suspend, the device may lose power and reset its PWM channels to
> +hardware defaults. The driver clears its cached duty values on resume so
> +that reads reflect the unknown hardware state rather than stale pre-suspend
> +values. Userspace is responsible for re-applying the desired duty cycles
> +after resume.
> +
> +Sysfs entries
> +-------------
> +
> +================ ==============================================================
> +fan[1-10]_input Fan speed in RPM (read-only). Updated from IN reports at ~1 Hz.
> +pwm[1-10] PWM duty cycle (0-255). Write: sends an OUT report setting the
> + duty cycle (scaled from 0-255 to 0-100% for the device);
> + the cached value is updated only after the device ACKs the
> + command with a success status. Read: returns the last
> + successfully written value; initialized to 0 at driver load
> + and after resume (hardware state unknown).
> +================ ==============================================================
> 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..2bfb003f01
> --- /dev/null
> +++ b/drivers/hwmon/arctic_fan_controller.c
> @@ -0,0 +1,370 @@
> +// 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/cache.h>
> +#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/string.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;
> + struct device *hwmon_dev; /* stored for explicit unregister in remove() */
> + spinlock_t in_report_lock; /* protects fan_rpm, ack_status, write_pending, pwm_duty */
> + struct completion in_report_received; /* ACK (ID 0x02) received in raw_event */
> + int ack_status; /* 0 = OK, negative errno on device error */
> + bool write_pending; /* true while an OUT report ACK is in flight */
> + 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. Cache-line aligned so it occupies its own cache
> + * line, preventing DMA cache-coherency issues with adjacent fields
> + * (fan_rpm[], pwm_duty[]) on non-coherent architectures.
> + * Embedded in the devm_kzalloc'd struct so it is heap-allocated and
> + * passes usb_hcd_map_urb_for_dma(). Serialized by the hwmon core.
> + */
> + u8 buf[ARCTIC_REPORT_LEN] ____cacheline_aligned;
> +};
> +
> +/*
> + * 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);
> + /*
> + * Only deliver if a write is in flight. This prevents a
> + * late-arriving ACK from a timed-out write from erroneously
> + * satisfying a subsequent write's completion wait.
> + */
> + if (priv->write_pending) {
> + 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) {
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + *val = priv->pwm_duty[channel];
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> + 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);
> + u8 new_duty = (u8)clamp_val(val, 0, 255);
> + unsigned long flags;
> + unsigned long t;
> + int i, ret;
> +
> + /*
> + * Build the buffer and arm write_pending under in_report_lock so that
> + * reset_resume() cannot clear pwm_duty[] between the pwm_duty[] read
> + * and the buffer write, and raw_event() cannot deliver a stale ACK
> + * from a previous write into this write's completion.
> + *
> + * priv->buf is heap-allocated (embedded in the devm_kzalloc'd struct),
> + * satisfying usb_hcd_map_urb_for_dma(). Exclusively accessed by
> + * write() which the hwmon core serializes.
> + *
> + * pwm_duty[channel] is committed only after a positive device ACK so a
> + * failed or timed-out write does not corrupt the cached state.
> + *
> + * Residual theoretical race: if write A times out (write_pending
> + * cleared), write B sets write_pending = true, and a late ACK from
> + * write A—delayed beyond ARCTIC_ACK_TIMEOUT_MS—arrives during write
> + * B's pending window, it would falsely satisfy write B's completion.
> + * This cannot be prevented in driver code without protocol support
> + * (for example, a correlation ID echoed in the device ACK report).
> + * In testing, observed ACK latency stayed below the 1 s timeout
> + * (maximum ~563 ms over 500 iterations).
> + *
> + * The wait is non-interruptible so that a signal cannot cause write()
> + * to return early while the OUT report is already in flight; an
> + * interruptible early return would create the same late-ACK window
> + * without even the timeout guard.
> + * Serialized by the hwmon core: only one arctic_fan_write() at a time.
> + * Use irqsave to match the IRQ context in which raw_event may run.
> + */
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + priv->buf[0] = ARCTIC_OUTPUT_REPORT_ID;
> + for (i = 0; i < ARCTIC_NUM_FANS; i++) {
> + u8 d = i == channel ? new_duty : priv->pwm_duty[i];
> +
> + priv->buf[1 + i] = DIV_ROUND_CLOSEST((unsigned int)d * 100, 255);
> + }
> + priv->ack_status = -ETIMEDOUT;
> + priv->write_pending = true;
> + 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) {
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + priv->write_pending = false;
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> + return ret;
> + }
> +
> + t = wait_for_completion_timeout(&priv->in_report_received,
> + msecs_to_jiffies(ARCTIC_ACK_TIMEOUT_MS));
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + priv->write_pending = false;
> + /* Commit inside the lock so reset_resume() cannot race with this write */
> + if (t && priv->ack_status == 0)
> + priv->pwm_duty[channel] = new_duty;
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> +
> + 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 int arctic_fan_reset_resume(struct hid_device *hdev)
> +{
> + struct arctic_fan_data *priv = hid_get_drvdata(hdev);
> + unsigned long flags;
> +
> + /*
> + * The device resets its PWM channels to hardware defaults on power
> + * loss during suspend. Clear the cached duty values so they reflect
> + * the unknown hardware state, consistent with probe-time behaviour
> + * (the device has no GET_REPORT support). Hold in_report_lock so
> + * this does not race with a concurrent pwm read or write callback.
> + */
> + spin_lock_irqsave(&priv->in_report_lock, flags);
> + memset(priv->pwm_duty, 0, sizeof(priv->pwm_duty));
> + spin_unlock_irqrestore(&priv->in_report_lock, flags);
> + return 0;
> +}
> +
> +static int arctic_fan_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + struct arctic_fan_data *priv;
> + 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)
> + goto out_stop;
> +
> + /*
> + * Start IO before registering with hwmon. If IO were started after
> + * hwmon registration, a sysfs write arriving in that narrow window
> + * would send an OUT report but the ACK could not be delivered (the HID
> + * core discards events until io_started), causing a spurious timeout.
> + */
> + hid_device_io_start(hdev);
> +
> + /*
> + * Use the non-devm variant and store the pointer so remove() can
> + * call hwmon_device_unregister() before tearing down the HID
> + * transport. devm_hwmon_device_register_with_info() would defer
> + * unregistration until after remove() returns, leaving a window
> + * where a concurrent sysfs write could call hid_hw_output_report()
> + * on an already-stopped device (use-after-free).
> + */
> + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "arctic_fan",
> + priv, &arctic_fan_chip_info,
> + NULL);
> + if (IS_ERR(priv->hwmon_dev)) {
> + ret = PTR_ERR(priv->hwmon_dev);
> + goto out_close;
> + }
> +
> + return 0;
> +
> +out_close:
> + hid_device_io_stop(hdev);
> + hid_hw_close(hdev);
> +out_stop:
> + hid_hw_stop(hdev);
> + return ret;
> +}
> +
> +static void arctic_fan_remove(struct hid_device *hdev)
> +{
> + struct arctic_fan_data *priv = hid_get_drvdata(hdev);
> +
> + /*
> + * Unregister hwmon before stopping the HID transport. This removes
> + * the sysfs files and waits for any in-progress write() callback to
> + * return, so no hwmon op can call hid_hw_output_report() after
> + * hid_hw_stop() frees the underlying USB resources.
> + * Matches the pattern used by nzxt-smart2 and aquacomputer_d5next.
> + */
> + hwmon_device_unregister(priv->hwmon_dev);
> + hid_device_io_stop(hdev);
> + hid_hw_close(hdev);
> + hid_hw_stop(hdev);
> +}
> +
> +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,
> + .reset_resume = arctic_fan_reset_resume,
> +};
> +
> +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 v2] doc: Add CPU Isolation documentation
From: Steven Rostedt @ 2026-04-01 17:08 UTC (permalink / raw)
To: Frederic Weisbecker
Cc: Randy Dunlap, LKML, Anna-Maria Behnsen, Gabriele Monaco,
Ingo Molnar, Jonathan Corbet, Marcelo Tosatti, Marco Crivellari,
Michal Hocko, Paul E . McKenney, Peter Zijlstra, Phil Auld,
Thomas Gleixner, Valentin Schneider, Vlastimil Babka, Waiman Long,
linux-doc, Sebastian Andrzej Siewior, Bagas Sanjaya
In-Reply-To: <ac1HV1HLErp8GkZ6@localhost.localdomain>
On Wed, 1 Apr 2026 18:27:03 +0200
Frederic Weisbecker <frederic@kernel.org> wrote:
> > > +"CPU Isolation" means leaving a CPU exclusive to a given workload
> > > +without any undesired code interference from the kernel.
> > > +
> > > +Those interferences, commonly pointed out as "noise", can be triggered
> >
> > nit: "noise,"
>
> Thanks! I have applied all your suggestions, except this one for now because I don't
> really understand the typo rule behind. Any hint?
So this looks to be an American English thing (placing commas within the
quote), but from what I read, British English places the comma outside the
quote.
Here's one case I much rather go the British English way. This also means
it's only incorrect to Americans ;-)
-- Steve
^ permalink raw reply
* Re: [PATCH net-next v3 2/3] dpll: add frequency monitoring callback ops
From: Vadim Fedorenko @ 2026-04-01 16:37 UTC (permalink / raw)
To: Ivan Vecera, netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Jonathan Corbet,
Michal Schmidt, Paolo Abeni, Petr Oros, Prathosh Satish,
Shuah Khan, Simon Horman, linux-doc, linux-kernel
In-Reply-To: <CE3CDF40-CA7B-43A5-9DBD-A04FA37F4E57@redhat.com>
On 01/04/2026 17:29, Ivan Vecera wrote:
> Hi Vadim,
>
> 1. dubna 2026 16:47:21 SELČ, Vadim Fedorenko <vadim.fedorenko@linux.dev> napsal:
>> On 01/04/2026 10:12, Ivan Vecera wrote:
>>> Add new callback operations for a dpll device:
>>> - freq_monitor_get(..) - to obtain current state of frequency monitor
>>> feature from dpll device,
>>> - freq_monitor_set(..) - to allow feature configuration.
>>>
>>> Add new callback operation for a dpll pin:
>>> - measured_freq_get(..) - to obtain the measured frequency in mHz.
>>>
>>> Obtain the feature state value using the get callback and provide it to
>>> the user if the device driver implements callbacks. The measured_freq_get
>>> pin callback is only invoked when the frequency monitor is enabled.
>>> The freq_monitor_get device callback is required when measured_freq_get
>>> is provided by the driver.
>>>
>>> Execute the set callback upon user requests.
>>>
>>> Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
>>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>>> ---
>>> Changes v2 -> v3:
>>> - Made freq_monitor_get required when measured_freq_get is present (Jakub)
>>>
>>> Changes v1 -> v2:
>>> - Renamed actual-frequency to measured-frequency (Vadim)
>>> ---
>>> drivers/dpll/dpll_netlink.c | 92 +++++++++++++++++++++++++++++++++++++
>>> include/linux/dpll.h | 10 ++++
>>> 2 files changed, 102 insertions(+)
>>>
>>> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
>>> index 83cbd64abf5a4..576d0cd074bd4 100644
>>> --- a/drivers/dpll/dpll_netlink.c
>>> +++ b/drivers/dpll/dpll_netlink.c
>>> @@ -175,6 +175,26 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll,
>>> return 0;
>>> }
>>> +static int
>>> +dpll_msg_add_freq_monitor(struct sk_buff *msg, struct dpll_device *dpll,
>>> + struct netlink_ext_ack *extack)
>>> +{
>>> + const struct dpll_device_ops *ops = dpll_device_ops(dpll);
>>> + enum dpll_feature_state state;
>>> + int ret;
>>> +
>>> + if (ops->freq_monitor_set && ops->freq_monitor_get) {
>>> + ret = ops->freq_monitor_get(dpll, dpll_priv(dpll),
>>> + &state, extack);
>>> + if (ret)
>>> + return ret;
>>> + if (nla_put_u32(msg, DPLL_A_FREQUENCY_MONITOR, state))
>>> + return -EMSGSIZE;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> static int
>>> dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg,
>>> struct dpll_device *dpll,
>>> @@ -400,6 +420,40 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
>>> ffo);
>>> }
>>> +static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
>>> + struct dpll_pin_ref *ref,
>>> + struct netlink_ext_ack *extack)
>>> +{
>>> + const struct dpll_device_ops *dev_ops = dpll_device_ops(ref->dpll);
>>> + const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
>>> + struct dpll_device *dpll = ref->dpll;
>>> + enum dpll_feature_state state;
>>> + u64 measured_freq;
>>> + int ret;
>>> +
>>> + if (!ops->measured_freq_get)
>>> + return 0;
>>> + if (WARN_ON(!dev_ops->freq_monitor_get))
>>> + return -EINVAL;
>>
>> I think pin registration function has to be adjusted to not allow
>> measured_freq_get() callback if device doesn't have freq_monitor_get()
>> callback (or both freq_monitor_{s,g}et). Then this defensive part can
>> be completely removed.
>
> Ok, make sense... Will move such check to pin registration function...
>
> Q: with WARN_ON or without?
Well, we have to provide reason for blocking device registration
somehow, and the only way to this is via "WARN_ON"...
>
> Thanks
> Ivan
>
^ permalink raw reply
* Re: [PATCH net-next v3 2/3] dpll: add frequency monitoring callback ops
From: Ivan Vecera @ 2026-04-01 16:29 UTC (permalink / raw)
To: Vadim Fedorenko, netdev
Cc: Arkadiusz Kubalewski, David S. Miller, Donald Hunter,
Eric Dumazet, Jakub Kicinski, Jiri Pirko, Jonathan Corbet,
Michal Schmidt, Paolo Abeni, Petr Oros, Prathosh Satish,
Shuah Khan, Simon Horman, linux-doc, linux-kernel
In-Reply-To: <ccb93d19-19a9-4dd9-8ac7-e0d41dbb884d@linux.dev>
Hi Vadim,
1. dubna 2026 16:47:21 SELČ, Vadim Fedorenko <vadim.fedorenko@linux.dev> napsal:
>On 01/04/2026 10:12, Ivan Vecera wrote:
>> Add new callback operations for a dpll device:
>> - freq_monitor_get(..) - to obtain current state of frequency monitor
>> feature from dpll device,
>> - freq_monitor_set(..) - to allow feature configuration.
>>
>> Add new callback operation for a dpll pin:
>> - measured_freq_get(..) - to obtain the measured frequency in mHz.
>>
>> Obtain the feature state value using the get callback and provide it to
>> the user if the device driver implements callbacks. The measured_freq_get
>> pin callback is only invoked when the frequency monitor is enabled.
>> The freq_monitor_get device callback is required when measured_freq_get
>> is provided by the driver.
>>
>> Execute the set callback upon user requests.
>>
>> Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
>> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
>> ---
>> Changes v2 -> v3:
>> - Made freq_monitor_get required when measured_freq_get is present (Jakub)
>>
>> Changes v1 -> v2:
>> - Renamed actual-frequency to measured-frequency (Vadim)
>> ---
>> drivers/dpll/dpll_netlink.c | 92 +++++++++++++++++++++++++++++++++++++
>> include/linux/dpll.h | 10 ++++
>> 2 files changed, 102 insertions(+)
>>
>> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
>> index 83cbd64abf5a4..576d0cd074bd4 100644
>> --- a/drivers/dpll/dpll_netlink.c
>> +++ b/drivers/dpll/dpll_netlink.c
>> @@ -175,6 +175,26 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll,
>> return 0;
>> }
>> +static int
>> +dpll_msg_add_freq_monitor(struct sk_buff *msg, struct dpll_device *dpll,
>> + struct netlink_ext_ack *extack)
>> +{
>> + const struct dpll_device_ops *ops = dpll_device_ops(dpll);
>> + enum dpll_feature_state state;
>> + int ret;
>> +
>> + if (ops->freq_monitor_set && ops->freq_monitor_get) {
>> + ret = ops->freq_monitor_get(dpll, dpll_priv(dpll),
>> + &state, extack);
>> + if (ret)
>> + return ret;
>> + if (nla_put_u32(msg, DPLL_A_FREQUENCY_MONITOR, state))
>> + return -EMSGSIZE;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> static int
>> dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg,
>> struct dpll_device *dpll,
>> @@ -400,6 +420,40 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
>> ffo);
>> }
>> +static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
>> + struct dpll_pin_ref *ref,
>> + struct netlink_ext_ack *extack)
>> +{
>> + const struct dpll_device_ops *dev_ops = dpll_device_ops(ref->dpll);
>> + const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
>> + struct dpll_device *dpll = ref->dpll;
>> + enum dpll_feature_state state;
>> + u64 measured_freq;
>> + int ret;
>> +
>> + if (!ops->measured_freq_get)
>> + return 0;
>> + if (WARN_ON(!dev_ops->freq_monitor_get))
>> + return -EINVAL;
>
>I think pin registration function has to be adjusted to not allow
>measured_freq_get() callback if device doesn't have freq_monitor_get()
>callback (or both freq_monitor_{s,g}et). Then this defensive part can
>be completely removed.
Ok, make sense... Will move such check to pin registration function...
Q: with WARN_ON or without?
Thanks
Ivan
^ permalink raw reply
* Re: [PATCH v2] doc: Add CPU Isolation documentation
From: Frederic Weisbecker @ 2026-04-01 16:27 UTC (permalink / raw)
To: Randy Dunlap
Cc: LKML, Anna-Maria Behnsen, Gabriele Monaco, Ingo Molnar,
Jonathan Corbet, Marcelo Tosatti, Marco Crivellari, Michal Hocko,
Paul E . McKenney, Peter Zijlstra, Phil Auld, Steven Rostedt,
Thomas Gleixner, Valentin Schneider, Vlastimil Babka, Waiman Long,
linux-doc, Sebastian Andrzej Siewior, Bagas Sanjaya
In-Reply-To: <6d113021-6208-4dcc-a209-a2317d680e3f@infradead.org>
Le Thu, Mar 26, 2026 at 02:42:32PM -0700, Randy Dunlap a écrit :
> (Just some small comments -- take them or not.)
>
> On 3/26/26 7:00 AM, Frederic Weisbecker wrote:
> > nohz_full was introduced in v3.10 in 2013, which means this
> > documentation is overdue for 13 years.
> >
> > Fortunately Paul wrote a part of the needed documentation a while ago,
> > especially concerning nohz_full in Documentation/timers/no_hz.rst and
> > also about per-CPU kthreads in
> > Documentation/admin-guide/kernel-per-CPU-kthreads.rst
> >
> > Introduce a new page that gives an overview of CPU isolation in general.
> >
> > Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
> > ---
> > v2:
> > - Fix links and code blocks (Bagas and Sebastian)
> > - Isolation is not only about userspace, rephrase accordingly (Valentin)
> > - Paste BIOS issues suggestion from Valentin
> > - Include the whole rtla suite (Valentin)
> > - Rephrase a few details (Waiman)
> > - Talk about RCU induced overhead rather than slower RCU (Sebastian)
> >
> > Documentation/admin-guide/cpu-isolation.rst | 357 ++++++++++++++++++++
> > Documentation/admin-guide/index.rst | 1 +
> > 2 files changed, 358 insertions(+)
> > create mode 100644 Documentation/admin-guide/cpu-isolation.rst
> >
> > diff --git a/Documentation/admin-guide/cpu-isolation.rst b/Documentation/admin-guide/cpu-isolation.rst
> > new file mode 100644
> > index 000000000000..886dec79b056
> > --- /dev/null
> > +++ b/Documentation/admin-guide/cpu-isolation.rst
> > @@ -0,0 +1,357 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +=============
> > +CPU Isolation
> > +=============
> > +
> > +Introduction
> > +============
> > +
> > +"CPU Isolation" means leaving a CPU exclusive to a given workload
> > +without any undesired code interference from the kernel.
> > +
> > +Those interferences, commonly pointed out as "noise", can be triggered
>
> nit: "noise,"
Thanks! I have applied all your suggestions, except this one for now because I don't
really understand the typo rule behind. Any hint?
--
Frederic Weisbecker
SUSE Labs
^ permalink raw reply
* Re: [PATCH v6 3/4] RISC-V: KVM: Detect and expose supported HGATP G-stage modes
From: Anup Patel @ 2026-04-01 16:05 UTC (permalink / raw)
To: fangyu.yu
Cc: pbonzini, corbet, atish.patra, pjw, palmer, aou, alex, skhan,
guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
linux-riscv, linux-kernel
In-Reply-To: <20260330122601.22140-4-fangyu.yu@linux.alibaba.com>
On Mon, Mar 30, 2026 at 5:56 PM <fangyu.yu@linux.alibaba.com> wrote:
>
> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>
> Extend kvm_riscv_gstage_mode_detect() to probe all HGATP.MODE values
> supported by the host and record them in a bitmask. Keep tracking the
> maximum supported G-stage page table level for existing internal users.
>
> Also provide lightweight helpers to retrieve the supported-mode bitmask
> and validate a requested HGATP.MODE against it.
>
> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
> Reviewed-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
> ---
> arch/riscv/include/asm/kvm_gstage.h | 11 ++++++++
> arch/riscv/kvm/gstage.c | 43 +++++++++++++++--------------
> 2 files changed, 34 insertions(+), 20 deletions(-)
>
> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> index 70d9d483365e..bbf8f45c6563 100644
> --- a/arch/riscv/include/asm/kvm_gstage.h
> +++ b/arch/riscv/include/asm/kvm_gstage.h
> @@ -31,6 +31,7 @@ struct kvm_gstage_mapping {
> #endif
>
> extern unsigned long kvm_riscv_gstage_max_pgd_levels;
> +extern u32 kvm_riscv_gstage_supported_mode_mask;
>
> #define kvm_riscv_gstage_pgd_xbits 2
> #define kvm_riscv_gstage_pgd_size (1UL << (HGATP_PAGE_SHIFT + kvm_riscv_gstage_pgd_xbits))
> @@ -102,4 +103,14 @@ static inline void kvm_riscv_gstage_init(struct kvm_gstage *gstage, struct kvm *
> gstage->pgd_levels = kvm->arch.pgd_levels;
> }
>
> +static inline u32 kvm_riscv_get_hgatp_mode_mask(void)
> +{
> + return kvm_riscv_gstage_supported_mode_mask;
> +}
> +
> +static inline bool kvm_riscv_hgatp_mode_is_valid(unsigned long mode)
> +{
> + return kvm_riscv_gstage_supported_mode_mask & BIT(mode);
> +}
> +
> #endif
> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> index 7c4c34bc191b..459041255c14 100644
> --- a/arch/riscv/kvm/gstage.c
> +++ b/arch/riscv/kvm/gstage.c
> @@ -16,6 +16,8 @@ unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 3;
> #else
> unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 2;
> #endif
> +/* Bitmask of supported HGATP.MODE encodings (BIT(HGATP_MODE_*)). */
> +u32 kvm_riscv_gstage_supported_mode_mask __ro_after_init;
>
> #define gstage_pte_leaf(__ptep) \
> (pte_val(*(__ptep)) & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC))
> @@ -315,42 +317,43 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
> }
> }
>
> +static bool __init kvm_riscv_hgatp_mode_supported(unsigned long mode)
> +{
> + csr_write(CSR_HGATP, mode << HGATP_MODE_SHIFT);
> + return ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == mode);
> +}
> +
> void __init kvm_riscv_gstage_mode_detect(void)
> {
> + kvm_riscv_gstage_supported_mode_mask = 0;
> + kvm_riscv_gstage_max_pgd_levels = 0;
> +
> #ifdef CONFIG_64BIT
> - /* Try Sv57x4 G-stage mode */
> - csr_write(CSR_HGATP, HGATP_MODE_SV57X4 << HGATP_MODE_SHIFT);
> - if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV57X4) {
> - kvm_riscv_gstage_max_pgd_levels = 5;
> - goto done;
> + /* Try Sv39x4 G-stage mode */
> + if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV39X4)) {
> + kvm_riscv_gstage_supported_mode_mask |= BIT(HGATP_MODE_SV39X4);
> + kvm_riscv_gstage_max_pgd_levels = 3;
> }
>
> /* Try Sv48x4 G-stage mode */
> - csr_write(CSR_HGATP, HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT);
> - if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV48X4) {
> + if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV48X4)) {
> + kvm_riscv_gstage_supported_mode_mask |= BIT(HGATP_MODE_SV48X4);
> kvm_riscv_gstage_max_pgd_levels = 4;
> - goto done;
Keep the original approach until then NACK to this series.
Regards,
Anup
> }
>
> - /* Try Sv39x4 G-stage mode */
> - csr_write(CSR_HGATP, HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT);
> - if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV39X4) {
> - kvm_riscv_gstage_max_pgd_levels = 3;
> - goto done;
> + /* Try Sv57x4 G-stage mode */
> + if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV57X4)) {
> + kvm_riscv_gstage_supported_mode_mask |= BIT(HGATP_MODE_SV57X4);
> + kvm_riscv_gstage_max_pgd_levels = 5;
> }
> #else /* CONFIG_32BIT */
> /* Try Sv32x4 G-stage mode */
> - csr_write(CSR_HGATP, HGATP_MODE_SV32X4 << HGATP_MODE_SHIFT);
> - if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV32X4) {
> + if (kvm_riscv_hgatp_mode_supported(HGATP_MODE_SV32X4)) {
> + kvm_riscv_gstage_supported_mode_mask |= BIT(HGATP_MODE_SV32X4);
> kvm_riscv_gstage_max_pgd_levels = 2;
> - goto done;
> }
> #endif
>
> - /* KVM depends on !HGATP_MODE_OFF */
> - kvm_riscv_gstage_max_pgd_levels = 0;
> -
> -done:
> csr_write(CSR_HGATP, 0);
> kvm_riscv_local_hfence_gvma_all();
> }
> --
> 2.50.1
>
^ permalink raw reply
* Re: [PATCH v6 2/4] RISC-V: KVM: Cache gstage pgd_levels in struct kvm_gstage
From: Anup Patel @ 2026-04-01 16:03 UTC (permalink / raw)
To: fangyu.yu
Cc: pbonzini, corbet, atish.patra, pjw, palmer, aou, alex, skhan,
guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
linux-riscv, linux-kernel
In-Reply-To: <20260330122601.22140-3-fangyu.yu@linux.alibaba.com>
On Mon, Mar 30, 2026 at 5:56 PM <fangyu.yu@linux.alibaba.com> wrote:
>
> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>
> Gstage page-table helpers frequently chase gstage->kvm->arch to
> fetch pgd_levels. This adds noise and repeats the same dereference
> chain in hot paths.
>
> Add pgd_levels to struct kvm_gstage and initialize it from kvm->arch
> when setting up a gstage instance. Introduce kvm_riscv_gstage_init()
> to centralize initialization and switch gstage code to use
> gstage->pgd_levels.
>
> Suggested-by: Anup Patel <anup@brainfault.org>
> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
LGTM.
Reviewed-by: Anup Patel <anup@brainfault.org>
Thanks,
Anup
> ---
> arch/riscv/include/asm/kvm_gstage.h | 10 ++++++
> arch/riscv/kvm/gstage.c | 10 +++---
> arch/riscv/kvm/mmu.c | 50 ++++++-----------------------
> 3 files changed, 25 insertions(+), 45 deletions(-)
>
> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> index 5aa58d1f692a..70d9d483365e 100644
> --- a/arch/riscv/include/asm/kvm_gstage.h
> +++ b/arch/riscv/include/asm/kvm_gstage.h
> @@ -15,6 +15,7 @@ struct kvm_gstage {
> #define KVM_GSTAGE_FLAGS_LOCAL BIT(0)
> unsigned long vmid;
> pgd_t *pgd;
> + unsigned long pgd_levels;
> };
>
> struct kvm_gstage_mapping {
> @@ -92,4 +93,13 @@ static inline unsigned long kvm_riscv_gstage_mode(unsigned long pgd_levels)
> }
> }
>
> +static inline void kvm_riscv_gstage_init(struct kvm_gstage *gstage, struct kvm *kvm)
> +{
> + gstage->kvm = kvm;
> + gstage->flags = 0;
> + gstage->vmid = READ_ONCE(kvm->arch.vmid.vmid);
> + gstage->pgd = kvm->arch.pgd;
> + gstage->pgd_levels = kvm->arch.pgd_levels;
> +}
> +
> #endif
> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> index 4beb9322fe76..7c4c34bc191b 100644
> --- a/arch/riscv/kvm/gstage.c
> +++ b/arch/riscv/kvm/gstage.c
> @@ -26,7 +26,7 @@ static inline unsigned long gstage_pte_index(struct kvm_gstage *gstage,
> unsigned long mask;
> unsigned long shift = HGATP_PAGE_SHIFT + (kvm_riscv_gstage_index_bits * level);
>
> - if (level == gstage->kvm->arch.pgd_levels - 1)
> + if (level == gstage->pgd_levels - 1)
> mask = (PTRS_PER_PTE * (1UL << kvm_riscv_gstage_pgd_xbits)) - 1;
> else
> mask = PTRS_PER_PTE - 1;
> @@ -45,7 +45,7 @@ static int gstage_page_size_to_level(struct kvm_gstage *gstage, unsigned long pa
> u32 i;
> unsigned long psz = 1UL << 12;
>
> - for (i = 0; i < gstage->kvm->arch.pgd_levels; i++) {
> + for (i = 0; i < gstage->pgd_levels; i++) {
> if (page_size == (psz << (i * kvm_riscv_gstage_index_bits))) {
> *out_level = i;
> return 0;
> @@ -58,7 +58,7 @@ static int gstage_page_size_to_level(struct kvm_gstage *gstage, unsigned long pa
> static int gstage_level_to_page_order(struct kvm_gstage *gstage, u32 level,
> unsigned long *out_pgorder)
> {
> - if (gstage->kvm->arch.pgd_levels < level)
> + if (gstage->pgd_levels < level)
> return -EINVAL;
>
> *out_pgorder = 12 + (level * kvm_riscv_gstage_index_bits);
> @@ -83,7 +83,7 @@ bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
> pte_t **ptepp, u32 *ptep_level)
> {
> pte_t *ptep;
> - u32 current_level = gstage->kvm->arch.pgd_levels - 1;
> + u32 current_level = gstage->pgd_levels - 1;
>
> *ptep_level = current_level;
> ptep = (pte_t *)gstage->pgd;
> @@ -127,7 +127,7 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
> struct kvm_mmu_memory_cache *pcache,
> const struct kvm_gstage_mapping *map)
> {
> - u32 current_level = gstage->kvm->arch.pgd_levels - 1;
> + u32 current_level = gstage->pgd_levels - 1;
> pte_t *next_ptep = (pte_t *)gstage->pgd;
> pte_t *ptep = &next_ptep[gstage_pte_index(gstage, map->addr, current_level)];
>
> diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
> index fbcdd75cb9af..2d3def024270 100644
> --- a/arch/riscv/kvm/mmu.c
> +++ b/arch/riscv/kvm/mmu.c
> @@ -24,10 +24,7 @@ static void mmu_wp_memory_region(struct kvm *kvm, int slot)
> phys_addr_t end = (memslot->base_gfn + memslot->npages) << PAGE_SHIFT;
> struct kvm_gstage gstage;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
>
> spin_lock(&kvm->mmu_lock);
> kvm_riscv_gstage_wp_range(&gstage, start, end);
> @@ -49,10 +46,7 @@ int kvm_riscv_mmu_ioremap(struct kvm *kvm, gpa_t gpa, phys_addr_t hpa,
> struct kvm_gstage_mapping map;
> struct kvm_gstage gstage;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
>
> end = (gpa + size + PAGE_SIZE - 1) & PAGE_MASK;
> pfn = __phys_to_pfn(hpa);
> @@ -89,10 +83,7 @@ void kvm_riscv_mmu_iounmap(struct kvm *kvm, gpa_t gpa, unsigned long size)
> {
> struct kvm_gstage gstage;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
>
> spin_lock(&kvm->mmu_lock);
> kvm_riscv_gstage_unmap_range(&gstage, gpa, size, false);
> @@ -109,10 +100,7 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
> phys_addr_t end = (base_gfn + __fls(mask) + 1) << PAGE_SHIFT;
> struct kvm_gstage gstage;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
>
> kvm_riscv_gstage_wp_range(&gstage, start, end);
> }
> @@ -141,10 +129,7 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
> phys_addr_t size = slot->npages << PAGE_SHIFT;
> struct kvm_gstage gstage;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
>
> spin_lock(&kvm->mmu_lock);
> kvm_riscv_gstage_unmap_range(&gstage, gpa, size, false);
> @@ -250,10 +235,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
> if (!kvm->arch.pgd)
> return false;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
> mmu_locked = spin_trylock(&kvm->mmu_lock);
> kvm_riscv_gstage_unmap_range(&gstage, range->start << PAGE_SHIFT,
> (range->end - range->start) << PAGE_SHIFT,
> @@ -275,10 +257,7 @@ bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
>
> WARN_ON(size != PAGE_SIZE && size != PMD_SIZE && size != PUD_SIZE);
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
> if (!kvm_riscv_gstage_get_leaf(&gstage, range->start << PAGE_SHIFT,
> &ptep, &ptep_level))
> return false;
> @@ -298,10 +277,7 @@ bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
>
> WARN_ON(size != PAGE_SIZE && size != PMD_SIZE && size != PUD_SIZE);
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
> if (!kvm_riscv_gstage_get_leaf(&gstage, range->start << PAGE_SHIFT,
> &ptep, &ptep_level))
> return false;
> @@ -463,10 +439,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
> struct kvm_gstage gstage;
> struct page *page;
>
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
>
> /* Setup initial state of output mapping */
> memset(out_map, 0, sizeof(*out_map));
> @@ -587,10 +560,7 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
>
> spin_lock(&kvm->mmu_lock);
> if (kvm->arch.pgd) {
> - gstage.kvm = kvm;
> - gstage.flags = 0;
> - gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> - gstage.pgd = kvm->arch.pgd;
> + kvm_riscv_gstage_init(&gstage, kvm);
> kvm_riscv_gstage_unmap_range(&gstage, 0UL,
> kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels), false);
> pgd = READ_ONCE(kvm->arch.pgd);
> --
> 2.50.1
>
^ permalink raw reply
* Re: [PATCH v6 1/4] RISC-V: KVM: Support runtime configuration for per-VM's HGATP mode
From: Anup Patel @ 2026-04-01 16:02 UTC (permalink / raw)
To: fangyu.yu
Cc: pbonzini, corbet, atish.patra, pjw, palmer, aou, alex, skhan,
guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
linux-riscv, linux-kernel
In-Reply-To: <20260330122601.22140-2-fangyu.yu@linux.alibaba.com>
On Mon, Mar 30, 2026 at 5:56 PM <fangyu.yu@linux.alibaba.com> wrote:
>
> From: Fangyu Yu <fangyu.yu@linux.alibaba.com>
>
> Introduces one per-VM architecture-specific fields to support runtime
> configuration of the G-stage page table format:
>
> - kvm->arch.pgd_levels: the corresponding number of page table levels
> for the selected mode.
>
> These fields replace the previous global variables
> kvm_riscv_gstage_mode and kvm_riscv_gstage_pgd_levels, enabling different
> virtual machines to independently select their G-stage page table format
> instead of being forced to share the maximum mode detected by the kernel
> at boot time.
>
> Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
> Reviewed-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
LGTM.
Reviewed-by: Anup Patel <anup@brainfault.org>
Thanks,
Anup
> ---
> arch/riscv/include/asm/kvm_gstage.h | 37 ++++++++++++----
> arch/riscv/include/asm/kvm_host.h | 1 +
> arch/riscv/kvm/gstage.c | 65 ++++++++++++++---------------
> arch/riscv/kvm/main.c | 12 +++---
> arch/riscv/kvm/mmu.c | 20 +++++----
> arch/riscv/kvm/vm.c | 2 +-
> arch/riscv/kvm/vmid.c | 3 +-
> 7 files changed, 83 insertions(+), 57 deletions(-)
>
> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> index 595e2183173e..5aa58d1f692a 100644
> --- a/arch/riscv/include/asm/kvm_gstage.h
> +++ b/arch/riscv/include/asm/kvm_gstage.h
> @@ -29,16 +29,22 @@ struct kvm_gstage_mapping {
> #define kvm_riscv_gstage_index_bits 10
> #endif
>
> -extern unsigned long kvm_riscv_gstage_mode;
> -extern unsigned long kvm_riscv_gstage_pgd_levels;
> +extern unsigned long kvm_riscv_gstage_max_pgd_levels;
>
> #define kvm_riscv_gstage_pgd_xbits 2
> #define kvm_riscv_gstage_pgd_size (1UL << (HGATP_PAGE_SHIFT + kvm_riscv_gstage_pgd_xbits))
> -#define kvm_riscv_gstage_gpa_bits (HGATP_PAGE_SHIFT + \
> - (kvm_riscv_gstage_pgd_levels * \
> - kvm_riscv_gstage_index_bits) + \
> - kvm_riscv_gstage_pgd_xbits)
> -#define kvm_riscv_gstage_gpa_size ((gpa_t)(1ULL << kvm_riscv_gstage_gpa_bits))
> +
> +static inline unsigned long kvm_riscv_gstage_gpa_bits(unsigned long pgd_levels)
> +{
> + return (HGATP_PAGE_SHIFT +
> + pgd_levels * kvm_riscv_gstage_index_bits +
> + kvm_riscv_gstage_pgd_xbits);
> +}
> +
> +static inline gpa_t kvm_riscv_gstage_gpa_size(unsigned long pgd_levels)
> +{
> + return BIT_ULL(kvm_riscv_gstage_gpa_bits(pgd_levels));
> +}
>
> bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
> pte_t **ptepp, u32 *ptep_level);
> @@ -69,4 +75,21 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
>
> void kvm_riscv_gstage_mode_detect(void);
>
> +static inline unsigned long kvm_riscv_gstage_mode(unsigned long pgd_levels)
> +{
> + switch (pgd_levels) {
> + case 2:
> + return HGATP_MODE_SV32X4;
> + case 3:
> + return HGATP_MODE_SV39X4;
> + case 4:
> + return HGATP_MODE_SV48X4;
> + case 5:
> + return HGATP_MODE_SV57X4;
> + default:
> + WARN_ON_ONCE(1);
> + return HGATP_MODE_OFF;
> + }
> +}
> +
> #endif
> diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
> index 24585304c02b..478f699e9dec 100644
> --- a/arch/riscv/include/asm/kvm_host.h
> +++ b/arch/riscv/include/asm/kvm_host.h
> @@ -94,6 +94,7 @@ struct kvm_arch {
> /* G-stage page table */
> pgd_t *pgd;
> phys_addr_t pgd_phys;
> + unsigned long pgd_levels;
>
> /* Guest Timer */
> struct kvm_guest_timer timer;
> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> index b67d60d722c2..4beb9322fe76 100644
> --- a/arch/riscv/kvm/gstage.c
> +++ b/arch/riscv/kvm/gstage.c
> @@ -12,22 +12,21 @@
> #include <asm/kvm_gstage.h>
>
> #ifdef CONFIG_64BIT
> -unsigned long kvm_riscv_gstage_mode __ro_after_init = HGATP_MODE_SV39X4;
> -unsigned long kvm_riscv_gstage_pgd_levels __ro_after_init = 3;
> +unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 3;
> #else
> -unsigned long kvm_riscv_gstage_mode __ro_after_init = HGATP_MODE_SV32X4;
> -unsigned long kvm_riscv_gstage_pgd_levels __ro_after_init = 2;
> +unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 2;
> #endif
>
> #define gstage_pte_leaf(__ptep) \
> (pte_val(*(__ptep)) & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC))
>
> -static inline unsigned long gstage_pte_index(gpa_t addr, u32 level)
> +static inline unsigned long gstage_pte_index(struct kvm_gstage *gstage,
> + gpa_t addr, u32 level)
> {
> unsigned long mask;
> unsigned long shift = HGATP_PAGE_SHIFT + (kvm_riscv_gstage_index_bits * level);
>
> - if (level == (kvm_riscv_gstage_pgd_levels - 1))
> + if (level == gstage->kvm->arch.pgd_levels - 1)
> mask = (PTRS_PER_PTE * (1UL << kvm_riscv_gstage_pgd_xbits)) - 1;
> else
> mask = PTRS_PER_PTE - 1;
> @@ -40,12 +39,13 @@ static inline unsigned long gstage_pte_page_vaddr(pte_t pte)
> return (unsigned long)pfn_to_virt(__page_val_to_pfn(pte_val(pte)));
> }
>
> -static int gstage_page_size_to_level(unsigned long page_size, u32 *out_level)
> +static int gstage_page_size_to_level(struct kvm_gstage *gstage, unsigned long page_size,
> + u32 *out_level)
> {
> u32 i;
> unsigned long psz = 1UL << 12;
>
> - for (i = 0; i < kvm_riscv_gstage_pgd_levels; i++) {
> + for (i = 0; i < gstage->kvm->arch.pgd_levels; i++) {
> if (page_size == (psz << (i * kvm_riscv_gstage_index_bits))) {
> *out_level = i;
> return 0;
> @@ -55,21 +55,23 @@ static int gstage_page_size_to_level(unsigned long page_size, u32 *out_level)
> return -EINVAL;
> }
>
> -static int gstage_level_to_page_order(u32 level, unsigned long *out_pgorder)
> +static int gstage_level_to_page_order(struct kvm_gstage *gstage, u32 level,
> + unsigned long *out_pgorder)
> {
> - if (kvm_riscv_gstage_pgd_levels < level)
> + if (gstage->kvm->arch.pgd_levels < level)
> return -EINVAL;
>
> *out_pgorder = 12 + (level * kvm_riscv_gstage_index_bits);
> return 0;
> }
>
> -static int gstage_level_to_page_size(u32 level, unsigned long *out_pgsize)
> +static int gstage_level_to_page_size(struct kvm_gstage *gstage, u32 level,
> + unsigned long *out_pgsize)
> {
> int rc;
> unsigned long page_order = PAGE_SHIFT;
>
> - rc = gstage_level_to_page_order(level, &page_order);
> + rc = gstage_level_to_page_order(gstage, level, &page_order);
> if (rc)
> return rc;
>
> @@ -81,11 +83,11 @@ bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
> pte_t **ptepp, u32 *ptep_level)
> {
> pte_t *ptep;
> - u32 current_level = kvm_riscv_gstage_pgd_levels - 1;
> + u32 current_level = gstage->kvm->arch.pgd_levels - 1;
>
> *ptep_level = current_level;
> ptep = (pte_t *)gstage->pgd;
> - ptep = &ptep[gstage_pte_index(addr, current_level)];
> + ptep = &ptep[gstage_pte_index(gstage, addr, current_level)];
> while (ptep && pte_val(ptep_get(ptep))) {
> if (gstage_pte_leaf(ptep)) {
> *ptep_level = current_level;
> @@ -97,7 +99,7 @@ bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
> current_level--;
> *ptep_level = current_level;
> ptep = (pte_t *)gstage_pte_page_vaddr(ptep_get(ptep));
> - ptep = &ptep[gstage_pte_index(addr, current_level)];
> + ptep = &ptep[gstage_pte_index(gstage, addr, current_level)];
> } else {
> ptep = NULL;
> }
> @@ -110,7 +112,7 @@ static void gstage_tlb_flush(struct kvm_gstage *gstage, u32 level, gpa_t addr)
> {
> unsigned long order = PAGE_SHIFT;
>
> - if (gstage_level_to_page_order(level, &order))
> + if (gstage_level_to_page_order(gstage, level, &order))
> return;
> addr &= ~(BIT(order) - 1);
>
> @@ -125,9 +127,9 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
> struct kvm_mmu_memory_cache *pcache,
> const struct kvm_gstage_mapping *map)
> {
> - u32 current_level = kvm_riscv_gstage_pgd_levels - 1;
> + u32 current_level = gstage->kvm->arch.pgd_levels - 1;
> pte_t *next_ptep = (pte_t *)gstage->pgd;
> - pte_t *ptep = &next_ptep[gstage_pte_index(map->addr, current_level)];
> + pte_t *ptep = &next_ptep[gstage_pte_index(gstage, map->addr, current_level)];
>
> if (current_level < map->level)
> return -EINVAL;
> @@ -151,7 +153,7 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
> }
>
> current_level--;
> - ptep = &next_ptep[gstage_pte_index(map->addr, current_level)];
> + ptep = &next_ptep[gstage_pte_index(gstage, map->addr, current_level)];
> }
>
> if (pte_val(*ptep) != pte_val(map->pte)) {
> @@ -175,7 +177,7 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
> out_map->addr = gpa;
> out_map->level = 0;
>
> - ret = gstage_page_size_to_level(page_size, &out_map->level);
> + ret = gstage_page_size_to_level(gstage, page_size, &out_map->level);
> if (ret)
> return ret;
>
> @@ -217,7 +219,7 @@ void kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
> u32 next_ptep_level;
> unsigned long next_page_size, page_size;
>
> - ret = gstage_level_to_page_size(ptep_level, &page_size);
> + ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
> if (ret)
> return;
>
> @@ -229,7 +231,7 @@ void kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
> if (ptep_level && !gstage_pte_leaf(ptep)) {
> next_ptep = (pte_t *)gstage_pte_page_vaddr(ptep_get(ptep));
> next_ptep_level = ptep_level - 1;
> - ret = gstage_level_to_page_size(next_ptep_level, &next_page_size);
> + ret = gstage_level_to_page_size(gstage, next_ptep_level, &next_page_size);
> if (ret)
> return;
>
> @@ -263,7 +265,7 @@ void kvm_riscv_gstage_unmap_range(struct kvm_gstage *gstage,
>
> while (addr < end) {
> found_leaf = kvm_riscv_gstage_get_leaf(gstage, addr, &ptep, &ptep_level);
> - ret = gstage_level_to_page_size(ptep_level, &page_size);
> + ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
> if (ret)
> break;
>
> @@ -297,7 +299,7 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
>
> while (addr < end) {
> found_leaf = kvm_riscv_gstage_get_leaf(gstage, addr, &ptep, &ptep_level);
> - ret = gstage_level_to_page_size(ptep_level, &page_size);
> + ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
> if (ret)
> break;
>
> @@ -319,39 +321,34 @@ void __init kvm_riscv_gstage_mode_detect(void)
> /* Try Sv57x4 G-stage mode */
> csr_write(CSR_HGATP, HGATP_MODE_SV57X4 << HGATP_MODE_SHIFT);
> if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV57X4) {
> - kvm_riscv_gstage_mode = HGATP_MODE_SV57X4;
> - kvm_riscv_gstage_pgd_levels = 5;
> + kvm_riscv_gstage_max_pgd_levels = 5;
> goto done;
> }
>
> /* Try Sv48x4 G-stage mode */
> csr_write(CSR_HGATP, HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT);
> if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV48X4) {
> - kvm_riscv_gstage_mode = HGATP_MODE_SV48X4;
> - kvm_riscv_gstage_pgd_levels = 4;
> + kvm_riscv_gstage_max_pgd_levels = 4;
> goto done;
> }
>
> /* Try Sv39x4 G-stage mode */
> csr_write(CSR_HGATP, HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT);
> if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV39X4) {
> - kvm_riscv_gstage_mode = HGATP_MODE_SV39X4;
> - kvm_riscv_gstage_pgd_levels = 3;
> + kvm_riscv_gstage_max_pgd_levels = 3;
> goto done;
> }
> #else /* CONFIG_32BIT */
> /* Try Sv32x4 G-stage mode */
> csr_write(CSR_HGATP, HGATP_MODE_SV32X4 << HGATP_MODE_SHIFT);
> if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV32X4) {
> - kvm_riscv_gstage_mode = HGATP_MODE_SV32X4;
> - kvm_riscv_gstage_pgd_levels = 2;
> + kvm_riscv_gstage_max_pgd_levels = 2;
> goto done;
> }
> #endif
>
> /* KVM depends on !HGATP_MODE_OFF */
> - kvm_riscv_gstage_mode = HGATP_MODE_OFF;
> - kvm_riscv_gstage_pgd_levels = 0;
> + kvm_riscv_gstage_max_pgd_levels = 0;
>
> done:
> csr_write(CSR_HGATP, 0);
> diff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c
> index 0f3fe3986fc0..90ee0a032b9a 100644
> --- a/arch/riscv/kvm/main.c
> +++ b/arch/riscv/kvm/main.c
> @@ -105,17 +105,17 @@ static int __init riscv_kvm_init(void)
> return rc;
>
> kvm_riscv_gstage_mode_detect();
> - switch (kvm_riscv_gstage_mode) {
> - case HGATP_MODE_SV32X4:
> + switch (kvm_riscv_gstage_max_pgd_levels) {
> + case 2:
> str = "Sv32x4";
> break;
> - case HGATP_MODE_SV39X4:
> + case 3:
> str = "Sv39x4";
> break;
> - case HGATP_MODE_SV48X4:
> + case 4:
> str = "Sv48x4";
> break;
> - case HGATP_MODE_SV57X4:
> + case 5:
> str = "Sv57x4";
> break;
> default:
> @@ -164,7 +164,7 @@ static int __init riscv_kvm_init(void)
> (rc) ? slist : "no features");
> }
>
> - kvm_info("using %s G-stage page table format\n", str);
> + kvm_info("highest G-stage page table mode is %s\n", str);
>
> kvm_info("VMID %ld bits available\n", kvm_riscv_gstage_vmid_bits());
>
> diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
> index 088d33ba90ed..fbcdd75cb9af 100644
> --- a/arch/riscv/kvm/mmu.c
> +++ b/arch/riscv/kvm/mmu.c
> @@ -67,7 +67,7 @@ int kvm_riscv_mmu_ioremap(struct kvm *kvm, gpa_t gpa, phys_addr_t hpa,
> if (!writable)
> map.pte = pte_wrprotect(map.pte);
>
> - ret = kvm_mmu_topup_memory_cache(&pcache, kvm_riscv_gstage_pgd_levels);
> + ret = kvm_mmu_topup_memory_cache(&pcache, kvm->arch.pgd_levels);
> if (ret)
> goto out;
>
> @@ -186,7 +186,7 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
> * space addressable by the KVM guest GPA space.
> */
> if ((new->base_gfn + new->npages) >=
> - (kvm_riscv_gstage_gpa_size >> PAGE_SHIFT))
> + kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels) >> PAGE_SHIFT)
> return -EFAULT;
>
> hva = new->userspace_addr;
> @@ -472,7 +472,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
> memset(out_map, 0, sizeof(*out_map));
>
> /* We need minimum second+third level pages */
> - ret = kvm_mmu_topup_memory_cache(pcache, kvm_riscv_gstage_pgd_levels);
> + ret = kvm_mmu_topup_memory_cache(pcache, kvm->arch.pgd_levels);
> if (ret) {
> kvm_err("Failed to topup G-stage cache\n");
> return ret;
> @@ -575,6 +575,7 @@ int kvm_riscv_mmu_alloc_pgd(struct kvm *kvm)
> return -ENOMEM;
> kvm->arch.pgd = page_to_virt(pgd_page);
> kvm->arch.pgd_phys = page_to_phys(pgd_page);
> + kvm->arch.pgd_levels = kvm_riscv_gstage_max_pgd_levels;
>
> return 0;
> }
> @@ -590,10 +591,12 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
> gstage.flags = 0;
> gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
> gstage.pgd = kvm->arch.pgd;
> - kvm_riscv_gstage_unmap_range(&gstage, 0UL, kvm_riscv_gstage_gpa_size, false);
> + kvm_riscv_gstage_unmap_range(&gstage, 0UL,
> + kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels), false);
> pgd = READ_ONCE(kvm->arch.pgd);
> kvm->arch.pgd = NULL;
> kvm->arch.pgd_phys = 0;
> + kvm->arch.pgd_levels = 0;
> }
> spin_unlock(&kvm->mmu_lock);
>
> @@ -603,11 +606,12 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
>
> void kvm_riscv_mmu_update_hgatp(struct kvm_vcpu *vcpu)
> {
> - unsigned long hgatp = kvm_riscv_gstage_mode << HGATP_MODE_SHIFT;
> - struct kvm_arch *k = &vcpu->kvm->arch;
> + struct kvm_arch *ka = &vcpu->kvm->arch;
> + unsigned long hgatp = kvm_riscv_gstage_mode(ka->pgd_levels)
> + << HGATP_MODE_SHIFT;
>
> - hgatp |= (READ_ONCE(k->vmid.vmid) << HGATP_VMID_SHIFT) & HGATP_VMID;
> - hgatp |= (k->pgd_phys >> PAGE_SHIFT) & HGATP_PPN;
> + hgatp |= (READ_ONCE(ka->vmid.vmid) << HGATP_VMID_SHIFT) & HGATP_VMID;
> + hgatp |= (ka->pgd_phys >> PAGE_SHIFT) & HGATP_PPN;
>
> ncsr_write(CSR_HGATP, hgatp);
>
> diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
> index 13c63ae1a78b..4d82a886102c 100644
> --- a/arch/riscv/kvm/vm.c
> +++ b/arch/riscv/kvm/vm.c
> @@ -199,7 +199,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
> r = KVM_USER_MEM_SLOTS;
> break;
> case KVM_CAP_VM_GPA_BITS:
> - r = kvm_riscv_gstage_gpa_bits;
> + r = kvm_riscv_gstage_gpa_bits(kvm->arch.pgd_levels);
> break;
> default:
> r = 0;
> diff --git a/arch/riscv/kvm/vmid.c b/arch/riscv/kvm/vmid.c
> index cf34d448289d..c15bdb1dd8be 100644
> --- a/arch/riscv/kvm/vmid.c
> +++ b/arch/riscv/kvm/vmid.c
> @@ -26,7 +26,8 @@ static DEFINE_SPINLOCK(vmid_lock);
> void __init kvm_riscv_gstage_vmid_detect(void)
> {
> /* Figure-out number of VMID bits in HW */
> - csr_write(CSR_HGATP, (kvm_riscv_gstage_mode << HGATP_MODE_SHIFT) | HGATP_VMID);
> + csr_write(CSR_HGATP, (kvm_riscv_gstage_mode(kvm_riscv_gstage_max_pgd_levels) <<
> + HGATP_MODE_SHIFT) | HGATP_VMID);
> vmid_bits = csr_read(CSR_HGATP);
> vmid_bits = (vmid_bits & HGATP_VMID) >> HGATP_VMID_SHIFT;
> vmid_bits = fls_long(vmid_bits);
> --
> 2.50.1
>
^ permalink raw reply
* Re: [PATCH v2] doc: Add CPU Isolation documentation
From: Frederic Weisbecker @ 2026-04-01 15:47 UTC (permalink / raw)
To: Waiman Long
Cc: LKML, Anna-Maria Behnsen, Gabriele Monaco, Ingo Molnar,
Jonathan Corbet, Marcelo Tosatti, Marco Crivellari, Michal Hocko,
Paul E . McKenney, Peter Zijlstra, Phil Auld, Steven Rostedt,
Thomas Gleixner, Valentin Schneider, Vlastimil Babka, linux-doc,
Sebastian Andrzej Siewior, Bagas Sanjaya
In-Reply-To: <90a6512f-5b6b-4781-87f3-4580ac426c37@redhat.com>
Le Thu, Mar 26, 2026 at 03:17:48PM -0400, Waiman Long a écrit :
> On 3/26/26 10:00 AM, Frederic Weisbecker wrote:
> > nohz_full was introduced in v3.10 in 2013, which means this
> > documentation is overdue for 13 years.
> >
> > Fortunately Paul wrote a part of the needed documentation a while ago,
> > especially concerning nohz_full in Documentation/timers/no_hz.rst and
> > also about per-CPU kthreads in
> > Documentation/admin-guide/kernel-per-CPU-kthreads.rst
> >
> > Introduce a new page that gives an overview of CPU isolation in general.
> >
> > Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
> > ---
> > v2:
> > - Fix links and code blocks (Bagas and Sebastian)
> > - Isolation is not only about userspace, rephrase accordingly (Valentin)
> > - Paste BIOS issues suggestion from Valentin
> > - Include the whole rtla suite (Valentin)
> > - Rephrase a few details (Waiman)
> > - Talk about RCU induced overhead rather than slower RCU (Sebastian)
> >
> > Documentation/admin-guide/cpu-isolation.rst | 357 ++++++++++++++++++++
> > Documentation/admin-guide/index.rst | 1 +
> > 2 files changed, 358 insertions(+)
> > create mode 100644 Documentation/admin-guide/cpu-isolation.rst
> >
> > diff --git a/Documentation/admin-guide/cpu-isolation.rst b/Documentation/admin-guide/cpu-isolation.rst
> > new file mode 100644
> > index 000000000000..886dec79b056
> > --- /dev/null
> > +++ b/Documentation/admin-guide/cpu-isolation.rst
> > @@ -0,0 +1,357 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +=============
> > +CPU Isolation
> > +=============
> > +
> > +Introduction
> > +============
> > +
> > +"CPU Isolation" means leaving a CPU exclusive to a given workload
> > +without any undesired code interference from the kernel.
> > +
> > +Those interferences, commonly pointed out as "noise", can be triggered
> > +by asynchronous events (interrupts, timers, scheduler preemption by
> > +workqueues and kthreads, ...) or synchronous events (syscalls and page
> > +faults).
> > +
> > +Such noise usually goes unnoticed. After all synchronous events are a
> > +component of the requested kernel service. And asynchronous events are
> > +either sufficiently well distributed by the scheduler when executed
> > +as tasks or reasonably fast when executed as interrupt. The timer
> > +interrupt can even execute 1024 times per seconds without a significant
> > +and measurable impact most of the time.
> > +
> > +However some rare and extreme workloads can be quite sensitive to
> > +those kinds of noise. This is the case, for example, with high
> > +bandwidth network processing that can't afford losing a single packet
> > +or very low latency network processing. Typically those usecases
> > +involve DPDK, bypassing the kernel networking stack and performing
> > +direct access to the networking device from userscace.
>
> As also pointed by by Sashiko, there is a typo "userscace" -> "userspace".
> There are also typos reported in
>
> https://sashiko.dev/#/patchset/20260326140055.41555-1-frederic%40kernel.org
Thanks!
What do you think about these lines of Sashiko's review:
"""
Does this script violate the cgroup v2 "no internal process" constraint?
By enabling the cpuset controller on the test directory's
cgroup.subtree_control file, the cgroup cannot also contain processes.
"""
That is confusing me...
>
> > +
> > +In order to run a CPU without or with limited kernel noise, the
> > +related housekeeping work needs to be either shutdown, migrated or
> > +offloaded.
> > +
> > +Housekeeping
> > +============
> > +
> > +In the CPU isolation terminology, housekeeping is the work, often
> > +asynchronous, that the kernel needs to process in order to maintain
> > +all its services. It matches the noises and disturbances enumerated
> > +above except when at least one CPU is isolated. Then housekeeping may
> > +make use of further coping mechanisms if CPU-tied work must be
> > +offloaded.
> > +
> > +Housekeeping CPUs are the non-isolated CPUs where the kernel noise
> > +is moved away from isolated CPUs.
> > +
> > +The isolation can be implemented in several ways depending on the
> > +nature of the noise:
> > +
> > +- Unbound work, where "unbound" means not tied to any CPU, can be
> > + simply migrated away from isolated CPUs to housekeeping CPUs.
> > + This is the case of unbound workqueues, kthreads and timers.
> > +
> > +- Bound work, where "bound" means tied to a specific CPU, usually
> > + can't be moved away as-is by nature. Either:
> > +
> > + - The work must switch to a locked implementation. Eg: This is
> > + the case of RCU with CONFIG_RCU_NOCB_CPU.
> > +
> > + - The related feature must be shutdown and considered
> > + incompatible with isolated CPUs. Eg: Lockup watchdog,
> > + unreliable clocksources, etc...
> > +
> > + - An elaborated and heavyweight coping mechanism stands as a
> > + replacement. Eg: the timer tick is shutdown on nohz_full but
>
> "shutdown" should be 2 words as "shutdown" isn't a verb. Should we add CPU
> after "nohz_full" to make it more clear?
Right.
>
>
> > + with the constraint of running a single task on the CPU. A
> > + significant cost penalty is added on kernel entry/exit and
> > + a residual 1Hz scheduler tick is offloaded to housekeeping
> > + CPUs.
> > +
> > --- a/Documentation/admin-guide/index.rst
> > +++ b/Documentation/admin-guide/index.rst
> > @@ -94,6 +94,7 @@ likely to be of interest on almost any system.
> > cgroup-v2
> > cgroup-v1/index
> > + cpu-isolation
> > cpu-load
> > mm/index
> > module-signing
>
> Other than the minor nits mentioned above,
>
> Acked-by: Waiman Long <longman@redhat.com>
Thanks!
>
--
Frederic Weisbecker
SUSE Labs
^ permalink raw reply
* [PATCH v6] hwmon: add driver for ARCTIC Fan Controller
From: Aureo Serrano de Souza @ 2026-04-01 15:39 UTC (permalink / raw)
To: linux-hwmon
Cc: linux, linux, corbet, skhan, linux-doc, linux-kernel,
Aureo Serrano de Souza
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[] is also protected by this spinlock
because reset_resume() clears it outside the hwmon core lock. The OUT
report buffer is built and write_pending is armed under the same lock so
that no reset_resume() can race with the pwm_duty[] snapshot. priv->buf
is exclusively accessed by write(), which the hwmon core serializes.
Signed-off-by: Aureo Serrano de Souza <aureo.serrano@arctic.de>
---
Thanks to Guenter Roeck and Thomas Weißschuh for the reviews.
Changes since v5:
- arctic_fan_probe(): switch from devm_hwmon_device_register_with_info()
to hwmon_device_register_with_info(); store the returned pointer in
priv->hwmon_dev for explicit teardown in remove()
- arctic_fan_remove(): call hwmon_device_unregister(priv->hwmon_dev)
before hid_device_io_stop/hid_hw_close/hid_hw_stop; this closes the
use-after-free window where a concurrent sysfs write could call
hid_hw_output_report() on an already-stopped device; matches the
removal pattern used by nzxt-smart2 and aquacomputer_d5next
- arctic_fan_write(): expand write_pending comment to document the
residual theoretical late-ACK race (unfixable without a correlation
ID in the device ACK report) and its practical impossibility (observed
max ACK latency ~563 ms, timeout 1 s; a delay > 1 s indicates a
non-functional device)
- arctic_fan_reset_resume(), arctic_fan_read(), arctic_fan_write():
extend in_report_lock coverage to pwm_duty[]; reset_resume() clears
pwm_duty[] outside the hwmon core lock, so all paths that read or
write pwm_duty[] now hold in_report_lock to prevent a data race
during resume
- arctic_fan_write(): build the OUT report buffer inside in_report_lock
so reset_resume() cannot clear pwm_duty[] between the pwm_duty[]
snapshot and the buffer write; this makes the lock coverage complete
Changes since v4:
- arctic_fan_write(): switch to wait_for_completion_timeout() (non-
interruptible); eliminates the signal-interrupted write case of the
late-ACK race that write_pending could not fully prevent
- arctic_fan_write(): guard pwm_duty[channel] commit with
ack_status == 0 check; a device error ACK (status 0x01) no longer
silently poisons the cached duty used in future OUT reports
- arctic_fan_probe()/remove(): replace devm_add_action_or_reset() +
no-op remove() with explicit hid_device_io_stop/hid_hw_close/
hid_hw_stop in remove(); devm_add_action_or_reset() was called after
hdev->driver = NULL, causing a NULL deref in hid_hw_close() on unbind
- add reset_resume callback: device resets PWM to hardware defaults on
power loss during suspend; driver now clears cached pwm_duty[] on
reset-resume so stale pre-suspend values are not re-sent as if valid
- Documentation/hwmon/arctic_fan_controller.rst: document suspend/
resume behaviour and the updated pwm[1-10] read semantics
Changes since v3:
- buf[]: upgrade from __aligned(8) to ____cacheline_aligned so the
DMA buffer occupies its own cache line, preventing false sharing with
adjacent fan_rpm[]/pwm_duty[] fields on non-coherent architectures
- arctic_fan_write(): add write_pending flag (protected by
in_report_lock) so raw_event() delivers ACKs only while a write is
in flight
- arctic_fan_write(): commit pwm_duty[channel] only after the device
ACKs the command; a failed or timed-out write no longer leaves a
stale value in the cached duty state
- arctic_fan_probe(): start IO (hid_device_io_start) before registering
with hwmon; previously a sysfs write arriving between hwmon
registration and io_start could send an OUT report whose ACK would be
discarded by the HID core, causing a spurious timeout
- Documentation/hwmon/arctic_fan_controller.rst: document that cached
PWM values start at 0 (hardware state unknown at probe) and that each
OUT report carries all 10 channel values
Changes since v2:
- buf[]: add __aligned(8) for DMA safety
- ARCTIC_ACK_TIMEOUT_MS: restore 1000 ms; note 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
- 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..b5be88ae46
--- /dev/null
+++ b/Documentation/hwmon/arctic_fan_controller.rst
@@ -0,0 +1,56 @@
+.. 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 RPM values (bytes 11-30, 10 x uint16 LE).
+Fan speed control is manual-only: the device does not change PWM
+autonomously; it only applies a new duty cycle when it receives an OUT
+report from the host.
+
+After the device applies an OUT report, it sends back a 2-byte ACK IN
+report (Report ID 0x02, byte 1 = 0x00 on success) confirming the command
+was applied.
+
+Usage notes
+-----------
+
+Since it is a USB device, hotplug is supported. The device is autodetected.
+
+The device does not support GET_REPORT, so the driver cannot read back the
+current hardware PWM state at probe time. The cached PWM values (readable
+via pwm[1-10]) start at 0 and reflect only values that have been
+successfully written. Because each OUT report carries all 10 channel values,
+writing a single channel also sends the cached values for all other channels.
+Users should set all channels to the desired values before relying on the
+cached state.
+
+On system suspend, the device may lose power and reset its PWM channels to
+hardware defaults. The driver clears its cached duty values on resume so
+that reads reflect the unknown hardware state rather than stale pre-suspend
+values. Userspace is responsible for re-applying the desired duty cycles
+after resume.
+
+Sysfs entries
+-------------
+
+================ ==============================================================
+fan[1-10]_input Fan speed in RPM (read-only). Updated from IN reports at ~1 Hz.
+pwm[1-10] PWM duty cycle (0-255). Write: sends an OUT report setting the
+ duty cycle (scaled from 0-255 to 0-100% for the device);
+ the cached value is updated only after the device ACKs the
+ command with a success status. Read: returns the last
+ successfully written value; initialized to 0 at driver load
+ and after resume (hardware state unknown).
+================ ==============================================================
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..2bfb003f01
--- /dev/null
+++ b/drivers/hwmon/arctic_fan_controller.c
@@ -0,0 +1,370 @@
+// 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/cache.h>
+#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/string.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;
+ struct device *hwmon_dev; /* stored for explicit unregister in remove() */
+ spinlock_t in_report_lock; /* protects fan_rpm, ack_status, write_pending, pwm_duty */
+ struct completion in_report_received; /* ACK (ID 0x02) received in raw_event */
+ int ack_status; /* 0 = OK, negative errno on device error */
+ bool write_pending; /* true while an OUT report ACK is in flight */
+ 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. Cache-line aligned so it occupies its own cache
+ * line, preventing DMA cache-coherency issues with adjacent fields
+ * (fan_rpm[], pwm_duty[]) on non-coherent architectures.
+ * Embedded in the devm_kzalloc'd struct so it is heap-allocated and
+ * passes usb_hcd_map_urb_for_dma(). Serialized by the hwmon core.
+ */
+ u8 buf[ARCTIC_REPORT_LEN] ____cacheline_aligned;
+};
+
+/*
+ * 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);
+ /*
+ * Only deliver if a write is in flight. This prevents a
+ * late-arriving ACK from a timed-out write from erroneously
+ * satisfying a subsequent write's completion wait.
+ */
+ if (priv->write_pending) {
+ 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) {
+ spin_lock_irqsave(&priv->in_report_lock, flags);
+ *val = priv->pwm_duty[channel];
+ spin_unlock_irqrestore(&priv->in_report_lock, flags);
+ 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);
+ u8 new_duty = (u8)clamp_val(val, 0, 255);
+ unsigned long flags;
+ unsigned long t;
+ int i, ret;
+
+ /*
+ * Build the buffer and arm write_pending under in_report_lock so that
+ * reset_resume() cannot clear pwm_duty[] between the pwm_duty[] read
+ * and the buffer write, and raw_event() cannot deliver a stale ACK
+ * from a previous write into this write's completion.
+ *
+ * priv->buf is heap-allocated (embedded in the devm_kzalloc'd struct),
+ * satisfying usb_hcd_map_urb_for_dma(). Exclusively accessed by
+ * write() which the hwmon core serializes.
+ *
+ * pwm_duty[channel] is committed only after a positive device ACK so a
+ * failed or timed-out write does not corrupt the cached state.
+ *
+ * Residual theoretical race: if write A times out (write_pending
+ * cleared), write B sets write_pending = true, and a late ACK from
+ * write A—delayed beyond ARCTIC_ACK_TIMEOUT_MS—arrives during write
+ * B's pending window, it would falsely satisfy write B's completion.
+ * This cannot be prevented in driver code without protocol support
+ * (for example, a correlation ID echoed in the device ACK report).
+ * In testing, observed ACK latency stayed below the 1 s timeout
+ * (maximum ~563 ms over 500 iterations).
+ *
+ * The wait is non-interruptible so that a signal cannot cause write()
+ * to return early while the OUT report is already in flight; an
+ * interruptible early return would create the same late-ACK window
+ * without even the timeout guard.
+ * Serialized by the hwmon core: only one arctic_fan_write() at a time.
+ * Use irqsave to match the IRQ context in which raw_event may run.
+ */
+ spin_lock_irqsave(&priv->in_report_lock, flags);
+ priv->buf[0] = ARCTIC_OUTPUT_REPORT_ID;
+ for (i = 0; i < ARCTIC_NUM_FANS; i++) {
+ u8 d = i == channel ? new_duty : priv->pwm_duty[i];
+
+ priv->buf[1 + i] = DIV_ROUND_CLOSEST((unsigned int)d * 100, 255);
+ }
+ priv->ack_status = -ETIMEDOUT;
+ priv->write_pending = true;
+ 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) {
+ spin_lock_irqsave(&priv->in_report_lock, flags);
+ priv->write_pending = false;
+ spin_unlock_irqrestore(&priv->in_report_lock, flags);
+ return ret;
+ }
+
+ t = wait_for_completion_timeout(&priv->in_report_received,
+ msecs_to_jiffies(ARCTIC_ACK_TIMEOUT_MS));
+ spin_lock_irqsave(&priv->in_report_lock, flags);
+ priv->write_pending = false;
+ /* Commit inside the lock so reset_resume() cannot race with this write */
+ if (t && priv->ack_status == 0)
+ priv->pwm_duty[channel] = new_duty;
+ spin_unlock_irqrestore(&priv->in_report_lock, flags);
+
+ 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 int arctic_fan_reset_resume(struct hid_device *hdev)
+{
+ struct arctic_fan_data *priv = hid_get_drvdata(hdev);
+ unsigned long flags;
+
+ /*
+ * The device resets its PWM channels to hardware defaults on power
+ * loss during suspend. Clear the cached duty values so they reflect
+ * the unknown hardware state, consistent with probe-time behaviour
+ * (the device has no GET_REPORT support). Hold in_report_lock so
+ * this does not race with a concurrent pwm read or write callback.
+ */
+ spin_lock_irqsave(&priv->in_report_lock, flags);
+ memset(priv->pwm_duty, 0, sizeof(priv->pwm_duty));
+ spin_unlock_irqrestore(&priv->in_report_lock, flags);
+ return 0;
+}
+
+static int arctic_fan_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ struct arctic_fan_data *priv;
+ 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)
+ goto out_stop;
+
+ /*
+ * Start IO before registering with hwmon. If IO were started after
+ * hwmon registration, a sysfs write arriving in that narrow window
+ * would send an OUT report but the ACK could not be delivered (the HID
+ * core discards events until io_started), causing a spurious timeout.
+ */
+ hid_device_io_start(hdev);
+
+ /*
+ * Use the non-devm variant and store the pointer so remove() can
+ * call hwmon_device_unregister() before tearing down the HID
+ * transport. devm_hwmon_device_register_with_info() would defer
+ * unregistration until after remove() returns, leaving a window
+ * where a concurrent sysfs write could call hid_hw_output_report()
+ * on an already-stopped device (use-after-free).
+ */
+ priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "arctic_fan",
+ priv, &arctic_fan_chip_info,
+ NULL);
+ if (IS_ERR(priv->hwmon_dev)) {
+ ret = PTR_ERR(priv->hwmon_dev);
+ goto out_close;
+ }
+
+ return 0;
+
+out_close:
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+out_stop:
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void arctic_fan_remove(struct hid_device *hdev)
+{
+ struct arctic_fan_data *priv = hid_get_drvdata(hdev);
+
+ /*
+ * Unregister hwmon before stopping the HID transport. This removes
+ * the sysfs files and waits for any in-progress write() callback to
+ * return, so no hwmon op can call hid_hw_output_report() after
+ * hid_hw_stop() frees the underlying USB resources.
+ * Matches the pattern used by nzxt-smart2 and aquacomputer_d5next.
+ */
+ hwmon_device_unregister(priv->hwmon_dev);
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+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,
+ .reset_resume = arctic_fan_reset_resume,
+};
+
+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 related
* [PATCH] docs/zh_CN: add module-signing Chinese translation
From: Yan Zhu @ 2026-04-01 15:40 UTC (permalink / raw)
To: alexs, si.yanteng, corbet
Cc: dzm91, skhan, linux-doc, linux-kernel, zhuyan2015
Translate .../admin-guide/module-signing.rst into Chinese.
Update the translation through commit 0ad9a71933e7
("modsign: Enable ML-DSA module signing")
Signed-off-by: Yan Zhu <zhuyan2015@qq.com>
---
.../zh_CN/admin-guide/module-signing.rst | 242 ++++++++++++++++++
1 file changed, 242 insertions(+)
create mode 100644 Documentation/translations/zh_CN/admin-guide/module-signing.rst
diff --git a/Documentation/translations/zh_CN/admin-guide/module-signing.rst b/Documentation/translations/zh_CN/admin-guide/module-signing.rst
new file mode 100644
index 000000000000..b8c209dd229d
--- /dev/null
+++ b/Documentation/translations/zh_CN/admin-guide/module-signing.rst
@@ -0,0 +1,242 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. include:: ../disclaimer-zh_CN.rst
+
+:Original: Documentation/admin-guide/module-signing.rst
+:翻译:
+ 朱岩 Yan Zhu <zhuyan2015@qq.com>
+
+
+==========================
+内核模块签名机制
+==========================
+
+.. 目录
+..
+.. - 概述
+.. - 配置模块签名
+.. - 生成签名密钥
+.. - 内核中的公钥
+.. - 模块手动签名
+.. - 已签名模块和剥离
+.. - 加载已签名模块
+.. - 无效签名和未签名模块
+.. - 管理/保护私钥
+
+
+概述
+====
+
+内核模块签名机制在安装过程中对模块进行加密签名,然后在加载模块时检查签名。
+这通过禁止加载未签名的模块或使用无效密钥签名的模块来提高内核安全性。
+模块签名通过使恶意模块更难加载到内核中来增加安全性。
+模块签名检查在内核中完成,因此不需要受信任的用户空间位。
+
+此机制使用 X.509 ITU-T 标准证书对涉及的公钥进行编码。
+签名本身不以任何工业标准类型编码。
+内置机制目前仅支持 RSA、NIST P-384 ECDSA 和 NIST FIPS-204 ML-DSA 公钥签名标准(尽管它是可插拔的并允许使用其他标准)。
+对于 RSA 和 ECDSA,可以使用的可能的哈希算法是大小为 256、384 和 512 的 SHA-2 和 SHA-3(算法由签名中的数据选择);
+ML-DSA会自行进行哈希运算,但允许与SHA512哈希算法结合用于签名属性。
+
+配置模块签名
+============
+
+通过进入内核配置的 :menuselection:`Enable Loadable Module Support` 菜单并打开以下选项来启用模块签名机制::
+
+ CONFIG_MODULE_SIG "Module signature verification"
+
+这有多个可用选项:
+
+ (1) :menuselection:`Require modules to be validly signed`
+ (``CONFIG_MODULE_SIG_FORCE``)
+
+ 这指定了内核应如何处理其密钥未知或未签名的模块。
+
+ 如果关闭(即"宽松模式"),则允许使用不可用密钥和未签名的模块,
+ 但内核将被标记为受污染,并且相关模块将被标记为受污染,显示字符'E'。
+
+ 如果打开(即"限制模式"),只有具有有效签名且可由内核拥有的公钥验证的模块才会被加载。
+ 所有其他模块将生成错误。
+
+ 无论此处的设置如何,如果模块的签名块无法解析,它将被直接拒绝。
+
+
+ (2) :menuselection:`Automatically sign all modules`
+ (``CONFIG_MODULE_SIG_ALL``)
+
+ 如果打开此选项,则在构建的 modules_install 阶段期间将自动签名模块。
+ 如果关闭,则必须使用以下命令手动签名模块::
+
+ scripts/sign-file
+
+
+ (3) :menuselection:`Which hash algorithm should modules be signed with?`
+
+ 这提供了安装阶段将用于签名模块的哈希算法选择:
+
+ =============================== ==========================================
+ ``CONFIG_MODULE_SIG_SHA256`` :menuselection:`Sign modules with SHA-256`
+ ``CONFIG_MODULE_SIG_SHA384`` :menuselection:`Sign modules with SHA-384`
+ ``CONFIG_MODULE_SIG_SHA512`` :menuselection:`Sign modules with SHA-512`
+ ``CONFIG_MODULE_SIG_SHA3_256`` :menuselection:`Sign modules with SHA3-256`
+ ``CONFIG_MODULE_SIG_SHA3_384`` :menuselection:`Sign modules with SHA3-384`
+ ``CONFIG_MODULE_SIG_SHA3_512`` :menuselection:`Sign modules with SHA3-512`
+ =============================== ==========================================
+
+ 此处选择的算法也将被构建到内核中(而不是作为模块),
+ 以便使用该算法签名的模块可以在不导致循环依赖的情况下检查其签名。
+
+
+ (4) :menuselection:`File name or PKCS#11 URI of module signing key`
+ (``CONFIG_MODULE_SIG_KEY``)
+
+ 将此选项设置为除默认值 ``certs/signing_key.pem`` 之外的其他值将禁用签名密钥的自动生成,
+ 并允许使用您选择的密钥对内核模块进行签名。
+ 提供的字符串应标识包含私钥及其对应的 PEM 格式 X.509 证书的文件,
+ 或者在 OpenSSL ENGINE_pkcs11 功能正常的系统上,使用 RFC7512 定义的 PKCS#11 URI。
+ 在后一种情况下,PKCS#11 URI 应引用证书和私钥。
+
+ 如果包含私钥的 PEM 文件已加密,或者 PKCS#11 令牌需要 PIN,
+ 可以通过 ``KBUILD_SIGN_PIN`` 变量在构建时提供。
+
+
+ (5) :menuselection:`Additional X.509 keys for default system keyring`
+ (``CONFIG_SYSTEM_TRUSTED_KEYS``)
+
+ 此选项可设置为包含附加证书的 PEM 编码文件的文件名,
+ 这些证书将默认包含在系统密钥环中。
+
+请注意,启用模块签名会为内核构建过程添加对执行签名工具的 OpenSSL 开发包的依赖。
+
+
+生成签名密钥
+============
+
+生成和检查签名需要加密密钥对。私钥用于生成签名,相应的公钥用于检查签名。
+私钥仅在构建期间需要,之后可以删除或安全存储。
+公钥被构建到内核中,以便在加载模块时可以使用它来检查签名。
+
+在正常情况下,当 ``CONFIG_MODULE_SIG_KEY`` 保持默认值时,
+如果文件中不存在密钥对,内核构建将使用 openssl 自动生成新的密钥对::
+
+ certs/signing_key.pem
+
+在构建 vmlinux 期间(公钥需要构建到 vmlinux 中)使用参数::
+
+ certs/x509.genkey
+
+文件(如果尚不存在也会生成)。
+
+可以在 RSA(``MODULE_SIG_KEY_TYPE_RSA``)、ECDSA(``MODULE_SIG_KEY_TYPE_ECDSA``)
+和 ML-DSA(``MODULE_SIG_KEY_TYPE_MLDSA_*``)之间选择生成 RSA 4k、NIST P-384 密钥对或 ML-DSA 44、65 或 87 密钥对。
+
+强烈建议您提供自己的 x509.genkey 文件。
+
+最值得注意的是,在 x509.genkey 文件中,req_distinguished_name 部分应从默认值更改::
+
+ [ req_distinguished_name ]
+ #O = Unspecified company
+ CN = Build time autogenerated kernel key
+ #emailAddress = unspecified.user@unspecified.company
+
+生成的 RSA 密钥大小也可以通过以下方式设置::
+
+ [ req ]
+ default_bits = 4096
+
+也可以使用位于 Linux 内核源代码树根节点中的 x509.genkey 密钥生成配置文件和 openssl 命令手动生成公钥/私钥文件。
+以下是生成公钥/私钥文件的示例::
+
+ openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
+ -config x509.genkey -outform PEM -out kernel_key.pem \
+ -keyout kernel_key.pem
+
+然后可以将生成的 kernel_key.pem 文件的完整路径名指定在 ``CONFIG_MODULE_SIG_KEY`` 选项中,
+并且将使用其中的证书和密钥而不是自动生成的密钥对。
+
+
+内核中的公钥
+============
+
+内核包含一个可由 root 查看的公钥环。它们在名为 ".builtin_trusted_keys" 的密钥环中,
+可以通过以下方式查看::
+
+ [root@deneb ~]# cat /proc/keys
+ ...
+ 223c7853 I------ 1 perm 1f030000 0 0 keyring .builtin_trusted_keys: 1
+ 302d2d52 I------ 1 perm 1f010000 0 0 asymmetri Fedora kernel signing key: d69a84e6bce3d216b979e9505b3e3ef9a7118079: X509.RSA a7118079 []
+
+除了专门为模块签名生成的公钥外,还可以在 ``CONFIG_SYSTEM_TRUSTED_KEYS`` 配置选项引用的 PEM 编码文件中提供其他受信任的证书。
+
+此外,架构代码可以从硬件存储中获取公钥并将其添加(例如从 UEFI 密钥数据库)。
+
+最后,可以通过以下方式添加其他公钥::
+
+ keyctl padd asymmetric "" [.builtin_trusted_keys-ID] <[key-file]
+
+例如::
+
+ keyctl padd asymmetric "" 0x223c7853 <my_public_key.x509
+
+但是,请注意,内核只允许将由已驻留在 ``.builtin_trusted_keys`` 中的密钥有效签名的密钥添加到 ``.builtin_trusted_keys``。
+
+模块手动签名
+============
+
+要手动对模块进行签名,请使用 Linux 内核源代码树中可用的 scripts/sign-file 工具。
+该脚本需要 4 个参数:
+
+ 1. 哈希算法(例如,sha256)
+ 2. 私钥文件名或 PKCS#11 URI
+ 3. 公钥文件名
+ 4. 要签名的内核模块
+
+以下是签名内核模块的示例::
+
+ scripts/sign-file sha512 kernel-signkey.priv \
+ kernel-signkey.x509 module.ko
+
+使用的哈希算法不必与配置的算法匹配,但如果不同,
+应确保哈希算法要么内置在内核中,要么可以在不需要自身的情况下加载。
+
+如果私钥需要密码或 PIN,可以在 $KBUILD_SIGN_PIN 环境变量中提供。
+
+
+已签名模块和剥离
+================
+
+已签名模块在末尾简单地附加了数字签名。模块文件末尾的字符串
+``~Module signature appended~.`` 确认签名存在,但不能确认签名有效!
+
+已签名模块是脆弱的,因为签名在定义的ELF容器之外。
+因此,一旦计算并附加签名,就不得剥离它们。
+请注意,整个模块都是签名的有效载荷,包括签名时存在的任何和所有调试信息。
+
+
+加载已签名模块
+==============
+
+模块通过 insmod、modprobe、``init_module()`` 或 ``finit_module()`` 加载,
+与未签名模块完全一样,因为在用户空间中不进行任何处理。
+所有签名检查都在内核内完成。
+
+
+无效签名和未签名模块
+====================
+
+如果启用了 ``CONFIG_MODULE_SIG_FORCE`` 或在内核启动命令提供了 module.sig_enforce=1,
+内核将仅加载具有有效签名且具有公钥的模块。
+否则,它还将加载未签名的模块。
+任何具有不匹配签名的模块将不被允许加载。
+
+任何具有不可解析签名的模块将被拒绝。
+
+
+管理/保护私钥
+==============
+
+由于私钥用于签名模块,病毒和恶意软件可以使用私钥签名模块并危害操作系统。
+私钥必须被销毁或移动到安全位置,而不是保存在内核源代码树的根节点中。
+
+如果使用相同的私钥为多个内核配置签名模块,
+必须确保模块版本信息足以防止将模块加载到不同的内核中。
+要么设置 ``CONFIG_MODVERSIONS=y``,要么通过更改 ``EXTRAVERSION`` 或 ``CONFIG_LOCALVERSION`` 确保每个配置具有不同的内核发布字符串。
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 02/33] rust: bump Clippy's MSRV and clean `incompatible_msrv` allows
From: Danilo Krummrich @ 2026-04-01 15:39 UTC (permalink / raw)
To: Miguel Ojeda
Cc: Nathan Chancellor, Nicolas Schier, Andreas Hindborg,
Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Courbot, David Airlie, Simona Vetter,
Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Trevor Gross, rust-for-linux,
linux-kbuild, Lorenzo Stoakes, Vlastimil Babka, Liam R . Howlett,
Uladzislau Rezki, linux-block, moderated for non-subscribers,
Alexandre Ghiti, linux-riscv, nouveau, dri-devel, Rae Moar,
linux-kselftest, kunit-dev, Nick Desaulniers, Bill Wendling,
Justin Stitt, llvm, linux-kernel, Shuah Khan, linux-doc
In-Reply-To: <20260401114540.30108-3-ojeda@kernel.org>
On 4/1/26 1:45 PM, Miguel Ojeda wrote:
> drivers/gpu/nova-core/gsp/cmdq.rs | 6 +-----
Acked-by: Danilo Krummrich <dakr@kernel.org>
^ permalink raw reply
* Re: [PATCH 05/33] rust: remove `RUSTC_HAS_COERCE_POINTEE` and simplify code
From: Danilo Krummrich @ 2026-04-01 15:38 UTC (permalink / raw)
To: Miguel Ojeda
Cc: Nathan Chancellor, Nicolas Schier, Andreas Hindborg,
Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
Albert Ou, Alexandre Courbot, David Airlie, Simona Vetter,
Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Trevor Gross, rust-for-linux,
linux-kbuild, Lorenzo Stoakes, Vlastimil Babka, Liam R . Howlett,
Uladzislau Rezki, linux-block, moderated for non-subscribers,
Alexandre Ghiti, linux-riscv, nouveau, dri-devel, Rae Moar,
linux-kselftest, kunit-dev, Nick Desaulniers, Bill Wendling,
Justin Stitt, llvm, linux-kernel, Shuah Khan, linux-doc
In-Reply-To: <20260401114540.30108-6-ojeda@kernel.org>
On 4/1/26 1:45 PM, Miguel Ojeda wrote:
> rust/kernel/alloc/kbox.rs | 29 ++---------------------------
Acked-by: Danilo Krummrich <dakr@kernel.org>
^ permalink raw reply
* Re: [PATCH RFC v4 10/44] KVM: guest_memfd: Add support for KVM_SET_MEMORY_ATTRIBUTES2
From: Michael Roth @ 2026-04-01 15:35 UTC (permalink / raw)
To: Ackerley Tng
Cc: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jroedel, jthoughton, oupton, pankaj.gupta,
qperret, rick.p.edgecombe, rientjes, shivankg, steven.price,
tabba, willy, wyihan, yan.y.zhao, forkloop, pratyush,
suzuki.poulose, aneesh.kumar, Paolo Bonzini, Sean Christopherson,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, Shuah Khan,
Vishal Annapurve, Andrew Morton, Chris Li, Kairui Song,
Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Axel Rasmussen,
Yuanchu Xie, Wei Xu, Jason Gunthorpe, Vlastimil Babka, kvm,
linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm
In-Reply-To: <20260326-gmem-inplace-conversion-v4-10-e202fe950ffd@google.com>
On Thu, Mar 26, 2026 at 03:24:19PM -0700, Ackerley Tng wrote:
> For shared to private conversions, if refcounts on any of the folios
> within the range are elevated, fail the conversion with -EAGAIN.
>
> At the point of shared to private conversion, all folios in range are
> also unmapped. The filemap_invalidate_lock() is held, so no faulting
> can occur. Hence, from that point on, only transient refcounts can be
> taken on the folios associated with that guest_memfd.
>
> Hence, it is safe to do the conversion from shared to private.
>
> After conversion is complete, refcounts may become elevated, but that
> is fine since users of transient refcounts don't actually access
> memory.
>
> For private to shared conversions, there are no refcount checks, since
> the guest is the only user of private pages, and guest_memfd will be the
> only holder of refcounts on private pages.
>
> Signed-off-by: Ackerley Tng <ackerleytng@google.com>
> Co-developed-by: Sean Christopherson <seanjc@google.com>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
> ---
> Documentation/virt/kvm/api.rst | 48 +++++++-
> include/linux/kvm_host.h | 10 ++
> include/uapi/linux/kvm.h | 9 +-
> virt/kvm/Kconfig | 1 +
> virt/kvm/guest_memfd.c | 245 ++++++++++++++++++++++++++++++++++++++---
> virt/kvm/kvm_main.c | 17 ++-
> 6 files changed, 300 insertions(+), 30 deletions(-)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 0b61e2579e1d8..15148c80cfdb6 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -117,7 +117,7 @@ description:
> x86 includes both i386 and x86_64.
>
> Type:
> - system, vm, or vcpu.
> + system, vm, vcpu or guest_memfd.
>
> Parameters:
> what parameters are accepted by the ioctl.
> @@ -6557,11 +6557,22 @@ KVM_S390_KEYOP_SSKE
> ---------------------------------
>
> :Capability: KVM_CAP_MEMORY_ATTRIBUTES2
> -:Architectures: x86
> -:Type: vm ioctl
> +:Architectures: all
> +:Type: vm, guest_memfd ioctl
> :Parameters: struct kvm_memory_attributes2 (in/out)
> :Returns: 0 on success, <0 on error
>
> +Errors:
> +
> + ========== ===============================================================
> + EINVAL The specified `offset` or `size` were invalid (e.g. not
> + page aligned, causes an overflow, or size is zero).
> + EFAULT The parameter address was invalid.
> + EAGAIN Some page within requested range had unexpected refcounts. The
> + offset of the page will be returned in `error_offset`.
> + ENOMEM Ran out of memory trying to track private/shared state
> + ========== ===============================================================
> +
> KVM_SET_MEMORY_ATTRIBUTES2 is an extension to
> KVM_SET_MEMORY_ATTRIBUTES that supports returning (writing) values to
> userspace. The original (pre-extension) fields are shared with
> @@ -6572,15 +6583,42 @@ Attribute values are shared with KVM_SET_MEMORY_ATTRIBUTES.
> ::
>
> struct kvm_memory_attributes2 {
> - __u64 address;
> + /* in */
> + union {
> + __u64 address;
> + __u64 offset;
> + };
> __u64 size;
> __u64 attributes;
> __u64 flags;
> - __u64 reserved[12];
> + /* out */
> + __u64 error_offset;
> + __u64 reserved[11];
> };
>
> #define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)
>
> +Set attributes for a range of offsets within a guest_memfd to
> +KVM_MEMORY_ATTRIBUTE_PRIVATE to limit the specified guest_memfd backed
> +memory range for guest_use. Even if KVM_CAP_GUEST_MEMFD_MMAP is
> +supported, after a successful call to set
> +KVM_MEMORY_ATTRIBUTE_PRIVATE, the requested range will not be mappable
> +into host userspace and will only be mappable by the guest.
> +
> +To allow the range to be mappable into host userspace again, call
> +KVM_SET_MEMORY_ATTRIBUTES2 on the guest_memfd again with
> +KVM_MEMORY_ATTRIBUTE_PRIVATE unset.
> +
> +If this ioctl returns -EAGAIN, the offset of the page with unexpected
> +refcounts will be returned in `error_offset`. This can occur if there
> +are transient refcounts on the pages, taken by other parts of the
> +kernel.
> +
> +Userspace is expected to figure out how to remove all known refcounts
> +on the shared pages, such as refcounts taken by get_user_pages(), and
> +try the ioctl again. A possible source of these long term refcounts is
> +if the guest_memfd memory was pinned in IOMMU page tables.
> +
> See also: :ref: `KVM_SET_MEMORY_ATTRIBUTES`.
>
> .. _kvm_run:
> diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> index 19f026f8de390..1ea14c66fc82e 100644
> --- a/include/linux/kvm_host.h
> +++ b/include/linux/kvm_host.h
> @@ -2514,6 +2514,16 @@ static inline bool kvm_memslot_is_gmem_only(const struct kvm_memory_slot *slot)
> }
>
> #ifdef CONFIG_KVM_MEMORY_ATTRIBUTES
> +static inline u64 kvm_supported_mem_attributes(struct kvm *kvm)
> +{
> +#ifdef kvm_arch_has_private_mem
> + if (!kvm || kvm_arch_has_private_mem(kvm))
> + return KVM_MEMORY_ATTRIBUTE_PRIVATE;
> +#endif
> +
> + return 0;
> +}
> +
> typedef unsigned long (kvm_get_memory_attributes_t)(struct kvm *kvm, gfn_t gfn);
> DECLARE_STATIC_CALL(__kvm_get_memory_attributes, kvm_get_memory_attributes_t);
>
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index 16567d4a769e5..29baaa60de35a 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -990,6 +990,7 @@ struct kvm_enable_cap {
> #define KVM_CAP_S390_USER_OPEREXEC 246
> #define KVM_CAP_S390_KEYOP 247
> #define KVM_CAP_MEMORY_ATTRIBUTES2 248
> +#define KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES 249
>
> struct kvm_irq_routing_irqchip {
> __u32 irqchip;
> @@ -1642,11 +1643,15 @@ struct kvm_memory_attributes {
> #define KVM_SET_MEMORY_ATTRIBUTES2 _IOWR(KVMIO, 0xd2, struct kvm_memory_attributes2)
>
> struct kvm_memory_attributes2 {
> - __u64 address;
> + union {
> + __u64 address;
> + __u64 offset;
> + };
> __u64 size;
> __u64 attributes;
> __u64 flags;
> - __u64 reserved[12];
> + __u64 error_offset;
> + __u64 reserved[11];
> };
>
> #define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)
> diff --git a/virt/kvm/Kconfig b/virt/kvm/Kconfig
> index 3fea89c45cfb4..e371e079e2c50 100644
> --- a/virt/kvm/Kconfig
> +++ b/virt/kvm/Kconfig
> @@ -109,6 +109,7 @@ config KVM_VM_MEMORY_ATTRIBUTES
>
> config KVM_GUEST_MEMFD
> select XARRAY_MULTI
> + select KVM_MEMORY_ATTRIBUTES
> bool
>
> config HAVE_KVM_ARCH_GMEM_PREPARE
> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> index d414ebfcb4c19..0cff9a85a4c53 100644
> --- a/virt/kvm/guest_memfd.c
> +++ b/virt/kvm/guest_memfd.c
> @@ -183,10 +183,12 @@ static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
>
> static enum kvm_gfn_range_filter kvm_gmem_get_invalidate_filter(struct inode *inode)
> {
> - if (GMEM_I(inode)->flags & GUEST_MEMFD_FLAG_INIT_SHARED)
> - return KVM_FILTER_SHARED;
> -
> - return KVM_FILTER_PRIVATE;
> + /*
> + * TODO: Limit invalidations based on the to-be-invalidated range, i.e.
> + * invalidate shared/private if and only if there can possibly be
> + * such mappings.
> + */
> + return KVM_FILTER_SHARED | KVM_FILTER_PRIVATE;
> }
>
> static void __kvm_gmem_invalidate_begin(struct gmem_file *f, pgoff_t start,
> @@ -552,11 +554,235 @@ unsigned long kvm_gmem_get_memory_attributes(struct kvm *kvm, gfn_t gfn)
> }
> EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gmem_get_memory_attributes);
>
> +static bool kvm_gmem_range_has_attributes(struct maple_tree *mt,
> + pgoff_t index, size_t nr_pages,
> + u64 attributes)
> +{
> + pgoff_t end = index + nr_pages - 1;
> + void *entry;
> +
> + lockdep_assert(mt_lock_is_held(mt));
> +
> + mt_for_each(mt, entry, index, end) {
> + if (xa_to_value(entry) != attributes)
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static bool kvm_gmem_is_safe_for_conversion(struct inode *inode, pgoff_t start,
> + size_t nr_pages, pgoff_t *err_index)
> +{
> + struct address_space *mapping = inode->i_mapping;
> + const int filemap_get_folios_refcount = 1;
> + pgoff_t last = start + nr_pages - 1;
> + struct folio_batch fbatch;
> + bool safe = true;
> + int i;
> +
> + folio_batch_init(&fbatch);
> + while (safe && filemap_get_folios(mapping, &start, last, &fbatch)) {
> +
> + for (i = 0; i < folio_batch_count(&fbatch); ++i) {
> + struct folio *folio = fbatch.folios[i];
> +
> + if (folio_ref_count(folio) !=
> + folio_nr_pages(folio) + filemap_get_folios_refcount) {
> + safe = false;
> + *err_index = folio->index;
> + break;
> + }
> + }
> +
> + folio_batch_release(&fbatch);
> + cond_resched();
> + }
> +
> + return safe;
> +}
> +
> +/*
> + * Preallocate memory for attributes to be stored on a maple tree, pointed to
> + * by mas. Adjacent ranges with attributes identical to the new attributes
> + * will be merged. Also sets mas's bounds up for storing attributes.
> + *
> + * This maintains the invariant that ranges with the same attributes will
> + * always be merged.
> + */
> +static int kvm_gmem_mas_preallocate(struct ma_state *mas, u64 attributes,
> + pgoff_t start, size_t nr_pages)
> +{
> + pgoff_t end = start + nr_pages;
> + pgoff_t last = end - 1;
> + void *entry;
> +
> + /* Try extending range. entry is NULL on overflow/wrap-around. */
> + mas_set_range(mas, end, end);
> + entry = mas_find(mas, end);
> + if (entry && xa_to_value(entry) == attributes)
> + last = mas->last;
> +
> + if (start > 0) {
> + mas_set_range(mas, start - 1, start - 1);
> + entry = mas_find(mas, start - 1);
> + if (entry && xa_to_value(entry) == attributes)
> + start = mas->index;
> + }
> +
> + mas_set_range(mas, start, last);
> + return mas_preallocate(mas, xa_mk_value(attributes), GFP_KERNEL);
> +}
> +
> +#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_INVALIDATE
> +static void kvm_gmem_invalidate(struct inode *inode, pgoff_t start, pgoff_t end)
> +{
> + struct folio_batch fbatch;
> + pgoff_t next = start;
> + int i;
> +
> + folio_batch_init(&fbatch);
> + while (filemap_get_folios(inode->i_mapping, &next, end - 1, &fbatch)) {
> + for (i = 0; i < folio_batch_count(&fbatch); ++i) {
> + struct folio *folio = fbatch.folios[i];
> + unsigned long pfn = folio_pfn(folio);
> +
> + kvm_arch_gmem_invalidate(pfn, pfn + folio_nr_pages(folio));
> + }
> +
> + folio_batch_release(&fbatch);
> + cond_resched();
> + }
> +}
> +#else
> +static void kvm_gmem_invalidate(struct inode *inode, pgoff_t start, pgoff_t end) {}
> +#endif
> +
> +static int __kvm_gmem_set_attributes(struct inode *inode, pgoff_t start,
> + size_t nr_pages, uint64_t attrs,
> + pgoff_t *err_index)
> +{
> + bool to_private = attrs & KVM_MEMORY_ATTRIBUTE_PRIVATE;
> + struct address_space *mapping = inode->i_mapping;
> + struct gmem_inode *gi = GMEM_I(inode);
> + pgoff_t end = start + nr_pages;
> + struct maple_tree *mt;
> + struct ma_state mas;
> + int r;
> +
> + mt = &gi->attributes;
> +
> + filemap_invalidate_lock(mapping);
> +
> + mas_init(&mas, mt, start);
> +
> + if (kvm_gmem_range_has_attributes(mt, start, nr_pages, attrs)) {
> + r = 0;
> + goto out;
> + }
> +
> + r = kvm_gmem_mas_preallocate(&mas, attrs, start, nr_pages);
> + if (r) {
> + *err_index = start;
> + goto out;
> + }
> +
> + if (to_private) {
> + unmap_mapping_pages(mapping, start, nr_pages, false);
> +
> + if (!kvm_gmem_is_safe_for_conversion(inode, start, nr_pages,
> + err_index)) {
> + mas_destroy(&mas);
> + r = -EAGAIN;
> + goto out;
> + }
> + }
> +
> + /*
> + * From this point on guest_memfd has performed necessary
> + * checks and can proceed to do guest-breaking changes.
> + */
> +
> + kvm_gmem_invalidate_begin(inode, start, end);
> +
> + if (!to_private)
> + kvm_gmem_invalidate(inode, start, end);
> +
> + mas_store_prealloc(&mas, xa_mk_value(attrs));
> +
> + kvm_gmem_invalidate_end(inode, start, end);
> +out:
> + filemap_invalidate_unlock(mapping);
> + return r;
> +}
> +
> +static long kvm_gmem_set_attributes(struct file *file, void __user *argp)
> +{
> + struct gmem_file *f = file->private_data;
> + struct inode *inode = file_inode(file);
> + struct kvm_memory_attributes2 attrs;
> + pgoff_t err_index;
> + size_t nr_pages;
> + pgoff_t index;
> + int i, r;
> +
> + if (copy_from_user(&attrs, argp, sizeof(attrs)))
> + return -EFAULT;
> +
> + if (attrs.flags)
> + return -EINVAL;
> + if (attrs.error_offset)
> + return -EINVAL;
> + for (i = 0; i < ARRAY_SIZE(attrs.reserved); i++) {
> + if (attrs.reserved[i])
> + return -EINVAL;
> + }
> + if (attrs.attributes & ~kvm_supported_mem_attributes(f->kvm))
> + return -EINVAL;
> + if (attrs.size == 0 || attrs.offset + attrs.size < attrs.offset)
> + return -EINVAL;
> + if (!PAGE_ALIGNED(attrs.offset) || !PAGE_ALIGNED(attrs.size))
> + return -EINVAL;
> +
> + if (attrs.offset >= inode->i_size ||
> + attrs.offset + attrs.size > inode->i_size)
> + return -EINVAL;
> +
> + nr_pages = attrs.size >> PAGE_SHIFT;
> + index = attrs.offset >> PAGE_SHIFT;
> + r = __kvm_gmem_set_attributes(inode, index, nr_pages, attrs.attributes,
> + &err_index);
> + if (r) {
> + attrs.error_offset = ((uint64_t)err_index) << PAGE_SHIFT;
> +
> + if (copy_to_user(argp, &attrs, sizeof(attrs)))
> + return -EFAULT;
> + }
> +
> + return r;
> +}
> +
> +static long kvm_gmem_ioctl(struct file *file, unsigned int ioctl,
> + unsigned long arg)
> +{
> + switch (ioctl) {
> + case KVM_SET_MEMORY_ATTRIBUTES2:
> + if (vm_memory_attributes)
> + return -ENOTTY;
> +
> + return kvm_gmem_set_attributes(file, (void __user *)arg);
> + default:
> + return -ENOTTY;
> + }
> +}
> +
> +
> static struct file_operations kvm_gmem_fops = {
> .mmap = kvm_gmem_mmap,
> .open = generic_file_open,
> .release = kvm_gmem_release,
> .fallocate = kvm_gmem_fallocate,
> + .unlocked_ioctl = kvm_gmem_ioctl,
> };
>
> static int kvm_gmem_migrate_folio(struct address_space *mapping,
> @@ -942,20 +1168,13 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gmem_get_pfn);
> static bool kvm_gmem_range_is_private(struct gmem_inode *gi, pgoff_t index,
> size_t nr_pages, struct kvm *kvm, gfn_t gfn)
> {
> - pgoff_t end = index + nr_pages - 1;
> - void *entry;
> -
> if (vm_memory_attributes)
> return kvm_range_has_vm_memory_attributes(kvm, gfn, gfn + nr_pages,
> KVM_MEMORY_ATTRIBUTE_PRIVATE,
> KVM_MEMORY_ATTRIBUTE_PRIVATE);
>
> - mt_for_each(&gi->attributes, entry, index, end) {
> - if (xa_to_value(entry) != KVM_MEMORY_ATTRIBUTE_PRIVATE)
> - return false;
> - }
> -
> - return true;
> + return kvm_gmem_range_has_attributes(&gi->attributes, index, nr_pages,
> + KVM_MEMORY_ATTRIBUTE_PRIVATE);
> }
>
> static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 3c261904322f0..85c14197587d4 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -2435,16 +2435,6 @@ static int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,
> #endif /* CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT */
>
> #ifdef CONFIG_KVM_MEMORY_ATTRIBUTES
> -static u64 kvm_supported_mem_attributes(struct kvm *kvm)
> -{
> -#ifdef kvm_arch_has_private_mem
> - if (!kvm || kvm_arch_has_private_mem(kvm))
> - return KVM_MEMORY_ATTRIBUTE_PRIVATE;
> -#endif
> -
> - return 0;
> -}
> -
> #ifdef CONFIG_KVM_VM_MEMORY_ATTRIBUTES
> static unsigned long kvm_get_vm_memory_attributes(struct kvm *kvm, gfn_t gfn)
> {
> @@ -2635,6 +2625,8 @@ static int kvm_vm_ioctl_set_mem_attributes(struct kvm *kvm,
> return -EINVAL;
> if (!PAGE_ALIGNED(attrs->address) || !PAGE_ALIGNED(attrs->size))
> return -EINVAL;
> + if (attrs->error_offset)
> + return -EINVAL;
> for (i = 0; i < ARRAY_SIZE(attrs->reserved); i++) {
> if (attrs->reserved[i])
> return -EINVAL;
> @@ -4983,6 +4975,11 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
> return 1;
> case KVM_CAP_GUEST_MEMFD_FLAGS:
> return kvm_gmem_get_supported_flags(kvm);
> + case KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES:
> + if (vm_memory_attributes)
> + return 0;
> +
> + return kvm_supported_mem_attributes(kvm);
Based on the discussion from the PUCK call this morning, it sounds like it
would be a good idea to limit kvm_supported_mem_attributes() to only
reporting KVM_MEMORY_ATTRIBUTE_PRIVATE if the underlying CoCo
implementation has all the necessary enablement to support in-place
conversion via guest_memfd. In the case of SNP, there is a
documentation/parameter check in snp_launch_update() that needs to be
relaxed in order for userspace to be able to pass in a NULL 'src'
parameter (since, for in-place conversion, it would be initialized in place
as shared memory prior to the call, since by the time kvm_gmem_poulate()
it will have been set to private and therefore cannot be faulted in via
GUP (and if it could, we'd be unecessarily copying the src back on top
of itself since src/dst are the same).
So maybe there should be an arch hook to check a whitelist of VM types
that support KVM_MEMORY_ATTRIBUTE_PRIVATE when vm_memory_attributes=0,
and if we decide to enable it for SNP as part of this series you could
include the 1-2 patches needed there, or I could enable the SNP support
separately as a small series and I guess that would then become a prereq
for the SNP self-tests?
Not sure if additional enablement is needed for TDX or not before
KVM_MEMORY_ATTRIBUTE_PRIVATE would be advertised, but similar
considerations there.
-Mike
> #endif
> default:
> break;
>
> --
> 2.53.0.1018.g2bb0e51243-goog
>
^ permalink raw reply
* Re: [PATCH v6] hwmon: add driver for ARCTIC Fan Controller
From: Guenter Roeck @ 2026-04-01 15:32 UTC (permalink / raw)
To: Aureo Serrano de Souza
Cc: linux-hwmon, linux, corbet, skhan, linux-doc, linux-kernel
In-Reply-To: <20260401112654.60560-1-aureo.serrano@arctic.de>
Hi,
On Wed, Apr 01, 2026 at 07:25:54PM +0800, 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[] is also protected by this spinlock
> because reset_resume() clears it outside the hwmon core lock. The OUT
> report buffer is built and write_pending is armed under the same lock so
> that no reset_resume() can race with the pwm_duty[] snapshot. priv->buf
> is exclusively accessed by write(), which the hwmon core serializes.
>
> Signed-off-by: Aureo Serrano de Souza <aureo.serrano@arctic.de>
> ---
Looks like the patch is corrupted.
Applying: hwmon: add driver for ARCTIC Fan Controller
error: corrupt patch at line 587
error: could not build fake ancestor
Patch failed at 0001 hwmon: add driver for ARCTIC Fan Controller
I can not figure out what is wrong, but both git and patch report that it is
corrupted. Please fix and resend.
Thanks,
Guenter
^ permalink raw reply
* Re: [PATCH 33/33] rust: kbuild: allow `clippy::precedence` for Rust < 1.86.0
From: Gary Guo @ 2026-04-01 15:28 UTC (permalink / raw)
To: Miguel Ojeda, Nathan Chancellor, Nicolas Schier, Danilo Krummrich,
Andreas Hindborg, Catalin Marinas, Will Deacon, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Courbot, David Airlie,
Simona Vetter, Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Trevor Gross, rust-for-linux, linux-kbuild, Lorenzo Stoakes,
Vlastimil Babka, Liam R . Howlett, Uladzislau Rezki, linux-block,
moderated for non-subscribers, Alexandre Ghiti, linux-riscv,
nouveau, dri-devel, Rae Moar, linux-kselftest, kunit-dev,
Nick Desaulniers, Bill Wendling, Justin Stitt, llvm, linux-kernel,
Shuah Khan, linux-doc
In-Reply-To: <20260401114540.30108-34-ojeda@kernel.org>
On Wed Apr 1, 2026 at 12:45 PM BST, Miguel Ojeda wrote:
> The Clippy `precedence` lint was extended in Rust 1.85.0 to include
> bitmasking and shift operations [1]. However, because it generated
> many hits, in Rust 1.86.0 it was split into a new `precedence_bits`
> lint which is not enabled by default [2].
>
> In other words, only Rust 1.85 has a different behavior. For instance,
> it reports:
>
> warning: operator precedence can trip the unwary
> --> drivers/gpu/nova-core/fb/hal/ga100.rs:16:5
> |
> 16 | / u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::read(bar).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT
> 17 | | | u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::read(bar).adr_63_40())
> 18 | | << FLUSH_SYSMEM_ADDR_SHIFT_HI
> | |_________________________________________^
> |
> = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#precedence
> = note: `-W clippy::precedence` implied by `-W clippy::all`
> = help: to override `-W clippy::all` add `#[allow(clippy::precedence)]`
> help: consider parenthesizing your expression
> |
> 16 ~ (u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::read(bar).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT) | (u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::read(bar).adr_63_40())
> 17 + << FLUSH_SYSMEM_ADDR_SHIFT_HI)
> |
>
> warning: operator precedence can trip the unwary
> --> drivers/gpu/nova-core/vbios.rs:511:17
> |
> 511 | / u32::from(data[29]) << 24
> 512 | | | u32::from(data[28]) << 16
> 513 | | | u32::from(data[27]) << 8
> | |______________________________________________^
> |
> = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#precedence
> help: consider parenthesizing your expression
> |
> 511 ~ u32::from(data[29]) << 24
> 512 + | u32::from(data[28]) << 16 | (u32::from(data[27]) << 8)
> |
>
> warning: operator precedence can trip the unwary
> --> drivers/gpu/nova-core/vbios.rs:511:17
> |
> 511 | / u32::from(data[29]) << 24
> 512 | | | u32::from(data[28]) << 16
> | |_______________________________________________^ help: consider parenthesizing your expression: `(u32::from(data[29]) << 24) | (u32::from(data[28]) << 16)`
> |
> = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#precedence
>
> While so far we try our best to keep all versions Clippy-clean, the
> minimum (which is now Rust 1.85.0 after the bump) and the latest stable
> are the most important ones; and this may be considered "false positives"
> with respect to the behavior in other versions.
>
> Thus allow this lint for this version using the per-version flags
> mechanism introduced in the previous commit.
>
> Link: https://github.com/rust-lang/rust-clippy/issues/14097 [1]
> Link: https://github.com/rust-lang/rust-clippy/pull/14115 [2]
> Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Link: https://lore.kernel.org/rust-for-linux/DFVDKMMA7KPC.2DN0951H3H55Y@kernel.org/
Reviewed-by: Gary Guo <gary@garyguo.net>
> ---
> Makefile | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
^ permalink raw reply
* Re: [PATCH 32/33] rust: kbuild: support global per-version flags
From: Gary Guo @ 2026-04-01 15:26 UTC (permalink / raw)
To: Miguel Ojeda, Nathan Chancellor, Nicolas Schier, Danilo Krummrich,
Andreas Hindborg, Catalin Marinas, Will Deacon, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Courbot, David Airlie,
Simona Vetter, Brendan Higgins, David Gow, Greg Kroah-Hartman,
Arve Hjønnevåg, Todd Kjos, Christian Brauner,
Carlos Llamas, Alice Ryhl, Jonathan Corbet
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Trevor Gross, rust-for-linux, linux-kbuild, Lorenzo Stoakes,
Vlastimil Babka, Liam R . Howlett, Uladzislau Rezki, linux-block,
moderated for non-subscribers, Alexandre Ghiti, linux-riscv,
nouveau, dri-devel, Rae Moar, linux-kselftest, kunit-dev,
Nick Desaulniers, Bill Wendling, Justin Stitt, llvm, linux-kernel,
Shuah Khan, linux-doc
In-Reply-To: <20260401114540.30108-33-ojeda@kernel.org>
On Wed Apr 1, 2026 at 12:45 PM BST, Miguel Ojeda wrote:
> Sometimes it is useful to gate global Rust flags per compiler version.
> For instance, we may want to disable a lint that has false positives in
> a single version [1].
>
> We already had helpers like `rustc-min-version` for that, which we use
> elsewhere, but we cannot currently use them for `rust_common_flags`,
> which contains the global flags for all Rust code (kernel and host),
> because `rustc-min-version` depends on `CONFIG_RUSTC_VERSION`, which
> does not exist when `rust_common_flags` is defined.
>
> Thus, to support that, introduce `rust_common_flags_per_version`,
> defined after the `include/config/auto.conf` inclusion (where
> `CONFIG_RUSTC_VERSION` becomes available), and append it to
> `rust_common_flags`, `KBUILD_HOSTRUSTFLAGS` and `KBUILD_RUSTFLAGS`.
>
> An alternative is moving all those three down, but that would mean
> separating them from the other `KBUILD_*` variables.
I think I would prefer moving these down.
The current approach append the flags to all variables, which will cause the
following equivalence to stop holding after the flag update.
KBUILD_HOSTRUSTFLAGS := $(rust_common_flags) -O -Cstrip=debuginfo \
-Zallow-features= $(HOSTRUSTFLAGS)
(Per version flags doesn't go before -O anymore, it comes after HOSTRUSTFLAGS).
Best,
Gary
>
> Link: https://lore.kernel.org/rust-for-linux/CANiq72mWdFU11GcCZRchzhy0Gi1QZShvZtyRkHV2O+WA2uTdVQ@mail.gmail.com/ [1]
> Link: https://patch.msgid.link/20260307170929.153892-1-ojeda@kernel.org
> Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
> ---
> Makefile | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/Makefile b/Makefile
> index 1a219bf1c771..20c8179d96ee 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -834,6 +834,14 @@ endif # CONFIG_TRACEPOINTS
>
> export WARN_ON_UNUSED_TRACEPOINTS
>
> +# Per-version Rust flags. These are like `rust_common_flags`, but may
> +# depend on the Rust compiler version (e.g. using `rustc-min-version`).
> +rust_common_flags_per_version :=
> +
> +rust_common_flags += $(rust_common_flags_per_version)
> +KBUILD_HOSTRUSTFLAGS += $(rust_common_flags_per_version)
> +KBUILD_RUSTFLAGS += $(rust_common_flags_per_version)
> +
> include $(srctree)/arch/$(SRCARCH)/Makefile
>
> ifdef need-config
^ permalink raw reply
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