From: Luigi Rizzo <lrizzo@google.com>
To: Thomas Gleixner <tglx@linutronix.de>,
Marc Zyngier <maz@kernel.org>,
Luigi Rizzo <rizzo.unipi@gmail.com>,
Paolo Abeni <pabeni@redhat.com>
Cc: linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org,
Bjorn Helgaas <bhelgaas@google.com>,
Luigi Rizzo <lrizzo@google.com>
Subject: [PATCH v5 5/7] genirq: Add GSIM user space configuration (procfs)
Date: Wed, 19 Aug 2026 12:43:39 +0000 [thread overview]
Message-ID: <20260819124341.4185621-6-lrizzo@google.com> (raw)
In-Reply-To: <20260819124341.4185621-1-lrizzo@google.com>
Introduce procfs interfaces to configure and monitor GSIM at runtime.
This adds:
- A global directory /proc/irq/sw_moderation/ containing:
- delay_us: Read/write interface to set/get the maximum moderation
delay in microseconds (defaults to 0, GSIM disabled).
- stats: Read-only interface to monitor GSIM statistics per-CPU.
- files /proc/irq/NN/allow_sw_moderation (where NN is the IRQ number) to
individually allow/disallow moderation. Created only for interrupts
that support moderation (e.g., edge-triggered, single-target, etc.).
Signed-off-by: Luigi Rizzo <lrizzo@google.com>
---
kernel/irq/internals.h | 4 +
kernel/irq/irq_moderation.c | 256 +++++++++++++++++++++++++++++++++++-
kernel/irq/proc.c | 2 +
3 files changed, 261 insertions(+), 1 deletion(-)
diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h
index 705c3bfe28e12..22a1926910f34 100644
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -403,6 +403,8 @@ static inline void irq_moderation_init_fields(struct irq_desc *desc)
int irq_moderation_allow(struct irq_desc *desc, bool allow);
bool irq_moderation_supported(struct irq_desc *desc);
+void irq_moderation_procfs_add(struct irq_desc *desc, umode_t umode);
+void irq_moderation_procfs_remove(struct irq_desc *desc);
#else
static inline void irq_moderation_init_fields(struct irq_desc *desc) {}
static inline int irq_moderation_allow(struct irq_desc *desc, bool allow)
@@ -410,4 +412,6 @@ static inline int irq_moderation_allow(struct irq_desc *desc, bool allow)
return allow ? -EOPNOTSUPP : 0;
}
static inline bool irq_moderation_supported(struct irq_desc *desc) { return false; }
+static inline void irq_moderation_procfs_add(struct irq_desc *desc, umode_t umode) {}
+static inline void irq_moderation_procfs_remove(struct irq_desc *desc) {}
#endif
diff --git a/kernel/irq/irq_moderation.c b/kernel/irq/irq_moderation.c
index 2c75feb6634f3..1474d33455410 100644
--- a/kernel/irq/irq_moderation.c
+++ b/kernel/irq/irq_moderation.c
@@ -11,6 +11,8 @@
#include <linux/irqdesc.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
#include <linux/suspend.h>
#include "internals.h"
@@ -24,6 +26,21 @@
* GSIM runs after the handler to implement software interrupt moderation
* with programmable delay.
*
+ * Configuration is done at runtime via procfs
+ * echo ${VALUE} > /proc/irq/sw_moderation/${NAME}
+ *
+ * Supported parameters:
+ *
+ * delay_us (default 0, suggested 100, 0 off, range 0-500)
+ * Maximum moderation delay. A reasonable range is 20-100. Higher values
+ * can be useful if the hardirq handler has long runtimes.
+ *
+ * Moderation is allowed/disallowed dynamically for individual interrupts with
+ * echo 1 > /proc/irq/NN/allow_sw_moderation # use 0 to disallow
+ *
+ * Monitoring of per-cpu and global statistics is available via procfs
+ * cat /proc/irq/sw_moderation/stats
+ *
* === ARCHITECTURE ===
*
* INTERRUPT HANDLING (for interrupt types that support moderation)
@@ -101,6 +118,8 @@ DEFINE_PER_CPU_ALIGNED(struct irq_mod_state, irq_mod_state);
DEFINE_STATIC_KEY_FALSE(irq_moderation_enabled_key);
+static DEFINE_MUTEX(swmod_mutex);
+
static void update_enable_key(void)
{
if (irq_mod_params.delay_ns != 0)
@@ -139,6 +158,185 @@ bool irq_moderation_do_start(struct irq_desc *desc, struct irq_mod_state *m)
return true;
}
+/*
+ * struct var_info - target and limits for parameters
+ * @ptr: pointer to the value, NULL if not used.
+ * @min: minimum value allowed
+ * @max: maximum value allowed
+ * @scale: scale factor between procfs and internal.
+ */
+struct var_info {
+ unsigned int *ptr;
+ unsigned int min;
+ unsigned int max;
+ unsigned int scale;
+};
+
+/*
+ * struct swmod_procfs_entry - description for procfs entries and parameter limits
+ * @name: name in procfs. If NULL, the entry is only for limit checks.
+ * @wr: write handler for procfs. NULL if readonly
+ * @rd: read handler for procfs.
+ * @var: variable address and limits, if used.
+ */
+struct swmod_procfs_entry {
+ const char *name;
+ ssize_t (*wr)(struct var_info *n, const char __user *s, size_t count);
+ void (*rd)(struct seq_file *p);
+ struct var_info var;
+};
+
+static ssize_t swmod_wr(struct var_info *v, const char __user *s, size_t count)
+{
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint_from_user(s, count, 0, &value);
+ if (ret)
+ return ret;
+ if (value < v->min || value > v->max)
+ return -ERANGE;
+ WRITE_ONCE(*v->ptr, value * v->scale);
+ return count;
+}
+
+static void swmod_rd(struct seq_file *p)
+{
+ struct swmod_procfs_entry *n = p->private;
+
+ seq_printf(p, "%u\n", *n->var.ptr / n->var.scale);
+}
+
+static ssize_t swmod_wr_delay(struct var_info *v, const char __user *s, size_t count)
+{
+ ssize_t ret = swmod_wr(v, s, count);
+
+ if (ret >= 0)
+ update_enable_key();
+ return ret;
+}
+
+#define HEAD_FMT "%5s %8s %11s %11s\n"
+#define BODY_FMT "%5u %8u %11u %11u\n"
+
+/* Print statistics */
+static void rd_stats(struct seq_file *p)
+{
+ unsigned int delay_ns = READ_ONCE(irq_mod_params.delay_ns);
+ int cpu;
+
+ if (delay_ns == 0)
+ return;
+ seq_printf(p, HEAD_FMT,
+ "# CPU", "delay_ns", "timer_set", "enqueue");
+
+ for_each_possible_cpu(cpu) {
+ /* Copy statistics, will only use some unsigned int values; races ok. */
+ struct irq_mod_state cur = data_race(*per_cpu_ptr(&irq_mod_state, cpu));
+
+ seq_printf(p, BODY_FMT,
+ cpu,
+ delay_ns,
+ cur.timer_set,
+ cur.enqueue);
+ }
+
+ seq_printf(p, "\n"
+ "delay_us %lu\n",
+ delay_ns / NSEC_PER_USEC);
+}
+
+static int param_show(struct seq_file *p, void *v)
+{
+ struct swmod_procfs_entry *n = p->private;
+
+ n->rd(p);
+ return 0;
+}
+
+static int param_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, param_show, pde_data(inode));
+}
+
+static ssize_t param_write(struct file *f, const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct swmod_procfs_entry *n = (struct swmod_procfs_entry *)pde_data(file_inode(f));
+ ssize_t ret;
+
+ if (!n->wr)
+ return -EINVAL;
+ mutex_lock(&swmod_mutex);
+ ret = n->wr(&n->var, buf, count);
+ mutex_unlock(&swmod_mutex);
+ return ret;
+}
+
+static const struct proc_ops param_ops = {
+ .proc_open = param_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_release = single_release,
+ .proc_write = param_write,
+};
+
+/* Handlers for /proc/irq/NN/allow_sw_moderation */
+static int allow_flag_show(struct seq_file *p, void *v)
+{
+ struct irq_desc *desc = irq_to_desc((long)p->private);
+
+ if (!desc)
+ return -ENODEV;
+
+ seq_puts(p, irq_settings_moderatable(desc) ? "on\n" : "off\n");
+ return 0;
+}
+
+
+static ssize_t allow_flag_write(struct file *f, const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct irq_desc *desc = irq_to_desc((long)pde_data(file_inode(f)));
+ bool allow;
+ int ret;
+
+ if (!desc)
+ return -ENODEV;
+
+ ret = kstrtobool_from_user(buf, count, &allow);
+
+ if (!ret) {
+ guard(raw_spinlock_irq)(&desc->lock);
+ ret = irq_moderation_allow(desc, allow);
+ }
+ return ret ? : count;
+}
+
+static int allow_flag_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, allow_flag_show, pde_data(inode));
+}
+
+static const struct proc_ops allow_flag_ops = {
+ .proc_open = allow_flag_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_release = single_release,
+ .proc_write = allow_flag_write,
+};
+
+void irq_moderation_procfs_add(struct irq_desc *desc, umode_t umode)
+{
+ if (!irq_moderation_supported(desc))
+ return;
+ proc_create_data("allow_sw_moderation", umode, desc->dir,
+ &allow_flag_ops, (void *)(long)desc->irq_data.irq);
+}
+
+void irq_moderation_procfs_remove(struct irq_desc *desc)
+{
+ remove_proc_entry("allow_sw_moderation", desc->dir);
+}
+
static void clean_moderation_state(struct irq_desc *desc)
{
/*
@@ -267,10 +465,41 @@ struct notifier_block mod_nb = {
.priority = 100,
};
+/* Helper to initialize the struct var_info. */
+#define SET_VAR(_ptr, _min, _max, _scale) \
+ { .ptr = (_ptr), .min = (_min), .max = (_max), .scale = (_scale), }
+
+static struct swmod_procfs_entry procfs_entries[] = {
+ {
+ .name = "delay_us",
+ .wr = swmod_wr_delay,
+ .rd = swmod_rd,
+ .var = SET_VAR(&irq_mod_params.delay_ns, 0, 500, NSEC_PER_USEC),
+ },
+ {
+ .name = "stats",
+ .rd = rd_stats,
+ },
+};
+
static int __init init_irq_moderation(void)
{
+ struct proc_dir_entry *dir;
int cpuhp_state;
- int ret;
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(procfs_entries); i++) {
+ struct var_info *v = &procfs_entries[i].var;
+
+ if (!v->ptr)
+ continue;
+ if (*v->ptr >= v->min * v->scale && *v->ptr <= v->max * v->scale)
+ continue;
+ pr_err("%s: Parameter %s: value %u out of bounds [%u,%u]\n",
+ __func__, procfs_entries[i].name ? : "no-name",
+ *v->ptr / v->scale, v->min, v->max);
+ return -ERANGE;
+ }
cpuhp_state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "sw_moderation",
cpu_setup_cb, cpu_remove_cb);
@@ -285,10 +514,35 @@ static int __init init_irq_moderation(void)
goto cleanup;
}
+ /* Safe because /proc/irq is created earlier, in kernel_init_freeable(). */
+ dir = proc_mkdir("irq/sw_moderation", NULL);
+ if (!dir) {
+ pr_err("%s: Failed to create procfs directory\n", __func__);
+ goto cleanup_1;
+ }
+ for (i = 0; i < ARRAY_SIZE(procfs_entries); i++) {
+ struct swmod_procfs_entry *n = &procfs_entries[i];
+
+ if (!n->name || proc_create_data(n->name, n->wr ? 0644 : 0444, dir, ¶m_ops, n))
+ continue;
+ pr_err("%s: Failed to create procfs entry %s\n", __func__, n->name);
+ for (i--; i >= 0; i--) {
+ n = &procfs_entries[i];
+ if (n->name)
+ remove_proc_entry(n->name, dir);
+ }
+ remove_proc_entry("irq/sw_moderation", NULL);
+ goto cleanup_1;
+ }
+
/* Enable if the defaults require it. */
update_enable_key();
return 0;
+cleanup_1:
+ ret = -ENOMEM;
+ unregister_pm_notifier(&mod_nb);
+
cleanup:
cpuhp_remove_state(cpuhp_state);
return ret;
diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index 1b835725f7b1c..edffb30efab58 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -379,6 +379,7 @@ void register_irq_proc(unsigned int irq, struct irq_desc *desc)
irq_effective_aff_list_proc_show, irqp);
# endif
#endif
+ irq_moderation_procfs_add(desc, 0644);
proc_create_single_data("spurious", 0444, desc->dir,
irq_spurious_proc_show, (void *)(long)irq);
@@ -400,6 +401,7 @@ void unregister_irq_proc(unsigned int irq, struct irq_desc *desc)
remove_proc_entry("effective_affinity_list", desc->dir);
# endif
#endif
+ irq_moderation_procfs_remove(desc);
remove_proc_entry("spurious", desc->dir);
snprintf(name, MAX_NAMELEN, "%u", irq);
--
2.55.0.737.g08866a6d13-goog
next prev parent reply other threads:[~2026-08-19 12:44 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 12:43 [PATCH v5 0/7] Global Software Interrupt Moderation (GSIM) Luigi Rizzo
2026-08-19 12:43 ` [PATCH v5 1/7] genirq: Add flags for software interrupt moderation Luigi Rizzo
2026-08-19 12:50 ` sashiko-bot
2026-08-19 12:43 ` [PATCH v5 2/7] genirq: Add GSIM infrastructure Luigi Rizzo
2026-08-19 12:48 ` sashiko-bot
2026-08-19 12:43 ` [PATCH v5 3/7] genirq: Implement core GSIM moderation logic Luigi Rizzo
2026-08-19 12:52 ` sashiko-bot
2026-08-19 12:43 ` [PATCH v5 4/7] genirq: Integrate GSIM into interrupt flow Luigi Rizzo
2026-08-19 12:58 ` sashiko-bot
2026-08-19 12:43 ` Luigi Rizzo [this message]
2026-08-19 12:58 ` [PATCH v5 5/7] genirq: Add GSIM user space configuration (procfs) sashiko-bot
2026-08-19 12:43 ` [PATCH v5 6/7] genirq: Adaptive Global Software Interrupt Moderation (GSIM) Luigi Rizzo
2026-08-19 12:59 ` sashiko-bot
2026-08-19 12:43 ` [PATCH v5 7/7] PCI/MSI: re-enable conditional parent mask/unmask with sw moderation Luigi Rizzo
2026-08-19 12:51 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819124341.4185621-6-lrizzo@google.com \
--to=lrizzo@google.com \
--cc=bhelgaas@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=maz@kernel.org \
--cc=pabeni@redhat.com \
--cc=rizzo.unipi@gmail.com \
--cc=tglx@linutronix.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox