Linux Documentation
 help / color / mirror / Atom feed
* [PATCH v4 06/11] HID: spi_hid: add spi_hid traces
From: Jingyuan Liang @ 2026-06-09  4:40 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-input, linux-doc, linux-kernel, linux-spi,
	linux-trace-kernel, devicetree, hbarnor, tfiga, Jingyuan Liang,
	Dmitry Antipov, Angela Czubak
In-Reply-To: <20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org>

Add traces for purposed of debugging spi_hid driver.

Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
Signed-off-by: Angela Czubak <acz@semihalf.com>
Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
---
 drivers/hid/spi-hid/Makefile        |   1 +
 drivers/hid/spi-hid/spi-hid-core.c  | 114 +++++++++---------------
 drivers/hid/spi-hid/spi-hid-core.h  |  91 +++++++++++++++++++
 drivers/hid/spi-hid/spi-hid-trace.h | 169 ++++++++++++++++++++++++++++++++++++
 4 files changed, 300 insertions(+), 75 deletions(-)

diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
index 92e24cddbfc2..733e006df56e 100644
--- a/drivers/hid/spi-hid/Makefile
+++ b/drivers/hid/spi-hid/Makefile
@@ -7,3 +7,4 @@
 
 obj-$(CONFIG_SPI_HID_CORE)	+= spi-hid.o
 spi-hid-objs 			= spi-hid-core.o
+CFLAGS_spi-hid-core.o 		:= -I$(src)
diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index f6ea2d4365a7..ef527999d6dc 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -44,6 +44,11 @@
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 
+#include "spi-hid-core.h"
+
+#define CREATE_TRACE_POINTS
+#include "spi-hid-trace.h"
+
 /* Protocol constants */
 #define SPI_HID_READ_APPROVAL_CONSTANT		0xff
 #define SPI_HID_INPUT_HEADER_SYNC_BYTE		0x5a
@@ -82,13 +87,6 @@
 #define SPI_HID_CREATE_DEVICE	4
 #define SPI_HID_ERROR	5
 
