* [PATCH 0/2] spi: add sysfs interface for userspace device instantiation
@ 2026-04-20 3:14 Vishwaroop A
2026-04-20 3:14 ` [PATCH 1/2] spi: add new_device/delete_device sysfs interface Vishwaroop A
2026-04-20 3:14 ` [PATCH 2/2] docs: spi: add documentation for userspace device instantiation Vishwaroop A
0 siblings, 2 replies; 3+ messages in thread
From: Vishwaroop A @ 2026-04-20 3:14 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Thierry Reding, Jonathan Hunter,
smangipudi, va
Development boards such as the Jetson AGX Orin expose SPI buses on
expansion headers so that users can connect and interact with SPI
peripherals from userspace via /dev/spidevB.C character devices.
Today there is no viable upstream mechanism to create these device nodes:
- The spidev driver rejects the bare "spidev" compatible string in DT,
since spidev is a Linux software interface, not a hardware description.
- Vendor-specific compatible strings (e.g. "nvidia,tegra-spidev") have
been rejected by DT maintainers for the same reason.
The I2C subsystem solved an analogous problem years ago by exposing
new_device/delete_device sysfs attributes on each i2c adapter. This
series adds the same interface to SPI host controllers.
Patch 1 adds the core implementation: new_device and delete_device sysfs
attributes under /sys/class/spi_master/spiB/, allowing userspace to
dynamically instantiate and remove SPI devices at runtime.
Patch 2 adds documentation: an RST guide describing usage, parameters,
examples, and limitations, plus a formal ABI entry.
Link: https://lore.kernel.org/linux-tegra/909f0c92-d110-4253-903e-5c81e21e12c9@nvidia.com/
Vishwaroop A (2):
spi: add new_device/delete_device sysfs interface
docs: spi: add documentation for userspace device instantiation
.../ABI/testing/sysfs-class-spi-master | 34 ++++
Documentation/spi/index.rst | 1 +
Documentation/spi/instantiating-devices.rst | 88 +++++++++
drivers/spi/spi.c | 172 ++++++++++++++++++
include/linux/spi/spi.h | 10 +
5 files changed, 305 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-class-spi-master
create mode 100644 Documentation/spi/instantiating-devices.rst
--
2.17.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/2] spi: add new_device/delete_device sysfs interface
2026-04-20 3:14 [PATCH 0/2] spi: add sysfs interface for userspace device instantiation Vishwaroop A
@ 2026-04-20 3:14 ` Vishwaroop A
2026-04-20 3:14 ` [PATCH 2/2] docs: spi: add documentation for userspace device instantiation Vishwaroop A
1 sibling, 0 replies; 3+ messages in thread
From: Vishwaroop A @ 2026-04-20 3:14 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Thierry Reding, Jonathan Hunter,
smangipudi, va
Development boards such as the Jetson AGX Orin expose SPI buses
on expansion headers (e.g. the 40-pin header) so that users can
connect and interact with SPI peripherals from userspace. The
standard way to get /dev/spidevB.C character device nodes for
this purpose is to register spi_device instances backed by the
spidev driver.
Today there is no viable way to do this on upstream kernels:
- The spidev driver rejects the bare "spidev" compatible
string in DT, since spidev is a Linux software interface
and not a description of real hardware.
- Vendor-specific compatible strings (e.g. "nvidia,tegra-spidev")
have been rejected by DT maintainers for the same reason.
The I2C subsystem solved an analogous problem by exposing
new_device/delete_device sysfs attributes on each adapter. Add
the same interface to SPI host controllers, so that userspace
(e.g. a systemd unit at boot) can instantiate SPI devices at
runtime without needing anything in device-tree.
The new_device file accepts:
<modalias> <chip_select> [<max_speed_hz> [<mode>]]
where chip_select is required, while max_speed_hz and mode are
optional and default to 0 if omitted. max_speed_hz == 0 is
clamped to the controller's maximum by spi_setup(); mode == 0
selects SPI mode 0 (CPOL=0, CPHA=0).
The modalias is used both as the device identifier and as a
driver_override, so that the device binds to the named driver
directly. This is necessary because some drivers like spidev
deliberately exclude generic names from their id_table.
Devices created this way are limited compared to those declared
via DT or board files:
- No IRQ is assigned (the device gets IRQ 0 / no interrupt).
- No platform_data or device properties are attached.
- No OF node is associated with the device.
These limitations are acceptable for spidev, which only needs a
registered spi_device to expose a character device to userspace.
Only devices created via new_device can be removed through
delete_device; DT and platform devices are unaffected.
Link: https://lore.kernel.org/linux-tegra/909f0c92-d110-4253-903e-5c81e21e12c9@nvidia.com/
Signed-off-by: Vishwaroop A <va@nvidia.com>
---
drivers/spi/spi.c | 172 ++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 10 +++
2 files changed, 182 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 7001f5dce8bd..b0c66f7b3ea0 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -296,8 +296,160 @@ static const struct attribute_group spi_controller_statistics_group = {
.attrs = spi_controller_statistics_attrs,
};
+/*
+ * new_device_store - instantiate a new SPI device from userspace
+ *
+ * Takes parameters: <modalias> <chip_select> [<max_speed_hz> [<mode>]]
+ *
+ * Examples:
+ * echo spidev 0 > new_device
+ * echo spidev 0 10000000 > new_device
+ * echo spidev 0 10000000 3 > new_device
+ */
+static ssize_t
+new_device_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct spi_controller *ctlr = container_of(dev, struct spi_controller,
+ dev);
+ struct spi_device *spi;
+ char modalias[SPI_NAME_SIZE];
+ u16 chip_select;
+ u32 max_speed_hz = 0;
+ u32 mode = 0;
+ char *blank;
+ int n, res, status;
+
+ blank = strchr(buf, ' ');
+ if (!blank) {
+ dev_err(dev, "%s: Missing parameters\n", "new_device");
+ return -EINVAL;
+ }
+
+ if (blank - buf > SPI_NAME_SIZE - 1) {
+ dev_err(dev, "%s: Invalid device name\n", "new_device");
+ return -EINVAL;
+ }
+
+ memset(modalias, 0, sizeof(modalias));
+ memcpy(modalias, buf, blank - buf);
+
+ /*
+ * sscanf fills only the fields it matches; unmatched optional
+ * fields (max_speed_hz, mode) stay zero from initialisation above.
+ * max_speed_hz == 0 is clamped to the controller max by spi_setup().
+ * mode == 0 selects SPI mode 0 (CPOL=0, CPHA=0).
+ */
+ res = sscanf(++blank, "%hu %u %u%n",
+ &chip_select, &max_speed_hz, &mode, &n);
+ if (res < 1) {
+ dev_err(dev, "%s: Can't parse chip select\n", "new_device");
+ return -EINVAL;
+ }
+
+ if (chip_select >= ctlr->num_chipselect) {
+ dev_err(dev, "%s: Chip select %u >= max %u\n", "new_device",
+ chip_select, ctlr->num_chipselect);
+ return -EINVAL;
+ }
+
+ spi = spi_alloc_device(ctlr);
+ if (!spi)
+ return -ENOMEM;
+
+ spi_set_chipselect(spi, 0, chip_select);
+ spi->max_speed_hz = max_speed_hz;
+ spi->mode = mode;
+ spi->cs_index_mask = BIT(0);
+ strscpy(spi->modalias, modalias, sizeof(spi->modalias));
+
+ /*
+ * Set driver_override so that the device binds to the driver
+ * named by modalias regardless of whether that driver's
+ * id_table contains a matching entry. This is needed because
+ * some drivers (e.g. spidev) deliberately omit generic names
+ * from their id_table.
+ */
+ status = device_set_driver_override(&spi->dev, modalias);
+ if (status) {
+ spi_dev_put(spi);
+ return status;
+ }
+
+ status = spi_add_device(spi);
+ if (status) {
+ spi_dev_put(spi);
+ return status;
+ }
+
+ mutex_lock(&ctlr->userspace_clients_lock);
+ list_add_tail(&spi->userspace_node, &ctlr->userspace_clients);
+ mutex_unlock(&ctlr->userspace_clients_lock);
+ dev_info(dev, "%s: Instantiated device %s at CS%u\n", "new_device",
+ modalias, chip_select);
+
+ return count;
+}
+static DEVICE_ATTR_WO(new_device);
+
+static ssize_t
+delete_device_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct spi_controller *ctlr = container_of(dev, struct spi_controller,
+ dev);
+ struct spi_device *spi, *next;
+ unsigned short cs;
+ char end;
+ int res;
+
+ res = sscanf(buf, "%hu%c", &cs, &end);
+ if (res < 1) {
+ dev_err(dev, "%s: Can't parse chip select\n", "delete_device");
+ return -EINVAL;
+ }
+ if (res > 1 && end != '\n') {
+ dev_err(dev, "%s: Extra parameters\n", "delete_device");
+ return -EINVAL;
+ }
+
+ res = -ENOENT;
+ mutex_lock(&ctlr->userspace_clients_lock);
+ list_for_each_entry_safe(spi, next, &ctlr->userspace_clients,
+ userspace_node) {
+ if (spi_get_chipselect(spi, 0) == cs) {
+ dev_info(dev, "%s: Deleting device %s at CS%u\n",
+ "delete_device", spi->modalias, cs);
+
+ list_del(&spi->userspace_node);
+ spi_unregister_device(spi);
+ res = count;
+ break;
+ }
+ }
+ mutex_unlock(&ctlr->userspace_clients_lock);
+
+ if (res < 0)
+ dev_err(dev, "%s: Can't find device in list\n",
+ "delete_device");
+ return res;
+}
+static DEVICE_ATTR_IGNORE_LOCKDEP(delete_device, 0200, NULL,
+ delete_device_store);
+
+static struct attribute *spi_controller_userspace_attrs[] = {
+ &dev_attr_new_device.attr,
+ &dev_attr_delete_device.attr,
+ NULL,
+};
+
+static const struct attribute_group spi_controller_userspace_group = {
+ .attrs = spi_controller_userspace_attrs,
+};
+
static const struct attribute_group *spi_controller_groups[] = {
&spi_controller_statistics_group,
+ &spi_controller_userspace_group,
NULL,
};
@@ -3256,6 +3408,8 @@ struct spi_controller *__spi_alloc_controller(struct device *dev,
mutex_init(&ctlr->bus_lock_mutex);
mutex_init(&ctlr->io_mutex);
mutex_init(&ctlr->add_lock);
+ mutex_init(&ctlr->userspace_clients_lock);
+ INIT_LIST_HEAD(&ctlr->userspace_clients);
ctlr->bus_num = -1;
ctlr->num_chipselect = 1;
ctlr->num_data_lanes = 1;
@@ -3633,6 +3787,24 @@ void spi_unregister_controller(struct spi_controller *ctlr)
if (IS_ENABLED(CONFIG_SPI_DYNAMIC))
mutex_lock(&ctlr->add_lock);
+ /*
+ * Remove devices created via the sysfs new_device interface.
+ * These must be explicitly removed before device_for_each_child()
+ * below because spi_unregister_device() does not remove devices
+ * from the userspace_clients list; freeing them without list_del()
+ * first would leave dangling pointers in that list.
+ */
+ mutex_lock(&ctlr->userspace_clients_lock);
+ while (!list_empty(&ctlr->userspace_clients)) {
+ struct spi_device *spi;
+
+ spi = list_first_entry(&ctlr->userspace_clients,
+ struct spi_device, userspace_node);
+ list_del(&spi->userspace_node);
+ spi_unregister_device(spi);
+ }
+ mutex_unlock(&ctlr->userspace_clients_lock);
+
device_for_each_child(&ctlr->dev, NULL, __unregister);
/* First make sure that this controller was ever added */
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 7587b1c5d7ec..63c267ca9730 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -250,6 +250,9 @@ struct spi_device {
u8 rx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX];
u8 num_rx_lanes;
+ /* Entry on controller's userspace_clients list */
+ struct list_head userspace_node;
+
/*
* Likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
@@ -554,6 +557,9 @@ extern struct spi_device *devm_spi_new_ancillary_device(struct spi_device *spi,
* @defer_optimize_message: set to true if controller cannot pre-optimize messages
* and needs to defer the optimization step until the message is actually
* being transferred
+ * @userspace_clients: list of SPI devices instantiated from userspace via
+ * the sysfs new_device interface
+ * @userspace_clients_lock: mutex protecting @userspace_clients
*
* Each SPI controller can communicate with one or more @spi_device
* children. These make a small bus, sharing MOSI, MISO and SCK signals
@@ -809,6 +815,10 @@ struct spi_controller {
bool queue_empty;
bool must_async;
bool defer_optimize_message;
+
+ /* List of SPI devices created via sysfs new_device interface */
+ struct list_head userspace_clients;
+ struct mutex userspace_clients_lock;
};
static inline void *spi_controller_get_devdata(struct spi_controller *ctlr)
--
2.17.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 2/2] docs: spi: add documentation for userspace device instantiation
2026-04-20 3:14 [PATCH 0/2] spi: add sysfs interface for userspace device instantiation Vishwaroop A
2026-04-20 3:14 ` [PATCH 1/2] spi: add new_device/delete_device sysfs interface Vishwaroop A
@ 2026-04-20 3:14 ` Vishwaroop A
1 sibling, 0 replies; 3+ messages in thread
From: Vishwaroop A @ 2026-04-20 3:14 UTC (permalink / raw)
To: Mark Brown
Cc: linux-spi, linux-kernel, Thierry Reding, Jonathan Hunter,
smangipudi, va
Document the new_device and delete_device sysfs attributes on SPI
controllers:
- Documentation/spi/instantiating-devices.rst: describes when and
why this interface is needed, accepted parameters, usage examples,
and limitations.
- Documentation/ABI/testing/sysfs-class-spi-master: formal ABI
entry for both attributes.
Signed-off-by: Vishwaroop A <va@nvidia.com>
---
.../ABI/testing/sysfs-class-spi-master | 34 +++++++
Documentation/spi/index.rst | 1 +
Documentation/spi/instantiating-devices.rst | 88 +++++++++++++++++++
3 files changed, 123 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-class-spi-master
create mode 100644 Documentation/spi/instantiating-devices.rst
diff --git a/Documentation/ABI/testing/sysfs-class-spi-master b/Documentation/ABI/testing/sysfs-class-spi-master
new file mode 100644
index 000000000000..b498be128bad
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-spi-master
@@ -0,0 +1,34 @@
+What: /sys/class/spi_master/spiB/new_device
+Date: April 2026
+KernelVersion: 7.2
+Contact: linux-spi@vger.kernel.org
+Description: (WO) Instantiate a new SPI device on bus B, where B
+ is the bus number (0, 1, 2, ...). Takes parameters
+ in the format:
+
+ <modalias> <chip_select> [<max_speed_hz> [<mode>]]
+
+ where modalias is the driver name, chip_select is the
+ CS line number, and max_speed_hz and mode are optional.
+
+ The device can later be removed with delete_device.
+
+ Only devices created via this interface can be removed
+ with delete_device; platform and DT devices are not
+ affected.
+
+ Example:
+ # echo spidev 0 > /sys/class/spi_master/spi0/new_device
+ # echo spidev 0 10000000 > /sys/class/spi_master/spi0/new_device
+ # echo spidev 0 10000000 3 > /sys/class/spi_master/spi0/new_device
+
+What: /sys/class/spi_master/spiB/delete_device
+Date: April 2026
+KernelVersion: 7.2
+Contact: linux-spi@vger.kernel.org
+Description: (WO) Remove a SPI device previously created via
+ new_device. Takes a single parameter: the chip select
+ number of the device to remove.
+
+ Example:
+ # echo 0 > /sys/class/spi_master/spi0/delete_device
diff --git a/Documentation/spi/index.rst b/Documentation/spi/index.rst
index ac0c2233ce48..3f723e2c07da 100644
--- a/Documentation/spi/index.rst
+++ b/Documentation/spi/index.rst
@@ -8,6 +8,7 @@ Serial Peripheral Interface (SPI)
:maxdepth: 1
spi-summary
+ instantiating-devices
spidev
multiple-data-lanes
butterfly
diff --git a/Documentation/spi/instantiating-devices.rst b/Documentation/spi/instantiating-devices.rst
new file mode 100644
index 000000000000..9ed08d94ae01
--- /dev/null
+++ b/Documentation/spi/instantiating-devices.rst
@@ -0,0 +1,88 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+==============================
+How to instantiate SPI devices
+==============================
+
+SPI devices are normally declared statically via device-tree, ACPI, or
+board files. When the SPI controller is registered, these devices are
+instantiated automatically by the SPI core. This is the preferred method
+for any device with a proper kernel driver.
+
+Instantiate from user-space
+---------------------------
+
+In certain cases a SPI device cannot be declared statically:
+
+* The ``spidev`` driver, which provides raw userspace access to SPI
+ buses, explicitly rejects the bare ``"spidev"`` compatible string in
+ device-tree because spidev is a Linux implementation detail, not a
+ hardware description. Vendor-specific compatible strings for spidev
+ (e.g. ``"vendor,board-spidev"``) are also generally not accepted
+ upstream. Device-tree overlays do not help here either, since the
+ spidev driver performs the same compatible check regardless of how
+ the DT node was loaded.
+
+* You are developing or testing a SPI device on a development board
+ where the SPI bus is exposed on expansion headers, and the connected
+ device may change frequently.
+
+For these cases, a sysfs interface is provided on each SPI controller
+(similar to the I2C ``new_device``/``delete_device`` interface described
+in Documentation/i2c/instantiating-devices.rst). Two write-only
+attribute files are created in every SPI controller directory:
+``new_device`` and ``delete_device``.
+
+File ``new_device`` takes 2 to 4 parameters: the name of the SPI
+device (a string), the chip select number, and optionally
+``max_speed_hz`` and ``mode``::
+
+ <modalias> <chip_select> [<max_speed_hz> [<mode>]]
+
+The modalias is set both as the device's ``modalias`` field and as its
+``driver_override``. This ensures that the device binds to the named
+driver directly, bypassing the normal bus matching logic (OF, ACPI,
+and ``id_table``). This is necessary because drivers like ``spidev``
+deliberately exclude generic names from their ``id_table``.
+
+If ``max_speed_hz`` is omitted or 0, ``spi_setup()`` clamps it to
+the controller's maximum speed. If ``mode`` is omitted, SPI mode 0
+(CPOL=0, CPHA=0) is used.
+
+File ``delete_device`` takes a single parameter: the chip select
+number. As no two devices can share a chip select on a given SPI bus,
+the chip select is sufficient to uniquely identify the device.
+
+Examples::
+
+ # Create a spidev device on SPI bus 0, chip select 0
+ echo spidev 0 > /sys/class/spi_master/spi0/new_device
+
+ # Create with explicit clock rate and SPI mode
+ echo spidev 0 10000000 3 > /sys/class/spi_master/spi0/new_device
+
+ # Remove the device
+ echo 0 > /sys/class/spi_master/spi0/delete_device
+
+On systems that need spidev access at boot, a systemd service or
+udev rule can write to ``new_device`` after the SPI controller is
+available.
+
+Limitations
+^^^^^^^^^^^
+
+Devices created through this interface have the following limitations
+compared to devices declared via device-tree:
+
+* No interrupt (IRQ) support.
+* No additional properties such as ``spi-max-frequency`` DT bindings
+ or controller-specific configuration.
+* No platform data or software nodes.
+
+For ``spidev`` usage these limitations are not relevant, since spidev
+provides a raw byte-level interface that does not require any of these
+features.
+
+Only devices created via ``new_device`` can be removed through
+``delete_device``. Devices declared via device-tree, ACPI, or board
+files are not affected by this interface.
--
2.17.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-04-20 3:14 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-20 3:14 [PATCH 0/2] spi: add sysfs interface for userspace device instantiation Vishwaroop A
2026-04-20 3:14 ` [PATCH 1/2] spi: add new_device/delete_device sysfs interface Vishwaroop A
2026-04-20 3:14 ` [PATCH 2/2] docs: spi: add documentation for userspace device instantiation Vishwaroop A
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox