All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.