-/* Raw input buffer with data from the bus */
-struct spi_hid_input_buf {
-	u8 header[HIDSPI_INPUT_HEADER_SIZE];
-	u8 body[HIDSPI_INPUT_BODY_HEADER_SIZE];
-	u8 content[];
-};
-
 /* Processed data from input report header */
 struct spi_hid_input_header {
 	u8 version;
@@ -105,12 +103,6 @@ struct spi_hid_input_report {
 	u8 *content;
 };
 
-/* Raw output report buffer to be put on the bus */
-struct spi_hid_output_buf {
-	u8 header[SPI_HID_OUTPUT_HEADER_LEN];
-	u8 content[];
-};
-
 /* Data necessary to send an output report */
 struct spi_hid_output_report {
 	u8 report_type;
@@ -119,19 +111,6 @@ struct spi_hid_output_report {
 	u8 *content;
 };
 
-/* Processed data from a device descriptor */
-struct spi_hid_device_descriptor {
-	u16 hid_version;
-	u16 report_descriptor_length;
-	u16 max_input_length;
-	u16 max_output_length;
-	u16 max_fragment_length;
-	u16 vendor_id;
-	u16 product_id;
-	u16 version_id;
-	u8 no_output_report_ack;
-};
-
 /* struct spi_hid_conf - Conf provided to the core */
 struct spi_hid_conf {
 	u32 input_report_header_address;
@@ -157,55 +136,6 @@ struct spihid_ops {
 	void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
 };
 
-/* Driver context */
-struct spi_hid {
-	struct spi_device	*spi;	/* spi device. */
-	struct hid_device	*hid;	/* pointer to corresponding HID dev. */
-
-	struct spi_transfer	input_transfer[2];	/* Transfer buffer for read and write. */
-	struct spi_message	input_message;	/* used to execute a sequence of spi transfers. */
-
-	struct spihid_ops	*ops;
-	struct spi_hid_conf	*conf;
-
-	struct spi_hid_device_descriptor desc;	/* HID device descriptor. */
-	struct spi_hid_output_buf *output;	/* Output buffer. */
-	struct spi_hid_input_buf *input;	/* Input buffer. */
-	struct spi_hid_input_buf *response;	/* Response buffer. */
-
-	u16 response_length;
-	u16 bufsize;
-
-	enum hidspi_power_state power_state;
-
-	u8 reset_attempts;	/* The number of reset attempts. */
-
-	unsigned long flags;	/* device flags. */
-
-	struct work_struct reset_work;
-
-	/* Control lock to ensure complete output transaction. */
-	struct mutex output_lock;
-	/* Power lock to make sure one power state change at a time. */
-	struct mutex power_lock;
-	/* Protect bus I/O and shid->hid pointer lifecycle. */
-	struct mutex io_lock;
-
-	struct completion output_done;
-
-	u32 report_descriptor_crc32;	/* HID report descriptor crc32 checksum. */
-
-	u32 regulator_error_count;
-	int regulator_last_error;
-	u32 bus_error_count;
-	int bus_last_error;
-	u32 dir_count;	/* device initiated reset count. */
-
-	/* DMA-safe transfer buffers */
-	u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN] ____cacheline_aligned;
-	u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN];
-};
-
 static struct hid_ll_driver spi_hid_ll_driver;
 
 static void spi_hid_populate_read_approvals(const struct spi_hid_conf *conf,
@@ -295,6 +225,11 @@ static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length,
 	spi_message_init_with_transfers(&shid->input_message,
 					shid->input_transfer, 2);
 
+	trace_spi_hid_input_sync(shid,	shid->input_transfer[0].tx_buf,
+				 shid->input_transfer[0].len,
+				 shid->input_transfer[1].rx_buf,
+				 shid->input_transfer[1].len, 0);
+
 	error = spi_sync(shid->spi, &shid->input_message);
 	if (error) {
 		dev_err(&shid->spi->dev, "Error starting sync transfer: %d\n", error);
@@ -353,6 +288,8 @@ static void spi_hid_error_handler(struct spi_hid *shid)
 	struct device *dev = &shid->spi->dev;
 	int error;
 
+	trace_spi_hid_error_handler(shid);
+
 	guard(mutex)(&shid->power_lock);
 	if (shid->power_state == HIDSPI_OFF)
 		return;
@@ -477,6 +414,8 @@ static void spi_hid_reset_response(struct spi_hid *shid)
 	};
 	int error;
 
+	trace_spi_hid_reset_response(shid);
+
 	if (test_bit(SPI_HID_READY, &shid->flags)) {
 		dev_err(dev, "Spontaneous FW reset!\n");
 		clear_bit(SPI_HID_READY, &shid->flags);
@@ -503,6 +442,7 @@ static int spi_hid_input_report_handler(struct spi_hid *shid,
 	int error = 0;
 
 	guard(mutex)(&shid->io_lock);
+	trace_spi_hid_input_report_handler(shid);
 
 	if (!test_bit(SPI_HID_READY, &shid->flags) ||
 	    test_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags) || !shid->hid) {
@@ -528,6 +468,8 @@ static int spi_hid_input_report_handler(struct spi_hid *shid,
 static void spi_hid_response_handler(struct spi_hid *shid,
 				     struct input_report_body_header *body)
 {
+	trace_spi_hid_response_handler(shid);
+
 	shid->response_length = body->content_len;
 	/* completion_done returns 0 if there are waiters, otherwise 1 */
 	if (completion_done(&shid->output_done)) {
@@ -584,6 +526,8 @@ static int spi_hid_create_device(struct spi_hid *shid)
 	struct device *dev = &shid->spi->dev;
 	int error;
 
+	trace_spi_hid_create_device(shid);
+
 	hid = hid_allocate_device();
 	error = PTR_ERR_OR_ZERO(hid);
 	if (error) {
@@ -627,6 +571,8 @@ static void spi_hid_refresh_device(struct spi_hid *shid)
 	u32 new_crc32 = 0;
 	int error = 0;
 
+	trace_spi_hid_refresh_device(shid);
+
 	error = spi_hid_report_descriptor_request(shid);
 	if (error < 0) {
 		dev_err(dev,
@@ -708,6 +654,8 @@ static int spi_hid_process_input_report(struct spi_hid *shid,
 	struct device *dev = &shid->spi->dev;
 	struct hidspi_dev_descriptor *raw;
 
+	trace_spi_hid_process_input_report(shid);
+
 	spi_hid_populate_input_header(buf->header, &header);
 	spi_hid_populate_input_body(buf->body, &body);
 
@@ -867,6 +815,9 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
 	struct spi_hid_input_header header;
 	int error = 0;
 
+	trace_spi_hid_dev_irq(shid, irq);
+	trace_spi_hid_header_transfer(shid);
+
 	scoped_guard(mutex, &shid->io_lock) {
 		error = spi_hid_input_sync(shid, shid->input->header,
 					   sizeof(shid->input->header), true);
@@ -880,6 +831,13 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
 			goto out;
 		}
 
+		trace_spi_hid_input_header_complete(shid,
+						    shid->input_transfer[0].tx_buf,
+						    shid->input_transfer[0].len,
+						    shid->input_transfer[1].rx_buf,
+						    shid->input_transfer[1].len,
+						    shid->input_message.status);
+
 		if (shid->input_message.status < 0) {
 			dev_warn(dev, "Error reading header: %d\n",
 				 shid->input_message.status);
@@ -916,6 +874,12 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
 			goto out;
 		}
 
+		trace_spi_hid_input_body_complete(shid, shid->input_transfer[0].tx_buf,
+						  shid->input_transfer[0].len,
+						  shid->input_transfer[1].rx_buf,
+						  shid->input_transfer[1].len,
+						  shid->input_message.status);
+
 		if (shid->input_message.status < 0) {
 			dev_warn(dev, "Error reading body: %d\n",
 				 shid->input_message.status);
diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-hid-core.h
new file mode 100644
index 000000000000..293e2cfcfbf7
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-core.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ * Copyright (c) 2026 Google LLC
+ */
+
+#ifndef SPI_HID_CORE_H
+#define SPI_HID_CORE_H
+
+#include <linux/hid-over-spi.h>
+#include <linux/spi/spi.h>
+
+/* Protocol message size constants */
+#define SPI_HID_READ_APPROVAL_LEN		5
+#define SPI_HID_OUTPUT_HEADER_LEN		8
+
+/* Raw input buffer with data from the bus */
+struct spi_hid_input_buf {
+	u8 header[HIDSPI_INPUT_HEADER_SIZE];
+	u8 body[HIDSPI_INPUT_BODY_HEADER_SIZE];
+	u8 content[];
+};
+
+/* Raw output report buffer to be put on the bus */
+struct spi_hid_output_buf {
+	u8 header[SPI_HID_OUTPUT_HEADER_LEN];
+	u8 content[];
+};
+
+/* Processed data from a device descriptor */
+struct spi_hid_device_descriptor {
+	u16 hid_version;
+	u16 report_descriptor_length;
+	u16 max_input_length;
+	u16 max_output_length;
+	u16 max_fragment_length;
+	u16 vendor_id;
+	u16 product_id;
+	u16 version_id;
+	u8 no_output_report_ack;
+};
+
+/* Driver context */
+struct spi_hid {
+	struct spi_device	*spi;	/* spi device. */
+	struct hid_device	*hid;	/* pointer to corresponding HID dev. */
+
+	struct spi_transfer	input_transfer[2];	/* Transfer buffer for read and write. */
+	struct spi_message	input_message;	/* used to execute a sequence of spi transfers. */
+
+	struct spihid_ops	*ops;
+	struct spi_hid_conf	*conf;
+
+	struct spi_hid_device_descriptor desc;	/* HID device descriptor. */
+	struct spi_hid_output_buf *output;	/* Output buffer. */
+	struct spi_hid_input_buf *input;	/* Input buffer. */
+	struct spi_hid_input_buf *response;	/* Response buffer. */
+
+	u16 response_length;
+	u16 bufsize;
+
+	enum hidspi_power_state power_state;
+
+	u8 reset_attempts;	/* The number of reset attempts. */
+
+	unsigned long flags;	/* device flags. */
+
+	struct work_struct reset_work;
+
+	/* Control lock to ensure complete output transaction. */
+	struct mutex output_lock;
+	/* Power lock to make sure one power state change at a time. */
+	struct mutex power_lock;
+	/* I/O lock to prevent concurrent output writes during the input read. */
+	struct mutex io_lock;
+
+	struct completion output_done;
+
+	u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN];
+	u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN];
+
+	u32 report_descriptor_crc32;	/* HID report descriptor crc32 checksum. */
+
+	u32 regulator_error_count;
+	int regulator_last_error;
+	u32 bus_error_count;
+	int bus_last_error;
+	u32 dir_count;		/* device initiated reset count. */
+};
+
+#endif /* SPI_HID_CORE_H */
diff --git a/drivers/hid/spi-hid/spi-hid-trace.h b/drivers/hid/spi-hid/spi-hid-trace.h
new file mode 100644
index 000000000000..841ec491826d
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-trace.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM spi_hid
+
+#if !defined(_SPI_HID_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _SPI_HID_TRACE_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+#include "spi-hid-core.h"
+
+DECLARE_EVENT_CLASS(spi_hid_transfer,
+	TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+		 const void *rx_buf, u16 rx_len, int ret),
+
+	TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret),
+
+	TP_STRUCT__entry(
+		__field(int, bus_num)
+		__field(int, chip_select)
+		__field(int, ret)
+		__dynamic_array(u8, rx_buf, rx_len)
+		__dynamic_array(u8, tx_buf, tx_len)
+	),
+
+	TP_fast_assign(
+		__entry->bus_num = shid->spi->controller->bus_num;
+		__entry->chip_select = spi_get_chipselect(shid->spi, 0);
+		__entry->ret = ret;
+
+		memcpy(__get_dynamic_array(tx_buf), tx_buf, tx_len);
+		memcpy(__get_dynamic_array(rx_buf), rx_buf, rx_len);
+	),
+
+	TP_printk("spi%d.%d: len=%d tx=[%*phD] rx=[%*phD] --> %d",
+		  __entry->bus_num, __entry->chip_select,
+		  __get_dynamic_array_len(tx_buf) + __get_dynamic_array_len(rx_buf),
+		  __get_dynamic_array_len(tx_buf), __get_dynamic_array(tx_buf),
+		  __get_dynamic_array_len(rx_buf), __get_dynamic_array(rx_buf),
+		  __entry->ret)
+);
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_input_sync,
+	     TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+		      const void *rx_buf, u16 rx_len, int ret),
+	     TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret));
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_input_header_complete,
+	     TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+		      const void *rx_buf, u16 rx_len, int ret),
+	     TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret));
+
+DEFINE_EVENT(spi_hid_transfer, spi_hid_input_body_complete,
+	     TP_PROTO(struct spi_hid *shid, const void *tx_buf, int tx_len,
+		      const void *rx_buf, u16 rx_len, int ret),
+	     TP_ARGS(shid, tx_buf, tx_len, rx_buf, rx_len, ret));
+
+DECLARE_EVENT_CLASS(spi_hid_irq,
+	TP_PROTO(struct spi_hid *shid, int irq),
+
+	TP_ARGS(shid, irq),
+
+	TP_STRUCT__entry(
+		__field(int, bus_num)
+		__field(int, chip_select)
+		__field(int, irq)
+	),
+
+	TP_fast_assign(
+		__entry->bus_num = shid->spi->controller->bus_num;
+		__entry->chip_select = spi_get_chipselect(shid->spi, 0);
+		__entry->irq = irq;
+	),
+
+	TP_printk("spi%d.%d: IRQ %d",
+		  __entry->bus_num, __entry->chip_select, __entry->irq)
+);
+
+DEFINE_EVENT(spi_hid_irq, spi_hid_dev_irq,
+	     TP_PROTO(struct spi_hid *shid, int irq), TP_ARGS(shid, irq));
+
+DECLARE_EVENT_CLASS(spi_hid,
+	TP_PROTO(struct spi_hid *shid),
+
+	TP_ARGS(shid),
+
+	TP_STRUCT__entry(
+		__field(int, bus_num)
+		__field(int, chip_select)
+		__field(int, power_state)
+		__field(u32, flags)
+
+		__field(int, vendor_id)
+		__field(int, product_id)
+		__field(int, max_input_length)
+		__field(int, max_output_length)
+		__field(u16, hid_version)
+		__field(u16, report_descriptor_length)
+		__field(u16, version_id)
+	),
+
+	TP_fast_assign(
+		__entry->bus_num = shid->spi->controller->bus_num;
+		__entry->chip_select = spi_get_chipselect(shid->spi, 0);
+		__entry->power_state = shid->power_state;
+		__entry->flags = shid->flags;
+
+		__entry->vendor_id = shid->desc.vendor_id;
+		__entry->product_id = shid->desc.product_id;
+		__entry->max_input_length = shid->desc.max_input_length;
+		__entry->max_output_length = shid->desc.max_output_length;
+		__entry->hid_version = shid->desc.hid_version;
+		__entry->report_descriptor_length =
+					shid->desc.report_descriptor_length;
+		__entry->version_id = shid->desc.version_id;
+	),
+
+	TP_printk("spi%d.%d: (%04x:%04x v%d) HID v%d.%d state p:%d len i:%d o:%d r:%d flags 0x%08x",
+		  __entry->bus_num, __entry->chip_select,
+		  __entry->vendor_id, __entry->product_id, __entry->version_id,
+		  __entry->hid_version >> 8, __entry->hid_version & 0xff,
+		  __entry->power_state,	__entry->max_input_length,
+		  __entry->max_output_length, __entry->report_descriptor_length,
+		  __entry->flags)
+);
+
+DEFINE_EVENT(spi_hid, spi_hid_header_transfer, TP_PROTO(struct spi_hid *shid),
+	     TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_process_input_report,
+	     TP_PROTO(struct spi_hid *shid), TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_input_report_handler,
+	     TP_PROTO(struct spi_hid *shid), TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_reset_response, TP_PROTO(struct spi_hid *shid),
+	     TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_create_device, TP_PROTO(struct spi_hid *shid),
+	     TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_refresh_device, TP_PROTO(struct spi_hid *shid),
+	     TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_response_handler, TP_PROTO(struct spi_hid *shid),
+	     TP_ARGS(shid));
+
+DEFINE_EVENT(spi_hid, spi_hid_error_handler, TP_PROTO(struct spi_hid *shid),
+	     TP_ARGS(shid));
+
+#endif /* _SPI_HID_TRACE_H */
+
+/*
+ * The following must be outside the protection of the above #if block.
+ */
+#undef TRACE_INCLUDE_PATH
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_PATH .
+
+/*
+ * It is required that the TRACE_INCLUDE_FILE be the same
+ * as this file without the ".h".
+ */
+#define TRACE_INCLUDE_FILE spi-hid-trace
+#include <trace/define_trace.h>

-- 
2.54.0.1064.gd145956f57-goog


^ permalink raw reply related

* [PATCH v4 07/11] HID: spi_hid: add ACPI support for SPI over HID
From: Jingyuan Liang @ 2026-06-09  4:40 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-input, linux-doc, linux-kernel, linux-spi,
	linux-trace-kernel, devicetree, hbarnor, tfiga, Jingyuan Liang,
	Angela Czubak
In-Reply-To: <20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org>

From: Angela Czubak <acz@semihalf.com>

Detect SPI HID devices described in ACPI.

Signed-off-by: Angela Czubak <acz@semihalf.com>
Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
---
 drivers/hid/spi-hid/Kconfig        |  15 +++
 drivers/hid/spi-hid/Makefile       |   1 +
 drivers/hid/spi-hid/spi-hid-acpi.c | 253 +++++++++++++++++++++++++++++++++++++
 drivers/hid/spi-hid/spi-hid-core.c |  26 +---
 drivers/hid/spi-hid/spi-hid.h      |  45 +++++++
 5 files changed, 315 insertions(+), 25 deletions(-)

diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
index 836fdefe8345..114b1e00da39 100644
--- a/drivers/hid/spi-hid/Kconfig
+++ b/drivers/hid/spi-hid/Kconfig
@@ -10,6 +10,21 @@ menuconfig SPI_HID
 
 if SPI_HID
 
+config SPI_HID_ACPI
+	tristate "HID over SPI transport layer ACPI driver"
+	depends on ACPI
+	select SPI_HID_CORE
+	help
+	  Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
+	  other HID based devices which are connected to your computer via SPI.
+	  This driver supports ACPI-based systems.
+
+	  If unsure, say N.
+
+	  This support is also available as a module.  If so, the module
+	  will be called spi-hid-acpi. It will also build/depend on the
+	  module spi-hid.
+
 config SPI_HID_CORE
 	tristate
 endif
diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
index 733e006df56e..3ca326602643 100644
--- a/drivers/hid/spi-hid/Makefile
+++ b/drivers/hid/spi-hid/Makefile
@@ -8,3 +8,4 @@
 obj-$(CONFIG_SPI_HID_CORE)	+= spi-hid.o
 spi-hid-objs 			= spi-hid-core.o
 CFLAGS_spi-hid-core.o 		:= -I$(src)
+obj-$(CONFIG_SPI_HID_ACPI)	+= spi-hid-acpi.o
diff --git a/drivers/hid/spi-hid/spi-hid-acpi.c b/drivers/hid/spi-hid/spi-hid-acpi.c
new file mode 100644
index 000000000000..298e3ba44d8a
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-acpi.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID over SPI protocol, ACPI related code
+ *
+ * Copyright (c) 2021 Microsoft Corporation
+ * Copyright (c) 2026 Google LLC
+ *
+ * This code was forked out of the HID over SPI core code, which is partially
+ * based on "HID over I2C protocol implementation:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * which in turn is partially based on "USB HID support for Linux":
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2007-2008 Oliver Neukum
+ * Copyright (c) 2006-2010 Jiri Kosina
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/reset.h>
+#include <linux/uuid.h>
+
+#include "spi-hid.h"
+
+/* Config structure is filled with data from ACPI */
+struct spi_hid_acpi_config {
+	struct spihid_ops ops;
+
+	struct spi_hid_conf property_conf;
+	u32 post_power_on_delay_ms;
+	u32 minimal_reset_delay_ms;
+	struct acpi_device *adev;
+};
+
+/* HID SPI Device: 6e2ac436-0fcf41af-a265-b32a220dcfab */
+static guid_t spi_hid_guid =
+	GUID_INIT(0x6E2AC436, 0x0FCF, 0x41AF,
+		  0xA2, 0x65, 0xB3, 0x2A, 0x22, 0x0D, 0xCF, 0xAB);
+
+static int spi_hid_acpi_populate_config(struct spi_hid_acpi_config *conf,
+					struct acpi_device *adev)
+{
+	acpi_handle handle = acpi_device_handle(adev);
+	union acpi_object *obj;
+
+	conf->adev = adev;
+
+	/* Revision 3 for HID over SPI V1, see specification. */
+	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 1, NULL,
+				      ACPI_TYPE_INTEGER);
+	if (!obj) {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID input report header address failed");
+		return -ENODEV;
+	}
+	conf->property_conf.input_report_header_address = obj->integer.value;
+	ACPI_FREE(obj);
+
+	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 2, NULL,
+				      ACPI_TYPE_INTEGER);
+	if (!obj) {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID input report body address failed");
+		return -ENODEV;
+	}
+	conf->property_conf.input_report_body_address = obj->integer.value;
+	ACPI_FREE(obj);
+
+	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 3, NULL,
+				      ACPI_TYPE_INTEGER);
+	if (!obj) {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID output report header address failed");
+		return -ENODEV;
+	}
+	conf->property_conf.output_report_address = obj->integer.value;
+	ACPI_FREE(obj);
+
+	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 4, NULL,
+				      ACPI_TYPE_BUFFER);
+	if (!obj) {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID read opcode failed");
+		return -ENODEV;
+	}
+	if (obj->buffer.length == 1) {
+		conf->property_conf.read_opcode = obj->buffer.pointer[0];
+	} else {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID read opcode, too long buffer");
+		ACPI_FREE(obj);
+		return -ENODEV;
+	}
+	ACPI_FREE(obj);
+
+	obj = acpi_evaluate_dsm_typed(handle, &spi_hid_guid, 3, 5, NULL,
+				      ACPI_TYPE_BUFFER);
+	if (!obj) {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID write opcode failed");
+		return -ENODEV;
+	}
+	if (obj->buffer.length == 1) {
+		conf->property_conf.write_opcode = obj->buffer.pointer[0];
+	} else {
+		acpi_handle_err(handle,
+				"Error _DSM call to get HID write opcode, too long buffer");
+		ACPI_FREE(obj);
+		return -ENODEV;
+	}
+	ACPI_FREE(obj);
+
+	/* Value not provided in ACPI,*/
+	conf->post_power_on_delay_ms = 5;
+	conf->minimal_reset_delay_ms = 150;
+
+	if (!acpi_has_method(handle, "_RST")) {
+		acpi_handle_err(handle, "No reset method for acpi handle");
+		return -EINVAL;
+	}
+
+	/* FIXME: not reading hid-over-spi-flags, multi-SPI not supported */
+
+	return 0;
+}
+
+static int spi_hid_acpi_power_none(struct spihid_ops *ops)
+{
+	return 0;
+}
+
+static int spi_hid_acpi_power_down(struct spihid_ops *ops)
+{
+	struct spi_hid_acpi_config *conf = container_of(ops,
+							struct spi_hid_acpi_config,
+							ops);
+
+	return acpi_device_set_power(conf->adev, ACPI_STATE_D3);
+}
+
+static int spi_hid_acpi_power_up(struct spihid_ops *ops)
+{
+	struct spi_hid_acpi_config *conf = container_of(ops,
+							struct spi_hid_acpi_config,
+							ops);
+	int error;
+
+	error = acpi_device_set_power(conf->adev, ACPI_STATE_D0);
+	if (error) {
+		dev_err(&conf->adev->dev, "Error could not power up ACPI device: %d.", error);
+		return error;
+	}
+
+	if (conf->post_power_on_delay_ms)
+		msleep(conf->post_power_on_delay_ms);
+
+	return 0;
+}
+
+static int spi_hid_acpi_assert_reset(struct spihid_ops *ops)
+{
+	return 0;
+}
+
+static int spi_hid_acpi_deassert_reset(struct spihid_ops *ops)
+{
+	struct spi_hid_acpi_config *conf = container_of(ops,
+							struct spi_hid_acpi_config,
+							ops);
+
+	return device_reset(&conf->adev->dev);
+}
+
+static void spi_hid_acpi_sleep_minimal_reset_delay(struct spihid_ops *ops)
+{
+	struct spi_hid_acpi_config *conf = container_of(ops,
+							struct spi_hid_acpi_config,
+							ops);
+	fsleep(1000 * conf->minimal_reset_delay_ms);
+}
+
+static int spi_hid_acpi_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct acpi_device *adev;
+	struct spi_hid_acpi_config *config;
+	int error;
+
+	adev = ACPI_COMPANION(dev);
+	if (!adev) {
+		dev_err(dev, "Error could not get ACPI device.");
+		return -ENODEV;
+	}
+
+	config = devm_kzalloc(dev, sizeof(struct spi_hid_acpi_config),
+			      GFP_KERNEL);
+	if (!config)
+		return -ENOMEM;
+
+	if (acpi_device_power_manageable(adev)) {
+		config->ops.power_up = spi_hid_acpi_power_up;
+		config->ops.power_down = spi_hid_acpi_power_down;
+	} else {
+		config->ops.power_up = spi_hid_acpi_power_none;
+		config->ops.power_down = spi_hid_acpi_power_none;
+	}
+	config->ops.assert_reset = spi_hid_acpi_assert_reset;
+	config->ops.deassert_reset = spi_hid_acpi_deassert_reset;
+	config->ops.sleep_minimal_reset_delay =
+		spi_hid_acpi_sleep_minimal_reset_delay;
+
+	error = spi_hid_acpi_populate_config(config, adev);
+	if (error) {
+		dev_err(dev, "%s: unable to populate config data.", __func__);
+		return error;
+	}
+
+	return spi_hid_core_probe(spi, &config->ops, &config->property_conf);
+}
+
+static const struct acpi_device_id spi_hid_acpi_match[] = {
+	{ "ACPI0C51", 0 },
+	{ "PNP0C51", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, spi_hid_acpi_match);
+
+static struct spi_driver spi_hid_acpi_driver = {
+	.driver = {
+		.name	= "spi_hid_acpi",
+		.owner	= THIS_MODULE,
+		.acpi_match_table = spi_hid_acpi_match,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+		.dev_groups = spi_hid_groups,
+	},
+	.probe		= spi_hid_acpi_probe,
+	.remove		= spi_hid_core_remove,
+};
+
+module_spi_driver(spi_hid_acpi_driver);
+
+MODULE_DESCRIPTION("HID over SPI ACPI transport driver");
+MODULE_AUTHOR("Angela Czubak <aczubak@google.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index ef527999d6dc..698e72102c11 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -44,6 +44,7 @@
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 
+#include "spi-hid.h"
 #include "spi-hid-core.h"
 
 #define CREATE_TRACE_POINTS
@@ -111,31 +112,6 @@ struct spi_hid_output_report {
 	u8 *content;
 };
 
-/* struct spi_hid_conf - Conf provided to the core */
-struct spi_hid_conf {
-	u32 input_report_header_address;
-	u32 input_report_body_address;
-	u32 output_report_address;
-	u8 read_opcode;
-	u8 write_opcode;
-};
-
-/**
- * struct spihid_ops - Ops provided to the core
- * @power_up: do sequencing to power up the device
- * @power_down: do sequencing to power down the device
- * @assert_reset: do sequencing to assert the reset line
- * @deassert_reset: do sequencing to deassert the reset line
- * @sleep_minimal_reset_delay: minimal sleep delay during reset
- */
-struct spihid_ops {
-	int (*power_up)(struct spihid_ops *ops);
-	int (*power_down)(struct spihid_ops *ops);
-	int (*assert_reset)(struct spihid_ops *ops);
-	int (*deassert_reset)(struct spihid_ops *ops);
-	void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
-};
-
 static struct hid_ll_driver spi_hid_ll_driver;
 
 static void spi_hid_populate_read_approvals(const struct spi_hid_conf *conf,
diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h
new file mode 100644
index 000000000000..f5a5f4d54beb
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Microsoft Corporation
+ * Copyright (c) 2026 Google LLC
+ */
+
+#ifndef SPI_HID_H
+#define SPI_HID_H
+
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+
+/* struct spi_hid_conf - Conf provided to the core */
+struct spi_hid_conf {
+	u32 input_report_header_address;
+	u32 input_report_body_address;
+	u32 output_report_address;
+	u8 read_opcode;
+	u8 write_opcode;
+};
+
+/**
+ * struct spihid_ops - Ops provided to the core
+ * @power_up: do sequencing to power up the device
+ * @power_down: do sequencing to power down the device
+ * @assert_reset: do sequencing to assert the reset line
+ * @deassert_reset: do sequencing to deassert the reset line
+ * @sleep_minimal_reset_delay: minimal sleep delay during reset
+ */
+struct spihid_ops {
+	int (*power_up)(struct spihid_ops *ops);
+	int (*power_down)(struct spihid_ops *ops);
+	int (*assert_reset)(struct spihid_ops *ops);
+	int (*deassert_reset)(struct spihid_ops *ops);
+	void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
+};
+
+int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
+		       struct spi_hid_conf *conf);
+
+void spi_hid_core_remove(struct spi_device *spi);
+
+extern const struct attribute_group *spi_hid_groups[];
+
+#endif /* SPI_HID_H */

-- 
2.54.0.1064.gd145956f57-goog


^ permalink raw reply related

* [PATCH v4 08/11] HID: spi_hid: add device tree support for SPI over HID
From: Jingyuan Liang @ 2026-06-09  4:40 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-input, linux-doc, linux-kernel, linux-spi,
	linux-trace-kernel, devicetree, hbarnor, tfiga, Jingyuan Liang,
	Jarrett Schultz, Dmitry Antipov
In-Reply-To: <20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org>

From: Jarrett Schultz <jaschultz@microsoft.com>

Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
---
 drivers/hid/spi-hid/Kconfig      |  15 +++
 drivers/hid/spi-hid/Makefile     |   1 +
 drivers/hid/spi-hid/spi-hid-of.c | 246 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 262 insertions(+)

diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
index 114b1e00da39..76a2cd587a3e 100644
--- a/drivers/hid/spi-hid/Kconfig
+++ b/drivers/hid/spi-hid/Kconfig
@@ -25,6 +25,21 @@ config SPI_HID_ACPI
 	  will be called spi-hid-acpi. It will also build/depend on the
 	  module spi-hid.
 
+config SPI_HID_OF
+	tristate "HID over SPI transport layer Open Firmware driver"
+	depends on OF
+	select SPI_HID_CORE
+	help
+	  Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
+	  other HID based devices which are connected to your computer via SPI.
+	  This driver supports Open Firmware (Device Tree)-based systems.
+
+	  If unsure, say N.
+
+	  This support is also available as a module.  If so, the module
+	  will be called spi-hid-of. It will also build/depend on the
+	  module spi-hid.
+
 config SPI_HID_CORE
 	tristate
 endif
diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
index 3ca326602643..31192e71edae 100644
--- a/drivers/hid/spi-hid/Makefile
+++ b/drivers/hid/spi-hid/Makefile
@@ -9,3 +9,4 @@ obj-$(CONFIG_SPI_HID_CORE)	+= spi-hid.o
 spi-hid-objs 			= spi-hid-core.o
 CFLAGS_spi-hid-core.o 		:= -I$(src)
 obj-$(CONFIG_SPI_HID_ACPI)	+= spi-hid-acpi.o
+obj-$(CONFIG_SPI_HID_OF)	+= spi-hid-of.o
diff --git a/drivers/hid/spi-hid/spi-hid-of.c b/drivers/hid/spi-hid/spi-hid-of.c
new file mode 100644
index 000000000000..ba7d5338f5d8
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-of.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HID over SPI protocol, Open Firmware related code
+ *
+ * Copyright (c) 2021 Microsoft Corporation
+ *
+ * This code was forked out of the HID over SPI core code, which is partially
+ * based on "HID over I2C protocol implementation:
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * which in turn is partially based on "USB HID support for Linux":
+ *
+ * Copyright (c) 1999 Andreas Gal
+ * Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ * Copyright (c) 2007-2008 Oliver Neukum
+ * Copyright (c) 2006-2010 Jiri Kosina
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "spi-hid.h"
+
+struct spi_hid_timing_data {
+	u32 post_power_on_delay_ms;
+	u32 minimal_reset_delay_ms;
+};
+
+/* Config structure is filled with data from Device Tree */
+struct spi_hid_of_config {
+	struct spihid_ops ops;
+
+	struct spi_hid_conf property_conf;
+	const struct spi_hid_timing_data *timing_data;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator *supply;
+	bool supply_enabled;
+	u16 hid_over_spi_flags;
+};
+
+static const struct spi_hid_timing_data timing_data = {
+	.post_power_on_delay_ms = 10,
+	.minimal_reset_delay_ms = 100,
+};
+
+static int spi_hid_of_populate_config(struct spi_hid_of_config *conf,
+				      struct device *dev)
+{
+	int error;
+	u32 val;
+
+	error = device_property_read_u32(dev, "input-report-header-address",
+					 &val);
+	if (error) {
+		dev_err(dev, "Input report header address not provided.");
+		return -ENODEV;
+	}
+	conf->property_conf.input_report_header_address = val;
+
+	error = device_property_read_u32(dev, "input-report-body-address", &val);
+	if (error) {
+		dev_err(dev, "Input report body address not provided.");
+		return -ENODEV;
+	}
+	conf->property_conf.input_report_body_address = val;
+
+	error = device_property_read_u32(dev, "output-report-address", &val);
+	if (error) {
+		dev_err(dev, "Output report address not provided.");
+		return -ENODEV;
+	}
+	conf->property_conf.output_report_address = val;
+
+	error = device_property_read_u32(dev, "read-opcode", &val);
+	if (error) {
+		dev_err(dev, "Read opcode not provided.");
+		return -ENODEV;
+	}
+	conf->property_conf.read_opcode = val;
+
+	error = device_property_read_u32(dev, "write-opcode", &val);
+	if (error) {
+		dev_err(dev, "Write opcode not provided.");
+		return -ENODEV;
+	}
+	conf->property_conf.write_opcode = val;
+
+	conf->supply = devm_regulator_get(dev, "vdd");
+	if (IS_ERR(conf->supply)) {
+		if (PTR_ERR(conf->supply) != -EPROBE_DEFER)
+			dev_err(dev, "Failed to get regulator: %ld.",
+				PTR_ERR(conf->supply));
+		return PTR_ERR(conf->supply);
+	}
+	conf->supply_enabled = false;
+
+	conf->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(conf->reset_gpio)) {
+		dev_err(dev, "%s: error getting reset GPIO.", __func__);
+		return PTR_ERR(conf->reset_gpio);
+	}
+
+	return 0;
+}
+
+static int spi_hid_of_power_down(struct spihid_ops *ops)
+{
+	struct spi_hid_of_config *conf = container_of(ops,
+						      struct spi_hid_of_config,
+						      ops);
+	int error;
+
+	if (!conf->supply_enabled)
+		return 0;
+
+	error = regulator_disable(conf->supply);
+	if (error == 0)
+		conf->supply_enabled = false;
+
+	return error;
+}
+
+static int spi_hid_of_power_up(struct spihid_ops *ops)
+{
+	struct spi_hid_of_config *conf = container_of(ops,
+						      struct spi_hid_of_config,
+						      ops);
+	int error;
+
+	if (conf->supply_enabled)
+		return 0;
+
+	error = regulator_enable(conf->supply);
+
+	if (error == 0) {
+		conf->supply_enabled = true;
+		fsleep(1000 * conf->timing_data->post_power_on_delay_ms);
+	}
+
+	return error;
+}
+
+static int spi_hid_of_assert_reset(struct spihid_ops *ops)
+{
+	struct spi_hid_of_config *conf = container_of(ops,
+						      struct spi_hid_of_config,
+						      ops);
+
+	gpiod_set_value(conf->reset_gpio, 1);
+	return 0;
+}
+
+static int spi_hid_of_deassert_reset(struct spihid_ops *ops)
+{
+	struct spi_hid_of_config *conf = container_of(ops,
+						      struct spi_hid_of_config,
+						      ops);
+
+	gpiod_set_value(conf->reset_gpio, 0);
+	return 0;
+}
+
+static void spi_hid_of_sleep_minimal_reset_delay(struct spihid_ops *ops)
+{
+	struct spi_hid_of_config *conf = container_of(ops,
+						      struct spi_hid_of_config,
+						      ops);
+	fsleep(1000 * conf->timing_data->minimal_reset_delay_ms);
+}
+
+static int spi_hid_of_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct spi_hid_of_config *config;
+	int error;
+
+	config = devm_kzalloc(dev, sizeof(struct spi_hid_of_config),
+			      GFP_KERNEL);
+	if (!config)
+		return -ENOMEM;
+
+	config->ops.power_up = spi_hid_of_power_up;
+	config->ops.power_down = spi_hid_of_power_down;
+	config->ops.assert_reset = spi_hid_of_assert_reset;
+	config->ops.deassert_reset = spi_hid_of_deassert_reset;
+	config->ops.sleep_minimal_reset_delay =
+		spi_hid_of_sleep_minimal_reset_delay;
+
+	config->timing_data = device_get_match_data(dev);
+	if (!config->timing_data)
+		config->timing_data = &timing_data;
+
+	/*
+	 * FIXME: hid_over_spi_flags could be retrieved from spi mode.
+	 * It is always 0 because multi-SPI not supported.
+	 */
+	config->hid_over_spi_flags = 0;
+
+	error = spi_hid_of_populate_config(config, dev);
+	if (error) {
+		dev_err(dev, "%s: unable to populate config data.", __func__);
+		return error;
+	}
+
+	return spi_hid_core_probe(spi, &config->ops, &config->property_conf);
+}
+
+static const struct of_device_id spi_hid_of_match[] = {
+	{ .compatible = "hid-over-spi", .data = &timing_data },
+	{}
+};
+MODULE_DEVICE_TABLE(of, spi_hid_of_match);
+
+static const struct spi_device_id spi_hid_of_id_table[] = {
+	{ "hid", 0 },
+	{ "hid-over-spi", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, spi_hid_of_id_table);
+
+static struct spi_driver spi_hid_of_driver = {
+	.driver = {
+		.name	= "spi_hid_of",
+		.owner	= THIS_MODULE,
+		.of_match_table = spi_hid_of_match,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+		.dev_groups = spi_hid_groups,
+	},
+	.probe		= spi_hid_of_probe,
+	.remove		= spi_hid_core_remove,
+	.id_table	= spi_hid_of_id_table,
+};
+
+module_spi_driver(spi_hid_of_driver);
+
+MODULE_DESCRIPTION("HID over SPI OF transport driver");
+MODULE_AUTHOR("Dmitry Antipov <dmanti@microsoft.com>");
+MODULE_LICENSE("GPL");

-- 
2.54.0.1064.gd145956f57-goog


^ permalink raw reply related

* [PATCH v4 09/11] dt-bindings: input: Document hid-over-spi DT schema
From: Jingyuan Liang @ 2026-06-09  4:41 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-input, linux-doc, linux-kernel, linux-spi,
	linux-trace-kernel, devicetree, hbarnor, tfiga, Jingyuan Liang,
	Dmitry Antipov, Jarrett Schultz
In-Reply-To: <20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org>

Documentation describes the required and optional properties for
implementing Device Tree for a Microsoft G6 Touch Digitizer that
supports HID over SPI Protocol 1.0 specification.

The properties are common to HID over SPI.

Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
Signed-off-by: Jarrett Schultz <jaschultz@microsoft.com>
Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
---
 .../devicetree/bindings/input/hid-over-spi.yaml    | 128 +++++++++++++++++++++
 1 file changed, 128 insertions(+)

diff --git a/Documentation/devicetree/bindings/input/hid-over-spi.yaml b/Documentation/devicetree/bindings/input/hid-over-spi.yaml
new file mode 100644
index 000000000000..27cf311e0aab
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/hid-over-spi.yaml
@@ -0,0 +1,128 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/hid-over-spi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: HID over SPI Devices
+
+maintainers:
+  - Benjamin Tissoires <benjamin.tissoires@redhat.com>
+  - Jiri Kosina <jkosina@suse.cz>
+  - Jingyuan Liang <jingyliang@chromium.org>
+
+description: |+
+  HID over SPI provides support for various Human Interface Devices over the
+  SPI bus. These devices can be for example touchpads, keyboards, touch screens
+  or sensors.
+
+  The specification has been written by Microsoft and is currently available
+  here: https://www.microsoft.com/en-us/download/details.aspx?id=103325
+
+  The Microsoft HID over SPI specification explicitly dictates that SPI
+  opcodes and register addresses (such as input/output report addresses)
+  are not standardized. Instead, the specification requires the system
+  firmware (e.g., ACPI or Device Tree) to provide these board-specific
+  parameters to the OS. Therefore, these varying parameters must be
+  defined as properties in the Device Tree.
+
+allOf:
+  - $ref: /schemas/input/touchscreen/touchscreen.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - microsoft,g6-touch-digitizer
+      - const: hid-over-spi
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+    description:
+      GPIO specifier for the digitizer's reset pin (active low). The line must
+      be flagged with GPIO_ACTIVE_LOW.
+
+  vdd-supply:
+    description:
+      Regulator for the VDD supply voltage.
+
+  input-report-header-address:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 0
+    maximum: 0xffffff
+    description:
+      A value to be included in the Read Approval packet, listing an address of
+      the input report header to be put on the SPI bus. This address has 24
+      bits.
+
+  input-report-body-address:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 0
+    maximum: 0xffffff
+    description:
+      A value to be included in the Read Approval packet, listing an address of
+      the input report body to be put on the SPI bus. This address has 24 bits.
+
+  output-report-address:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 0
+    maximum: 0xffffff
+    description:
+      A value to be included in the Output Report sent by the host, listing an
+      address where the output report on the SPI bus is to be written to. This
+      address has 24 bits.
+
+  read-opcode:
+    $ref: /schemas/types.yaml#/definitions/uint8
+    description:
+      Value to be used in Read Approval packets. 1 byte.
+
+  write-opcode:
+    $ref: /schemas/types.yaml#/definitions/uint8
+    description:
+      Value to be used in Write Approval packets. 1 byte.
+
+required:
+  - compatible
+  - interrupts
+  - reset-gpios
+  - vdd-supply
+  - input-report-header-address
+  - input-report-body-address
+  - output-report-address
+  - read-opcode
+  - write-opcode
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/gpio/gpio.h>
+
+    spi {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      hid@0 {
+        compatible = "microsoft,g6-touch-digitizer", "hid-over-spi";
+        reg = <0x0>;
+        interrupts-extended = <&gpio 42 IRQ_TYPE_EDGE_FALLING>;
+        reset-gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
+        vdd-supply = <&pm8350c_l3>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&ts_d6_int_bias>;
+        input-report-header-address = <0x1000>;
+        input-report-body-address = <0x1004>;
+        output-report-address = <0x2000>;
+        read-opcode = /bits/ 8 <0x0b>;
+        write-opcode = /bits/ 8 <0x02>;
+      };
+    };

-- 
2.54.0.1064.gd145956f57-goog


^ permalink raw reply related

* [PATCH v4 10/11] HID: spi-hid: add power management implementation
From: Jingyuan Liang @ 2026-06-09  4:41 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-input, linux-doc, linux-kernel, linux-spi,
	linux-trace-kernel, devicetree, hbarnor, tfiga, Jingyuan Liang
In-Reply-To: <20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org>

Implement HID over SPI driver power management callbacks.

Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
---
 drivers/hid/spi-hid/spi-hid-acpi.c |   1 +
 drivers/hid/spi-hid/spi-hid-core.c | 133 +++++++++++++++++++++++++++++++++++--
 drivers/hid/spi-hid/spi-hid-of.c   |   1 +
 drivers/hid/spi-hid/spi-hid.h      |   1 +
 4 files changed, 131 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-acpi.c b/drivers/hid/spi-hid/spi-hid-acpi.c
index 298e3ba44d8a..15cfc4e6cc2f 100644
--- a/drivers/hid/spi-hid/spi-hid-acpi.c
+++ b/drivers/hid/spi-hid/spi-hid-acpi.c
@@ -238,6 +238,7 @@ static struct spi_driver spi_hid_acpi_driver = {
 	.driver = {
 		.name	= "spi_hid_acpi",
 		.owner	= THIS_MODULE,
+		.pm	= &spi_hid_core_pm,
 		.acpi_match_table = spi_hid_acpi_match,
 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
 		.dev_groups = spi_hid_groups,
diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index 698e72102c11..517f06913477 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -36,6 +36,8 @@
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/pm_wakeirq.h>
 #include <linux/slab.h>
 #include <linux/spi/spi.h>
 #include <linux/string.h>
@@ -245,6 +247,96 @@ static const char *spi_hid_power_mode_string(enum hidspi_power_state power_state
 	}
 }
 
+static int spi_hid_suspend(struct spi_hid *shid)
+{
+	int error;
+	struct device *dev = &shid->spi->dev;
+
+	guard(mutex)(&shid->power_lock);
+	if (shid->power_state == HIDSPI_OFF)
+		return 0;
+
+	if (shid->hid) {
+		error = hid_driver_suspend(shid->hid, PMSG_SUSPEND);
+		if (error) {
+			dev_err(dev, "%s failed to suspend hid driver: %d\n",
+				__func__, error);
+			return error;
+		}
+	}
+
+	disable_irq(shid->spi->irq);
+
+	if (!device_may_wakeup(dev)) {
+		set_bit(SPI_HID_RESET_PENDING, &shid->flags);
+
+		shid->ops->assert_reset(shid->ops);
+
+		error = shid->ops->power_down(shid->ops);
+		if (error) {
+			dev_err(dev, "%s: could not power down\n", __func__);
+			shid->regulator_error_count++;
+			shid->regulator_last_error = error;
+			/* Undo partial suspend before returning error */
+			shid->ops->deassert_reset(shid->ops);
+			clear_bit(SPI_HID_RESET_PENDING, &shid->flags);
+			enable_irq(shid->spi->irq);
+			if (shid->hid)
+				hid_driver_reset_resume(shid->hid);
+			return error;
+		}
+
+		shid->power_state = HIDSPI_OFF;
+	}
+	return 0;
+}
+
+static int spi_hid_resume(struct spi_hid *shid)
+{
+	int error;
+	struct device *dev = &shid->spi->dev;
+
+	guard(mutex)(&shid->power_lock);
+
+	if (!device_may_wakeup(dev)) {
+		if (shid->power_state == HIDSPI_OFF) {
+			shid->ops->assert_reset(shid->ops);
+
+			shid->ops->sleep_minimal_reset_delay(shid->ops);
+
+			error = shid->ops->power_up(shid->ops);
+			if (error) {
+				dev_err(dev, "%s: could not power up\n", __func__);
+				shid->regulator_error_count++;
+				shid->regulator_last_error = error;
+				return error;
+			}
+			shid->power_state = HIDSPI_ON;
+			shid->ops->deassert_reset(shid->ops);
+		}
+	}
+
+	enable_irq(shid->spi->irq);
+
+	if (shid->hid) {
+		error = hid_driver_reset_resume(shid->hid);
+		if (error) {
+			dev_err(dev, "%s: failed to reset resume hid driver: %d\n",
+				__func__, error);
+			/* Undo partial resume before returning error */
+			disable_irq(shid->spi->irq);
+			if (!device_may_wakeup(dev)) {
+				set_bit(SPI_HID_RESET_PENDING, &shid->flags);
+				shid->ops->assert_reset(shid->ops);
+				shid->ops->power_down(shid->ops);
+				shid->power_state = HIDSPI_OFF;
+			}
+			return error;
+		}
+	}
+	return 0;
+}
+
 static void spi_hid_stop_hid(struct spi_hid *shid)
 {
 	struct hid_device *hid;
@@ -795,6 +887,11 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
 	trace_spi_hid_header_transfer(shid);
 
 	scoped_guard(mutex, &shid->io_lock) {
+		if (shid->power_state == HIDSPI_OFF) {
+			dev_warn(dev, "Device is off, ignoring interrupt\n");
+			goto out;
+		}
+
 		error = spi_hid_input_sync(shid, shid->input->header,
 					   sizeof(shid->input->header), true);
 		if (error) {
@@ -802,11 +899,6 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
 			goto err;
 		}
 
-		if (shid->power_state == HIDSPI_OFF) {
-			dev_warn(dev, "Device is off after header was received\n");
-			goto out;
-		}
-
 		trace_spi_hid_input_header_complete(shid,
 						    shid->input_transfer[0].tx_buf,
 						    shid->input_transfer[0].len,
@@ -1251,10 +1343,19 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 		dev_err(dev, "%s: unable to request threaded IRQ\n", __func__);
 		return error;
 	}
+	if (device_may_wakeup(dev)) {
+		error = dev_pm_set_wake_irq(dev, spi->irq);
+		if (error) {
+			dev_err(dev, "%s: failed to set wake IRQ\n", __func__);
+			return error;
+		}
+	}
 
 	error = shid->ops->power_up(shid->ops);
 	if (error) {
 		dev_err(dev, "%s: could not power up\n", __func__);
+		if (device_may_wakeup(dev))
+			dev_pm_clear_wake_irq(dev);
 		return error;
 	}
 
@@ -1284,9 +1385,31 @@ void spi_hid_core_remove(struct spi_device *spi)
 	error = shid->ops->power_down(shid->ops);
 	if (error)
 		dev_err(dev, "failed to disable regulator\n");
+
+	if (device_may_wakeup(dev))
+		dev_pm_clear_wake_irq(dev);
 }
 EXPORT_SYMBOL_GPL(spi_hid_core_remove);
 
+static int spi_hid_core_pm_suspend(struct device *dev)
+{
+	struct spi_hid *shid = dev_get_drvdata(dev);
+
+	return spi_hid_suspend(shid);
+}
+
+static int spi_hid_core_pm_resume(struct device *dev)
+{
+	struct spi_hid *shid = dev_get_drvdata(dev);
+
+	return spi_hid_resume(shid);
+}
+
+const struct dev_pm_ops spi_hid_core_pm = {
+	SYSTEM_SLEEP_PM_OPS(spi_hid_core_pm_suspend, spi_hid_core_pm_resume)
+};
+EXPORT_SYMBOL_GPL(spi_hid_core_pm);
+
 MODULE_DESCRIPTION("HID over SPI transport driver");
 MODULE_AUTHOR("Dmitry Antipov <dmanti@microsoft.com>");
 MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-of.c b/drivers/hid/spi-hid/spi-hid-of.c
index ba7d5338f5d8..561cf453e44a 100644
--- a/drivers/hid/spi-hid/spi-hid-of.c
+++ b/drivers/hid/spi-hid/spi-hid-of.c
@@ -230,6 +230,7 @@ static struct spi_driver spi_hid_of_driver = {
 	.driver = {
 		.name	= "spi_hid_of",
 		.owner	= THIS_MODULE,
+		.pm	= &spi_hid_core_pm,
 		.of_match_table = spi_hid_of_match,
 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
 		.dev_groups = spi_hid_groups,
diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h
index f5a5f4d54beb..17b2fdf192ed 100644
--- a/drivers/hid/spi-hid/spi-hid.h
+++ b/drivers/hid/spi-hid/spi-hid.h
@@ -41,5 +41,6 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 void spi_hid_core_remove(struct spi_device *spi);
 
 extern const struct attribute_group *spi_hid_groups[];
+extern const struct dev_pm_ops spi_hid_core_pm;
 
 #endif /* SPI_HID_H */

-- 
2.54.0.1064.gd145956f57-goog


^ permalink raw reply related

* [PATCH v4 11/11] HID: spi-hid: add panel follower support
From: Jingyuan Liang @ 2026-06-09  4:41 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, Jonathan Corbet, Mark Brown,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-input, linux-doc, linux-kernel, linux-spi,
	linux-trace-kernel, devicetree, hbarnor, tfiga, Jingyuan Liang
In-Reply-To: <20260609-send-upstream-v4-0-b843d5e6ced3@chromium.org>

Add support to spi-hid to be a panel follower.

Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
---
 drivers/hid/spi-hid/spi-hid-core.c | 164 ++++++++++++++++++++++++++++++-------
 drivers/hid/spi-hid/spi-hid-core.h |   7 ++
 2 files changed, 142 insertions(+), 29 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index 517f06913477..27f25d95ed28 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -1281,6 +1281,106 @@ const struct attribute_group *spi_hid_groups[] = {
 };
 EXPORT_SYMBOL_GPL(spi_hid_groups);
 
+/*
+ * At the end of probe we initialize the device:
+ *   0) assert reset, bias the interrupt line
+ *   1) sleep minimal reset delay
+ *   2) power up the device
+ *   3) deassert reset (high)
+ * After this we expect an IRQ with a reset response.
+ */
+static int spi_hid_dev_init(struct spi_hid *shid)
+{
+	struct spi_device *spi = shid->spi;
+	struct device *dev = &spi->dev;
+	int error;
+
+	shid->ops->assert_reset(shid->ops);
+
+	shid->ops->sleep_minimal_reset_delay(shid->ops);
+
+	error = shid->ops->power_up(shid->ops);
+	if (error) {
+		dev_err(dev, "%s: could not power up\n", __func__);
+		shid->regulator_error_count++;
+		shid->regulator_last_error = error;
+		return error;
+	}
+
+	shid->ops->deassert_reset(shid->ops);
+
+	enable_irq(spi->irq);
+
+	return 0;
+}
+
+static void spi_hid_panel_follower_work(struct work_struct *work)
+{
+	struct spi_hid *shid = container_of(work, struct spi_hid,
+					    panel_follower_work);
+	int error;
+
+	if (!shid->desc.hid_version)
+		error = spi_hid_dev_init(shid);
+	else
+		error = spi_hid_resume(shid);
+	if (error)
+		dev_warn(&shid->spi->dev, "Power on failed: %d\n", error);
+	else
+		WRITE_ONCE(shid->panel_follower_work_finished, true);
+}
+
+static int spi_hid_panel_follower_resume(struct drm_panel_follower *follower)
+{
+	struct spi_hid *shid = container_of(follower, struct spi_hid, panel_follower);
+
+	/*
+	 * Powering on a touchscreen can be a slow process. Queue the work to
+	 * the system workqueue so we don't block the panel's power up.
+	 */
+	WRITE_ONCE(shid->panel_follower_work_finished, false);
+	schedule_work(&shid->panel_follower_work);
+
+	return 0;
+}
+
+static int spi_hid_panel_follower_suspend(struct drm_panel_follower *follower)
+{
+	struct spi_hid *shid = container_of(follower, struct spi_hid, panel_follower);
+
+	cancel_work_sync(&shid->panel_follower_work);
+
+	if (!READ_ONCE(shid->panel_follower_work_finished))
+		return 0;
+
+	return spi_hid_suspend(shid);
+}
+
+static const struct drm_panel_follower_funcs
+				spi_hid_panel_follower_prepare_funcs = {
+	.panel_prepared = spi_hid_panel_follower_resume,
+	.panel_unpreparing = spi_hid_panel_follower_suspend,
+};
+
+static int spi_hid_register_panel_follower(struct spi_hid *shid)
+{
+	struct device *dev = &shid->spi->dev;
+
+	shid->panel_follower.funcs = &spi_hid_panel_follower_prepare_funcs;
+
+	/*
+	 * If we're not in control of our own power up/power down then we can't
+	 * do the logic to manage wakeups. Give a warning if a user thought
+	 * that was possible then force the capability off.
+	 */
+	if (device_can_wakeup(dev)) {
+		dev_warn(dev, "Can't wakeup if following panel\n");
+		device_set_wakeup_capable(dev, false);
+	}
+
+	return drm_panel_add_follower(dev, &shid->panel_follower);
+}
+
 int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 		       struct spi_hid_conf *conf)
 {
@@ -1300,6 +1400,7 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 	shid->ops = ops;
 	shid->conf = conf;
 	set_bit(SPI_HID_RESET_PENDING, &shid->flags);
+	shid->is_panel_follower = drm_is_panel_follower(&spi->dev);
 
 	spi_set_drvdata(spi, shid);
 
@@ -1313,6 +1414,7 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 	init_completion(&shid->output_done);
 
 	INIT_WORK(&shid->reset_work, spi_hid_reset_work);
+	INIT_WORK(&shid->panel_follower_work, spi_hid_panel_follower_work);
 
 	/*
 	 * We need to allocate the buffer without knowing the maximum
@@ -1323,20 +1425,6 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 	if (error)
 		return error;
 
-	/*
-	 * At the end of probe we initialize the device:
-	 *   0) assert reset, bias the interrupt line
-	 *   1) sleep minimal reset delay
-	 *   2) request IRQ
-	 *   3) power up the device
-	 *   4) deassert reset (high)
-	 * After this we expect an IRQ with a reset response.
-	 */
-
-	shid->ops->assert_reset(shid->ops);
-
-	shid->ops->sleep_minimal_reset_delay(shid->ops);
-
 	error = devm_request_threaded_irq(dev, spi->irq, NULL, spi_hid_dev_irq,
 					  IRQF_ONESHOT | IRQF_NO_AUTOEN, dev_name(&spi->dev), shid);
 	if (error) {
@@ -1351,22 +1439,28 @@ int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
 		}
 	}
 
-	error = shid->ops->power_up(shid->ops);
-	if (error) {
-		dev_err(dev, "%s: could not power up\n", __func__);
-		if (device_may_wakeup(dev))
-			dev_pm_clear_wake_irq(dev);
-		return error;
+	if (shid->is_panel_follower) {
+		error = spi_hid_register_panel_follower(shid);
+		if (error) {
+			dev_err_probe(dev, error,
+				      "Failed to register panel follower");
+			goto err_wake_irq;
+		}
+	} else {
+		error = spi_hid_dev_init(shid);
+		if (error)
+			goto err_wake_irq;
 	}
 
-	shid->ops->deassert_reset(shid->ops);
-
-	enable_irq(spi->irq);
-
 	dev_dbg(dev, "%s: d3 -> %s\n", __func__,
 		spi_hid_power_mode_string(shid->power_state));
 
 	return 0;
+
+err_wake_irq:
+	if (device_may_wakeup(dev))
+		dev_pm_clear_wake_irq(dev);
+	return error;
 }
 EXPORT_SYMBOL_GPL(spi_hid_core_probe);
 
@@ -1376,15 +1470,21 @@ void spi_hid_core_remove(struct spi_device *spi)
 	struct device *dev = &spi->dev;
 	int error;
 
-	disable_irq(spi->irq);
+	if (shid->is_panel_follower)
+		drm_panel_remove_follower(&shid->panel_follower);
+	else
+		disable_irq(spi->irq);
+
 	cancel_work_sync(&shid->reset_work);
 
 	spi_hid_stop_hid(shid);
 
-	shid->ops->assert_reset(shid->ops);
-	error = shid->ops->power_down(shid->ops);
-	if (error)
-		dev_err(dev, "failed to disable regulator\n");
+	if (shid->power_state != HIDSPI_OFF) {
+		shid->ops->assert_reset(shid->ops);
+		error = shid->ops->power_down(shid->ops);
+		if (error)
+			dev_err(dev, "failed to disable regulator\n");
+	}
 
 	if (device_may_wakeup(dev))
 		dev_pm_clear_wake_irq(dev);
@@ -1395,6 +1495,9 @@ static int spi_hid_core_pm_suspend(struct device *dev)
 {
 	struct spi_hid *shid = dev_get_drvdata(dev);
 
+	if (shid->is_panel_follower)
+		return 0;
+
 	return spi_hid_suspend(shid);
 }
 
@@ -1402,6 +1505,9 @@ static int spi_hid_core_pm_resume(struct device *dev)
 {
 	struct spi_hid *shid = dev_get_drvdata(dev);
 
+	if (shid->is_panel_follower)
+		return 0;
+
 	return spi_hid_resume(shid);
 }
 
diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-hid-core.h
index 293e2cfcfbf7..261b2fd7f332 100644
--- a/drivers/hid/spi-hid/spi-hid-core.h
+++ b/drivers/hid/spi-hid/spi-hid-core.h
@@ -10,6 +10,8 @@
 #include <linux/hid-over-spi.h>
 #include <linux/spi/spi.h>
 
+#include <drm/drm_panel.h>
+
 /* Protocol message size constants */
 #define SPI_HID_READ_APPROVAL_LEN		5
 #define SPI_HID_OUTPUT_HEADER_LEN		8
@@ -56,6 +58,10 @@ struct spi_hid {
 	struct spi_hid_input_buf *input;	/* Input buffer. */
 	struct spi_hid_input_buf *response;	/* Response buffer. */
 
+	struct drm_panel_follower panel_follower;
+	bool	is_panel_follower;
+	bool	panel_follower_work_finished;
+
 	u16 response_length;
 	u16 bufsize;
 
@@ -66,6 +72,7 @@ struct spi_hid {
 	unsigned long flags;	/* device flags. */
 
 	struct work_struct reset_work;
+	struct work_struct panel_follower_work;
 
 	/* Control lock to ensure complete output transaction. */
 	struct mutex output_lock;

-- 
2.54.0.1064.gd145956f57-goog


^ permalink raw reply related

* [PATCH iproute2-next 0/7] devlink: add per-port resource support
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata

Hi,

Currently, devlink resource show only supports querying a specific
device and displays device-level resources. However, some resources
are per-port, such as the maximum number of SFs that can be created
on a specific PF port.

This series extends devlink resource show with full support for
port-level resources, including a dump mode, per-port querying syntax,
and scope filtering. In preparation for these features, the first two
patches refactor how dpipe tables are handled to unblock dump support
and ensure errors in secondary queries are non-fatal.

The series is organized as follows:

Patch 1 splits the dpipe tables display into a separate function.

Patch 2 moves the dpipe tables query into the per-device resource show
callback, ensuring it behaves correctly during a multi-device dump.

Patch 3 fixes a pre-existing memory leak in resource_ctx_fini.

Patch 4 adds dump support to resource show (no device required).

Patch 5 shows port-level resources returned in a dump reply.

Patch 6 adds DEV/PORT_INDEX syntax to resource show.

Patch 7 adds scope filter to resource show.

With this series, users can query resources at all levels:

$ devlink resource show
pci/0000:03:00.0:
  name local_max_SFs size 508 unit entry
  name external_max_SFs size 508 unit entry
pci/0000:03:00.0/196608:
  name max_SFs size 20 unit entry

$ devlink resource show scope dev
pci/0000:03:00.0:
  name local_max_SFs size 508 unit entry
  name external_max_SFs size 508 unit entry

$ devlink resource show scope port
pci/0000:03:00.0/196608:
  name max_SFs size 20 unit entry

$ devlink resource show pci/0000:03:00.0/196608
pci/0000:03:00.0/196608:
  name max_SFs size 20 unit entry

This series is the userspace counterpart to the kernel series:
https://lore.kernel.org/all/20260407194107.148063-1-tariqt@nvidia.com/

Ido Schimmel (2):
  devlink: Split dpipe tables output to a separate function
  devlink: Move dpipe tables query to resources show callback

Or Har-Toov (5):
  devlink: fix memory leak in resource_ctx_fini
  devlink: add dump support for resource show
  devlink: show port resources in resource dump
  devlink: add per-port resource show support
  devlink: add scope filter to resource show

 bash-completion/devlink     |   8 ++
 devlink/devlink.c           | 202 +++++++++++++++++++++++++++---------
 man/man8/devlink-resource.8 |  34 +++++-
 3 files changed, 192 insertions(+), 52 deletions(-)


base-commit: 7340b539841dc739bc0b813e8e86825bc1eb5a4c
-- 
2.44.0


^ permalink raw reply

* [PATCH iproute2-next 1/7] devlink: Split dpipe tables output to a separate function
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Ido Schimmel <idosch@nvidia.com>

As part of showing device resources, devlink also iterates over
available dpipe tables and shows the associated tables, if any.

A subsequent patch will show the device resources even if we failed to
retrieve the dpipe tables. In preparation for this change, split the
functionality of showing the associated dpipe tables into a separate
function.

No functional changes intended.

Reviewed-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 devlink/devlink.c | 54 +++++++++++++++++++++++++++--------------------
 1 file changed, 31 insertions(+), 23 deletions(-)

diff --git a/devlink/devlink.c b/devlink/devlink.c
index b4deba30c538..d998520cfd88 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -8811,34 +8811,13 @@ static const char *resource_unit_str_get(enum devlink_resource_unit unit)
 	}
 }
 
-static void resource_show(struct resource *resource,
-			  struct resource_ctx *ctx)
+static void resource_dpipe_tables_show(const struct resource *resource,
+				       const struct resource_ctx *ctx)
 {
-	struct resource *child_resource;
 	struct dpipe_table *table;
 	struct dl *dl = ctx->dl;
 	bool array = false;
 
-	check_indent_newline(dl);
-	print_string(PRINT_ANY, "name", "name %s", resource->name);
-	if (dl->verbose)
-		resource_path_print(dl, ctx->resources, resource->id);
-	pr_out_u64(dl, "size", resource->size);
-	if (resource->size != resource->size_new)
-		pr_out_u64(dl, "size_new", resource->size_new);
-	if (resource->occ_valid)
-		print_uint(PRINT_ANY, "occ", " occ %u",  resource->size_occ);
-	print_string(PRINT_ANY, "unit", " unit %s",
-		     resource_unit_str_get(resource->unit));
-
-	if (resource->size_min != resource->size_max) {
-		print_uint(PRINT_ANY, "size_min", " size_min %u",
-			   resource->size_min);
-		pr_out_u64(dl, "size_max", resource->size_max);
-		print_uint(PRINT_ANY, "size_gran", " size_gran %u",
-			   resource->size_gran);
-	}
-
 	list_for_each_entry(table, &ctx->tables->table_list, list)
 		if (table->resource_id == resource->id &&
 		    table->resource_valid)
@@ -8862,6 +8841,35 @@ static void resource_show(struct resource *resource,
 	}
 	if (array)
 		pr_out_array_end(dl);
+}
+
+static void resource_show(struct resource *resource,
+			  struct resource_ctx *ctx)
+{
+	struct resource *child_resource;
+	struct dl *dl = ctx->dl;
+
+	check_indent_newline(dl);
+	print_string(PRINT_ANY, "name", "name %s", resource->name);
+	if (dl->verbose)
+		resource_path_print(dl, ctx->resources, resource->id);
+	pr_out_u64(dl, "size", resource->size);
+	if (resource->size != resource->size_new)
+		pr_out_u64(dl, "size_new", resource->size_new);
+	if (resource->occ_valid)
+		print_uint(PRINT_ANY, "occ", " occ %u",  resource->size_occ);
+	print_string(PRINT_ANY, "unit", " unit %s",
+		     resource_unit_str_get(resource->unit));
+
+	if (resource->size_min != resource->size_max) {
+		print_uint(PRINT_ANY, "size_min", " size_min %u",
+			   resource->size_min);
+		pr_out_u64(dl, "size_max", resource->size_max);
+		print_uint(PRINT_ANY, "size_gran", " size_gran %u",
+			   resource->size_gran);
+	}
+
+	resource_dpipe_tables_show(resource, ctx);
 
 	if (list_empty(&resource->resource_list))
 		return;
-- 
2.44.0


^ permalink raw reply related

* [PATCH iproute2-next 2/7] devlink: Move dpipe tables query to resources show callback
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Ido Schimmel <idosch@nvidia.com>

As previously explained, as part of showing device resources, devlink
queries the device's dpipe tables so that it will be able to show the
association between resources and dpipe tables.

Currently, the dpipe tables are queried before the device resources.
This will become a problem when devlink is extended to support dumping
the resources of all available devices.

Therefore, in preparation for resource dump support, move the querying
of the device's dpipe tables to the resources show callback which is
invoked per-device.

There is no difference in the output of the resource show command, but
one functional difference is that errors in dpipe tables query are not
considered fatal. This seems reasonable given that errors are unlikely
and that displaying the dpipe tables is secondary to showing the device
resources.

Reviewed-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 devlink/devlink.c | 82 +++++++++++++++++++++++++++++++++++------------
 1 file changed, 62 insertions(+), 20 deletions(-)

diff --git a/devlink/devlink.c b/devlink/devlink.c
index d998520cfd88..7a8be3ad9b6a 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -8818,6 +8818,9 @@ static void resource_dpipe_tables_show(const struct resource *resource,
 	struct dl *dl = ctx->dl;
 	bool array = false;
 
+	if (!ctx->tables)
+		return;
+
 	list_for_each_entry(table, &ctx->tables->table_list, list)
 		if (table->resource_id == resource->id &&
 		    table->resource_valid)
@@ -8888,17 +8891,75 @@ static void resource_show(struct resource *resource,
 	pr_out_array_end(dl);
 }
 
+static void resources_dpipe_tables_init(struct dpipe_ctx *dpipe_ctx,
+					struct resource_ctx *resource_ctx,
+					struct nlattr **tb)
+{
+	const char *bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+	const char *dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+	struct mnlu_gen_socket nlg_dpipe;
+	struct dl *dl = resource_ctx->dl;
+	struct nlmsghdr *nlh;
+	int err;
+
+	err = dpipe_ctx_init(dpipe_ctx, dl);
+	if (err)
+		return;
+
+	err = mnlu_gen_socket_open(&nlg_dpipe, DEVLINK_GENL_NAME,
+				   DEVLINK_GENL_VERSION);
+	if (err)
+		goto ctx_fini;
+
+	nlh = mnlu_gen_socket_cmd_prepare(&nlg_dpipe,
+					  DEVLINK_CMD_DPIPE_TABLE_GET,
+					  NLM_F_REQUEST);
+
+	mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, bus_name);
+	mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, dev_name);
+
+	err = mnlu_gen_socket_sndrcv(&nlg_dpipe, nlh, cmd_dpipe_table_show_cb,
+				     dpipe_ctx);
+	if (err)
+		goto socket_close;
+
+	resource_ctx->tables = dpipe_ctx->tables;
+	mnlu_gen_socket_close(&nlg_dpipe);
+
+	return;
+
+socket_close:
+	mnlu_gen_socket_close(&nlg_dpipe);
+ctx_fini:
+	dpipe_ctx_fini(dpipe_ctx);
+}
+
+static void resources_dpipe_tables_fini(struct dpipe_ctx *dpipe_ctx,
+					struct resource_ctx *resource_ctx)
+{
+	if (!resource_ctx->tables)
+		return;
+
+	resource_ctx->tables = NULL;
+	dpipe_ctx_fini(dpipe_ctx);
+}
+
 static void
 resources_show(struct resource_ctx *ctx, struct nlattr **tb)
 {
 	struct resources *resources = ctx->resources;
+	struct dpipe_ctx dpipe_ctx = {};
 	struct resource *resource;
 
+	resources_dpipe_tables_init(&dpipe_ctx, ctx, tb);
+
 	list_for_each_entry(resource, &resources->resource_list, list) {
 		pr_out_handle_start_arr(ctx->dl, tb);
 		resource_show(resource, ctx);
 		pr_out_handle_end(ctx->dl);
 	}
+
+	resources_dpipe_tables_fini(&dpipe_ctx, ctx);
 }
 
 static int resources_get(struct resource_ctx *ctx, struct nlattr **tb)
@@ -8933,7 +8994,6 @@ static int cmd_resource_dump_cb(const struct nlmsghdr *nlh, void *data)
 static int cmd_resource_show(struct dl *dl)
 {
 	struct nlmsghdr *nlh;
-	struct dpipe_ctx dpipe_ctx = {};
 	struct resource_ctx resource_ctx = {};
 	int err;
 
@@ -8941,27 +9001,11 @@ static int cmd_resource_show(struct dl *dl)
 	if (err)
 		return err;
 
-	nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_DPIPE_TABLE_GET,
-			       NLM_F_REQUEST);
-	dl_opts_put(nlh, dl);
-
-	err = dpipe_ctx_init(&dpipe_ctx, dl);
-	if (err)
-		return err;
-
-	err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dpipe_table_show_cb,
-				  &dpipe_ctx);
-	if (err) {
-		pr_err("error get tables %s\n", strerror(dpipe_ctx.err));
-		goto out;
-	}
-
 	err = resource_ctx_init(&resource_ctx, dl);
 	if (err)
-		goto out;
+		return err;
 
 	resource_ctx.print_resources = true;
-	resource_ctx.tables = dpipe_ctx.tables;
 	nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_RESOURCE_DUMP,
 			       NLM_F_REQUEST | NLM_F_ACK);
 	dl_opts_put(nlh, dl);
@@ -8970,8 +9014,6 @@ static int cmd_resource_show(struct dl *dl)
 				  &resource_ctx);
 	pr_out_section_end(dl);
 	resource_ctx_fini(&resource_ctx);
-out:
-	dpipe_ctx_fini(&dpipe_ctx);
 	return err;
 }
 
