Linux PCI subsystem development
 help / color / mirror / Atom feed
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, &param_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


  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