From e0db3f62dbe6bd683a3533fff955165da1c143f7 Mon Sep 17 00:00:00 2001 From: Vishwaroop A Date: Mon, 3 Aug 2026 10:46:13 +0000 Subject: spi: add new_device/delete_device sysfs interface 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: [ []] 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. The sysfs attributes are gated behind CONFIG_SPI_DYNAMIC since this feature adds a new way of dynamically instantiating and removing SPI devices, and the add_lock locking in spi_unregister_controller() is already conditional on CONFIG_SPI_DYNAMIC. The userspace sysfs group is created manually as the last step of spi_register_controller() and removed as the first step of spi_unregister_controller(). Removing the group before taking add_lock means kernfs_drain() completes any in-flight new_device_store()/delete_device_store() calls before add_lock is acquired, so unregister never blocks on a store that is itself waiting for add_lock and no store can be touching an spi_device that is about to be torn down. Non-sysfs callers of __spi_add_device() (DT/ACPI dynamic add, ancillary registration) continue to be protected by the pre-existing !device_is_registered(&ctlr->dev) check added in commit ddf75be47ca7 ("spi: Prevent adding devices below an unregistering controller"): device_del(&ctlr->dev) runs inside add_lock in spi_unregister_controller() so state_in_sysfs flips to 0 before add_lock is released. Link: https://lore.kernel.org/linux-tegra/909f0c92-d110-4253-903e-5c81e21e12c9@nvidia.com/ Signed-off-by: Vishwaroop A Link: https://patch.msgid.link/20260803104614.2548375-2-va@nvidia.com Signed-off-by: Mark Brown --- include/linux/spi/spi.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'include/linux') diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 4c285d3ede1d..88d17fce02dc 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -179,6 +179,8 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg, * @num_tx_lanes: Number of transmit lanes wired up. * @rx_lane_map: Map of peripheral lanes (index) to controller lanes (value). * @num_rx_lanes: Number of receive lanes wired up. + * @userspace_node: entry on the parent controller's userspace_clients list + * when this device was instantiated via the sysfs new_device interface * * A @spi_device is used to interchange data between an SPI target device * (usually a discrete chip) and CPU memory. @@ -252,6 +254,10 @@ struct spi_device { u8 rx_lane_map[SPI_DEVICE_DATA_LANE_CNT_MAX]; u8 num_rx_lanes; +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + struct list_head userspace_node; +#endif + /* * Likely need more hooks for more protocol options affecting how * the controller talks to each chip, like: @@ -555,6 +561,11 @@ 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; protected by @add_lock + * @userspace_registered: true once the new_device/delete_device sysfs + * group has been added by spi_register_controller(); used by + * spi_unregister_controller() to know whether to remove it * * Each SPI controller can communicate with one or more @spi_device * children. These make a small bus, sharing MOSI, MISO and SCK signals @@ -807,6 +818,13 @@ struct spi_controller { bool queue_empty; bool must_async; bool defer_optimize_message; + +#if IS_ENABLED(CONFIG_SPI_DYNAMIC) + /* List of userspace-instantiated devices; protected by @add_lock */ + struct list_head userspace_clients; + /* True after new_device/delete_device sysfs group is created */ + bool userspace_registered; +#endif }; static inline void *spi_controller_get_devdata(struct spi_controller *ctlr) -- cgit v1.2.3