-- 
2.44.0


^ permalink raw reply related

* [PATCH iproute2-next 3/7] devlink: fix memory leak in resource_ctx_fini
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Or Har-Toov <ohartoov@nvidia.com>

resource_ctx_init() allocates ctx->resources via resources_alloc()
which calls calloc(). resource_ctx_fini() calls resources_free() to
free the list items inside the struct, but never frees the struct
itself.

Signed-off-by: Or Har-Toov <ohartoov@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 devlink/devlink.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/devlink/devlink.c b/devlink/devlink.c
index 7a8be3ad9b6a..ba14c0056b1c 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -7441,6 +7441,7 @@ static int resource_ctx_init(struct resource_ctx *ctx, struct dl *dl)
 static void resource_ctx_fini(struct resource_ctx *ctx)
 {
 	resources_free(ctx->resources);
+	free(ctx->resources);
 }
 
 struct dpipe_ctx {
-- 
2.44.0


^ permalink raw reply related

* [PATCH iproute2-next 4/7] devlink: add dump support for resource show
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Or Har-Toov <ohartoov@nvidia.com>

Allow 'devlink resource show' without specifying a device to dump
resources from all devlink devices.

Signed-off-by: Or Har-Toov <ohartoov@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 devlink/devlink.c           | 18 ++++++++++++++----
 man/man8/devlink-resource.8 | 10 ++++++++--
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/devlink/devlink.c b/devlink/devlink.c
index ba14c0056b1c..0962ffd861ad 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -7429,6 +7429,12 @@ static void resources_free(struct resources *resources)
 		resource_free(resource);
 }
 
+static void resources_reset(struct resources *resources)
+{
+	resources_free(resources);
+	INIT_LIST_HEAD(&resources->resource_list);
+}
+
 static int resource_ctx_init(struct resource_ctx *ctx, struct dl *dl)
 {
 	ctx->resources = resources_alloc();
@@ -8986,19 +8992,23 @@ static int cmd_resource_dump_cb(const struct nlmsghdr *nlh, void *data)
 		return MNL_CB_ERROR;
 	}
 
-	if (ctx->print_resources)
+	if (ctx->print_resources) {
 		resources_show(ctx, tb);
+		resources_reset(ctx->resources);
+	}
 
 	return MNL_CB_OK;
 }
 
 static int cmd_resource_show(struct dl *dl)
 {
+	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
 	struct nlmsghdr *nlh;
 	struct resource_ctx resource_ctx = {};
 	int err;
 
-	err = dl_argv_parse(dl, DL_OPT_HANDLE, 0);
+	err = dl_argv_parse_with_selector(dl, &flags, DEVLINK_CMD_RESOURCE_DUMP,
+					  DL_OPT_HANDLE, 0, 0, 0);
 	if (err)
 		return err;
 
@@ -9008,7 +9018,7 @@ static int cmd_resource_show(struct dl *dl)
 
 	resource_ctx.print_resources = true;
 	nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_RESOURCE_DUMP,
-			       NLM_F_REQUEST | NLM_F_ACK);
+					  flags);
 	dl_opts_put(nlh, dl);
 	pr_out_section_start(dl, "resources");
 	err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_resource_dump_cb,
@@ -9020,7 +9030,7 @@ static int cmd_resource_show(struct dl *dl)
 
 static void cmd_resource_help(void)
 {
-	pr_err("Usage: devlink resource show DEV\n"
+	pr_err("Usage: devlink resource show [ DEV ]\n"
 	       "       devlink resource set DEV path PATH size SIZE\n");
 }
 
diff --git a/man/man8/devlink-resource.8 b/man/man8/devlink-resource.8
index c4f6918c9b03..b55138d950c7 100644
--- a/man/man8/devlink-resource.8
+++ b/man/man8/devlink-resource.8
@@ -19,7 +19,7 @@ devlink-resource \- devlink device resource configuration
 
 .ti -8
 .B devlink resource show
-.IR DEV
+.RI "[ " DEV " ]"
 
 .ti -8
 .B devlink resource help
@@ -31,11 +31,12 @@ devlink-resource \- devlink device resource configuration
 .BI size " RESOURCE_SIZE"
 
 .SH "DESCRIPTION"
-.SS devlink resource show - display devlink device's resosources
+.SS devlink resource show - display devlink device resources
 
 .PP
 .I "DEV"
 - specifies the devlink device to show.
+If omitted, all devices are listed.
 
 .in +4
 Format is:
@@ -58,6 +59,11 @@ The new resource's size.
 
 .SH "EXAMPLES"
 .PP
+devlink resource show
+.RS 4
+Shows resources for all devlink devices.
+.RE
+.PP
 devlink resource show pci/0000:01:00.0
 .RS 4
 Shows the resources of the specified devlink device.
-- 
2.44.0


^ permalink raw reply related

* [PATCH iproute2-next 6/7] devlink: add per-port resource show support
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Or Har-Toov <ohartoov@nvidia.com>

Extend 'devlink resource show' to accept DEV/PORT_INDEX, sending
DEVLINK_ATTR_PORT_INDEX to the kernel so it returns only that port's
resources directly.

For example:

$ devlink resource show pci/0000:03:00.0/196608
pci/0000:03:00.0/196608:
  name max_SFs size 128 unit entry dpipe_tables none

Signed-off-by: Or Har-Toov <ohartoov@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 bash-completion/devlink     |  1 +
 devlink/devlink.c           |  5 +++--
 man/man8/devlink-resource.8 | 17 ++++++++++++++++-
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/bash-completion/devlink b/bash-completion/devlink
index 7ec6a7cb6abd..3d8452a8869e 100644
--- a/bash-completion/devlink
+++ b/bash-completion/devlink
@@ -704,6 +704,7 @@ _devlink_resource()
         show)
             if [[ $cword -eq 3 ]]; then
                 _devlink_direct_complete "dev"
+                _devlink_direct_complete "port"
             fi
             return
             ;;
diff --git a/devlink/devlink.c b/devlink/devlink.c
index 737cfc7437f9..4224b7fa6792 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -9013,7 +9013,8 @@ static int cmd_resource_show(struct dl *dl)
 	int err;
 
 	err = dl_argv_parse_with_selector(dl, &flags, DEVLINK_CMD_RESOURCE_DUMP,
-					  DL_OPT_HANDLE, 0, 0, 0);
+					  DL_OPT_HANDLE | DL_OPT_HANDLEP,
+					  0, 0, 0);
 	if (err)
 		return err;
 
@@ -9035,7 +9036,7 @@ static int cmd_resource_show(struct dl *dl)
 
 static void cmd_resource_help(void)
 {
-	pr_err("Usage: devlink resource show [ DEV ]\n"
+	pr_err("Usage: devlink resource show [ DEV[/PORT_INDEX] ]\n"
 	       "       devlink resource set DEV path PATH size SIZE\n");
 }
 
diff --git a/man/man8/devlink-resource.8 b/man/man8/devlink-resource.8
index b55138d950c7..1e7d96126ce5 100644
--- a/man/man8/devlink-resource.8
+++ b/man/man8/devlink-resource.8
@@ -19,7 +19,7 @@ devlink-resource \- devlink device resource configuration
 
 .ti -8
 .B devlink resource show
-.RI "[ " DEV " ]"
+.RI "[ " DEV "[/" PORT_INDEX "] ]"
 
 .ti -8
 .B devlink resource help
@@ -43,6 +43,16 @@ Format is:
 .in +2
 BUS_NAME/BUS_ADDRESS
 
+.PP
+.I "PORT_INDEX"
+- specifies the port to show resources for.
+When given, only port-level resources for that port are shown.
+
+.in +4
+Format is:
+.in +2
+BUS_NAME/BUS_ADDRESS/PORT_INDEX
+
 .SS devlink resource set - sets resource size of specific resource
 
 .PP
@@ -69,6 +79,11 @@ devlink resource show pci/0000:01:00.0
 Shows the resources of the specified devlink device.
 .RE
 .PP
+devlink resource show pci/0000:01:00.0/1
+.RS 4
+Shows port-level resources for port 1 of the specified devlink device.
+.RE
+.PP
 devlink resource set pci/0000:01:00.0 path /kvd/linear size 98304
 .RS 4
 Sets the size of the specified resource for the specified devlink device.
-- 
2.44.0


^ permalink raw reply related

* [PATCH iproute2-next 5/7] devlink: show port resources in resource dump
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Or Har-Toov <ohartoov@nvidia.com>

When the kernel returns port-level resource messages during a
DEVLINK_CMD_RESOURCE_DUMP, display them alongside device-level
resources.

For example:

$ devlink resource show
pci/0000:03:00.0:
  name max_local_SFs size 32 unit entry dpipe_tables none
  name max_external_SFs size 32 unit entry dpipe_tables none
pci/0000:03:00.0/196608:
  name max_SFs size 32 unit entry dpipe_tables none
pci/0000:03:00.1:
  name max_local_SFs size 32 unit entry dpipe_tables none
  name max_external_SFs size 32 unit entry dpipe_tables none
pci/0000:03:00.1/262144:
  name max_SFs size 32 unit entry dpipe_tables none

Signed-off-by: Or Har-Toov <ohartoov@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 devlink/devlink.c | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/devlink/devlink.c b/devlink/devlink.c
index 0962ffd861ad..737cfc7437f9 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -8954,18 +8954,23 @@ static void resources_dpipe_tables_fini(struct dpipe_ctx *dpipe_ctx,
 static void
 resources_show(struct resource_ctx *ctx, struct nlattr **tb)
 {
-	struct resources *resources = ctx->resources;
+	bool is_port = !!tb[DEVLINK_ATTR_PORT_INDEX];
 	struct dpipe_ctx dpipe_ctx = {};
 	struct resource *resource;
+	struct dl *dl = ctx->dl;
 
 	resources_dpipe_tables_init(&dpipe_ctx, ctx, tb);
-
-	list_for_each_entry(resource, &resources->resource_list, list) {
-		pr_out_handle_start_arr(ctx->dl, tb);
+	list_for_each_entry(resource, &ctx->resources->resource_list, list) {
+		if (is_port)
+			pr_out_port_handle_start_arr(dl, tb, false);
+		else
+			pr_out_handle_start_arr(dl, tb);
 		resource_show(resource, ctx);
-		pr_out_handle_end(ctx->dl);
+		if (is_port)
+			pr_out_port_handle_end(dl);
+		else
+			pr_out_handle_end(dl);
 	}
-
 	resources_dpipe_tables_fini(&dpipe_ctx, ctx);
 }
 
-- 
2.44.0


^ permalink raw reply related

* [PATCH iproute2-next 7/7] devlink: add scope filter to resource show
From: Tariq Toukan @ 2026-06-09  5:39 UTC (permalink / raw)
  To: Stephen Hemminger, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Andrew Lunn, David S. Miller
  Cc: David Ahern, Donald Hunter, Simon Horman, Jiri Pirko,
	Jonathan Corbet, Shuah Khan, Saeed Mahameed, Leon Romanovsky,
	Tariq Toukan, Mark Bloch, Shuah Khan, Matthieu Baerts (NGI0),
	Chuck Lever, Or Har-Toov, Carolina Jubran, Moshe Shemesh,
	Shay Drori, Dragos Tatulea, Daniel Zahka, Shahar Shitrit,
	Jacob Keller, Cosmin Ratiu, Parav Pandit, Kees Cook,
	Adithya Jayachandran, Daniel Jurgens, netdev, linux-kernel,
	linux-doc, linux-rdma, linux-kselftest, Gal Pressman,
	Ido Schimmel, Jiri Pirko, Petr Machata
In-Reply-To: <20260609053953.487152-1-tariqt@nvidia.com>

From: Or Har-Toov <ohartoov@nvidia.com>

Add optional 'scope { dev | port }' argument to 'devlink resource show'
without a device handle to filter the full dump to device-level or
port-level resources only.

Example - dump only device-level resources:

  $ devlink resource show scope dev
  pci/0000:03:00.0:
    name max_local_SFs size 128 unit entry dpipe_tables none
    name max_external_SFs size 128 unit entry dpipe_tables none
  pci/0000:03:00.1:
    name max_local_SFs size 128 unit entry dpipe_tables none
    name max_external_SFs size 128 unit entry dpipe_tables none

Example - dump only port-level resources:

  $ devlink resource show scope port
  pci/0000:03:00.0/196608:
    name max_SFs size 128 unit entry dpipe_tables none
  pci/0000:03:00.0/196609:
    name max_SFs size 128 unit entry dpipe_tables none
  pci/0000:03:00.1/196708:
    name max_SFs size 128 unit entry dpipe_tables none
  pci/0000:03:00.1/196709:
    name max_SFs size 128 unit entry dpipe_tables none

Signed-off-by: Or Har-Toov <ohartoov@nvidia.com>
Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
---
 bash-completion/devlink     |  7 ++++++
 devlink/devlink.c           | 47 ++++++++++++++++++++++++++++++++-----
 man/man8/devlink-resource.8 | 11 ++++++++-
 3 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/bash-completion/devlink b/bash-completion/devlink
index 3d8452a8869e..bfc0083f647b 100644
--- a/bash-completion/devlink
+++ b/bash-completion/devlink
@@ -702,9 +702,16 @@ _devlink_resource()
 {
     case $command in
         show)
+            case $prev in
+                scope)
+                    COMPREPLY=( $( compgen -W "dev port" -- "$cur" ) )
+                    return
+                    ;;
+            esac
             if [[ $cword -eq 3 ]]; then
                 _devlink_direct_complete "dev"
                 _devlink_direct_complete "port"
+                COMPREPLY+=( $( compgen -W "scope" -- "$cur" ) )
             fi
             return
             ;;
diff --git a/devlink/devlink.c b/devlink/devlink.c
index 4224b7fa6792..1a94b6413048 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -314,6 +314,7 @@ static int ifname_map_update(struct ifname_map *ifname_map, const char *ifname)
 #define DL_OPT_PORT_FN_RATE_TC_BWS	BIT(59)
 #define DL_OPT_HEALTH_REPORTER_BURST_PERIOD	BIT(60)
 #define DL_OPT_PARAM_SET_DEFAULT	BIT(61)
+#define DL_OPT_RESOURCE_SCOPE		BIT(62)
 
 struct dl_opts {
 	uint64_t present; /* flags of present items */
@@ -382,6 +383,7 @@ struct dl_opts {
 	bool selftests_opt[DEVLINK_ATTR_SELFTEST_ID_MAX + 1];
 	struct nla_bitfield32 port_fn_caps;
 	uint32_t port_fn_max_io_eqs;
+	uint32_t resource_scope_mask;
 };
 
 struct dl {
@@ -1467,6 +1469,19 @@ static int flash_overwrite_section_get(const char *sectionstr, uint32_t *mask)
 	return 0;
 }
 
+static int resource_scope_get(const char *scopestr, uint32_t *scope)
+{
+	if (strcmp(scopestr, "dev") == 0) {
+		*scope = DEVLINK_RESOURCE_SCOPE_DEV;
+	} else if (strcmp(scopestr, "port") == 0) {
+		*scope = DEVLINK_RESOURCE_SCOPE_PORT;
+	} else {
+		pr_err("Unknown resource scope \"%s\"\n", scopestr);
+		return -EINVAL;
+	}
+	return 0;
+}
+
 static int param_cmode_get(const char *cmodestr,
 			   enum devlink_param_cmode *cmode)
 {
@@ -1647,6 +1662,7 @@ static const struct dl_args_metadata dl_args_required[] = {
 	{DL_OPT_ESWITCH_ENCAP_MODE,   "E-Switch encapsulation option expected."},
 	{DL_OPT_RESOURCE_PATH,	      "Resource path expected."},
 	{DL_OPT_RESOURCE_SIZE,	      "Resource size expected."},
+	{DL_OPT_RESOURCE_SCOPE,	      "Resource scope expected."},
 	{DL_OPT_PARAM_NAME,	      "Parameter name expected."},
 	{DL_OPT_PARAM_VALUE,	      "Value to set expected."},
 	{DL_OPT_PARAM_CMODE,	      "Configuration mode expected."},
@@ -2662,6 +2678,9 @@ static void dl_opts_put(struct nlmsghdr *nlh, struct dl *dl)
 	if (opts->present & DL_OPT_RESOURCE_SIZE)
 		mnl_attr_put_u64(nlh, DEVLINK_ATTR_RESOURCE_SIZE,
 				 opts->resource_size);
+	if (opts->present & DL_OPT_RESOURCE_SCOPE)
+		mnl_attr_put_u32(nlh, DEVLINK_ATTR_RESOURCE_SCOPE_MASK,
+				 opts->resource_scope_mask);
 	if (opts->present & DL_OPT_PARAM_NAME)
 		mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_NAME,
 				  opts->param_name);
@@ -9010,13 +9029,29 @@ static int cmd_resource_show(struct dl *dl)
 	uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
 	struct nlmsghdr *nlh;
 	struct resource_ctx resource_ctx = {};
+	struct dl_opts *opts = &dl->opts;
 	int err;
 
-	err = dl_argv_parse_with_selector(dl, &flags, DEVLINK_CMD_RESOURCE_DUMP,
-					  DL_OPT_HANDLE | DL_OPT_HANDLEP,
-					  0, 0, 0);
-	if (err)
-		return err;
+	if (dl_argv_match(dl, "scope")) {
+		const char *scopestr;
+
+		dl_arg_inc(dl);
+		err = dl_argv_str(dl, &scopestr);
+		if (err)
+			return err;
+		err = resource_scope_get(scopestr, &opts->resource_scope_mask);
+		if (err)
+			return err;
+		opts->present |= DL_OPT_RESOURCE_SCOPE;
+		flags |= NLM_F_DUMP;
+	} else {
+		err = dl_argv_parse_with_selector(dl, &flags,
+						  DEVLINK_CMD_RESOURCE_DUMP,
+						  DL_OPT_HANDLE | DL_OPT_HANDLEP,
+						  0, 0, 0);
+		if (err)
+			return err;
+	}
 
 	err = resource_ctx_init(&resource_ctx, dl);
 	if (err)
@@ -9036,7 +9071,7 @@ static int cmd_resource_show(struct dl *dl)
 
 static void cmd_resource_help(void)
 {
-	pr_err("Usage: devlink resource show [ DEV[/PORT_INDEX] ]\n"
+	pr_err("Usage: devlink resource show [ DEV[/PORT_INDEX] | scope { dev | port } ]\n"
 	       "       devlink resource set DEV path PATH size SIZE\n");
 }
 
diff --git a/man/man8/devlink-resource.8 b/man/man8/devlink-resource.8
index 1e7d96126ce5..04cde2bf8958 100644
--- a/man/man8/devlink-resource.8
+++ b/man/man8/devlink-resource.8
@@ -19,7 +19,7 @@ devlink-resource \- devlink device resource configuration
 
 .ti -8
 .B devlink resource show
-.RI "[ " DEV "[/" PORT_INDEX "] ]"
+.RI "[ " DEV "[/" PORT_INDEX "] | " scope " { " dev " | " port " } ]"
 
 .ti -8
 .B devlink resource help
@@ -53,6 +53,15 @@ Format is:
 .in +2
 BUS_NAME/BUS_ADDRESS/PORT_INDEX
 
+.TP
+.BI scope " { dev | port }"
+Filter resources by scope.
+.B dev
+shows only device-level resources.
+.B port
+shows only port-level resources.
+When omitted, resources of both scopes are shown.
+
 .SS devlink resource set - sets resource size of specific resource
 
 .PP
-- 
2.44.0


^ permalink raw reply related

* Re: [PATCH 1/1] docs: Fix minor grammatical error
From: Brigham Campbell @ 2026-06-09  5:50 UTC (permalink / raw)
  To: Randy Dunlap, Brigham Campbell, Thorsten Leemhuis,
	Jonathan Corbet, Shuah Khan,
	open list:DOCUMENTATION REPORTING ISSUES, open list
In-Reply-To: <4c9b2927-29e0-40a6-bed4-14142dedd2ef@infradead.org>

On Sat Jun 6, 2026 at 1:10 PM MDT, Randy Dunlap wrote:
> Can't you just modify the first line only and leave the other 3 changed lines
> intact?

I'm not as familiar with the kernel documentation project as I am with
the code itself. I figured that it's generally preferred to maintain
80-character hard wrapping consistently across all documentation. Is it
actually preferable to _not_ reflow text after editing in order to avoid
munging the git history?

Thanks for your time, Randy,
Brigham

^ permalink raw reply

* Re: [RFC PATCH v1 00/13] exec: add spawn templates for repeated executable startup
From: Florian Weimer @ 2026-06-09  6:08 UTC (permalink / raw)
  To: Jann Horn
  Cc: Mateusz Guzik, Christian Brauner, Li Chen, Kees Cook,
	Alexander Viro, linux-fsdevel, linux-api, linux-kernel, linux-mm,
	linux-arch, linux-doc, linux-kselftest, x86, Arnd Bergmann,
	Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan
In-Reply-To: <CAG48ez38OEE8ZPLyU6nr9=cYx-hMsdoh5WRrv-GMZGMDKyyOTA@mail.gmail.com>

* Jann Horn:

>> Per the above, the primary win would stem from *NOT* messing with mm.
>
> As you write below, I think we have that with CLONE_MM? The C function
> vfork() is kind of a terrible API because of its returns-twice
> behavior, but I think if process cloning with CLONE_VM|CLONE_VFORK was
> wrapped by libc in a way similar to clone() (with the child executing
> a separate handler function), or if it was used in the implementation
> of some higher-level process-spawning API, it would be a perfectly
> fine API?

No, there is still a problem with SIGTSTP handling because we cannot
atomically unmask the signal during execve.  We need to unblock SIGTSTP
before execve in the new process, but this means that it can get
suspended by SIGTSTP.  Consequently, the execve never happens and the
original process is stuck in vfork:

  posix_spawn: parent can get stuck in uninterruptible sleep if child
  receives SIGTSTP early enough
  <https://inbox.sourceware.org/libc-help/2921668c-773e-465d-9480-0abb6f979bf9@www.fastmail.com/>

More on the low-level side, it's difficult to make sure that execve gets
a consistent snapshot of the environ vector.  Both vfork and execve need
to be async-signal-safe.  Any locking or memory allocation (except for
the stack …) persists in the original process after vfork returns.  The
environ vector can be large, so making a copy on the stack is not ideal.
It's even harder for getenv/setenv/unsetenv implementations that use
locking instead of software transactional memory.

In general, I prefer the vfork+execve API over things like posix_spawn
because eventually, you have dependencies between the syslets, or need
control flow.  This introduces a lot of complexity.  Conceptually,
vfork+execve is much simpler, and in many ways quite safe (even mutexes
work as long as they do not need a correct TID).

Thanks,
Florian


^ permalink raw reply

* Re: [PATCH v3 5/6] kselftest: alloc_tag: add kselftest for ioctl interface
From: Hao Ge @ 2026-06-09  6:09 UTC (permalink / raw)
  To: Abhishek Bapat
  Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
	Sourav Panda, Suren Baghdasaryan, Andrew Morton, Kent Overstreet
In-Reply-To: <2e55b3b1388a4f7a59f670a83f222ba6c836ac4e.1780701922.git.abhishekbapat@google.com>

Hi Abhishek


On 2026/6/6 07:36, Abhishek Bapat wrote:
> Introduce a kselftest to verify the new IOCTL-based interface for
> /proc/allocinfo. The test covers:
>
> 1. Validation of the filename filter.
> 2. Validation of the function filter.
>
> The first test validates the functionality of the filename filter. Using
> "mm/memory.c" as the candidate filename filter, it retrieves filtered
> entries from both procfs and ioctl and matches the first VEC_MAX_ENTRIES
> entries.
>
> The second test validates the functionality of the function filter.
> It uses "dup_mm" as the candidate function as we do not expect this
> function name to change frequently and hence won't be needing to modify
> this test often.
>
> Note that both the tests match line no, function name and file name
> fields. Bytes allocated and calls are not matched as those values may
> change in the time when the data is being read from procfs and ioctl and
> hence can lead to false negatives.
>
> Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
> ---
>   MAINTAINERS                                   |   1 +
>   tools/testing/selftests/alloc_tag/Makefile    |   9 +
>   .../alloc_tag/allocinfo_ioctl_test.c          | 313 ++++++++++++++++++
>   3 files changed, 323 insertions(+)
>   create mode 100644 tools/testing/selftests/alloc_tag/Makefile
>   create mode 100644 tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 77f3fc487691..80560f5f1292 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16713,6 +16713,7 @@ F:	include/linux/alloc_tag.h
>   F:	include/linux/pgalloc_tag.h
>   F:	include/uapi/linux/alloc_tag.h
>   F:	lib/alloc_tag.c
> +F:	tools/testing/selftests/alloc_tag/
>   
>   MEMORY CONTROLLER DRIVERS
>   M:	Krzysztof Kozlowski <krzk@kernel.org>
> diff --git a/tools/testing/selftests/alloc_tag/Makefile b/tools/testing/selftests/alloc_tag/Makefile
> new file mode 100644
> index 000000000000..f2b8fc022c3b
> --- /dev/null
> +++ b/tools/testing/selftests/alloc_tag/Makefile
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +TEST_GEN_PROGS := allocinfo_ioctl_test
> +
> +CFLAGS += -Wall
> +CFLAGS += -I../../../../usr/include
> +
> +include ../lib.mk
> +
> diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
> new file mode 100644
> index 000000000000..5c3c16e86c23
> --- /dev/null
> +++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
> @@ -0,0 +1,313 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/* kselftest for allocinfo ioctl
> + * allocinfo ioctl retrives allocinfo data through ioctl


nit: s/retrives/retrieves/


I've applied the full patch series locally and ran the kselftest, all 4 
tests pass:

[root@localhost alloc_tag]# ./allocinfo_ioctl_test
1..4
ok 1 test_filename_filter
ok 2 test_function_filter
ok 3 test_size_filter
ok 4 test_lineno_filter
# Totals: pass:4 fail:0 xfail:0 xpass:0 skip:0 error:0

But there are no tests for ALLOCINFO_FILTER_MASK_MODNAME and

ALLOCINFO_FILTER_MASK_INACCURATE.


Thanks

Best Regards

Hao

> + * Copyright (C) 2026 Google, Inc.
> + */
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdbool.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <linux/types.h>
> +#include <linux/alloc_tag.h>
> +#include "../kselftest.h"
> +
> +#define MAX_LINE_LEN		512
> +#define ALLOCINFO_PROC		"/proc/allocinfo"
> +
> +enum ioctl_ret {
> +	IOCTL_SUCCESS = 0,
> +	IOCTL_FAILURE = 1,
> +	IOCTL_INVALID_DATA = 2,
> +};
> +
> +#define VEC_MAX_ENTRIES 32
> +
> +struct allocinfo_tag_data_vec {
> +	struct allocinfo_tag_data tag[VEC_MAX_ENTRIES];
> +	__u64 count;
> +};
> +
> +static inline int __allocinfo_get_content_id(int dev_fd, struct allocinfo_content_id *params)
> +{
> +	return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params);
> +}
> +
> +static inline int __allocinfo_get_at(int dev_fd, struct allocinfo_get_at *params)
> +{
> +	return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params);
> +}
> +
> +static inline int __allocinfo_get_next(int dev_fd, struct allocinfo_tag_data *params)
> +{
> +	return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params);
> +}
> +
> +static bool match_entry(const struct allocinfo_tag_data *procfs_entry,
> +			const struct allocinfo_tag_data *tag_data,
> +			bool match_bytes, bool match_calls, bool match_lineno,
> +			bool match_function, bool match_filename)
> +{
> +	if (match_bytes && tag_data->counter.bytes != procfs_entry->counter.bytes) {
> +		ksft_print_msg("size retrieved through ioctl does not match procfs\n");
> +		return false;
> +	}
> +
> +	if (match_calls && tag_data->counter.calls != procfs_entry->counter.calls) {
> +		ksft_print_msg("call count retrieved through ioctl does not match procfs\n");
> +		return false;
> +	}
> +
> +	if (match_lineno && tag_data->tag.lineno != procfs_entry->tag.lineno) {
> +		ksft_print_msg("lineno retrieved through ioctl does not match procfs\n");
> +		return false;
> +	}
> +
> +	if (match_function &&
> +	    strncmp(tag_data->tag.function, procfs_entry->tag.function, ALLOCINFO_STR_SIZE)) {
> +		ksft_print_msg("function retrieved through ioctl does not match procfs\n");
> +		return false;
> +	}
> +
> +	if (match_filename &&
> +	    strncmp(tag_data->tag.filename, procfs_entry->tag.filename, ALLOCINFO_STR_SIZE)) {
> +		ksft_print_msg("filename retrieved through ioctl does not match procfs\n");
> +		return false;
> +	}
> +	return true;
> +}
> +
> +static bool match_entries(const struct allocinfo_tag_data_vec *procfs_entries,
> +			  const struct allocinfo_tag_data_vec *tags,
> +			  bool match_bytes, bool match_calls, bool match_lineno,
> +			  bool match_function, bool match_filename)
> +{
> +	__u64 i;
> +
> +	if (procfs_entries->count != tags->count) {
> +		ksft_print_msg("Entry count mismatch. ioctl entries: %llu, proc entries: %llu\n",
> +			       tags->count, procfs_entries->count);
> +		return false;
> +	}
> +	for (i = 0; i < procfs_entries->count; i++) {
> +		if (!match_entry(&procfs_entries->tag[i], &tags->tag[i],
> +				 match_bytes, match_calls, match_lineno,
> +				 match_function, match_filename)) {
> +			ksft_print_msg("%lluth entry does not match.\n", i);
> +			return false;
> +		}
> +	}
> +	return true;
> +}
> +
> +static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec *procfs_entries,
> +				       const struct allocinfo_filter *filter, int fd)
> +{
> +	FILE *fp = fdopen(fd, "r");
> +	char line[MAX_LINE_LEN];
> +	int matches;
> +	struct allocinfo_tag_data procfs_entry;
> +
> +	if (!fp) {
> +		ksft_print_msg("Failed to open " ALLOCINFO_PROC " for reading\n");
> +		return 1;
> +	}
> +	memset(procfs_entries, 0, sizeof(*procfs_entries));
> +	while (fgets(line, sizeof(line), fp) && procfs_entries->count < VEC_MAX_ENTRIES) {
> +
> +		memset(&procfs_entry, 0, sizeof(procfs_entry));
> +		matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s",
> +				 &procfs_entry.counter.bytes,
> +				 &procfs_entry.counter.calls,
> +				 procfs_entry.tag.filename,
> +				 &procfs_entry.tag.lineno,
> +				 procfs_entry.tag.function);
> +
> +		if (matches != 5)
> +			continue;
> +
> +		if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) {
> +			if (strncmp(procfs_entry.tag.filename,
> +				    filter->fields.filename, ALLOCINFO_STR_SIZE))
> +				continue;
> +		}
> +		if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) {
> +			if (strncmp(procfs_entry.tag.function,
> +				    filter->fields.function, ALLOCINFO_STR_SIZE))
> +				continue;
> +		}
> +		if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) {
> +			if (procfs_entry.tag.lineno != filter->fields.lineno)
> +				continue;
> +		}
> +		if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) {
> +			if (procfs_entry.counter.bytes < filter->min_size)
> +				continue;
> +		}
> +		if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) {
> +			if (procfs_entry.counter.bytes > filter->max_size)
> +				continue;
> +		}
> +
> +		memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry,
> +		       sizeof(procfs_entry));
> +	}
> +	return 0;
> +}
> +
> +static enum ioctl_ret get_filtered_ioctl_entries(struct allocinfo_tag_data_vec *tags,
> +						 const struct allocinfo_filter *filter, int fd,
> +						 __u64 start_pos)
> +{
> +	struct allocinfo_content_id start_cont_id, end_cont_id;
> +	struct allocinfo_get_at get_at_params;
> +	const int max_retries = 10;
> +	int retry_count = 0;
> +	int status;
> +
> +	/*
> +	 * __allocinfo_get_content_id may return different values if a kernel module was loaded
> +	 * between the two calls. If that happens, the data gathered cannot be considered consistent
> +	 * and hence needs to be fetched again to avoid flakiness.
> +	 */
> +	do {
> +		if (__allocinfo_get_content_id(fd, &start_cont_id)) {
> +			ksft_print_msg("allocinfo_get_content_id failed\n");
> +			return IOCTL_FAILURE;
> +		}
> +
> +		memset(tags, 0, sizeof(*tags));
> +		memset(&get_at_params, 0, sizeof(get_at_params));
> +		memcpy(&get_at_params.filter, filter, sizeof(*filter));
> +		get_at_params.pos = start_pos;
> +		if (__allocinfo_get_at(fd, &get_at_params)) {
> +			ksft_print_msg("allocinfo_get_at failed\n");
> +			return IOCTL_FAILURE;
> +		}
> +		memcpy(&tags->tag[tags->count++], &get_at_params.data, sizeof(get_at_params.data));
> +
> +		while (tags->count < VEC_MAX_ENTRIES &&
> +		       __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
> +			tags->count++;
> +
> +		if (__allocinfo_get_content_id(fd, &end_cont_id)) {
> +			ksft_print_msg("allocinfo_get_content_id failed\n");
> +			return IOCTL_FAILURE;
> +		}
> +
> +		if (start_cont_id.id == end_cont_id.id) {
> +			status = IOCTL_SUCCESS;
> +		} else {
> +			ksft_print_msg("allocinfo_get_content_id mismatch, retrying...\n");
> +			status = IOCTL_INVALID_DATA;
> +		}
> +	} while (status == IOCTL_INVALID_DATA && retry_count++ < max_retries);
> +
> +	return status;
> +}
> +
> +static int run_filter_test(const struct allocinfo_filter *filter)
> +{
> +	int fd;
> +	struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
> +	struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
> +	int ioctl_status;
> +	int ret = KSFT_PASS;
> +
> +	if (!tags || !procfs_entries) {
> +		ksft_print_msg("Memory allocation failed.\n");
> +		ret = KSFT_FAIL;
> +		goto freemem;
> +	}
> +
> +	fd = open(ALLOCINFO_PROC, O_RDONLY);
> +	if (fd < 0) {
> +		ksft_exit_skip("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
> +		ret = KSFT_FAIL;
> +		goto freemem;
> +	}
> +
> +	if (get_filtered_procfs_entries(procfs_entries, filter, fd)) {
> +		ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
> +		ret = KSFT_FAIL;
> +		goto exit;
> +	}
> +
> +	if (procfs_entries->count == 0) {
> +		ksft_print_msg("No entries found in " ALLOCINFO_PROC ", skipping test\n");
> +		ret = KSFT_SKIP;
> +		goto exit;
> +	}
> +
> +	ioctl_status = get_filtered_ioctl_entries(tags, filter, fd, 0);
> +	if (ioctl_status == IOCTL_INVALID_DATA) {
> +		ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
> +		ret = KSFT_SKIP;
> +		goto exit;
> +	}
> +	if (ioctl_status == IOCTL_FAILURE) {
> +		ksft_print_msg("Error retrieving IOCTL entries.\n");
> +		ret = KSFT_FAIL;
> +		goto exit;
> +	}
> +
> +	if (!match_entries(procfs_entries, tags, false, false, true, true, true))
> +		ret = KSFT_FAIL;
> +
> +exit:
> +	close(fd);
> +freemem:
> +	free(tags);
> +	free(procfs_entries);
> +	return ret;
> +}
> +
> +static int test_filename_filter(void)
> +{
> +	struct allocinfo_filter filter;
> +	const char *target_filename = "mm/memory.c";
> +
> +	memset(&filter, 0, sizeof(filter));
> +	filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME;
> +	strncpy(filter.fields.filename, target_filename, ALLOCINFO_STR_SIZE);
> +
> +	return run_filter_test(&filter);
> +}
> +
> +static int test_function_filter(void)
> +{
> +	struct allocinfo_filter filter;
> +	const char *target_function = "dup_mm";
> +
> +	memset(&filter, 0, sizeof(filter));
> +	filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
> +	strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
> +
> +	return run_filter_test(&filter);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int ret;
> +
> +	ksft_set_plan(2);
> +
> +	ret = test_filename_filter();
> +	if (ret == KSFT_SKIP)
> +		ksft_test_result_skip("Skipping test_filename_filter\n");
> +	else
> +		ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n");
> +
> +	ret = test_function_filter();
> +	if (ret == KSFT_SKIP)
> +		ksft_test_result_skip("Skipping test_function_filter\n");
> +	else
> +		ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
> +
> +	ksft_finished();
> +}

^ permalink raw reply

* Re: [PATCH 1/1] docs: Fix minor grammatical error
From: Thorsten Leemhuis @ 2026-06-09  6:15 UTC (permalink / raw)
  To: Brigham Campbell, Randy Dunlap, Jonathan Corbet, Shuah Khan,
	open list:DOCUMENTATION REPORTING ISSUES, open list
In-Reply-To: <DJ4A839X388E.376Q6KVB6JE14@brighamcampbell.com>

On 6/9/26 07:50, Brigham Campbell wrote:
> On Sat Jun 6, 2026 at 1:10 PM MDT, Randy Dunlap wrote:
>> Can't you just modify the first line only and leave the other 3 changed lines
>> intact?
> 
> I'm not as familiar with the kernel documentation project as I am with
> the code itself. I figured that it's generally preferred to maintain
> 80-character hard wrapping consistently across all documentation. Is it
> actually preferable to _not_ reflow text after editing in order to avoid
> munging the git history?

It's a "80-character wrapping" vs "keep patches small/simple/obvious"
situation where one has to weighting things up against each other.

If you'd change one thing in a line that would make the line say only
something like 60 characters or less long, then reflowing becomes the
right thing, as the para otherwise would look odd.

But in this case it won't matter much, so it's likely better to not
reflow to keep the patch smaller.

In the end it's a judgement call that the maintainer has to make. I
don't care much, but if I'd be forced to decide for one way or the other
I'd go with what Randy suggested.

BTW, thx for fixing this!

Ciao, Thorsten

^ permalink raw reply

* Re: [PATCH v3 5/6] kselftest: alloc_tag: add kselftest for ioctl interface
From: Hao Ge @ 2026-06-09  6:26 UTC (permalink / raw)
  To: Abhishek Bapat
  Cc: Shuah Khan, Jonathan Corbet, linux-doc, linux-kernel, linux-mm,
	Sourav Panda, Suren Baghdasaryan, Andrew Morton, Kent Overstreet
In-Reply-To: <49f725a7-577d-4036-bd5a-5a33fc9e17c3@linux.dev>


On 2026/6/9 14:09, Hao Ge wrote:
> Hi Abhishek
>
>
> On 2026/6/6 07:36, Abhishek Bapat wrote:
>> Introduce a kselftest to verify the new IOCTL-based interface for
>> /proc/allocinfo. The test covers:
>>
>> 1. Validation of the filename filter.
>> 2. Validation of the function filter.
>>
>> The first test validates the functionality of the filename filter. Using
>> "mm/memory.c" as the candidate filename filter, it retrieves filtered
>> entries from both procfs and ioctl and matches the first VEC_MAX_ENTRIES
>> entries.
>>
>> The second test validates the functionality of the function filter.
>> It uses "dup_mm" as the candidate function as we do not expect this
>> function name to change frequently and hence won't be needing to modify
>> this test often.
>>
>> Note that both the tests match line no, function name and file name
>> fields. Bytes allocated and calls are not matched as those values may
>> change in the time when the data is being read from procfs and ioctl and
>> hence can lead to false negatives.
>>
>> Signed-off-by: Abhishek Bapat <abhishekbapat@google.com>
>> ---
>>   MAINTAINERS                                   |   1 +
>>   tools/testing/selftests/alloc_tag/Makefile    |   9 +
>>   .../alloc_tag/allocinfo_ioctl_test.c          | 313 ++++++++++++++++++
>>   3 files changed, 323 insertions(+)
>>   create mode 100644 tools/testing/selftests/alloc_tag/Makefile
>>   create mode 100644 
>> tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 77f3fc487691..80560f5f1292 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -16713,6 +16713,7 @@ F:    include/linux/alloc_tag.h
>>   F:    include/linux/pgalloc_tag.h
>>   F:    include/uapi/linux/alloc_tag.h
>>   F:    lib/alloc_tag.c
>> +F:    tools/testing/selftests/alloc_tag/
>>     MEMORY CONTROLLER DRIVERS
>>   M:    Krzysztof Kozlowski <krzk@kernel.org>
>> diff --git a/tools/testing/selftests/alloc_tag/Makefile 
>> b/tools/testing/selftests/alloc_tag/Makefile
>> new file mode 100644
>> index 000000000000..f2b8fc022c3b
>> --- /dev/null
>> +++ b/tools/testing/selftests/alloc_tag/Makefile
>> @@ -0,0 +1,9 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +
>> +TEST_GEN_PROGS := allocinfo_ioctl_test
>> +
>> +CFLAGS += -Wall
>> +CFLAGS += -I../../../../usr/include
>> +
>> +include ../lib.mk
>> +
>> diff --git a/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c 
>> b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
>> new file mode 100644
>> index 000000000000..5c3c16e86c23
>> --- /dev/null
>> +++ b/tools/testing/selftests/alloc_tag/allocinfo_ioctl_test.c
>> @@ -0,0 +1,313 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +
>> +/* kselftest for allocinfo ioctl
>> + * allocinfo ioctl retrives allocinfo data through ioctl
>
>
> nit: s/retrives/retrieves/
>
>
> I've applied the full patch series locally and ran the kselftest, all 
> 4 tests pass:
>
> [root@localhost alloc_tag]# ./allocinfo_ioctl_test
> 1..4
> ok 1 test_filename_filter
> ok 2 test_function_filter
> ok 3 test_size_filter
> ok 4 test_lineno_filter
> # Totals: pass:4 fail:0 xfail:0 xpass:0 skip:0 error:0
>
> But there are no tests for ALLOCINFO_FILTER_MASK_MODNAME and
>
> ALLOCINFO_FILTER_MASK_INACCURATE.


Sorry, please disregard my suggestion about adding tests for

ALLOCINFO_FILTER_MASK_MODNAME and ALLOCINFO_FILTER_MASK_INACCURATE.

ALLOCINFO_FILTER_MASK_MODNAME depends on kernel config and also requires

the module to be loaded. ALLOCINFO_FILTER_MASK_INACCURATE entries may not

be common, unless we can find a stable way to produce them.


>
>
> Thanks
>
> Best Regards
>
> Hao
>
>> + * Copyright (C) 2026 Google, Inc.
>> + */
>> +
>> +#include <errno.h>
>> +#include <fcntl.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <stdbool.h>
>> +#include <unistd.h>
>> +#include <sys/ioctl.h>
>> +#include <linux/types.h>
>> +#include <linux/alloc_tag.h>
>> +#include "../kselftest.h"
>> +
>> +#define MAX_LINE_LEN        512
>> +#define ALLOCINFO_PROC        "/proc/allocinfo"
>> +
>> +enum ioctl_ret {
>> +    IOCTL_SUCCESS = 0,
>> +    IOCTL_FAILURE = 1,
>> +    IOCTL_INVALID_DATA = 2,
>> +};
>> +
>> +#define VEC_MAX_ENTRIES 32
>> +
>> +struct allocinfo_tag_data_vec {
>> +    struct allocinfo_tag_data tag[VEC_MAX_ENTRIES];
>> +    __u64 count;
>> +};
>> +
>> +static inline int __allocinfo_get_content_id(int dev_fd, struct 
>> allocinfo_content_id *params)
>> +{
>> +    return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params);
>> +}
>> +
>> +static inline int __allocinfo_get_at(int dev_fd, struct 
>> allocinfo_get_at *params)
>> +{
>> +    return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params);
>> +}
>> +
>> +static inline int __allocinfo_get_next(int dev_fd, struct 
>> allocinfo_tag_data *params)
>> +{
>> +    return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params);
>> +}
>> +
>> +static bool match_entry(const struct allocinfo_tag_data *procfs_entry,
>> +            const struct allocinfo_tag_data *tag_data,
>> +            bool match_bytes, bool match_calls, bool match_lineno,
>> +            bool match_function, bool match_filename)
>> +{
>> +    if (match_bytes && tag_data->counter.bytes != 
>> procfs_entry->counter.bytes) {
>> +        ksft_print_msg("size retrieved through ioctl does not match 
>> procfs\n");
>> +        return false;
>> +    }
>> +
>> +    if (match_calls && tag_data->counter.calls != 
>> procfs_entry->counter.calls) {
>> +        ksft_print_msg("call count retrieved through ioctl does not 
>> match procfs\n");
>> +        return false;
>> +    }
>> +
>> +    if (match_lineno && tag_data->tag.lineno != 
>> procfs_entry->tag.lineno) {
>> +        ksft_print_msg("lineno retrieved through ioctl does not 
>> match procfs\n");
>> +        return false;
>> +    }
>> +
>> +    if (match_function &&
>> +        strncmp(tag_data->tag.function, procfs_entry->tag.function, 
>> ALLOCINFO_STR_SIZE)) {
>> +        ksft_print_msg("function retrieved through ioctl does not 
>> match procfs\n");
>> +        return false;
>> +    }
>> +
>> +    if (match_filename &&
>> +        strncmp(tag_data->tag.filename, procfs_entry->tag.filename, 
>> ALLOCINFO_STR_SIZE)) {
>> +        ksft_print_msg("filename retrieved through ioctl does not 
>> match procfs\n");
>> +        return false;
>> +    }
>> +    return true;
>> +}
>> +
>> +static bool match_entries(const struct allocinfo_tag_data_vec 
>> *procfs_entries,
>> +              const struct allocinfo_tag_data_vec *tags,
>> +              bool match_bytes, bool match_calls, bool match_lineno,
>> +              bool match_function, bool match_filename)
>> +{
>> +    __u64 i;
>> +
>> +    if (procfs_entries->count != tags->count) {
>> +        ksft_print_msg("Entry count mismatch. ioctl entries: %llu, 
>> proc entries: %llu\n",
>> +                   tags->count, procfs_entries->count);
>> +        return false;
>> +    }
>> +    for (i = 0; i < procfs_entries->count; i++) {
>> +        if (!match_entry(&procfs_entries->tag[i], &tags->tag[i],
>> +                 match_bytes, match_calls, match_lineno,
>> +                 match_function, match_filename)) {
>> +            ksft_print_msg("%lluth entry does not match.\n", i);
>> +            return false;
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec 
>> *procfs_entries,
>> +                       const struct allocinfo_filter *filter, int fd)
>> +{
>> +    FILE *fp = fdopen(fd, "r");
>> +    char line[MAX_LINE_LEN];
>> +    int matches;
>> +    struct allocinfo_tag_data procfs_entry;
>> +
>> +    if (!fp) {
>> +        ksft_print_msg("Failed to open " ALLOCINFO_PROC " for 
>> reading\n");
>> +        return 1;
>> +    }
>> +    memset(procfs_entries, 0, sizeof(*procfs_entries));
>> +    while (fgets(line, sizeof(line), fp) && procfs_entries->count < 
>> VEC_MAX_ENTRIES) {
>> +
>> +        memset(&procfs_entry, 0, sizeof(procfs_entry));
>> +        matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s",
>> +                 &procfs_entry.counter.bytes,
>> +                 &procfs_entry.counter.calls,
>> +                 procfs_entry.tag.filename,
>> +                 &procfs_entry.tag.lineno,
>> +                 procfs_entry.tag.function);
>> +
>> +        if (matches != 5)
>> +            continue;
>> +
>> +        if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) {
>> +            if (strncmp(procfs_entry.tag.filename,
>> +                    filter->fields.filename, ALLOCINFO_STR_SIZE))
>> +                continue;
>> +        }
>> +        if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) {
>> +            if (strncmp(procfs_entry.tag.function,
>> +                    filter->fields.function, ALLOCINFO_STR_SIZE))
>> +                continue;
>> +        }
>> +        if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) {
>> +            if (procfs_entry.tag.lineno != filter->fields.lineno)
>> +                continue;
>> +        }
>> +        if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) {
>> +            if (procfs_entry.counter.bytes < filter->min_size)
>> +                continue;
>> +        }
>> +        if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) {
>> +            if (procfs_entry.counter.bytes > filter->max_size)
>> +                continue;
>> +        }
>> +
>> + memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry,
>> +               sizeof(procfs_entry));
>> +    }
>> +    return 0;
>> +}
>> +
>> +static enum ioctl_ret get_filtered_ioctl_entries(struct 
>> allocinfo_tag_data_vec *tags,
>> +                         const struct allocinfo_filter *filter, int fd,
>> +                         __u64 start_pos)
>> +{
>> +    struct allocinfo_content_id start_cont_id, end_cont_id;
>> +    struct allocinfo_get_at get_at_params;
>> +    const int max_retries = 10;
>> +    int retry_count = 0;
>> +    int status;
>> +
>> +    /*
>> +     * __allocinfo_get_content_id may return different values if a 
>> kernel module was loaded
>> +     * between the two calls. If that happens, the data gathered 
>> cannot be considered consistent
>> +     * and hence needs to be fetched again to avoid flakiness.
>> +     */
>> +    do {
>> +        if (__allocinfo_get_content_id(fd, &start_cont_id)) {
>> +            ksft_print_msg("allocinfo_get_content_id failed\n");
>> +            return IOCTL_FAILURE;
>> +        }
>> +
>> +        memset(tags, 0, sizeof(*tags));
>> +        memset(&get_at_params, 0, sizeof(get_at_params));
>> +        memcpy(&get_at_params.filter, filter, sizeof(*filter));
>> +        get_at_params.pos = start_pos;
>> +        if (__allocinfo_get_at(fd, &get_at_params)) {
>> +            ksft_print_msg("allocinfo_get_at failed\n");
>> +            return IOCTL_FAILURE;
>> +        }
>> +        memcpy(&tags->tag[tags->count++], &get_at_params.data, 
>> sizeof(get_at_params.data));
>> +
>> +        while (tags->count < VEC_MAX_ENTRIES &&
>> +               __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
>> +            tags->count++;
>> +
>> +        if (__allocinfo_get_content_id(fd, &end_cont_id)) {
>> +            ksft_print_msg("allocinfo_get_content_id failed\n");
>> +            return IOCTL_FAILURE;
>> +        }
>> +
>> +        if (start_cont_id.id == end_cont_id.id) {
>> +            status = IOCTL_SUCCESS;
>> +        } else {
>> +            ksft_print_msg("allocinfo_get_content_id mismatch, 
>> retrying...\n");
>> +            status = IOCTL_INVALID_DATA;
>> +        }
>> +    } while (status == IOCTL_INVALID_DATA && retry_count++ < 
>> max_retries);
>> +
>> +    return status;
>> +}
>> +
>> +static int run_filter_test(const struct allocinfo_filter *filter)
>> +{
>> +    int fd;
>> +    struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
>> +    struct allocinfo_tag_data_vec *procfs_entries = 
>> malloc(sizeof(*procfs_entries));
>> +    int ioctl_status;
>> +    int ret = KSFT_PASS;
>> +
>> +    if (!tags || !procfs_entries) {
>> +        ksft_print_msg("Memory allocation failed.\n");
>> +        ret = KSFT_FAIL;
>> +        goto freemem;
>> +    }
>> +
>> +    fd = open(ALLOCINFO_PROC, O_RDONLY);
>> +    if (fd < 0) {
>> +        ksft_exit_skip("Failed to open " ALLOCINFO_PROC ": %s\n", 
>> strerror(errno));
>> +        ret = KSFT_FAIL;
>> +        goto freemem;
>> +    }
>> +
>> +    if (get_filtered_procfs_entries(procfs_entries, filter, fd)) {
>> +        ksft_print_msg("Error retrieving entries from " 
>> ALLOCINFO_PROC "\n");
>> +        ret = KSFT_FAIL;
>> +        goto exit;
>> +    }
>> +
>> +    if (procfs_entries->count == 0) {
>> +        ksft_print_msg("No entries found in " ALLOCINFO_PROC ", 
>> skipping test\n");
>> +        ret = KSFT_SKIP;
>> +        goto exit;
>> +    }
>> +
>> +    ioctl_status = get_filtered_ioctl_entries(tags, filter, fd, 0);
>> +    if (ioctl_status == IOCTL_INVALID_DATA) {
>> +        ksft_print_msg("Trouble retrieving valid IOCTL entries, 
>> skipping.\n");
>> +        ret = KSFT_SKIP;
>> +        goto exit;
>> +    }
>> +    if (ioctl_status == IOCTL_FAILURE) {
>> +        ksft_print_msg("Error retrieving IOCTL entries.\n");
>> +        ret = KSFT_FAIL;
>> +        goto exit;
>> +    }
>> +
>> +    if (!match_entries(procfs_entries, tags, false, false, true, 
>> true, true))
>> +        ret = KSFT_FAIL;
>> +
>> +exit:
>> +    close(fd);
>> +freemem:
>> +    free(tags);
>> +    free(procfs_entries);
>> +    return ret;
>> +}
>> +
>> +static int test_filename_filter(void)
>> +{
>> +    struct allocinfo_filter filter;
>> +    const char *target_filename = "mm/memory.c";
>> +
>> +    memset(&filter, 0, sizeof(filter));
>> +    filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME;
>> +    strncpy(filter.fields.filename, target_filename, 
>> ALLOCINFO_STR_SIZE);
>> +
>> +    return run_filter_test(&filter);
>> +}
>> +
>> +static int test_function_filter(void)
>> +{
>> +    struct allocinfo_filter filter;
>> +    const char *target_function = "dup_mm";
>> +
>> +    memset(&filter, 0, sizeof(filter));
>> +    filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
>> +    strncpy(filter.fields.function, target_function, 
>> ALLOCINFO_STR_SIZE);
>> +
>> +    return run_filter_test(&filter);
>> +}
>> +
>> +int main(int argc, char *argv[])
>> +{
>> +    int ret;
>> +
>> +    ksft_set_plan(2);
>> +
>> +    ret = test_filename_filter();
>> +    if (ret == KSFT_SKIP)
>> +        ksft_test_result_skip("Skipping test_filename_filter\n");
>> +    else
>> +        ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n");
>> +
>> +    ret = test_function_filter();
>> +    if (ret == KSFT_SKIP)
>> +        ksft_test_result_skip("Skipping test_function_filter\n");
>> +    else
>> +        ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
>> +
>> +    ksft_finished();
>> +}

^ permalink raw reply

* Re: [PATCH v3 3/4] cpufreq: Remove driver default policy->min/max init
From: Pierre Gondois @ 2026-06-09  6:52 UTC (permalink / raw)
  To: Rafael J. Wysocki
  Cc: linux-kernel, Jie Zhan, Lifeng Zheng, Ionela Voinescu,
	Sumit Gupta, Zhongqiu Han, Viresh Kumar, Jonathan Corbet,
	Shuah Khan, Huang Rui, Mario Limonciello, Perry Yuan,
	K Prateek Nayak, Srinivas Pandruvada, Len Brown, Saravana Kannan,
	linux-pm, linux-doc
In-Reply-To: <CAJZ5v0hqjdd79J-Hi=mSLMm2Fxhia+xa6iguJwZy2-pRBSJ6gA@mail.gmail.com>

Hello Rafael,

On 6/8/26 18:50, Rafael J. Wysocki wrote:
> Hi,
>
> On Wed, Jun 3, 2026 at 9:49 AM Pierre Gondois <pierre.gondois@arm.com> wrote:
>> Hello Rafael,
>>
>> On 6/1/26 20:08, Rafael J. Wysocki wrote:
>>> On Thu, May 28, 2026 at 11:10 AM Pierre Gondois <pierre.gondois@arm.com> wrote:
>>>> Prior to [1], drivers were setting policy->min/max and
>>>> the value was used as a QoS constraint. After that change,
>>>> the values were only temporarily used: cpufreq_set_policy()
>>>> ultimately overriding them through:
>>>> cpufreq_policy_online()
>>>> \-cpufreq_init_policy()
>>>>     \-cpufreq_set_policy()
>>>>       \-/* Set policy->min/max */
>>>>
>>>> This patch reinstate the initial behaviour. This will allow
>>>> drivers to request min/max QoS frequencies if desired.
>>>> For instance, the cppc driver advertises a lowest non-linear
>>>> frequency, which should be used as a min QoS value.
>>>>
>>>> To avoid having drivers setting policy->min/max to default
>>>> values which are considered as QoS values (i.e. the reason
>>>> why [1] was introduced), remove the initialization of
>>>> policy->min/max in .init() callbacks wherever the
>>>> policy->min/max values are identical to the
>>>> policy->cpuinfo.min/max_freq.
>>>>
>>>> Indeed, the previous patch ("cpufreq: Set default
>>>> policy->min/max values for all drivers") makes this initialization
>>>> redundant.
>>>>
>>>> The only drivers where these values are different are:
>>>> - gx-suspmod.c (min)
>>>> - cppc-cpufreq.c (min)
>>>> - longrun.c
>>>>
>>>> [1]
>>>> commit 521223d8b3ec ("cpufreq: Fix initialization of min and
>>>> max frequency QoS requests")
>>>>
>>>> Signed-off-by: Pierre Gondois <pierre.gondois@arm.com>
>>>> Acked-by: Jie Zhan <zhanjie9@hisilicon.com>
>>> sashiko.dev has some feedback on this patch and appears to have a point:
>>>
>>> https://sashiko.dev/#/patchset/20260528090913.2759118-1-pierre.gondois%40arm.com
>>>
>>> Can you have a look at it please?
>>>
>> [sashiko]
>>
>>   > Does removing the policy->max = max_freq assignment here break UAPI
>>   > expectations by exposing the unlisted boost frequency in
>> scaling_max_freq?
>>   >
>>   > Commit 538b0188da4653 intentionally allowed drivers like acpi-cpufreq
>> to set
>>   > policy->cpuinfo.max_freq to a higher boost frequency while relying on
>>   > cpufreq_frequency_table_cpuinfo() to clamp policy->max to the frequency
>>   > table's nominal maximum (max_freq). This ensured that user-space
>> tools saw
>>   > the nominal maximum in scaling_max_freq.
>>   >
>>   > Although commit 521223d8b3ec temporarily disrupted this by defaulting
>> the QoS
>>   > max to -1, a subsequent patch in this series changes the core to
>> initialize
>>   > the QoS request using policy->max.
>>
>> Effectively PATCH [4/4] cpufreq: Use policy->min/max init as QoS request
>> now uses the policy->max value set by the .init() callback to set
>> the max_freq_req QoS constraint.
>>
>>   >
>>   > If the policy->max = max_freq assignment were preserved, the subsequent
>>   > patch would successfully use the nominal frequency as the QoS max
>> request,
>>   > restoring the correct clamping behavior.
>>
>> IIUC this suggests to use the nominal freq. as the QoS max request.
>> This was behaving like that prior to 521223d8b3ec. However doing
>> that would mean that if boost is enabled and the max_freq_req sysfs
>> is not updated, then the frequency would still be clamped by
>> the max_freq_req. 521223d8b3ec intended to correct that.
>>
>> Sashiko seems to suggest modifications to come back to the
>> pre-521223d8b3ec behaviour, but I think 521223d8b3ec is correct
>> and we should conserve this behaviour.
> So there is some confusion in the patch changelogs of this series, but
> not in the code, regarding the role of the last argument of
> freq_qos_add_request().  Namely, that argument is the initial request
> value for the given request object which is subsequently managed by
> user space.  User space may in fact change it to whatever value it
> wants (either lower or higher) and it is only taken into account along
> with the other requests in the given chain.  IMV it is better to
> clarify that, so I have updated the changelogs when applying the
> patches.
>
> Please see
>
> https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git/commit/?h=bleeding-edge&id=8c83947c5dbbd49b36d08bb99e344327c6278781
>
> and its ancestors and let me know if there's anything missing in the
> changelogs thereof.

The new commit message is indeed clearer.
Thanks for the update.

Regards,

Pierre


> Thanks!

^ permalink raw reply

* [PATCH v2] docs: Fix minor grammatical error
From: Brigham Campbell @ 2026-06-09  7:06 UTC (permalink / raw)
  To: Thorsten Leemhuis, Jonathan Corbet, Shuah Khan,
	open list:DOCUMENTATION REPORTING ISSUES, open list
  Cc: Brigham Campbell

Fix minor grammatical error in the administration guide.

Signed-off-by: Brigham Campbell <me@brighamcampbell.com>
---

Since v1:
* Drop pedantic line re-wrapping.

In hindsight, I should have guessed that reflowing the paragraph was
overzealous. Thanks for the guidance, Randy, Thorsten. I'll remember it
if I make minor doc fixes in the future.

 Documentation/admin-guide/quickly-build-trimmed-linux.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/quickly-build-trimmed-linux.rst b/Documentation/admin-guide/quickly-build-trimmed-linux.rst
index cb178e0a6208..3432dc8e1a85 100644
--- a/Documentation/admin-guide/quickly-build-trimmed-linux.rst
+++ b/Documentation/admin-guide/quickly-build-trimmed-linux.rst
@@ -217,7 +217,7 @@ again.
 
    There is a catch: 'localmodconfig' is likely to disable kernel features you
    did not use since you booted your Linux -- like drivers for currently
-   disconnected peripherals or a virtualization software not haven't used yet.
+   disconnected peripherals or virtualization software not currently in use.
    You can reduce or nearly eliminate that risk with tricks the reference
    section outlines; but when building a kernel just for quick testing purposes
    it is often negligible if such features are missing. But you should keep that

base-commit: 738bb6e6c8d992f33335b3cbcce051ab118a33dc
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v8 2/6] mm/memory-failure: surface unhandlable kernel pages as -ENOTRECOVERABLE
From: David Hildenbrand (Arm) @ 2026-06-09  7:09 UTC (permalink / raw)
  To: Miaohe Lin, Breno Leitao
  Cc: linux-mm, linux-kernel, linux-doc, linux-kselftest,
	linux-trace-kernel, kernel-team, Lance Yang, Andrew Morton,
	Lorenzo Stoakes, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Shuah Khan, Naoya Horiguchi,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Jonathan Corbet, Shuah Khan, Liam R. Howlett
In-Reply-To: <4953bcee-5a0f-2bc5-7295-63e5e7513e8b@huawei.com>

On 6/9/26 04:39, Miaohe Lin wrote:
> On 2026/6/8 22:15, Breno Leitao wrote:
>> On Fri, Jun 05, 2026 at 11:42:53AM +0200, David Hildenbrand (Arm) wrote:
>>>
>>> I mean, any such races can currently already happen one way or the other?
>>>
>>> Really, the only way to not get races is to tryget the (compound)page,
>>> revalidate that the page is still part of the compound page.
>>>
>>> I'm not sure if that's really a good idea.
>>>
>>> But my memory is a bit vague in which scenarios we already hold a page reference
>>> here to prevent any concurrent freeing?
>>
>> No, we don't hold one here in the case that matters.
>>
>> HWPoisonKernelOwned() runs at the very top of get_any_page(), before
>> try_again: and before __get_hwpoison_page(). The first refcount taken in
>> the whole path is the folio_try_get() inside __get_hwpoison_page(), which
>> runs *after* the short-circuit.
>>
>> So get_any_page() itself never holds a reference at the check -- the only way
>> one exists is if the caller passed MF_COUNT_INCREASED (count_increased ==
>> true).
>>
>> So on the MCE/GHES path -- the one this panic option exists for -- no
>> reference is held when HWPoisonKernelOwned() does its compound_head() +
>> PageSlab()/PageTable()/PageLargeKmalloc() checks.
>>
>> Given that, I'd rather keep it racy and take no refcount than add a
>> tryget + revalidate purely for this check. As I've said earleir, an operator
> 
> Would it be acceptable to add a simple recheck? Something like below:
> 
> retry:
> head = compound_head(page);
> PageSlab()/PageTable()/PageLargeKmalloc() checks
> if (head != compound_head(page))
> 	goto retry

Sure. I guess it could still be racy in some weird scenarios where we
free+allocate+free in-between.

-- 
Cheers,

David

^ permalink raw reply

* Re: [PATCH 4/4] block: add configurable error injection
From: Christoph Hellwig @ 2026-06-09  7:41 UTC (permalink / raw)
  To: Jens Axboe
  Cc: Christoph Hellwig, Jonathan Corbet, Damien Le Moal,
	Hannes Reinecke, Keith Busch, linux-block, linux-doc,
	Hannes Reinecke
In-Reply-To: <bac50400-dd86-4c7f-bab3-481c1306877b@kernel.dk>

On Mon, Jun 08, 2026 at 08:53:22AM -0600, Jens Axboe wrote:
> > +	if (!test_bit(GD_ERROR_INJECT, &bio->bi_bdev->bd_disk->state))
> > +		return false;
> > +	return __blk_error_inject(bio);
> > +}
> 
> I really hate this part, that's a pretty deep set of pointer chasings to
> figure out if injection is enabled or not,

It's to the bdev we use everywhere, and then to the disk which we use
in a lot of places in the submission path.

The only easy way to reduce it would be to move the state to the
block_device.  We currently don't do partitions in debugfs, but maybe
we should?

> when in practice error
> injection is only ever enabled for specific test cases and distros
> invariably will set CONFIG_BLK_ERROR_INJECTION because they turn on
> every damn thing under the sun.
> 
> IOW, that won't fly for the hot path. Maybe a static key would be useful
> here?

a static_key makes sense here, probably including the legacy error
injection.


^ permalink raw reply

* Re: [PATCH 2/4] block: add a "tag" for block status codes
From: Christoph Hellwig @ 2026-06-09  7:43 UTC (permalink / raw)
  To: Bart Van Assche
  Cc: Christoph Hellwig, Jens Axboe, Jonathan Corbet, Damien Le Moal,
	Hannes Reinecke, Keith Busch, linux-block, linux-doc,
	Hannes Reinecke
In-Reply-To: <f4c0895b-4758-4eb1-9c3a-38cda0db50d2@acm.org>

On Mon, Jun 08, 2026 at 02:55:20PM -0700, Bart Van Assche wrote:
> On 6/7/26 10:14 PM, Christoph Hellwig wrote:
>> +const char *blk_status_to_tag(blk_status_t status)
>> +{
>> +	int idx = (__force int)status;
>> +
>> +	if (WARN_ON_ONCE(idx >= ARRAY_SIZE(blk_errors)))
>> +		return "<null>";
>> +	return blk_errors[idx].tag;
>> +}
>
> Since designated initializers are used to initialize blk_errors[], it's
> probably a good idea to check the value of blk_errors[idx].tag, e.g. as
> follows:
>
> return blk_errors[idx].tag ?: "<null>";

I'd go for the good old and readable if statement, but yes, I can add
extra error checking here.


^ permalink raw reply

* Re: [PATCH 3/4] block: add a str_to_blk_op helper
From: Christoph Hellwig @ 2026-06-09  7:45 UTC (permalink / raw)
  To: Bart Van Assche
  Cc: Christoph Hellwig, Jens Axboe, Jonathan Corbet, Damien Le Moal,
	Hannes Reinecke, Keith Busch, linux-block, linux-doc,
	Hannes Reinecke
In-Reply-To: <5e738f79-e1d9-4224-ae85-322967682a1a@acm.org>

On Mon, Jun 08, 2026 at 02:57:40PM -0700, Bart Van Assche wrote:
> On 6/7/26 10:14 PM, Christoph Hellwig wrote:
>> +enum req_op str_to_blk_op(const char *op)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(blk_op_name); i++)
>> +		if (blk_op_name[i] && !strcmp(blk_op_name[i], op))
>> +			return (enum req_op)i;
>> +	return REQ_OP_LAST;
>> +}
> The above function is similar but not identical to
> __sysfs_match_string(). Is __sysfs_match_string() good enough in this
> context?

__sysfs_match_string exists as soon as an array entry is NULL, but
blk_status values are not fully contiguous, so no.


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox