From f81808de37338aac8e167f99bfae647b1b835c70 Mon Sep 17 00:00:00 2001 From: Chun-Yi Lee Date: Mon, 15 Jun 2026 18:47:50 +0800 Subject: debugfs: Fix lockdown check for mmap_prepare Commit 651fdda8406d ("relay: update relay to use mmap_prepare") changed the `mmap` file operation to `mmap_prepare` for relayfs, but the lockdown check in debugfs was not updated accordingly. This prevents debugfs from being locked down when the kernel is in integrity mode if a file uses `mmap_prepare` but not `mmap`. Since the conversion to `mmap_prepare` across the kernel is not yet complete, update the lockdown check to look for both `mmap` and `mmap_prepare` to ensure comprehensive coverage. Fixes: 651fdda8406d ("relay: update relay to use mmap_prepare") Signed-off-by: Chun-Yi Lee Cc: David Howells Cc: Lorenzo Stoakes Cc: Andy Shevchenko Cc: Thomas Gleixner Cc: Rafael J. Wysocki Cc: Matthew Garrett Cc: Greg Kroah-Hartman Cc: Danilo Krummrich Cc: driver-core@lists.linux.dev Cc: linux-kernel@vger.kernel.org Cc: stable@vger.kernel.org Tested-by: Disha Goel Reviewed-by: Lorenzo Stoakes Link: https://patch.msgid.link/20260615104750.1000-1-jlee@suse.com Signed-off-by: Danilo Krummrich --- fs/debugfs/file.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c index edd6aafbfbaa..08de6652a4f3 100644 --- a/fs/debugfs/file.c +++ b/fs/debugfs/file.c @@ -273,7 +273,8 @@ static int debugfs_locked_down(struct inode *inode, (!real_fops || (!real_fops->unlocked_ioctl && !real_fops->compat_ioctl && - !real_fops->mmap))) + !real_fops->mmap && + !real_fops->mmap_prepare))) return 0; if (security_locked_down(LOCKDOWN_DEBUGFS)) -- cgit From e7e09a8546a9e41a164378979471a4b4bd166bc7 Mon Sep 17 00:00:00 2001 From: Zenghui Yu Date: Sun, 21 Jun 2026 15:52:39 +0800 Subject: driver core: fix path of /sys/module in comment The comment wrongly references to /sys/modules directory instead of /sys/module. Fix it. Signed-off-by: Zenghui Yu Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260621075239.11330-1-zenghui.yu@linux.dev Signed-off-by: Danilo Krummrich --- drivers/base/module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/module.c b/drivers/base/module.c index 218aaa096455..6789dca485ab 100644 --- a/drivers/base/module.c +++ b/drivers/base/module.c @@ -42,7 +42,7 @@ int module_add_driver(struct module *mod, const struct device_driver *drv) if (mod) mk = &mod->mkobj; else if (drv->mod_name) { - /* Lookup or create built-in module entry in /sys/modules */ + /* Lookup or create built-in module entry in /sys/module */ mk = lookup_or_create_module_kobject(drv->mod_name); if (mk) { /* remember our module structure */ -- cgit From 0fa8e47dea17dd7899201f73cdf3d8efcbf78448 Mon Sep 17 00:00:00 2001 From: Syed Nayyar Waris Date: Thu, 2 Jul 2026 02:28:57 +0530 Subject: isa: refactor code to remove nested blocks Remove nested blocks in isa_bus_init(). This will make the module easy-to-understand and make the code clearer. Previous discussion regarding this change can be found at below link: https://lore.kernel.org/all/20260504063518.515620-1-wbg@kernel.org/ Acked-by: William Breathitt Gray Suggested-by: William Breathitt Gray Signed-off-by: Syed Nayyar Waris Acked-by: Randy Dunlap Link: https://lore.kernel.org/all/20260504063518.515620-1-wbg@kernel.org/ Link: https://patch.msgid.link/20260701205857.GA4113@syednwaris Signed-off-by: Danilo Krummrich --- drivers/base/isa.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/base/isa.c b/drivers/base/isa.c index 5887e4211f80..4e9f68080f39 100644 --- a/drivers/base/isa.c +++ b/drivers/base/isa.c @@ -166,14 +166,16 @@ static int __init isa_bus_init(void) int error; error = bus_register(&isa_bus_type); - if (!error) { - isa_bus = root_device_register("isa"); - if (IS_ERR(isa_bus)) { - error = PTR_ERR(isa_bus); - bus_unregister(&isa_bus_type); - } + if (error) + return error; + + isa_bus = root_device_register("isa"); + if (IS_ERR(isa_bus)) { + bus_unregister(&isa_bus_type); + return PTR_ERR(isa_bus); } - return error; + + return 0; } postcore_initcall(isa_bus_init); -- cgit From 5dcef303b29f004a447d9c69e62963328da9c608 Mon Sep 17 00:00:00 2001 From: Link Mauve Date: Wed, 1 Jul 2026 18:03:54 +0200 Subject: rust: io: fix example in `register!` macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this example, an u32 register is split in two u8 in big-endian order, but the high byte is actually defined as taking nine bits instead of eight. This is completely inconsequential, as I expect most users will just copy the bit ranges from their datasheets, but it doesn’t hurt to fix that typo. Signed-off-by: Link Mauve Acked-by: Alexandre Courbot Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260701160357.29031-1-linkmauve@linkmauve.fr Signed-off-by: Danilo Krummrich --- rust/kernel/io/register.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index f924c7c7c1db..66449377a0b7 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -448,7 +448,7 @@ where /// /// register! { /// FIXED_REG(u32) @ 0x100 { -/// 16:8 high_byte; +/// 15:8 high_byte; /// 7:0 low_byte; /// } /// } -- cgit From bd0abfe6b013aeb2a1aebc5fbc7ceeb50355bda3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:13 +0200 Subject: powerpc/powermac: fix OF node refcount Platform devices created with platform_device_alloc() call platform_device_release() when the last reference to the device's kobject is dropped. This function calls of_node_put() unconditionally. This works fine for devices created with platform_device_register_full() but users of the split approach (platform_device_alloc() + platform_device_add()) must bump the reference of the of_node they assign manually. Add the missing call to of_node_get(). Cc: stable@vger.kernel.org Fixes: 81e5d8646ff6 ("i2c/powermac: Register i2c devices from device-tree") Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-1-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/platforms/powermac/low_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/powermac/low_i2c.c b/arch/powerpc/platforms/powermac/low_i2c.c index da72a30ab865..973f58771d96 100644 --- a/arch/powerpc/platforms/powermac/low_i2c.c +++ b/arch/powerpc/platforms/powermac/low_i2c.c @@ -1471,7 +1471,7 @@ static int __init pmac_i2c_create_platform_devices(void) if (bus->platform_dev == NULL) return -ENOMEM; bus->platform_dev->dev.platform_data = bus; - bus->platform_dev->dev.of_node = bus->busnode; + bus->platform_dev->dev.of_node = of_node_get(bus->busnode); platform_device_add(bus->platform_dev); } -- cgit From 714cfe9e143feb2136e9dc331c43e2453b3c3d4d Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:14 +0200 Subject: driver core: platform: provide platform_device_set_of_node() Encapsulate the reference counting logic for OF nodes assigned to platform devices created with platform_device_alloc() in a helper function. Make the kerneldoc state that this is the proper interface for assigning OF nodes to dynamically allocated platform devices. This will allow us to switch to counting the references of the device's firmware nodes, not only the OF nodes. Reviewed-by: Manuel Ebner Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-2-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 18 ++++++++++++++++++ include/linux/platform_device.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index fb9120b0bcfe..3188d5aba5f9 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -693,6 +693,24 @@ int platform_device_add_data(struct platform_device *pdev, const void *data, } EXPORT_SYMBOL_GPL(platform_device_add_data); +/** + * platform_device_set_of_node - assign an OF node to device + * @pdev: platform device to add the node for + * @np: new device node + * + * Assign an OF node to this platform device. Internally keep track of the + * reference count. Devices created with platform_device_alloc() must use this + * function instead of assigning the node manually. + */ +void platform_device_set_of_node(struct platform_device *pdev, + struct device_node *np) +{ + of_node_put(pdev->dev.of_node); + pdev->dev.of_node = of_node_get(np); + pdev->dev.fwnode = of_fwnode_handle(np); +} +EXPORT_SYMBOL_GPL(platform_device_set_of_node); + /** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 26e6a43358e2..870d168aeff8 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -19,6 +19,7 @@ struct irq_affinity; struct mfd_cell; struct property_entry; struct platform_device_id; +struct device_node; struct platform_device { const char *name; @@ -262,6 +263,8 @@ extern int platform_device_add_resources(struct platform_device *pdev, unsigned int num); extern int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size); +void platform_device_set_of_node(struct platform_device *pdev, + struct device_node *np); extern int platform_device_add(struct platform_device *pdev); extern void platform_device_del(struct platform_device *pdev); extern void platform_device_put(struct platform_device *pdev); -- cgit From 8877c06885ce43780f7630973dc47cd3a625524b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:15 +0200 Subject: driver core: platform: provide platform_device_set_fwnode() Provide a helper function encapsulating the logic of assigning firmware nodes to platform devices created with platform_device_alloc(). Make the kerneldoc state that this is the proper interface for assigning firmware nodes to dynamically allocated platform devices. This will allow us to switch to counting the references of the device's firmware nodes in the future, not only the OF nodes. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-3-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 19 +++++++++++++++++++ include/linux/platform_device.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 3188d5aba5f9..6520b70cf305 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -711,6 +711,25 @@ void platform_device_set_of_node(struct platform_device *pdev, } EXPORT_SYMBOL_GPL(platform_device_set_of_node); +/** + * platform_device_set_fwnode - assign a firmware node to device + * @pdev: platform device to set the node for + * @fwnode: new firmware node + * + * Assign a firmware node to this platform device. Internally keep track of the + * reference count. Devices created with platform_device_alloc() must use this + * function instead of assigning the node manually. + */ +void platform_device_set_fwnode(struct platform_device *pdev, + struct fwnode_handle *fwnode) +{ + if (is_of_node(fwnode)) + platform_device_set_of_node(pdev, to_of_node(fwnode)); + else + pdev->dev.fwnode = fwnode; +} +EXPORT_SYMBOL_GPL(platform_device_set_fwnode); + /** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index 870d168aeff8..c463d46b7b9a 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -20,6 +20,7 @@ struct mfd_cell; struct property_entry; struct platform_device_id; struct device_node; +struct fwnode_handle; struct platform_device { const char *name; @@ -265,6 +266,8 @@ extern int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size); void platform_device_set_of_node(struct platform_device *pdev, struct device_node *np); +void platform_device_set_fwnode(struct platform_device *pdev, + struct fwnode_handle *fwnode); extern int platform_device_add(struct platform_device *pdev); extern void platform_device_del(struct platform_device *pdev); extern void platform_device_put(struct platform_device *pdev); -- cgit From 5b84ef0b029eb488c6a8fd83eea37010ac63b5e1 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:16 +0200 Subject: driver core: platform: provide platform_device_set_of_node_from_dev() Provide a platform-specific variant of device_set_of_node_from_dev(). In addition to bumping the reference count of the OF node being assigned, it also assigns the fwnode of the platform device. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-4-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 16 ++++++++++++++++ include/linux/platform_device.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 6520b70cf305..f24a5f406746 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -730,6 +730,22 @@ void platform_device_set_fwnode(struct platform_device *pdev, } EXPORT_SYMBOL_GPL(platform_device_set_fwnode); +/** + * platform_device_set_of_node_from_dev - reuse OF node of another device + * @pdev: platform device to set the node for + * @dev2: device whose OF node to reuse + * + * Reuses the OF node of another device in this platform device while + * internally keeping track of reference counting. + */ +void platform_device_set_of_node_from_dev(struct platform_device *pdev, + const struct device *dev2) +{ + device_set_of_node_from_dev(&pdev->dev, dev2); + pdev->dev.fwnode = of_fwnode_handle(pdev->dev.of_node); +} +EXPORT_SYMBOL_GPL(platform_device_set_of_node_from_dev); + /** * platform_device_add - add a platform device to device hierarchy * @pdev: platform device we're adding diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h index c463d46b7b9a..94b8d2b46e91 100644 --- a/include/linux/platform_device.h +++ b/include/linux/platform_device.h @@ -268,6 +268,8 @@ void platform_device_set_of_node(struct platform_device *pdev, struct device_node *np); void platform_device_set_fwnode(struct platform_device *pdev, struct fwnode_handle *fwnode); +void platform_device_set_of_node_from_dev(struct platform_device *pdev, + const struct device *dev2); extern int platform_device_add(struct platform_device *pdev); extern void platform_device_del(struct platform_device *pdev); extern void platform_device_put(struct platform_device *pdev); -- cgit From 67e1ddc9f7e37d19d749d7767e5370d57fc92406 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:17 +0200 Subject: driver core: update kerneldoc for platform_device_alloc() Users of platform_device_alloc() + platform_device_add() must not modify certain fields of the dynamically created platform device object. Update the kernel doc to say which fields are affected and which functions to use. Suggested-by: Manuel Ebner Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-5-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index f24a5f406746..9357942d0c79 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -619,6 +619,13 @@ static void platform_device_release_full(struct device *dev) * * Create a platform device object which can have other objects attached * to it, and which will have attached objects freed when it is released. + * + * The following fields of the dynamically allocated platform device must not + * be modified manually: resource, num_resources, dev.platform_data, + * dev.of_node and dev.fwnode. Users wishing to do the split platform device + * registration with platform_device_alloc() + platform_device_add() are + * required to use dedicated helpers for adding resources, platform data or + * assigning firmware nodes. */ struct platform_device *platform_device_alloc(const char *name, int id) { -- cgit From 6261c7cde02c0d4b52b68e751df1309dc4893eaf Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:18 +0200 Subject: of: platform: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Acked-by: Rob Herring (Arm) Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-6-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/of/platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 53bca8c6f781..8b1e76407782 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -126,7 +126,7 @@ struct platform_device *of_device_alloc(struct device_node *np, } /* setup generic device info */ - device_set_node(&dev->dev, of_fwnode_handle(of_node_get(np))); + platform_device_set_of_node(dev, np); dev->dev.parent = parent ? : &platform_bus; if (bus_id) -- cgit From 002919316dac259ffaee8e1187ecce617ffcdb2c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:19 +0200 Subject: powerpc/powermac: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-7-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- arch/powerpc/platforms/powermac/low_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/platforms/powermac/low_i2c.c b/arch/powerpc/platforms/powermac/low_i2c.c index 973f58771d96..a175a32a222b 100644 --- a/arch/powerpc/platforms/powermac/low_i2c.c +++ b/arch/powerpc/platforms/powermac/low_i2c.c @@ -1471,7 +1471,7 @@ static int __init pmac_i2c_create_platform_devices(void) if (bus->platform_dev == NULL) return -ENOMEM; bus->platform_dev->dev.platform_data = bus; - bus->platform_dev->dev.of_node = of_node_get(bus->busnode); + platform_device_set_of_node(bus->platform_dev, bus->busnode); platform_device_add(bus->platform_dev); } -- cgit From 34934e7cd1be95a3cd8b33f5231cfae2d08a815b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:20 +0200 Subject: i2c: pxa-pci: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Acked-by: Andi Shyti Acked-by: Wolfram Sang # for I2C Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-8-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/i2c/busses/i2c-pxa-pci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-pxa-pci.c b/drivers/i2c/busses/i2c-pxa-pci.c index dbd542300f80..92a0647f08c6 100644 --- a/drivers/i2c/busses/i2c-pxa-pci.c +++ b/drivers/i2c/busses/i2c-pxa-pci.c @@ -76,7 +76,8 @@ static struct platform_device *add_i2c_device(struct pci_dev *dev, int bar) goto out; } pdev->dev.parent = &dev->dev; - pdev->dev.of_node = child; + + platform_device_set_of_node(pdev, child); ret = platform_device_add_resources(pdev, res, ARRAY_SIZE(res)); if (ret) -- cgit From 6fc33ee89d274b72383cc6b275dffc0a99576dc4 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:21 +0200 Subject: iommu/fsl: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Acked-by: Robin Murphy Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Reviewed-by: Frank Li Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-9-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/iommu/fsl_pamu.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/iommu/fsl_pamu.c b/drivers/iommu/fsl_pamu.c index 25aa477a95a9..c83bbc3faad5 100644 --- a/drivers/iommu/fsl_pamu.c +++ b/drivers/iommu/fsl_pamu.c @@ -8,6 +8,7 @@ #include "fsl_pamu.h" +#include #include #include #include @@ -933,7 +934,6 @@ static struct platform_driver fsl_of_pamu_driver = { static __init int fsl_pamu_init(void) { struct platform_device *pdev = NULL; - struct device_node *np; int ret; /* @@ -955,7 +955,8 @@ static __init int fsl_pamu_init(void) * PAMU node would require significant changes to a lot of code. */ - np = of_find_compatible_node(NULL, NULL, "fsl,pamu"); + struct device_node *np __free(device_node) = + of_find_compatible_node(NULL, NULL, "fsl,pamu"); if (!np) { pr_err("could not find a PAMU node\n"); return -ENODEV; @@ -964,7 +965,7 @@ static __init int fsl_pamu_init(void) ret = platform_driver_register(&fsl_of_pamu_driver); if (ret) { pr_err("could not register driver (err=%i)\n", ret); - goto error_driver_register; + return ret; } pdev = platform_device_alloc("fsl-of-pamu", 0); @@ -973,7 +974,8 @@ static __init int fsl_pamu_init(void) ret = -ENOMEM; goto error_device_alloc; } - pdev->dev.of_node = of_node_get(np); + + platform_device_set_of_node(pdev, np); ret = pamu_domain_init(); if (ret) @@ -988,17 +990,11 @@ static __init int fsl_pamu_init(void) return 0; error_device_add: - of_node_put(pdev->dev.of_node); - pdev->dev.of_node = NULL; - platform_device_put(pdev); error_device_alloc: platform_driver_unregister(&fsl_of_pamu_driver); -error_driver_register: - of_node_put(np); - return ret; } arch_initcall(fsl_pamu_init); -- cgit From fa20a8968c03650deec369245fcac535ac1d7839 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:22 +0200 Subject: net: bcmgenet: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-10-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/broadcom/genet/bcmmii.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmmii.c b/drivers/net/ethernet/broadcom/genet/bcmmii.c index a4e0d5a68268..0f0dbabfaabb 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmmii.c +++ b/drivers/net/ethernet/broadcom/genet/bcmmii.c @@ -490,8 +490,9 @@ static int bcmgenet_mii_register(struct bcmgenet_priv *priv) /* Retain this platform_device pointer for later cleanup */ priv->mii_pdev = ppdev; ppdev->dev.parent = &pdev->dev; + if (dn) - ppdev->dev.of_node = bcmgenet_mii_of_find_mdio(priv); + platform_device_set_of_node(ppdev, bcmgenet_mii_of_find_mdio(priv)); else ppd.phy_mask = ~0; -- cgit From b830f5f871ad32be484f486ab518edfdf4ee8b97 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:23 +0200 Subject: pmdomain: imx: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Reviewed-by: Frank Li Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-11-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/pmdomain/imx/gpc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/pmdomain/imx/gpc.c b/drivers/pmdomain/imx/gpc.c index 42e50c9b4fb9..abca5f449a22 100644 --- a/drivers/pmdomain/imx/gpc.c +++ b/drivers/pmdomain/imx/gpc.c @@ -487,8 +487,7 @@ static int imx_gpc_probe(struct platform_device *pdev) domain->ipg_rate_mhz = ipg_rate_mhz; pd_pdev->dev.parent = &pdev->dev; - pd_pdev->dev.of_node = of_node_get(np); - pd_pdev->dev.fwnode = of_fwnode_handle(np); + platform_device_set_of_node(pd_pdev, np); ret = platform_device_add(pd_pdev); if (ret) { -- cgit From 4108b2633d082c194409d4ab62af887e8e01da39 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:24 +0200 Subject: mfd: tps6586: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Acked-by: Lee Jones Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-12-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/mfd/tps6586x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/tps6586x.c b/drivers/mfd/tps6586x.c index f5f805446603..3cfd2f02b62f 100644 --- a/drivers/mfd/tps6586x.c +++ b/drivers/mfd/tps6586x.c @@ -397,7 +397,7 @@ static int tps6586x_add_subdevs(struct tps6586x *tps6586x, pdev->dev.parent = tps6586x->dev; pdev->dev.platform_data = subdev->platform_data; - pdev->dev.of_node = of_node_get(subdev->of_node); + platform_device_set_of_node(pdev, subdev->of_node); ret = platform_device_add(pdev); if (ret) { -- cgit From 6e502e1725ad76bd8718d3d6bba9177cce959ef8 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:25 +0200 Subject: slimbus: qcom-ngd-ctrl: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Acked-by: Konrad Dybcio Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-13-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 3071e46d03be..6e89415712ce 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1539,7 +1539,7 @@ static int of_qcom_slim_ngd_register(struct device *parent, kfree(ngd); return ret; } - ngd->pdev->dev.of_node = of_node_get(node); + platform_device_set_of_node(ngd->pdev, node); ctrl->ngd = ngd; ret = platform_device_add(ngd->pdev); -- cgit From d5e15708c868283712768e5e0a1e3031353881f5 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:26 +0200 Subject: net: mv643xx: use platform_device_set_of_node() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node for dynamically allocated platform devices with the provided helper. Acked-by: Jakub Kicinski Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-14-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/net/ethernet/marvell/mv643xx_eth.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mv643xx_eth.c b/drivers/net/ethernet/marvell/mv643xx_eth.c index 1881583be5ce..9caa1e47c174 100644 --- a/drivers/net/ethernet/marvell/mv643xx_eth.c +++ b/drivers/net/ethernet/marvell/mv643xx_eth.c @@ -2780,7 +2780,7 @@ static int mv643xx_eth_shared_of_add_port(struct platform_device *pdev, goto put_err; } ppdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); - ppdev->dev.of_node = of_node_get(pnp); + platform_device_set_of_node(ppdev, pnp); ret = platform_device_add_resources(ppdev, &res, 1); if (ret) -- cgit From e766abbc5191b444176c3b26713e7ac2f412b351 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:27 +0200 Subject: drm/xe/i2c: use platform_device_set_fwnode() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the firmware node for dynamically allocated platform devices with the provided helper. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-15-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/gpu/drm/xe/xe_i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index 706783863d07..af4ebd93ad8e 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -123,7 +123,7 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) } pdev->dev.parent = i2c->drm_dev; - pdev->dev.fwnode = fwnode; + platform_device_set_fwnode(pdev, fwnode); i2c->adapter_node = fwnode; i2c->pdev = pdev; -- cgit From 5963c8563d07bdb7b80226d5d3e57d68d7641b53 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:28 +0200 Subject: platform/surface: gpe: use platform_device_set_fwnode() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the firmware node for dynamically allocated platform devices with the provided helper. Acked-by: Ilpo Järvinen Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-16-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/platform/surface/surface_gpe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index b359413903b1..40896a8544b0 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -317,7 +317,7 @@ static int __init surface_gpe_init(void) goto err_alloc; } - pdev->dev.fwnode = fwnode; + platform_device_set_fwnode(pdev, fwnode); status = platform_device_add(pdev); if (status) -- cgit From 7afe78d6daa374e942a14df8c17311c512c98a02 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:29 +0200 Subject: usb: chipidea: use platform_device_set_of_node_from_dev() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node from another device for dynamically allocated platform devices with the provided helper. Acked-by: Peter Chen Link: https://lore.kernel.org/r/20211215225646.1997946-1-robh@kernel.org Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Signed-off-by: Rob Herring Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-17-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 07563be0013f..7edc512cc37d 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -879,7 +879,7 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, } pdev->dev.parent = dev; - device_set_of_node_from_dev(&pdev->dev, dev); + platform_device_set_of_node_from_dev(pdev, dev); ret = platform_device_add_resources(pdev, res, nres); if (ret) -- cgit From 16aa2d3847bef0cfbab1ad76f29334369eda2374 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:30 +0200 Subject: usb: musb: use platform_device_set_of_node_from_dev() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node from another device for dynamically allocated platform devices with the provided helper. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-18-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/jz4740.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/musb/jz4740.c b/drivers/usb/musb/jz4740.c index df56c972986f..c770ba576f05 100644 --- a/drivers/usb/musb/jz4740.c +++ b/drivers/usb/musb/jz4740.c @@ -273,7 +273,7 @@ static int jz4740_probe(struct platform_device *pdev) musb->dev.parent = dev; musb->dev.dma_mask = &musb->dev.coherent_dma_mask; musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); - device_set_of_node_from_dev(&musb->dev, dev); + platform_device_set_of_node_from_dev(musb, dev); glue->pdev = musb; glue->clk = clk; -- cgit From 7a78fc208252c9a63eea5fe35259cbaf455b9227 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:31 +0200 Subject: reset: rzg2l: use platform_device_set_of_node_from_dev() Ahead of reworking the reference counting logic for platform devices, encapsulate the assignment of the OF node from another device for dynamically allocated platform devices with the provided helper. Acked-by: Philipp Zabel Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-19-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/reset/reset-rzg2l-usbphy-ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/reset/reset-rzg2l-usbphy-ctrl.c b/drivers/reset/reset-rzg2l-usbphy-ctrl.c index fd75d9601a3b..f003b360629c 100644 --- a/drivers/reset/reset-rzg2l-usbphy-ctrl.c +++ b/drivers/reset/reset-rzg2l-usbphy-ctrl.c @@ -249,7 +249,7 @@ static int rzg2l_usbphy_ctrl_probe(struct platform_device *pdev) vdev->dev.parent = dev; priv->vdev = vdev; - device_set_of_node_from_dev(&vdev->dev, dev); + platform_device_set_of_node_from_dev(vdev, dev); error = platform_device_add(vdev); if (error) goto err_device_put; -- cgit From 7b5b51b2b05cf2b49eaa3808b1c2953cfb8e1cae Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 6 Jul 2026 14:44:32 +0200 Subject: driver core: platform: count references to all kinds of firmware nodes When using platform_device_register_full(), we currently only increase the reference count of the OF node associated with a platform device. We symmetrically decrease it in platform_device_release(). With all users in tree now converted to using provided platform device helpers for assigning OF and firmware nodes, we can now switch to counting references of all kinds of firmware nodes. Reviewed-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260706-pdev-fwnode-ref-v3-20-1ff028e33779@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/platform.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 9357942d0c79..a71015f1d915 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -599,7 +599,7 @@ static void platform_device_release(struct device *dev) struct platform_object *pa = container_of(dev, struct platform_object, pdev.dev); - of_node_put(pa->pdev.dev.of_node); + fwnode_handle_put(pa->pdev.dev.fwnode); kfree(pa->pdev.dev.platform_data); kfree(pa->pdev.mfd_cell); kfree(pa->pdev.resource); @@ -712,9 +712,7 @@ EXPORT_SYMBOL_GPL(platform_device_add_data); void platform_device_set_of_node(struct platform_device *pdev, struct device_node *np) { - of_node_put(pdev->dev.of_node); - pdev->dev.of_node = of_node_get(np); - pdev->dev.fwnode = of_fwnode_handle(np); + platform_device_set_fwnode(pdev, of_fwnode_handle(np)); } EXPORT_SYMBOL_GPL(platform_device_set_of_node); @@ -730,10 +728,8 @@ EXPORT_SYMBOL_GPL(platform_device_set_of_node); void platform_device_set_fwnode(struct platform_device *pdev, struct fwnode_handle *fwnode) { - if (is_of_node(fwnode)) - platform_device_set_of_node(pdev, to_of_node(fwnode)); - else - pdev->dev.fwnode = fwnode; + fwnode_handle_put(pdev->dev.fwnode); + device_set_node(&pdev->dev, fwnode_handle_get(fwnode)); } EXPORT_SYMBOL_GPL(platform_device_set_fwnode); @@ -928,8 +924,7 @@ struct platform_device *platform_device_register_full(const struct platform_devi return ERR_PTR(-ENOMEM); pdev->dev.parent = pdevinfo->parent; - pdev->dev.fwnode = pdevinfo->fwnode; - pdev->dev.of_node = of_node_get(to_of_node(pdev->dev.fwnode)); + device_set_node(&pdev->dev, fwnode_handle_get(pdevinfo->fwnode)); dev_assign_of_node_reused(&pdev->dev, pdevinfo->of_node_reused); if (pdevinfo->dma_mask) { -- cgit From 8b29d0b45266619533b5bc3c99a65ef96d01814f Mon Sep 17 00:00:00 2001 From: Francesco Valla Date: Fri, 3 Jul 2026 17:10:21 +0200 Subject: driver core: add driver name to probe debug print The initcall_debug command line option is a useful tool while debugging and optimizing the initialization of a new system, mainly because it allows to see probe failures and deferrals without recompiling the kernel (e.g., with CONFIG_DEBUG_DRIVER). However, matching a device with the driver it is being probed with can become difficult, since some devices use names that are not explicit, at least at a first sight (e.g.: '1-0:1.0' or '1-0060'). Add the driver name alongside the device name, to allow for an immediate match between the two. Suggested-by: Tim Bird Signed-off-by: Francesco Valla Acked-by: Danilo Krummrich Link: https://patch.msgid.link/20260703-probe_driver-v2-1-b6060559f33b@valla.it Signed-off-by: Greg Kroah-Hartman --- drivers/base/dd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 60c005223844..f6525a7ee8c5 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -790,8 +790,8 @@ static int really_probe_debug(struct device *dev, const struct device_driver *dr * CONFIG_DYNAMIC_DEBUG and we want a simple 'initcall_debug' on the * kernel commandline to print this all the time at the debug level. */ - printk(KERN_DEBUG "probe of %s returned %d after %lld usecs\n", - dev_name(dev), ret, ktime_us_delta(rettime, calltime)); + printk(KERN_DEBUG "probe of %s with driver %s returned %d after %lld usecs\n", + dev_name(dev), drv->name, ret, ktime_us_delta(rettime, calltime)); return ret; } -- cgit From 426c92ca1bdd33dcbc01d6d66bb5bb4a356f2c54 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:14 +0100 Subject: rust: io: add dynamically-sized `Region` type Currently many I/O related structs carry a `SIZE` parameter to denote the minimum size of the I/O region, while they also carry a field indicating the actual size. Proliferation of the pattern creates a lot of duplicated code, and makes it hard to create typed views of I/O. Introduce a `Region` type that carries the `SIZE` parameter. It is a wrapper of `[u8]`, which makes it dynamically sized with a metadata of `usize`. This way, pointers to `Region` naturally carry size information. This type is required to be 4-byte aligned. Expose the minimum size information via `MIN_SIZE` constant of the `KnownSize` trait. Similarly, expose the minimum alignment information via `KnownSize::MIN_ALIGN`. With these changes, it is possible to add an associated type to `Io` trait to represent the type of I/O region. For untyped regions, this is the newly added `Region` type. Remove `IoKnownSize` as it is no longer necessary. Use the same mechanism to indicate minimum size of PCI config spaces. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-1-72cd5d055d54@garyguo.net [ Add brief explanation on MIN_ALIGN. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 6 +-- rust/kernel/io.rs | 130 +++++++++++++++++++++++++++++++++----------------- rust/kernel/lib.rs | 3 ++ rust/kernel/pci.rs | 1 - rust/kernel/pci/io.rs | 40 +++++++--------- rust/kernel/ptr.rs | 12 +++++ 6 files changed, 118 insertions(+), 74 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 11ce500e9b76..ed30ccc6e68e 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -68,7 +68,6 @@ struct Inner { /// devres::Devres, /// io::{ /// Io, -/// IoKnownSize, /// Mmio, /// MmioRaw, /// PhysAddr, // @@ -297,10 +296,7 @@ impl Devres { /// use kernel::{ /// device::Core, /// devres::Devres, - /// io::{ - /// Io, - /// IoKnownSize, // - /// }, + /// io::Io, /// pci, // /// }; /// diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index fcc7678fd9e3..b4cfa3588098 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -6,7 +6,11 @@ use crate::{ bindings, - prelude::*, // + prelude::*, + ptr::{ + Alignment, + KnownSize, // + }, // }; pub mod mem; @@ -31,6 +35,58 @@ pub type PhysAddr = bindings::phys_addr_t; /// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures. pub type ResourceSize = bindings::resource_size_t; +/// Untyped I/O region. +/// +/// This type can be used when an I/O region without known type information has a compile-time known +/// minimum size (and a runtime known actual size). +/// +/// # Invariants +/// +/// - Size of the region is at least as large as the `SIZE` generic parameter. +/// - Size of the region is multiple of 4. +#[repr(C, align(4))] +pub struct Region { + inner: [u8], +} + +impl Region { + /// Create a raw mutable pointer from given base address and size. + /// + /// `size` should be at least as large as the minimum size `SIZE`, and `base` and `size` should + /// be 4-byte aligned to uphold the type invariant. + /// + /// Just like other methods on raw pointers, it is not unsafe to create a raw pointer + /// that does not uphold the type invariants. However such pointers are not valid. + #[inline] + pub fn ptr_from_raw_parts_mut(base: *mut u8, size: usize) -> *mut Self { + core::ptr::slice_from_raw_parts_mut(base, size) as *mut Region + } + + /// Create a raw mutable pointer from given base address and size. + /// + /// The alignment of `base` is checked, and `size` is checked against the minimum size specified + /// via const generics. + #[inline] + pub fn ptr_try_from_raw_parts_mut(base: *mut u8, size: usize) -> Result<*mut Self> { + if size < SIZE || base.align_offset(4) != 0 || !size.is_multiple_of(4) { + return Err(EINVAL); + } + + Ok(Self::ptr_from_raw_parts_mut(base, size)) + } +} + +impl KnownSize for Region { + const MIN_SIZE: usize = SIZE; + // Alignment of 4 is the most common; different base types can be added once required. + const MIN_ALIGN: Alignment = Alignment::new::<4>(); + + #[inline(always)] + fn size(p: *const Self) -> usize { + (p as *const [u8]).len() + } +} + /// Raw representation of an MMIO region. /// /// By itself, the existence of an instance of this structure does not provide any guarantees that @@ -85,7 +141,6 @@ impl MmioRaw { /// ffi::c_void, /// io::{ /// Io, -/// IoKnownSize, /// Mmio, /// MmioRaw, /// PhysAddr, @@ -241,12 +296,25 @@ impl_usize_ioloc!(u8, u16, u32, u64); /// For MMIO regions, all widths (u8, u16, u32, and u64 on 64-bit systems) are typically /// supported. For PCI configuration space, u8, u16, and u32 are supported but u64 is not. pub trait Io { + /// Type of this I/O region. For untyped regions, [`Region`] can be used. + type Target: ?Sized + KnownSize; + /// Returns the base address of this mapping. fn addr(&self) -> usize; /// Returns the maximum size of this mapping. fn maxsize(&self) -> usize; + /// Returns the absolute I/O address for a given `offset`, + /// performing compile-time bound checks. + // Always inline to optimize out error path of `build_assert`. + #[inline(always)] + fn io_addr_assert(&self, offset: usize) -> usize { + build_assert!(offset_valid::(offset, Self::Target::MIN_SIZE)); + + self.addr() + offset + } + /// Returns the absolute I/O address for a given `offset`, /// performing runtime bound checks. #[inline] @@ -336,7 +404,7 @@ pub trait Io { #[inline(always)] fn read8(&self, offset: usize) -> u8 where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.read(offset) } @@ -345,7 +413,7 @@ pub trait Io { #[inline(always)] fn read16(&self, offset: usize) -> u16 where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.read(offset) } @@ -354,7 +422,7 @@ pub trait Io { #[inline(always)] fn read32(&self, offset: usize) -> u32 where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.read(offset) } @@ -363,7 +431,7 @@ pub trait Io { #[inline(always)] fn read64(&self, offset: usize) -> u64 where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.read(offset) } @@ -372,7 +440,7 @@ pub trait Io { #[inline(always)] fn write8(&self, value: u8, offset: usize) where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.write(offset, value) } @@ -381,7 +449,7 @@ pub trait Io { #[inline(always)] fn write16(&self, value: u16, offset: usize) where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.write(offset, value) } @@ -390,7 +458,7 @@ pub trait Io { #[inline(always)] fn write32(&self, value: u32, offset: usize) where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.write(offset, value) } @@ -399,7 +467,7 @@ pub trait Io { #[inline(always)] fn write64(&self, value: u64, offset: usize) where - Self: IoKnownSize + IoCapable, + Self: IoCapable, { self.write(offset, value) } @@ -582,7 +650,7 @@ pub trait Io { fn read(&self, location: L) -> T where L: IoLoc, - Self: IoKnownSize + IoCapable, + Self: IoCapable, { let address = self.io_addr_assert::(location.offset()); @@ -614,7 +682,7 @@ pub trait Io { fn write(&self, location: L, value: T) where L: IoLoc, - Self: IoKnownSize + IoCapable, + Self: IoCapable, { let address = self.io_addr_assert::(location.offset()); let io_value = value.into(); @@ -658,7 +726,7 @@ pub trait Io { where L: IoLoc, V: LocatedRegister, - Self: IoKnownSize + IoCapable, + Self: IoCapable, { let (location, value) = value.into_io_op(); @@ -690,7 +758,7 @@ pub trait Io { fn update(&self, location: L, f: F) where L: IoLoc, - Self: IoKnownSize + IoCapable + Sized, + Self: IoCapable + Sized, F: FnOnce(T) -> T, { let address = self.io_addr_assert::(location.offset()); @@ -704,28 +772,6 @@ pub trait Io { } } -/// Trait for types with a known size at compile time. -/// -/// This trait is implemented by I/O backends that have a compile-time known size, -/// enabling the use of infallible I/O accessors with compile-time bounds checking. -/// -/// Types implementing this trait can use the infallible methods in [`Io`] trait -/// (e.g., `read8`, `write32`), which require `Self: IoKnownSize` bound. -pub trait IoKnownSize: Io { - /// Minimum usable size of this region. - const MIN_SIZE: usize; - - /// Returns the absolute I/O address for a given `offset`, - /// performing compile-time bound checks. - // Always inline to optimize out error path of `build_assert`. - #[inline(always)] - fn io_addr_assert(&self, offset: usize) -> usize { - build_assert!(offset_valid::(offset, Self::MIN_SIZE)); - - self.addr() + offset - } -} - /// Implements [`IoCapable`] on `$mmio` for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_mmio_io_capable { ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => { @@ -758,6 +804,8 @@ impl_mmio_io_capable!( ); impl Io for Mmio { + type Target = Region; + /// Returns the base address of this mapping. #[inline] fn addr(&self) -> usize { @@ -771,10 +819,6 @@ impl Io for Mmio { } } -impl IoKnownSize for Mmio { - const MIN_SIZE: usize = SIZE; -} - impl Mmio { /// Converts an `MmioRaw` into an `Mmio` instance, providing the accessors to the MMIO mapping. /// @@ -798,6 +842,8 @@ impl Mmio { pub struct RelaxedMmio(Mmio); impl Io for RelaxedMmio { + type Target = Region; + #[inline] fn addr(&self) -> usize { self.0.addr() @@ -809,10 +855,6 @@ impl Io for RelaxedMmio { } } -impl IoKnownSize for RelaxedMmio { - const MIN_SIZE: usize = SIZE; -} - impl Mmio { /// Returns a [`RelaxedMmio`] reference that performs relaxed I/O operations. /// diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 9512af7156df..68f4d9a3425d 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -16,6 +16,9 @@ // Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on // the unstable features in use. // +// Stable since Rust 1.87.0. +#![feature(unsigned_is_multiple_of)] +// // Stable since Rust 1.89.0. #![feature(generic_arg_infer)] // diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 5071cae6543f..c6d6bd8f251d 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -43,7 +43,6 @@ pub use self::id::{ pub use self::io::{ Bar, ConfigSpace, - ConfigSpaceKind, ConfigSpaceSize, Extended, Normal, // diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 0461e01aaa20..b4996aa059d8 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -10,11 +10,12 @@ use crate::{ io::{ Io, IoCapable, - IoKnownSize, Mmio, - MmioRaw, // + MmioRaw, + Region, // }, - prelude::*, // + prelude::*, + ptr::KnownSize, // }; use core::{ marker::PhantomData, @@ -46,28 +47,21 @@ impl ConfigSpaceSize { } } -/// Marker type for normal (256-byte) PCI configuration space. -pub struct Normal; +/// Alias for normal (256-byte) PCI configuration space. +pub type Normal = Region<256>; -/// Marker type for extended (4096-byte) PCIe configuration space. -pub struct Extended; +/// Alias for extended (4096-byte) PCIe configuration space. +pub type Extended = Region<4096>; /// Trait for PCI configuration space size markers. /// /// This trait is implemented by [`Normal`] and [`Extended`] to provide /// compile-time knowledge of the configuration space size. -pub trait ConfigSpaceKind { - /// The size of this configuration space in bytes. - const SIZE: usize; -} +pub trait ConfigSpaceKind: KnownSize {} -impl ConfigSpaceKind for Normal { - const SIZE: usize = 256; -} +impl ConfigSpaceKind for Normal {} -impl ConfigSpaceKind for Extended { - const SIZE: usize = 4096; -} +impl ConfigSpaceKind for Extended {} /// The PCI configuration space of a device. /// @@ -77,7 +71,7 @@ impl ConfigSpaceKind for Extended { /// The generic parameter `S` indicates the maximum size of the configuration space. /// Use [`Normal`] for 256-byte legacy configuration space or [`Extended`] for /// 4096-byte PCIe extended configuration space (default). -pub struct ConfigSpace<'a, S: ConfigSpaceKind = Extended> { +pub struct ConfigSpace<'a, S: ?Sized + ConfigSpaceKind = Extended> { pub(crate) pdev: &'a Device, _marker: PhantomData, } @@ -85,7 +79,7 @@ pub struct ConfigSpace<'a, S: ConfigSpaceKind = Extended> { /// Implements [`IoCapable`] on [`ConfigSpace`] for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_config_space_io_capable { ($ty:ty, $read_fn:ident, $write_fn:ident) => { - impl<'a, S: ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> { + impl<'a, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> { unsafe fn io_read(&self, address: usize) -> $ty { let mut val: $ty = 0; @@ -118,7 +112,9 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte); impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word); impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword); -impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> { +impl<'a, S: ?Sized + ConfigSpaceKind> Io for ConfigSpace<'a, S> { + type Target = S; + /// Returns the base address of the I/O region. It is always 0 for configuration space. #[inline] fn addr(&self) -> usize { @@ -132,10 +128,6 @@ impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> { } } -impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> { - const MIN_SIZE: usize = S::SIZE; -} - /// A PCI BAR to perform I/O-Operations on. /// /// I/O backend assumes that the device is little-endian and will automatically diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs index 3f3e529e9f58..82acb531b17b 100644 --- a/rust/kernel/ptr.rs +++ b/rust/kernel/ptr.rs @@ -235,11 +235,20 @@ impl_alignable_uint!(u8, u16, u32, u64, usize); /// /// This is a generalization of [`size_of`] that works for dynamically sized types. pub trait KnownSize { + /// Minimum size of this type known at compile-time. + const MIN_SIZE: usize; + + /// Minimum alignment of this type known at compile-time. + const MIN_ALIGN: Alignment; + /// Get the size of an object of this type in bytes, with the metadata of the given pointer. fn size(p: *const Self) -> usize; } impl KnownSize for T { + const MIN_SIZE: usize = size_of::(); + const MIN_ALIGN: Alignment = Alignment::of::(); + #[inline(always)] fn size(_: *const Self) -> usize { size_of::() @@ -247,6 +256,9 @@ impl KnownSize for T { } impl KnownSize for [T] { + const MIN_SIZE: usize = 0; + const MIN_ALIGN: Alignment = Alignment::of::(); + #[inline(always)] fn size(p: *const Self) -> usize { p.len() * size_of::() -- cgit From 65f6abf9fa81617c8455a28b5f07269d883d080a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:15 +0100 Subject: rust: io: add missing safety requirement in `IoCapable` methods The current safety comment on `io_read`/`io_write` does not cover the topic about alignment. Add it so it can be relied on by implementor of `IoCapable`. Expand the check performed by `Io` by taking `self.addr()` into consideration when checking if `offset` is aligned. For the compile-time `io_addr_assert` check, check using the known minimum alignment of `Io::Target` and the accessed type. While at it, fix the alignment check to use `align_of` instead of `size_of`. The values match for all primitives (including u64, given that we do not provide u64 accessor on 32-bit platforms), but are not necessarily true for custom types. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-2-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index b4cfa3588098..c9597065a776 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -196,13 +196,14 @@ impl MmioRaw { #[repr(transparent)] pub struct Mmio(MmioRaw); -/// Checks whether an access of type `U` at the given `offset` +/// Checks whether an access of type `U` at the given `base` and the given `offset` /// is valid within this region. +/// +/// The `base` is used for alignment checking only. This can be set to 0 to skip the check. #[inline] -const fn offset_valid(offset: usize, size: usize) -> bool { - let type_size = core::mem::size_of::(); - if let Some(end) = offset.checked_add(type_size) { - end <= size && offset % type_size == 0 +const fn offset_valid(base: usize, offset: usize, size: usize) -> bool { + if let Some(end) = offset.checked_add(size_of::()) { + end <= size && (base.wrapping_add(offset) % align_of::() == 0) } else { false } @@ -221,14 +222,16 @@ pub trait IoCapable { /// /// # Safety /// - /// The range `[address..address + size_of::()]` must be within the bounds of `Self`. + /// - The range `[address..address + size_of::()]` must be within the bounds of `Self`. + /// - `address` must be aligned. unsafe fn io_read(&self, address: usize) -> T; /// Performs an I/O write of `value` at `address`. /// /// # Safety /// - /// The range `[address..address + size_of::()]` must be within the bounds of `Self`. + /// - The range `[address..address + size_of::()]` must be within the bounds of `Self`. + /// - `address` must be aligned. unsafe fn io_write(&self, value: T, address: usize); } @@ -310,7 +313,11 @@ pub trait Io { // Always inline to optimize out error path of `build_assert`. #[inline(always)] fn io_addr_assert(&self, offset: usize) -> usize { - build_assert!(offset_valid::(offset, Self::Target::MIN_SIZE)); + // We cannot check alignment with `offset_valid` using `self.addr()`. So set 0 for it and + // ensure alignment by checking that the alignment of `U` is smaller or equal to the + // alignment of `Self::Target`. + const_assert!(Alignment::of::().as_usize() <= Self::Target::MIN_ALIGN.as_usize()); + build_assert!(offset_valid::(0, offset, Self::Target::MIN_SIZE)); self.addr() + offset } @@ -319,7 +326,7 @@ pub trait Io { /// performing runtime bound checks. #[inline] fn io_addr(&self, offset: usize) -> Result { - if !offset_valid::(offset, self.maxsize()) { + if !offset_valid::(self.addr(), offset, self.maxsize()) { return Err(EINVAL); } -- cgit From 6461c5776bf0f546cfeaf2a72f1a2f7de27bfe0d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:16 +0100 Subject: rust: io: restrict untyped IO access and `register!` to `Region` Currently the `Io` trait exposes a bunch of untyped IO accesses, but if the `Io` region itself is typed, then it might be weird to have let io: Mmio = /* ... */; io.read8(1); while not unsound, it is surely strange. Thus, restrict the untyped methods and also the register macro to `Region` type only. Implement it by adding a generic type to `IoLoc` indicating allowed base types. This also paves the way to add typed register blocks in the future; for example, we could use this mechanism to block driver A's `register!()` generated macro from being used on driver B's MMIO. The same mechanism could be used for relative IO registers. These are future opportunities, and for now restrict everything to require `IoLoc, _>`. Suggested-by: Alexandre Courbot Link: https://lore.kernel.org/rust-for-linux/DHLB3RO3OSF5.2R7F27U99BKLN@nvidia.com/ Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-3-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 49 +++++++++++++++++++++++++++++++--------------- rust/kernel/io/register.rs | 20 ++++++++++--------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index c9597065a776..b0dac2a54a24 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -244,15 +244,16 @@ pub trait IoCapable { /// (for primitive types like [`u32`]) and typed ones (like those generated by the [`register!`] /// macro). /// -/// An `IoLoc` carries three pieces of information: +/// An `IoLoc` carries the following pieces of information: /// +/// - The valid `Base` to operate on. For most registers, this should be [`Region`]. /// - The offset to access (returned by [`IoLoc::offset`]), /// - The width of the access (determined by [`IoLoc::IoType`]), /// - The type `T` in which the raw data is returned or provided. /// /// `T` and `IoLoc::IoType` may differ: for instance, a typed register has `T` = the register type /// with its bitfields, and `IoType` = its backing primitive (e.g. `u32`). -pub trait IoLoc { +pub trait IoLoc { /// Size ([`u8`], [`u16`], etc) of the I/O performed on the returned [`offset`](IoLoc::offset). type IoType: Into + From; @@ -260,12 +261,12 @@ pub trait IoLoc { fn offset(self) -> usize; } -/// Implements [`IoLoc<$ty>`] for [`usize`], allowing [`usize`] to be used as a parameter of -/// [`Io::read`] and [`Io::write`]. +/// Implements [`IoLoc, $ty>`] for [`usize`], allowing [`usize`] to be used as a +/// parameter of [`Io::read`] and [`Io::write`]. macro_rules! impl_usize_ioloc { ($($ty:ty),*) => { $( - impl IoLoc<$ty> for usize { + impl IoLoc, $ty> for usize { type IoType = $ty; #[inline(always)] @@ -339,6 +340,7 @@ pub trait Io { #[inline(always)] fn try_read8(&self, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_read(offset) @@ -348,6 +350,7 @@ pub trait Io { #[inline(always)] fn try_read16(&self, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_read(offset) @@ -357,6 +360,7 @@ pub trait Io { #[inline(always)] fn try_read32(&self, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_read(offset) @@ -366,6 +370,7 @@ pub trait Io { #[inline(always)] fn try_read64(&self, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_read(offset) @@ -375,6 +380,7 @@ pub trait Io { #[inline(always)] fn try_write8(&self, value: u8, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_write(offset, value) @@ -384,6 +390,7 @@ pub trait Io { #[inline(always)] fn try_write16(&self, value: u16, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_write(offset, value) @@ -393,6 +400,7 @@ pub trait Io { #[inline(always)] fn try_write32(&self, value: u32, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_write(offset, value) @@ -402,6 +410,7 @@ pub trait Io { #[inline(always)] fn try_write64(&self, value: u64, offset: usize) -> Result where + usize: IoLoc, Self: IoCapable, { self.try_write(offset, value) @@ -411,6 +420,7 @@ pub trait Io { #[inline(always)] fn read8(&self, offset: usize) -> u8 where + usize: IoLoc, Self: IoCapable, { self.read(offset) @@ -420,6 +430,7 @@ pub trait Io { #[inline(always)] fn read16(&self, offset: usize) -> u16 where + usize: IoLoc, Self: IoCapable, { self.read(offset) @@ -429,6 +440,7 @@ pub trait Io { #[inline(always)] fn read32(&self, offset: usize) -> u32 where + usize: IoLoc, Self: IoCapable, { self.read(offset) @@ -438,6 +450,7 @@ pub trait Io { #[inline(always)] fn read64(&self, offset: usize) -> u64 where + usize: IoLoc, Self: IoCapable, { self.read(offset) @@ -447,6 +460,7 @@ pub trait Io { #[inline(always)] fn write8(&self, value: u8, offset: usize) where + usize: IoLoc, Self: IoCapable, { self.write(offset, value) @@ -456,6 +470,7 @@ pub trait Io { #[inline(always)] fn write16(&self, value: u16, offset: usize) where + usize: IoLoc, Self: IoCapable, { self.write(offset, value) @@ -465,6 +480,7 @@ pub trait Io { #[inline(always)] fn write32(&self, value: u32, offset: usize) where + usize: IoLoc, Self: IoCapable, { self.write(offset, value) @@ -474,6 +490,7 @@ pub trait Io { #[inline(always)] fn write64(&self, value: u64, offset: usize) where + usize: IoLoc, Self: IoCapable, { self.write(offset, value) @@ -504,7 +521,7 @@ pub trait Io { #[inline(always)] fn try_read(&self, location: L) -> Result where - L: IoLoc, + L: IoLoc, Self: IoCapable, { let address = self.io_addr::(location.offset())?; @@ -538,7 +555,7 @@ pub trait Io { #[inline(always)] fn try_write(&self, location: L, value: T) -> Result where - L: IoLoc, + L: IoLoc, Self: IoCapable, { let address = self.io_addr::(location.offset())?; @@ -584,8 +601,8 @@ pub trait Io { #[inline(always)] fn try_write_reg(&self, value: V) -> Result where - L: IoLoc, - V: LocatedRegister, + L: IoLoc, + V: LocatedRegister, Self: IoCapable, { let (location, value) = value.into_io_op(); @@ -617,7 +634,7 @@ pub trait Io { #[inline(always)] fn try_update(&self, location: L, f: F) -> Result where - L: IoLoc, + L: IoLoc, Self: IoCapable, F: FnOnce(T) -> T, { @@ -656,7 +673,7 @@ pub trait Io { #[inline(always)] fn read(&self, location: L) -> T where - L: IoLoc, + L: IoLoc, Self: IoCapable, { let address = self.io_addr_assert::(location.offset()); @@ -688,7 +705,7 @@ pub trait Io { #[inline(always)] fn write(&self, location: L, value: T) where - L: IoLoc, + L: IoLoc, Self: IoCapable, { let address = self.io_addr_assert::(location.offset()); @@ -731,8 +748,8 @@ pub trait Io { #[inline(always)] fn write_reg(&self, value: V) where - L: IoLoc, - V: LocatedRegister, + L: IoLoc, + V: LocatedRegister, Self: IoCapable, { let (location, value) = value.into_io_op(); @@ -764,8 +781,8 @@ pub trait Io { #[inline(always)] fn update(&self, location: L, f: F) where - L: IoLoc, - Self: IoCapable + Sized, + L: IoLoc, + Self: IoCapable, F: FnOnce(T) -> T, { let address = self.io_addr_assert::(location.offset()); diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index f924c7c7c1db..3122b17098ee 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -113,6 +113,8 @@ use crate::{ io::IoLoc, // }; +use super::Region; + /// Trait implemented by all registers. pub trait Register: Sized { /// Backing primitive type of the register. @@ -129,7 +131,7 @@ pub trait FixedRegister: Register {} /// Allows `()` to be used as the `location` parameter of [`Io::write`](super::Io::write) when /// passing a [`FixedRegister`] value. -impl IoLoc for () +impl IoLoc, T> for () where T: FixedRegister, { @@ -143,7 +145,7 @@ where /// A [`FixedRegister`] carries its location in its type. Thus `FixedRegister` values can be used /// as an [`IoLoc`]. -impl IoLoc for T +impl IoLoc, T> for T where T: FixedRegister, { @@ -168,7 +170,7 @@ impl FixedRegisterLoc { } } -impl IoLoc for FixedRegisterLoc +impl IoLoc, T> for FixedRegisterLoc where T: FixedRegister, { @@ -239,7 +241,7 @@ where } } -impl IoLoc for RelativeRegisterLoc +impl IoLoc, T> for RelativeRegisterLoc where T: RelativeRegister, B: RegisterBase + ?Sized, @@ -283,7 +285,7 @@ impl RegisterArrayLoc { } } -impl IoLoc for RegisterArrayLoc +impl IoLoc, T> for RegisterArrayLoc where T: RegisterArray, { @@ -370,7 +372,7 @@ where } } -impl IoLoc for RelativeRegisterArrayLoc +impl IoLoc, T> for RelativeRegisterArrayLoc where T: RelativeRegisterArray, B: RegisterBase + ?Sized, @@ -387,18 +389,18 @@ where /// which to write it. /// /// Implementors can be used with [`Io::write_reg`](super::Io::write_reg). -pub trait LocatedRegister { +pub trait LocatedRegister { /// Register value to write. type Value: Register; /// Full location information at which to write the value. - type Location: IoLoc; + type Location: IoLoc; /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write /// operation. fn into_io_op(self) -> (Self::Location, Self::Value); } -impl LocatedRegister for T +impl LocatedRegister> for T where T: FixedRegister, { -- cgit From 46b1b54139c3e24b80eefc8da09c2f731ecc7e73 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:17 +0100 Subject: rust: io: implement `Io` on reference types instead Currently, `Io` is implemented on owned I/O objects (e.g. `Bar`). This is going to change with I/O projections, as then `Io` needs to work both for owned objects and views of them. Views are themselves reference-like (however they obviously cannot be references, because they belong to a different address space). To facilitate the change, change `Io` to be implemented on reference types for the owned I/O objects, and make methods take `self` instead of `&self`. When I/O views are implemented, we can then naturally implement `Io` for these objects. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-4-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 82 ++++++++++++++++++++++++++------------------------- rust/kernel/pci/io.rs | 12 ++++---- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index b0dac2a54a24..a2aa6173ce87 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -224,7 +224,7 @@ pub trait IoCapable { /// /// - The range `[address..address + size_of::()]` must be within the bounds of `Self`. /// - `address` must be aligned. - unsafe fn io_read(&self, address: usize) -> T; + unsafe fn io_read(self, address: usize) -> T; /// Performs an I/O write of `value` at `address`. /// @@ -232,7 +232,7 @@ pub trait IoCapable { /// /// - The range `[address..address + size_of::()]` must be within the bounds of `Self`. /// - `address` must be aligned. - unsafe fn io_write(&self, value: T, address: usize); + unsafe fn io_write(self, value: T, address: usize); } /// Describes a given I/O location: its offset, width, and type to convert the raw value from and @@ -295,25 +295,27 @@ impl_usize_ioloc!(u8, u16, u32, u64); /// Which I/O methods are available depends on which [`IoCapable`] traits /// are implemented for the type. /// +/// This should be implemented on cheaply copyable handles, such as references or view types. +/// /// # Examples /// /// For MMIO regions, all widths (u8, u16, u32, and u64 on 64-bit systems) are typically /// supported. For PCI configuration space, u8, u16, and u32 are supported but u64 is not. -pub trait Io { +pub trait Io: Copy { /// Type of this I/O region. For untyped regions, [`Region`] can be used. type Target: ?Sized + KnownSize; /// Returns the base address of this mapping. - fn addr(&self) -> usize; + fn addr(self) -> usize; /// Returns the maximum size of this mapping. - fn maxsize(&self) -> usize; + fn maxsize(self) -> usize; /// Returns the absolute I/O address for a given `offset`, /// performing compile-time bound checks. // Always inline to optimize out error path of `build_assert`. #[inline(always)] - fn io_addr_assert(&self, offset: usize) -> usize { + fn io_addr_assert(self, offset: usize) -> usize { // We cannot check alignment with `offset_valid` using `self.addr()`. So set 0 for it and // ensure alignment by checking that the alignment of `U` is smaller or equal to the // alignment of `Self::Target`. @@ -326,7 +328,7 @@ pub trait Io { /// Returns the absolute I/O address for a given `offset`, /// performing runtime bound checks. #[inline] - fn io_addr(&self, offset: usize) -> Result { + fn io_addr(self, offset: usize) -> Result { if !offset_valid::(self.addr(), offset, self.maxsize()) { return Err(EINVAL); } @@ -338,7 +340,7 @@ pub trait Io { /// Fallible 8-bit read with runtime bounds check. #[inline(always)] - fn try_read8(&self, offset: usize) -> Result + fn try_read8(self, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -348,7 +350,7 @@ pub trait Io { /// Fallible 16-bit read with runtime bounds check. #[inline(always)] - fn try_read16(&self, offset: usize) -> Result + fn try_read16(self, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -358,7 +360,7 @@ pub trait Io { /// Fallible 32-bit read with runtime bounds check. #[inline(always)] - fn try_read32(&self, offset: usize) -> Result + fn try_read32(self, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -368,7 +370,7 @@ pub trait Io { /// Fallible 64-bit read with runtime bounds check. #[inline(always)] - fn try_read64(&self, offset: usize) -> Result + fn try_read64(self, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -378,7 +380,7 @@ pub trait Io { /// Fallible 8-bit write with runtime bounds check. #[inline(always)] - fn try_write8(&self, value: u8, offset: usize) -> Result + fn try_write8(self, value: u8, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -388,7 +390,7 @@ pub trait Io { /// Fallible 16-bit write with runtime bounds check. #[inline(always)] - fn try_write16(&self, value: u16, offset: usize) -> Result + fn try_write16(self, value: u16, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -398,7 +400,7 @@ pub trait Io { /// Fallible 32-bit write with runtime bounds check. #[inline(always)] - fn try_write32(&self, value: u32, offset: usize) -> Result + fn try_write32(self, value: u32, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -408,7 +410,7 @@ pub trait Io { /// Fallible 64-bit write with runtime bounds check. #[inline(always)] - fn try_write64(&self, value: u64, offset: usize) -> Result + fn try_write64(self, value: u64, offset: usize) -> Result where usize: IoLoc, Self: IoCapable, @@ -418,7 +420,7 @@ pub trait Io { /// Infallible 8-bit read with compile-time bounds check. #[inline(always)] - fn read8(&self, offset: usize) -> u8 + fn read8(self, offset: usize) -> u8 where usize: IoLoc, Self: IoCapable, @@ -428,7 +430,7 @@ pub trait Io { /// Infallible 16-bit read with compile-time bounds check. #[inline(always)] - fn read16(&self, offset: usize) -> u16 + fn read16(self, offset: usize) -> u16 where usize: IoLoc, Self: IoCapable, @@ -438,7 +440,7 @@ pub trait Io { /// Infallible 32-bit read with compile-time bounds check. #[inline(always)] - fn read32(&self, offset: usize) -> u32 + fn read32(self, offset: usize) -> u32 where usize: IoLoc, Self: IoCapable, @@ -448,7 +450,7 @@ pub trait Io { /// Infallible 64-bit read with compile-time bounds check. #[inline(always)] - fn read64(&self, offset: usize) -> u64 + fn read64(self, offset: usize) -> u64 where usize: IoLoc, Self: IoCapable, @@ -458,7 +460,7 @@ pub trait Io { /// Infallible 8-bit write with compile-time bounds check. #[inline(always)] - fn write8(&self, value: u8, offset: usize) + fn write8(self, value: u8, offset: usize) where usize: IoLoc, Self: IoCapable, @@ -468,7 +470,7 @@ pub trait Io { /// Infallible 16-bit write with compile-time bounds check. #[inline(always)] - fn write16(&self, value: u16, offset: usize) + fn write16(self, value: u16, offset: usize) where usize: IoLoc, Self: IoCapable, @@ -478,7 +480,7 @@ pub trait Io { /// Infallible 32-bit write with compile-time bounds check. #[inline(always)] - fn write32(&self, value: u32, offset: usize) + fn write32(self, value: u32, offset: usize) where usize: IoLoc, Self: IoCapable, @@ -488,7 +490,7 @@ pub trait Io { /// Infallible 64-bit write with compile-time bounds check. #[inline(always)] - fn write64(&self, value: u64, offset: usize) + fn write64(self, value: u64, offset: usize) where usize: IoLoc, Self: IoCapable, @@ -519,7 +521,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn try_read(&self, location: L) -> Result + fn try_read(self, location: L) -> Result where L: IoLoc, Self: IoCapable, @@ -553,7 +555,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn try_write(&self, location: L, value: T) -> Result + fn try_write(self, location: L, value: T) -> Result where L: IoLoc, Self: IoCapable, @@ -599,7 +601,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn try_write_reg(&self, value: V) -> Result + fn try_write_reg(self, value: V) -> Result where L: IoLoc, V: LocatedRegister, @@ -632,7 +634,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn try_update(&self, location: L, f: F) -> Result + fn try_update(self, location: L, f: F) -> Result where L: IoLoc, Self: IoCapable, @@ -671,7 +673,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn read(&self, location: L) -> T + fn read(self, location: L) -> T where L: IoLoc, Self: IoCapable, @@ -703,7 +705,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn write(&self, location: L, value: T) + fn write(self, location: L, value: T) where L: IoLoc, Self: IoCapable, @@ -746,7 +748,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn write_reg(&self, value: V) + fn write_reg(self, value: V) where L: IoLoc, V: LocatedRegister, @@ -779,7 +781,7 @@ pub trait Io { /// } /// ``` #[inline(always)] - fn update(&self, location: L, f: F) + fn update(self, location: L, f: F) where L: IoLoc, Self: IoCapable, @@ -800,13 +802,13 @@ pub trait Io { macro_rules! impl_mmio_io_capable { ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => { $(#[$attr])* - impl IoCapable<$ty> for $mmio { - unsafe fn io_read(&self, address: usize) -> $ty { + impl IoCapable<$ty> for &$mmio { + unsafe fn io_read(self, address: usize) -> $ty { // SAFETY: By the trait invariant `address` is a valid address for MMIO operations. unsafe { bindings::$read_fn(address as *const c_void) } } - unsafe fn io_write(&self, value: $ty, address: usize) { + unsafe fn io_write(self, value: $ty, address: usize) { // SAFETY: By the trait invariant `address` is a valid address for MMIO operations. unsafe { bindings::$write_fn(value, address as *mut c_void) } } @@ -827,18 +829,18 @@ impl_mmio_io_capable!( writeq ); -impl Io for Mmio { +impl<'a, const SIZE: usize> Io for &'a Mmio { type Target = Region; /// Returns the base address of this mapping. #[inline] - fn addr(&self) -> usize { + fn addr(self) -> usize { self.0.addr() } /// Returns the maximum size of this mapping. #[inline] - fn maxsize(&self) -> usize { + fn maxsize(self) -> usize { self.0.maxsize() } } @@ -865,16 +867,16 @@ impl Mmio { #[repr(transparent)] pub struct RelaxedMmio(Mmio); -impl Io for RelaxedMmio { +impl<'a, const SIZE: usize> Io for &'a RelaxedMmio { type Target = Region; #[inline] - fn addr(&self) -> usize { + fn addr(self) -> usize { self.0.addr() } #[inline] - fn maxsize(&self) -> usize { + fn maxsize(self) -> usize { self.0.maxsize() } } diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index b4996aa059d8..505305cd9b86 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -79,8 +79,8 @@ pub struct ConfigSpace<'a, S: ?Sized + ConfigSpaceKind = Extended> { /// Implements [`IoCapable`] on [`ConfigSpace`] for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_config_space_io_capable { ($ty:ty, $read_fn:ident, $write_fn:ident) => { - impl<'a, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> { - unsafe fn io_read(&self, address: usize) -> $ty { + impl<'a, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for &ConfigSpace<'a, S> { + unsafe fn io_read(self, address: usize) -> $ty { let mut val: $ty = 0; // Return value from C function is ignored in infallible accessors. @@ -94,7 +94,7 @@ macro_rules! impl_config_space_io_capable { val } - unsafe fn io_write(&self, value: $ty, address: usize) { + unsafe fn io_write(self, value: $ty, address: usize) { // Return value from C function is ignored in infallible accessors. let _ret = // SAFETY: By the type invariant `self.pdev` is a valid address. @@ -112,18 +112,18 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte); impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word); impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword); -impl<'a, S: ?Sized + ConfigSpaceKind> Io for ConfigSpace<'a, S> { +impl<'a, S: ?Sized + ConfigSpaceKind> Io for &ConfigSpace<'a, S> { type Target = S; /// Returns the base address of the I/O region. It is always 0 for configuration space. #[inline] - fn addr(&self) -> usize { + fn addr(self) -> usize { 0 } /// Returns the maximum size of the configuration space. #[inline] - fn maxsize(&self) -> usize { + fn maxsize(self) -> usize { self.pdev.cfg_size().into_raw() } } -- cgit From 9734e905119c5f7d7af9dd3e483f9a0d9ee12187 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:18 +0100 Subject: rust: io: generalize `MmioRaw` to pointer to arbitrary type Conceptually, `MmioRaw` is just `__iomem *`, so it should work for any types. Update the existing use case where it represents a region of compile-time known minimum size and run-time known actual size to use the dynamic-sized type `Region` instead. Rename `maxsize` method to reflect that it is the actual size (not a bound) of the region. Implement `Clone` and `Copy` manually, which cannot be derived due to the generic parameter. The use of raw pointers also cause the `Send` and `Sync` auto trait implementation to be lost, so add them back by manual implementation. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Suggested-by: Danilo Krummrich Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/near/571198078 Link: https://patch.msgid.link/20260706-io_projection-v6-5-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 7 +++--- rust/kernel/io.rs | 67 +++++++++++++++++++++++++++++++++++++-------------- rust/kernel/io/mem.rs | 5 ++-- rust/kernel/pci/io.rs | 4 +-- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index ed30ccc6e68e..d0c677fd7932 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -70,14 +70,15 @@ struct Inner { /// Io, /// Mmio, /// MmioRaw, -/// PhysAddr, // +/// PhysAddr, +/// Region, // /// }, /// prelude::*, /// }; /// use core::ops::Deref; /// /// // See also [`pci::Bar`] for a real example. -/// struct IoMem(MmioRaw); +/// struct IoMem(MmioRaw>); /// /// impl IoMem { /// /// # Safety @@ -92,7 +93,7 @@ struct Inner { /// return Err(ENOMEM); /// } /// -/// Ok(IoMem(MmioRaw::new(addr as usize, SIZE)?)) +/// Ok(IoMem(MmioRaw::new_region(addr as usize, SIZE)?)) /// } /// } /// diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index a2aa6173ce87..3013d8cf39e4 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -89,37 +89,67 @@ impl KnownSize for Region { /// Raw representation of an MMIO region. /// +/// `MmioRaw` is equivalent to `T __iomem *` in C. +/// /// By itself, the existence of an instance of this structure does not provide any guarantees that /// the represented MMIO region does exist or is properly mapped. /// /// Instead, the bus specific MMIO implementation must convert this raw representation into an /// `Mmio` instance providing the actual memory accessors. Only by the conversion into an `Mmio` /// structure any guarantees are given. -pub struct MmioRaw { - addr: usize, - maxsize: usize, +pub struct MmioRaw { + /// Pointer is in I/O address space. + /// + /// The provenance does not matter, only the address and metadata do. + ptr: *mut T, } -impl MmioRaw { - /// Returns a new `MmioRaw` instance on success, an error otherwise. - pub fn new(addr: usize, maxsize: usize) -> Result { - if maxsize < SIZE { - return Err(EINVAL); +impl Copy for MmioRaw {} +impl Clone for MmioRaw { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +// SAFETY: `MmioRaw` is just an address, so is thread-safe. +unsafe impl Send for MmioRaw {} +// SAFETY: `MmioRaw` is just an address, so is thread-safe. +unsafe impl Sync for MmioRaw {} + +impl MmioRaw { + /// Create a `MmioRaw` from address. + #[inline] + pub fn new(addr: usize) -> Self { + Self { + ptr: core::ptr::without_provenance_mut(addr), } + } +} - Ok(Self { addr, maxsize }) +impl MmioRaw> { + /// Create a `MmioRaw` representing a I/O region with given size. + /// + /// The size is checked against the minimum size specified via const generics. + #[inline] + pub fn new_region(addr: usize, size: usize) -> Result { + Ok(Self { + ptr: Region::ptr_try_from_raw_parts_mut(core::ptr::without_provenance_mut(addr), size)?, + }) } +} +impl MmioRaw { /// Returns the base address of the MMIO region. #[inline] pub fn addr(&self) -> usize { - self.addr + self.ptr.addr() } - /// Returns the maximum size of the MMIO region. + /// Returns the size of the MMIO region. #[inline] - pub fn maxsize(&self) -> usize { - self.maxsize + pub fn size(&self) -> usize { + KnownSize::size(self.ptr) } } @@ -144,12 +174,13 @@ impl MmioRaw { /// Mmio, /// MmioRaw, /// PhysAddr, +/// Region, /// }, /// }; /// use core::ops::Deref; /// /// // See also `pci::Bar` for a real example. -/// struct IoMem(MmioRaw); +/// struct IoMem(MmioRaw>); /// /// impl IoMem { /// /// # Safety @@ -164,7 +195,7 @@ impl MmioRaw { /// return Err(ENOMEM); /// } /// -/// Ok(IoMem(MmioRaw::new(addr as usize, SIZE)?)) +/// Ok(IoMem(MmioRaw::new_region(addr as usize, SIZE)?)) /// } /// } /// @@ -194,7 +225,7 @@ impl MmioRaw { /// # } /// ``` #[repr(transparent)] -pub struct Mmio(MmioRaw); +pub struct Mmio(MmioRaw>); /// Checks whether an access of type `U` at the given `base` and the given `offset` /// is valid within this region. @@ -841,7 +872,7 @@ impl<'a, const SIZE: usize> Io for &'a Mmio { /// Returns the maximum size of this mapping. #[inline] fn maxsize(self) -> usize { - self.0.maxsize() + self.0.size() } } @@ -852,7 +883,7 @@ impl Mmio { /// /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size /// `maxsize`. - pub unsafe fn from_raw(raw: &MmioRaw) -> &Self { + pub unsafe fn from_raw(raw: &MmioRaw>) -> &Self { // SAFETY: `Mmio` is a transparent wrapper around `MmioRaw`. unsafe { &*core::ptr::from_ref(raw).cast() } } diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index fc2a3e24f8d5..9e15bc8fde78 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -229,7 +229,7 @@ impl Deref for ExclusiveIoMem<'_, SIZE> { /// start of the I/O memory mapped region. pub struct IoMem<'a, const SIZE: usize = 0> { dev: &'a Device, - io: MmioRaw, + io: MmioRaw>, } impl<'a, const SIZE: usize> IoMem<'a, SIZE> { @@ -264,8 +264,7 @@ impl<'a, const SIZE: usize> IoMem<'a, SIZE> { return Err(ENOMEM); } - let io = MmioRaw::new(addr as usize, size)?; - + let io = MmioRaw::new_region(addr as usize, size)?; Ok(IoMem { dev, io }) } diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 505305cd9b86..42f840d64a6f 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -139,7 +139,7 @@ impl<'a, S: ?Sized + ConfigSpaceKind> Io for &ConfigSpace<'a, S> { /// memory mapped PCI BAR and its size. pub struct Bar<'a, const SIZE: usize = 0> { pdev: &'a Device, - io: MmioRaw, + io: MmioRaw>, num: i32, } @@ -179,7 +179,7 @@ impl<'a, const SIZE: usize> Bar<'a, SIZE> { return Err(ENOMEM); } - let io = match MmioRaw::new(ioptr, len as usize) { + let io = match MmioRaw::new_region(ioptr, len as usize) { Ok(io) => io, Err(err) => { // SAFETY: -- cgit From 691c75967d44bef006e4d4e783baa88470b33ea5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:19 +0100 Subject: rust: io: rename `Mmio` to `MmioOwned` Most users would more commonly reach out to a view of `Mmio` rather than an owned instance of `Mmio`. Only implementor of `Io` like `Bar` or `IoMem` would need the owned version. Thus, rename `Mmio` to `MmioOwned` so that the name `Mmio` can be used for the view type instead. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Suggested-by: Danilo Krummrich Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/near/571198078 Link: https://patch.msgid.link/20260706-io_projection-v6-6-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 6 ++-- rust/kernel/io.rs | 77 +++++++++++++++++++++++----------------------- rust/kernel/io/mem.rs | 8 ++--- rust/kernel/io/poll.rs | 8 ++--- rust/kernel/io/register.rs | 24 +++++++-------- rust/kernel/pci/io.rs | 6 ++-- 6 files changed, 65 insertions(+), 64 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index d0c677fd7932..aed0c994fd30 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -68,7 +68,7 @@ struct Inner { /// devres::Devres, /// io::{ /// Io, -/// Mmio, +/// MmioOwned, /// MmioRaw, /// PhysAddr, /// Region, // @@ -105,11 +105,11 @@ struct Inner { /// } /// /// impl Deref for IoMem { -/// type Target = Mmio; +/// type Target = MmioOwned; /// /// fn deref(&self) -> &Self::Target { /// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`. -/// unsafe { Mmio::from_raw(&self.0) } +/// unsafe { MmioOwned::from_raw(&self.0) } /// } /// } /// # fn no_run(dev: &Device) -> Result<(), Error> { diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 3013d8cf39e4..ec4ac42aa25d 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -95,8 +95,8 @@ impl KnownSize for Region { /// the represented MMIO region does exist or is properly mapped. /// /// Instead, the bus specific MMIO implementation must convert this raw representation into an -/// `Mmio` instance providing the actual memory accessors. Only by the conversion into an `Mmio` -/// structure any guarantees are given. +/// `MmioOwned` instance providing the actual memory accessors. Only by the conversion into an +/// `MmioOwned` structure any guarantees are given. pub struct MmioRaw { /// Pointer is in I/O address space. /// @@ -171,7 +171,7 @@ impl MmioRaw { /// ffi::c_void, /// io::{ /// Io, -/// Mmio, +/// MmioOwned, /// MmioRaw, /// PhysAddr, /// Region, @@ -207,11 +207,11 @@ impl MmioRaw { /// } /// /// impl Deref for IoMem { -/// type Target = Mmio; +/// type Target = MmioOwned; /// /// fn deref(&self) -> &Self::Target { /// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`. -/// unsafe { Mmio::from_raw(&self.0) } +/// unsafe { MmioOwned::from_raw(&self.0) } /// } /// } /// @@ -225,7 +225,7 @@ impl MmioRaw { /// # } /// ``` #[repr(transparent)] -pub struct Mmio(MmioRaw>); +pub struct MmioOwned(MmioRaw>); /// Checks whether an access of type `U` at the given `base` and the given `offset` /// is valid within this region. @@ -538,10 +538,10 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// }; /// - /// fn do_reads(io: &Mmio) -> Result { + /// fn do_reads(io: &MmioOwned) -> Result { /// // 32-bit read from address `0x10`. /// let v: u32 = io.try_read(0x10)?; /// @@ -572,10 +572,10 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// }; /// - /// fn do_writes(io: &Mmio) -> Result { + /// fn do_writes(io: &MmioOwned) -> Result { /// // 32-bit write of value `1` at address `0x10`. /// io.try_write(0x10, 1u32)?; /// @@ -610,7 +610,7 @@ pub trait Io: Copy { /// use kernel::io::{ /// register, /// Io, - /// Mmio, + /// MmioOwned, /// }; /// /// register! { @@ -626,7 +626,7 @@ pub trait Io: Copy { /// } /// } /// - /// fn do_write_reg(io: &Mmio) -> Result { + /// fn do_write_reg(io: &MmioOwned) -> Result { /// /// io.try_write_reg(VERSION::new(1, 0)) /// } @@ -655,10 +655,10 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// }; /// - /// fn do_update(io: &Mmio<0x1000>) -> Result { + /// fn do_update(io: &MmioOwned<0x1000>) -> Result { /// io.try_update(0x10, |v: u32| { /// v + 1 /// }) @@ -692,10 +692,10 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// }; /// - /// fn do_reads(io: &Mmio<0x1000>) { + /// fn do_reads(io: &MmioOwned<0x1000>) { /// // 32-bit read from address `0x10`. /// let v: u32 = io.read(0x10); /// @@ -724,10 +724,10 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// }; /// - /// fn do_writes(io: &Mmio<0x1000>) { + /// fn do_writes(io: &MmioOwned<0x1000>) { /// // 32-bit write of value `1` at address `0x10`. /// io.write(0x10, 1u32); /// @@ -758,7 +758,7 @@ pub trait Io: Copy { /// use kernel::io::{ /// register, /// Io, - /// Mmio, + /// MmioOwned, /// }; /// /// register! { @@ -774,7 +774,7 @@ pub trait Io: Copy { /// } /// } /// - /// fn do_write_reg(io: &Mmio<0x1000>) { + /// fn do_write_reg(io: &MmioOwned<0x1000>) { /// io.write_reg(VERSION::new(1, 0)); /// } /// ``` @@ -802,10 +802,10 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// }; /// - /// fn do_update(io: &Mmio<0x1000>) { + /// fn do_update(io: &MmioOwned<0x1000>) { /// io.update(0x10, |v: u32| { /// v + 1 /// }) @@ -848,19 +848,19 @@ macro_rules! impl_mmio_io_capable { } // MMIO regions support 8, 16, and 32-bit accesses. -impl_mmio_io_capable!(Mmio, u8, readb, writeb); -impl_mmio_io_capable!(Mmio, u16, readw, writew); -impl_mmio_io_capable!(Mmio, u32, readl, writel); +impl_mmio_io_capable!(MmioOwned, u8, readb, writeb); +impl_mmio_io_capable!(MmioOwned, u16, readw, writew); +impl_mmio_io_capable!(MmioOwned, u32, readl, writel); // MMIO regions on 64-bit systems also support 64-bit accesses. impl_mmio_io_capable!( - Mmio, + MmioOwned, #[cfg(CONFIG_64BIT)] u64, readq, writeq ); -impl<'a, const SIZE: usize> Io for &'a Mmio { +impl<'a, const SIZE: usize> Io for &'a MmioOwned { type Target = Region; /// Returns the base address of this mapping. @@ -876,27 +876,28 @@ impl<'a, const SIZE: usize> Io for &'a Mmio { } } -impl Mmio { - /// Converts an `MmioRaw` into an `Mmio` instance, providing the accessors to the MMIO mapping. +impl MmioOwned { + /// Converts an `MmioRaw` into an `MmioOwned` instance, providing the accessors to the MMIO + /// mapping. /// /// # Safety /// /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size /// `maxsize`. pub unsafe fn from_raw(raw: &MmioRaw>) -> &Self { - // SAFETY: `Mmio` is a transparent wrapper around `MmioRaw`. + // SAFETY: `MmioOwned` is a transparent wrapper around `MmioRaw`. unsafe { &*core::ptr::from_ref(raw).cast() } } } -/// [`Mmio`] wrapper using relaxed accessors. +/// [`MmioOwned`] wrapper using relaxed accessors. /// /// This type provides an implementation of [`Io`] that uses relaxed I/O MMIO operands instead of /// the regular ones. /// -/// See [`Mmio::relaxed`] for a usage example. +/// See [`MmioOwned::relaxed`] for a usage example. #[repr(transparent)] -pub struct RelaxedMmio(Mmio); +pub struct RelaxedMmio(MmioOwned); impl<'a, const SIZE: usize> Io for &'a RelaxedMmio { type Target = Region; @@ -912,7 +913,7 @@ impl<'a, const SIZE: usize> Io for &'a RelaxedMmio { } } -impl Mmio { +impl MmioOwned { /// Returns a [`RelaxedMmio`] reference that performs relaxed I/O operations. /// /// Relaxed accessors do not provide ordering guarantees with respect to DMA or memory accesses @@ -923,19 +924,19 @@ impl Mmio { /// ```no_run /// use kernel::io::{ /// Io, - /// Mmio, + /// MmioOwned, /// RelaxedMmio, /// }; /// - /// fn do_io(io: &Mmio<0x100>) { + /// fn do_io(io: &MmioOwned<0x100>) { /// // The access is performed using `readl_relaxed` instead of `readl`. /// let v = io.relaxed().read32(0x10); /// } /// /// ``` pub fn relaxed(&self) -> &RelaxedMmio { - // SAFETY: `RelaxedMmio` is `#[repr(transparent)]` over `Mmio`, so `Mmio` and - // `RelaxedMmio` have identical layout. + // SAFETY: `RelaxedMmio` is `#[repr(transparent)]` over `MmioOwned`, so `MmioOwned` + // and `RelaxedMmio` have identical layout. unsafe { core::mem::transmute(self) } } } diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index 9e15bc8fde78..8f6c257c5b8e 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -16,7 +16,7 @@ use crate::{ Region, Resource, // }, - Mmio, + MmioOwned, MmioRaw, // }, prelude::*, @@ -211,7 +211,7 @@ impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> { } impl Deref for ExclusiveIoMem<'_, SIZE> { - type Target = Mmio; + type Target = MmioOwned; fn deref(&self) -> &Self::Target { &self.iomem @@ -291,10 +291,10 @@ impl Drop for IoMem<'_, SIZE> { } impl Deref for IoMem<'_, SIZE> { - type Target = Mmio; + type Target = MmioOwned; fn deref(&self) -> &Self::Target { // SAFETY: Safe as by the invariant of `IoMem`. - unsafe { Mmio::from_raw(&self.io) } + unsafe { MmioOwned::from_raw(&self.io) } } } diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs index 75d1b3e8596c..79828a8006b5 100644 --- a/rust/kernel/io/poll.rs +++ b/rust/kernel/io/poll.rs @@ -47,14 +47,14 @@ use crate::{ /// ```no_run /// use kernel::io::{ /// Io, -/// Mmio, +/// MmioOwned, /// poll::read_poll_timeout, // /// }; /// use kernel::time::Delta; /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &Mmio) -> Result { +/// fn wait_for_hardware(io: &MmioOwned) -> Result { /// read_poll_timeout( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), @@ -134,14 +134,14 @@ where /// ```no_run /// use kernel::io::{ /// Io, -/// Mmio, +/// MmioOwned, /// poll::read_poll_timeout_atomic, // /// }; /// use kernel::time::Delta; /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &Mmio) -> Result { +/// fn wait_for_hardware(io: &MmioOwned) -> Result { /// read_poll_timeout_atomic( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index 3122b17098ee..43284d9fba96 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -58,7 +58,7 @@ //! }, //! num::Bounded, //! }; -//! # use kernel::io::Mmio; +//! # use kernel::io::MmioOwned; //! # register! { //! # pub BOOT_0(u32) @ 0x00000100 { //! # 15:8 vendor_id; @@ -66,7 +66,7 @@ //! # 3:0 minor_revision; //! # } //! # } -//! # fn test(io: &Mmio<0x1000>) { +//! # fn test(io: &MmioOwned<0x1000>) { //! # fn obtain_vendor_id() -> u8 { 0xff } //! //! // Read from the register's defined offset (0x100). @@ -446,7 +446,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::Mmio; +/// # use kernel::io::MmioOwned; /// /// register! { /// FIXED_REG(u32) @ 0x100 { @@ -455,7 +455,7 @@ where /// } /// } /// -/// # fn test(io: &Mmio<0x1000>) { +/// # fn test(io: &MmioOwned<0x1000>) { /// let val = io.read(FIXED_REG); /// /// // Write from an already-existing value. @@ -559,7 +559,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::Mmio; +/// # use kernel::io::MmioOwned; /// /// // Type used to identify the base. /// pub struct CpuCtlBase; @@ -584,7 +584,7 @@ where /// } /// } /// -/// # fn test(io: Mmio<0x1000>) { +/// # fn test(io: MmioOwned<0x1000>) { /// // Read the status of `Cpu0`. /// let cpu0_started = io.read(CPU_CTL::of::()); /// @@ -601,7 +601,7 @@ where /// } /// } /// -/// # fn test2(io: Mmio<0x1000>) { +/// # fn test2(io: MmioOwned<0x1000>) { /// // Start the aliased `CPU0`, leaving its other fields untouched. /// io.update(CPU_CTL_ALIAS::of::(), |r| r.with_alias_start(true)); /// # } @@ -638,7 +638,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::Mmio; +/// # use kernel::io::MmioOwned; /// # fn get_scratch_idx() -> usize { /// # 0x15 /// # } @@ -651,7 +651,7 @@ where /// } /// } /// -/// # fn test(io: &Mmio<0x1000>) +/// # fn test(io: &MmioOwned<0x1000>) /// # -> Result<(), Error>{ /// // Read scratch register 0, i.e. I/O address `0x80`. /// let scratch_0 = io.read(SCRATCH::at(0)).value(); @@ -724,7 +724,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::Mmio; +/// # use kernel::io::MmioOwned; /// # fn get_scratch_idx() -> usize { /// # 0x15 /// # } @@ -752,7 +752,7 @@ where /// } /// } /// -/// # fn test(io: &Mmio<0x1000>) -> Result<(), Error> { +/// # fn test(io: &MmioOwned<0x1000>) -> Result<(), Error> { /// // Read scratch register 0 of CPU0. /// let scratch = io.read(CPU_SCRATCH::of::().at(0)); /// @@ -794,7 +794,7 @@ where /// } /// } /// -/// # fn test2(io: &Mmio<0x1000>) -> Result<(), Error> { +/// # fn test2(io: &MmioOwned<0x1000>) -> Result<(), Error> { /// let cpu0_status = io.read(CPU_FIRMWARE_STATUS::of::()).status(); /// # Ok(()) /// # } diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 42f840d64a6f..e0acb62f58a2 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -10,7 +10,7 @@ use crate::{ io::{ Io, IoCapable, - Mmio, + MmioOwned, MmioRaw, Region, // }, @@ -242,11 +242,11 @@ impl Drop for Bar<'_, SIZE> { } impl Deref for Bar<'_, SIZE> { - type Target = Mmio; + type Target = MmioOwned; fn deref(&self) -> &Self::Target { // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped. - unsafe { Mmio::from_raw(&self.io) } + unsafe { MmioOwned::from_raw(&self.io) } } } -- cgit From 9f64c84af008b8e01309ec6fe04bc772fbf23ea5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:20 +0100 Subject: rust: io: implement `Mmio` as view type Implement `Mmio` as view type and convert `RelaxedMmio` to view type as well. I/O implementations of `MmioOwned` are changed to delegate to the `Mmio` view type. All existing users of `MmioOwned` in the documentation which do not actually reflect the owning semantics is converted. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Tested-by: Daniel Almeida Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-7-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 176 ++++++++++++++++++++++++++++++++++----------- rust/kernel/io/poll.rs | 10 +-- rust/kernel/io/register.rs | 24 +++---- 3 files changed, 153 insertions(+), 57 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index ec4ac42aa25d..081b4613bc12 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -4,6 +4,10 @@ //! //! C header: [`include/asm-generic/io.h`](srctree/include/asm-generic/io.h) +use core::{ + marker::PhantomData, // +}; + use crate::{ bindings, prelude::*, @@ -538,10 +542,11 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// - /// fn do_reads(io: &MmioOwned) -> Result { + /// fn do_reads(io: Mmio<'_, Region>) -> Result { /// // 32-bit read from address `0x10`. /// let v: u32 = io.try_read(0x10)?; /// @@ -572,10 +577,11 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// - /// fn do_writes(io: &MmioOwned) -> Result { + /// fn do_writes(io: Mmio<'_, Region>) -> Result { /// // 32-bit write of value `1` at address `0x10`. /// io.try_write(0x10, 1u32)?; /// @@ -610,7 +616,8 @@ pub trait Io: Copy { /// use kernel::io::{ /// register, /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// /// register! { @@ -626,7 +633,7 @@ pub trait Io: Copy { /// } /// } /// - /// fn do_write_reg(io: &MmioOwned) -> Result { + /// fn do_write_reg(io: Mmio<'_, Region>) -> Result { /// /// io.try_write_reg(VERSION::new(1, 0)) /// } @@ -655,10 +662,11 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// - /// fn do_update(io: &MmioOwned<0x1000>) -> Result { + /// fn do_update(io: Mmio<'_, Region<0x1000>>) -> Result { /// io.try_update(0x10, |v: u32| { /// v + 1 /// }) @@ -692,10 +700,11 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// - /// fn do_reads(io: &MmioOwned<0x1000>) { + /// fn do_reads(io: Mmio<'_, Region<0x1000>>) { /// // 32-bit read from address `0x10`. /// let v: u32 = io.read(0x10); /// @@ -724,10 +733,11 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// - /// fn do_writes(io: &MmioOwned<0x1000>) { + /// fn do_writes(io: Mmio<'_, Region<0x1000>>) { /// // 32-bit write of value `1` at address `0x10`. /// io.write(0x10, 1u32); /// @@ -758,7 +768,8 @@ pub trait Io: Copy { /// use kernel::io::{ /// register, /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// /// register! { @@ -774,7 +785,7 @@ pub trait Io: Copy { /// } /// } /// - /// fn do_write_reg(io: &MmioOwned<0x1000>) { + /// fn do_write_reg(io: Mmio<'_, Region<0x1000>>) { /// io.write_reg(VERSION::new(1, 0)); /// } /// ``` @@ -802,10 +813,11 @@ pub trait Io: Copy { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// }; /// - /// fn do_update(io: &MmioOwned<0x1000>) { + /// fn do_update(io: Mmio<'_, Region<0x1000>>) { /// io.update(0x10, |v: u32| { /// v + 1 /// }) @@ -829,16 +841,72 @@ pub trait Io: Copy { } } +/// A view of memory-mapped I/O region. +/// +/// # Invariant +/// +/// `ptr` points to a valid and aligned memory-mapped I/O region for the duration lifetime `'a`. +pub struct Mmio<'a, T: ?Sized> { + ptr: *mut T, + phantom: PhantomData<&'a ()>, +} + +impl Copy for Mmio<'_, T> {} +impl Clone for Mmio<'_, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: ?Sized> Mmio<'a, T> { + /// Create a `Mmio`, providing the accessors to the MMIO mapping. + /// + /// # Safety + /// + /// `raw` represents a valid and aligned memory-mapped I/O region while `'a` is alive. + #[inline] + pub unsafe fn from_raw(raw: MmioRaw) -> Self { + // INVARIANT: Per safety requirement. + Self { + ptr: raw.ptr, + phantom: PhantomData, + } + } +} + +// SAFETY: `Mmio<'_, T>` is conceptually `&T` but in I/O memory. +unsafe impl Send for Mmio<'_, T> {} + +// SAFETY: `Mmio<'_, T>` is conceptually `&T` but in I/O memory. +unsafe impl Sync for Mmio<'_, T> {} + +impl Io for Mmio<'_, T> { + type Target = T; + + #[inline] + fn addr(self) -> usize { + self.ptr.addr() + } + + #[inline] + fn maxsize(self) -> usize { + KnownSize::size(self.ptr) + } +} + /// Implements [`IoCapable`] on `$mmio` for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_mmio_io_capable { ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => { $(#[$attr])* - impl IoCapable<$ty> for &$mmio { + impl IoCapable<$ty> for $mmio<'_, T> { + #[inline] unsafe fn io_read(self, address: usize) -> $ty { // SAFETY: By the trait invariant `address` is a valid address for MMIO operations. unsafe { bindings::$read_fn(address as *const c_void) } } + #[inline] unsafe fn io_write(self, value: $ty, address: usize) { // SAFETY: By the trait invariant `address` is a valid address for MMIO operations. unsafe { bindings::$write_fn(value, address as *mut c_void) } @@ -848,17 +916,12 @@ macro_rules! impl_mmio_io_capable { } // MMIO regions support 8, 16, and 32-bit accesses. -impl_mmio_io_capable!(MmioOwned, u8, readb, writeb); -impl_mmio_io_capable!(MmioOwned, u16, readw, writew); -impl_mmio_io_capable!(MmioOwned, u32, readl, writel); +impl_mmio_io_capable!(Mmio, u8, readb, writeb); +impl_mmio_io_capable!(Mmio, u16, readw, writew); +impl_mmio_io_capable!(Mmio, u32, readl, writel); // MMIO regions on 64-bit systems also support 64-bit accesses. -impl_mmio_io_capable!( - MmioOwned, - #[cfg(CONFIG_64BIT)] - u64, - readq, - writeq -); +#[cfg(CONFIG_64BIT)] +impl_mmio_io_capable!(Mmio, u64, readq, writeq); impl<'a, const SIZE: usize> Io for &'a MmioOwned { type Target = Region; @@ -876,6 +939,23 @@ impl<'a, const SIZE: usize> Io for &'a MmioOwned { } } +impl<'a, const SIZE: usize, T> IoCapable for &'a MmioOwned +where + Mmio<'a, Region>: IoCapable, +{ + #[inline] + unsafe fn io_read(self, address: usize) -> T { + // SAFETY: Per safety requirement. + unsafe { self.as_view().io_read(address) } + } + + #[inline] + unsafe fn io_write(self, value: T, address: usize) { + // SAFETY: Per safety requirement. + unsafe { self.as_view().io_write(value, address) } + } +} + impl MmioOwned { /// Converts an `MmioRaw` into an `MmioOwned` instance, providing the accessors to the MMIO /// mapping. @@ -888,19 +968,33 @@ impl MmioOwned { // SAFETY: `MmioOwned` is a transparent wrapper around `MmioRaw`. unsafe { &*core::ptr::from_ref(raw).cast() } } + + /// Return a view that covers the full region. + #[inline] + pub fn as_view(&self) -> Mmio<'_, Region> { + // SAFETY: `Mmio` has same invariant as `MmioOwned`. + unsafe { Mmio::from_raw(self.0) } + } } -/// [`MmioOwned`] wrapper using relaxed accessors. +/// [`Mmio`] but using relaxed accessors. /// /// This type provides an implementation of [`Io`] that uses relaxed I/O MMIO operands instead of /// the regular ones. /// -/// See [`MmioOwned::relaxed`] for a usage example. -#[repr(transparent)] -pub struct RelaxedMmio(MmioOwned); +/// See [`Mmio::relaxed`] for a usage example. +pub struct RelaxedMmio<'a, T: ?Sized>(Mmio<'a, T>); -impl<'a, const SIZE: usize> Io for &'a RelaxedMmio { - type Target = Region; +impl Copy for RelaxedMmio<'_, T> {} +impl Clone for RelaxedMmio<'_, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl Io for RelaxedMmio<'_, T> { + type Target = T; #[inline] fn addr(self) -> usize { @@ -913,8 +1007,8 @@ impl<'a, const SIZE: usize> Io for &'a RelaxedMmio { } } -impl MmioOwned { - /// Returns a [`RelaxedMmio`] reference that performs relaxed I/O operations. +impl<'a, T: ?Sized> Mmio<'a, T> { + /// Returns a [`RelaxedMmio`] that performs relaxed I/O operations. /// /// Relaxed accessors do not provide ordering guarantees with respect to DMA or memory accesses /// and can be used when such ordering is not required. @@ -924,20 +1018,20 @@ impl MmioOwned { /// ```no_run /// use kernel::io::{ /// Io, - /// MmioOwned, + /// Mmio, + /// Region, /// RelaxedMmio, /// }; /// - /// fn do_io(io: &MmioOwned<0x100>) { + /// fn do_io(io: Mmio<'_, Region<0x100>>) { /// // The access is performed using `readl_relaxed` instead of `readl`. /// let v = io.relaxed().read32(0x10); /// } /// /// ``` - pub fn relaxed(&self) -> &RelaxedMmio { - // SAFETY: `RelaxedMmio` is `#[repr(transparent)]` over `MmioOwned`, so `MmioOwned` - // and `RelaxedMmio` have identical layout. - unsafe { core::mem::transmute(self) } + #[inline] + pub fn relaxed(self) -> RelaxedMmio<'a, T> { + RelaxedMmio(self) } } diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs index 79828a8006b5..d75f2fcf46f2 100644 --- a/rust/kernel/io/poll.rs +++ b/rust/kernel/io/poll.rs @@ -47,14 +47,15 @@ use crate::{ /// ```no_run /// use kernel::io::{ /// Io, -/// MmioOwned, +/// Mmio, +/// Region, /// poll::read_poll_timeout, // /// }; /// use kernel::time::Delta; /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &MmioOwned) -> Result { +/// fn wait_for_hardware(io: Mmio<'_, Region>) -> Result { /// read_poll_timeout( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), @@ -134,14 +135,15 @@ where /// ```no_run /// use kernel::io::{ /// Io, -/// MmioOwned, +/// Mmio, +/// Region, /// poll::read_poll_timeout_atomic, // /// }; /// use kernel::time::Delta; /// /// const HW_READY: u16 = 0x01; /// -/// fn wait_for_hardware(io: &MmioOwned) -> Result { +/// fn wait_for_hardware(io: Mmio<'_, Region>) -> Result { /// read_poll_timeout_atomic( /// // The `op` closure reads the value of a specific status register. /// || io.try_read16(0x1000), diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index 43284d9fba96..80e638a892d7 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -58,7 +58,7 @@ //! }, //! num::Bounded, //! }; -//! # use kernel::io::MmioOwned; +//! # use kernel::io::{Mmio, Region}; //! # register! { //! # pub BOOT_0(u32) @ 0x00000100 { //! # 15:8 vendor_id; @@ -66,7 +66,7 @@ //! # 3:0 minor_revision; //! # } //! # } -//! # fn test(io: &MmioOwned<0x1000>) { +//! # fn test(io: Mmio<'_, Region<0x1000>>) { //! # fn obtain_vendor_id() -> u8 { 0xff } //! //! // Read from the register's defined offset (0x100). @@ -446,7 +446,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::MmioOwned; +/// # use kernel::io::{Mmio, Region}; /// /// register! { /// FIXED_REG(u32) @ 0x100 { @@ -455,7 +455,7 @@ where /// } /// } /// -/// # fn test(io: &MmioOwned<0x1000>) { +/// # fn test(io: Mmio<'_, Region<0x1000>>) { /// let val = io.read(FIXED_REG); /// /// // Write from an already-existing value. @@ -559,7 +559,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::MmioOwned; +/// # use kernel::io::{Mmio, Region}; /// /// // Type used to identify the base. /// pub struct CpuCtlBase; @@ -584,7 +584,7 @@ where /// } /// } /// -/// # fn test(io: MmioOwned<0x1000>) { +/// # fn test(io: Mmio<'_, Region<0x1000>>) { /// // Read the status of `Cpu0`. /// let cpu0_started = io.read(CPU_CTL::of::()); /// @@ -601,7 +601,7 @@ where /// } /// } /// -/// # fn test2(io: MmioOwned<0x1000>) { +/// # fn test2(io: Mmio<'_, Region<0x1000>>) { /// // Start the aliased `CPU0`, leaving its other fields untouched. /// io.update(CPU_CTL_ALIAS::of::(), |r| r.with_alias_start(true)); /// # } @@ -638,7 +638,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::MmioOwned; +/// # use kernel::io::{Mmio, Region}; /// # fn get_scratch_idx() -> usize { /// # 0x15 /// # } @@ -651,7 +651,7 @@ where /// } /// } /// -/// # fn test(io: &MmioOwned<0x1000>) +/// # fn test(io: Mmio<'_, Region<0x1000>>) /// # -> Result<(), Error>{ /// // Read scratch register 0, i.e. I/O address `0x80`. /// let scratch_0 = io.read(SCRATCH::at(0)).value(); @@ -724,7 +724,7 @@ where /// Io, /// }, /// }; -/// # use kernel::io::MmioOwned; +/// # use kernel::io::{Mmio, Region}; /// # fn get_scratch_idx() -> usize { /// # 0x15 /// # } @@ -752,7 +752,7 @@ where /// } /// } /// -/// # fn test(io: &MmioOwned<0x1000>) -> Result<(), Error> { +/// # fn test(io: Mmio<'_, Region<0x1000>>) -> Result<(), Error> { /// // Read scratch register 0 of CPU0. /// let scratch = io.read(CPU_SCRATCH::of::().at(0)); /// @@ -794,7 +794,7 @@ where /// } /// } /// -/// # fn test2(io: &MmioOwned<0x1000>) -> Result<(), Error> { +/// # fn test2(io: Mmio<'_, Region<0x1000>>) -> Result<(), Error> { /// let cpu0_status = io.read(CPU_FIRMWARE_STATUS::of::()).status(); /// # Ok(()) /// # } -- cgit From 6e5f28968d7b3f2137e99528579e6109acb4d4c8 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:21 +0100 Subject: rust: pci: io: make `ConfigSpace` a view In order to support I/O projection, we are splitting I/O types into two categories: owned objects and views. Owned objects have a specific type that is related to setting up and tearing down, while views can have their type changed with I/O projection. Things like `IoMem` or `Bar` are owned objects, which requires setting up mapping and cleaning up on drop. On the other side, `ConfigSpace` is really just a view, as the resource is associated with the `pci::Device`. Remove the `ConfigSpaceKind` bound on `ConfigSpace` and make it a generic view. This means that `ConfigSpace` object now represents a subregion and therefore encodes offset (as address of pointers) and size (as metadata of pointers) itself. The full region case is still supported with offset 0 and size of `cfg_size`. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-8-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/pci/io.rs | 64 +++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index e0acb62f58a2..89f4bb483a7f 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -18,7 +18,6 @@ use crate::{ ptr::KnownSize, // }; use core::{ - marker::PhantomData, ops::Deref, // }; @@ -53,33 +52,42 @@ pub type Normal = Region<256>; /// Alias for extended (4096-byte) PCIe configuration space. pub type Extended = Region<4096>; -/// Trait for PCI configuration space size markers. -/// -/// This trait is implemented by [`Normal`] and [`Extended`] to provide -/// compile-time knowledge of the configuration space size. -pub trait ConfigSpaceKind: KnownSize {} - -impl ConfigSpaceKind for Normal {} - -impl ConfigSpaceKind for Extended {} - -/// The PCI configuration space of a device. +/// A view of PCI configuration space of a device. /// /// Provides typed read and write accessors for configuration registers /// using the standard `pci_read_config_*` and `pci_write_config_*` helpers. /// -/// The generic parameter `S` indicates the maximum size of the configuration space. -/// Use [`Normal`] for 256-byte legacy configuration space or [`Extended`] for -/// 4096-byte PCIe extended configuration space (default). -pub struct ConfigSpace<'a, S: ?Sized + ConfigSpaceKind = Extended> { +/// The generic parameter `T` is the type of the view. The full configuration space is also a +/// special type of view; in such cases, `T` can be [`Normal`] for 256-byte legacy configuration +/// space or [`Extended`] for 4096-byte PCIe extended configuration space (default). +/// +/// # Invariants +/// +/// `ptr` is aligned and range `ptr..ptr + KnownSize::size(ptr)` is within +/// `0..pdev.cfg_size().into_raw()`. +pub struct ConfigSpace<'a, T: ?Sized = Extended> { pub(crate) pdev: &'a Device, - _marker: PhantomData, + ptr: *mut T, } +impl Copy for ConfigSpace<'_, T> {} +impl Clone for ConfigSpace<'_, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +// SAFETY: `ConfigSpace<'_, T>` is conceptually `&T` but in I/O memory. +unsafe impl Send for ConfigSpace<'_, T> {} + +// SAFETY: `ConfigSpace<'_, T>` is conceptually `&T` but in I/O memory. +unsafe impl Sync for ConfigSpace<'_, T> {} + /// Implements [`IoCapable`] on [`ConfigSpace`] for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_config_space_io_capable { ($ty:ty, $read_fn:ident, $write_fn:ident) => { - impl<'a, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for &ConfigSpace<'a, S> { + impl<'a, T: ?Sized> IoCapable<$ty> for ConfigSpace<'a, T> { unsafe fn io_read(self, address: usize) -> $ty { let mut val: $ty = 0; @@ -112,19 +120,17 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte); impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word); impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword); -impl<'a, S: ?Sized + ConfigSpaceKind> Io for &ConfigSpace<'a, S> { - type Target = S; +impl<'a, T: ?Sized + KnownSize> Io for ConfigSpace<'a, T> { + type Target = T; - /// Returns the base address of the I/O region. It is always 0 for configuration space. #[inline] fn addr(self) -> usize { - 0 + self.ptr.addr() } - /// Returns the maximum size of the configuration space. #[inline] fn maxsize(self) -> usize { - self.pdev.cfg_size().into_raw() + KnownSize::size(self.ptr) } } @@ -281,23 +287,25 @@ impl Device { } } - /// Return an initialized normal (256-byte) config space object. + /// Return a view of the normal (256-byte) config space. pub fn config_space<'a>(&'a self) -> ConfigSpace<'a, Normal> { + // INVARIANT: null is aligned and the range is within config space. ConfigSpace { pdev: self, - _marker: PhantomData, + ptr: Normal::ptr_from_raw_parts_mut(core::ptr::null_mut(), self.cfg_size().into_raw()), } } - /// Return an initialized extended (4096-byte) config space object. + /// Return a view of the extended (4096-byte) config space. pub fn config_space_extended<'a>(&'a self) -> Result> { if self.cfg_size() != ConfigSpaceSize::Extended { return Err(EINVAL); } + // INVARIANT: null is aligned and we just checked the `cfg_size`. Ok(ConfigSpace { pdev: self, - _marker: PhantomData, + ptr: Extended::ptr_from_raw_parts_mut(core::ptr::null_mut(), 4096), }) } } -- cgit From e0454ec1220c29178c13c209197f29f29e324d7f Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:22 +0100 Subject: rust: io: use view types instead of addresses for `Io` Currently, `io_read` and `io_write` methods require the exact type of `Io` plus an address. This means that they need to be monomorphized for each different `Io` instance. This also means that multiple I/O implementors for the same I/O kind needs to duplicate implementation (e.g. `Mmio` and `MmioOwned`). Create a new `IoBackend` trait and define these operations on it instead. The operations are just going to receive a view type and operate on them. This has the additional advantage that the invariants can be moved from the trait (and guaranteed via `unsafe`) to type invariants on the canonical view types of the backends, so `io_read` and `io_write` can be safe. Note that a view type is needed; addresses are insufficient in this design, as they do not carry sufficient information. For example, `ConfigSpace` needs `&pci::Device` in addition to the address. `io_addr_assert` and `io_addr` are renamed to `io_view*` to reflect that they operate on views now, and make them standalone functions so they cannot be used by users to cast types outside io.rs. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-9-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 382 ++++++++++++++++++++++++++------------------------ rust/kernel/pci/io.rs | 70 +++++---- 2 files changed, 246 insertions(+), 206 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 081b4613bc12..82a3369ae110 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -244,6 +244,81 @@ const fn offset_valid(base: usize, offset: usize, size: usize) -> bool { } } +/// Returns a view for a given `offset`, performing compile-time bound checks. +// Always inline to optimize out error path of `build_assert`. +#[inline(always)] +fn io_view_assert<'a, IO: Io<'a>, U>( + this: IO, + offset: usize, +) -> ::View<'a, U> { + // We cannot check alignment with `offset_valid` using `ptr.addr()`. So set 0 for it and + // ensure alignment by checking that the alignment of `U` is smaller or equal to the + // alignment of `IO::Target`. + const_assert!(Alignment::of::().as_usize() <= IO::Target::MIN_ALIGN.as_usize()); + build_assert!(offset_valid::(0, offset, IO::Target::MIN_SIZE)); + + let view = this.as_view(); + let ptr = IO::Backend::as_ptr(view); + let projected_ptr = ptr.cast::().wrapping_byte_add(offset); + // SAFETY: `offset_valid` checks for size and alignment and therefore `projected_ptr` is a + // valid projection. + unsafe { IO::Backend::project_view(view, projected_ptr) } +} + +/// Returns a view for a given `offset`, performing runtime bound checks. +#[inline] +fn io_view<'a, IO: Io<'a>, U>( + this: IO, + offset: usize, +) -> Result<::View<'a, U>> { + let view = this.as_view(); + let ptr = IO::Backend::as_ptr(view); + + if !offset_valid::(ptr.addr(), offset, KnownSize::size(ptr)) { + return Err(EINVAL); + } + + let projected_ptr = ptr.cast::().wrapping_byte_add(offset); + // SAFETY: `offset_valid` checks for size and alignment and therefore `projected_ptr` is a + // valid projection. + Ok(unsafe { IO::Backend::project_view(view, projected_ptr) }) +} + +/// I/O backends. +/// +/// This is an abstract representation to be implemented by arbitrary I/O +/// backends (e.g. MMIO, PCI config space, etc.). +/// +/// The base trait only defines the projection operations; which I/O methods are available depends +/// on which [`IoCapable`] traits are implemented for the type. For example, for MMIO regions, +/// all widths (u8, u16, u32, and u64 on 64-bit systems) are typically supported. For PCI +/// configuration space, u8, u16, and u32 are supported but u64 is not. +/// +/// This trait is separate from the `Io` trait as multiple different I/O types may share the same +/// operation. +pub trait IoBackend { + /// View type for this I/O backend. + type View<'a, T: ?Sized + KnownSize>: Io<'a, Backend = Self, Target = T>; + + /// Convert a `view` to a raw pointer for projection. + /// + /// The returned pointer is private implementation detail of the backend; it is likely not + /// valid. It should not be dereferenced. + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T; + + /// Project `view` to its subregion indicated by `ptr`. + /// + /// If input `view` is valid, returned view must also be valid. + /// + /// # Safety + /// + /// `ptr` must be a projection of `Self::as_ptr(view)`. + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U>; +} + /// Trait indicating that an I/O backend supports operations of a certain type and providing an /// implementation for these operations. /// @@ -252,22 +327,12 @@ const fn offset_valid(base: usize, offset: usize, size: usize) -> bool { /// For example, a PCI configuration space may implement `IoCapable`, `IoCapable`, /// and `IoCapable`, but not `IoCapable`, while an MMIO region on a 64-bit /// system might implement all four. -pub trait IoCapable { - /// Performs an I/O read of type `T` at `address` and returns the result. - /// - /// # Safety - /// - /// - The range `[address..address + size_of::()]` must be within the bounds of `Self`. - /// - `address` must be aligned. - unsafe fn io_read(self, address: usize) -> T; +pub trait IoCapable: IoBackend { + /// Performs an I/O read of type `T` at `view` and returns the result. + fn io_read<'a>(view: Self::View<'a, T>) -> T; - /// Performs an I/O write of `value` at `address`. - /// - /// # Safety - /// - /// - The range `[address..address + size_of::()]` must be within the bounds of `Self`. - /// - `address` must be aligned. - unsafe fn io_write(self, value: T, address: usize); + /// Performs an I/O write of `value` at `view`. + fn io_write<'a>(view: Self::View<'a, T>, value: T); } /// Describes a given I/O location: its offset, width, and type to convert the raw value from and @@ -319,66 +384,30 @@ impl_usize_ioloc!(u8, u16, u32, u64); /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) /// can perform I/O operations on regions of memory. /// -/// This is an abstract representation to be implemented by arbitrary I/O -/// backends (e.g. MMIO, PCI config space, etc.). -/// /// The [`Io`] trait provides: -/// - Base address and size information +/// - Method to convert into [`IoBackend::View`]. /// - Helper methods for offset validation and address calculation /// - Fallible (runtime checked) accessors for different data widths /// -/// Which I/O methods are available depends on which [`IoCapable`] traits -/// are implemented for the type. +/// Which I/O methods are available depends on the associated [`IoBackend`] implementation. /// /// This should be implemented on cheaply copyable handles, such as references or view types. -/// -/// # Examples -/// -/// For MMIO regions, all widths (u8, u16, u32, and u64 on 64-bit systems) are typically -/// supported. For PCI configuration space, u8, u16, and u32 are supported but u64 is not. -pub trait Io: Copy { +pub trait Io<'a>: Copy { + /// Type that defines all I/O operations. + type Backend: IoBackend; + /// Type of this I/O region. For untyped regions, [`Region`] can be used. type Target: ?Sized + KnownSize; - /// Returns the base address of this mapping. - fn addr(self) -> usize; - - /// Returns the maximum size of this mapping. - fn maxsize(self) -> usize; - - /// Returns the absolute I/O address for a given `offset`, - /// performing compile-time bound checks. - // Always inline to optimize out error path of `build_assert`. - #[inline(always)] - fn io_addr_assert(self, offset: usize) -> usize { - // We cannot check alignment with `offset_valid` using `self.addr()`. So set 0 for it and - // ensure alignment by checking that the alignment of `U` is smaller or equal to the - // alignment of `Self::Target`. - const_assert!(Alignment::of::().as_usize() <= Self::Target::MIN_ALIGN.as_usize()); - build_assert!(offset_valid::(0, offset, Self::Target::MIN_SIZE)); - - self.addr() + offset - } - - /// Returns the absolute I/O address for a given `offset`, - /// performing runtime bound checks. - #[inline] - fn io_addr(self, offset: usize) -> Result { - if !offset_valid::(self.addr(), offset, self.maxsize()) { - return Err(EINVAL); - } - - // Probably no need to check, since the safety requirements of `Self::new` guarantee that - // this can't overflow. - self.addr().checked_add(offset).ok_or(EINVAL) - } + /// Return a view that covers the full region. + fn as_view(self) -> ::View<'a, Self::Target>; /// Fallible 8-bit read with runtime bounds check. #[inline(always)] fn try_read8(self, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_read(offset) } @@ -388,7 +417,7 @@ pub trait Io: Copy { fn try_read16(self, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_read(offset) } @@ -398,7 +427,7 @@ pub trait Io: Copy { fn try_read32(self, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_read(offset) } @@ -408,7 +437,7 @@ pub trait Io: Copy { fn try_read64(self, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_read(offset) } @@ -418,7 +447,7 @@ pub trait Io: Copy { fn try_write8(self, value: u8, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_write(offset, value) } @@ -428,7 +457,7 @@ pub trait Io: Copy { fn try_write16(self, value: u16, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_write(offset, value) } @@ -438,7 +467,7 @@ pub trait Io: Copy { fn try_write32(self, value: u32, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_write(offset, value) } @@ -448,7 +477,7 @@ pub trait Io: Copy { fn try_write64(self, value: u64, offset: usize) -> Result where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.try_write(offset, value) } @@ -458,7 +487,7 @@ pub trait Io: Copy { fn read8(self, offset: usize) -> u8 where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.read(offset) } @@ -468,7 +497,7 @@ pub trait Io: Copy { fn read16(self, offset: usize) -> u16 where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.read(offset) } @@ -478,7 +507,7 @@ pub trait Io: Copy { fn read32(self, offset: usize) -> u32 where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.read(offset) } @@ -488,7 +517,7 @@ pub trait Io: Copy { fn read64(self, offset: usize) -> u64 where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.read(offset) } @@ -498,7 +527,7 @@ pub trait Io: Copy { fn write8(self, value: u8, offset: usize) where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.write(offset, value) } @@ -508,7 +537,7 @@ pub trait Io: Copy { fn write16(self, value: u16, offset: usize) where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.write(offset, value) } @@ -518,7 +547,7 @@ pub trait Io: Copy { fn write32(self, value: u32, offset: usize) where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.write(offset, value) } @@ -528,7 +557,7 @@ pub trait Io: Copy { fn write64(self, value: u64, offset: usize) where usize: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { self.write(offset, value) } @@ -560,12 +589,10 @@ pub trait Io: Copy { fn try_read(self, location: L) -> Result where L: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { - let address = self.io_addr::(location.offset())?; - - // SAFETY: `address` has been validated by `io_addr`. - Ok(unsafe { self.io_read(address) }.into()) + let view = io_view::(self, location.offset())?; + Ok(Self::Backend::io_read(view).into()) } /// Generic fallible write with runtime bounds check. @@ -595,14 +622,11 @@ pub trait Io: Copy { fn try_write(self, location: L, value: T) -> Result where L: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { - let address = self.io_addr::(location.offset())?; + let view = io_view::(self, location.offset())?; let io_value = value.into(); - - // SAFETY: `address` has been validated by `io_addr`. - unsafe { self.io_write(io_value, address) } - + Self::Backend::io_write(view, io_value); Ok(()) } @@ -643,7 +667,7 @@ pub trait Io: Copy { where L: IoLoc, V: LocatedRegister, - Self: IoCapable, + Self::Backend: IoCapable, { let (location, value) = value.into_io_op(); @@ -676,17 +700,14 @@ pub trait Io: Copy { fn try_update(self, location: L, f: F) -> Result where L: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, F: FnOnce(T) -> T, { - let address = self.io_addr::(location.offset())?; + let view = io_view::(self, location.offset())?; - // SAFETY: `address` has been validated by `io_addr`. - let value: T = unsafe { self.io_read(address) }.into(); + let value: T = Self::Backend::io_read(view).into(); let io_value = f(value).into(); - - // SAFETY: `address` has been validated by `io_addr`. - unsafe { self.io_write(io_value, address) } + Self::Backend::io_write(view, io_value); Ok(()) } @@ -716,12 +737,10 @@ pub trait Io: Copy { fn read(self, location: L) -> T where L: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { - let address = self.io_addr_assert::(location.offset()); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_read(address) }.into() + let view = io_view_assert::(self, location.offset()); + Self::Backend::io_read(view).into() } /// Generic infallible write with compile-time bounds check. @@ -749,13 +768,11 @@ pub trait Io: Copy { fn write(self, location: L, value: T) where L: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, { - let address = self.io_addr_assert::(location.offset()); + let view = io_view_assert::(self, location.offset()); let io_value = value.into(); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_write(io_value, address) } + Self::Backend::io_write(view, io_value); } /// Generic infallible write of a fully-located register value. @@ -794,7 +811,7 @@ pub trait Io: Copy { where L: IoLoc, V: LocatedRegister, - Self: IoCapable, + Self::Backend: IoCapable, { let (location, value) = value.into_io_op(); @@ -827,17 +844,13 @@ pub trait Io: Copy { fn update(self, location: L, f: F) where L: IoLoc, - Self: IoCapable, + Self::Backend: IoCapable, F: FnOnce(T) -> T, { - let address = self.io_addr_assert::(location.offset()); - - // SAFETY: `address` has been validated by `io_addr_assert`. - let value: T = unsafe { self.io_read(address) }.into(); + let view = io_view_assert::(self, location.offset()); + let value: T = Self::Backend::io_read(view).into(); let io_value = f(value).into(); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_write(io_value, address) } + Self::Backend::io_write(view, io_value); } } @@ -881,78 +894,78 @@ unsafe impl Send for Mmio<'_, T> {} // SAFETY: `Mmio<'_, T>` is conceptually `&T` but in I/O memory. unsafe impl Sync for Mmio<'_, T> {} -impl Io for Mmio<'_, T> { +impl<'a, T: ?Sized + KnownSize> Io<'a> for Mmio<'a, T> { + type Backend = MmioBackend; type Target = T; #[inline] - fn addr(self) -> usize { - self.ptr.addr() + fn as_view(self) -> Mmio<'a, T> { + self } +} + +/// I/O Backend for memory-mapped I/O. +pub struct MmioBackend; + +impl IoBackend for MmioBackend { + type View<'a, T: ?Sized + KnownSize> = Mmio<'a, T>; #[inline] - fn maxsize(self) -> usize { - KnownSize::size(self.ptr) + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T { + view.ptr + } + + #[inline] + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + _view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U> { + // INVARIANT: Per safety requirement, `ptr` is projection from `view`, so it is also a valid + // memory-mapped I/O region. + Mmio { + ptr, + phantom: PhantomData, + } } } -/// Implements [`IoCapable`] on `$mmio` for `$ty` using `$read_fn` and `$write_fn`. +/// Implements [`IoCapable`] on `$backend` for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_mmio_io_capable { - ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => { - $(#[$attr])* - impl IoCapable<$ty> for $mmio<'_, T> { + ($backend: ident, $ty:ty, $read_fn:ident, $write_fn:ident) => { + impl IoCapable<$ty> for $backend { #[inline] - unsafe fn io_read(self, address: usize) -> $ty { - // SAFETY: By the trait invariant `address` is a valid address for MMIO operations. - unsafe { bindings::$read_fn(address as *const c_void) } + fn io_read(view: <$backend as IoBackend>::View<'_, $ty>) -> $ty { + // SAFETY: `$backend::as_ptr(view)` is a valid pointer for MMIO operations for both + // `MmioBackend` and `RelaxedMmioBackend`. + unsafe { bindings::$read_fn($backend::as_ptr(view).cast_const().cast()) } } #[inline] - unsafe fn io_write(self, value: $ty, address: usize) { - // SAFETY: By the trait invariant `address` is a valid address for MMIO operations. - unsafe { bindings::$write_fn(value, address as *mut c_void) } + fn io_write(view: <$backend as IoBackend>::View<'_, $ty>, value: $ty) { + // SAFETY: `$backend::as_ptr(view)` is a valid pointer for MMIO operations for both + // `MmioBackend` and `RelaxedMmioBackend`. + unsafe { bindings::$write_fn(value, $backend::as_ptr(view).cast()) } } } }; } // MMIO regions support 8, 16, and 32-bit accesses. -impl_mmio_io_capable!(Mmio, u8, readb, writeb); -impl_mmio_io_capable!(Mmio, u16, readw, writew); -impl_mmio_io_capable!(Mmio, u32, readl, writel); +impl_mmio_io_capable!(MmioBackend, u8, readb, writeb); +impl_mmio_io_capable!(MmioBackend, u16, readw, writew); +impl_mmio_io_capable!(MmioBackend, u32, readl, writel); // MMIO regions on 64-bit systems also support 64-bit accesses. #[cfg(CONFIG_64BIT)] -impl_mmio_io_capable!(Mmio, u64, readq, writeq); +impl_mmio_io_capable!(MmioBackend, u64, readq, writeq); -impl<'a, const SIZE: usize> Io for &'a MmioOwned { +impl<'a, const SIZE: usize> Io<'a> for &'a MmioOwned { + type Backend = MmioBackend; type Target = Region; - /// Returns the base address of this mapping. #[inline] - fn addr(self) -> usize { - self.0.addr() - } - - /// Returns the maximum size of this mapping. - #[inline] - fn maxsize(self) -> usize { - self.0.size() - } -} - -impl<'a, const SIZE: usize, T> IoCapable for &'a MmioOwned -where - Mmio<'a, Region>: IoCapable, -{ - #[inline] - unsafe fn io_read(self, address: usize) -> T { - // SAFETY: Per safety requirement. - unsafe { self.as_view().io_read(address) } - } - - #[inline] - unsafe fn io_write(self, value: T, address: usize) { - // SAFETY: Per safety requirement. - unsafe { self.as_view().io_write(value, address) } + fn as_view(self) -> Mmio<'a, Self::Target> { + // SAFETY: `Mmio` has same invariant as `MmioOwned` + unsafe { Mmio::from_raw(self.0) } } } @@ -968,13 +981,6 @@ impl MmioOwned { // SAFETY: `MmioOwned` is a transparent wrapper around `MmioRaw`. unsafe { &*core::ptr::from_ref(raw).cast() } } - - /// Return a view that covers the full region. - #[inline] - pub fn as_view(&self) -> Mmio<'_, Region> { - // SAFETY: `Mmio` has same invariant as `MmioOwned`. - unsafe { Mmio::from_raw(self.0) } - } } /// [`Mmio`] but using relaxed accessors. @@ -993,17 +999,34 @@ impl Clone for RelaxedMmio<'_, T> { } } -impl Io for RelaxedMmio<'_, T> { - type Target = T; +/// I/O Backend for memory-mapped I/O, with relaxed access semantics. +pub struct RelaxedMmioBackend; + +impl IoBackend for RelaxedMmioBackend { + type View<'a, T: ?Sized + KnownSize> = RelaxedMmio<'a, T>; #[inline] - fn addr(self) -> usize { - self.0.addr() + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T { + MmioBackend::as_ptr(view.0) } #[inline] - fn maxsize(self) -> usize { - self.0.maxsize() + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U> { + // SAFETY: Per safety requirement. + RelaxedMmio(unsafe { MmioBackend::project_view(view.0, ptr) }) + } +} + +impl<'a, T: ?Sized + KnownSize> Io<'a> for RelaxedMmio<'a, T> { + type Backend = RelaxedMmioBackend; + type Target = T; + + #[inline] + fn as_view(self) -> RelaxedMmio<'a, T> { + self } } @@ -1036,14 +1059,9 @@ impl<'a, T: ?Sized> Mmio<'a, T> { } // MMIO regions support 8, 16, and 32-bit accesses. -impl_mmio_io_capable!(RelaxedMmio, u8, readb_relaxed, writeb_relaxed); -impl_mmio_io_capable!(RelaxedMmio, u16, readw_relaxed, writew_relaxed); -impl_mmio_io_capable!(RelaxedMmio, u32, readl_relaxed, writel_relaxed); +impl_mmio_io_capable!(RelaxedMmioBackend, u8, readb_relaxed, writeb_relaxed); +impl_mmio_io_capable!(RelaxedMmioBackend, u16, readw_relaxed, writew_relaxed); +impl_mmio_io_capable!(RelaxedMmioBackend, u32, readl_relaxed, writel_relaxed); // MMIO regions on 64-bit systems also support 64-bit accesses. -impl_mmio_io_capable!( - RelaxedMmio, - #[cfg(CONFIG_64BIT)] - u64, - readq_relaxed, - writeq_relaxed -); +#[cfg(CONFIG_64BIT)] +impl_mmio_io_capable!(RelaxedMmioBackend, u64, readq_relaxed, writeq_relaxed); diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 89f4bb483a7f..e67c1e3694fb 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -9,6 +9,7 @@ use crate::{ devres::Devres, io::{ Io, + IoBackend, IoCapable, MmioOwned, MmioRaw, @@ -84,32 +85,57 @@ unsafe impl Send for ConfigSpace<'_, T> {} // SAFETY: `ConfigSpace<'_, T>` is conceptually `&T` but in I/O memory. unsafe impl Sync for ConfigSpace<'_, T> {} +/// I/O Backend for PCI configuration space. +pub struct ConfigSpaceBackend; + +impl IoBackend for ConfigSpaceBackend { + type View<'a, T: ?Sized + KnownSize> = ConfigSpace<'a, T>; + + #[inline] + fn as_ptr<'a, T: ?Sized + KnownSize>(view: ConfigSpace<'a, T>) -> *mut T { + view.ptr + } + + #[inline] + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U> { + // INVARIANT: Per safety requirement. + ConfigSpace { + pdev: view.pdev, + ptr, + } + } +} + /// Implements [`IoCapable`] on [`ConfigSpace`] for `$ty` using `$read_fn` and `$write_fn`. macro_rules! impl_config_space_io_capable { ($ty:ty, $read_fn:ident, $write_fn:ident) => { - impl<'a, T: ?Sized> IoCapable<$ty> for ConfigSpace<'a, T> { - unsafe fn io_read(self, address: usize) -> $ty { + impl IoCapable<$ty> for ConfigSpaceBackend { + fn io_read(view: ConfigSpace<'_, $ty>) -> $ty { + // CAST: The offset is cast to `i32` because the C functions expect a 32-bit + // signed offset parameter. PCI configuration space size is at most 4096 bytes, + // so the value always fits within `i32` without truncation or sign change. + let addr = view.ptr.addr() as i32; + let mut val: $ty = 0; // Return value from C function is ignored in infallible accessors. - let _ret = - // SAFETY: By the type invariant `self.pdev` is a valid address. - // CAST: The offset is cast to `i32` because the C functions expect a 32-bit - // signed offset parameter. PCI configuration space size is at most 4096 bytes, - // so the value always fits within `i32` without truncation or sign change. - unsafe { bindings::$read_fn(self.pdev.as_raw(), address as i32, &mut val) }; - + // SAFETY: By the type invariant `pdev` is a valid address. + let _ = unsafe { bindings::$read_fn(view.pdev.as_raw(), addr, &mut val) }; val } - unsafe fn io_write(self, value: $ty, address: usize) { + fn io_write(view: ConfigSpace<'_, $ty>, value: $ty) { + // CAST: The offset is cast to `i32` because the C functions expect a 32-bit + // signed offset parameter. PCI configuration space size is at most 4096 bytes, + // so the value always fits within `i32` without truncation or sign change. + let addr = view.ptr.addr() as i32; + // Return value from C function is ignored in infallible accessors. - let _ret = - // SAFETY: By the type invariant `self.pdev` is a valid address. - // CAST: The offset is cast to `i32` because the C functions expect a 32-bit - // signed offset parameter. PCI configuration space size is at most 4096 bytes, - // so the value always fits within `i32` without truncation or sign change. - unsafe { bindings::$write_fn(self.pdev.as_raw(), address as i32, value) }; + // SAFETY: By the type invariant `pdev` is a valid address. + let _ = unsafe { bindings::$write_fn(view.pdev.as_raw(), addr, value) }; } } }; @@ -120,17 +146,13 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte); impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word); impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword); -impl<'a, T: ?Sized + KnownSize> Io for ConfigSpace<'a, T> { +impl<'a, T: ?Sized + KnownSize> Io<'a> for ConfigSpace<'a, T> { + type Backend = ConfigSpaceBackend; type Target = T; #[inline] - fn addr(self) -> usize { - self.ptr.addr() - } - - #[inline] - fn maxsize(self) -> usize { - KnownSize::size(self.ptr) + fn as_view(self) -> ConfigSpace<'a, T> { + self } } -- cgit From 0adc93b85374277514e5145970037e3a287b62dd Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:23 +0100 Subject: pwm: th1520: remove unnecessary `deref` `Deref` is automatic and should normally not be used directly. Also, `IoMem` is going to be implementing `Io` directly, so it will no longer to be implementing `Deref`. Reported-by: Andreas Hindborg Link: https://rust-for-linux.zulipchat.com/#narrow/channel/291565-Help/topic/.E2.9C.94.20Projection.20in.20dma.20bus.20address.20space/near/606672061 Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-10-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- drivers/pwm/pwm_th1520.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index 3e3fa51ccef9..022338d17218 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -20,7 +20,6 @@ //! this method is not used in this driver. //! -use core::ops::Deref; use kernel::{ clk::Clk, device::{Bound, Core, Device}, @@ -213,8 +212,7 @@ impl pwm::PwmOps for Th1520PwmDriverData { ) -> Result { let data = chip.drvdata(); let hwpwm = pwm.hwpwm(); - let iomem_accessor = data.iomem.access(parent_dev)?; - let iomap = iomem_accessor.deref(); + let iomap = data.iomem.access(parent_dev)?; let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?; let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?; @@ -248,8 +246,7 @@ impl pwm::PwmOps for Th1520PwmDriverData { ) -> Result { let data = chip.drvdata(); let hwpwm = pwm.hwpwm(); - let iomem_accessor = data.iomem.access(parent_dev)?; - let iomap = iomem_accessor.deref(); + let iomap = data.iomem.access(parent_dev)?; let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?; let was_enabled = duty_cycles != 0; -- cgit From bed01ca9e9cf8f8fea5352c07fa206cc3c106045 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:24 +0100 Subject: rust: io: remove `MmioOwned` `Io` trait is now very easy to implement. Thus, implement it on `Bar` and `IoMem` directly and remove the `MmioOwned` struct. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Suggested-by: Danilo Krummrich Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/near/571198078 Link: https://patch.msgid.link/20260706-io_projection-v6-11-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 12 +++--- rust/kernel/io.rs | 103 +------------------------------------------------- rust/kernel/io/mem.rs | 26 +++++++------ rust/kernel/pci/io.rs | 16 ++++---- 4 files changed, 32 insertions(+), 125 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index aed0c994fd30..3545ffc5345d 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -68,8 +68,9 @@ struct Inner { /// devres::Devres, /// io::{ /// Io, -/// MmioOwned, +/// Mmio, /// MmioRaw, +/// MmioBackend, /// PhysAddr, /// Region, // /// }, @@ -104,12 +105,13 @@ struct Inner { /// } /// } /// -/// impl Deref for IoMem { -/// type Target = MmioOwned; +/// impl<'a, const SIZE: usize> Io<'a> for &'a IoMem { +/// type Backend = MmioBackend; +/// type Target = Region; /// -/// fn deref(&self) -> &Self::Target { +/// fn as_view(self) -> Mmio<'a, Region> { /// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`. -/// unsafe { MmioOwned::from_raw(&self.0) } +/// unsafe { Mmio::from_raw(self.0) } /// } /// } /// # fn no_run(dev: &Device) -> Result<(), Error> { diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 82a3369ae110..729b64a385c3 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -99,8 +99,8 @@ impl KnownSize for Region { /// the represented MMIO region does exist or is properly mapped. /// /// Instead, the bus specific MMIO implementation must convert this raw representation into an -/// `MmioOwned` instance providing the actual memory accessors. Only by the conversion into an -/// `MmioOwned` structure any guarantees are given. +/// `Mmio` instance providing the actual memory accessors. Only by the conversion into an `Mmio` +/// structure any guarantees are given. pub struct MmioRaw { /// Pointer is in I/O address space. /// @@ -157,80 +157,6 @@ impl MmioRaw { } } -/// IO-mapped memory region. -/// -/// The creator (usually a subsystem / bus such as PCI) is responsible for creating the -/// mapping, performing an additional region request etc. -/// -/// # Invariant -/// -/// `addr` is the start and `maxsize` the length of valid I/O mapped memory region of size -/// `maxsize`. -/// -/// # Examples -/// -/// ```no_run -/// use kernel::{ -/// bindings, -/// ffi::c_void, -/// io::{ -/// Io, -/// MmioOwned, -/// MmioRaw, -/// PhysAddr, -/// Region, -/// }, -/// }; -/// use core::ops::Deref; -/// -/// // See also `pci::Bar` for a real example. -/// struct IoMem(MmioRaw>); -/// -/// impl IoMem { -/// /// # Safety -/// /// -/// /// [`paddr`, `paddr` + `SIZE`) must be a valid MMIO region that is mappable into the CPUs -/// /// virtual address space. -/// unsafe fn new(paddr: usize) -> Result{ -/// // SAFETY: By the safety requirements of this function [`paddr`, `paddr` + `SIZE`) is -/// // valid for `ioremap`. -/// let addr = unsafe { bindings::ioremap(paddr as PhysAddr, SIZE) }; -/// if addr.is_null() { -/// return Err(ENOMEM); -/// } -/// -/// Ok(IoMem(MmioRaw::new_region(addr as usize, SIZE)?)) -/// } -/// } -/// -/// impl Drop for IoMem { -/// fn drop(&mut self) { -/// // SAFETY: `self.0.addr()` is guaranteed to be properly mapped by `Self::new`. -/// unsafe { bindings::iounmap(self.0.addr() as *mut c_void); }; -/// } -/// } -/// -/// impl Deref for IoMem { -/// type Target = MmioOwned; -/// -/// fn deref(&self) -> &Self::Target { -/// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`. -/// unsafe { MmioOwned::from_raw(&self.0) } -/// } -/// } -/// -///# fn no_run() -> Result<(), Error> { -/// // SAFETY: Invalid usage for example purposes. -/// let iomem = unsafe { IoMem::<{ core::mem::size_of::() }>::new(0xBAAAAAAD)? }; -/// iomem.write32(0x42, 0x0); -/// assert!(iomem.try_write32(0x42, 0x0).is_ok()); -/// assert!(iomem.try_write32(0x42, 0x4).is_err()); -/// # Ok(()) -/// # } -/// ``` -#[repr(transparent)] -pub struct MmioOwned(MmioRaw>); - /// Checks whether an access of type `U` at the given `base` and the given `offset` /// is valid within this region. /// @@ -958,31 +884,6 @@ impl_mmio_io_capable!(MmioBackend, u32, readl, writel); #[cfg(CONFIG_64BIT)] impl_mmio_io_capable!(MmioBackend, u64, readq, writeq); -impl<'a, const SIZE: usize> Io<'a> for &'a MmioOwned { - type Backend = MmioBackend; - type Target = Region; - - #[inline] - fn as_view(self) -> Mmio<'a, Self::Target> { - // SAFETY: `Mmio` has same invariant as `MmioOwned` - unsafe { Mmio::from_raw(self.0) } - } -} - -impl MmioOwned { - /// Converts an `MmioRaw` into an `MmioOwned` instance, providing the accessors to the MMIO - /// mapping. - /// - /// # Safety - /// - /// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size - /// `maxsize`. - pub unsafe fn from_raw(raw: &MmioRaw>) -> &Self { - // SAFETY: `MmioOwned` is a transparent wrapper around `MmioRaw`. - unsafe { &*core::ptr::from_ref(raw).cast() } - } -} - /// [`Mmio`] but using relaxed accessors. /// /// This type provides an implementation of [`Io`] that uses relaxed I/O MMIO operands instead of diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index 8f6c257c5b8e..d9b3189d09b4 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -2,8 +2,6 @@ //! Generic memory-mapped IO. -use core::ops::Deref; - use crate::{ device::{ Bound, @@ -16,7 +14,9 @@ use crate::{ Region, Resource, // }, - MmioOwned, + Io, + Mmio, + MmioBackend, MmioRaw, // }, prelude::*, @@ -210,11 +210,13 @@ impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> { } } -impl Deref for ExclusiveIoMem<'_, SIZE> { - type Target = MmioOwned; +impl<'a, const SIZE: usize> Io<'a> for &'a ExclusiveIoMem<'_, SIZE> { + type Backend = MmioBackend; + type Target = super::Region; - fn deref(&self) -> &Self::Target { - &self.iomem + #[inline] + fn as_view(self) -> Mmio<'a, Self::Target> { + self.iomem.as_view() } } @@ -290,11 +292,13 @@ impl Drop for IoMem<'_, SIZE> { } } -impl Deref for IoMem<'_, SIZE> { - type Target = MmioOwned; +impl<'a, const SIZE: usize> Io<'a> for &'a IoMem<'_, SIZE> { + type Backend = MmioBackend; + type Target = super::Region; - fn deref(&self) -> &Self::Target { + #[inline] + fn as_view(self) -> Mmio<'a, Self::Target> { // SAFETY: Safe as by the invariant of `IoMem`. - unsafe { MmioOwned::from_raw(&self.io) } + unsafe { Mmio::from_raw(self.io) } } } diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index e67c1e3694fb..4be33ecb4192 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -11,16 +11,14 @@ use crate::{ Io, IoBackend, IoCapable, - MmioOwned, + Mmio, + MmioBackend, MmioRaw, Region, // }, prelude::*, ptr::KnownSize, // }; -use core::{ - ops::Deref, // -}; /// Represents the size of a PCI configuration space. /// @@ -269,12 +267,14 @@ impl Drop for Bar<'_, SIZE> { } } -impl Deref for Bar<'_, SIZE> { - type Target = MmioOwned; +impl<'a, const SIZE: usize> Io<'a> for &'a Bar<'_, SIZE> { + type Backend = MmioBackend; + type Target = crate::io::Region; - fn deref(&self) -> &Self::Target { + #[inline] + fn as_view(self) -> Mmio<'a, Self::Target> { // SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped. - unsafe { MmioOwned::from_raw(&self.io) } + unsafe { Mmio::from_raw(self.io) } } } -- cgit From 9b36c13cbd4fb761212b1ea9a2e89f7df2d3c9f8 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:25 +0100 Subject: rust: io: move `Io` methods to extension trait `Io` trait now has a single required method with many more provided methods. Provided methods may want to rely on their implementations to not be arbitrarily overridden by implementers for correctness or soundness. A good example is the `size` method, it may be relied by unsafe code and thus must be consistent with the metadata obtained from `as_ptr`. Thus, create a new trait to host `size` method, extract existing provided methods to the new trait, and provide a blanket implementation. This pattern is used extensively in userspace Rust libraries e.g. `tokio` where `AsyncRead` has minimum methods and `AsyncReadExt` is what users mostly interact with. To avoid changing all user imports, the base trait is renamed to `IoBase` and the newly added trait takes the existing `Io` name. Reviewed-by: Alexandre Courbot Suggested-by: Danilo Krummrich Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-12-72cd5d055d54@garyguo.net [ Add comment explaining the purpose of the Io blanket implementation. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 3 ++- rust/kernel/io.rs | 36 ++++++++++++++++++++++++++---------- rust/kernel/io/mem.rs | 6 +++--- rust/kernel/pci/io.rs | 6 +++--- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 3545ffc5345d..6e0b845b229b 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -68,6 +68,7 @@ struct Inner { /// devres::Devres, /// io::{ /// Io, +/// IoBase, /// Mmio, /// MmioRaw, /// MmioBackend, @@ -105,7 +106,7 @@ struct Inner { /// } /// } /// -/// impl<'a, const SIZE: usize> Io<'a> for &'a IoMem { +/// impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem { /// type Backend = MmioBackend; /// type Target = Region; /// diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 729b64a385c3..a9ff9e2c9f5c 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -224,7 +224,7 @@ fn io_view<'a, IO: Io<'a>, U>( /// operation. pub trait IoBackend { /// View type for this I/O backend. - type View<'a, T: ?Sized + KnownSize>: Io<'a, Backend = Self, Target = T>; + type View<'a, T: ?Sized + KnownSize>: IoBase<'a, Backend = Self, Target = T>; /// Convert a `view` to a raw pointer for projection. /// @@ -310,15 +310,12 @@ impl_usize_ioloc!(u8, u16, u32, u64); /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) /// can perform I/O operations on regions of memory. /// -/// The [`Io`] trait provides: -/// - Method to convert into [`IoBackend::View`]. -/// - Helper methods for offset validation and address calculation -/// - Fallible (runtime checked) accessors for different data widths -/// -/// Which I/O methods are available depends on the associated [`IoBackend`] implementation. +/// This trait defines which backend shall be used for I/O operations and provides a method to +/// convert into [`IoBackend::View`]. Users should use the [`Io`] trait which provides the actual +/// methods to perform I/O operations. /// /// This should be implemented on cheaply copyable handles, such as references or view types. -pub trait Io<'a>: Copy { +pub trait IoBase<'a>: Copy { /// Type that defines all I/O operations. type Backend: IoBackend; @@ -327,6 +324,21 @@ pub trait Io<'a>: Copy { /// Return a view that covers the full region. fn as_view(self) -> ::View<'a, Self::Target>; +} + +/// Extension trait to provide I/O operation methods to types that implement [`IoBase`]. +/// +/// This trait provides: +/// - Helper methods for offset validation and address calculation +/// - Fallible (runtime checked) accessors for different data widths +/// +/// Which I/O methods are available depends on the associated [`IoBackend`] implementation. +pub trait Io<'a>: IoBase<'a> { + /// Returns the size of this I/O region. + #[inline] + fn size(self) -> usize { + KnownSize::size(Self::Backend::as_ptr(self.as_view())) + } /// Fallible 8-bit read with runtime bounds check. #[inline(always)] @@ -780,6 +792,10 @@ pub trait Io<'a>: Copy { } } +// Blanket implementation ensures that provided methods cannot be arbitrarily overridden by +// implementers, which is relied upon for correctness and soundness. +impl<'a, T: IoBase<'a>> Io<'a> for T {} + /// A view of memory-mapped I/O region. /// /// # Invariant @@ -820,7 +836,7 @@ unsafe impl Send for Mmio<'_, T> {} // SAFETY: `Mmio<'_, T>` is conceptually `&T` but in I/O memory. unsafe impl Sync for Mmio<'_, T> {} -impl<'a, T: ?Sized + KnownSize> Io<'a> for Mmio<'a, T> { +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for Mmio<'a, T> { type Backend = MmioBackend; type Target = T; @@ -921,7 +937,7 @@ impl IoBackend for RelaxedMmioBackend { } } -impl<'a, T: ?Sized + KnownSize> Io<'a> for RelaxedMmio<'a, T> { +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for RelaxedMmio<'a, T> { type Backend = RelaxedMmioBackend; type Target = T; diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index d9b3189d09b4..e95b769ebe47 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -14,7 +14,7 @@ use crate::{ Region, Resource, // }, - Io, + IoBase, Mmio, MmioBackend, MmioRaw, // @@ -210,7 +210,7 @@ impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> { } } -impl<'a, const SIZE: usize> Io<'a> for &'a ExclusiveIoMem<'_, SIZE> { +impl<'a, const SIZE: usize> IoBase<'a> for &'a ExclusiveIoMem<'_, SIZE> { type Backend = MmioBackend; type Target = super::Region; @@ -292,7 +292,7 @@ impl Drop for IoMem<'_, SIZE> { } } -impl<'a, const SIZE: usize> Io<'a> for &'a IoMem<'_, SIZE> { +impl<'a, const SIZE: usize> IoBase<'a> for &'a IoMem<'_, SIZE> { type Backend = MmioBackend; type Target = super::Region; diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 4be33ecb4192..4d1d0afdc491 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -8,8 +8,8 @@ use crate::{ device, devres::Devres, io::{ - Io, IoBackend, + IoBase, IoCapable, Mmio, MmioBackend, @@ -144,7 +144,7 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte); impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word); impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword); -impl<'a, T: ?Sized + KnownSize> Io<'a> for ConfigSpace<'a, T> { +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for ConfigSpace<'a, T> { type Backend = ConfigSpaceBackend; type Target = T; @@ -267,7 +267,7 @@ impl Drop for Bar<'_, SIZE> { } } -impl<'a, const SIZE: usize> Io<'a> for &'a Bar<'_, SIZE> { +impl<'a, const SIZE: usize> IoBase<'a> for &'a Bar<'_, SIZE> { type Backend = MmioBackend; type Target = crate::io::Region; -- cgit From 1f989555fe5c823de108d55603ca3cb30053fb45 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:26 +0100 Subject: rust: io: add projection macro and methods Add an `io_project!()` macro to allow projection from `Io` to a subview of it, using the pointer projection mechanism to perform compile-time checks. For cases where type-casting is required, the `try_cast()` function may be used where the size and alignment checks are performed at runtime. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-13-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index a9ff9e2c9f5c..9f6515a717de 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -49,6 +49,7 @@ pub type ResourceSize = bindings::resource_size_t; /// - Size of the region is at least as large as the `SIZE` generic parameter. /// - Size of the region is multiple of 4. #[repr(C, align(4))] +#[derive(FromBytes)] pub struct Region { inner: [u8], } @@ -91,6 +92,19 @@ impl KnownSize for Region { } } +// SAFETY: +// - Values read from I/O are always treated as initialized. +// - Per type invariant the size is multiple of 4 and the type is 4-byte aligned, so it is padding +// free. +// +// This cannot be derived as `derive(IntoBytes)` as the padding free property comes from type +// invariant which the macro does not know. +unsafe impl IntoBytes for Region { + #[inline] + #[allow(unused)] // Rust 1.87+ stops requiring this and will emit unused warnings. + fn only_derive_is_allowed_to_implement_this_trait() {} +} + /// Raw representation of an MMIO region. /// /// `MmioRaw` is equivalent to `T __iomem *` in C. @@ -340,6 +354,51 @@ pub trait Io<'a>: IoBase<'a> { KnownSize::size(Self::Backend::as_ptr(self.as_view())) } + /// Try to convert into a different typed I/O view. + /// + /// A runtime check is performed to ensure that the target type is of same or smaller size to + /// current type, and the current view is properly aligned for the target type. Returns + /// `Err(EINVAL)` if the runtime check fails. + /// + /// # Examples + /// + /// ```no_run + /// use kernel::io::{ + /// io_project, + /// Mmio, + /// Io, + /// Region, + /// }; + /// #[derive(FromBytes, IntoBytes)] + /// #[repr(C)] + /// struct MyStruct { field: u32, } + /// + /// # fn test(mmio: &Mmio<'_, Region>) -> Result { + /// // let mmio: Mmio<'_, Region>; + /// let whole: Mmio<'_, MyStruct> = mmio.try_cast()?; + /// # Ok::<(), Error>(()) } + /// ``` + #[inline] + fn try_cast(self) -> Result<::View<'a, U>> + where + Self::Target: FromBytes + IntoBytes, + U: FromBytes + IntoBytes, + { + let view = self.as_view(); + let ptr = Self::Backend::as_ptr(view); + + if size_of::() > KnownSize::size(ptr) { + return Err(EINVAL); + } + + if ptr.addr() % align_of::() != 0 { + return Err(EINVAL); + } + + // SAFETY: We have checked bounds and alignment, so this is a valid projection. + Ok(unsafe { Self::Backend::project_view(view, ptr.cast()) }) + } + /// Fallible 8-bit read with runtime bounds check. #[inline(always)] fn try_read8(self, offset: usize) -> Result @@ -982,3 +1041,72 @@ impl_mmio_io_capable!(RelaxedMmioBackend, u32, readl_relaxed, writel_relaxed); // MMIO regions on 64-bit systems also support 64-bit accesses. #[cfg(CONFIG_64BIT)] impl_mmio_io_capable!(RelaxedMmioBackend, u64, readq_relaxed, writeq_relaxed); + +// This helper turns associated functions to methods so it can be invoked in macro. +// Used by `io_project!()` only. +#[doc(hidden)] +#[derive(Clone, Copy)] +pub struct ProjectHelper(pub T); + +impl<'a, T> ProjectHelper +where + T: Io<'a, Backend: IoBackend = T>>, +{ + // These helper methods must not have symbols present in the binary to avoid confusion. + #[inline(always)] + pub fn as_ptr(self) -> *mut T::Target { + T::Backend::as_ptr(self.0) + } + + /// # Safety + /// + /// Same as `IoBackend::project_view` + #[inline(always)] + pub unsafe fn project_view( + self, + ptr: *mut U, + ) -> ::View<'a, U> { + // SAFETY: Per safety requirement. + unsafe { T::Backend::project_view::(self.0, ptr) } + } +} + +/// Project an I/O type to a subview of it. +/// +/// The syntax is of form `io_project!(io, proj)` where `io` is an expression to a type that +/// implements [`Io`] and `proj` is a [projection specification](kernel::ptr::project!). +/// +/// # Examples +/// +/// ``` +/// use kernel::io::{ +/// io_project, +/// Mmio, +/// }; +/// #[repr(C)] +/// struct MyStruct { field: u32, } +/// +/// # fn test(mmio: Mmio<'_, [MyStruct]>) -> Result { +/// // let mmio: Mmio<[MyStruct]>; +/// let field: Mmio<'_, u32> = io_project!(mmio, [try: 1].field); +/// let whole: Mmio<'_, MyStruct> = io_project!(mmio, [try: 2]); +/// let nested: Mmio<'_, u32> = io_project!(whole, .field); +/// # Ok::<(), Error>(()) } +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! io_project { + ($io:expr, $($proj:tt)*) => {{ + #[allow(unused)] + use $crate::io::IoBase as _; + let view = $crate::io::ProjectHelper($io.as_view()); + let ptr = $crate::ptr::project!( + mut view.as_ptr(), $($proj)* + ); + #[allow(unused_unsafe)] + // SAFETY: `ptr` is a projection. + unsafe { view.project_view(ptr) } + }}; +} +#[doc(inline)] +pub use crate::io_project; -- cgit From 2fabff7807853804af2ae691b263c3ac5cc9a636 Mon Sep 17 00:00:00 2001 From: Laura Nao Date: Mon, 6 Jul 2026 13:44:27 +0100 Subject: rust: io: add I/O backend for system memory with volatile access Add `SysMem`, an `Io` trait implementation for kernel virtual address ranges. It uses volatile accessors to provide safe access to shared memory that may be concurrently accessed by external hardware. Implement `IoCapable` for `u8`, `u16`, `u32`, and `u64` (for 64-bit system). This can be used instead of `Coherent` for cases where a different layer takes care of mapping the system memory to the device (e.g. dma-buf or GPUVM). Signed-off-by: Laura Nao [ Rebased and adapted on top of I/O rework. - Gary ] Co-developed-by: Gary Guo Signed-off-by: Gary Guo Reviewed-by: Alexandre Courbot Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-14-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 9f6515a717de..c212de8860ac 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -1042,6 +1042,128 @@ impl_mmio_io_capable!(RelaxedMmioBackend, u32, readl_relaxed, writel_relaxed); #[cfg(CONFIG_64BIT)] impl_mmio_io_capable!(RelaxedMmioBackend, u64, readq_relaxed, writeq_relaxed); +/// I/O Backend for system memory. +pub struct SysMemBackend; + +impl IoBackend for SysMemBackend { + type View<'a, T: ?Sized + KnownSize> = SysMem<'a, T>; + + #[inline] + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T { + view.ptr + } + + #[inline] + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + _view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U> { + // INVARIANT: Per safety requirement, `ptr` is projection from `view`, so it is also a valid + // kernel accessible memory region. + SysMem { + ptr, + phantom: PhantomData, + } + } +} + +/// Implements [`IoCapable`] on `SysMemBackend` for `$ty` using `read_volatile` and +/// `write_volatile`. +macro_rules! impl_sysmem_io_capable { + ($ty:ty) => { + impl IoCapable<$ty> for SysMemBackend { + #[inline] + fn io_read(view: SysMem<'_, $ty>) -> $ty { + // SAFETY: + // - Per type invariant, `ptr` is valid and aligned. + // - Using read_volatile() here so that race with hardware is well-defined. + // - Using read_volatile() here is not sound if it races with other CPU per Rust + // rules, but this is allowed per LKMM. + // - The macro is only used on primitives so all bit patterns are valid. + unsafe { view.ptr.read_volatile() } + } + + #[inline] + fn io_write(view: SysMem<'_, $ty>, value: $ty) { + // SAFETY: + // - Per type invariant, `ptr` is valid and aligned. + // - Using write_volatile() here so that race with hardware is well-defined. + // - Using write_volatile() here is not sound if it races with other CPU per Rust + // rules, but this is allowed per LKMM. + unsafe { view.ptr.write_volatile(value) } + } + } + }; +} + +impl_sysmem_io_capable!(u8); +impl_sysmem_io_capable!(u16); +impl_sysmem_io_capable!(u32); +#[cfg(CONFIG_64BIT)] +impl_sysmem_io_capable!(u64); + +/// A view of a system memory region. +/// +/// Provides `Io` trait implementation for kernel virtual address ranges, +/// using volatile read/write to safely access shared memory that may be +/// concurrently accessed by external hardware. +/// +/// # Invariants +/// +/// `self.ptr.addr() .. self.ptr.addr() + KnownSize::size(self.ptr)` is valid and aligned kernel +/// accessible memory region for the lifetime `'a`. +pub struct SysMem<'a, T: ?Sized> { + ptr: *mut T, + phantom: PhantomData<&'a ()>, +} + +impl Copy for SysMem<'_, T> {} +impl Clone for SysMem<'_, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +// SAFETY: `SysMem<'_, T>` is conceptually `&T`. +unsafe impl Send for SysMem<'_, T> {} + +// SAFETY: `SysMem<'_, T>` is conceptually `&T`. +unsafe impl Sync for SysMem<'_, T> {} + +impl<'a, T: ?Sized> SysMem<'a, T> { + /// Create a `SysMem` from a raw pointer. + /// + /// # Safety + /// + /// `ptr.addr() .. ptr.addr() + KnownSize::size(ptr)` must be valid and aligned kernel + /// accessible memory region for the lifetime `'a`. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: Per safety requirement. + Self { + ptr, + phantom: PhantomData, + } + } + + /// Obtain the raw pointer to the memory. + #[inline] + pub fn as_ptr(self) -> *mut T { + self.ptr + } +} + +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for SysMem<'a, T> { + type Backend = SysMemBackend; + type Target = T; + + #[inline] + fn as_view(self) -> ::View<'a, Self::Target> { + self + } +} + // This helper turns associated functions to methods so it can be invoked in macro. // Used by `io_project!()` only. #[doc(hidden)] -- cgit From 1d409d1e7a874b3aeff8908292bc26ba4113cc06 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:28 +0100 Subject: rust: io: implement a view type for `Coherent` Implement a `CoherentView` type which is a view of `Coherent`. To be able to give out DMA handles, the view type contains both CPU and DMA pointers, and the projection method projects both at once. Delegate most of the `Io` implementation to `SysMemBackend`. Provide a method to erase the DMA handle and give out a `SysMem` view, if the user does not need the `dma_handle`. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-15-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/dma.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index 200def84fb69..1535bc6eec64 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -14,14 +14,21 @@ use crate::{ }, error::to_result, fs::file, + io::{ + IoBackend, + IoBase, + IoCapable, + SysMem, + SysMemBackend, // + }, prelude::*, ptr::KnownSize, sync::aref::ARef, transmute::{ AsBytes, FromBytes, // - }, // - uaccess::UserSliceWriter, + }, + uaccess::UserSliceWriter, // }; use core::{ ops::{ @@ -1133,6 +1140,133 @@ unsafe impl Send for CoherentHandle {} // plain `Copy` values. unsafe impl Sync for CoherentHandle {} +/// View type for `Coherent`. +/// +/// This is same as [`SysMem`] but with additional information that allows handing out a DMA handle. +pub struct CoherentView<'a, T: ?Sized> { + cpu_addr: SysMem<'a, T>, + dma_handle: DmaAddress, +} + +impl Copy for CoherentView<'_, T> {} +impl Clone for CoherentView<'_, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: ?Sized> CoherentView<'a, T> { + /// Erase the DMA handle information and obtain a [`SysMem`] view of the same memory region. + #[inline] + pub fn as_sys_mem(self) -> SysMem<'a, T> { + self.cpu_addr + } + + /// Returns a DMA handle which may be given to the device as the DMA address base of the region. + #[inline] + pub fn dma_handle(self) -> DmaAddress { + self.dma_handle + } + + /// Returns a reference to the data in the region. + /// + /// # Safety + /// + /// * Callers must ensure that the device does not read/write to/from memory while the returned + /// reference is live. + /// * Callers must ensure that this call does not race with a write (including call to `as_mut`) + /// to the same region while the returned reference is live. + #[inline] + pub unsafe fn as_ref(self) -> &'a T { + // SAFETY: pointer is aligned and valid per type invariant. Aliasing rule is satisfied per + // safety requirement. + unsafe { &*self.cpu_addr.as_ptr() } + } + + /// Returns a mutable reference to the data in the region. + /// + /// # Safety + /// + /// * Callers must ensure that the device does not read/write to/from memory while the returned + /// reference is live. + /// * Callers must ensure that this call does not race with a read (including call to `as_ref`) + /// or write (including call to `as_mut`) to the same region while the returned reference is + /// live. + #[inline] + pub unsafe fn as_mut(self) -> &'a mut T { + // SAFETY: pointer is aligned and valid per type invariant. Aliasing rule is satisfied per + // safety requirement. + unsafe { &mut *self.cpu_addr.as_ptr() } + } +} + +/// `IoBackend` implementation for `Coherent`. +pub struct CoherentIoBackend; + +impl IoBackend for CoherentIoBackend { + type View<'a, T: ?Sized + KnownSize> = CoherentView<'a, T>; + + #[inline] + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T { + SysMemBackend::as_ptr(view.cpu_addr) + } + + #[inline] + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U> { + let offset = ptr.addr() - view.cpu_addr.as_ptr().addr(); + // CAST: The offset DMA address can never overflow. + let dma_handle = view.dma_handle + offset as DmaAddress; + CoherentView { + dma_handle, + // SAFETY: Per safety requirement. + cpu_addr: unsafe { SysMemBackend::project_view(view.cpu_addr, ptr) }, + } + } +} + +impl IoCapable for CoherentIoBackend +where + SysMemBackend: IoCapable, +{ + #[inline] + fn io_read<'a>(view: Self::View<'a, T>) -> T { + SysMemBackend::io_read(view.cpu_addr) + } + + #[inline] + fn io_write<'a>(view: Self::View<'a, T>, value: T) { + SysMemBackend::io_write(view.cpu_addr, value) + } +} + +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for CoherentView<'a, T> { + type Backend = CoherentIoBackend; + type Target = T; + + #[inline] + fn as_view(self) -> CoherentView<'a, Self::Target> { + self + } +} + +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for &'a Coherent { + type Backend = CoherentIoBackend; + type Target = T; + + #[inline] + fn as_view(self) -> CoherentView<'a, Self::Target> { + CoherentView { + // SAFETY: `cpu_addr` is valid and aligned kernel accessible memory. + cpu_addr: unsafe { SysMem::new(self.cpu_addr.as_ptr()) }, + dma_handle: self.dma_handle, + } + } +} + /// Reads a field of an item from an allocated region of structs. /// /// The syntax is of the form `kernel::dma_read!(dma, proj)` where `dma` is an expression evaluating -- cgit From 89814c42c19ea63600f7235156ae665f6bf8b369 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:29 +0100 Subject: rust: io: add `read_val` and `write_val` functions on `Io` Provide `read_val` and `write_val` that allow I/O views to be accessed when they're narrowed down to just views of primitives. This is used to provide `io_read!` and `io_write!` macros, which are generalized version of current `dma_read!` and `dma_write!` macro that work for all types that implement `Io`. Note though `io_read!` and `io_write!` only works if backend implements `IoCapable` for the type; which is typically only implemented for atomically accessible primitives. `dma_read!` and `dma_write!` currently supports them via `read_volatile` and `write_volatile`; this can be undesirable for aggregates as LLVM may turn them to multiple instructions to access parts and re-assemble, even if they could be combined to a single instruction. Thus, `io_read!()` and `io_write!()` does not fully replace `dma_read!()` and `dma_write!()` in this scenario. The ability to read/write aggregates (when atomicity is of no concern) is better served with copying primitives (e.g. memcpy_{from,to}io). Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-16-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index c212de8860ac..3f430bce61e5 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -399,6 +399,50 @@ pub trait Io<'a>: IoBase<'a> { Ok(unsafe { Self::Backend::project_view(view, ptr.cast()) }) } + /// Read a value from I/O. + /// + /// This only works for primitives supported by the I/O backend. + /// + /// # Examples + /// + /// ```no_run + /// # use kernel::io::*; + /// # fn test_read_val(mmio: Mmio<'_, u32>) { + /// // let mmio: Mmio<'_, u32>; + /// let val: u32 = mmio.read_val(); + /// # } + /// ``` + #[inline] + fn read_val(self) -> Self::Target + where + Self::Backend: IoCapable, + Self::Target: Sized, + { + Self::Backend::io_read(self.as_view()) + } + + /// Write a value to I/O. + /// + /// This only works for primitives supported by the I/O backend. + /// + /// # Examples + /// + /// ```no_run + /// # use kernel::io::*; + /// # fn test_write_val(mmio: Mmio<'_, u32>) { + /// // let mmio: Mmio<'_, u32>; + /// mmio.write_val(1u32); + /// # } + /// ``` + #[inline] + fn write_val(self, value: Self::Target) + where + Self::Backend: IoCapable, + Self::Target: Sized, + { + Self::Backend::io_write(self.as_view(), value) + } + /// Fallible 8-bit read with runtime bounds check. #[inline(always)] fn try_read8(self, offset: usize) -> Result @@ -1232,3 +1276,65 @@ macro_rules! io_project { } #[doc(inline)] pub use crate::io_project; + +/// Read from I/O memory. +/// +/// The syntax is of form `io_read!(io, proj)` where `io` is an expression to a type that +/// implements [`Io`] and `proj` is a [projection specification](kernel::ptr::project!). +/// +/// # Examples +/// +/// ``` +/// #[repr(C)] +/// struct MyStruct { field: u32, } +/// +/// # fn test(mmio: kernel::io::Mmio<'_, [MyStruct]>) -> Result { +/// // let mmio: Mmio<'_, [MyStruct]>; +/// let field: u32 = kernel::io::io_read!(mmio, [try: 2].field); +/// # Ok::<(), Error>(()) } +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! io_read { + ($io:expr, $($proj:tt)*) => { + $crate::io::Io::read_val($crate::io_project!($io, $($proj)*)) + }; +} +#[doc(inline)] +pub use crate::io_read; + +/// Writes to I/O memory. +/// +/// The syntax is of form `io_write!(io, proj, val)` where `io` is an expression to a type that +/// implements [`Io`] and `proj` is a [projection specification](kernel::ptr::project!), +/// and `val` is the value to be written to the projected location. +/// +/// # Examples +/// +/// ``` +/// #[repr(C)] +/// struct MyStruct { field: u32, } +/// +/// # fn test(mmio: kernel::io::Mmio<'_, [MyStruct]>) -> Result { +/// // let mmio: Mmio<'_, [MyStruct]>; +/// kernel::io::io_write!(mmio, [try: 2].field, 10); +/// # Ok::<(), Error>(()) } +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! io_write { + (@parse [$io:expr] [$($proj:tt)*] [, $val:expr]) => { + $crate::io::Io::write_val($crate::io_project!($io, $($proj)*), $val) + }; + (@parse [$io:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => { + $crate::io_write!(@parse [$io] [$($proj)* .$field] [$($rest)*]) + }; + (@parse [$io:expr] [$($proj:tt)*] [[$flavor:ident: $index:expr] $($rest:tt)*]) => { + $crate::io_write!(@parse [$io] [$($proj)* [$flavor: $index]] [$($rest)*]) + }; + ($io:expr, $($rest:tt)*) => { + $crate::io_write!(@parse [$io] [] [$($rest)*]) + }; +} +#[doc(inline)] +pub use crate::io_write; -- cgit From 0722567f5085bfc48d8b01c5c759745997e94785 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:30 +0100 Subject: gpu: nova-core: use I/O projection for cleaner encapsulation Use `io_project!` for PTE array and message queues to restore the proper encapsulation. The remaining `dma_read!` and `dma_write!` is now only acting on primitives; thus replace by `io_read!` and `io_write!`. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260706-io_projection-v6-17-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- drivers/gpu/nova-core/gsp.rs | 53 ++++++++++++------------- drivers/gpu/nova-core/gsp/cmdq.rs | 66 +++++++++++++++++-------------- drivers/gpu/nova-core/gsp/fw.rs | 82 +++++++++++++-------------------------- 3 files changed, 90 insertions(+), 111 deletions(-) diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs index 69175ca3315c..cfa7553cd820 100644 --- a/drivers/gpu/nova-core/gsp.rs +++ b/drivers/gpu/nova-core/gsp.rs @@ -9,14 +9,16 @@ use kernel::{ dma::{ Coherent, CoherentBox, + CoherentView, DmaAddress, // }, + io::{ + io_project, + io_write, + Io, // + }, pci, - prelude::*, - transmute::{ - AsBytes, - FromBytes, // - }, // + prelude::*, // }; pub(crate) mod cmdq; @@ -48,21 +50,21 @@ const LOG_BUFFER_SIZE: usize = RM_LOG_BUFFER_NUM_PAGES * GSP_PAGE_SIZE; /// Array of page table entries, as understood by the GSP bootloader. #[repr(C)] +#[derive(FromBytes, IntoBytes)] struct PteArray([u64; NUM_ENTRIES]); -/// SAFETY: arrays of `u64` implement `FromBytes` and we are but a wrapper around one. -unsafe impl FromBytes for PteArray {} - -/// SAFETY: arrays of `u64` implement `AsBytes` and we are but a wrapper around one. -unsafe impl AsBytes for PteArray {} - impl PteArray { - /// Returns the page table entry for `index`, for a mapping starting at `start`. - // TODO: Replace with `IoView` projection once available. - fn entry(start: DmaAddress, index: usize) -> Result { - start - .checked_add(num::usize_as_u64(index) << GSP_PAGE_SHIFT) - .ok_or(EOVERFLOW) + /// Initialize a new page table array mapping `NUM_PAGES` GSP pages starting at address `start`. + fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> { + for i in 0..NUM_PAGES { + io_write!(view, .0[build: i], + start + .checked_add(num::usize_as_u64(i) << GSP_PAGE_SHIFT) + .ok_or(EOVERFLOW)? + ); + } + + Ok(()) } } @@ -89,17 +91,12 @@ impl LogBuffer { let start_addr = obj.0.dma_handle(); - // SAFETY: `obj` has just been created and we are its sole user. - let pte_region = unsafe { - &mut obj.0.as_mut()[size_of::()..][..RM_LOG_BUFFER_NUM_PAGES * size_of::()] - }; - - // Write values one by one to avoid an on-stack instance of `PteArray`. - for (i, chunk) in pte_region.chunks_exact_mut(size_of::()).enumerate() { - let pte_value = PteArray::<0>::entry(start_addr, i)?; - - chunk.copy_from_slice(&pte_value.to_ne_bytes()); - } + let pte_view = io_project!( + obj.0, + [build: size_of::()..][build: ..RM_LOG_BUFFER_NUM_PAGES * size_of::()] + ) + .try_cast::>()?; + PteArray::init(pte_view, start_addr)?; Ok(obj) } diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs index 070de0731e95..c34b48961496 100644 --- a/drivers/gpu/nova-core/gsp/cmdq.rs +++ b/drivers/gpu/nova-core/gsp/cmdq.rs @@ -2,16 +2,23 @@ mod continuation; -use core::mem; +use core::{ + mem, + sync::atomic::{ + fence, + Ordering, // + }, +}; use kernel::{ device, dma::{ Coherent, + CoherentBox, DmaAddress, // }, - dma_write, io::{ + io_project, poll::read_poll_timeout, Io, // }, @@ -171,20 +178,18 @@ static_assert!(align_of::() == GSP_PAGE_SIZE); #[repr(C)] // There is no struct defined for this in the open-gpu-kernel-source headers. // Instead it is defined by code in `GspMsgQueuesInit()`. -// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module. -pub(super) struct Msgq { +struct Msgq { /// Header for sending messages, including the write pointer. - pub(super) tx: MsgqTxHeader, + tx: MsgqTxHeader, /// Header for receiving messages, including the read pointer. - pub(super) rx: MsgqRxHeader, + rx: MsgqRxHeader, /// The message queue proper. msgq: MsgqData, } /// Structure shared between the driver and the GSP and containing the command and message queues. #[repr(C)] -// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module. -pub(super) struct GspMem { +struct GspMem { /// Self-mapping page table entries. ptes: PteArray<{ Self::PTE_ARRAY_SIZE }>, /// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the @@ -192,13 +197,13 @@ pub(super) struct GspMem { /// index into the GSP queue. /// /// This member is read-only for the GSP. - pub(super) cpuq: Msgq, + cpuq: Msgq, /// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the /// write and read pointers that the GSP updates. This means that the read pointer here is an /// index into the CPU queue. /// /// This member is read-only for the driver. - pub(super) gspq: Msgq, + gspq: Msgq, } impl GspMem { @@ -232,20 +237,12 @@ impl DmaGspMem { const MSGQ_SIZE: u32 = num::usize_into_u32::<{ size_of::() }>(); const RX_HDR_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>(); - let gsp_mem = Coherent::::zeroed(dev, GFP_KERNEL)?; - - let start = gsp_mem.dma_handle(); - // Write values one by one to avoid an on-stack instance of `PteArray`. - for i in 0..GspMem::PTE_ARRAY_SIZE { - dma_write!(gsp_mem, .ptes.0[build: i], PteArray::<0>::entry(start, i)?); - } + let mut gsp_mem = CoherentBox::::zeroed(dev, GFP_KERNEL)?; + gsp_mem.cpuq.tx = MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES); + gsp_mem.cpuq.rx = MsgqRxHeader::new(); - dma_write!( - gsp_mem, - .cpuq.tx, - MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES) - ); - dma_write!(gsp_mem, .cpuq.rx, MsgqRxHeader::new()); + let gsp_mem: Coherent<_> = gsp_mem.into(); + PteArray::init(io_project!(gsp_mem, .ptes), gsp_mem.dma_handle())?; Ok(Self(gsp_mem)) } @@ -406,7 +403,7 @@ impl DmaGspMem { // // - The returned value is within `0..MSGQ_NUM_PAGES`. fn gsp_write_ptr(&self) -> u32 { - super::fw::gsp_mem::gsp_write_ptr(&self.0) + MsgqTxHeader::write_ptr(io_project!(self.0, .gspq.tx)) % MSGQ_NUM_PAGES } // Returns the index of the memory page the GSP will read the next command from. @@ -415,7 +412,7 @@ impl DmaGspMem { // // - The returned value is within `0..MSGQ_NUM_PAGES`. fn gsp_read_ptr(&self) -> u32 { - super::fw::gsp_mem::gsp_read_ptr(&self.0) + MsgqRxHeader::read_ptr(io_project!(self.0, .gspq.rx)) % MSGQ_NUM_PAGES } // Returns the index of the memory page the CPU can read the next message from. @@ -424,12 +421,18 @@ impl DmaGspMem { // // - The returned value is within `0..MSGQ_NUM_PAGES`. fn cpu_read_ptr(&self) -> u32 { - super::fw::gsp_mem::cpu_read_ptr(&self.0) + MsgqRxHeader::read_ptr(io_project!(self.0, .cpuq.rx)) % MSGQ_NUM_PAGES } // Informs the GSP that it can send `elem_count` new pages into the message queue. fn advance_cpu_read_ptr(&mut self, elem_count: u32) { - super::fw::gsp_mem::advance_cpu_read_ptr(&self.0, elem_count) + let rx = io_project!(self.0, .cpuq.rx); + let rptr = MsgqRxHeader::read_ptr(rx).wrapping_add(elem_count) % MSGQ_NUM_PAGES; + + // Ensure read pointer is properly ordered. + fence(Ordering::SeqCst); + + MsgqRxHeader::set_read_ptr(rx, rptr) } // Returns the index of the memory page the CPU can write the next command to. @@ -438,12 +441,17 @@ impl DmaGspMem { // // - The returned value is within `0..MSGQ_NUM_PAGES`. fn cpu_write_ptr(&self) -> u32 { - super::fw::gsp_mem::cpu_write_ptr(&self.0) + MsgqTxHeader::write_ptr(io_project!(self.0, .cpuq.tx)) % MSGQ_NUM_PAGES } // Informs the GSP that it can process `elem_count` new pages from the command queue. fn advance_cpu_write_ptr(&mut self, elem_count: u32) { - super::fw::gsp_mem::advance_cpu_write_ptr(&self.0, elem_count) + let tx = io_project!(self.0, .cpuq.tx); + let wptr = MsgqTxHeader::write_ptr(tx).wrapping_add(elem_count) % MSGQ_NUM_PAGES; + MsgqTxHeader::set_write_ptr(tx, wptr); + + // Ensure all command data is visible before triggering the GSP read. + fence(Ordering::SeqCst); } } diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs index 4db0cfa4dc4d..b0e7de328eaf 100644 --- a/drivers/gpu/nova-core/gsp/fw.rs +++ b/drivers/gpu/nova-core/gsp/fw.rs @@ -10,7 +10,14 @@ use r570_144 as bindings; use core::ops::Range; use kernel::{ - dma::Coherent, + dma::{ + Coherent, + CoherentView, // + }, + io::{ + io_read, + io_write, // + }, prelude::*, ptr::{ Alignable, @@ -44,59 +51,6 @@ use crate::{ }, }; -// TODO: Replace with `IoView` projections once available. -pub(super) mod gsp_mem { - use core::sync::atomic::{ - fence, - Ordering, // - }; - - use kernel::{ - dma::Coherent, - dma_read, - dma_write, // - }; - - use crate::gsp::cmdq::{ - GspMem, - MSGQ_NUM_PAGES, // - }; - - pub(in crate::gsp) fn gsp_write_ptr(qs: &Coherent) -> u32 { - dma_read!(qs, .gspq.tx.0.writePtr) % MSGQ_NUM_PAGES - } - - pub(in crate::gsp) fn gsp_read_ptr(qs: &Coherent) -> u32 { - dma_read!(qs, .gspq.rx.0.readPtr) % MSGQ_NUM_PAGES - } - - pub(in crate::gsp) fn cpu_read_ptr(qs: &Coherent) -> u32 { - dma_read!(qs, .cpuq.rx.0.readPtr) % MSGQ_NUM_PAGES - } - - pub(in crate::gsp) fn advance_cpu_read_ptr(qs: &Coherent, count: u32) { - let rptr = cpu_read_ptr(qs).wrapping_add(count) % MSGQ_NUM_PAGES; - - // Ensure read pointer is properly ordered. - fence(Ordering::SeqCst); - - dma_write!(qs, .cpuq.rx.0.readPtr, rptr); - } - - pub(in crate::gsp) fn cpu_write_ptr(qs: &Coherent) -> u32 { - dma_read!(qs, .cpuq.tx.0.writePtr) % MSGQ_NUM_PAGES - } - - pub(in crate::gsp) fn advance_cpu_write_ptr(qs: &Coherent, count: u32) { - let wptr = cpu_write_ptr(qs).wrapping_add(count) % MSGQ_NUM_PAGES; - - dma_write!(qs, .cpuq.tx.0.writePtr, wptr); - - // Ensure all command data is visible before triggering the GSP read. - fence(Ordering::SeqCst); - } -} - /// Maximum size of a single GSP message queue element in bytes. pub(crate) const GSP_MSG_QUEUE_ELEMENT_SIZE_MAX: usize = num::u32_as_usize(bindings::GSP_MSG_QUEUE_ELEMENT_SIZE_MAX); @@ -720,6 +674,16 @@ impl MsgqTxHeader { entryOff: num::usize_into_u32::(), }) } + + /// Returns the value of the write pointer for this queue. + pub(crate) fn write_ptr(this: CoherentView<'_, Self>) -> u32 { + io_read!(this, .0.writePtr) + } + + /// Sets the value of the write pointer for this queue. + pub(crate) fn set_write_ptr(this: CoherentView<'_, Self>, val: u32) { + io_write!(this, .0.writePtr, val) + } } // SAFETY: Padding is explicit and does not contain uninitialized data. @@ -735,6 +699,16 @@ impl MsgqRxHeader { pub(crate) fn new() -> Self { Self(Default::default()) } + + /// Returns the value of the read pointer for this queue. + pub(crate) fn read_ptr(this: CoherentView<'_, Self>) -> u32 { + io_read!(this, .0.readPtr) + } + + /// Sets the value of the read pointer for this queue. + pub(crate) fn set_read_ptr(this: CoherentView<'_, Self>, val: u32) { + io_write!(this, .0.readPtr, val) + } } // SAFETY: Padding is explicit and does not contain uninitialized data. -- cgit From 6ff7d69b7e6e0b09d53ffde472760f904ea5714f Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:31 +0100 Subject: rust: dma: drop `dma_read!` and `dma_write!` API The primitive read/write use case is covered by the `io_read!` and `io_write!` macro. The non-primitive use case was finicky; they should either be achieved using `CoherentBox` or `as_ref()/as_mut()` to assert the lack of concurrent access, or should be using memcpy-like APIs to express the non-atomic and tearable nature. Reviewed-by: Andreas Hindborg Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Reviewed-by: Daniel Almeida Link: https://patch.msgid.link/20260706-io_projection-v6-18-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/dma.rs | 128 ----------------------------------------------- samples/rust/rust_dma.rs | 11 ++-- 2 files changed, 8 insertions(+), 131 deletions(-) diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index 1535bc6eec64..6e7ea3b72f2f 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -661,52 +661,6 @@ impl Coherent { // SAFETY: per safety requirement. unsafe { &mut *self.as_mut_ptr() } } - - /// Reads the value of `field` and ensures that its type is [`FromBytes`]. - /// - /// # Safety - /// - /// This must be called from the [`dma_read`] macro which ensures that the `field` pointer is - /// validated beforehand. - /// - /// Public but hidden since it should only be used from [`dma_read`] macro. - #[doc(hidden)] - pub unsafe fn field_read(&self, field: *const F) -> F { - // SAFETY: - // - By the safety requirements field is valid. - // - Using read_volatile() here is not sound as per the usual rules, the usage here is - // a special exception with the following notes in place. When dealing with a potential - // race from a hardware or code outside kernel (e.g. user-space program), we need that - // read on a valid memory is not UB. Currently read_volatile() is used for this, and the - // rationale behind is that it should generate the same code as READ_ONCE() which the - // kernel already relies on to avoid UB on data races. Note that the usage of - // read_volatile() is limited to this particular case, it cannot be used to prevent - // the UB caused by racing between two kernel functions nor do they provide atomicity. - unsafe { field.read_volatile() } - } - - /// Writes a value to `field` and ensures that its type is [`AsBytes`]. - /// - /// # Safety - /// - /// This must be called from the [`dma_write`] macro which ensures that the `field` pointer is - /// validated beforehand. - /// - /// Public but hidden since it should only be used from [`dma_write`] macro. - #[doc(hidden)] - pub unsafe fn field_write(&self, field: *mut F, val: F) { - // SAFETY: - // - By the safety requirements field is valid. - // - Using write_volatile() here is not sound as per the usual rules, the usage here is - // a special exception with the following notes in place. When dealing with a potential - // race from a hardware or code outside kernel (e.g. user-space program), we need that - // write on a valid memory is not UB. Currently write_volatile() is used for this, and the - // rationale behind is that it should generate the same code as WRITE_ONCE() which the - // kernel already relies on to avoid UB on data races. Note that the usage of - // write_volatile() is limited to this particular case, it cannot be used to prevent - // the UB caused by racing between two kernel functions nor do they provide atomicity. - unsafe { field.write_volatile(val) } - } } impl Coherent { @@ -1266,85 +1220,3 @@ impl<'a, T: ?Sized + KnownSize> IoBase<'a> for &'a Coherent { } } } - -/// Reads a field of an item from an allocated region of structs. -/// -/// The syntax is of the form `kernel::dma_read!(dma, proj)` where `dma` is an expression evaluating -/// to a [`Coherent`] and `proj` is a [projection specification](kernel::ptr::project!). -/// -/// # Examples -/// -/// ``` -/// use kernel::device::Device; -/// use kernel::dma::{attrs::*, Coherent}; -/// -/// struct MyStruct { field: u32, } -/// -/// // SAFETY: All bit patterns are acceptable values for `MyStruct`. -/// unsafe impl kernel::transmute::FromBytes for MyStruct{}; -/// // SAFETY: Instances of `MyStruct` have no uninitialized portions. -/// unsafe impl kernel::transmute::AsBytes for MyStruct{}; -/// -/// # fn test(alloc: &kernel::dma::Coherent<[MyStruct]>) -> Result { -/// let whole = kernel::dma_read!(alloc, [try: 2]); -/// let field = kernel::dma_read!(alloc, [panic: 1].field); -/// # Ok::<(), Error>(()) } -/// ``` -#[macro_export] -macro_rules! dma_read { - ($dma:expr, $($proj:tt)*) => {{ - let dma = &$dma; - let ptr = $crate::ptr::project!( - $crate::dma::Coherent::as_ptr(dma), $($proj)* - ); - // SAFETY: The pointer created by the projection is within the DMA region. - unsafe { $crate::dma::Coherent::field_read(dma, ptr) } - }}; -} - -/// Writes to a field of an item from an allocated region of structs. -/// -/// The syntax is of the form `kernel::dma_write!(dma, proj, val)` where `dma` is an expression -/// evaluating to a [`Coherent`], `proj` is a -/// [projection specification](kernel::ptr::project!), and `val` is the value to be written to the -/// projected location. -/// -/// # Examples -/// -/// ``` -/// use kernel::device::Device; -/// use kernel::dma::{attrs::*, Coherent}; -/// -/// struct MyStruct { member: u32, } -/// -/// // SAFETY: All bit patterns are acceptable values for `MyStruct`. -/// unsafe impl kernel::transmute::FromBytes for MyStruct{}; -/// // SAFETY: Instances of `MyStruct` have no uninitialized portions. -/// unsafe impl kernel::transmute::AsBytes for MyStruct{}; -/// -/// # fn test(alloc: &kernel::dma::Coherent<[MyStruct]>) -> Result { -/// kernel::dma_write!(alloc, [try: 2].member, 0xf); -/// kernel::dma_write!(alloc, [panic: 1], MyStruct { member: 0xf }); -/// # Ok::<(), Error>(()) } -/// ``` -#[macro_export] -macro_rules! dma_write { - (@parse [$dma:expr] [$($proj:tt)*] [, $val:expr]) => {{ - let dma = &$dma; - let ptr = $crate::ptr::project!( - mut $crate::dma::Coherent::as_mut_ptr(dma), $($proj)* - ); - let val = $val; - // SAFETY: The pointer created by the projection is within the DMA region. - unsafe { $crate::dma::Coherent::field_write(dma, ptr, val) } - }}; - (@parse [$dma:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => { - $crate::dma_write!(@parse [$dma] [$($proj)* .$field] [$($rest)*]) - }; - (@parse [$dma:expr] [$($proj:tt)*] [[$flavor:ident: $index:expr] $($rest:tt)*]) => { - $crate::dma_write!(@parse [$dma] [$($proj)* [$flavor: $index]] [$($rest)*]) - }; - ($dma:expr, $($rest:tt)*) => { - $crate::dma_write!(@parse [$dma] [] [$($rest)*]) - }; -} diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 5046b4628d0e..4af46e99d2dd 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -12,6 +12,10 @@ use kernel::{ Device, DmaMask, // }, + io::{ + io_project, + io_read, // + }, page, pci, prelude::*, scatterlist::{Owned, SGTable}, @@ -77,7 +81,8 @@ impl pci::Driver for DmaSampleDriver { Coherent::zeroed_slice(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?; for (i, value) in TEST_VALUES.into_iter().enumerate() { - kernel::dma_write!(ca, [try: i], MyStruct::new(value.0, value.1)); + // SAFETY: `ca` is not yet shared with device or other threads. + unsafe { *io_project!(ca, [panic: i]).as_mut() = MyStruct::new(value.0, value.1) }; } let size = 4 * page::PAGE_SIZE; @@ -97,8 +102,8 @@ impl pci::Driver for DmaSampleDriver { impl DmaSampleDriver { fn check_dma(&self) { for (i, value) in TEST_VALUES.into_iter().enumerate() { - let val0 = kernel::dma_read!(self.ca, [panic: i].h); - let val1 = kernel::dma_read!(self.ca, [panic: i].b); + let val0 = io_read!(self.ca, [panic: i].h); + let val1 = io_read!(self.ca, [panic: i].b); assert_eq!(val0, value.0); assert_eq!(val1, value.1); -- cgit From e7219e53c525db87b43f4a9064d0e6331d7dc710 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:32 +0100 Subject: rust: io: add copying methods One feature that was lost from the old `dma_read!` and `dma_write!` when moving to `io_read!` and `io_write!` was the ability to read/write a large structs. However, the semantics was unclear to begin with, as there was no guarantee about their atomicity even for structs that were small enough to fit in u32. Re-introduce the capability in the form of copying methods. dma_read!(foo, bar) -> io_project!(foo, bar).copy_read() dma_write!(foo, bar, baz) -> io_project!(foo, bar).copy_write(baz) Model these semantics after memcpy so user has clear expectation of lack of atomicity. As an additional benefit of this change, this now works for MMIO as well by mapping them to `memcpy_{from,to}io`. For slices which is DST so the `copy_read` and `copy_write` API above can't work, add `copy_from_slice` and `copy_to_slice` to copy from/to normal memory. Signed-off-by: Gary Guo Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260706-io_projection-v6-19-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/helpers/io.c | 13 +++ rust/kernel/dma.rs | 25 +++++ rust/kernel/io.rs | 262 ++++++++++++++++++++++++++++++++++++++++++++++- samples/rust/rust_dma.rs | 7 +- 4 files changed, 303 insertions(+), 4 deletions(-) diff --git a/rust/helpers/io.c b/rust/helpers/io.c index 397810864a24..7ed9a4f77f1b 100644 --- a/rust/helpers/io.c +++ b/rust/helpers/io.c @@ -19,6 +19,19 @@ __rust_helper void rust_helper_iounmap(void __iomem *addr) iounmap(addr); } +__rust_helper void rust_helper_memcpy_fromio(void *dst, + const volatile void __iomem *src, + size_t count) +{ + memcpy_fromio(dst, src, count); +} + +__rust_helper void rust_helper_memcpy_toio(volatile void __iomem *dst, + const void *src, size_t count) +{ + memcpy_toio(dst, src, count); +} + __rust_helper u8 rust_helper_readb(const void __iomem *addr) { return readb(addr); diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index 6e7ea3b72f2f..e275f2562a5b 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -18,6 +18,7 @@ use crate::{ IoBackend, IoBase, IoCapable, + IoCopyable, SysMem, SysMemBackend, // }, @@ -1197,6 +1198,30 @@ where } } +impl IoCopyable for CoherentIoBackend { + #[inline] + unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) { + // SAFETY: Per safety requirement. + unsafe { SysMemBackend::copy_from_io(view.cpu_addr, buffer) } + } + + #[inline] + unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) { + // SAFETY: Per safety requirement. + unsafe { SysMemBackend::copy_to_io(view.cpu_addr, buffer) } + } + + #[inline] + fn copy_read(view: Self::View<'_, T>) -> T { + SysMemBackend::copy_read(view.cpu_addr) + } + + #[inline] + fn copy_write(view: Self::View<'_, T>, value: T) { + SysMemBackend::copy_write(view.cpu_addr, value) + } +} + impl<'a, T: ?Sized + KnownSize> IoBase<'a> for CoherentView<'a, T> { type Backend = CoherentIoBackend; type Target = T; diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 3f430bce61e5..dbaa88898c3b 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -5,7 +5,8 @@ //! C header: [`include/asm-generic/io.h`](srctree/include/asm-generic/io.h) use core::{ - marker::PhantomData, // + marker::PhantomData, + mem::MaybeUninit, // }; use crate::{ @@ -275,6 +276,69 @@ pub trait IoCapable: IoBackend { fn io_write<'a>(view: Self::View<'a, T>, value: T); } +/// Trait indicating that an I/O backend supports memory copy operations. +pub trait IoCopyable: IoBackend { + /// Copy contents of `view` to `buffer`. + /// + /// # Safety + /// + /// - `buffer` is valid for volatile write for `view.size()` bytes. + /// - `buffer` should not overlap with `view`. + unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8); + + /// Copy contents from `buffer` to `view`. + /// + /// # Safety + /// + /// - `buffer` is valid for volatile read for `view.size()` bytes. + /// - `buffer` should not overlap with `view`. + unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8); + + /// Copy from `view` and return the value. + #[inline] + fn copy_read(view: Self::View<'_, T>) -> T { + // Project `self` to `[u8]`. + let ptr = Self::as_ptr(view); + // SAFETY: This is a identity projection. + let slice_view = unsafe { + Self::project_view( + view, + core::ptr::slice_from_raw_parts_mut::(ptr.cast(), size_of::()), + ) + }; + + let mut buf = MaybeUninit::::uninit(); + // SAFETY: + // - `buf.as_mut_ptr()` is valid for write for `size_of::()` bytes. + // - `buf` is local so `buf.as_mut_ptr()` cannot overlap with `slice_view`. + unsafe { Self::copy_from_io(slice_view, buf.as_mut_ptr().cast()) }; + // SAFETY: `T: FromBytes` guarantee that all bit patterns are valid. + unsafe { buf.assume_init() } + } + + /// Copy `value` to `view`. + /// + /// Destructor of `value` will not be executed, consistent with [`zerocopy::transmute`]. + #[inline] + fn copy_write(view: Self::View<'_, T>, value: T) { + // Project `self` to `[u8]`. + let ptr = Self::as_ptr(view); + // SAFETY: This is a identity projection. + let slice_view = unsafe { + Self::project_view( + view, + core::ptr::slice_from_raw_parts_mut::(ptr.cast(), size_of::()), + ) + }; + + // SAFETY: + // - `&raw const value` is valid for read for `size_of::()` bytes. + // - `value` is local so `&raw const value` cannot overlap with `slice_view`. + unsafe { Self::copy_to_io(slice_view, (&raw const value).cast()) }; + core::mem::forget(value); + } +} + /// Describes a given I/O location: its offset, width, and type to convert the raw value from and /// into. /// @@ -354,6 +418,24 @@ pub trait Io<'a>: IoBase<'a> { KnownSize::size(Self::Backend::as_ptr(self.as_view())) } + /// Returns the length of the slice in number of elements. + #[inline] + fn len(self) -> usize + where + Self: Io<'a, Target = [T]>, + { + Self::Backend::as_ptr(self.as_view()).len() + } + + /// Returns `true` if the slice has a length of 0. + #[inline] + fn is_empty(self) -> bool + where + Self: Io<'a, Target = [T]>, + { + self.len() == 0 + } + /// Try to convert into a different typed I/O view. /// /// A runtime check is performed to ensure that the target type is of same or smaller size to @@ -443,6 +525,121 @@ pub trait Io<'a>: IoBase<'a> { Self::Backend::io_write(self.as_view(), value) } + /// Copy-read from I/O memory. + /// + /// This is equivalent to reading from the I/O memory with byte-wise copy, although the actual + /// implementation might be more efficient. There is no atomicity guarantee. Note that for some + /// backends (e.g. `Mmio`), this can read different value compared to [`read_val`] as + /// byte-swapping is not performed. + /// + /// [`read_val`]: Io::read_val + /// + /// # Examples + /// + /// ```no_run + /// # use kernel::io::*; + /// # fn test_copy_read(mmio: Mmio<'_, [u8; 6]>) { + /// // let mmio: Mmio<'_, [u8; 6]>; + /// let val: [u8; 6] = mmio.copy_read(); + /// # } + /// ``` + #[inline] + fn copy_read(self) -> Self::Target + where + Self::Backend: IoCopyable, + Self::Target: Sized + FromBytes, + { + Self::Backend::copy_read(self.as_view()) + } + + /// Copy-write to I/O memory. + /// + /// This is equivalent to writing to the I/O memory with byte-wise copy, although the actual + /// implementation might be more efficient. There is no atomicity guarantee. Note that for some + /// backends (e.g. `Mmio`), this can write different value compared to [`write_val`] as + /// byte-swapping is not performed. + /// + /// [`write_val`]: Io::write_val + /// + /// # Examples + /// + /// ```no_run + /// # use kernel::io::*; + /// # fn test_copy_write(mmio: Mmio<'_, [u8; 6]>) { + /// // let mmio: Mmio<'_, [u8; 6]>; + /// mmio.copy_write([0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); + /// # } + /// ``` + #[inline] + fn copy_write(self, value: Self::Target) + where + Self::Backend: IoCopyable, + Self::Target: Sized + IntoBytes, + { + Self::Backend::copy_write(self.as_view(), value); + } + + /// Copy bytes from `data` to I/O memory. + /// + /// # Panics + /// + /// This function will panic if the length of `self` differs from the length of `data`, similar + /// to [`[u8]::copy_from_slice`]. + /// + /// # Examples + /// + /// ```no_run + /// # use kernel::io::*; + /// # fn test_copy_write(mmio: Mmio<'_, [u8]>) { + /// // let mmio: Mmio<'_, [u8]>; + /// mmio.copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); + /// # } + /// ``` + #[inline] + fn copy_from_slice(self, data: &[u8]) + where + Self::Backend: IoCopyable, + Self: Io<'a, Target = [u8]>, + { + assert_eq!(self.len(), data.len()); + + // SAFETY: `data.as_ptr()` is valid for read for `self.size()` bytes. + unsafe { + Self::Backend::copy_to_io(self.as_view(), data.as_ptr()); + } + } + + /// Copy bytes from I/O memory to `data`. + /// + /// # Panics + /// + /// This function will panic if the length of `self` differs from the length of `data`, similar + /// to [`[u8]::copy_from_slice`]. + /// + /// # Examples + /// + /// ```no_run + /// # use kernel::io::*; + /// # fn test_copy_write(mmio: Mmio<'_, [u8]>) { + /// // let mmio: Mmio<'_, [u8]>; + /// let mut buf = [0; 6]; + /// mmio.copy_to_slice(&mut buf); + /// # } + /// ``` + #[inline] + fn copy_to_slice(self, data: &mut [u8]) + where + Self::Backend: IoCopyable, + Self: Io<'a, Target = [u8]>, + { + assert_eq!(self.len(), data.len()); + + // SAFETY: `data.as_mut_ptr()` is valid for write for `self.size()` bytes. + unsafe { + Self::Backend::copy_from_io(self.as_view(), data.as_mut_ptr()); + } + } + /// Fallible 8-bit read with runtime bounds check. #[inline(always)] fn try_read8(self, offset: usize) -> Result @@ -1003,6 +1200,28 @@ impl_mmio_io_capable!(MmioBackend, u32, readl, writel); #[cfg(CONFIG_64BIT)] impl_mmio_io_capable!(MmioBackend, u64, readq, writeq); +impl IoCopyable for MmioBackend { + #[inline] + unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) { + // SAFETY: + // - `view.ptr` is valid MMIO memory for `view.size()` bytes. + // - `buffer` is valid for write for `view.size()` bytes. + unsafe { + bindings::memcpy_fromio(buffer.cast(), view.ptr.cast(), view.size()); + } + } + + #[inline] + unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) { + // SAFETY: + // - `view.ptr` is valid MMIO memory for `view.size()` bytes. + // - `buffer` is valid for read for `view.size()` bytes. + unsafe { + bindings::memcpy_toio(view.ptr.cast(), buffer.cast(), view.size()); + } + } +} + /// [`Mmio`] but using relaxed accessors. /// /// This type provides an implementation of [`Io`] that uses relaxed I/O MMIO operands instead of @@ -1146,6 +1365,47 @@ impl_sysmem_io_capable!(u32); #[cfg(CONFIG_64BIT)] impl_sysmem_io_capable!(u64); +impl IoCopyable for SysMemBackend { + #[inline] + unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) { + // Use `bindings::memcpy` instead of `copy_nonoverlapping` for volatile. + // SAFETY: + // - `view.ptr` is in CPU address space and valid for read. + // - `buffer` is valid for write for `view.size()` bytes which is equal to `view.ptr.len()`. + unsafe { bindings::memcpy(buffer.cast(), view.ptr.cast(), view.ptr.len()) }; + } + + #[inline] + unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) { + // Use `bindings::memcpy` instead of `copy_nonoverlapping` for volatile. + // SAFETY: + // - `view.ptr` is in CPU address space and valid for write. + // - `buffer` is valid for read for `view.size()` bytes which is equal to `view.ptr.len()`. + unsafe { bindings::memcpy(view.ptr.cast(), buffer.cast(), view.ptr.len()) }; + } + + #[inline] + fn copy_read(view: Self::View<'_, T>) -> T { + // SAFETY: + // - Per type invariant, `ptr` is valid and aligned. + // - Using read_volatile() here so that race with hardware is well-defined. + // - Using read_volatile() here is not sound if it races with other CPU per Rust + // rules, but this is allowed per LKMM. + // - `T: FromBytes` so all bit patterns are valid. + unsafe { view.ptr.read_volatile() } + } + + #[inline] + fn copy_write(view: Self::View<'_, T>, value: T) { + // SAFETY: + // - Per type invariant, `ptr` is valid and aligned. + // - Using write_volatile() here so that race with hardware is well-defined. + // - Using write_volatile() here is not sound if it races with other CPU per Rust + // rules, but this is allowed per LKMM. + unsafe { view.ptr.write_volatile(value) } + } +} + /// A view of a system memory region. /// /// Provides `Io` trait implementation for kernel virtual address ranges, diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 4af46e99d2dd..b629acc6d915 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -14,7 +14,8 @@ use kernel::{ }, io::{ io_project, - io_read, // + io_read, + Io, // }, page, pci, prelude::*, @@ -38,6 +39,7 @@ const TEST_VALUES: [(u32, u32); 5] = [ (0xcd, 0xef), ]; +#[derive(FromBytes, IntoBytes)] struct MyStruct { h: u32, b: u32, @@ -81,8 +83,7 @@ impl pci::Driver for DmaSampleDriver { Coherent::zeroed_slice(pdev.as_ref(), TEST_VALUES.len(), GFP_KERNEL)?; for (i, value) in TEST_VALUES.into_iter().enumerate() { - // SAFETY: `ca` is not yet shared with device or other threads. - unsafe { *io_project!(ca, [panic: i]).as_mut() = MyStruct::new(value.0, value.1) }; + io_project!(ca, [panic: i]).copy_write(MyStruct::new(value.0, value.1)); } let size = 4 * page::PAGE_SIZE; -- cgit From 11a4784f902ebf3e674dbaf07dbec9a37aabb5e4 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 6 Jul 2026 13:44:33 +0100 Subject: rust: io: implement `IoSysMap` Add an enum as sum type for `Mmio` and `SysMem`. This serves similar purpose of `iosys_map`. Thanks to Rust's type system, all of projection and struct read/write can be handled by the generic I/O projection mechanism (i.e. `io_project!`, `io_read!, `io_write!`) for free, and there is no need to provide things like `iosys_map_rd_field` or `iosys_map_wr_field`. An enum type also makes it very easy to construct or destruct. This could be made more generic by implementing on a general purpose sum type like `Either`; however this is kept specific unless a need arises that warrants this to be generic over other I/O backends. Reviewed-by: Alexandre Courbot Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260706-io_projection-v6-20-72cd5d055d54@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index dbaa88898c3b..95f46bb75f9e 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -1468,6 +1468,143 @@ impl<'a, T: ?Sized + KnownSize> IoBase<'a> for SysMem<'a, T> { } } +/// I/O Backend for [`IoSysMap`]. +pub struct IoSysMapBackend; + +/// Either [`Mmio`] or [`SysMem`]. +/// +/// This can be used when a piece of logic may wish to handle both MMIO or system memory but does +/// not want or cannot be generic over I/O backends. This serves a similar purpose to +/// [`include/linux/iosys-map.h`] in C. +/// +/// This type can be used like any other types that implements [`Io`]; this also include +/// [`io_project!`], [`io_read!`], [`io_write!`]. +/// +/// [`include/linux/iosys-map.h`]: srctree/include/linux/iosys-map.h +pub enum IoSysMap<'a, T: ?Sized> { + /// The view is I/O memory. + Io(Mmio<'a, T>), + /// The view is system memory. + Sys(SysMem<'a, T>), +} + +impl Copy for IoSysMap<'_, T> {} +impl Clone for IoSysMap<'_, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: ?Sized> From> for IoSysMap<'a, T> { + #[inline] + fn from(value: Mmio<'a, T>) -> Self { + IoSysMap::Io(value) + } +} + +impl<'a, T: ?Sized> From> for IoSysMap<'a, T> { + #[inline] + fn from(value: SysMem<'a, T>) -> Self { + IoSysMap::Sys(value) + } +} + +impl IoBackend for IoSysMapBackend { + type View<'a, T: ?Sized + KnownSize> = IoSysMap<'a, T>; + + #[inline] + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T { + match view { + IoSysMap::Io(l) => MmioBackend::as_ptr(l), + IoSysMap::Sys(r) => SysMemBackend::as_ptr(r), + } + } + + #[inline] + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>( + view: Self::View<'a, T>, + ptr: *mut U, + ) -> Self::View<'a, U> { + match view { + // SAFETY: Per safety requirement. + IoSysMap::Io(l) => IoSysMap::Io(unsafe { MmioBackend::project_view(l, ptr) }), + // SAFETY: Per safety requirement. + IoSysMap::Sys(r) => IoSysMap::Sys(unsafe { SysMemBackend::project_view(r, ptr) }), + } + } +} + +impl IoCapable for IoSysMapBackend +where + MmioBackend: IoCapable, + SysMemBackend: IoCapable, +{ + #[inline] + fn io_read(view: Self::View<'_, T>) -> T { + match view { + IoSysMap::Io(l) => MmioBackend::io_read(l), + IoSysMap::Sys(r) => SysMemBackend::io_read(r), + } + } + + #[inline] + fn io_write<'a>(view: Self::View<'a, T>, value: T) { + match view { + IoSysMap::Io(l) => MmioBackend::io_write(l, value), + IoSysMap::Sys(r) => SysMemBackend::io_write(r, value), + } + } +} + +impl IoCopyable for IoSysMapBackend { + #[inline] + unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) { + match view { + // SAFETY: Per safety requirement. + IoSysMap::Io(l) => unsafe { MmioBackend::copy_from_io(l, buffer) }, + // SAFETY: Per safety requirement. + IoSysMap::Sys(r) => unsafe { SysMemBackend::copy_from_io(r, buffer) }, + } + } + + #[inline] + unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) { + match view { + // SAFETY: Per safety requirement. + IoSysMap::Io(l) => unsafe { MmioBackend::copy_to_io(l, buffer) }, + // SAFETY: Per safety requirement. + IoSysMap::Sys(r) => unsafe { SysMemBackend::copy_to_io(r, buffer) }, + } + } + + #[inline] + fn copy_read(view: Self::View<'_, T>) -> T { + match view { + IoSysMap::Io(l) => MmioBackend::copy_read(l), + IoSysMap::Sys(r) => SysMemBackend::copy_read(r), + } + } + + #[inline] + fn copy_write(view: Self::View<'_, T>, value: T) { + match view { + IoSysMap::Io(l) => MmioBackend::copy_write(l, value), + IoSysMap::Sys(r) => SysMemBackend::copy_write(r, value), + } + } +} + +impl<'a, T: ?Sized + KnownSize> IoBase<'a> for IoSysMap<'a, T> { + type Backend = IoSysMapBackend; + type Target = T; + + #[inline] + fn as_view(self) -> IoSysMap<'a, T> { + self + } +} + // This helper turns associated functions to methods so it can be invoked in macro. // Used by `io_project!()` only. #[doc(hidden)] -- cgit From 2c91f57e81a7ece7b0b83e93462558f661055963 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:08 +0200 Subject: rust: types: rename ForLt to CovariantForLt Rename ForLt to CovariantForLt to prepare for the introduction of a new ForLt base trait that does not require covariance. The existing ForLt trait requires covariance, which enables the safe cast_ref() method. This rename preserves the same semantics under a more precise name, making room for a weaker ForLt trait in a subsequent commit. No functional change. Reviewed-by: Alexandre Courbot Reviewed-by: Gary Guo Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20260626183630.2585057-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- drivers/gpu/nova-core/driver.rs | 4 +-- rust/kernel/auxiliary.rs | 23 +++++++------- rust/kernel/types.rs | 2 +- rust/kernel/types/for_lt.rs | 57 ++++++++++++++++++----------------- rust/macros/for_lt.rs | 6 ++-- rust/macros/lib.rs | 11 +++---- samples/rust/rust_driver_auxiliary.rs | 8 ++--- 7 files changed, 56 insertions(+), 55 deletions(-) diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index 5738d4ac521b..48380ac15f68 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -15,7 +15,7 @@ use kernel::{ Atomic, Relaxed, // }, - types::ForLt, + types::CovariantForLt, }; use crate::gpu::Gpu; @@ -29,7 +29,7 @@ pub(crate) struct NovaCore<'bound> { pub(crate) gpu: Gpu<'bound>, bar: pci::Bar<'bound, BAR0_SIZE>, #[allow(clippy::type_complexity)] - _reg: auxiliary::Registration<'bound, ForLt!(())>, + _reg: auxiliary::Registration<'bound, CovariantForLt!(())>, } pub(crate) struct NovaCoreDriver; diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index c42928d5a239..40a0af74a8e5 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -20,7 +20,7 @@ use crate::{ }, prelude::*, types::{ - ForLt, + CovariantForLt, ForeignOwnable, Opaque, // }, @@ -272,16 +272,16 @@ impl Device { /// Returns a pinned reference to the registration data set by the registering (parent) driver. /// - /// `F` is the [`ForLt`](trait@ForLt) encoding of the data type. The returned + /// `F` is the [`CovariantForLt`](trait@CovariantForLt) encoding of the data type. The returned /// reference has its lifetime shortened from `'static` to `&self`'s borrow lifetime via - /// [`ForLt::cast_ref`]. + /// [`CovariantForLt::cast_ref`]. /// /// Returns [`EINVAL`] if `F` does not match the type used by the parent driver when calling /// [`Registration::new()`]. /// /// Returns [`ENOENT`] if no registration data has been set, e.g. when the device was /// registered by a C driver. - pub fn registration_data(&self) -> Result>> { + pub fn registration_data(&self) -> Result>> { // SAFETY: By the type invariant, `self.as_raw()` is a valid `struct auxiliary_device`. let ptr = unsafe { (*self.as_raw()).registration_data_rust }; if ptr.is_null() { @@ -399,8 +399,9 @@ struct RegistrationData { /// This type represents the registration of a [`struct auxiliary_device`]. When its parent device /// is unbound, the corresponding auxiliary device will be unregistered from the system. /// -/// The type parameter `F` is a [`ForLt`](trait@ForLt) encoding of the registration -/// data type. For non-lifetime-parameterized types, use [`ForLt!(T)`](macro@ForLt). +/// The type parameter `F` is a [`CovariantForLt`](trait@CovariantForLt) encoding of the +/// registration data type. For non-lifetime-parameterized types, use +/// [`CovariantForLt!(T)`](macro@CovariantForLt). /// The data can be accessed by the auxiliary driver through [`Device::registration_data()`]. /// /// # Invariants @@ -408,12 +409,12 @@ struct RegistrationData { /// `self.adev` always holds a valid pointer to an initialized and registered /// [`struct auxiliary_device`] whose `registration_data_rust` field points to a /// valid `Pin>>>`. -pub struct Registration<'a, F: ForLt + 'static> { +pub struct Registration<'a, F: CovariantForLt + 'static> { adev: NonNull, _phantom: PhantomData>, } -impl<'a, F: ForLt> Registration<'a, F> +impl<'a, F: CovariantForLt> Registration<'a, F> where for<'b> F::Of<'b>: Send + Sync, { @@ -525,7 +526,7 @@ where } } -impl Drop for Registration<'_, F> { +impl Drop for Registration<'_, F> { fn drop(&mut self) { // SAFETY: By the type invariant of `Self`, `self.adev.as_ptr()` is a valid registered // `struct auxiliary_device`. @@ -547,7 +548,7 @@ impl Drop for Registration<'_, F> { } // SAFETY: A `Registration` of a `struct auxiliary_device` can be released from any thread. -unsafe impl Send for Registration<'_, F> where for<'a> F::Of<'a>: Send {} +unsafe impl Send for Registration<'_, F> where for<'a> F::Of<'a>: Send {} // SAFETY: `Registration` does not expose any methods or fields that need synchronization. -unsafe impl Sync for Registration<'_, F> where for<'a> F::Of<'a>: Send {} +unsafe impl Sync for Registration<'_, F> where for<'a> F::Of<'a>: Send {} diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index ac316fd7b538..cbe6907042d3 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -13,7 +13,7 @@ use pin_init::{PinInit, Wrapper, Zeroable}; #[doc(hidden)] pub mod for_lt; -pub use for_lt::ForLt; +pub use for_lt::CovariantForLt; /// Used to transfer ownership to and from foreign (non-Rust) languages. /// diff --git a/rust/kernel/types/for_lt.rs b/rust/kernel/types/for_lt.rs index d44323c28e8d..a11f7509633c 100644 --- a/rust/kernel/types/for_lt.rs +++ b/rust/kernel/types/for_lt.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Provide implementation and test of the `ForLt` trait and macro. +//! Provide implementation and test of the `CovariantForLt` trait and macro. //! -//! This module is hidden and user should just use `ForLt!` directly. +//! This module is hidden and user should just use `CovariantForLt!` directly. use core::marker::PhantomData; @@ -15,38 +15,39 @@ use core::marker::PhantomData; /// /// # Macro /// -/// It is not recommended to implement this trait directly. `ForLt!` macro is provided to obtain a -/// type that implements this trait. +/// It is not recommended to implement this trait directly. `CovariantForLt!` macro is provided to +/// obtain a type that implements this trait. /// /// The full syntax is /// /// ``` -/// # use kernel::types::ForLt; -/// # fn expect_lt() {} +/// # use kernel::types::CovariantForLt; +/// # fn expect_lt() {} /// # struct TypeThatUse<'a>(&'a ()); /// # expect_lt::< -/// ForLt!(for<'a> TypeThatUse<'a>) +/// CovariantForLt!(for<'a> TypeThatUse<'a>) /// # >(); /// ``` /// -/// which gives a type so that ` TypeThatUse<'a>) as ForLt>::Of<'b>` +/// which gives a type so that +/// ` TypeThatUse<'a>) as CovariantForLt>::Of<'b>` /// is `TypeThatUse<'b>`. /// /// You may also use a short-hand syntax which works similar to lifetime elision. /// The macro also accepts types that do not involve a lifetime at all. /// /// ``` -/// # use kernel::types::ForLt; -/// # fn expect_lt() {} +/// # use kernel::types::CovariantForLt; +/// # fn expect_lt() {} /// # struct TypeThatUse<'a>(&'a ()); /// # expect_lt::< -/// ForLt!(TypeThatUse<'_>) // Equivalent to `ForLt!(for<'a> TypeThatUse<'a>)`. +/// CovariantForLt!(TypeThatUse<'_>) // Equivalent to `CovariantForLt!(for<'a> TypeThatUse<'a>)`. /// # >(); /// # expect_lt::< -/// ForLt!(&u32) // Equivalent to `ForLt!(for<'a> &'a u32)`. +/// CovariantForLt!(&u32) // Equivalent to `CovariantForLt!(for<'a> &'a u32)`. /// # >(); /// # expect_lt::< -/// ForLt!(u32) // Equivalent to `ForLt!(for<'a> u32)`. +/// CovariantForLt!(u32) // Equivalent to `CovariantForLt!(for<'a> u32)`. /// # >(); /// ``` /// @@ -55,10 +56,10 @@ use core::marker::PhantomData; /// it. /// /// ```ignore,compile_fail -/// # use kernel::types::ForLt; -/// # fn expect_lt() {} +/// # use kernel::types::CovariantForLt; +/// # fn expect_lt() {} /// # expect_lt::< -/// ForLt!(fn(&u32)) // Contravariant, will fail compilation. +/// CovariantForLt!(fn(&u32)) // Contravariant, will fail compilation. /// # >(); /// ``` /// @@ -67,23 +68,23 @@ use core::marker::PhantomData; /// the generic parameter but is in a separate item. /// /// ``` -/// # use kernel::types::ForLt; -/// fn expect_lt() {} +/// # use kernel::types::CovariantForLt; +/// fn expect_lt() {} /// # #[allow(clippy::unnecessary_safety_comment, reason = "false positive")] /// fn generic_fn() { /// // Syntactically proven by the macro -/// expect_lt::(); +/// expect_lt::(); /// // Syntactically proven by the macro -/// expect_lt::)>(); +/// expect_lt::)>(); /// // Cannot be syntactically proven, need to check covariance of `KBox` -/// // expect_lt::)>(); +/// // expect_lt::)>(); /// } /// ``` /// /// # Safety /// /// `Self::Of<'a>` must be covariant over the lifetime `'a`. -pub unsafe trait ForLt { +pub unsafe trait CovariantForLt { /// The type parameterized by the lifetime. type Of<'a>: 'a; @@ -94,11 +95,11 @@ pub unsafe trait ForLt { unsafe { core::mem::transmute(long) } } } -pub use macros::ForLt; +pub use macros::CovariantForLt; /// This is intended to be an "unsafe-to-refer-to" type. /// -/// Must only be used by the `ForLt!` macro. +/// Must only be used by the `CovariantForLt!` macro. /// /// `T` is the magic `dyn for<'a> WithLt<'a, TypeThatUse<'a>>` generated by macro. /// @@ -109,14 +110,14 @@ pub use macros::ForLt; #[doc(hidden)] pub struct UnsafeForLtImpl(PhantomData<(WF, T)>); -// This is a helper trait for implementation `ForLt` to be able to use HRTB. +// This is a helper trait for implementation `CovariantForLt` to be able to use HRTB. #[doc(hidden)] pub trait WithLt<'a> { type Of: 'a; } -// SAFETY: In `ForLt!` macro, a covariance proof is generated when naming `UnsafeForLtImpl` -// and it will fail to evaluate if the type is not covariant. -unsafe impl WithLt<'a>, WF> ForLt for UnsafeForLtImpl { +// SAFETY: In `CovariantForLt!` macro, a covariance proof is generated when naming +// `UnsafeForLtImpl` and it will fail to evaluate if the type is not covariant. +unsafe impl WithLt<'a>, WF> CovariantForLt for UnsafeForLtImpl { type Of<'a> = >::Of; } diff --git a/rust/macros/for_lt.rs b/rust/macros/for_lt.rs index 364d4113cd10..9487a9352f1c 100644 --- a/rust/macros/for_lt.rs +++ b/rust/macros/for_lt.rs @@ -154,8 +154,8 @@ impl<'a> Prover<'a> { // Note that if we encounter `&'other_lt T`, then we still need to make sure the type // is wellformed if `T` involves `&'lt`, so we defer to the compiler. // - // This is to block cases like `ForLt!(for<'a> &'static &'a u32)`, as the presence of - // the type implies `'a: 'static` but this is unsound. + // This is to block cases like `CovariantForLt!(for<'a> &'static &'a u32)`, as the + // presence of the type implies `'a: 'static` but this is unsound. Type::Reference(ty) if ty.mutability.is_none() && ty.lifetime.as_ref() == Some(self.0) => { @@ -176,7 +176,7 @@ impl<'a> Prover<'a> { } } -pub(crate) fn for_lt(input: HigherRankedType) -> TokenStream { +pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream { let (ty, lifetime) = match input { HigherRankedType::Explicit { lifetime, ty, .. } => (ty, lifetime), HigherRankedType::Implicit { ty } => { diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 4a48fabbc268..2167cb270928 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -491,14 +491,13 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream { .into() } -/// Obtain a type that implements [`ForLt`] for the given higher-ranked type. +/// Obtain a type that implements [`CovariantForLt`] for the given higher-ranked type. /// -/// Please refer to the documentation of the [`ForLt`] trait. +/// Please refer to the documentation of the [`CovariantForLt`] trait. /// -/// [`ForLt`]: trait.ForLt.html +/// [`CovariantForLt`]: trait.CovariantForLt.html #[proc_macro] -// The macro shares the name with the trait. #[allow(non_snake_case)] -pub fn ForLt(input: TokenStream) -> TokenStream { - for_lt::for_lt(parse_macro_input!(input)).into() +pub fn CovariantForLt(input: TokenStream) -> TokenStream { + for_lt::covariant_for_lt(parse_macro_input!(input)).into() } diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 2c1351040e45..92ee6a6d348e 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -13,7 +13,7 @@ use kernel::{ driver, pci, prelude::*, - types::ForLt, + types::CovariantForLt, InPlaceModule, // }; @@ -60,8 +60,8 @@ struct ParentDriver; #[allow(clippy::type_complexity)] struct ParentData<'bound> { - _reg0: auxiliary::Registration<'bound, ForLt!(Data<'_>)>, - _reg1: auxiliary::Registration<'bound, ForLt!(Data<'_>)>, + _reg0: auxiliary::Registration<'bound, CovariantForLt!(Data<'_>)>, + _reg1: auxiliary::Registration<'bound, CovariantForLt!(Data<'_>)>, } kernel::pci_device_table!( @@ -115,7 +115,7 @@ impl pci::Driver for ParentDriver { impl ParentDriver { fn connect(adev: &auxiliary::Device) -> Result { - let data = adev.registration_data::)>()?; + let data = adev.registration_data::)>()?; let pdev = data.parent; dev_info!( -- cgit From 1b56056294d45162325bb491f0356fbc6b473a12 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:09 +0200 Subject: rust: types: introduce ForLt base trait for CovariantForLt Add a new ForLt trait as a base for CovariantForLt: - ForLt (non-unsafe): represents a type generic over a lifetime, with no covariance guarantee. - CovariantForLt (unsafe): becomes a subtrait of ForLt that additionally proves the type is covariant over its lifetime parameter, providing a safe cast_ref() method. This split allows non-covariant types (e.g. types behind a Mutex) to implement ForLt and participate in DevresLt / registration data patterns that use HRTB closures for sound access, without requiring a covariance proof that would fail to compile. Both macros share the UnsafeForLtImpl helper type, distinguished by a const generic N: ForLt! emits N = 0 (no covariance proof), CovariantForLt! emits N = 1 (with compile-time covariance proof). Reviewed-by: Gary Guo Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20260626183630.2585057-3-dakr@kernel.org [ Merge ForLt pub use, inline resolve_hrt/ty_static, add intra-doc links. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/types.rs | 5 ++- rust/kernel/types/for_lt.rs | 80 +++++++++++++++++++++++++++++++++++---------- rust/macros/for_lt.rs | 37 +++++++++++++++------ rust/macros/lib.rs | 19 ++++++++++- 4 files changed, 112 insertions(+), 29 deletions(-) diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index cbe6907042d3..699aabe01ee5 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -13,7 +13,10 @@ use pin_init::{PinInit, Wrapper, Zeroable}; #[doc(hidden)] pub mod for_lt; -pub use for_lt::CovariantForLt; +pub use for_lt::{ + CovariantForLt, + ForLt, // +}; /// Used to transfer ownership to and from foreign (non-Rust) languages. /// diff --git a/rust/kernel/types/for_lt.rs b/rust/kernel/types/for_lt.rs index a11f7509633c..b8f422c802dc 100644 --- a/rust/kernel/types/for_lt.rs +++ b/rust/kernel/types/for_lt.rs @@ -1,22 +1,67 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -//! Provide implementation and test of the `CovariantForLt` trait and macro. +//! Provide implementation and test of the [`trait@ForLt`] and [`trait@CovariantForLt`] traits and +//! macros. //! -//! This module is hidden and user should just use `CovariantForLt!` directly. +//! This module is hidden and users should just use [`ForLt!`](macro@ForLt) / +//! [`CovariantForLt!`](macro@CovariantForLt) directly. use core::marker::PhantomData; /// Representation of types generic over a lifetime. /// -/// The type must be covariant over the generic lifetime, i.e. the lifetime parameter -/// can be soundly shortened. +/// # Macro +/// +/// It is not recommended to implement this trait directly. [`ForLt!`](macro@ForLt) macro is +/// provided to obtain a type that implements this trait. /// -/// The lifetime involved must be covariant. +/// The full syntax is +/// +/// ``` +/// # use kernel::types::ForLt; +/// # fn expect_lt() {} +/// # struct TypeThatUse<'a>(&'a ()); +/// # expect_lt::< +/// ForLt!(for<'a> TypeThatUse<'a>) +/// # >(); +/// ``` +/// +/// which gives a type so that ` TypeThatUse<'a>) as ForLt>::Of<'b>` +/// is `TypeThatUse<'b>`. +/// +/// You may also use a short-hand syntax which works similar to lifetime elision. +/// The macro also accepts types that do not involve a lifetime at all. +/// +/// ``` +/// # use kernel::types::ForLt; +/// # fn expect_lt() {} +/// # struct TypeThatUse<'a>(&'a ()); +/// # expect_lt::< +/// ForLt!(TypeThatUse<'_>) // Equivalent to `ForLt!(for<'a> TypeThatUse<'a>)`. +/// # >(); +/// # expect_lt::< +/// ForLt!(&u32) // Equivalent to `ForLt!(for<'a> &'a u32)`. +/// # >(); +/// # expect_lt::< +/// ForLt!(u32) // Equivalent to `ForLt!(for<'a> u32)`. +/// # >(); +/// ``` +pub trait ForLt { + /// The type parameterized by the lifetime. + type Of<'a>: 'a; +} +pub use macros::ForLt; + +/// [`trait@ForLt`] subtrait for types that are covariant over their lifetime parameter. +/// +/// Provides a safe [`cast_ref`](CovariantForLt::cast_ref) method for types that are proven to be +/// covariant. The `CovariantForLt!` macro syntax is the same as `ForLt!`. /// /// # Macro /// -/// It is not recommended to implement this trait directly. `CovariantForLt!` macro is provided to -/// obtain a type that implements this trait. +/// It is not recommended to implement this trait directly. +/// [`CovariantForLt!`](macro@CovariantForLt) macro is provided to obtain a type that implements +/// this trait. /// /// The full syntax is /// @@ -84,10 +129,7 @@ use core::marker::PhantomData; /// # Safety /// /// `Self::Of<'a>` must be covariant over the lifetime `'a`. -pub unsafe trait CovariantForLt { - /// The type parameterized by the lifetime. - type Of<'a>: 'a; - +pub unsafe trait CovariantForLt: ForLt { /// Cast a reference to a shorter lifetime. #[inline(always)] fn cast_ref<'r, 'short: 'r, 'long: 'short>(long: &'r Self::Of<'long>) -> &'r Self::Of<'short> { @@ -99,25 +141,29 @@ pub use macros::CovariantForLt; /// This is intended to be an "unsafe-to-refer-to" type. /// -/// Must only be used by the `CovariantForLt!` macro. +/// Must only be used by the [`ForLt!`](macro@ForLt) / [`CovariantForLt!`](macro@CovariantForLt) +/// macros. /// /// `T` is the magic `dyn for<'a> WithLt<'a, TypeThatUse<'a>>` generated by macro. /// /// `WF` is a type that the macro can use to assert some specific type is well-formed. /// /// `N` is to provide the macro a place to emit arbitrary items, in case it needs to prove -/// additional properties. +/// additional properties. [`ForLt!`](macro@ForLt) emits `N = 0`; +/// [`CovariantForLt!`](macro@CovariantForLt) emits `N = 1` after a covariance proof. #[doc(hidden)] pub struct UnsafeForLtImpl(PhantomData<(WF, T)>); -// This is a helper trait for implementation `CovariantForLt` to be able to use HRTB. +// This is a helper trait for implementation of `ForLt` / `CovariantForLt` to be able to use HRTB. #[doc(hidden)] pub trait WithLt<'a> { type Of: 'a; } -// SAFETY: In `CovariantForLt!` macro, a covariance proof is generated when naming -// `UnsafeForLtImpl` and it will fail to evaluate if the type is not covariant. -unsafe impl WithLt<'a>, WF> CovariantForLt for UnsafeForLtImpl { +impl WithLt<'a>, WF, const N: usize> ForLt for UnsafeForLtImpl { type Of<'a> = >::Of; } + +// SAFETY: In `CovariantForLt!` macro, a covariance proof is generated in the `N` const generic +// and it will fail to evaluate if the type is not covariant. Only `N = 1` gets this impl. +unsafe impl WithLt<'a>, WF> CovariantForLt for UnsafeForLtImpl {} diff --git a/rust/macros/for_lt.rs b/rust/macros/for_lt.rs index 9487a9352f1c..4372cbad3ec4 100644 --- a/rust/macros/for_lt.rs +++ b/rust/macros/for_lt.rs @@ -176,7 +176,12 @@ impl<'a> Prover<'a> { } } -pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream { +/// Shared implementation for both `ForLt!` and `CovariantForLt!`. +/// +/// Both macros run the prover and emit `ProveWf` structs to check well-formedness for all lifetime +/// instances (workaround for ). `CovariantForLt!` +/// additionally emits covariance proof functions and sets `N = 1`. +fn for_lt_inner(input: HigherRankedType, prove_covariance: bool) -> TokenStream { let (ty, lifetime) = match input { HigherRankedType::Explicit { lifetime, ty, .. } => (ty, lifetime), HigherRankedType::Implicit { ty } => { @@ -211,14 +216,16 @@ pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream { )); // Insert a proof that the type is covariant. - let cov_proof_name = format_ident!("prove_covariant_{idx}"); - proof.push(quote!( - fn #cov_proof_name<'__short, '__long: '__short>( - long: #wf_proof_name<'__long> - ) -> #wf_proof_name<'__short> { - long - } - )); + if prove_covariance { + let cov_proof_name = format_ident!("prove_covariant_{idx}"); + proof.push(quote!( + fn #cov_proof_name<'__short, '__long: '__short>( + long: #wf_proof_name<'__long> + ) -> #wf_proof_name<'__short> { + long + } + )); + } } // Make sure that the type is wellformed when substituting lifetime with `'static`. @@ -234,6 +241,8 @@ pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream { }, ); + let n: usize = prove_covariance.into(); + quote!( ::kernel::types::for_lt::UnsafeForLtImpl::< dyn for<#lifetime> ::kernel::types::for_lt::WithLt<#lifetime, Of = #ty>, @@ -241,8 +250,16 @@ pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream { { #(#proof)* - 0 + #n } > ) } + +pub(crate) fn for_lt(input: HigherRankedType) -> TokenStream { + for_lt_inner(input, false) +} + +pub(crate) fn covariant_for_lt(input: HigherRankedType) -> TokenStream { + for_lt_inner(input, true) +} diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 2167cb270928..e970769609f3 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -491,11 +491,28 @@ pub fn kunit_tests(attr: TokenStream, input: TokenStream) -> TokenStream { .into() } -/// Obtain a type that implements [`CovariantForLt`] for the given higher-ranked type. +/// Obtain a type that implements [`ForLt`] for the given higher-ranked type. +/// +/// Please refer to the documentation of the [`ForLt`] trait. +/// +/// [`ForLt`]: trait.ForLt.html +#[proc_macro] +#[allow(non_snake_case)] +pub fn ForLt(input: TokenStream) -> TokenStream { + for_lt::for_lt(parse_macro_input!(input)).into() +} + +/// Obtain a type that implements [`CovariantForLt`] (and [`ForLt`]) for the given higher-ranked +/// type. +/// +/// Unlike [`ForLt!`], this macro additionally proves that the type is covariant over the lifetime, +/// providing a safe [`CovariantForLt::cast_ref`] method. /// /// Please refer to the documentation of the [`CovariantForLt`] trait. /// /// [`CovariantForLt`]: trait.CovariantForLt.html +/// [`CovariantForLt::cast_ref`]: trait.CovariantForLt.html#method.cast_ref +/// [`ForLt`]: trait.ForLt.html #[proc_macro] #[allow(non_snake_case)] pub fn CovariantForLt(input: TokenStream) -> TokenStream { -- cgit From 491784c54cc10dcc018059a2f2594dd336fa480b Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:10 +0200 Subject: rust: auxiliary: add registration_data_with() for ForLt types Add registration_data_with() taking a for<'a> closure that receives Pin<&'a F::Of<'a>>, which works with any ForLt type. Taking a for<'a> closure rather than returning a direct reference prevents callers from choosing a concrete lifetime for the data, which is required for soundness with non-covariant ForLt types. Extract the common null-check, TypeId-check and KBox-borrow logic into a private registration_data_pinned() helper shared by both registration_data_with() and the existing registration_data(). Relax Registration's bound from CovariantForLt to ForLt so that non-covariant types can be registered. Reviewed-by: Gary Guo Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260626183630.2585057-4-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/auxiliary.rs | 93 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 40a0af74a8e5..19a488700bb9 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -21,6 +21,7 @@ use crate::{ prelude::*, types::{ CovariantForLt, + ForLt, ForeignOwnable, Opaque, // }, @@ -270,18 +271,15 @@ impl Device { unsafe { parent.as_bound() } } - /// Returns a pinned reference to the registration data set by the registering (parent) driver. + /// Returns the stored registration data as a pinned reference. /// - /// `F` is the [`CovariantForLt`](trait@CovariantForLt) encoding of the data type. The returned - /// reference has its lifetime shortened from `'static` to `&self`'s borrow lifetime via - /// [`CovariantForLt::cast_ref`]. + /// Performs null and [`TypeId`] checks, then borrows the stored [`KBox`]. /// - /// Returns [`EINVAL`] if `F` does not match the type used by the parent driver when calling - /// [`Registration::new()`]. + /// # Safety /// - /// Returns [`ENOENT`] if no registration data has been set, e.g. when the device was - /// registered by a C driver. - pub fn registration_data(&self) -> Result>> { + /// Callers must ensure that the lifetime shortening from the original `'static` storage to + /// `'_` is sound, e.g. via an HRTB closure or [`CovariantForLt`] guarantee. + unsafe fn registration_data_pinned(&self) -> Result>> { // SAFETY: By the type invariant, `self.as_raw()` is a valid `struct auxiliary_device`. let ptr = unsafe { (*self.as_raw()).registration_data_rust }; if ptr.is_null() { @@ -300,17 +298,59 @@ impl Device { return Err(EINVAL); } - // SAFETY: The `TypeId` check above confirms that the stored type matches - // `F::Of<'static>`; `ptr` remains valid until `Registration::drop()` calls - // `from_foreign()`. - let wrapper = unsafe { Pin::>>>::borrow(ptr) }; + // SAFETY: The `TypeId` check above confirms that the stored type matches `F`'s + // encoding; lifetimes are erased at runtime, so borrowing as `F::Of<'_>` is + // layout-compatible with the stored `F::Of<'static>`. `ptr` remains valid until + // `Registration::drop()` calls `from_foreign()`. + let wrapper = unsafe { Pin::>>>::borrow(ptr) }; // SAFETY: `data` is a structurally pinned field of `RegistrationData`. - let pinned: Pin<&F::Of<'_>> = unsafe { wrapper.map_unchecked(|w| &w.data) }; + Ok(unsafe { wrapper.map_unchecked(|w| &w.data) }) + } - // SAFETY: The data was pinned when stored; `cast_ref` only shortens - // the lifetime, so the pinning guarantee is preserved. - Ok(unsafe { Pin::new_unchecked(F::cast_ref(pinned.get_ref())) }) + /// Access the registration data set by the registering (parent) driver through a closure. + /// + /// `F` is the [`ForLt`](trait@ForLt) encoding of the data type. The closure receives a pinned + /// reference to the registration data. + /// + /// For covariant types that implement [`trait@CovariantForLt`], prefer + /// [`registration_data`](Self::registration_data) which returns a direct reference. + /// + /// Returns [`EINVAL`] if `F` does not match the type used by the parent driver when calling + /// [`Registration::new()`]. + /// + /// Returns [`ENOENT`] if no registration data has been set, e.g. when the device was + /// registered by a C driver. + #[inline] + pub fn registration_data_with( + &self, + f: impl for<'a> FnOnce(Pin<&'a F::Of<'a>>) -> R, + ) -> Result { + // SAFETY: The HRTB closure prevents the caller from smuggling in references with a + // concrete short lifetime, making the round-trip from `'static` sound regardless of + // variance. + let pinned = unsafe { self.registration_data_pinned::()? }; + + Ok(f(pinned)) + } + + /// Returns a pinned reference to the registration data set by the registering (parent) driver. + /// + /// This method is only available when `F` implements [`trait@CovariantForLt`], which guarantees + /// that the lifetime shortening is sound. + /// + /// For non-covariant types, use the closure-based [`Self::registration_data_with`]. + /// + /// Returns [`EINVAL`] if `F` does not match the type used by the parent driver when calling + /// [`Registration::new()`]. + /// + /// Returns [`ENOENT`] if no registration data has been set, e.g. when the device was + /// registered by a C driver. + #[inline] + pub fn registration_data(&self) -> Result>> { + // SAFETY: `CovariantForLt` guarantees covariance, which makes the lifetime shortening + // from `'static` to `'_` performed by `registration_data_pinned` sound. + unsafe { self.registration_data_pinned::() } } } @@ -399,22 +439,23 @@ struct RegistrationData { /// This type represents the registration of a [`struct auxiliary_device`]. When its parent device /// is unbound, the corresponding auxiliary device will be unregistered from the system. /// -/// The type parameter `F` is a [`CovariantForLt`](trait@CovariantForLt) encoding of the -/// registration data type. For non-lifetime-parameterized types, use -/// [`CovariantForLt!(T)`](macro@CovariantForLt). -/// The data can be accessed by the auxiliary driver through [`Device::registration_data()`]. +/// The type parameter `F` is a [`ForLt`](trait@ForLt) encoding of the registration +/// data type. For non-lifetime-parameterized types, use [`ForLt!(T)`](macro@ForLt). +/// +/// The data can be accessed by the auxiliary driver through [`Device::registration_data()`] and +/// [`Device::registration_data_with()`]. /// /// # Invariants /// /// `self.adev` always holds a valid pointer to an initialized and registered /// [`struct auxiliary_device`] whose `registration_data_rust` field points to a /// valid `Pin>>>`. -pub struct Registration<'a, F: CovariantForLt + 'static> { +pub struct Registration<'a, F: ForLt + 'static> { adev: NonNull, _phantom: PhantomData>, } -impl<'a, F: CovariantForLt> Registration<'a, F> +impl<'a, F: ForLt> Registration<'a, F> where for<'b> F::Of<'b>: Send + Sync, { @@ -526,7 +567,7 @@ where } } -impl Drop for Registration<'_, F> { +impl Drop for Registration<'_, F> { fn drop(&mut self) { // SAFETY: By the type invariant of `Self`, `self.adev.as_ptr()` is a valid registered // `struct auxiliary_device`. @@ -548,7 +589,7 @@ impl Drop for Registration<'_, F> { } // SAFETY: A `Registration` of a `struct auxiliary_device` can be released from any thread. -unsafe impl Send for Registration<'_, F> where for<'a> F::Of<'a>: Send {} +unsafe impl Send for Registration<'_, F> where for<'a> F::Of<'a>: Send {} // SAFETY: `Registration` does not expose any methods or fields that need synchronization. -unsafe impl Sync for Registration<'_, F> where for<'a> F::Of<'a>: Send {} +unsafe impl Sync for Registration<'_, F> where for<'a> F::Of<'a>: Send {} -- cgit From 6763c8876d2a74ebed1754c3ea1f7c13653409a9 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:11 +0200 Subject: rust: auxiliary: sample: demonstrate ForLt with invariant Mutex type Extend the auxiliary driver sample to demonstrate both access patterns: - registration_data() with CovariantForLt!(Data<'_>) for the covariant data type that holds a plain &'bound reference. - registration_data_with() with ForLt!(MutexData<'_>) for an invariant data type that wraps a Mutex<&'bound Device>. Since Mutex is invariant over T, MutexData cannot implement CovariantForLt and must use the closure-based accessor. Reviewed-by: Gary Guo Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260626183630.2585057-5-dakr@kernel.org Signed-off-by: Danilo Krummrich --- samples/rust/rust_driver_auxiliary.rs | 94 +++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 26 deletions(-) diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 92ee6a6d348e..e441ae81fa2c 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -11,14 +11,21 @@ use kernel::{ Core, // }, driver, + new_mutex, pci, prelude::*, - types::CovariantForLt, + sync::Mutex, + types::{ + CovariantForLt, + ForLt, // + }, InPlaceModule, // }; const MODULE_NAME: &CStr = ::NAME; const AUXILIARY_NAME: &CStr = c"auxiliary"; +const COVARIANT_DEV_ID: u32 = 0; +const INVARIANT_DEV_ID: u32 = 1; struct AuxiliaryDriver; @@ -56,12 +63,26 @@ struct Data<'bound> { parent: &'bound pci::Device, } +/// Registration data with interior mutability. +/// +/// `Mutex<&'bound T>` is invariant over `'bound`, so this type cannot implement +/// [`CovariantForLt`](trait@CovariantForLt). Access must go through the closure-based +/// [`auxiliary::Device::registration_data_with()`]. +#[pin_data] +struct MutexData<'bound> { + #[pin] + parent: Mutex<&'bound pci::Device>, + index: u32, +} + struct ParentDriver; #[allow(clippy::type_complexity)] +#[pin_data] struct ParentData<'bound> { _reg0: auxiliary::Registration<'bound, CovariantForLt!(Data<'_>)>, - _reg1: auxiliary::Registration<'bound, CovariantForLt!(Data<'_>)>, + #[pin] + _reg1: auxiliary::Registration<'bound, ForLt!(MutexData<'_>)>, } kernel::pci_device_table!( @@ -81,17 +102,17 @@ impl pci::Driver for ParentDriver { pdev: &'bound pci::Device>, _info: &'bound Self::IdInfo, ) -> impl PinInit, Error> + 'bound { - Ok(ParentData { + try_pin_init!(ParentData { // SAFETY: `ParentData` is the driver's private data, which is dropped when the // device is unbound; i.e. `mem::forget()` is never called on it. _reg0: unsafe { auxiliary::Registration::new_with_lt( pdev.as_ref(), AUXILIARY_NAME, - 0, + COVARIANT_DEV_ID, MODULE_NAME, Data { - index: 0, + index: COVARIANT_DEV_ID, parent: pdev, }, )? @@ -101,12 +122,16 @@ impl pci::Driver for ParentDriver { auxiliary::Registration::new_with_lt( pdev.as_ref(), AUXILIARY_NAME, - 1, + INVARIANT_DEV_ID, MODULE_NAME, - Data { - index: 1, - parent: pdev, - }, + pin_init!(MutexData { + parent <- { + let pdev: &pci::Device = pdev; + + new_mutex!(pdev) + }, + index: INVARIANT_DEV_ID, + }), )? }, }) @@ -115,22 +140,39 @@ impl pci::Driver for ParentDriver { impl ParentDriver { fn connect(adev: &auxiliary::Device) -> Result { - let data = adev.registration_data::)>()?; - let pdev = data.parent; - - dev_info!( - pdev, - "Connect auxiliary {} with parent: VendorID={}, DeviceID={:#x}\n", - adev.id(), - pdev.vendor_id(), - pdev.device_id() - ); - - dev_info!( - pdev, - "Connected to auxiliary device with index {}.\n", - data.index - ); + match adev.id() { + // CovariantForLt types can use the direct-reference accessor. + COVARIANT_DEV_ID => { + let data = adev.registration_data::)>()?; + let pdev = data.parent; + + dev_info!( + pdev, + "Connect auxiliary {} with parent: VendorID={}, DeviceID={:#x}\n", + adev.id(), + pdev.vendor_id(), + pdev.device_id() + ); + + dev_info!( + pdev, + "Connected to auxiliary device with index {}.\n", + data.index + ); + } + // Invariant ForLt types (e.g. containing a Mutex) require the closure-based accessor. + INVARIANT_DEV_ID => { + adev.registration_data_with::), _>(|data| { + let pdev = *data.parent.lock(); + dev_info!( + pdev, + "Connected to auxiliary device with index {} (via Mutex).\n", + data.index + ); + })?; + } + _ => return Err(EINVAL), + } Ok(()) } -- cgit From b85f672a3e68ed16e8c7fa96ec1f090a173930ad Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:12 +0200 Subject: rust: devres: add DevresLt for ForLt-aware device resource access Devres stores resources as T and returns &'a T from access(). For lifetime-parameterized types like Bar<'a, SIZE> that are transmuted to 'static for storage, this exposes the synthetic 'static lifetime to callers -- any method on the stored type that returns a reference with its lifetime parameter would yield a &'static reference, which is unsound. Add DevresLt, a thin wrapper around Devres> that shortens the stored 'static lifetime to the caller's borrow lifetime in all access methods. DevresLt::new() is unsafe because the caller must guarantee that the data remains valid for the device's full bound scope; the internal transmute from F::Of<'a> to F::Of<'static> would otherwise allow use-after-free. Two access patterns are provided: - CovariantForLt types get direct-reference accessors (access, try_access) that return shortened references via CovariantForLt::cast_ref. - Plain ForLt types use closure-based accessors (access_with, try_access_with) whose universally quantified lifetime prevents callers from smuggling in concrete short-lived references. Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260626183630.2585057-6-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 11ce500e9b76..b7c075a39ba4 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -24,6 +24,8 @@ use crate::{ Arc, // }, types::{ + CovariantForLt, + ForLt, ForeignOwnable, Opaque, // }, @@ -365,6 +367,110 @@ impl Drop for Devres { } } +/// Guard returned by [`DevresLt::try_access`]. +/// +/// Dereferences to `F::Of<'a>`, shortening the lifetime of the stored data to the guard's borrow +/// lifetime. +pub struct DevresGuard<'a, F: CovariantForLt>(RevocableGuard<'a, F::Of<'static>>); + +impl<'a, F: CovariantForLt> core::ops::Deref for DevresGuard<'a, F> { + type Target = F::Of<'a>; + + #[inline] + fn deref(&self) -> &Self::Target { + F::cast_ref(&*self.0) + } +} + +/// Device-managed resource with [`ForLt`](trait@ForLt)-aware access. +/// +/// `DevresLt` wraps [`Devres`] and shortens the stored `'static` lifetime to the caller's borrow +/// lifetime in all access methods. +/// +/// Types that implement [`trait@CovariantForLt`] get direct-reference accessors ([`Self::access`], +/// [`Self::try_access`]). Plain [`ForLt`](trait@ForLt) types use closure-based accessors +/// ([`Self::access_with`], [`Self::try_access_with`]). +pub struct DevresLt(Devres>) +where + for<'a> F::Of<'a>: Send; + +impl DevresLt +where + for<'a> F::Of<'a>: Send, +{ + /// Creates a new [`DevresLt`] instance of the given `data`. + /// + /// # Safety + /// + /// The data must remain valid for the device's full bound scope. [`DevresLt`] allows + /// access until the device is unbound, which may outlast `'a`. + pub unsafe fn new<'a, E>( + dev: &'a Device, + data: impl PinInit, E>, + ) -> Result + where + Error: From, + { + // SAFETY: The caller guarantees the data is valid for the device's full bound scope. + // Lifetimes do not affect layout, so F::Of<'a> and F::Of<'static> have identical + // representation; casting the slot pointer is sound. + let data = unsafe { + pin_init::pin_init_from_closure::, E>(move |slot| { + data.__pinned_init(slot.cast()) + }) + }; + + Ok(Self(Devres::new(dev, data)?)) + } + + /// Return a reference of the [`Device`] this [`DevresLt`] instance has been created with. + #[inline] + pub fn device(&self) -> &Device { + self.0.device() + } + + /// Obtain `&F::Of<'_>`, bypassing the [`Revocable`], through a closure. + /// + /// This method works like [`DevresLt::access`](DevresLt::access) but accepts any + /// [`trait@ForLt`] type, not just [`trait@CovariantForLt`]. + #[inline] + pub fn access_with(&self, dev: &Device, f: G) -> Result + where + G: for<'a> FnOnce(&F::Of<'a>) -> R, + { + self.0.access(dev).map(f) + } + + /// [`DevresLt`] accessor for [`Revocable::try_access_with`]. + #[inline] + pub fn try_access_with(&self, f: G) -> Option + where + G: for<'a> FnOnce(&F::Of<'a>) -> R, + { + self.0.data().try_access_with(f) + } +} + +impl DevresLt +where + for<'a> F::Of<'a>: Send, +{ + /// Obtain `&'a F::Of<'a>`, bypassing the [`Revocable`]. + /// + /// This method works like [`Devres::access`], but shortens the returned reference's lifetime + /// from `'static` to `'a` via [`CovariantForLt::cast_ref`]. + #[inline] + pub fn access<'a>(&'a self, dev: &'a Device) -> Result<&'a F::Of<'a>> { + self.0.access(dev).map(F::cast_ref) + } + + /// [`DevresLt`] accessor for [`Revocable::try_access`]. + #[inline] + pub fn try_access(&self) -> Option> { + self.0.data().try_access().map(DevresGuard) + } +} + /// Consume `data` and [`Drop::drop`] `data` once `dev` is unbound. fn register_foreign

(dev: &Device, data: P) -> Result where -- cgit From e7e6bf5af80e7454ebb92a998a2ab931cb27204f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:13 +0200 Subject: rust: pci: return DevresLt from Bar::into_devres() Implement ForLt and CovariantForLt for Bar<'static, SIZE> so that DevresLt can shorten the stored 'static lifetime back to the caller's borrow lifetime. CovariantForLt is sound because Bar<'a, SIZE> only holds &'a Device, which is covariant over 'a. Since DevresLt::new() handles the lifetime transmutation internally, into_devres() no longer needs an explicit transmute to Bar<'static>. Add a DevresBar type alias for convenience. Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260626183630.2585057-7-dakr@kernel.org [ Add default SIZE parameter to DevresBar. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 1 + rust/kernel/pci/io.rs | 37 ++++++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 5071cae6543f..f783b9d9fa26 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -45,6 +45,7 @@ pub use self::io::{ ConfigSpace, ConfigSpaceKind, ConfigSpaceSize, + DevresBar, Extended, Normal, // }; diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs index 0461e01aaa20..6ebfbf368cd3 100644 --- a/rust/kernel/pci/io.rs +++ b/rust/kernel/pci/io.rs @@ -6,7 +6,7 @@ use super::Device; use crate::{ bindings, device, - devres::Devres, + devres::DevresLt, io::{ Io, IoCapable, @@ -14,7 +14,11 @@ use crate::{ Mmio, MmioRaw, // }, - prelude::*, // + prelude::*, + types::{ + CovariantForLt, + ForLt, // + }, // }; use core::{ marker::PhantomData, @@ -151,6 +155,19 @@ pub struct Bar<'a, const SIZE: usize = 0> { num: i32, } +impl ForLt for Bar<'static, SIZE> { + type Of<'a> = Bar<'a, SIZE>; +} + +// SAFETY: `Bar<'a, SIZE>` is covariant over `'a`; it holds `&'a Device`, +// which is covariant. +unsafe impl CovariantForLt for Bar<'static, SIZE> {} + +/// A device-managed PCI BAR mapping. +/// +/// See [`Bar::into_devres`]. +pub type DevresBar = DevresLt>; + impl<'a, const SIZE: usize> Bar<'a, SIZE> { pub(super) fn new( pdev: &'a Device, @@ -223,15 +240,13 @@ impl<'a, const SIZE: usize> Bar<'a, SIZE> { /// Consume the `Bar` and register it as a device-managed resource. /// - /// The returned `Devres>` can outlive the original lifetime `'a`. Access - /// to the BAR is revoked when the device is unbound. - pub fn into_devres(self) -> Result>> { - // SAFETY: Casting to `'static` is sound because `Devres` guarantees the `Bar` does not - // actually outlive the device -- access is revoked and the resource is released when the - // device is unbound. - let bar: Bar<'static, SIZE> = unsafe { core::mem::transmute(self) }; - let pdev = bar.pdev; - Devres::new(pdev.as_ref(), bar) + /// The returned [`DevresBar`] can outlive the original borrow and be stored in driver data. + /// Access to the BAR is revoked automatically when the device is unbound. + pub fn into_devres(self) -> Result> { + let pdev = self.pdev; + // SAFETY: `Bar` only holds a reference to the device and an I/O mapping, both of which + // remain valid for the device's full bound scope, not just for `'a`. + unsafe { DevresLt::new(pdev.as_ref(), self) } } } -- cgit From 7488dc14b05aa4a478497ee1b498a4a46ab9428c Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Fri, 26 Jun 2026 20:36:14 +0200 Subject: rust: io: mem: return DevresLt from IoMem/ExclusiveIoMem::into_devres() Implement ForLt and CovariantForLt for IoMem<'static, SIZE> and ExclusiveIoMem<'static, SIZE> so that DevresLt can shorten the stored 'static lifetime back to the caller's borrow lifetime. CovariantForLt is sound because both types only hold &'a Device, which is covariant over 'a. Since DevresLt::new() handles the lifetime transmutation internally, into_devres() no longer needs an explicit transmute to 'static. Add DevresIoMem and DevresExclusiveIoMem type aliases. Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260626183630.2585057-8-dakr@kernel.org [ Add default SIZE parameter to DevresIoMem. - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/pwm/pwm_th1520.rs | 5 ++-- rust/kernel/io/mem.rs | 65 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index 3e3fa51ccef9..7102c60fd14b 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -24,9 +24,8 @@ use core::ops::Deref; use kernel::{ clk::Clk, device::{Bound, Core, Device}, - devres, io::{ - mem::IoMem, + mem::DevresIoMem, Io, // }, of, platform, @@ -86,7 +85,7 @@ struct Th1520WfHw { #[pin_data(PinnedDrop)] struct Th1520PwmDriverData { #[pin] - iomem: devres::Devres>, + iomem: DevresIoMem, clk: Clk, } diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs index fc2a3e24f8d5..931f2fa3bb10 100644 --- a/rust/kernel/io/mem.rs +++ b/rust/kernel/io/mem.rs @@ -9,7 +9,7 @@ use crate::{ Bound, Device, // }, - devres::Devres, + devres::DevresLt, io::{ self, resource::{ @@ -20,6 +20,10 @@ use crate::{ MmioRaw, // }, prelude::*, + types::{ + CovariantForLt, + ForLt, // + }, }; /// An IO request for a specific device and resource. @@ -172,6 +176,19 @@ pub struct ExclusiveIoMem<'a, const SIZE: usize> { _region: Region, } +impl ForLt for ExclusiveIoMem<'static, SIZE> { + type Of<'a> = ExclusiveIoMem<'a, SIZE>; +} + +// SAFETY: `ExclusiveIoMem<'a, SIZE>` is covariant over `'a`; it holds an `IoMem<'a, SIZE>`, +// which holds `&'a Device`, which is covariant. +unsafe impl CovariantForLt for ExclusiveIoMem<'static, SIZE> {} + +/// A device-managed exclusive I/O memory region. +/// +/// See [`ExclusiveIoMem::into_devres`]. +pub type DevresExclusiveIoMem = DevresLt>; + impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> { /// Creates a new `ExclusiveIoMem` instance. fn ioremap(dev: &'a Device, resource: &Resource) -> Result { @@ -198,15 +215,13 @@ impl<'a, const SIZE: usize> ExclusiveIoMem<'a, SIZE> { /// Consume the `ExclusiveIoMem` and register it as a device-managed resource. /// - /// The returned `Devres>` can outlive the original lifetime - /// `'a`. Access to the I/O memory is revoked when the device is unbound. - pub fn into_devres(self) -> Result>> { - // SAFETY: Casting to `'static` is sound because `Devres` guarantees the - // `ExclusiveIoMem` does not actually outlive the device -- access is revoked and the - // resource is released when the device is unbound. - let iomem: ExclusiveIoMem<'static, SIZE> = unsafe { core::mem::transmute(self) }; - let dev = iomem.iomem.dev; - Devres::new(dev, iomem) + /// The returned [`DevresExclusiveIoMem`] can outlive the original borrow and be stored in + /// driver data. Access to the I/O memory is revoked automatically when the device is unbound. + pub fn into_devres(self) -> Result> { + let dev = self.iomem.dev; + // SAFETY: `ExclusiveIoMem` only holds a device reference and an I/O mapping, both of + // which remain valid for the device's full bound scope, not just for `'a`. + unsafe { DevresLt::new(dev, self) } } } @@ -232,6 +247,19 @@ pub struct IoMem<'a, const SIZE: usize = 0> { io: MmioRaw, } +impl ForLt for IoMem<'static, SIZE> { + type Of<'a> = IoMem<'a, SIZE>; +} + +// SAFETY: `IoMem<'a, SIZE>` is covariant over `'a`; it holds `&'a Device`, +// which is covariant. +unsafe impl CovariantForLt for IoMem<'static, SIZE> {} + +/// A device-managed I/O memory region. +/// +/// See [`IoMem::into_devres`]. +pub type DevresIoMem = DevresLt>; + impl<'a, const SIZE: usize> IoMem<'a, SIZE> { fn ioremap(dev: &'a Device, resource: &Resource) -> Result { // Note: Some ioremap() implementations use types that depend on the CPU @@ -271,16 +299,13 @@ impl<'a, const SIZE: usize> IoMem<'a, SIZE> { /// Consume the `IoMem` and register it as a device-managed resource. /// - /// The returned `Devres>` can outlive the original - /// lifetime `'a`. Access to the I/O memory is revoked when the device - /// is unbound. - pub fn into_devres(self) -> Result>> { - // SAFETY: Casting to `'static` is sound because `Devres` guarantees the `IoMem` does not - // actually outlive the device -- access is revoked and the resource is released when the - // device is unbound. - let iomem: IoMem<'static, SIZE> = unsafe { core::mem::transmute(self) }; - let dev = iomem.dev; - Devres::new(dev, iomem) + /// The returned [`DevresIoMem`] can outlive the original borrow and be stored in driver data. + /// Access to the I/O memory is revoked automatically when the device is unbound. + pub fn into_devres(self) -> Result> { + let dev = self.dev; + // SAFETY: `IoMem` only holds a device reference and an I/O mapping, both of which + // remain valid for the device's full bound scope, not just for `'a`. + unsafe { DevresLt::new(dev, self) } } } -- cgit From 418a74d106ef4fe08fe9eb239e8b9717b3d79ca4 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 7 Jul 2026 18:56:32 +0200 Subject: kobject: Provide macros to initialize 'struct kobj_attribute' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the generic __ATTR*() macros are used to initialize kobject attributes. These are not able to handle the upcoming piece-by-piece constification of the attribute callback handlers. Add dedicated macros of kobject attributes. For now these are the same as the generic macros, but they will change soon. Users will be converted to these macros as part of the constification, but it is expected that they will be used generally in the future. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260707-sysfs-const-attr-kobj-attr-prep-v2-1-35ae1bc0f9ef@weissschuh.net Signed-off-by: Danilo Krummrich --- include/linux/kobject.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/linux/kobject.h b/include/linux/kobject.h index bcb5d4e32001..13bd0e87af7b 100644 --- a/include/linux/kobject.h +++ b/include/linux/kobject.h @@ -144,6 +144,28 @@ struct kobj_attribute { const char *buf, size_t count); }; +#define __KOBJ_ATTR(_name, _mode, _show, _store) { \ + .attr = { .name = __stringify(_name), \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ + .show = _show, \ + .store = _store, \ +} + +#define __KOBJ_ATTR_RO_MODE(_name, _mode) \ + __KOBJ_ATTR(_name, _mode, _name##_show, NULL) + +#define __KOBJ_ATTR_RO(_name) \ + __KOBJ_ATTR_RO_MODE(_name, 0444) + +#define __KOBJ_ATTR_RW_MODE(_name, _mode) \ + __KOBJ_ATTR(_name, _mode, _name##_show, _name##_store) + +#define __KOBJ_ATTR_WO(_name) \ + __KOBJ_ATTR(_name, 0200, NULL, _name##_store) + +#define __KOBJ_ATTR_RW(_name) \ + __KOBJ_ATTR(_name, 0644, _name##_show, _name##_store) + extern const struct sysfs_ops kobj_sysfs_ops; struct sock; -- cgit From a3a9c7453fd512e1700d63563fcd316b7795148d Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 7 Jul 2026 18:56:33 +0200 Subject: samples/kobject: Switch to the new __KOBJ_ATTR() macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To demonstrate and test the upcoming 'const struct kobj_attribute' handling, convert the sample to the new macro. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260707-sysfs-const-attr-kobj-attr-prep-v2-2-35ae1bc0f9ef@weissschuh.net Signed-off-by: Danilo Krummrich --- samples/kobject/kobject-example.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/kobject/kobject-example.c b/samples/kobject/kobject-example.c index 36d87ca0bee2..7404f310dc45 100644 --- a/samples/kobject/kobject-example.c +++ b/samples/kobject/kobject-example.c @@ -45,7 +45,7 @@ static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, /* Sysfs attributes cannot be world-writable. */ static struct kobj_attribute foo_attribute = - __ATTR(foo, 0664, foo_show, foo_store); + __KOBJ_ATTR(foo, 0664, foo_show, foo_store); /* * More complex function where we determine which variable is being accessed by @@ -80,9 +80,9 @@ static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr, } static struct kobj_attribute baz_attribute = - __ATTR(baz, 0664, b_show, b_store); + __KOBJ_ATTR(baz, 0664, b_show, b_store); static struct kobj_attribute bar_attribute = - __ATTR(bar, 0664, b_show, b_store); + __KOBJ_ATTR(bar, 0664, b_show, b_store); /* -- cgit From ed96337a5baeae5eec8ed16f8c614dc316640680 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 7 Jul 2026 18:56:34 +0200 Subject: kobject: Allow the constification of kobject attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow kobject attribute to reside in read-only memory. Both const and non-const attributes are handled by the utility macros and attributes can be migrated one-by-one. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260707-sysfs-const-attr-kobj-attr-prep-v2-3-35ae1bc0f9ef@weissschuh.net Signed-off-by: Danilo Krummrich --- include/linux/kobject.h | 57 +++++++++++++++++++++++++++++++++++++++++++------ lib/kobject.c | 8 +++++-- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/include/linux/kobject.h b/include/linux/kobject.h index 13bd0e87af7b..55e37a5d405e 100644 --- a/include/linux/kobject.h +++ b/include/linux/kobject.h @@ -138,17 +138,62 @@ struct kset_uevent_ops { struct kobj_attribute { struct attribute attr; - ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, - char *buf); - ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, - const char *buf, size_t count); + __SYSFS_FUNCTION_ALTERNATIVE( + ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf); + ssize_t (*show_const)(struct kobject *kobj, const struct kobj_attribute *attr, + char *buf); + ); + __SYSFS_FUNCTION_ALTERNATIVE( + ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count); + ssize_t (*store_const)(struct kobject *kobj, const struct kobj_attribute *attr, + const char *buf, size_t count); + ); }; +typedef ssize_t __kobj_show_handler_const(struct kobject *kobj, const struct kobj_attribute *attr, + char *buf); +typedef ssize_t __kobj_store_handler_const(struct kobject *kobj, const struct kobj_attribute *attr, + const char *buf, size_t count); + +#ifdef CONFIG_CFI + +#define __KOBJ_ATTR_SHOW_STORE(_show, _store) \ + .show = _Generic(_show, \ + __kobj_show_handler_const * : NULL, \ + default : _show \ + ), \ + .show_const = _Generic(_show, \ + __kobj_show_handler_const * : _show, \ + default : NULL \ + ), \ + .store = _Generic(_store, \ + __kobj_store_handler_const * : NULL, \ + default : _store \ + ), \ + .store_const = _Generic(_store, \ + __kobj_store_handler_const * : _store, \ + default : NULL \ + ), + +#else + +#define __KOBJ_ATTR_SHOW_STORE(_show, _store) \ + .show = _Generic(_show, \ + __kobj_show_handler_const * : (void *)_show, \ + default : _show \ + ), \ + .store = _Generic(_store, \ + __kobj_store_handler_const * : (void *)_store, \ + default : _store \ + ), \ + +#endif + #define __KOBJ_ATTR(_name, _mode, _show, _store) { \ .attr = { .name = __stringify(_name), \ .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ - .show = _show, \ - .store = _store, \ + __KOBJ_ATTR_SHOW_STORE(_show, _store) \ } #define __KOBJ_ATTR_RO_MODE(_name, _mode) \ diff --git a/lib/kobject.c b/lib/kobject.c index 9c9ff0f5175f..e7b010a989fb 100644 --- a/lib/kobject.c +++ b/lib/kobject.c @@ -823,9 +823,11 @@ static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr, struct kobj_attribute *kattr; ssize_t ret = -EIO; - kattr = container_of(attr, struct kobj_attribute, attr); + kattr = container_of_const(attr, struct kobj_attribute, attr); if (kattr->show) ret = kattr->show(kobj, kattr, buf); + else if (kattr->show_const) + ret = kattr->show_const(kobj, kattr, buf); return ret; } @@ -835,9 +837,11 @@ static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr, struct kobj_attribute *kattr; ssize_t ret = -EIO; - kattr = container_of(attr, struct kobj_attribute, attr); + kattr = container_of_const(attr, struct kobj_attribute, attr); if (kattr->store) ret = kattr->store(kobj, kattr, buf, count); + else if (kattr->store_const) + ret = kattr->store_const(kobj, kattr, buf, count); return ret; } -- cgit From 584afc86a1d44235ae4548d69efd40f649564233 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 7 Jul 2026 18:56:35 +0200 Subject: samples/kobject: Constify kobject attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Demonstrate that 'const struct kobj_attribute' is now supported by the kobject core. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260707-sysfs-const-attr-kobj-attr-prep-v2-4-35ae1bc0f9ef@weissschuh.net Signed-off-by: Danilo Krummrich --- samples/kobject/kobject-example.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/samples/kobject/kobject-example.c b/samples/kobject/kobject-example.c index 7404f310dc45..36f3fbc47fe0 100644 --- a/samples/kobject/kobject-example.c +++ b/samples/kobject/kobject-example.c @@ -25,13 +25,13 @@ static int bar; /* * The "foo" file where a static variable is read from and written to. */ -static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t foo_show(struct kobject *kobj, const struct kobj_attribute *attr, char *buf) { return sysfs_emit(buf, "%d\n", foo); } -static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t foo_store(struct kobject *kobj, const struct kobj_attribute *attr, const char *buf, size_t count) { int ret; @@ -44,14 +44,14 @@ static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, } /* Sysfs attributes cannot be world-writable. */ -static struct kobj_attribute foo_attribute = +static const struct kobj_attribute foo_attribute = __KOBJ_ATTR(foo, 0664, foo_show, foo_store); /* * More complex function where we determine which variable is being accessed by * looking at the attribute for the "baz" and "bar" files. */ -static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t b_show(struct kobject *kobj, const struct kobj_attribute *attr, char *buf) { int var; @@ -63,7 +63,7 @@ static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr, return sysfs_emit(buf, "%d\n", var); } -static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr, +static ssize_t b_store(struct kobject *kobj, const struct kobj_attribute *attr, const char *buf, size_t count) { int var, ret; @@ -79,9 +79,9 @@ static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr, return count; } -static struct kobj_attribute baz_attribute = +static const struct kobj_attribute baz_attribute = __KOBJ_ATTR(baz, 0664, b_show, b_store); -static struct kobj_attribute bar_attribute = +static const struct kobj_attribute bar_attribute = __KOBJ_ATTR(bar, 0664, b_show, b_store); @@ -89,7 +89,7 @@ static struct kobj_attribute bar_attribute = * Create a group of attributes so that we can create and destroy them all * at once. */ -static struct attribute *attrs[] = { +static const struct attribute *const attrs[] = { &foo_attribute.attr, &baz_attribute.attr, &bar_attribute.attr, @@ -103,7 +103,7 @@ static struct attribute *attrs[] = { * attribute group. */ static const struct attribute_group attr_group = { - .attrs = attrs, + .attrs_const = attrs, }; static struct kobject *example_kobj; -- cgit From d059966c0c7d24c9a8b8e577432209d53a9ebfe0 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 25 Apr 2026 21:30:38 -0700 Subject: firmware_loader: builtin: ignore 0-size firmware Currently, the builtin firmware loader allows 0-size firmware to be returned successfully to drivers. This differs from all other loading mechanisms (filesystem, sysfs fallback) which reject 0-byte files, and forces drivers to add boilerplate size checks. Modify firmware_request_builtin() to reject 0-size firmware. This will also result in firmware loader falling back to other mechanisms if an empty built-in firmware is present. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260426043041.649202-1-dmitry.torokhov@gmail.com Signed-off-by: Danilo Krummrich --- drivers/base/firmware_loader/builtin/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/base/firmware_loader/builtin/main.c b/drivers/base/firmware_loader/builtin/main.c index d36befebb1b9..1dcebe8e7f8e 100644 --- a/drivers/base/firmware_loader/builtin/main.c +++ b/drivers/base/firmware_loader/builtin/main.c @@ -53,6 +53,8 @@ bool firmware_request_builtin(struct firmware *fw, const char *name) for (b_fw = __start_builtin_fw; b_fw != __end_builtin_fw; b_fw++) { if (strcmp(name, b_fw->name) == 0) { + if (b_fw->size == 0) + return false; fw->size = b_fw->size; fw->data = b_fw->data; return true; -- cgit From 1160c2208fab4eaf4a32d738f83474c32c3a6944 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 25 Apr 2026 21:30:39 -0700 Subject: firmware_loader: builtin: fail build on empty firmware If an empty firmware file is supplied via CONFIG_EXTRA_FIRMWARE, the build currently succeeds but creates an empty section. While the firmware loader will not return such sections it is better to avoid adding them in the first place. Add a compile-time size check to the filechk_fwbin macro to abort the build early if a 0-size firmware is encountered. Signed-off-by: Dmitry Torokhov Link: https://patch.msgid.link/20260426043041.649202-2-dmitry.torokhov@gmail.com [ Use 'exit 1' to properly fail the build. - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/base/firmware_loader/builtin/Makefile | 32 +++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/base/firmware_loader/builtin/Makefile b/drivers/base/firmware_loader/builtin/Makefile index 6c067dedc01e..c0264b15d48e 100644 --- a/drivers/base/firmware_loader/builtin/Makefile +++ b/drivers/base/firmware_loader/builtin/Makefile @@ -16,20 +16,24 @@ ASM_ALIGN = $(if $(CONFIG_64BIT),3,2) PROGBITS = $(if $(CONFIG_ARM),%,@)progbits filechk_fwbin = \ - echo "/* Generated by $(src)/Makefile */" ;\ - echo " .section .rodata" ;\ - echo " .p2align 4" ;\ - echo "_fw_$(FWSTR)_bin:" ;\ - echo " .incbin \"$(fwdir)/$(FWNAME)\"" ;\ - echo "_fw_end:" ;\ - echo " .section .rodata.str,\"aMS\",$(PROGBITS),1" ;\ - echo " .p2align $(ASM_ALIGN)" ;\ - echo "_fw_$(FWSTR)_name:" ;\ - echo " .string \"$(FWNAME)\"" ;\ - echo " .section .builtin_fw,\"a\",$(PROGBITS)" ;\ - echo " .p2align $(ASM_ALIGN)" ;\ - echo " $(ASM_WORD) _fw_$(FWSTR)_name" ;\ - echo " $(ASM_WORD) _fw_$(FWSTR)_bin" ;\ + if [ ! -s "$(fwdir)/$(FWNAME)" ]; then \ + echo "error: empty firmware: $(fwdir)/$(FWNAME)" >&2 ;\ + exit 1 ;\ + fi ;\ + echo "/* Generated by $(src)/Makefile */" ;\ + echo " .section .rodata" ;\ + echo " .p2align 4" ;\ + echo "_fw_$(FWSTR)_bin:" ;\ + echo " .incbin \"$(fwdir)/$(FWNAME)\"" ;\ + echo "_fw_end:" ;\ + echo " .section .rodata.str,\"aMS\",$(PROGBITS),1" ;\ + echo " .p2align $(ASM_ALIGN)" ;\ + echo "_fw_$(FWSTR)_name:" ;\ + echo " .string \"$(FWNAME)\"" ;\ + echo " .section .builtin_fw,\"a\",$(PROGBITS)" ;\ + echo " .p2align $(ASM_ALIGN)" ;\ + echo " $(ASM_WORD) _fw_$(FWSTR)_name" ;\ + echo " $(ASM_WORD) _fw_$(FWSTR)_bin" ;\ echo " $(ASM_WORD) _fw_end - _fw_$(FWSTR)_bin" $(obj)/%.gen.S: FORCE -- cgit From acc516dfa1972d31836b50abc0115216cd0fccc5 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sun, 28 Jun 2026 19:44:38 +0200 Subject: rust: devres: fix race between concurrent revokers There is a potential race condition when two paths try to revoke a Devres concurrently. The driver core's devres_release_all() calls Revocable::revoke() via the release callback, while Devres::drop() calls revoke_nosync() on another CPU. The revoker that does not claim the is_available swap returns immediately, but the revoker that did may still be executing drop_in_place() on the inner data. This can cause a use-after-free when the other revoker's caller proceeds to drop adjacent resources that drop_in_place() still references (e.g., Devres racing with SGTable freeing the backing sg_table and pages). Fix this by adding a Completion. The release callback signals the Completion after revoke() finishes, and Devres::drop() waits for it when it loses the is_available swap. This ensures the wrapped object is fully torn down before Devres::drop() returns. Cc: stable@vger.kernel.org Reported-by: Sashiko Closes: https://lore.kernel.org/dri-devel/20260612202841.2577C1F000E9@smtp.kernel.org/ Fixes: 05aa6fb1c21d ("rust: scatterlist: Add abstraction for sg_table") Reviewed-by: Gary Guo Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260628174451.2275679-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 20f94030f977..4a1e5eec78ab 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -21,7 +21,8 @@ use crate::{ sync::{ aref::ARef, rcu, - Arc, // + Arc, + Completion, // }, types::{ CovariantForLt, @@ -39,6 +40,8 @@ struct Inner { node: Opaque, #[pin] data: Revocable, + #[pin] + revocation: Completion, } /// This abstraction is meant to be used by subsystems to containerize [`Device`] bound resources to @@ -55,6 +58,10 @@ struct Inner { /// After the [`Devres`] has been unbound it is not possible to access the encapsulated resource /// anymore. /// +/// When a [`Devres`] is dropped, it is guaranteed that `T` has been fully dropped by the time +/// [`Devres::drop`] returns, even if a concurrent revocation through the release callback is in +/// progress. +/// /// [`Devres`] users should make sure to simply free the corresponding backing resource in `T`'s /// [`Drop`] implementation. /// @@ -222,6 +229,7 @@ impl Devres { }; }), data <- Revocable::new(data), + revocation <- Completion::new(), }), GFP_KERNEL, )?; @@ -259,7 +267,9 @@ impl Devres { // SAFETY: `inner` is a valid `Inner` pointer. let inner = unsafe { &*inner }; - inner.data.revoke(); + if inner.data.revoke() { + inner.revocation.complete_all(); + } } #[allow(clippy::missing_safety_doc)] @@ -363,6 +373,10 @@ impl Drop for Devres { // this additional reference count. drop(unsafe { Arc::from_raw(Arc::as_ptr(&self.inner)) }); } + } else { + // The release callback is concurrently revoking; wait for it to finish + // `drop_in_place()` of the wrapped object before returning. + self.inner.revocation.wait_for_completion(); } } } -- cgit From a10639966fd72fff8f7fbf3c8e733307daabd38f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sun, 28 Jun 2026 22:02:53 +0200 Subject: rust: devres: ensure revocation is complete before device finishes unbinding Now that the revocation Completion is in place, also address the symmetric case. When Devres::drop() wins the is_available swap and the devres callback loses, the callback returns to devres_release_all() without waiting. This means device unbinding can complete while Devres::drop() is still executing drop_in_place() on another CPU, which is a problem if T's destructor accesses device state. Make the synchronization bidirectional. Whichever side performs drop_in_place() signals the Completion, and the other side waits. This does not reintroduce the nested Devres deadlock fixed by commit ba268514ea14 ("rust: devres: fix race condition due to nesting"), because that deadlock was caused by drop waiting for the release callback to return (the old 'devm' Completion). Here, both sides only wait for drop_in_place() to finish, which completes within the current call chain. The Arc> keeps the Inner allocation alive independently. Cc: stable@vger.kernel.org Fixes: ba268514ea14 ("rust: devres: fix race condition due to nesting") Reviewed-by: Gary Guo Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260628200304.2365598-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index 4a1e5eec78ab..ebb5c19e851e 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -269,6 +269,11 @@ impl Devres { if inner.data.revoke() { inner.revocation.complete_all(); + } else { + // Devres::drop() is concurrently revoking; wait for it to finish `drop_in_place()` + // before returning to `devres_release_all()`, ensuring `T` is fully torn down before + // the device finishes unbinding. + inner.revocation.wait_for_completion(); } } @@ -366,6 +371,8 @@ impl Drop for Devres { // SAFETY: When `drop` runs, it is guaranteed that nobody is accessing the revocable data // anymore, hence it is safe not to wait for the grace period to finish. if unsafe { self.data().revoke_nosync() } { + self.inner.revocation.complete_all(); + // We revoked `self.data` before devres did, hence try to remove it. if self.remove_node() { // SAFETY: In `Self::new` we have taken an additional reference count of `self.data` -- cgit From f810b41defc44535715afa978dbcba8b3b9b9d99 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Tue, 14 Jul 2026 20:18:01 +0200 Subject: container_of: apply typeof_member() to container_of() container_of() uses the construct below: ((type *)0)->member to retrieve the type of the structure's member and then ensure that it matches the type of the given pointer. This construct being rather difficult to understand, the typeof_member() macro was created to encapsulate it and give it a descriptive name. Apply typeof_member() to container_of() to make it easier to read and understand what this macro does. Signed-off-by: Vincent Mailhol Link: https://patch.msgid.link/20260714-containerof_refactor-v1-1-b5c31164d2ad@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/linux/container_of.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/container_of.h b/include/linux/container_of.h index 1f6ebf27d962..28500a62ab7e 100644 --- a/include/linux/container_of.h +++ b/include/linux/container_of.h @@ -18,7 +18,7 @@ */ #define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ - static_assert(__same_type(*(ptr), ((type *)0)->member) || \ + static_assert(__same_type(*(ptr), typeof_member(type, member)) || \ __same_type(*(ptr), void), \ "pointer type mismatch in container_of()"); \ ((type *)(__mptr - offsetof(type, member))); }) -- cgit From 1ead62bc1bd7f48bc7d4d107769fb991f6f3961c Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Tue, 14 Jul 2026 20:18:02 +0200 Subject: container_of: remove useless pair of parentheses The last expression in container_of() doesn't need an extra pair of parenthesis. Remove it. Signed-off-by: Vincent Mailhol Link: https://patch.msgid.link/20260714-containerof_refactor-v1-2-b5c31164d2ad@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/linux/container_of.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/container_of.h b/include/linux/container_of.h index 28500a62ab7e..68153170db32 100644 --- a/include/linux/container_of.h +++ b/include/linux/container_of.h @@ -21,7 +21,7 @@ static_assert(__same_type(*(ptr), typeof_member(type, member)) || \ __same_type(*(ptr), void), \ "pointer type mismatch in container_of()"); \ - ((type *)(__mptr - offsetof(type, member))); }) + (type *)(__mptr - offsetof(type, member)); }) /** * container_of_const - cast a member of a structure out to the containing -- cgit From f9e7a7564834af3e1239fbb9a4161f448edd04fa Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Tue, 14 Jul 2026 20:18:03 +0200 Subject: container_of: remove local __mptr variable container_of() can be called in a nested manner to retrieve the grand parent structure as illustrated below: struct foo { int a; }; struct bar { struct foo foo; }; #define to_foo(a_ptr) container_of(a_ptr, struct foo, a) #define to_bar(a_ptr) container_of(to_foo(a_ptr), struct bar, foo) The issue is that the above construct will cause __mptr, the local variable of container_of(), to shadow itself because of the nested call. This then triggers a warning in sparse and W=2 builds. While this warning is benign, it still causes some overhead as proven by below list of commits in which people made local workarounds: - commit 7eab14de73a8 ("mdio, phy: fix -Wshadow warnings triggered by nested container_of()") - commit 8d8c3131248d ("clk: define to_clk_regmap() as inline function") - commit bfb972c5e1cb ("IB/verbs: avoid nested container_of()") - commit 093adbcedf12 ("btrfs: switch helper macros to static inlines in sysfs.h") - commit c1d35dfa0f7d ("rt2x00: Fix sparse warning on nested container_of()") (the list is probably not exhaustive). As a matter of fact, the local variable __mptr is only used once in container_of(). As such, it is not strictly needed. Inline that local __mptr variable to remove once and for all the risk of variable shadowing when nesting container_of() and prevent people from writing further local fixes. Signed-off-by: Vincent Mailhol Link: https://patch.msgid.link/20260714-containerof_refactor-v1-3-b5c31164d2ad@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/linux/container_of.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/linux/container_of.h b/include/linux/container_of.h index 68153170db32..28db38e9ee3e 100644 --- a/include/linux/container_of.h +++ b/include/linux/container_of.h @@ -17,11 +17,10 @@ * Do not use container_of() in new code. */ #define container_of(ptr, type, member) ({ \ - void *__mptr = (void *)(ptr); \ static_assert(__same_type(*(ptr), typeof_member(type, member)) || \ __same_type(*(ptr), void), \ "pointer type mismatch in container_of()"); \ - (type *)(__mptr - offsetof(type, member)); }) + (type *)((void *)(ptr) - offsetof(type, member)); }) /** * container_of_const - cast a member of a structure out to the containing -- cgit From 3fadfb9362cf1b7d0a775f406f011d78800d6054 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sat, 11 Jul 2026 20:02:31 +0200 Subject: rust: firmware: avoid UB in example by passing parameter The `Firmware` example crafts an `ARef` using a null pointer, which breaks the safety requirements of `Device::get_device()`. Instead, pass an `ARef` via a parameter, which is simpler, avoids UB and removes an `unsafe` block. Signed-off-by: Miguel Ojeda Reviewed-by: Alexandre Courbot Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260711180231.229525-1-ojeda@kernel.org [ Also drop the second superfluous empty line. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/firmware.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/rust/kernel/firmware.rs b/rust/kernel/firmware.rs index 71168d8004e2..1e3dd2e3422d 100644 --- a/rust/kernel/firmware.rs +++ b/rust/kernel/firmware.rs @@ -51,12 +51,8 @@ impl FwFunc { /// # Examples /// /// ```no_run -/// # use kernel::{device::Device, firmware::Firmware}; -/// -/// # fn no_run() -> Result<(), Error> { -/// # // SAFETY: *NOT* safe, just for the example to get an `ARef` instance -/// # let dev = unsafe { Device::get_device(core::ptr::null_mut()) }; -/// +/// # use kernel::{device::Device, firmware::Firmware, sync::aref::ARef}; +/// # fn no_run(dev: ARef) -> Result<(), Error> { /// let fw = Firmware::request(c"path/to/firmware.bin", &dev)?; /// let blob = fw.data(); /// -- cgit From 10864921212fc88afd39adb859a00f0912e88f7e Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Sat, 9 May 2026 19:17:28 +0800 Subject: driver core: avoid repeatedly printing the same 'Fixed dependency' log The "Fixed dependency cycle(s) with ..." log is printed for each cycle finding process. In particular, the same cycle may be parsed many times during the proxy link setup. For example, if /soc/bus@4c000000/usb@4c100000 is a devicetree structure and node usb@4c100000 is in a cycle, then for each device registration (soc, 4c000000.bus and 4c100000.usb), fw_devlink_create_devlink() will find the same cycle and print a log. The repeat logs are redundant and may cause confusion for the user. Move the pr_debug() to __fwnode_link_cycle() and avoid printing the log if it has already been printed. Signed-off-by: Xu Yang Link: https://patch.msgid.link/20260509111728.4123331-1-xu.yang_2@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/base/core.c b/drivers/base/core.c index 4d026682944f..4c0c373998a1 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -127,7 +127,13 @@ static void __fwnode_link_cycle(struct fwnode_link *link) { pr_debug("%pfwf: cycle: depends on %pfwf\n", link->consumer, link->supplier); + + if (link->flags & FWLINK_FLAG_CYCLE) + return; + link->flags |= FWLINK_FLAG_CYCLE; + pr_info("%pfwf: Fixed dependency cycle(s) with %pfwf\n", + link->consumer, link->supplier); } /** @@ -2206,8 +2212,6 @@ static int fw_devlink_create_devlink(struct device *con, if (__fw_devlink_relax_cycles(link->consumer, sup_handle)) { __fwnode_link_cycle(link); pr_debug("----- cycle: end -----\n"); - pr_info("%pfwf: Fixed dependency cycle(s) with %pfwf\n", - link->consumer, sup_handle); } device_links_write_unlock(); -- cgit From 1900692555826753adab8799a1a8d50bb1ee200c Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Thu, 11 Jun 2026 22:31:06 +0200 Subject: device property: fix infinite loop in fwnode_for_each_child_node() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When iterate over children of a fwnode that has a secondary fwnode, fwnode_get_next_child_node() can enter an infinite loop if the secondary fwnode has more than one child. Parent Child (Primary fwnode) FWa: {FWa1, FWa2, FWa3} (Secondary fwnode) FWb: {FWb1, FWb2} In this case: ┌─> fwnode_get_next_child_node(FWa, FWa1) │ - fwnode_call_ptr_op(FWa, get_next_child_node, FWa1) returns FWa2 │ │ ... │ │ fwnode_get_next_child_node(FWa, FWa3) │ - fwnode_call_ptr_op(FWa, get_next_child_node, FWa3) returns NULL │ - fwnode_call_ptr_op(FWb, get_next_child_node, FWa3) returns FWb1 │ │ fwnode_get_next_child_node(FWa, FWb1) │ - fwnode_call_ptr_op(FWa, get_next_child_node, FWb1) returns FWa1 └────┘ This cause fwnode_for_each_child_node() to loop indefinitely, reapeatedly output {FWa1, FWa2, FWa3, FWb1, FWa1, ...}. The root cause is that when the current child (FWb1) belongs to the secondary fwnode, calling get_next_child_node() on the parimary fwnode incorrectly returns the first child (FWa1) again instead of NULL. Fix this by dynamically checking the parent fwnode of the current child before calling get_next_child_node(). This approach follows the pattern established in commit b5b41ab6b0c1 ("device property: Check fwnode->secondary in fwnode_graph_get_next_endpoint()"). Fixes: 2692c614f8f0 ("device property: Allow secondary lookup in fwnode_get_next_child_node()") Cc: stable@vger.kernel.org Signed-off-by: Xu Yang Tested-by: Andy Shevchenko Signed-off-by: Andy Shevchenko Tested-by: Xu Yang Link: https://patch.msgid.link/20260611203537.1786399-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/property.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/base/property.c b/drivers/base/property.c index e08eadd66f4f..e0f570f48bc3 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -807,18 +807,31 @@ struct fwnode_handle * fwnode_get_next_child_node(const struct fwnode_handle *fwnode, struct fwnode_handle *child) { + const struct fwnode_handle *parent; + struct fwnode_handle *child_parent __free(fwnode_handle) = NULL; struct fwnode_handle *next; - if (IS_ERR_OR_NULL(fwnode)) + /* + * If this function is in a loop and the previous iteration returned + * an child from fwnode->secondary, then we need to use the secondary + * as parent rather than @fwnode. + */ + if (child) { + child_parent = fwnode_get_parent(child); + parent = child_parent; + } else { + parent = fwnode; + } + if (IS_ERR_OR_NULL(parent)) return NULL; /* Try to find a child in primary fwnode */ - next = fwnode_call_ptr_op(fwnode, get_next_child_node, child); + next = fwnode_call_ptr_op(parent, get_next_child_node, child); if (next) return next; /* When no more children in primary, continue with secondary */ - return fwnode_call_ptr_op(fwnode->secondary, get_next_child_node, child); + return fwnode_get_next_child_node(parent->secondary, NULL); } EXPORT_SYMBOL_GPL(fwnode_get_next_child_node); -- cgit From c37ab60d55e34ed0fd050926abd602bb31a5036d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 11 Jun 2026 22:31:07 +0200 Subject: device property: Refactor to use RAII approach In a couple of functions code can be made cleaner with help of __free() macro. Refactor these to use RAII approach. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260611203537.1786399-3-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/property.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/base/property.c b/drivers/base/property.c index e0f570f48bc3..b136c339ddae 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -7,10 +7,10 @@ * Mika Westerberg */ +#include #include #include #include -#include #include #include #include @@ -517,7 +517,6 @@ EXPORT_SYMBOL_GPL(fwnode_property_read_string); int fwnode_property_match_string(const struct fwnode_handle *fwnode, const char *propname, const char *string) { - const char **values; int nval, ret; nval = fwnode_property_string_array_count(fwnode, propname); @@ -527,20 +526,18 @@ int fwnode_property_match_string(const struct fwnode_handle *fwnode, if (nval == 0) return -ENODATA; - values = kcalloc(nval, sizeof(*values), GFP_KERNEL); + const char **values __free(kfree) = kcalloc(nval, sizeof(*values), GFP_KERNEL); if (!values) return -ENOMEM; ret = fwnode_property_read_string_array(fwnode, propname, values, nval); if (ret < 0) - goto out_free; + return ret; ret = match_string(values, nval, string); if (ret < 0) - ret = -ENODATA; + return -ENODATA; -out_free: - kfree(values); return ret; } EXPORT_SYMBOL_GPL(fwnode_property_match_string); @@ -1128,8 +1125,9 @@ struct fwnode_handle * fwnode_graph_get_next_endpoint(const struct fwnode_handle *fwnode, struct fwnode_handle *prev) { - struct fwnode_handle *ep, *port_parent = NULL; const struct fwnode_handle *parent; + struct fwnode_handle *port_parent __free(fwnode_handle) = NULL; + struct fwnode_handle *ep; /* * If this function is in a loop and the previous iteration returned @@ -1147,13 +1145,9 @@ fwnode_graph_get_next_endpoint(const struct fwnode_handle *fwnode, ep = fwnode_call_ptr_op(parent, graph_get_next_endpoint, prev); if (ep) - goto out_put_port_parent; - - ep = fwnode_graph_get_next_endpoint(parent->secondary, NULL); + return ep; -out_put_port_parent: - fwnode_handle_put(port_parent); - return ep; + return fwnode_graph_get_next_endpoint(parent->secondary, NULL); } EXPORT_SYMBOL_GPL(fwnode_graph_get_next_endpoint); -- cgit From 807be7ee6fb8d7ea63eab0277ed081d8fe83abee Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sat, 30 May 2026 15:27:31 +0200 Subject: rust: device: add BoundInternal device context and InternalBoundContext trait Add a new BoundInternal device context type for cases where bus abstractions need access to internal device infrastructure, where CoreInternal would not be justified. Introduce the InternalBoundContext marker trait, implemented by both CoreInternal and BoundInternal, to allow methods that require internal bus abstraction access to a bound device to be generic over both contexts. The deref hierarchy now has two branches: - CoreInternal<'a> => Core<'a> => Bound => Normal - BoundInternal => Bound => Normal Update impl_device_context_deref! and impl_device_context_into_aref! macros to emit the BoundInternal => Bound deref and the corresponding ARef conversion. Signed-off-by: Danilo Krummrich Reviewed-By: Markus Probst Link: https://patch.msgid.link/20260530132736.3298549-1-dakr@kernel.org Signed-off-by: Greg Kroah-Hartman --- rust/kernel/device.rs | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 645afc49a27d..354c4045f404 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -54,7 +54,8 @@ pub mod property; /// reference is valid in. For instance, the [`Bound`] context guarantees that the [`Device`] is /// bound to a driver for the entire duration of the existence of a [`Device`] reference. /// -/// Other [`DeviceContext`] types besides [`Bound`] are [`Normal`], [`Core`] and [`CoreInternal`]. +/// Other [`DeviceContext`] types besides [`Bound`] are [`Normal`], [`Core`], [`CoreInternal`] and +/// [`BoundInternal`]. /// /// Unless selected otherwise [`Device`] defaults to the [`Normal`] [`DeviceContext`], which by /// itself has no additional requirements. @@ -476,7 +477,8 @@ unsafe impl Sync for Device {} /// [`DeviceContext`] is a marker trait for types representing the context of a bus specific /// [`Device`]. /// -/// The specific device context types are: [`CoreInternal`], [`Core`], [`Bound`] and [`Normal`]. +/// The specific device context types are: [`CoreInternal`], [`Core`], [`BoundInternal`], [`Bound`] +/// and [`Normal`]. /// /// [`DeviceContext`] types are hierarchical, which means that there is a strict hierarchy that /// defines which [`DeviceContext`] type can be derived from another. For instance, any @@ -485,6 +487,11 @@ unsafe impl Sync for Device {} /// The following enumeration illustrates the dereference hierarchy of [`DeviceContext`] types. /// /// - [`CoreInternal`] => [`Core`] => [`Bound`] => [`Normal`] +/// - [`BoundInternal`] => [`Bound`] => [`Normal`] +/// +/// Both [`CoreInternal`] and [`BoundInternal`] implement the [`InternalBoundContext`] trait, +/// which provides access to internal bus abstraction methods on [`Device`] that are not available +/// to drivers. /// /// Bus devices can automatically implement the dereference hierarchy by using /// [`impl_device_context_deref`]. @@ -524,6 +531,18 @@ pub struct Core<'a>(PhantomData<&'a ()>); /// from bus callbacks with bus abstractions, but without making them accessible for drivers. pub struct CoreInternal<'a>(PhantomData<&'a ()>); +/// Semantically the same as [`Bound`], but reserved for internal usage of the corresponding bus +/// abstraction. +/// +/// The internal bound context is intended to be used in exactly the same way as the [`Bound`] +/// context, with the difference that this [`DeviceContext`] is internal to the corresponding bus +/// abstraction. +/// +/// This context exists for cases where the bus abstraction needs access to internal device +/// infrastructure (such as [`Device::drvdata_borrow`]), where [`CoreInternal`] would not be +/// justified. +pub struct BoundInternal; + /// The [`Bound`] context is the [`DeviceContext`] of a bus specific device when it is guaranteed to /// be bound to a driver. /// @@ -546,16 +565,28 @@ mod private { pub trait Sealed {} impl Sealed for super::Bound {} + impl Sealed for super::BoundInternal {} impl<'a> Sealed for super::Core<'a> {} impl<'a> Sealed for super::CoreInternal<'a> {} impl Sealed for super::Normal {} } impl DeviceContext for Bound {} +impl DeviceContext for BoundInternal {} impl<'a> DeviceContext for Core<'a> {} impl<'a> DeviceContext for CoreInternal<'a> {} impl DeviceContext for Normal {} +/// Marker trait for [`DeviceContext`] types that have internal bound-level access. +/// +/// This trait is implemented by [`CoreInternal`] and [`BoundInternal`], allowing methods that +/// require internal bus abstraction access to a bound device to be generic over both contexts. +/// +/// Methods bounded by this trait are available to bus abstractions but not to drivers. +pub trait InternalBoundContext: DeviceContext {} +impl<'a> InternalBoundContext for CoreInternal<'a> {} +impl InternalBoundContext for BoundInternal {} + impl AsRef> for Device { #[inline] fn as_ref(&self) -> &Device { @@ -661,6 +692,13 @@ macro_rules! impl_device_context_deref { <'a> $crate::device::Core<'a> => $crate::device::Bound }); + // SAFETY: This macro has the exact same safety requirement as + // `__impl_device_context_deref!`. + ::kernel::__impl_device_context_deref!(unsafe { + $device, + $crate::device::BoundInternal => $crate::device::Bound + }); + // SAFETY: This macro has the exact same safety requirement as // `__impl_device_context_deref!`. ::kernel::__impl_device_context_deref!(unsafe { @@ -700,6 +738,7 @@ macro_rules! impl_device_context_into_aref { ::kernel::__impl_device_context_into_aref!( <'a> $crate::device::Core<'a>, $device ); + ::kernel::__impl_device_context_into_aref!($crate::device::BoundInternal, $device); ::kernel::__impl_device_context_into_aref!($crate::device::Bound, $device); }; } -- cgit From 54b4b3aa7c5b59ca6b507ef36f8f6060bdf34f5f Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sat, 30 May 2026 15:27:32 +0200 Subject: rust: device: move drvdata_borrow() to InternalBoundContext Move drvdata_borrow() from impl Device> to impl Device, making it available from both CoreInternal and BoundInternal contexts. Fold drvdata_unchecked() (previously on Device) directly into drvdata_borrow(), since it was only called from there and the generic context cannot resolve methods through the deref chain. Signed-off-by: Danilo Krummrich Reviewed-By: Markus Probst Link: https://patch.msgid.link/20260530132736.3298549-2-dakr@kernel.org Signed-off-by: Greg Kroah-Hartman --- rust/kernel/device.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index 354c4045f404..b538c39982a6 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -236,7 +236,9 @@ impl<'a> Device> { // in `into_foreign()`. Some(unsafe { Pin::>::from_foreign(ptr.cast()) }) } +} +impl Device { /// Borrow the driver's private data bound to this [`Device`]. /// /// # Safety @@ -246,22 +248,6 @@ impl<'a> Device> { /// - The type `T` must match the type of the `ForeignOwnable` previously stored by /// [`Device::set_drvdata`]. pub unsafe fn drvdata_borrow(&self) -> Pin<&T> { - // SAFETY: `drvdata_unchecked()` has the exact same safety requirements as the ones - // required by this method. - unsafe { self.drvdata_unchecked() } - } -} - -impl Device { - /// Borrow the driver's private data bound to this [`Device`]. - /// - /// # Safety - /// - /// - Must only be called after a preceding call to [`Device::set_drvdata`] and before - /// the device is fully unbound. - /// - The type `T` must match the type of the `ForeignOwnable` previously stored by - /// [`Device::set_drvdata`]. - unsafe fn drvdata_unchecked(&self) -> Pin<&T> { // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`. let ptr = unsafe { bindings::dev_get_drvdata(self.as_raw()) }; -- cgit From 6e328b4a208f7a06df3c1df18d7645c74c90f5f3 Mon Sep 17 00:00:00 2001 From: "Ewan D. Milne" Date: Mon, 13 Jul 2026 13:33:18 -0400 Subject: drivers: base: Remove statistics group if encryption group not created If transport_add_class_device() gets an error from sysfs_create_group() when creating the encryption group, it does not remove the statistics group in the error path. Adjust the error path to do this properly. v2: Only remove statistics group if tcont->statistics is non-NULL Fixes: bd2bc528691e ("scsi: scsi_transport_fc: Introduce encryption group") Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Ewan D. Milne Reviewed-by: Justin Tee Link: https://patch.msgid.link/20260713173318.3060047-1-emilne@redhat.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/transport_class.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/base/transport_class.c b/drivers/base/transport_class.c index 416e9f819df5..351c3d3ce6a0 100644 --- a/drivers/base/transport_class.c +++ b/drivers/base/transport_class.c @@ -168,11 +168,14 @@ static int transport_add_class_device(struct attribute_container *cont, if (tcont->encryption) { error = sysfs_create_group(&classdev->kobj, tcont->encryption); if (error) - goto err_del; + goto err_del_statistics; } return 0; +err_del_statistics: + if (tcont->statistics) + sysfs_remove_group(&classdev->kobj, tcont->statistics); err_del: attribute_container_class_device_del(classdev); err_remove: -- cgit From 8cfe7ad274bb42c4653b645267410d4a3a23db98 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 8 Jun 2026 13:27:17 -0400 Subject: driver core: attribute_container: Unwind device_add() on attr failure attribute_container_add_class_device() first calls device_add() and then creates the container attributes. If attribute_container_add_attrs() fails, the helper returns the error with the class device still registered. Callers use an error from this helper as a failed add and do not call device_del() on that path. Undo the successful device_add() before returning the attribute creation error. Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260608172717.88657-1-dbgh9129@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/attribute_container.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/base/attribute_container.c b/drivers/base/attribute_container.c index 4ad26b8dd6a5..3c456374247b 100644 --- a/drivers/base/attribute_container.c +++ b/drivers/base/attribute_container.c @@ -425,7 +425,12 @@ attribute_container_add_class_device(struct device *classdev) if (error) return error; - return attribute_container_add_attrs(classdev); + + error = attribute_container_add_attrs(classdev); + if (error) + device_del(classdev); + + return error; } /** -- cgit From 38f5745f49e446bf0fa3fcfa2d1a58538591ed77 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Thu, 18 Jun 2026 01:39:19 +0900 Subject: arch_numa: remove redundant nodemask clears in numa_init() numa_init() clears numa_nodes_parsed, node_possible_map and node_online_map, then calls numa_memblks_init(), which clears the same nodemasks. Nothing uses them in between. These clears have been redundant since commit 767507654c22 ("arch_numa: switch over to numa_memblks") made numa_init() use numa_memblks_init(). No functional change. Signed-off-by: Sang-Heon Jeon Reviewed-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260617163919.2544899-1-ekffu200098@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/arch_numa.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/base/arch_numa.c b/drivers/base/arch_numa.c index c99f2ab105e5..442ea239bba7 100644 --- a/drivers/base/arch_numa.c +++ b/drivers/base/arch_numa.c @@ -231,10 +231,6 @@ static int __init numa_init(int (*init_func)(void)) { int ret; - nodes_clear(numa_nodes_parsed); - nodes_clear(node_possible_map); - nodes_clear(node_online_map); - ret = numa_memblks_init(init_func, /* memblock_force_top_down */ false); if (ret < 0) goto out_free_distance; -- cgit From 32e21e9509915aef8b4043b4596ddb40f390438b Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Fri, 12 Jun 2026 16:53:32 +0200 Subject: docs: ABI: sysfs-uevent: add missing bracket Add ']' to complete the command. Signed-off-by: Manuel Ebner Link: https://patch.msgid.link/20260612145331.189632-2-manuelebner@mailbox.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-uevent | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-uevent b/Documentation/ABI/testing/sysfs-uevent index 0b6227706b35..c15e18c47a0e 100644 --- a/Documentation/ABI/testing/sysfs-uevent +++ b/Documentation/ABI/testing/sysfs-uevent @@ -8,7 +8,7 @@ Description: Recognized extended format is:: - ACTION [UUID [KEY=VALUE ...] + ACTION [UUID [KEY=VALUE ...]] The ACTION is compulsory - it is the name of the uevent action (``add``, ``change``, ``remove``). There is no change -- cgit From d996995a0ffb86fa0ff0f953221892dced6143a1 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Wed, 17 Jun 2026 10:39:41 +0300 Subject: kernfs: simplify kernfs_name_hash() In 'kernfs_name_hash()', 'name' is NUL-terminated so it may be directly scanned up to end without an extra call to 'strlen()'. Signed-off-by: Dmitry Antipov Link: https://patch.msgid.link/20260617073941.472337-1-dmantipov@yandex.ru Signed-off-by: Greg Kroah-Hartman --- fs/kernfs/dir.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 97d9d227b66d..82bbaeb326aa 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -330,8 +330,8 @@ static unsigned int kernfs_name_hash(const char *name, const struct ns_common *ns) { unsigned long hash = init_name_hash(kernfs_ns_id(ns)); - unsigned int len = strlen(name); - while (len--) + + while (*name) hash = partial_name_hash(*name++, hash); hash = end_name_hash(hash); hash &= 0x7fffffffU; -- cgit From 06553566a841cc5ff8cd4dd11c00251b834c7fba Mon Sep 17 00:00:00 2001 From: David Laight Date: Sat, 6 Jun 2026 21:26:00 +0100 Subject: kernfs: Replace strcpy(s, "../") with memcpy(s, "../", 4) The code has already checked there is enough room. Use memcpy() to avoid compiler warnings from possibly unbounded strcpy(). Signed-off-by: David Laight Link: https://patch.msgid.link/20260606202633.5018-6-david.laight.linux@gmail.com Signed-off-by: Greg Kroah-Hartman --- fs/kernfs/symlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 0bd8a2143723..90e2b3221b83 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -73,7 +73,7 @@ static int kernfs_get_target_path(struct kernfs_node *parent, if ((s - path) + 3 >= PATH_MAX) return -ENAMETOOLONG; - strcpy(s, "../"); + memcpy(s, "../", 4); s += 3; base = kernfs_parent(base); } -- cgit From c3a280ff728a9039c72cd64ad0b32bc3a28c25b2 Mon Sep 17 00:00:00 2001 From: Yohei Kojima Date: Thu, 11 Jun 2026 22:50:16 +0900 Subject: debugfs: warn if file creation failed due to uninitialized debugfs Improve debugfs_start_creating() to warn if it was used before debugfs initialization. It silently returned ERR_PTR(-ENOENT) before, but it is hard to find the cause of failure especially if it was called by debugfs_create_dir(), because the document of the function says: > NOTE: it's expected that most callers should _ignore_ the errors returned > by this function. Other debugfs functions handle the fact that the "dentry" > passed to them could be an error and they don't crash in that case. > Drivers should generally work fine even if debugfs fails to init anyway. Signed-off-by: Yohei Kojima Link: https://patch.msgid.link/6d1dc775f7d5e754d734907514534054f682bac5.1781171918.git.yk@y-koj.net Signed-off-by: Greg Kroah-Hartman --- fs/debugfs/inode.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index 4598142355b9..e054e62919ec 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -368,8 +368,11 @@ static struct dentry *debugfs_start_creating(const char *name, if (!debugfs_enabled) return ERR_PTR(-EPERM); - if (!debugfs_initialized()) + if (!debugfs_initialized()) { + pr_err("Unable to create file '%s', debugfs is not initialized yet\n", + name); return ERR_PTR(-ENOENT); + } pr_debug("creating file '%s'\n", name); -- cgit From ba3dedcf3bd47017307595a7e54924198f018246 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Thu, 11 Jun 2026 18:40:05 +0200 Subject: software node: Fix software_node_get_reference_args() with index -1 The bounds check for the index passed to software_node_get_reference_args() was failing when passed UINT_MAX, this in turn would lead to an out of bound access in the property array. Fix the bound check to also cover the UINT_MAX case. Fixes: 31e4e12e0e960 ("software node: Correct a OOB check in software_node_get_reference_args()") Reported-by: Sashiko Closes: https://lore.kernel.org/linux-devicetree/20260611103904.7CB131F00893@smtp.kernel.org/ Signed-off-by: Alban Bedel Link: https://patch.msgid.link/20260611164005.2930205-1-alban.bedel@lht.dlh.de Signed-off-by: Greg Kroah-Hartman --- drivers/base/swnode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 1f2315858cc3..84a77ffa3dc4 100644 --- a/drivers/base/swnode.c +++ b/drivers/base/swnode.c @@ -537,7 +537,7 @@ software_node_get_reference_args(const struct fwnode_handle *fwnode, if (prop->is_inline) return -EINVAL; - if ((index + 1) * sizeof(*ref) > prop->length) + if (index >= prop->length / sizeof(*ref)) return -ENOENT; ref_array = prop->pointer; -- cgit From 45dfa004893dfeae182ec27eddbd153c6d4ddbf9 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 15 Jun 2026 14:07:46 -0400 Subject: driver core: soc: Unregister bus on early device registration failure soc_bus_register() registers the SoC bus before registering a deferred early SoC device. If soc_device_register() fails in that path, the function returns the error directly and leaves the bus registered. Store the returned SoC device pointer explicitly so the success and error cases are handled separately. On failure, clear soc_bus_registered and unregister the bus before returning the error. Fixes: 6e12db376b60 ("base: soc: Allow early registration of a single SoC device") Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260615180746.713540-1-dbgh9129@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/soc.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/base/soc.c b/drivers/base/soc.c index 65ce72d49230..af7d71393774 100644 --- a/drivers/base/soc.c +++ b/drivers/base/soc.c @@ -191,6 +191,7 @@ EXPORT_SYMBOL_GPL(soc_device_unregister); static int __init soc_bus_register(void) { + struct soc_device *soc_dev; int ret; ret = bus_register(&soc_bus_type); @@ -198,10 +199,20 @@ static int __init soc_bus_register(void) return ret; soc_bus_registered = true; - if (early_soc_dev_attr) - return PTR_ERR(soc_device_register(early_soc_dev_attr)); + if (early_soc_dev_attr) { + soc_dev = soc_device_register(early_soc_dev_attr); + if (IS_ERR(soc_dev)) { + ret = PTR_ERR(soc_dev); + goto err_unregister_bus; + } + } return 0; + +err_unregister_bus: + soc_bus_registered = false; + bus_unregister(&soc_bus_type); + return ret; } core_initcall(soc_bus_register); -- cgit From 4e63c0582e2aef7284a2cc7c62215f245003e775 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 19 Jun 2026 12:03:31 +0200 Subject: base: Remove unused DMA_FENCE_TRACE Kconfig symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit d72277b6c37db66b ("dma-buf: nuke DMA_FENCE_TRACE macros v2") in v5.16 removed all users of DMA_FENCE_TRACE on the premise that the Kconfig symbol did not exist. Apparently one failed to notice the symbol did exist since almost five years before: it was renamed from FENCE_TRACE to DMA_FENCE_TRACE in commit f54d1867005c3323 ("dma-buf: Rename struct fence to dma_fence") in v4.10. Time passed by, so remove the Kconfig symbol, as no one seems to have missed the functionality. Signed-off-by: Geert Uytterhoeven Reviewed-by: Christian König Link: https://patch.msgid.link/16fb40ded203d1e2b72f4eeecad3fd0c0d23ad6f.1781863296.git.geert@linux-m68k.org Signed-off-by: Greg Kroah-Hartman --- drivers/base/Kconfig | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index f7d385cbd3ba..43f20ca95a2a 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -222,15 +222,6 @@ config DMA_SHARED_BUFFER APIs extension; the file's descriptor can then be passed on to other driver. -config DMA_FENCE_TRACE - bool "Enable verbose DMA_FENCE_TRACE messages" - depends on DMA_SHARED_BUFFER - help - Enable the DMA_FENCE_TRACE printks. This will add extra - spam to the console log, but will make it easier to diagnose - lockup related problems for dma-buffers shared across multiple - devices. - config GENERIC_ARCH_TOPOLOGY bool help -- cgit From e6054f410c9757e359d697ebade2213c398247e7 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Jul 2026 14:31:36 +0200 Subject: driver core: platform: amend the API contract for fwnode setters Calling platform_device_set_fwnode() (and by extension: platform_device_set_of_node()) of platform_device_set_of_node_from_dev() for a dynamically allocated platform device whose primary firmware node is already assigned and is a software node leads to a resource leak as we never perform the corresponding call to software_node_notify_remove(). As there are currently no users for this special case - just disallow it and warn loudly if it ever happens. Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260716-swnode-remove-on-dev-unreg-v8-1-5c2b8cc38c28@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/base/platform.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index a71015f1d915..fb57d487a6c2 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -707,7 +707,9 @@ EXPORT_SYMBOL_GPL(platform_device_add_data); * * Assign an OF node to this platform device. Internally keep track of the * reference count. Devices created with platform_device_alloc() must use this - * function instead of assigning the node manually. + * function instead of assigning the node manually. This function must not be + * called for a platform device that already has a software node as its primary + * firmware node assigned. */ void platform_device_set_of_node(struct platform_device *pdev, struct device_node *np) @@ -723,11 +725,20 @@ EXPORT_SYMBOL_GPL(platform_device_set_of_node); * * Assign a firmware node to this platform device. Internally keep track of the * reference count. Devices created with platform_device_alloc() must use this - * function instead of assigning the node manually. + * function instead of assigning the node manually. This function must not be + * called for a platform device that already has a software node as its primary + * firmware node assigned. */ void platform_device_set_fwnode(struct platform_device *pdev, struct fwnode_handle *fwnode) { + /* + * If we call this function for a platform device whose primary + * firmware node is a software node, we'll never end up calling the + * symmetric software_node_notify_remove(). There are no users for this + * right now in the tree so just disallow it. + */ + WARN_ON(is_software_node(dev_fwnode(&pdev->dev))); fwnode_handle_put(pdev->dev.fwnode); device_set_node(&pdev->dev, fwnode_handle_get(fwnode)); } @@ -739,11 +750,15 @@ EXPORT_SYMBOL_GPL(platform_device_set_fwnode); * @dev2: device whose OF node to reuse * * Reuses the OF node of another device in this platform device while - * internally keeping track of reference counting. + * internally keeping track of reference counting. This function must not be + * called for a platform device that already has a software node as its primary + * firmware node assigned. */ void platform_device_set_of_node_from_dev(struct platform_device *pdev, const struct device *dev2) { + /* See platform_device_set_fwnode(). */ + WARN_ON(is_software_node(dev_fwnode(&pdev->dev))); device_set_of_node_from_dev(&pdev->dev, dev2); pdev->dev.fwnode = of_fwnode_handle(pdev->dev.of_node); } -- cgit From 1e0bd438b87659396bab4e795a3948a2ac58047c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Jul 2026 14:31:37 +0200 Subject: platform/surface: gpe: use platform_device_register_full() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating a software node for a given set of properties and adding it to a platform device can be achieved with a single call to platform_device_register_full(). There's nothing in this driver that suggests using the more fine-grained interfaces was intentional so switch to using the high-level helper. Acked-by: Ilpo Järvinen Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260716-swnode-remove-on-dev-unreg-v8-2-5c2b8cc38c28@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/platform/surface/surface_gpe.c | 39 ++++++++++------------------------ 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index 40896a8544b0..4ca7ad76f3b9 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -290,9 +290,9 @@ static struct platform_device *surface_gpe_device; static int __init surface_gpe_init(void) { + struct platform_device_info pdevinfo; const struct dmi_system_id *match; struct platform_device *pdev; - struct fwnode_handle *fwnode; int status; match = dmi_first_match(dmi_lid_device_table); @@ -305,44 +305,27 @@ static int __init surface_gpe_init(void) if (status) return status; - fwnode = fwnode_create_software_node(match->driver_data, NULL); - if (IS_ERR(fwnode)) { - status = PTR_ERR(fwnode); - goto err_node; - } - - pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); - if (!pdev) { - status = -ENOMEM; - goto err_alloc; + pdevinfo = (struct platform_device_info){ + .name = "surface_gpe", + .id = PLATFORM_DEVID_NONE, + .properties = match->driver_data, + }; + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + platform_driver_unregister(&surface_gpe_driver); + return PTR_ERR(pdev); } - platform_device_set_fwnode(pdev, fwnode); - - status = platform_device_add(pdev); - if (status) - goto err_add; - surface_gpe_device = pdev; return 0; - -err_add: - platform_device_put(pdev); -err_alloc: - fwnode_remove_software_node(fwnode); -err_node: - platform_driver_unregister(&surface_gpe_driver); - return status; } module_init(surface_gpe_init); static void __exit surface_gpe_exit(void) { - struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode; - platform_device_unregister(surface_gpe_device); platform_driver_unregister(&surface_gpe_driver); - fwnode_remove_software_node(fwnode); } module_exit(surface_gpe_exit); -- cgit From 0f485f88656afc9b690e4d33d5e815c94f8ee964 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Jul 2026 14:31:38 +0200 Subject: drm/xe/i2c: use device_create_managed_software_node() This driver intentionally uses the fine-grained approach to creating platform devices. It assigns a software node as the primary firmware node of the device it creates. Ahead of improving the reference counting of platform device software nodes, switch to using device_create_managed_software_node(). This way, we create a dynamic software node whose life-time is tied to the device to which it's assigned. Reviewed-by: Heikki Krogerus Acked-by: Rodrigo Vivi Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260716-swnode-remove-on-dev-unreg-v8-3-5c2b8cc38c28@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/gpu/drm/xe/xe_i2c.c | 22 ++++++++-------------- drivers/gpu/drm/xe/xe_i2c.h | 1 - 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/xe/xe_i2c.c b/drivers/gpu/drm/xe/xe_i2c.c index af4ebd93ad8e..5b0026bdb1c6 100644 --- a/drivers/gpu/drm/xe/xe_i2c.c +++ b/drivers/gpu/drm/xe/xe_i2c.c @@ -93,13 +93,8 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) { struct pci_dev *pci = to_pci_dev(i2c->drm_dev); struct platform_device *pdev; - struct fwnode_handle *fwnode; int ret; - fwnode = fwnode_create_software_node(xe_i2c_adapter_properties, NULL); - if (IS_ERR(fwnode)) - return PTR_ERR(fwnode); - /* * Not using platform_device_register_full() here because we don't have * a handle to the platform_device before it returns. xe_i2c_notifier() @@ -107,10 +102,14 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) * platform_device_register_full() is done. */ pdev = platform_device_alloc(adapter_name, pci_dev_id(pci)); - if (!pdev) { - ret = -ENOMEM; - goto err_fwnode_remove; - } + if (!pdev) + return -ENOMEM; + + ret = device_create_managed_software_node(&pdev->dev, + xe_i2c_adapter_properties, + NULL); + if (ret) + goto err_pdev_put; if (i2c->adapter_irq) { struct resource res; @@ -123,8 +122,6 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) } pdev->dev.parent = i2c->drm_dev; - platform_device_set_fwnode(pdev, fwnode); - i2c->adapter_node = fwnode; i2c->pdev = pdev; ret = platform_device_add(pdev); @@ -135,8 +132,6 @@ static int xe_i2c_register_adapter(struct xe_i2c *i2c) err_pdev_put: platform_device_put(pdev); -err_fwnode_remove: - fwnode_remove_software_node(fwnode); return ret; } @@ -144,7 +139,6 @@ err_fwnode_remove: static void xe_i2c_unregister_adapter(struct xe_i2c *i2c) { platform_device_unregister(i2c->pdev); - fwnode_remove_software_node(i2c->adapter_node); } /** diff --git a/drivers/gpu/drm/xe/xe_i2c.h b/drivers/gpu/drm/xe/xe_i2c.h index 425d8160835f..b28229f056c5 100644 --- a/drivers/gpu/drm/xe/xe_i2c.h +++ b/drivers/gpu/drm/xe/xe_i2c.h @@ -30,7 +30,6 @@ struct xe_i2c_endpoint { }; struct xe_i2c { - struct fwnode_handle *adapter_node; struct platform_device *pdev; struct i2c_adapter *adapter; struct i2c_client *client[XE_I2C_MAX_CLIENTS]; -- cgit From 7d80509ff0daab86cc83cf939cae6f68f5f09871 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Jul 2026 14:31:39 +0200 Subject: driver core: platform: unify release path With no drivers that manually assign software nodes to platform devices created with platform_device_alloc(), we can now unify the release path and remove platform_device_release_full(). Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260716-swnode-remove-on-dev-unreg-v8-4-5c2b8cc38c28@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/base/platform.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/base/platform.c b/drivers/base/platform.c index fb57d487a6c2..66f9ec73d47e 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -599,19 +599,19 @@ static void platform_device_release(struct device *dev) struct platform_object *pa = container_of(dev, struct platform_object, pdev.dev); - fwnode_handle_put(pa->pdev.dev.fwnode); + device_remove_software_node(dev); + /* + * If the primary firmware node is a software node, its reference count + * was already decreased by the call to device_remove_software_node(). + */ + if (!is_software_node(dev_fwnode(dev))) + fwnode_handle_put(pa->pdev.dev.fwnode); kfree(pa->pdev.dev.platform_data); kfree(pa->pdev.mfd_cell); kfree(pa->pdev.resource); kfree(pa); } -static void platform_device_release_full(struct device *dev) -{ - device_remove_software_node(dev); - platform_device_release(dev); -} - /** * platform_device_alloc - create a platform device * @name: base name of the device we're adding @@ -960,8 +960,6 @@ struct platform_device *platform_device_register_full(const struct platform_devi ret = device_add_software_node(&pdev->dev, pdevinfo->swnode); if (ret) goto err; - - pdev->dev.release = platform_device_release_full; } else if (pdevinfo->properties) { ret = device_create_managed_software_node(&pdev->dev, pdevinfo->properties, NULL); -- cgit From 397f0a3958c97228c3c3f53a603eaaa0e7fb3fda Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 16 Jul 2026 14:31:40 +0200 Subject: driver core: platform: tests: add test cases for correct swnode removal Extend the kunit module for platform devices with test cases verifying that the same software node can be added to platform devices repeatedly. Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260716-swnode-remove-on-dev-unreg-v8-5-5c2b8cc38c28@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/base/test/platform-device-test.c | 180 +++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/drivers/base/test/platform-device-test.c b/drivers/base/test/platform-device-test.c index 6355a2231b74..9ce563f76aad 100644 --- a/drivers/base/test/platform-device-test.c +++ b/drivers/base/test/platform-device-test.c @@ -1,12 +1,15 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include #include #include +#include #include #include +#include #define DEVICE_NAME "test" @@ -253,9 +256,186 @@ static struct kunit_suite platform_device_match_test_suite = { .test_cases = platform_device_match_tests, }; +static int platform_device_swnode_test_probe(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver platform_swnode_test_driver = { + .probe = platform_device_swnode_test_probe, + .driver = { + .name = DEVICE_NAME, + }, +}; + +static const struct software_node platform_device_test_swnode = { }; + +/* + * Check that reusing a software node works correctly. If the call to + * platform_device_register_full() fails after adding the secondary firmware + * node, the software node must be unregistered in the device's release() + * callback or the subsequent call to platform_device_register_full() will fail + * with -EBUSY due to the software node already having been registered. + */ +static void platform_device_swnode_add_twice(struct kunit *test) +{ + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + bool bound = false; + int ret; + + fwnode = kunit_kzalloc(test, sizeof(*fwnode), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + ret = kunit_platform_driver_register(test, &platform_swnode_test_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode_init(fwnode, NULL); + pdevinfo = (struct platform_device_info){ + .name = DEVICE_NAME, + .id = PLATFORM_DEVID_NONE, + .fwnode = fwnode, + .swnode = &platform_device_test_swnode, + }; + + pdev = platform_device_register_full(&pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + platform_device_unregister(pdev); + + pdev = platform_device_register_full(&pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + platform_device_unregister(pdev); +} + +/* + * Check that passing a software node as the primary firmware node of the + * platform device does not result in it being unregistered by the call to + * device_remove_software_node() in its release path. + */ +static void platform_device_swnode_as_primary(struct kunit *test) +{ + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &platform_swnode_test_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode = kunit_software_node_register(test, &platform_device_test_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = DEVICE_NAME, + .id = PLATFORM_DEVID_NONE, + .fwnode = fwnode, + }; + + pdev = platform_device_register_full(&pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev); + + wait_for_device_probe(); + scoped_guard(device, &pdev->dev) + bound = device_is_bound(&pdev->dev); + + KUNIT_ASSERT_TRUE(test, bound); + + platform_device_unregister(pdev); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, software_node_fwnode(&platform_device_test_swnode)); +} + +/* + * Check that passing two software nodes to platform_device_register_full() + * fails. + */ +static void platform_device_two_swnodes(struct kunit *test) +{ + static const struct property_entry properties[] = { + PROPERTY_ENTRY_U32("foo", 42), + { } + }; + + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + int ret; + + ret = kunit_platform_driver_register(test, &platform_swnode_test_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode = kunit_software_node_register(test, &platform_device_test_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = DEVICE_NAME, + .id = PLATFORM_DEVID_NONE, + .fwnode = fwnode, + .swnode = &platform_device_test_swnode, + }; + + pdev = platform_device_register_full(&pdevinfo); + KUNIT_ASSERT_TRUE(test, IS_ERR(pdev)); + KUNIT_ASSERT_EQ_MSG(test, PTR_ERR(pdev), -EINVAL, + "Expected errno == -EINVAL, got: %pe", pdev); + + pdevinfo = (struct platform_device_info){ + .name = DEVICE_NAME, + .id = PLATFORM_DEVID_NONE, + .swnode = &platform_device_test_swnode, + .properties = properties, + }; + + pdev = platform_device_register_full(&pdevinfo); + KUNIT_ASSERT_TRUE(test, IS_ERR(pdev)); + KUNIT_ASSERT_EQ_MSG(test, PTR_ERR(pdev), -EINVAL, + "Expected errno == -EINVAL, got: %pe", pdev); + + pdevinfo = (struct platform_device_info){ + .name = DEVICE_NAME, + .id = PLATFORM_DEVID_NONE, + .fwnode = fwnode, + .properties = properties, + }; + + pdev = platform_device_register_full(&pdevinfo); + KUNIT_ASSERT_TRUE(test, IS_ERR(pdev)); + KUNIT_ASSERT_EQ_MSG(test, PTR_ERR(pdev), -EINVAL, + "Expected errno == -EINVAL, got: %pe", pdev); +} + +static struct kunit_case platform_device_swnode_tests[] = { + KUNIT_CASE(platform_device_swnode_add_twice), + KUNIT_CASE(platform_device_swnode_as_primary), + KUNIT_CASE(platform_device_two_swnodes), + {} +}; + +static struct kunit_suite platform_device_swnode_test_suite = { + .name = "platform-device-swnode", + .test_cases = platform_device_swnode_tests, +}; + kunit_test_suites( &platform_device_devm_test_suite, &platform_device_match_test_suite, + &platform_device_swnode_test_suite, ); MODULE_DESCRIPTION("Test module for platform devices"); -- cgit From f7acb19abcd867e5d2e1feb6197dcc65aa3b8e45 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 13 Jul 2026 21:14:54 +0100 Subject: rust: device: make lifetime on `Core` and `CoreInternal` invariant Currently the lifetime on `Core` and `CoreInternal` is covariant. This means that they can be coerced into shorter living lifetimes. On `probe` function, signature has `&'bound Device>`; the type's wellformness would imply `'a: 'bound` and thus the type can be coerced `&'bound Device>`, defeating the purpose of having the lifetime bound to prevent users of the `Core` type to escape the function. Fix this by making the lifetime invariant, so the coercion is impossible. The lifetime here only needs to be "branded" so it does not coerce or unify with other lifetimes, so we do not need to ensure `'bound: 'a`. This requires modifying `nova-core` which relies on this implied bound due to pre-2024 capture rule. The "use" bound can be removed if built with edition 2024. Fixes: 24799831d631 ("rust: device: make Core and CoreInternal lifetime-parameterized") Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260713201455.640151-1-gary@kernel.org [ Fixup the debugfs sample to use an explicit lifetime instead of Core<'_>. - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/gpu/nova-core/gpu.rs | 6 +++--- rust/kernel/device.rs | 10 ++++++++-- samples/rust/rust_debugfs.rs | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs index b3c91731db45..b603b0bd2692 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -285,10 +285,10 @@ pub(crate) struct Gpu<'gpu> { } impl<'gpu> Gpu<'gpu> { - pub(crate) fn new( - pdev: &'gpu pci::Device>, + pub(crate) fn new<'a>( + pdev: &'gpu pci::Device>, bar: Bar0<'gpu>, - ) -> impl PinInit + 'gpu { + ) -> impl PinInit + use<'gpu, 'a> { try_pin_init!(Self { device: pdev.as_ref(), spec: Spec::new(pdev.as_ref(), bar).inspect(|spec| { diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs index b538c39982a6..82e46b37e814 100644 --- a/rust/kernel/device.rs +++ b/rust/kernel/device.rs @@ -504,7 +504,11 @@ pub struct Normal; /// callback it appears in. It is intended to be used for synchronization purposes. Bus device /// implementations can implement methods for [`Device`], such that they can only be called /// from bus callbacks. -pub struct Core<'a>(PhantomData<&'a ()>); +/// +/// The lifetime `'a` is for "lifetime branding" purpose. Callbacks need to polymorphic over this +/// lifetime so the `&'bound Device>` provided to them cannot outlive the scope of the +/// function. For this reason, it needs to be invariant. +pub struct Core<'a>(PhantomData &'a ()>); /// Semantically the same as [`Core`], but reserved for internal usage of the corresponding bus /// abstraction. @@ -515,7 +519,9 @@ pub struct Core<'a>(PhantomData<&'a ()>); /// /// This context mainly exists to share generic [`Device`] infrastructure that should only be called /// from bus callbacks with bus abstractions, but without making them accessible for drivers. -pub struct CoreInternal<'a>(PhantomData<&'a ()>); +/// +/// Lifetime `'a` is invariant for the same reason as [`Core`]. +pub struct CoreInternal<'a>(PhantomData &'a ()>); /// Semantically the same as [`Bound`], but reserved for internal usage of the corresponding bus /// abstraction. diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index 1f59e08aaa4b..0b27ad96ecbf 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -147,7 +147,9 @@ impl RustDebugFs { dir.read_write_file(c"pair", new_mutex!(Inner { x: 3, y: 10 })) } - fn new<'a>(pdev: &'a platform::Device>) -> impl PinInit + 'a { + fn new<'a, 'b>( + pdev: &'a platform::Device>, + ) -> impl PinInit + use<'a, 'b> { let debugfs = Dir::new(c"sample_debugfs"); let dev = pdev.as_ref(); -- cgit From c2d8b262057c0707ef5a1cd8e80842b7fde281b8 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 16 Jul 2026 15:25:43 +0100 Subject: rust: io: document const-offset requirement for infallible accessors Due to the usage of `build_assert!` for address validity checking, these accessors want constant offsets. Non-constant offsets can work but it depend on compiler optimization levels, so it should be avoided. Signed-off-by: Gary Guo Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260716142545.3622278-1-gary@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/io.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 95f46bb75f9e..a38c20ba3d23 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -721,6 +721,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 8-bit read with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn read8(self, offset: usize) -> u8 where @@ -731,6 +733,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 16-bit read with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn read16(self, offset: usize) -> u16 where @@ -741,6 +745,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 32-bit read with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn read32(self, offset: usize) -> u32 where @@ -751,6 +757,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 64-bit read with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn read64(self, offset: usize) -> u64 where @@ -761,6 +769,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 8-bit write with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn write8(self, value: u8, offset: usize) where @@ -771,6 +781,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 16-bit write with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn write16(self, value: u16, offset: usize) where @@ -781,6 +793,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 32-bit write with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn write32(self, value: u32, offset: usize) where @@ -791,6 +805,8 @@ pub trait Io<'a>: IoBase<'a> { } /// Infallible 64-bit write with compile-time bounds check. + /// + /// `offset` should be constant. #[inline(always)] fn write64(self, value: u64, offset: usize) where -- cgit From f5256ad60651fc010e2cf6dc7fe195415f496fc9 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 16 Jul 2026 15:41:43 +0100 Subject: samples: rust: debugfs: fix excessive stack use The current implementation creates a 4K array and move it into the box. Klint reports that this causes excesssive stack usage: warning: stack size of `create_file_write` is 4472 bytes, exceeds the 2048-byte limit --> samples/rust/rust_debugfs_scoped.rs:54:1 | 54 | / fn create_file_write( 55 | | mod_data: &ModuleData, 56 | | reader: &mut kernel::uaccess::UserSliceReader, 57 | | ) -> Result { | |___________^ | = note: the stack size is inferred from instruction `sub $0x1178,%rsp` at .text+2205 Use pin-init to create the array in-place instead. Fixes: f656279afde1 ("samples: rust: debugfs_scoped: add example for blobs") Signed-off-by: Gary Guo Reviewed-by: Alexandre Courbot Link: https://patch.msgid.link/20260716144144.3665719-1-gary@kernel.org [ Make the patch rustfmtcheck complient. - Danilo ] Signed-off-by: Danilo Krummrich --- samples/rust/rust_debugfs_scoped.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/rust/rust_debugfs_scoped.rs b/samples/rust/rust_debugfs_scoped.rs index 6a575a15a2c2..ca2b154be384 100644 --- a/samples/rust/rust_debugfs_scoped.rs +++ b/samples/rust/rust_debugfs_scoped.rs @@ -75,7 +75,10 @@ fn create_file_write( GFP_KERNEL, )?; } - let blob = KBox::pin_init(new_mutex!([0x42; SZ_4K]), GFP_KERNEL)?; + let blob = KBox::pin_init( + new_mutex!(pin_init::init_array_from_fn(|_| 0x42)), + GFP_KERNEL, + )?; let scope = KBox::pin_init( mod_data.device_dir.scope( -- cgit From 01899cb7b92401b40501a25df98c0e15178397e3 Mon Sep 17 00:00:00 2001 From: Younes Akhouayri Date: Fri, 17 Jul 2026 15:26:46 +0200 Subject: rust: irq: fix C header path in module docs The IRQ module documentation displays `include/linux/device.h`, while the link points to `include/linux/interrupt.h`. The module wraps the IRQ registration interfaces declared in `include/linux/interrupt.h`, so fix the displayed header path. Fixes: 1f54d5e5cd2a ("rust: irq: add irq module") Link: https://github.com/Rust-for-Linux/linux/issues/1246 Signed-off-by: Younes Akhouayri Link: https://patch.msgid.link/20260717-docs-irq-rustdoc-header-v1-1-36749192aa04@younes.io Signed-off-by: Danilo Krummrich --- rust/kernel/irq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/irq.rs b/rust/kernel/irq.rs index 20abd4056655..09ef1e7f853c 100644 --- a/rust/kernel/irq.rs +++ b/rust/kernel/irq.rs @@ -8,7 +8,7 @@ //! The current abstractions handle IRQ requests and handlers, i.e.: it allows //! drivers to register a handler for a given IRQ line. //! -//! C header: [`include/linux/device.h`](srctree/include/linux/interrupt.h) +//! C header: [`include/linux/interrupt.h`](srctree/include/linux/interrupt.h) /// Flags to be used when registering IRQ handlers. mod flags; -- cgit From 08bc14983723e8afb808fe832b9a1a80e6b7bac1 Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 21 Jul 2026 18:54:47 +0800 Subject: of: kobj: export of_node_ktype for use by modules of_node_init() is a static inline that references of_node_ktype when CONFIG_OF_KOBJ=y. Any module that calls of_node_init() will therefore have an unresolved reference to of_node_ktype at load time, because the symbol is defined in drivers/of/kobj.c but was never exported. This causes a modpost build error when CONFIG_OF_KOBJ=y and CONFIG_DRIVER_PE_KUNIT_TEST=m: ERROR: modpost: "of_node_ktype" [drivers/base/test/property-entry-test.ko] undefined! Add EXPORT_SYMBOL_IF_KUNIT(of_node_ktype) so that modules such as the KUnit property-entry test can call of_node_init() without hitting this linker error. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607181651.RnUuV8n6-lkp@intel.com/ Signed-off-by: Xu Yang Acked-by: Rob Herring (Arm) Acked-by: Andy Shevchenko Link: https://lore.kernel.org/oe-kbuild-all/202607181651.RnUuV8n6-lkp@intel.com/ Link: https://patch.msgid.link/20260721105448.2109894-2-xu.yang_2@oss.nxp.com [ Add empty line before the kunit include. - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/of/kobj.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/of/kobj.c b/drivers/of/kobj.c index 1bb61a2c3399..9f17305f112b 100644 --- a/drivers/of/kobj.c +++ b/drivers/of/kobj.c @@ -2,6 +2,8 @@ #include #include +#include + #include "of_private.h" /* true when node is initialized */ @@ -27,6 +29,7 @@ static void of_node_release(struct kobject *kobj) const struct kobj_type of_node_ktype = { .release = of_node_release, }; +EXPORT_SYMBOL_IF_KUNIT(of_node_ktype); static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buf, -- cgit From 0e6f8ccd4618afdb504d5c56fe2fea1a2cd5d86a Mon Sep 17 00:00:00 2001 From: Xu Yang Date: Tue, 21 Jul 2026 18:54:48 +0800 Subject: device property: add test cases for fwnode_for_each_child_node() Add test cases for fwnode_for_each_child_node() API. Test command: $ ./tools/testing/kunit/kunit.py run property-entry Signed-off-by: Xu Yang Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260721105448.2109894-3-xu.yang_2@oss.nxp.com Signed-off-by: Danilo Krummrich --- drivers/base/test/Kconfig | 1 + drivers/base/test/property-entry-test.c | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig index 1ecf0791241a..542ce07530a1 100644 --- a/drivers/base/test/Kconfig +++ b/drivers/base/test/Kconfig @@ -17,6 +17,7 @@ config DM_KUNIT_TEST config DRIVER_PE_KUNIT_TEST tristate "KUnit Tests for property entry API" if !KUNIT_ALL_TESTS depends on KUNIT + select OF default KUNIT_ALL_TESTS config DRIVER_SWNODE_KUNIT_TEST diff --git a/drivers/base/test/property-entry-test.c b/drivers/base/test/property-entry-test.c index a8657eb06f94..855e73b9b21f 100644 --- a/drivers/base/test/property-entry-test.c +++ b/drivers/base/test/property-entry-test.c @@ -4,6 +4,8 @@ // Copyright 2019 Google LLC. #include + +#include #include #include @@ -489,6 +491,139 @@ static void pe_test_reference(struct kunit *test) software_node_unregister_node_group(group); } +static struct fwnode_handle *create_device_node(struct kunit *test, + const char *name, + const char *full_name, + struct device_node *parent) +{ + struct device_node *node; + + node = kunit_kzalloc(test, sizeof(*node), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node); + + node->name = kunit_kstrdup(test, name, GFP_KERNEL); + node->full_name = kunit_kstrdup(test, full_name, GFP_KERNEL); + + if (parent) { + node->sibling = parent->child; + /* set the node as the first child of the parent */ + parent->child = node; + node->parent = parent; + } + + of_node_init(node); + return of_fwnode_handle(node); +} + +/* Verifies that fwnode_for_each_child_node() can output correct children */ +static void pe_test_child_iteration(struct kunit *test) +{ + struct fwnode_handle *of_node, *of_node1; + struct fwnode_handle *sw_node, *sw_node1; + struct fwnode_handle *child; + int error, i, num; + + static const struct software_node node = { .name = "sw" }; + static const struct software_node node1 = { .name = "sw-1", .parent = &node}; + static const struct software_node node2 = { .name = "sw-2", .parent = &node}; + static const struct software_node node3 = { .name = "sw-3", .parent = &node}; + static const struct software_node *group[] = { &node, &node1, &node2, &node3, NULL }; + + static const char * const of_child_array[] = { "of-1", "of-2", "of-3" }; + static const char * const sw_child_array[] = { "sw-1", "sw-2", "sw-3" }; + static const char * const of_sw_child_array[] = { "of-1", "of-2", "of-3", + "sw-1", "sw-2", "sw-3" }; + static const char * const sw_of_child_array[] = { "sw-1", "sw-2", "sw-3", + "of-1", "of-2", "of-3" }; + + /* 1. Test OF node child iteration */ + + of_node = create_device_node(test, "of", "of", NULL); + create_device_node(test, "of", "of-3", to_of_node(of_node)); + create_device_node(test, "of", "of-2", to_of_node(of_node)); + of_node1 = create_device_node(test, "of", "of-1", to_of_node(of_node)); + + i = 0; + num = ARRAY_SIZE(of_child_array); + fwnode_for_each_child_node(of_node, child) { + KUNIT_ASSERT_LT(test, i, num); + KUNIT_EXPECT_STREQ(test, of_child_array[i++], fwnode_get_name(child)); + } + KUNIT_EXPECT_PTR_EQ(test, child, NULL); + + /* 2. Test SW node child iteration */ + + error = software_node_register_node_group(group); + KUNIT_ASSERT_EQ(test, error, 0); + + sw_node = software_node_fwnode(&node); + + i = 0; + num = ARRAY_SIZE(sw_child_array); + fwnode_for_each_child_node(sw_node, child) { + KUNIT_ASSERT_LT(test, i, num); + KUNIT_EXPECT_STREQ(test, sw_child_array[i++], fwnode_get_name(child)); + } + KUNIT_EXPECT_PTR_EQ(test, child, NULL); + + /* 3. Test OF (primary) + SW (secondary) node child iteration */ + + of_node->secondary = sw_node; + sw_node->secondary = ERR_PTR(-ENODEV); + + i = 0; + num = ARRAY_SIZE(of_sw_child_array); + fwnode_for_each_child_node(of_node, child) { + KUNIT_ASSERT_LT(test, i, num); + KUNIT_EXPECT_STREQ(test, of_sw_child_array[i++], fwnode_get_name(child)); + } + KUNIT_EXPECT_PTR_EQ(test, child, NULL); + + /* 4. Test SW (primary) + OF (secondary) node child iteration */ + + sw_node->secondary = of_node; + of_node->secondary = ERR_PTR(-ENODEV); + + i = 0; + num = ARRAY_SIZE(sw_of_child_array); + fwnode_for_each_child_node(sw_node, child) { + KUNIT_ASSERT_LT(test, i, num); + KUNIT_EXPECT_STREQ(test, sw_of_child_array[i++], fwnode_get_name(child)); + } + KUNIT_EXPECT_PTR_EQ(test, child, NULL); + + /* 5. Test OF (primary) + SW (secondary, but no children) node child iteration */ + + sw_node1 = software_node_fwnode(&node1); + of_node->secondary = sw_node1; + sw_node->secondary = ERR_PTR(-ENODEV); + + i = 0; + num = ARRAY_SIZE(of_child_array); + fwnode_for_each_child_node(of_node, child) { + KUNIT_ASSERT_LT(test, i, num); + KUNIT_EXPECT_STREQ(test, of_child_array[i++], fwnode_get_name(child)); + } + KUNIT_EXPECT_PTR_EQ(test, child, NULL); + + /* 6. Test SW (primary) + OF (secondary, but no children) node child iteration */ + + sw_node->secondary = of_node1; + of_node->secondary = ERR_PTR(-ENODEV); + + i = 0; + num = ARRAY_SIZE(sw_child_array); + fwnode_for_each_child_node(sw_node, child) { + KUNIT_ASSERT_LT(test, i, num); + KUNIT_EXPECT_STREQ(test, sw_child_array[i++], fwnode_get_name(child)); + } + KUNIT_EXPECT_PTR_EQ(test, child, NULL); + + of_node->secondary = NULL; + sw_node->secondary = NULL; + software_node_unregister_node_group(group); +} + static struct kunit_case property_entry_test_cases[] = { KUNIT_CASE(pe_test_uints), KUNIT_CASE(pe_test_uint_arrays), @@ -497,6 +632,7 @@ static struct kunit_case property_entry_test_cases[] = { KUNIT_CASE(pe_test_move_inline_u8), KUNIT_CASE(pe_test_move_inline_str), KUNIT_CASE(pe_test_reference), + KUNIT_CASE(pe_test_child_iteration), { } }; @@ -509,4 +645,5 @@ kunit_test_suite(property_entry_test_suite); MODULE_DESCRIPTION("Test module for the property entry API"); MODULE_AUTHOR("Dmitry Torokhov "); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); MODULE_LICENSE("GPL"); -- cgit From 99f59aa82341c8491a1b94c5dd9c666fe979a479 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Sat, 18 Jul 2026 15:33:11 +0000 Subject: rust: add basic serial device bus abstractions Implement the basic serial device bus abstractions required to write a serial device bus device driver with or without the need for initial device data. This includes the following data structures: The `serdev::Driver` trait represents the interface to the driver. The `serdev::Device` abstraction represents a `struct serdev_device`. In order to provide the Serdev specific parts to a generic `driver::Registration` the `driver::RegistrationOps` trait is implemented by `serdev::Adapter`. Acked-by: Greg Kroah-Hartman Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260718-rust_serdev-v16-1-5809384d2e1b@posteo.de Signed-off-by: Danilo Krummrich --- MAINTAINERS | 2 + drivers/tty/serdev/Kconfig | 7 + rust/bindings/bindings_helper.h | 1 + rust/helpers/helpers.c | 1 + rust/helpers/serdev.c | 22 ++ rust/kernel/lib.rs | 2 + rust/kernel/serdev.rs | 605 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 640 insertions(+) create mode 100644 rust/helpers/serdev.c create mode 100644 rust/kernel/serdev.rs diff --git a/MAINTAINERS b/MAINTAINERS index e30aa3b63eff..836b2baae3be 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24567,6 +24567,8 @@ S: Maintained F: Documentation/devicetree/bindings/serial/serial.yaml F: drivers/tty/serdev/ F: include/linux/serdev.h +F: rust/helpers/serdev.c +F: rust/kernel/serdev.rs SERIAL IR RECEIVER M: Sean Young diff --git a/drivers/tty/serdev/Kconfig b/drivers/tty/serdev/Kconfig index 46ae732bfc68..e6dfe949ad01 100644 --- a/drivers/tty/serdev/Kconfig +++ b/drivers/tty/serdev/Kconfig @@ -9,6 +9,13 @@ menuconfig SERIAL_DEV_BUS Note that you typically also want to enable TTY port controller support. +config RUST_SERIAL_DEV_BUS_ABSTRACTIONS + bool "Rust Serial device bus abstractions" + depends on RUST + select SERIAL_DEV_BUS + help + This enables the Rust abstraction for the serial device bus API. + if SERIAL_DEV_BUS config SERIAL_DEV_CTRL_TTYPORT diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 1124785e210b..fe7c505da236 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -85,6 +85,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 998e31052e66..6921a5ecb78a 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -87,6 +87,7 @@ #include "regulator.c" #include "scatterlist.c" #include "security.c" +#include "serdev.c" #include "signal.c" #include "slab.c" #include "spinlock.c" diff --git a/rust/helpers/serdev.c b/rust/helpers/serdev.c new file mode 100644 index 000000000000..c52b78ca3fc7 --- /dev/null +++ b/rust/helpers/serdev.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +__rust_helper +void rust_helper_serdev_device_driver_unregister(struct serdev_device_driver *sdrv) +{ + serdev_device_driver_unregister(sdrv); +} + +__rust_helper +void rust_helper_serdev_device_put(struct serdev_device *serdev) +{ + serdev_device_put(serdev); +} + +__rust_helper +void rust_helper_serdev_device_set_client_ops(struct serdev_device *serdev, + const struct serdev_device_ops *ops) +{ + serdev_device_set_client_ops(serdev, ops); +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 68f4d9a3425d..3edc343caba5 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -122,6 +122,8 @@ pub mod safety; pub mod scatterlist; pub mod security; pub mod seq_file; +#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)] +pub mod serdev; pub mod sizes; #[cfg(CONFIG_SOC_BUS)] pub mod soc; diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs new file mode 100644 index 000000000000..cf537d9eeeb4 --- /dev/null +++ b/rust/kernel/serdev.rs @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Abstractions for the serial device bus. +//! +//! C header: [`include/linux/serdev.h`](srctree/include/linux/serdev.h) + +use crate::{ + acpi, + device, + driver, + error::{ + from_result, + to_result, + VTABLE_DEFAULT_ERROR, // + }, + new_mutex, + of, + prelude::*, + sync::{ + aref::AlwaysRefCounted, + Mutex, // + }, + time::Jiffies, + types::{ + Opaque, + ScopeGuard, // + }, // +}; + +use core::{ + cell::UnsafeCell, + marker::PhantomData, + mem::{offset_of, MaybeUninit}, + ptr::NonNull, // +}; + +/// Parity bit to use with a serial device. +#[repr(u32)] +pub enum Parity { + /// No parity bit. + None = bindings::serdev_parity_SERDEV_PARITY_NONE, + /// Even partiy. + Even = bindings::serdev_parity_SERDEV_PARITY_EVEN, + /// Odd parity. + Odd = bindings::serdev_parity_SERDEV_PARITY_ODD, +} + +/// An adapter for the registration of serial device bus device drivers. +pub struct Adapter(T); + +// SAFETY: +// - `bindings::serdev_device_driver` is a C type declared as `repr(C)`. +// - `PrivateData<'bound, T>` is the type of the driver's device private data. +// - `struct serdev_device_driver` embeds a `struct device_driver`. +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`. +unsafe impl driver::DriverLayout for Adapter { + type DriverType = bindings::serdev_device_driver; + type DriverData<'bound> = PrivateData<'bound, T>; + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver); +} + +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if +// a preceding call to `register` has been successful. +unsafe impl driver::RegistrationOps for Adapter { + unsafe fn register( + sdrv: &Opaque, + name: &'static CStr, + module: &'static ThisModule, + ) -> Result { + let of_table = match T::OF_ID_TABLE { + Some(table) => table.as_ptr(), + None => core::ptr::null(), + }; + + let acpi_table = match T::ACPI_ID_TABLE { + Some(table) => table.as_ptr(), + None => core::ptr::null(), + }; + + // SAFETY: It's safe to set the fields of `struct serdev_device_driver` on initialization. + unsafe { + (*sdrv.get()).driver.name = name.as_char_ptr(); + (*sdrv.get()).probe = Some(Self::probe_callback); + (*sdrv.get()).remove = Some(Self::remove_callback); + (*sdrv.get()).driver.of_match_table = of_table; + (*sdrv.get()).driver.acpi_match_table = acpi_table; + } + + // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`. + to_result(unsafe { bindings::__serdev_device_driver_register(sdrv.get(), module.0) }) + } + + unsafe fn unregister(sdrv: &Opaque) { + // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`. + unsafe { bindings::serdev_device_driver_unregister(sdrv.get()) }; + } +} + +#[doc(hidden)] +#[pin_data(PinnedDrop)] +pub struct PrivateData<'bound, T: Driver> { + sdev: &'bound Device, + #[pin] + driver: UnsafeCell>>, + open: UnsafeCell, + /// Whether `receive_buf_callback` is allowed to call `Driver::receive`. + /// + /// If locked, the receive_buf_callback will be blocked on data reception. + /// This is the case while the driver is being probed or while [`PrivateData`] is being dropped. + /// This is necessary, because we need to open the serdev device before the driver has been + /// probed in order to allow it to be configured, which allows `receive_buf_callback` to be + /// called. Thus we need to block data until probe completes and the driver data becomes + /// initialized. + /// + /// If unlocked and true, the receive_buf_callback will forward the data to + /// `Driver::receive`. This is the normal state of operation. + /// + /// If unlocked and false, the receive_buf_callback will throw away the data. + /// This is only the case, if the serdev device is open and + /// - the driver returned an error in probe + /// or + /// - the driver data already has been dropped, because it was unbound. + #[pin] + active: Mutex, +} + +#[pinned_drop] +impl PinnedDrop for PrivateData<'_, T> { + fn drop(self: Pin<&mut Self>) { + let mut active = self.active.lock(); + if *active { + // SAFETY: + // - We have exclusive access to `self.driver`. + // - `self.driver` is guaranteed to be initialized. + unsafe { (*self.driver.get()).assume_init_drop() }; + *active = false; + } + drop(active); + + // SAFETY: We have exclusive access to `self.open`. + if unsafe { *self.open.get() } { + // SAFETY: `self.sdev.as_raw()` is guaranteed to be a pointer to a valid + // `struct serdev_device`. + unsafe { bindings::serdev_device_close(self.sdev.as_raw()) }; + } + } +} + +impl Adapter { + const OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops { + receive_buf: if T::HAS_RECEIVE { + Some(Self::receive_buf_callback) + } else { + None + }, + write_wakeup: Some(bindings::serdev_device_write_wakeup), + }; + + extern "C" fn probe_callback(sdev: *mut bindings::serdev_device) -> kernel::ffi::c_int { + // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer to + // a `struct serdev_device`. + // + // INVARIANT: `sdev` is valid for the duration of `probe_callback()`. + let sdev = unsafe { &*sdev.cast::>>() }; + let info = ::id_info(sdev.as_ref()); + + from_result(|| { + sdev.as_ref().set_drvdata(try_pin_init!(PrivateData:: { + sdev: &**sdev, + driver: MaybeUninit::>::zeroed().into(), + open: false.into(), + active <- new_mutex!(false), + }))?; + // SAFETY: We just set drvdata to `PrivateData<'_, T>`. + let private_data = unsafe { sdev.as_ref().drvdata_borrow::>() }; + let private_data = ScopeGuard::new_with_data(private_data, |_| { + // SAFETY: We just set drvdata to `PrivateData<'_, T>`. + drop(unsafe { sdev.as_ref().drvdata_obtain::>() }); + }); + let mut active = private_data.active.lock(); + + // SAFETY: `sdev.as_raw()` is guaranteed to be a valid pointer to `serdev_device`. + unsafe { bindings::serdev_device_set_client_ops(sdev.as_raw(), Self::OPS) }; + + // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer + // to a `serdev_device`. + to_result(unsafe { bindings::serdev_device_open(sdev.as_raw()) })?; + + // SAFETY: We have exclusive access to `private_data.open`. + unsafe { *private_data.open.get() = true }; + + let data = T::probe(sdev, info); + + // SAFETY: We have exclusive access to `private_data.driver`. + let driver = unsafe { &mut *private_data.driver.get() }; + // SAFETY: + // - `driver.as_mut_ptr()` is a valid pointer to uninitialized data. + // - `private_data.driver` is pinned. + let result = unsafe { data.__pinned_init(driver.as_mut_ptr()) }; + + *active = result.is_ok(); + + drop(active); + + result.map(|()| { + private_data.dismiss(); + 0 + }) + }) + } + + extern "C" fn remove_callback(sdev: *mut bindings::serdev_device) { + // SAFETY: The serial device bus only ever calls the remove callback with a valid pointer + // to a `struct serdev_device`. + // + // INVARIANT: `sdev` is valid for the duration of `remove_callback()`. + let sdev = unsafe { &*sdev.cast::>>() }; + + // SAFETY: `remove_callback` is only ever called after a successful call to + // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called + // and stored a `Pin>>`. + let private_data = unsafe { sdev.as_ref().drvdata_borrow::>() }; + + // SAFETY: No one has exclusive access to `private_data.driver`. + let data = unsafe { &*private_data.driver.get() }; + // SAFETY: + // - `private_data.driver` is pinned. + // - `remove_callback` is only ever called after a successful call to `probe_callback`, + // hence it's guaranteed that `private_data.driver` was initialized. + let data_pinned = unsafe { Pin::new_unchecked(data.assume_init_ref()) }; + + T::unbind(sdev, data_pinned); + } + + extern "C" fn receive_buf_callback( + sdev: *mut bindings::serdev_device, + buf: *const u8, + length: usize, + ) -> usize { + // SAFETY: The serial device bus only ever calls the receive buf callback with a valid + // pointer to a `struct serdev_device`. + // + // INVARIANT: `sdev` is valid for the duration of `receive_buf_callback()`. + let sdev = unsafe { &*sdev.cast::>() }; + + // SAFETY: `receive_buf_callback` is only ever called after a successful call to + // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called + // and stored a `Pin>>`. + let private_data = unsafe { sdev.as_ref().drvdata_borrow::>() }; + let active = private_data.active.lock(); + + if !*active { + return length; + } + + // SAFETY: No one has exclusive access to `private_data.driver`. + let data = unsafe { &*private_data.driver.get() }; + // SAFETY: + // - `private_data.driver` is pinned. + // - `receive_buf_callback` is only ever called after a successful call to `probe_callback`, + // hence it's guaranteed that `private_data.driver` was initialized. + let data_pinned = unsafe { Pin::new_unchecked(data.assume_init_ref()) }; + + // SAFETY: `buf` is guaranteed to be non-null and has the size of `length`. + let buf = unsafe { core::slice::from_raw_parts(buf, length) }; + + T::receive(sdev, data_pinned, buf) + } +} + +impl driver::Adapter for Adapter { + type IdInfo = T::IdInfo; + + fn of_id_table() -> Option> { + T::OF_ID_TABLE + } + + fn acpi_id_table() -> Option> { + T::ACPI_ID_TABLE + } +} + +/// Declares a kernel module that exposes a single serial device bus device driver. +/// +/// # Examples +/// +/// ```ignore +/// kernel::module_serdev_device_driver! { +/// type: MyDriver, +/// name: "Module name", +/// authors: ["Author name"], +/// description: "Description", +/// license: "GPL v2", +/// } +/// ``` +#[macro_export] +macro_rules! module_serdev_device_driver { + ($($f:tt)*) => { + $crate::module_driver!(, $crate::serdev::Adapter, { $($f)* }); + }; +} + +/// The serial device bus device driver trait. +/// +/// Drivers must implement this trait in order to get a serial device bus device driver registered. +/// +/// # Examples +/// +///``` +/// # use kernel::{ +/// acpi, +/// bindings, +/// device::{ +/// Bound, +/// Core, // +/// }, +/// of, +/// serdev, // +/// }; +/// +/// struct MyDriver; +/// +/// kernel::of_device_table!( +/// OF_TABLE, +/// MODULE_OF_TABLE, +/// ::IdInfo, +/// [ +/// (of::DeviceId::new(c"test,device"), ()) +/// ] +/// ); +/// +/// kernel::acpi_device_table!( +/// ACPI_TABLE, +/// MODULE_ACPI_TABLE, +/// ::IdInfo, +/// [ +/// (acpi::DeviceId::new(c"LNUXBEEF"), ()) +/// ] +/// ); +/// +/// #[vtable] +/// impl serdev::Driver for MyDriver { +/// type IdInfo = (); +/// type Data<'bound> = Self; +/// const OF_ID_TABLE: Option> = Some(&OF_TABLE); +/// const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); +/// +/// fn probe<'bound>( +/// sdev: &'bound serdev::Device>, +/// _id_info: Option<&'bound Self::IdInfo>, +/// ) -> impl PinInit, Error> + 'bound { +/// sdev.set_baudrate(115200); +/// sdev.write_all(b"Hello\n", 0)?; +/// Ok(MyDriver) +/// } +/// } +///``` +#[vtable] +pub trait Driver { + /// The type holding driver private data about each device id supported by the driver. + // TODO: Use associated_type_defaults once stabilized: + // + // ``` + // type IdInfo: 'static = (); + // ``` + type IdInfo: 'static; + + /// The type of the driver's bus device private data. + type Data<'bound>: Send + Sync + 'bound; + + /// The table of OF device ids supported by the driver. + const OF_ID_TABLE: Option> = None; + + /// The table of ACPI device ids supported by the driver. + const ACPI_ID_TABLE: Option> = None; + + /// Serial device bus device driver probe. + /// + /// Called when a new serial device bus device is added or discovered. + /// Implementers should attempt to initialize the device here. + fn probe<'bound>( + sdev: &'bound Device>, + id_info: Option<&'bound Self::IdInfo>, + ) -> impl PinInit, Error> + 'bound; + + /// Serial device bus device driver unbind. + /// + /// Called when a [`Device`] is unbound from its bound [`Driver`]. Implementing this callback + /// is optional. + /// + /// This callback serves as a place for drivers to perform teardown operations that require a + /// `&Device` or `&Device` reference. For instance. + /// + /// Otherwise, release operations for driver resources should be performed in `Drop`. + fn unbind<'bound>(sdev: &'bound Device>, this: Pin<&Self::Data<'bound>>) { + let _ = (sdev, this); + } + + /// Serial device bus device data receive callback. + /// + /// Called when data got received from device. + /// + /// Returns the number of bytes accepted. + fn receive<'bound>( + sdev: &'bound Device, + this: Pin<&Self::Data<'bound>>, + data: &[u8], + ) -> usize { + let _ = (sdev, this, data); + build_error!(VTABLE_DEFAULT_ERROR) + } +} + +/// The serial device bus device representation. +/// +/// This structure represents the Rust abstraction for a C `struct serdev_device`. The +/// implementation abstracts the usage of an already existing C `struct serdev_device` within Rust +/// code that we get passed from the C side. +/// +/// # Invariants +/// +/// A [`Device`] instance represents a valid `struct serdev_device` created by the C portion of +/// the kernel. +#[repr(transparent)] +pub struct Device( + Opaque, + PhantomData, +); + +impl Device { + #[inline] + fn as_raw(&self) -> *mut bindings::serdev_device { + self.0.get() + } +} + +impl Device { + /// Set the baudrate in bits per second. + /// + /// Common baudrates are 115200, 9600, 19200, 57600, 4800. + /// + /// Use [`Device::write_flush`] before calling this if you have written data prior to this call. + #[inline] + pub fn set_baudrate(&self, speed: u32) -> Result<(), u32> { + // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + let ret = unsafe { bindings::serdev_device_set_baudrate(self.as_raw(), speed) }; + if ret == speed { + Ok(()) + } else { + Err(ret) + } + } + + /// Set if flow control should be enabled. + /// + /// Use [`Device::write_flush`] before calling this if you have written data prior to this call. + #[inline] + pub fn set_flow_control(&self, enable: bool) { + // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + unsafe { bindings::serdev_device_set_flow_control(self.as_raw(), enable) }; + } + + /// Set parity to use. + /// + /// Use [`Device::write_flush`] before calling this if you have written data prior to this call. + #[inline] + pub fn set_parity(&self, parity: Parity) -> Result { + // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + to_result(unsafe { bindings::serdev_device_set_parity(self.as_raw(), parity as u32) }) + } + + /// Write data to the serial device until the controller has accepted all the data or has + /// been interrupted by a timeout or signal. + /// + /// Note that any accepted data has only been buffered by the controller. Use + /// [`Device::wait_until_sent`] to make sure the controller write buffer has actually been + /// emptied. + /// + /// Use a timeout of 0 to wait indefinitely. + /// + /// Returns the number of bytes written (less than `data.len()` if interrupted). + /// [`kernel::error::code::ETIMEDOUT`] or [`kernel::error::code::ERESTARTSYS`] if interrupted + /// before any bytes were written. [`kernel::error::code::EINVAL`] if `data.len() > i32::MAX`. + #[inline] + pub fn write_all(&self, data: &[u8], timeout: Jiffies) -> Result { + if data.len() > i32::MAX as usize { + return Err(EINVAL); + } + + // SAFETY: + // - `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + // - `data.as_ptr()` is guaranteed to be a valid array pointer with the size of + // `data.len()`. + let ret = unsafe { + bindings::serdev_device_write( + self.as_raw(), + data.as_ptr(), + data.len(), + isize::try_from(timeout).unwrap_or_default(), + ) + }; + // CAST: negative return values are guaranteed to be between `-MAX_ERRNO` and `-1`, + // which always fit into a `i32`. + to_result(ret as i32).map(|()| ret.unsigned_abs()) + } + + /// Write data to the serial device. + /// + /// If you want to write until the controller has accepted all the data, use + /// [`Device::write_all`]. + /// + /// Note that any accepted data has only been buffered by the controller. Use + /// [`Device::wait_until_sent`] to make sure the controller write buffer has actually been + /// emptied. + /// + /// Returns the number of bytes written (less than `data.len()` if not enough room in the + /// write buffer). + #[inline] + pub fn write(&self, data: &[u8]) -> Result { + if data.len() > i32::MAX as usize { + return Err(EINVAL); + } + + // SAFETY: + // - `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + // - `data.as_ptr()` is guaranteed to be a valid array pointer with the size of + // `data.len()`. + let ret = + unsafe { bindings::serdev_device_write_buf(self.as_raw(), data.as_ptr(), data.len()) }; + + to_result(ret as i32).map(|()| ret.unsigned_abs()) + } + + /// Send data to the serial device immediately. + /// + /// Note that this doesn't guarantee that the data has been transmitted. + /// Use [`Device::wait_until_sent`] for this purpose. + #[inline] + pub fn write_flush(&self) { + // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + unsafe { bindings::serdev_device_write_flush(self.as_raw()) }; + } + + /// Wait for the data to be sent. + /// + /// After this function, the write buffer of the controller should be empty or the timeout + /// elapsed. + /// + /// Use a timeout of 0 to wait indefinitely. + #[inline] + pub fn wait_until_sent(&self, timeout: Jiffies) { + // SAFETY: `self.as_raw()` is guaranteed to be a pointer to a valid `serdev_device`. + unsafe { + bindings::serdev_device_wait_until_sent( + self.as_raw(), + isize::try_from(timeout).unwrap_or_default(), + ) + }; + } +} + +// SAFETY: `serdev::Device` is a transparent wrapper of `struct serdev_device`. +// The offset is guaranteed to point to a valid device field inside `serdev::Device`. +unsafe impl device::AsBusDevice for Device { + const OFFSET: usize = offset_of!(bindings::serdev_device, dev); +} + +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic +// argument. +kernel::impl_device_context_deref!(unsafe { Device }); +kernel::impl_device_context_into_aref!(Device); + +// SAFETY: Instances of `Device` are always reference-counted. +unsafe impl AlwaysRefCounted for Device { + fn inc_ref(&self) { + self.as_ref().inc_ref(); + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::serdev_device_put(obj.cast().as_ptr()) } + } +} + +impl AsRef> for Device { + fn as_ref(&self) -> &device::Device { + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid + // `struct serdev_device`. + let dev = unsafe { &raw mut (*self.as_raw()).dev }; + + // SAFETY: `dev` points to a valid `struct device`. + unsafe { device::Device::from_raw(dev) } + } +} + +// SAFETY: A `Device` is always reference-counted and can be released from any thread. +unsafe impl Send for Device {} + +// SAFETY: `Device` can be shared among threads because all methods of `Device` +// (i.e. `Device) are thread safe. +unsafe impl Sync for Device {} + +// SAFETY: Same as `Device` -- the underlying `struct serdev_device` is the same; +// `Bound` is a zero-sized type-state marker that does not affect thread safety. +unsafe impl Sync for Device {} -- cgit From 4b2c9156d312b36d67265305c68088f542dc0933 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Sat, 18 Jul 2026 15:33:14 +0000 Subject: samples: rust: add Rust serial device bus sample device driver Add a sample Rust serial device bus device driver illustrating the usage of the serial device bus abstractions. This drivers probes through either a match of device / driver name or a match within the OF ID table. Acked-by: Greg Kroah-Hartman Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260718-rust_serdev-v16-2-5809384d2e1b@posteo.de Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + samples/rust/Kconfig | 11 +++++ samples/rust/Makefile | 1 + samples/rust/rust_driver_serdev.rs | 90 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 samples/rust/rust_driver_serdev.rs diff --git a/MAINTAINERS b/MAINTAINERS index 836b2baae3be..bb7d8e9b53e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24569,6 +24569,7 @@ F: drivers/tty/serdev/ F: include/linux/serdev.h F: rust/helpers/serdev.c F: rust/kernel/serdev.rs +F: samples/rust/rust_driver_serdev.rs SERIAL IR RECEIVER M: Sean Young diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index c49ab9106345..31d62533ef25 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -161,6 +161,17 @@ config SAMPLE_RUST_DRIVER_AUXILIARY If unsure, say N. +config SAMPLE_RUST_DRIVER_SERDEV + tristate "Serial Device Bus Device Driver" + select RUST_SERIAL_DEV_BUS_ABSTRACTIONS + help + This option builds the Rust serial device bus driver sample. + + To compile this as a module, choose M here: + the module will be called rust_driver_serdev. + + If unsure, say N. + config SAMPLE_RUST_SOC tristate "SoC Driver" select SOC_BUS diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 6c0aaa58cccc..b986b681cde5 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o +obj-$(CONFIG_SAMPLE_RUST_DRIVER_SERDEV) += rust_driver_serdev.o obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) += rust_configfs.o obj-$(CONFIG_SAMPLE_RUST_SOC) += rust_soc.o diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs new file mode 100644 index 000000000000..a12b1412db30 --- /dev/null +++ b/samples/rust/rust_driver_serdev.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust Serial device bus device driver sample. + +use kernel::{ + acpi, + device::{ + Bound, + Core, // + }, + of, + prelude::*, + serdev, + sync::aref::ARef, // +}; + +struct SampleDriver { + sdev: ARef, +} + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [(of::DeviceId::new(c"test,rust_driver_serdev"), ())] +); + +kernel::acpi_device_table!( + ACPI_TABLE, + MODULE_ACPI_TABLE, + ::IdInfo, + [(acpi::DeviceId::new(c"LNUXBEEF"), ())] +); + +#[vtable] +impl serdev::Driver for SampleDriver { + type IdInfo = (); + type Data<'bound> = Self; + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + const ACPI_ID_TABLE: Option> = Some(&ACPI_TABLE); + + fn probe<'bound>( + sdev: &'bound serdev::Device>, + _info: Option<&'bound Self::IdInfo>, + ) -> impl PinInit + 'bound { + let dev = sdev.as_ref(); + + dev_dbg!(dev, "Probe Rust Serial device bus device driver sample.\n"); + + if sdev + .set_baudrate( + dev.fwnode() + .and_then(|fwnode| fwnode.property_read(c"baudrate").optional()) + .unwrap_or(115200), + ) + .is_err() + { + return Err(EINVAL); + } + sdev.set_flow_control(false); + sdev.set_parity(serdev::Parity::None)?; + + Ok(Self { sdev: sdev.into() }) + } + + fn receive<'bound>( + sdev: &'bound serdev::Device, + _this: Pin<&Self>, + data: &[u8], + ) -> usize { + sdev.write(data).unwrap_or_default() as usize + } +} + +impl Drop for SampleDriver { + fn drop(&mut self) { + dev_dbg!( + self.sdev.as_ref(), + "Remove Rust Serial device bus device driver sample.\n" + ); + } +} + +kernel::module_serdev_device_driver! { + type: SampleDriver, + name: "rust_driver_serdev", + authors: ["Markus Probst"], + description: "Rust Serial device bus device driver", + license: "GPL v2", +} -- cgit From 6b71fef6f05cee64ebbaa8aec6dc688169f47e9c Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Sat, 18 Jul 2026 15:33:15 +0000 Subject: MAINTAINERS: serdev: Add self for serdev Rob mentioned he needs to find someone else to maintain serdev. Link: https://lore.kernel.org/rust-for-linux/20260430195858.GA1650658-robh@kernel.org/ Link: https://lore.kernel.org/rust-for-linux/da85ceb81f51079d4a8248a1ffde6a27d2ef24ad.camel@posteo.de/ Acked-by: Rob Herring (Arm) Acked-by: Greg Kroah-Hartman Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260718-rust_serdev-v16-3-5809384d2e1b@posteo.de Signed-off-by: Danilo Krummrich --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index bb7d8e9b53e7..8034273de98b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24561,7 +24561,7 @@ F: drivers/iio/chemical/sps30_i2c.c F: drivers/iio/chemical/sps30_serial.c SERIAL DEVICE BUS -M: Rob Herring +M: Markus Probst L: linux-serial@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/serial/serial.yaml -- cgit From 5037353473201f51567cdd420fdc02bf6e973776 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 20 Jul 2026 19:32:18 -0700 Subject: device property: mark internal data as private for kernel-doc Mark the @value union members as private since they are an internal representation of @value. This prevents kernel-doc warnings: Warning: include/linux/property.h:406 struct member 'u8_data' not described in 'property_entry' Warning: include/linux/property.h:406 struct member 'u16_data' not described in 'property_entry' Warning: include/linux/property.h:406 struct member 'u32_data' not described in 'property_entry' Warning: include/linux/property.h:406 struct member 'u64_data' not described in 'property_entry' Warning: include/linux/property.h:406 struct member 'str' not described in 'property_entry' Signed-off-by: Randy Dunlap Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260721023218.3490573-1-rdunlap@infradead.org Signed-off-by: Danilo Krummrich --- include/linux/property.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/property.h b/include/linux/property.h index 14c304db4664..907c790a3f01 100644 --- a/include/linux/property.h +++ b/include/linux/property.h @@ -397,11 +397,13 @@ struct property_entry { union { const void *pointer; union { + /* private: internal representation of @value */ u8 u8_data[sizeof(u64) / sizeof(u8)]; u16 u16_data[sizeof(u64) / sizeof(u16)]; u32 u32_data[sizeof(u64) / sizeof(u32)]; u64 u64_data[sizeof(u64) / sizeof(u64)]; const char *str[sizeof(u64) / sizeof(char *)]; + /* public: */ } value; }; }; -- cgit From fde6b526a9ee0a20d70e992e67ef79f2dfe42362 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 21 Jul 2026 00:55:30 -0700 Subject: samples: rust_dma: use vertical import style Convert `use` imports to vertical layout for better readability and maintainability. Signed-off-by: Guru Das Srinagesh Link: https://patch.msgid.link/20260721-rfl-vert-imp-v2-1-71ce0df519f2@gurudas.dev Signed-off-by: Danilo Krummrich --- samples/rust/rust_dma.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index b629acc6d915..3b640b2d15cd 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -17,10 +17,14 @@ use kernel::{ io_read, Io, // }, - page, pci, + page, + pci, prelude::*, - scatterlist::{Owned, SGTable}, - sync::aref::ARef, + scatterlist::{ + Owned, + SGTable, // + }, + sync::aref::ARef, // }; #[pin_data(PinnedDrop)] -- cgit From b47dcd7b41e22fa3383c2aa07f2935c7b8725f88 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 27 Jul 2026 16:43:08 +0200 Subject: platform/surface: gpe: add missing err.h include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now use PTR_ERR() and IS_ERR() in this module so pull in the header that provides them. Fixes: 1e0bd438b876 ("platform/surface: gpe: use platform_device_register_full()") Signed-off-by: Bartosz Golaszewski Acked-by: Ilpo Järvinen Link: https://patch.msgid.link/20260727144308.61842-1-bartosz.golaszewski@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/platform/surface/surface_gpe.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c index 4ca7ad76f3b9..29b264f2a444 100644 --- a/drivers/platform/surface/surface_gpe.c +++ b/drivers/platform/surface/surface_gpe.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include -- cgit From 1c2c8484b9de941a5363d880e92d8b00a73065b0 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 14:41:18 -0700 Subject: base: soc: fixup sys_soc.h kernel-doc warnings - add function return values in 2 places - use the correct function parameter name in kernel-doc comments to avoid these kernel-doc warnings: Warning: include/linux/sys_soc.h:25 No description found for return value of 'soc_device_register' Warning: include/linux/sys_soc.h:37 No description found for return value of 'soc_device_to_device' Warning: include/linux/sys_soc.h:31 function parameter 'soc_dev' not described in 'soc_device_unregister' Warning: include/linux/sys_soc.h:31 Excess function parameter 'dev' description in 'soc_device_unregister' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260723214118.652616-1-rdunlap@infradead.org Signed-off-by: Danilo Krummrich --- include/linux/sys_soc.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/linux/sys_soc.h b/include/linux/sys_soc.h index f19f5cec18e2..cedabf177f47 100644 --- a/include/linux/sys_soc.h +++ b/include/linux/sys_soc.h @@ -21,19 +21,26 @@ struct soc_device_attribute { /** * soc_device_register - register SoC as a device * @soc_plat_dev_attr: Attributes passed from platform to be attributed to a SoC + * + * Returns: + * - %NULL if the SoC bus is not yet registered; + * - on success, the newly allocated &struct soc_device pointer; + * - on failure, a negative error code as an ERR_PTR(). */ struct soc_device *soc_device_register( struct soc_device_attribute *soc_plat_dev_attr); /** * soc_device_unregister - unregister SoC device - * @dev: SoC device to be unregistered + * @soc_dev: SoC device to be unregistered */ void soc_device_unregister(struct soc_device *soc_dev); /** * soc_device_to_device - helper function to fetch struct device * @soc: Previously registered SoC device container + * + * Returns: &struct device pointer for this @soc */ struct device *soc_device_to_device(struct soc_device *soc); -- cgit From a1f1b3b478b45259a7b5472ea9ec1b48ef6b5cd6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 22 Jul 2026 19:50:16 +0100 Subject: rust: devres: use `cast_pin_init` instead of manual reimplementation Remove the manual type-casting which is already available as `cast_pin_init`. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260722-merge-init-v1-2-d4594de76538@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/devres.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs index ebb5c19e851e..aecc8906de2c 100644 --- a/rust/kernel/devres.rs +++ b/rust/kernel/devres.rs @@ -435,11 +435,7 @@ where // SAFETY: The caller guarantees the data is valid for the device's full bound scope. // Lifetimes do not affect layout, so F::Of<'a> and F::Of<'static> have identical // representation; casting the slot pointer is sound. - let data = unsafe { - pin_init::pin_init_from_closure::, E>(move |slot| { - data.__pinned_init(slot.cast()) - }) - }; + let data = unsafe { pin_init::cast_pin_init(data) }; Ok(Self(Devres::new(dev, data)?)) } -- cgit From 227717928656448498d1a1dd02c0e93b662b8edc Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 27 Jul 2026 22:27:40 +0200 Subject: fwnode: add missing kernel-doc for struct fwnode_operations members Document the four undocumented struct members in struct fwnode_operations: device_dma_supported, device_get_dma_attr, iomap, and irq_get. This avoids kernel-doc warnings once include/linux/fwnode.h is included in the driver-api infrastructure documentation. Reviewed-by: Bartosz Golaszewski Reviewed-by: Sakari Ailus Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Link: https://patch.msgid.link/20260727202748.2232253-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- include/linux/fwnode.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h index 4e86e6990d28..a9dcaf7e7076 100644 --- a/include/linux/fwnode.h +++ b/include/linux/fwnode.h @@ -117,6 +117,8 @@ struct fwnode_reference_args { * @put: Put a reference to an fwnode. * @device_is_available: Return true if the device is available. * @device_get_match_data: Return the device driver match data. + * @device_dma_supported: Return true if DMA is supported. + * @device_get_dma_attr: Return the device DMA attribute. * @property_present: Return true if a property is present. * @property_read_bool: Return a boolean property value. * @property_read_int_array: Read an array of integer properties. Return zero on @@ -134,6 +136,8 @@ struct fwnode_reference_args { * endpoint node. * @graph_get_port_parent: Return the parent node of a port node. * @graph_parse_endpoint: Parse endpoint for port and endpoint id. + * @iomap: Map the I/O memory of a given index for a fwnode. + * @irq_get: Get the IRQ of a given index for a fwnode. * @add_links: Create fwnode links to all the suppliers of the fwnode. Return * zero on success, a negative error code otherwise. */ -- cgit From f6e6e96d16b8626628dc91c524f36f9667aac679 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 26 Jul 2026 14:12:00 -0700 Subject: docs/driver-api: infrastructure: add property.h and fwnode.h Add more driver core header files to infrastructure.rst for completeness. Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260726211200.2643484-1-rdunlap@infradead.org Signed-off-by: Danilo Krummrich --- Documentation/driver-api/infrastructure.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/driver-api/infrastructure.rst b/Documentation/driver-api/infrastructure.rst index 35e36fee4238..ac73e60fb09c 100644 --- a/Documentation/driver-api/infrastructure.rst +++ b/Documentation/driver-api/infrastructure.rst @@ -17,6 +17,10 @@ The Basic Device Driver-Model Structures .. kernel-doc:: include/linux/device/driver.h :identifiers: probe_type device_driver +.. kernel-doc:: include/linux/property.h + +.. kernel-doc:: include/linux/fwnode.h + Device Drivers Base ------------------- -- cgit From 3474bbba4ddd1ffaad2fa1d5e4a4a06e55ac45c6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:35 +0100 Subject: rust: driver: remove `IdTable::id` This is unused. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-1-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/device_id.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs index 8e9721446014..fbf6d8e6afb9 100644 --- a/rust/kernel/device_id.rs +++ b/rust/kernel/device_id.rs @@ -166,9 +166,6 @@ pub trait IdTable { /// Obtain the pointer to the ID table. fn as_ptr(&self) -> *const T::RawType; - /// Obtain the pointer to the bus specific device ID from an index. - fn id(&self, index: usize) -> &T::RawType; - /// Obtain the pointer to the driver-specific information from an index. fn info(&self, index: usize) -> &U; } @@ -180,10 +177,6 @@ impl IdTable for IdArray { core::ptr::from_ref(self).cast() } - fn id(&self, index: usize) -> &T::RawType { - &self.raw_ids.ids[index] - } - fn info(&self, index: usize) -> &U { &self.id_infos[index] } -- cgit From e49f626ef541c38ab8548d60015872c700711884 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:36 +0100 Subject: rust: driver: simplify `IdArray::new_without_index` This method can very easily construct the `IdArray` on its own without delegating to `Self::build`. Doing so also simplifies the phy device table macro because it does not need to construct tuples anymore. This also allows simplification of `new` and `build` which removes the `unsafe`. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-2-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/device_id.rs | 64 ++++++++++++++++++++---------------------------- rust/kernel/net/phy.rs | 2 +- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs index fbf6d8e6afb9..eeef3f5e7b63 100644 --- a/rust/kernel/device_id.rs +++ b/rust/kernel/device_id.rs @@ -73,19 +73,11 @@ pub struct IdArray { id_infos: [U; N], } -impl IdArray { +impl IdArray { /// Creates a new instance of the array. /// /// The contents are derived from the given identifiers and context information. - /// - /// # Safety - /// - /// `data_offset` as `None` is always safe. - /// If `data_offset` is `Some(data_offset)`, then: - /// - `data_offset` must be the correct offset (in bytes) to the context/data field - /// (e.g., the `driver_data` field) within the raw device ID structure. - /// - The field at `data_offset` must be correctly sized to hold a `usize`. - const unsafe fn build(ids: [(T, U); N], data_offset: Option) -> Self { + pub const fn new(ids: [(T, U); N]) -> Self { let mut raw_ids = [const { MaybeUninit::::uninit() }; N]; let mut infos = [const { MaybeUninit::uninit() }; N]; @@ -94,16 +86,14 @@ impl IdArray { // SAFETY: by the safety requirement of `RawDeviceId`, we're guaranteed that `T` is // layout-wise compatible with `RawType`. raw_ids[i] = unsafe { core::mem::transmute_copy(&ids[i].0) }; - if let Some(data_offset) = data_offset { - // SAFETY: by the safety requirement of this function, this would be effectively - // `raw_ids[i].driver_data = i;`. - unsafe { - raw_ids[i] - .as_mut_ptr() - .byte_add(data_offset) - .cast::() - .write(i); - } + // SAFETY: by the safety requirement of `RawDeviceIdIndex`, this would be effectively + // `raw_ids[i].driver_data = i;`. + unsafe { + raw_ids[i] + .as_mut_ptr() + .byte_add(T::DRIVER_DATA_OFFSET) + .cast::() + .write(i); } // SAFETY: this is effectively a move: `infos[i] = ids[i].1`. We make a copy here but @@ -127,32 +117,32 @@ impl IdArray { id_infos: unsafe { core::mem::transmute_copy(&infos) }, } } +} - /// Creates a new instance of the array without writing index values. - /// - /// The contents are derived from the given identifiers and context information. - /// If the device implements [`RawDeviceIdIndex`], consider using [`IdArray::new`] instead. - pub const fn new_without_index(ids: [(T, U); N]) -> Self { - // SAFETY: Calling `Self::build` with `offset = None` is always safe, - // because no raw memory writes are performed in this case. - unsafe { Self::build(ids, None) } - } - +impl IdArray { /// Reference to the contained [`RawIdArray`]. pub const fn raw_ids(&self) -> &RawIdArray { &self.raw_ids } } -impl IdArray { - /// Creates a new instance of the array. +impl IdArray { + /// Creates a new instance of the array without writing index values. /// /// The contents are derived from the given identifiers and context information. - pub const fn new(ids: [(T, U); N]) -> Self { - // SAFETY: by the safety requirement of `RawDeviceIdIndex`, - // `T::DRIVER_DATA_OFFSET` is guaranteed to be the correct offset (in bytes) to - // a field within `T::RawType`. - unsafe { Self::build(ids, Some(T::DRIVER_DATA_OFFSET)) } + /// If the device implements [`RawDeviceIdIndex`], consider using [`IdArray::new`] instead. + pub const fn new_without_index(ids: [T; N]) -> Self { + // SAFETY: `T` is layout-wise compatible with `T::RawType`, so is the array of them. + let raw_ids: [T::RawType; N] = unsafe { core::mem::transmute_copy(&ids) }; + core::mem::forget(ids); + + Self { + raw_ids: RawIdArray { + ids: raw_ids, + sentinel: MaybeUninit::zeroed(), + }, + id_infos: [(); N], + } } } diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index 3ca99db5cccf..2868e3a9e02c 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -868,7 +868,7 @@ macro_rules! module_phy_driver { const N: usize = $crate::module_phy_driver!(@count_devices $($dev),+); const TABLE: $crate::device_id::IdArray<$crate::net::phy::DeviceId, (), N> = - $crate::device_id::IdArray::new_without_index([ $(($dev,())),+, ]); + $crate::device_id::IdArray::new_without_index([ $($dev),+, ]); $crate::module_device_table!("mdio", phydev, TABLE); }; -- cgit From 4fcbf7f1e4a439b40426a663a6034b52739671a3 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:37 +0100 Subject: rust: pci: use `Option<&IdInfo>` for device ID info It is possible that `pci_device_id_any` will be passed to the driver, e.g. `driver_override` is used on the device. Therefore, the driver must be able to handle the case where `driver_data` is 0. Thus, update the `probe` functions to get `Option`. The current code cannot tell if the info does not exist or is the first entry; however this will be achievable once the code is updated to use a `&'static IdInfo` pointer instead of indices. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-3-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- drivers/gpu/nova-core/driver.rs | 2 +- rust/kernel/pci.rs | 6 +++--- samples/rust/rust_dma.rs | 2 +- samples/rust/rust_driver_auxiliary.rs | 2 +- samples/rust/rust_driver_pci.rs | 3 ++- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index 48380ac15f68..1eef79ec12f1 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -70,7 +70,7 @@ impl pci::Driver for NovaCoreDriver { fn probe<'bound>( pdev: &'bound pci::Device>, - _info: &'bound Self::IdInfo, + _info: Option<&'bound Self::IdInfo>, ) -> impl PinInit, Error> + 'bound { pin_init::pin_init_scope(move || { dev_dbg!(pdev, "Probe Nova Core GPU driver.\n"); diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index ee25ae56bb92..8f11711bf342 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -113,7 +113,7 @@ impl Adapter { let info = T::ID_TABLE.info(id.index()); from_result(|| { - let data = T::probe(pdev, info); + let data = T::probe(pdev, Some(info)); pdev.as_ref().set_drvdata(data)?; Ok(0) @@ -284,7 +284,7 @@ macro_rules! pci_device_table { /// /// fn probe<'bound>( /// _pdev: &'bound pci::Device>, -/// _id_info: &'bound Self::IdInfo, +/// _id_info: Option<&'bound Self::IdInfo>, /// ) -> impl PinInit, Error> + 'bound { /// Err(ENODEV) /// } @@ -313,7 +313,7 @@ pub trait Driver { /// attempt to initialize the device here. fn probe<'bound>( dev: &'bound Device>, - id_info: &'bound Self::IdInfo, + id_info: Option<&'bound Self::IdInfo>, ) -> impl PinInit, Error> + 'bound; /// PCI driver unbind. diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 3b640b2d15cd..3298b3f427f4 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -73,7 +73,7 @@ impl pci::Driver for DmaSampleDriver { fn probe<'bound>( pdev: &'bound pci::Device>, - _info: &'bound Self::IdInfo, + _info: Option<&'bound Self::IdInfo>, ) -> impl PinInit + 'bound { pin_init::pin_init_scope(move || { dev_info!(pdev, "Probe DMA test driver.\n"); diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index e441ae81fa2c..5b23ab273a35 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -100,7 +100,7 @@ impl pci::Driver for ParentDriver { fn probe<'bound>( pdev: &'bound pci::Device>, - _info: &'bound Self::IdInfo, + _info: Option<&'bound Self::IdInfo>, ) -> impl PinInit, Error> + 'bound { try_pin_init!(ParentData { // SAFETY: `ParentData` is the driver's private data, which is dropped when the diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 1aa8197d8698..5547dd704a1b 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -144,7 +144,7 @@ impl pci::Driver for SampleDriver { fn probe<'bound>( pdev: &'bound pci::Device>, - info: &'bound Self::IdInfo, + info: Option<&'bound Self::IdInfo>, ) -> impl PinInit, Error> + 'bound { let vendor = pdev.vendor_id(); dev_dbg!( @@ -153,6 +153,7 @@ impl pci::Driver for SampleDriver { vendor, pdev.device_id() ); + let info = info.ok_or(ENODEV)?; pdev.enable_device_mem()?; pdev.set_master(); -- cgit From c7b6e07f83b4710c885d3c9fa8094b11a719a5a0 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:38 +0100 Subject: rust: usb: use `Option<&IdInfo>` for device ID info It is possible that ID without driver_data will be passed to the driver, e.g. `new_id` is used to dynamically create a new ID without data. Therefore, the driver must be able to handle the case where `driver_data` is 0. Thus, update the `probe` functions to get `Option`. The current code cannot tell if the info does not exist or is the first entry; however this will be achievable once the code is updated to use a `&'static IdInfo` pointer instead of indices. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-4-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/usb.rs | 6 +++--- samples/rust/rust_driver_usb.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 7aff0c82d0af..6750a49e466b 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -90,7 +90,7 @@ impl Adapter { let id = unsafe { &*id.cast::() }; let info = T::ID_TABLE.info(id.index()); - let data = T::probe(intf, id, info); + let data = T::probe(intf, id, Some(info)); let dev: &device::Device> = intf.as_ref(); dev.set_drvdata(data)?; @@ -293,7 +293,7 @@ macro_rules! usb_device_table { /// fn probe<'bound>( /// _interface: &'bound usb::Interface>, /// _id: &usb::DeviceId, -/// _info: &'bound Self::IdInfo, +/// _info: Option<&'bound Self::IdInfo>, /// ) -> impl PinInit, Error> + 'bound { /// Err(ENODEV) /// } @@ -322,7 +322,7 @@ pub trait Driver { fn probe<'bound>( interface: &'bound Interface>, id: &DeviceId, - id_info: &'bound Self::IdInfo, + id_info: Option<&'bound Self::IdInfo>, ) -> impl PinInit, Error> + 'bound; /// USB driver disconnect. diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs index 02bd5085f9bc..176ef625ed75 100644 --- a/samples/rust/rust_driver_usb.rs +++ b/samples/rust/rust_driver_usb.rs @@ -32,7 +32,7 @@ impl usb::Driver for SampleDriver { fn probe<'bound>( intf: &'bound usb::Interface>, _id: &usb::DeviceId, - _info: &'bound Self::IdInfo, + _info: Option<&'bound Self::IdInfo>, ) -> impl PinInit + 'bound { let dev: &device::Device> = intf.as_ref(); dev_info!(dev, "Rust USB driver sample probed\n"); -- cgit From 1d92f2e8b8810556d631cd5dffb536a66ff55553 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:40 +0100 Subject: rust: driver: centralize device ID handling Move the `IdArray` creation from individual buses to be handled by shared code in `device_id.rs`. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-6-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/acpi.rs | 10 ++-------- rust/kernel/auxiliary.rs | 10 ++-------- rust/kernel/device_id.rs | 31 ++++++++++++++++++++++++++++++- rust/kernel/i2c.rs | 10 ++-------- rust/kernel/net/phy.rs | 10 ++++------ rust/kernel/of.rs | 10 ++-------- rust/kernel/pci.rs | 10 ++-------- rust/kernel/usb.rs | 10 ++-------- 8 files changed, 46 insertions(+), 55 deletions(-) diff --git a/rust/kernel/acpi.rs b/rust/kernel/acpi.rs index 9b8efa623130..315f2f2af446 100644 --- a/rust/kernel/acpi.rs +++ b/rust/kernel/acpi.rs @@ -53,13 +53,7 @@ impl DeviceId { /// Create an ACPI `IdTable` with an "alias" for modpost. #[macro_export] macro_rules! acpi_device_table { - ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { - const $table_name: $crate::device_id::IdArray< - $crate::acpi::DeviceId, - $id_info_type, - { $table_data.len() }, - > = $crate::device_id::IdArray::new($table_data); - - $crate::module_device_table!("acpi", $module_table_name, $table_name); + ($($tt:tt)*) => { + $crate::module_device_table!("acpi", $crate::acpi::DeviceId, $($tt)*); }; } diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index 19a488700bb9..a5e93f18e0fc 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -182,14 +182,8 @@ pub type IdTable = &'static dyn kernel::device_id::IdTable; /// Create a auxiliary `IdTable` with its alias for modpost. #[macro_export] macro_rules! auxiliary_device_table { - ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { - const $table_name: $crate::device_id::IdArray< - $crate::auxiliary::DeviceId, - $id_info_type, - { $table_data.len() }, - > = $crate::device_id::IdArray::new($table_data); - - $crate::module_device_table!("auxiliary", $module_table_name, $table_name); + ($($tt:tt)*) => { + $crate::module_device_table!("auxiliary", $crate::auxiliary::DeviceId, $($tt)*); }; } diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs index eeef3f5e7b63..0239f89d5f69 100644 --- a/rust/kernel/device_id.rs +++ b/rust/kernel/device_id.rs @@ -175,7 +175,36 @@ impl IdTable for IdArray { /// Create device table alias for modpost. #[macro_export] macro_rules! module_device_table { - ($table_type: literal, $module_table_name:ident, $table_name:ident) => { + ( + $table_type: literal, $device_id_ty: ty, + $table_name: ident, $module_table_name: ident, $id_info_type: ty, + [$(($id: expr, $info:expr $(,)?)),* $(,)?] + ) => { + const $table_name: $crate::device_id::IdArray< + $device_id_ty, + $id_info_type, + { <[$device_id_ty]>::len(&[$($id,)*]) }, + > = $crate::device_id::IdArray::new([$(($id, $info),)*]); + + $crate::module_device_table!($table_type, $module_table_name, $table_name); + }; + + // Case for no ID info. + ( + $table_type: literal, $device_id_ty: ty, + $table_name: ident, $module_table_name: ident, @none, + [$($id: expr),* $(,)?] + ) => { + const $table_name: $crate::device_id::IdArray< + $device_id_ty, + (), + { <[$device_id_ty]>::len(&[$($id,)*]) }, + > = $crate::device_id::IdArray::new_without_index([$($id),*]); + + $crate::module_device_table!($table_type, $module_table_name, $table_name); + }; + + ($table_type: literal, $module_table_name: ident, $table_name:ident) => { #[rustfmt::skip] #[export_name = concat!("__mod_device_table__", line!(), diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index 624b971ca8b0..a1f968fd873d 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -77,14 +77,8 @@ pub type IdTable = &'static dyn kernel::device_id::IdTable; /// Create a I2C `IdTable` with its alias for modpost. #[macro_export] macro_rules! i2c_device_table { - ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { - const $table_name: $crate::device_id::IdArray< - $crate::i2c::DeviceId, - $id_info_type, - { $table_data.len() }, - > = $crate::device_id::IdArray::new($table_data); - - $crate::module_device_table!("i2c", $module_table_name, $table_name); + ($($tt:tt)*) => { + $crate::module_device_table!("i2c", $crate::i2c::DeviceId, $($tt)*); }; } diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index 2868e3a9e02c..e25c61c6d8bb 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -865,12 +865,10 @@ macro_rules! module_phy_driver { }; (@device_table [$($dev:expr),+]) => { - const N: usize = $crate::module_phy_driver!(@count_devices $($dev),+); - - const TABLE: $crate::device_id::IdArray<$crate::net::phy::DeviceId, (), N> = - $crate::device_id::IdArray::new_without_index([ $($dev),+, ]); - - $crate::module_device_table!("mdio", phydev, TABLE); + $crate::module_device_table!( + "mdio", $crate::net::phy::DeviceId, + phydev, TABLE, @none, [$($dev),+] + ); }; (drivers: [$($driver:ident),+ $(,)?], device_table: [$($dev:expr),+ $(,)?], $($f:tt)*) => { diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index 58b20c367f99..35aa6d36d309 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -53,13 +53,7 @@ impl DeviceId { /// Create an OF `IdTable` with an "alias" for modpost. #[macro_export] macro_rules! of_device_table { - ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { - const $table_name: $crate::device_id::IdArray< - $crate::of::DeviceId, - $id_info_type, - { $table_data.len() }, - > = $crate::device_id::IdArray::new($table_data); - - $crate::module_device_table!("of", $module_table_name, $table_name); + ($($tt:tt)*) => { + $crate::module_device_table!("of", $crate::of::DeviceId, $($tt)*); }; } diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 8f11711bf342..5cc4052991e7 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -245,14 +245,8 @@ pub type IdTable = &'static dyn kernel::device_id::IdTable; /// Create a PCI `IdTable` with its alias for modpost. #[macro_export] macro_rules! pci_device_table { - ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { - const $table_name: $crate::device_id::IdArray< - $crate::pci::DeviceId, - $id_info_type, - { $table_data.len() }, - > = $crate::device_id::IdArray::new($table_data); - - $crate::module_device_table!("pci", $module_table_name, $table_name); + ($($tt:tt)*) => { + $crate::module_device_table!("pci", $crate::pci::DeviceId, $($tt)*); }; } diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 6750a49e466b..3797f4a79b79 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -254,14 +254,8 @@ pub type IdTable = &'static dyn kernel::device_id::IdTable; /// Create a USB `IdTable` with its alias for modpost. #[macro_export] macro_rules! usb_device_table { - ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { - const $table_name: $crate::device_id::IdArray< - $crate::usb::DeviceId, - $id_info_type, - { $table_data.len() }, - > = $crate::device_id::IdArray::new($table_data); - - $crate::module_device_table!("usb", $module_table_name, $table_name); + ($($tt:tt)*) => { + $crate::module_device_table!("usb", $crate::usb::DeviceId, $($tt)*); }; } -- cgit From 0deeb4222dd1bd76fe0e0bbeedff862d7a42ce44 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:41 +0100 Subject: rust: driver: remove `$module_table_name` from `module_device_table` Wrap the generated code in a `const _: ()` block to avoid symbol conflict. This removes the need of creating a new identifier. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-7-56fccbe9c5ef@garyguo.net [ Consider the serdev code merged in the meantime. - Danilo ] Signed-off-by: Danilo Krummrich --- drivers/cpufreq/rcpufreq_dt.rs | 1 - drivers/gpu/drm/nova/driver.rs | 1 - drivers/gpu/drm/tyr/driver.rs | 1 - drivers/gpu/nova-core/driver.rs | 1 - drivers/pwm/pwm_th1520.rs | 1 - rust/kernel/device_id.rs | 30 ++++++++++++++++-------------- rust/kernel/i2c.rs | 3 --- rust/kernel/net/phy.rs | 2 +- rust/kernel/pci.rs | 1 - rust/kernel/platform.rs | 2 -- rust/kernel/serdev.rs | 2 -- rust/kernel/usb.rs | 1 - samples/rust/rust_debugfs.rs | 1 - samples/rust/rust_dma.rs | 1 - samples/rust/rust_driver_auxiliary.rs | 2 -- samples/rust/rust_driver_i2c.rs | 3 --- samples/rust/rust_driver_pci.rs | 1 - samples/rust/rust_driver_platform.rs | 2 -- samples/rust/rust_driver_serdev.rs | 2 -- samples/rust/rust_driver_usb.rs | 1 - samples/rust/rust_i2c_client.rs | 2 -- samples/rust/rust_soc.rs | 2 -- 22 files changed, 17 insertions(+), 46 deletions(-) diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs index 10106fa13095..145daa12072f 100644 --- a/drivers/cpufreq/rcpufreq_dt.rs +++ b/drivers/cpufreq/rcpufreq_dt.rs @@ -194,7 +194,6 @@ impl cpufreq::Driver for CPUFreqDTDriver { kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"operating-points-v2"), ())] ); diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs index 48933d86ddda..43f15cdfeb09 100644 --- a/drivers/gpu/drm/nova/driver.rs +++ b/drivers/gpu/drm/nova/driver.rs @@ -43,7 +43,6 @@ const AUXILIARY_NAME: &CStr = c"nova-drm"; kernel::auxiliary_device_table!( AUX_TABLE, - MODULE_AUX_TABLE, ::IdInfo, [( auxiliary::DeviceId::new(NOVA_CORE_MODULE_NAME, AUXILIARY_NAME), diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index d063bc664cc1..218e9af899c7 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -87,7 +87,6 @@ fn issue_soft_reset(dev: &Device, iomem: &IoMem<'_>) -> Result { kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [ (of::DeviceId::new(c"rockchip,rk3588-mali"), ()), diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs index 1eef79ec12f1..bbd93959e0b2 100644 --- a/drivers/gpu/nova-core/driver.rs +++ b/drivers/gpu/nova-core/driver.rs @@ -40,7 +40,6 @@ pub(crate) type Bar0<'a> = &'a pci::Bar<'a, BAR0_SIZE>; kernel::pci_device_table!( PCI_TABLE, - MODULE_PCI_TABLE, ::IdInfo, [ // Modern NVIDIA GPUs will show up as either VGA or 3D controllers. diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs index 9372cd3e6a6d..e4387f4fedd9 100644 --- a/drivers/pwm/pwm_th1520.rs +++ b/drivers/pwm/pwm_th1520.rs @@ -299,7 +299,6 @@ struct Th1520PwmPlatformDriver; kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"thead,th1520-pwm"), ())] ); diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs index 0239f89d5f69..022f0101871f 100644 --- a/rust/kernel/device_id.rs +++ b/rust/kernel/device_id.rs @@ -177,7 +177,7 @@ impl IdTable for IdArray { macro_rules! module_device_table { ( $table_type: literal, $device_id_ty: ty, - $table_name: ident, $module_table_name: ident, $id_info_type: ty, + $table_name: ident, $id_info_type: ty, [$(($id: expr, $info:expr $(,)?)),* $(,)?] ) => { const $table_name: $crate::device_id::IdArray< @@ -186,13 +186,13 @@ macro_rules! module_device_table { { <[$device_id_ty]>::len(&[$($id,)*]) }, > = $crate::device_id::IdArray::new([$(($id, $info),)*]); - $crate::module_device_table!($table_type, $module_table_name, $table_name); + $crate::module_device_table!($table_type, $table_name); }; // Case for no ID info. ( $table_type: literal, $device_id_ty: ty, - $table_name: ident, $module_table_name: ident, @none, + $table_name: ident, @none, [$($id: expr),* $(,)?] ) => { const $table_name: $crate::device_id::IdArray< @@ -201,18 +201,20 @@ macro_rules! module_device_table { { <[$device_id_ty]>::len(&[$($id,)*]) }, > = $crate::device_id::IdArray::new_without_index([$($id),*]); - $crate::module_device_table!($table_type, $module_table_name, $table_name); + $crate::module_device_table!($table_type, $table_name); }; - ($table_type: literal, $module_table_name: ident, $table_name:ident) => { - #[rustfmt::skip] - #[export_name = - concat!("__mod_device_table__", line!(), - "__kmod_", module_path!(), - "__", $table_type, - "__", stringify!($table_name)) - ] - static $module_table_name: [::core::mem::MaybeUninit; $table_name.raw_ids().size()] = - unsafe { ::core::mem::transmute_copy($table_name.raw_ids()) }; + ($table_type: literal, $table_name:ident) => { + const _: () = { + #[rustfmt::skip] + #[export_name = + concat!("__mod_device_table__", line!(), + "__kmod_", module_path!(), + "__", $table_type, + "__", stringify!($table_name)) + ] + static MOD_DEVICE_TABLE: [::core::mem::MaybeUninit; $table_name.raw_ids().size()] = + unsafe { ::core::mem::transmute_copy($table_name.raw_ids()) }; + }; }; } diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index a1f968fd873d..6f2dcd467e72 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -261,7 +261,6 @@ macro_rules! module_i2c_driver { /// /// kernel::acpi_device_table!( /// ACPI_TABLE, -/// MODULE_ACPI_TABLE, /// ::IdInfo, /// [ /// (acpi::DeviceId::new(c"LNUXBEEF"), ()) @@ -270,7 +269,6 @@ macro_rules! module_i2c_driver { /// /// kernel::i2c_device_table!( /// I2C_TABLE, -/// MODULE_I2C_TABLE, /// ::IdInfo, /// [ /// (i2c::DeviceId::new(c"rust_driver_i2c"), ()) @@ -279,7 +277,6 @@ macro_rules! module_i2c_driver { /// /// kernel::of_device_table!( /// OF_TABLE, -/// MODULE_OF_TABLE, /// ::IdInfo, /// [ /// (of::DeviceId::new(c"test,device"), ()) diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index e25c61c6d8bb..cc7ec436cfca 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -867,7 +867,7 @@ macro_rules! module_phy_driver { (@device_table [$($dev:expr),+]) => { $crate::module_device_table!( "mdio", $crate::net::phy::DeviceId, - phydev, TABLE, @none, [$($dev),+] + TABLE, @none, [$($dev),+] ); }; diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index 5cc4052991e7..b170cf8fffe1 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -261,7 +261,6 @@ macro_rules! pci_device_table { /// /// kernel::pci_device_table!( /// PCI_TABLE, -/// MODULE_PCI_TABLE, /// ::IdInfo, /// [ /// ( diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 9b362e0495d3..210a815925ce 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -176,7 +176,6 @@ macro_rules! module_platform_driver { /// /// kernel::of_device_table!( /// OF_TABLE, -/// MODULE_OF_TABLE, /// ::IdInfo, /// [ /// (of::DeviceId::new(c"test,device"), ()) @@ -185,7 +184,6 @@ macro_rules! module_platform_driver { /// /// kernel::acpi_device_table!( /// ACPI_TABLE, -/// MODULE_ACPI_TABLE, /// ::IdInfo, /// [ /// (acpi::DeviceId::new(c"LNUXBEEF"), ()) diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs index cf537d9eeeb4..383f62b1b713 100644 --- a/rust/kernel/serdev.rs +++ b/rust/kernel/serdev.rs @@ -322,7 +322,6 @@ macro_rules! module_serdev_device_driver { /// /// kernel::of_device_table!( /// OF_TABLE, -/// MODULE_OF_TABLE, /// ::IdInfo, /// [ /// (of::DeviceId::new(c"test,device"), ()) @@ -331,7 +330,6 @@ macro_rules! module_serdev_device_driver { /// /// kernel::acpi_device_table!( /// ACPI_TABLE, -/// MODULE_ACPI_TABLE, /// ::IdInfo, /// [ /// (acpi::DeviceId::new(c"LNUXBEEF"), ()) diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index 3797f4a79b79..d8cffbe594ff 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -271,7 +271,6 @@ macro_rules! usb_device_table { /// /// kernel::usb_device_table!( /// USB_TABLE, -/// MODULE_USB_TABLE, /// ::IdInfo, /// [ /// (usb::DeviceId::from_id(0x1234, 0x5678), ()), diff --git a/samples/rust/rust_debugfs.rs b/samples/rust/rust_debugfs.rs index 0b27ad96ecbf..77c19a437695 100644 --- a/samples/rust/rust_debugfs.rs +++ b/samples/rust/rust_debugfs.rs @@ -110,7 +110,6 @@ impl FromStr for Inner { kernel::acpi_device_table!( ACPI_TABLE, - MODULE_ACPI_TABLE, ::IdInfo, [(acpi::DeviceId::new(c"LNUXBEEF"), ())] ); diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs index 3298b3f427f4..bd60034ded23 100644 --- a/samples/rust/rust_dma.rs +++ b/samples/rust/rust_dma.rs @@ -61,7 +61,6 @@ unsafe impl kernel::transmute::FromBytes for MyStruct {} kernel::pci_device_table!( PCI_TABLE, - MODULE_PCI_TABLE, ::IdInfo, [(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())] ); diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs index 5b23ab273a35..0bee16faecc6 100644 --- a/samples/rust/rust_driver_auxiliary.rs +++ b/samples/rust/rust_driver_auxiliary.rs @@ -31,7 +31,6 @@ struct AuxiliaryDriver; kernel::auxiliary_device_table!( AUX_TABLE, - MODULE_AUX_TABLE, ::IdInfo, [(auxiliary::DeviceId::new(MODULE_NAME, AUXILIARY_NAME), ())] ); @@ -87,7 +86,6 @@ struct ParentData<'bound> { kernel::pci_device_table!( PCI_TABLE, - MODULE_PCI_TABLE, ::IdInfo, [(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())] ); diff --git a/samples/rust/rust_driver_i2c.rs b/samples/rust/rust_driver_i2c.rs index ead8263a7d48..a0df0c6097c4 100644 --- a/samples/rust/rust_driver_i2c.rs +++ b/samples/rust/rust_driver_i2c.rs @@ -14,21 +14,18 @@ struct SampleDriver; kernel::acpi_device_table! { ACPI_TABLE, - MODULE_ACPI_TABLE, ::IdInfo, [(acpi::DeviceId::new(c"LNUXBEEF"), 0)] } kernel::i2c_device_table! { I2C_TABLE, - MODULE_I2C_TABLE, ::IdInfo, [(i2c::DeviceId::new(c"rust_driver_i2c"), 0)] } kernel::of_device_table! { OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"test,rust_driver_i2c"), 0)] } diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 5547dd704a1b..2282191e6292 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -74,7 +74,6 @@ struct SampleDriver; kernel::pci_device_table!( PCI_TABLE, - MODULE_PCI_TABLE, ::IdInfo, [( pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), diff --git a/samples/rust/rust_driver_platform.rs b/samples/rust/rust_driver_platform.rs index ec0d6cac4f57..710145b3605a 100644 --- a/samples/rust/rust_driver_platform.rs +++ b/samples/rust/rust_driver_platform.rs @@ -87,14 +87,12 @@ struct Info(u32); kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"test,rust-device"), Info(42))] ); kernel::acpi_device_table!( ACPI_TABLE, - MODULE_ACPI_TABLE, ::IdInfo, [(acpi::DeviceId::new(c"LNUXBEEF"), Info(0))] ); diff --git a/samples/rust/rust_driver_serdev.rs b/samples/rust/rust_driver_serdev.rs index a12b1412db30..51b4898cd855 100644 --- a/samples/rust/rust_driver_serdev.rs +++ b/samples/rust/rust_driver_serdev.rs @@ -20,14 +20,12 @@ struct SampleDriver { kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"test,rust_driver_serdev"), ())] ); kernel::acpi_device_table!( ACPI_TABLE, - MODULE_ACPI_TABLE, ::IdInfo, [(acpi::DeviceId::new(c"LNUXBEEF"), ())] ); diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs index 176ef625ed75..7ef04e177c80 100644 --- a/samples/rust/rust_driver_usb.rs +++ b/samples/rust/rust_driver_usb.rs @@ -19,7 +19,6 @@ struct SampleDriver { kernel::usb_device_table!( USB_TABLE, - MODULE_USB_TABLE, ::IdInfo, [(usb::DeviceId::from_id(0x1234, 0x5678), ()),] ); diff --git a/samples/rust/rust_i2c_client.rs b/samples/rust/rust_i2c_client.rs index 2d876f4e3ee0..c8a23875ef5b 100644 --- a/samples/rust/rust_i2c_client.rs +++ b/samples/rust/rust_i2c_client.rs @@ -87,14 +87,12 @@ struct SampleDriver { kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"test,rust-device"), ())] ); kernel::acpi_device_table!( ACPI_TABLE, - MODULE_ACPI_TABLE, ::IdInfo, [(acpi::DeviceId::new(c"LNUXBEEF"), ())] ); diff --git a/samples/rust/rust_soc.rs b/samples/rust/rust_soc.rs index 808d58200eb6..f5e5f2f9adf7 100644 --- a/samples/rust/rust_soc.rs +++ b/samples/rust/rust_soc.rs @@ -23,14 +23,12 @@ struct SampleSocDriver { kernel::of_device_table!( OF_TABLE, - MODULE_OF_TABLE, ::IdInfo, [(of::DeviceId::new(c"test,rust-device"), ())] ); kernel::acpi_device_table!( ACPI_TABLE, - MODULE_ACPI_TABLE, ::IdInfo, [(acpi::DeviceId::new(c"LNUXBEEF"), ())] ); -- cgit From 0b76335e8f321a59c32a5f52759f635d2e5db5c8 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:42 +0100 Subject: rust: driver: store pointers in `DeviceId` The common practice in C drivers is to store pointers into `driver_data` field of device IDs. The Rust code is however currently storing indices into the fields and then carry a side table that maps the index to pointers. It is much simpler to just have `DeviceId` carry the pointer like C code does. However, just doing so naively would cause a "pointers cannot be cast to integers during const eval" error, as kernel_ulong_t does not have provenance while pointers do, and Rust forbids `expose_provenance` during consteval. Work around this limitation by wrapping raw IDs in `MaybeUninit`. `MaybeUninit` is allowed to host arbitrary bytes with or without provenance, so we can just then use `unsafe` to store a pointer with provenance there. This has the same effect as changing the C-side definition to use `void*` instead of `kernel_ulong_t`, but without actually changing the C side. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-8-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/acpi.rs | 4 --- rust/kernel/auxiliary.rs | 8 ++--- rust/kernel/device_id.rs | 88 +++++++++++++++++++++++++++++------------------- rust/kernel/driver.rs | 14 ++++---- rust/kernel/i2c.rs | 7 ++-- rust/kernel/of.rs | 4 --- rust/kernel/pci.rs | 12 +++---- rust/kernel/usb.rs | 11 +++--- 8 files changed, 77 insertions(+), 71 deletions(-) diff --git a/rust/kernel/acpi.rs b/rust/kernel/acpi.rs index 315f2f2af446..ea2ce61ee393 100644 --- a/rust/kernel/acpi.rs +++ b/rust/kernel/acpi.rs @@ -25,10 +25,6 @@ unsafe impl RawDeviceId for DeviceId { // SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. unsafe impl RawDeviceIdIndex for DeviceId { const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::acpi_device_id, driver_data); - - fn index(&self) -> usize { - self.0.driver_data - } } impl DeviceId { diff --git a/rust/kernel/auxiliary.rs b/rust/kernel/auxiliary.rs index a5e93f18e0fc..03f0675ce202 100644 --- a/rust/kernel/auxiliary.rs +++ b/rust/kernel/auxiliary.rs @@ -94,7 +94,9 @@ impl Adapter { // SAFETY: `DeviceId` is a `#[repr(transparent)`] wrapper of `struct auxiliary_device_id` // and does not add additional invariants, so it's safe to transmute. let id = unsafe { &*id.cast::() }; - let info = T::ID_TABLE.info(id.index()); + + // SAFETY: `id` comes from `T::ID_TABLE` which is of type `IdArray<_, T::IdInfo>`. + let info = unsafe { id.info_unchecked::() }; from_result(|| { let data = T::probe(adev, info); @@ -170,10 +172,6 @@ unsafe impl RawDeviceId for DeviceId { unsafe impl RawDeviceIdIndex for DeviceId { const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::auxiliary_device_id, driver_data); - - fn index(&self) -> usize { - self.0.driver_data - } } /// IdTable type for auxiliary drivers. diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs index 022f0101871f..50d82bfca9b8 100644 --- a/rust/kernel/device_id.rs +++ b/rust/kernel/device_id.rs @@ -5,7 +5,10 @@ //! Each bus / subsystem that matches device and driver through a bus / subsystem specific ID is //! expected to implement [`RawDeviceId`]. -use core::mem::MaybeUninit; +use core::{ + marker::PhantomData, + mem::MaybeUninit, // +}; /// Marker trait to indicate a Rust device ID type represents a corresponding C device ID type. /// @@ -47,15 +50,48 @@ pub unsafe trait RawDeviceIdIndex: RawDeviceId { /// The offset (in bytes) to the context/data field in the raw device ID. const DRIVER_DATA_OFFSET: usize; - /// The index stored at `DRIVER_DATA_OFFSET` of the implementor of the [`RawDeviceIdIndex`] - /// trait. - fn index(&self) -> usize; + /// Obtain the data pointer stored inside the device ID. + /// + /// # Safety + /// + /// `&Self` must be stored inside a `IdArray`. + unsafe fn info_unchecked(&self) -> &'static U { + // SAFETY: By safety requirement of the trait, this is `self.driver_data as *const U` and by + // the safety requirement of the function, this is stored in `IdArray` so is + // convertible to `&'static U`. + unsafe { + core::ptr::from_ref(self) + .byte_add(Self::DRIVER_DATA_OFFSET) + .cast::<&U>() + .read() + } + } + + /// Obtain the data pointer stored inside the device ID. + /// + /// # Safety + /// + /// `&Self` must be stored inside a `IdArray`, or has NULL (or 0) as driver data. + unsafe fn info_unchecked_opt(&self) -> Option<&'static U> { + // SAFETY: By safety requirement of the trait, this is `self.driver_data as *const U` and by + // the safety requirement of the function, if this is stored in `IdArray`, this is + // convertible to `Option<&'static U>`. Otherwise it is NULL which is `None` as + // `Option<&U>`. + unsafe { + core::ptr::from_ref(self) + .byte_add(Self::DRIVER_DATA_OFFSET) + .cast::>() + .read() + } + } } /// A zero-terminated device id array. #[repr(C)] pub struct RawIdArray { - ids: [T::RawType; N], + // This is `MaybeUninit` so any bytes inside it can carry provenance in CTFE. + // If this were `T::RawType`, integer fields would not be able to contain pointers. + ids: [MaybeUninit; N], sentinel: MaybeUninit, } @@ -68,18 +104,17 @@ impl RawIdArray { /// A zero-terminated device id array, followed by context data. #[repr(C)] -pub struct IdArray { +pub struct IdArray { raw_ids: RawIdArray, - id_infos: [U; N], + phantom: PhantomData<&'static U>, } -impl IdArray { +impl IdArray { /// Creates a new instance of the array. /// /// The contents are derived from the given identifiers and context information. - pub const fn new(ids: [(T, U); N]) -> Self { + pub const fn new(ids: [(T, &'static U); N]) -> Self { let mut raw_ids = [const { MaybeUninit::::uninit() }; N]; - let mut infos = [const { MaybeUninit::uninit() }; N]; let mut i = 0usize; while i < N { @@ -87,18 +122,15 @@ impl IdArray { // layout-wise compatible with `RawType`. raw_ids[i] = unsafe { core::mem::transmute_copy(&ids[i].0) }; // SAFETY: by the safety requirement of `RawDeviceIdIndex`, this would be effectively - // `raw_ids[i].driver_data = i;`. + // `raw_ids[i].driver_data = ids[i].1;`. unsafe { raw_ids[i] .as_mut_ptr() .byte_add(T::DRIVER_DATA_OFFSET) - .cast::() - .write(i); + .cast::<&U>() + .write(ids[i].1); } - // SAFETY: this is effectively a move: `infos[i] = ids[i].1`. We make a copy here but - // later forget `ids`. - infos[i] = MaybeUninit::new(unsafe { core::ptr::read(&ids[i].1) }); i += 1; } @@ -106,20 +138,15 @@ impl IdArray { Self { raw_ids: RawIdArray { - // SAFETY: this is effectively `array_assume_init`, which is unstable, so we use - // `transmute_copy` instead. We have initialized all elements of `raw_ids` so this - // `array_assume_init` is safe. - ids: unsafe { core::mem::transmute_copy(&raw_ids) }, + ids: raw_ids, sentinel: MaybeUninit::zeroed(), }, - // SAFETY: We have initialized all elements of `infos` so this `array_assume_init` is - // safe. - id_infos: unsafe { core::mem::transmute_copy(&infos) }, + phantom: PhantomData, } } } -impl IdArray { +impl IdArray { /// Reference to the contained [`RawIdArray`]. pub const fn raw_ids(&self) -> &RawIdArray { &self.raw_ids @@ -133,7 +160,7 @@ impl IdArray { /// If the device implements [`RawDeviceIdIndex`], consider using [`IdArray::new`] instead. pub const fn new_without_index(ids: [T; N]) -> Self { // SAFETY: `T` is layout-wise compatible with `T::RawType`, so is the array of them. - let raw_ids: [T::RawType; N] = unsafe { core::mem::transmute_copy(&ids) }; + let raw_ids: [MaybeUninit; N] = unsafe { core::mem::transmute_copy(&ids) }; core::mem::forget(ids); Self { @@ -141,7 +168,7 @@ impl IdArray { ids: raw_ids, sentinel: MaybeUninit::zeroed(), }, - id_infos: [(); N], + phantom: PhantomData, } } } @@ -155,9 +182,6 @@ impl IdArray { pub trait IdTable { /// Obtain the pointer to the ID table. fn as_ptr(&self) -> *const T::RawType; - - /// Obtain the pointer to the driver-specific information from an index. - fn info(&self, index: usize) -> &U; } impl IdTable for IdArray { @@ -166,10 +190,6 @@ impl IdTable for IdArray { // to access the sentinel. core::ptr::from_ref(self).cast() } - - fn info(&self, index: usize) -> &U { - &self.id_infos[index] - } } /// Create device table alias for modpost. @@ -184,7 +204,7 @@ macro_rules! module_device_table { $device_id_ty, $id_info_type, { <[$device_id_ty]>::len(&[$($id,)*]) }, - > = $crate::device_id::IdArray::new([$(($id, $info),)*]); + > = $crate::device_id::IdArray::new([$(($id, &$info),)*]); $crate::module_device_table!($table_type, $table_name); }; diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs index bf5ba0d27553..824899d76fed 100644 --- a/rust/kernel/driver.rs +++ b/rust/kernel/driver.rs @@ -107,6 +107,7 @@ use crate::{ acpi, device, + device_id::RawDeviceIdIndex, of, prelude::*, types::Opaque, @@ -350,7 +351,8 @@ pub trait Adapter { // and does not add additional invariants, so it's safe to transmute. let id = unsafe { &*raw_id.cast::() }; - Some(table.info(::index(id))) + // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. + Some(unsafe { id.info_unchecked::() }) } } } @@ -381,9 +383,8 @@ pub trait Adapter { // and does not add additional invariants, so it's safe to transmute. let id = unsafe { &*raw_id.cast::() }; - return Some(table.info( - ::index(id), - )); + // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. + return Some(unsafe { id.info_unchecked::() }); } } @@ -412,9 +413,8 @@ pub trait Adapter { // and does not add additional invariants, so it's safe to transmute. let id = unsafe { &*raw_id.cast::() }; - return Some(table.info( - ::index(id), - )); + // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. + return Some(unsafe { id.info_unchecked::() }); } } diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index 6f2dcd467e72..31f2cfe6be31 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -65,10 +65,6 @@ unsafe impl RawDeviceId for DeviceId { // SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. unsafe impl RawDeviceIdIndex for DeviceId { const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::i2c_device_id, driver_data); - - fn index(&self) -> usize { - self.0.driver_data - } } /// IdTable type for I2C @@ -212,7 +208,8 @@ impl Adapter { // does not add additional invariants, so it's safe to transmute. let id = unsafe { &*raw_id.cast::() }; - Some(table.info(::index(id))) + // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. + Some(unsafe { id.info_unchecked::() }) } } diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index 35aa6d36d309..d0318f62afd7 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -25,10 +25,6 @@ unsafe impl RawDeviceId for DeviceId { // SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `data` field. unsafe impl RawDeviceIdIndex for DeviceId { const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::of_device_id, data); - - fn index(&self) -> usize { - self.0.data as usize - } } impl DeviceId { diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index b170cf8fffe1..cb83e2831b9c 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -110,10 +110,14 @@ impl Adapter { // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct pci_device_id` and // does not add additional invariants, so it's safe to transmute. let id = unsafe { &*id.cast::() }; - let info = T::ID_TABLE.info(id.index()); + + // SAFETY: `id` comes from `T::ID_TABLE` which is of type `IdArray<_, T::IdInfo>` or + // `pci_device_id_any` which has 0 as driver_data. It can also come from dynamic IDs, which + // will ensure that `driver_data` exists in `T::ID_TABLE`. + let info = unsafe { id.info_unchecked_opt::() }; from_result(|| { - let data = T::probe(pdev, Some(info)); + let data = T::probe(pdev, info); pdev.as_ref().set_drvdata(data)?; Ok(0) @@ -233,10 +237,6 @@ unsafe impl RawDeviceId for DeviceId { // SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field. unsafe impl RawDeviceIdIndex for DeviceId { const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::pci_device_id, driver_data); - - fn index(&self) -> usize { - self.0.driver_data - } } /// `IdTable` type for PCI. diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs index d8cffbe594ff..aec7dce04955 100644 --- a/rust/kernel/usb.rs +++ b/rust/kernel/usb.rs @@ -89,8 +89,11 @@ impl Adapter { // does not add additional invariants, so it's safe to transmute. let id = unsafe { &*id.cast::() }; - let info = T::ID_TABLE.info(id.index()); - let data = T::probe(intf, id, Some(info)); + // SAFETY: `id` comes from `T::ID_TABLE` which is of type `IdArray<_, T::IdInfo>`. It + // can also come from dynamic IDs, which will ensure that `driver_data` exists in + // `T::ID_TABLE` or is 0. + let info = unsafe { id.info_unchecked_opt::() }; + let data = T::probe(intf, id, info); let dev: &device::Device> = intf.as_ref(); dev.set_drvdata(data)?; @@ -242,10 +245,6 @@ unsafe impl RawDeviceId for DeviceId { // SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_info` field. unsafe impl RawDeviceIdIndex for DeviceId { const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::usb_device_id, driver_info); - - fn index(&self) -> usize { - self.0.driver_info - } } /// [`IdTable`](kernel::device_id::IdTable) type for USB. -- cgit From 2c5d17cdc4e9783e3e95d4f71cf4d3e73d113d4e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:43 +0100 Subject: rust: driver: remove open-coded matching logic With device ID info now including pointers instead of indices, the open-coded ACPI/OF matching is no longer needed and can be replaced with `device_get_match_data`. With the removal of open-coded matching, the exposed functions and helpers are also removed; this effectively reverts most of commit 2690d071584e ("rust: ACPI: fix missing match data for PRP0001"). Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-9-56fccbe9c5ef@garyguo.net [ Consider the serdev code merged in the meantime. - Danilo ] Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 - drivers/acpi/bus.c | 6 +-- include/acpi/acpi_bus.h | 11 ---- rust/helpers/acpi.c | 16 ------ rust/helpers/helpers.c | 1 - rust/kernel/driver.rs | 137 ++++-------------------------------------------- rust/kernel/i2c.rs | 6 ++- rust/kernel/platform.rs | 3 +- rust/kernel/serdev.rs | 3 +- 9 files changed, 22 insertions(+), 162 deletions(-) delete mode 100644 rust/helpers/acpi.c diff --git a/MAINTAINERS b/MAINTAINERS index 8034273de98b..857dbe75a099 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -297,7 +297,6 @@ F: include/linux/acpi.h F: include/linux/fwnode.h F: include/linux/fw_table.h F: lib/fw_table.c -F: rust/helpers/acpi.c F: rust/kernel/acpi.rs F: tools/power/acpi/ diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index a30a904f6535..1fb0b10bf924 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -898,9 +898,9 @@ const struct acpi_device *acpi_companion_match(const struct device *dev) * identifiers and a _DSD object with the "compatible" property, use that * property to match against the given list of identifiers. */ -bool acpi_of_match_device(const struct acpi_device *adev, - const struct of_device_id *of_match_table, - const struct of_device_id **of_id) +static bool acpi_of_match_device(const struct acpi_device *adev, + const struct of_device_id *of_match_table, + const struct of_device_id **of_id) { const union acpi_object *of_compatible, *obj; int i, nval; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 714d111d8053..7e57f9698f7c 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -189,10 +189,6 @@ struct acpi_driver { * ----------- */ -bool acpi_of_match_device(const struct acpi_device *adev, - const struct of_device_id *of_match_table, - const struct of_device_id **of_id); - /* Status (_STA) */ struct acpi_device_status { @@ -1000,13 +996,6 @@ int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices); u32 arch_acpi_add_auto_dep(acpi_handle handle); #else /* CONFIG_ACPI */ -static inline bool acpi_of_match_device(const struct acpi_device *adev, - const struct of_device_id *of_match_table, - const struct of_device_id **of_id) -{ - return false; -} - static inline int register_acpi_bus_type(void *bus) { return 0; } static inline int unregister_acpi_bus_type(void *bus) { return 0; } diff --git a/rust/helpers/acpi.c b/rust/helpers/acpi.c deleted file mode 100644 index e75c9807bbad..000000000000 --- a/rust/helpers/acpi.c +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include - -__rust_helper bool rust_helper_acpi_of_match_device(const struct acpi_device *adev, - const struct of_device_id *of_match_table, - const struct of_device_id **of_id) -{ - return acpi_of_match_device(adev, of_match_table, of_id); -} - -__rust_helper struct acpi_device *rust_helper_to_acpi_device_node(struct fwnode_handle *fwnode) -{ - return to_acpi_device_node(fwnode); -} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 6921a5ecb78a..85cc332ed446 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -38,7 +38,6 @@ #define __rust_helper __always_inline #endif -#include "acpi.c" #include "atomic.c" #include "atomic_ext.c" #include "auxiliary.c" diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs index 824899d76fed..c9c74c4dde8f 100644 --- a/rust/kernel/driver.rs +++ b/rust/kernel/driver.rs @@ -107,7 +107,6 @@ use crate::{ acpi, device, - device_id::RawDeviceIdIndex, of, prelude::*, types::Opaque, @@ -292,26 +291,6 @@ macro_rules! module_driver { } } -// Calling the FFI function directly from the `Adapter` impl may result in it being called -// directly from driver modules. This happens since the Rust compiler will use monomorphisation, so -// it might happen that functions are instantiated within the calling driver module. For now, work -// around this with `#[inline(never)]` helpers. -// -// TODO: Remove once a more generic solution has been implemented. For instance, we may be able to -// leverage `bindgen` to take care of this depending on whether a symbol is (already) exported. -#[inline(never)] -#[allow(clippy::missing_safety_doc)] -#[allow(dead_code)] -#[must_use] -unsafe fn acpi_of_match_device( - adev: *const bindings::acpi_device, - of_match_table: *const bindings::of_device_id, - of_id: *mut *const bindings::of_device_id, -) -> bool { - // SAFETY: Safety requirements are the same as `bindings::acpi_of_match_device`. - unsafe { bindings::acpi_of_match_device(adev, of_match_table, of_id) } -} - /// The bus independent adapter to match a drivers and a devices. /// /// This trait should be implemented by the bus specific adapter, which represents the connection @@ -325,117 +304,23 @@ pub trait Adapter { /// The [`acpi::IdTable`] of the corresponding driver fn acpi_id_table() -> Option>; - /// Returns the driver's private data from the matching entry in the [`acpi::IdTable`], if any. - /// - /// If this returns `None`, it means there is no match with an entry in the [`acpi::IdTable`]. - fn acpi_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> { - #[cfg(not(CONFIG_ACPI))] - { - let _ = dev; - None - } - - #[cfg(CONFIG_ACPI)] - { - let table = Self::acpi_id_table()?; - - // SAFETY: - // - `table` has static lifetime, hence it's valid for read, - // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`. - let raw_id = unsafe { bindings::acpi_match_device(table.as_ptr(), dev.as_raw()) }; - - if raw_id.is_null() { - None - } else { - // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct acpi_device_id` - // and does not add additional invariants, so it's safe to transmute. - let id = unsafe { &*raw_id.cast::() }; - - // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. - Some(unsafe { id.info_unchecked::() }) - } - } - } - /// The [`of::IdTable`] of the corresponding driver. fn of_id_table() -> Option>; - /// Returns the driver's private data from the matching entry in the [`of::IdTable`], if any. - /// - /// If this returns `None`, it means there is no match with an entry in the [`of::IdTable`]. - fn of_id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> { - let table = Self::of_id_table()?; - - #[cfg(not(any(CONFIG_OF, CONFIG_ACPI)))] - { - let _ = (dev, table); - } - - #[cfg(CONFIG_OF)] - { - // SAFETY: - // - `table` has static lifetime, hence it's valid for read, - // - `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`. - let raw_id = unsafe { bindings::of_match_device(table.as_ptr(), dev.as_raw()) }; - - if !raw_id.is_null() { - // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct of_device_id` - // and does not add additional invariants, so it's safe to transmute. - let id = unsafe { &*raw_id.cast::() }; - - // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. - return Some(unsafe { id.info_unchecked::() }); - } - } - - #[cfg(CONFIG_ACPI)] - { - use core::ptr; - use device::property::FwNode; - - let mut raw_id = ptr::null(); - - let fwnode = dev.fwnode().map_or(ptr::null_mut(), FwNode::as_raw); - - // SAFETY: `fwnode` is a pointer to a valid `fwnode_handle`. A null pointer will be - // passed through the function. - let adev = unsafe { bindings::to_acpi_device_node(fwnode) }; - - // SAFETY: - // - `adev` is a valid pointer to `acpi_device` or is null. It is guaranteed to be - // valid as long as `dev` is alive. - // - `table` has static lifetime, hence it's valid for read. - if unsafe { acpi_of_match_device(adev, table.as_ptr(), &raw mut raw_id) } { - // SAFETY: - // - the function returns true, therefore `raw_id` has been set to a pointer to a - // valid `of_device_id`. - // - `DeviceId` is a `#[repr(transparent)]` wrapper of `struct of_device_id` - // and does not add additional invariants, so it's safe to transmute. - let id = unsafe { &*raw_id.cast::() }; - - // SAFETY: `id` comes from `table` which is of type `IdArray<_, Self::IdInfo>`. - return Some(unsafe { id.info_unchecked::() }); - } - } - - None - } - /// Returns the driver's private data from the matching entry of any of the ID tables, if any. /// /// If this returns `None`, it means that there is no match in any of the ID tables directly /// associated with a [`device::Device`]. - fn id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> { - let id = Self::acpi_id_info(dev); - if id.is_some() { - return id; - } - - let id = Self::of_id_info(dev); - if id.is_some() { - return id; - } - - None + /// + /// # Safety + /// + /// The caller must ensure that the `dev` matched data is of type `Self::IdInfo`. + #[inline] + unsafe fn id_info(dev: &device::Device) -> Option<&'static Self::IdInfo> { + // SAFETY: `dev` is guaranteed to be valid while it's alive, and so is `dev.as_raw()`. + let data = unsafe { bindings::device_get_match_data(dev.as_raw()) }; + + // SAFETY: Per safety requirement, `data` is of type `Self::IdInfo`. + unsafe { data.cast::().as_ref() } } } diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs index 31f2cfe6be31..2d2191164e25 100644 --- a/rust/kernel/i2c.rs +++ b/rust/kernel/i2c.rs @@ -149,8 +149,10 @@ impl Adapter { // INVARIANT: `idev` is valid for the duration of `probe_callback()`. let idev = unsafe { &*idev.cast::>>() }; - let info = - Self::i2c_id_info(idev).or_else(|| ::id_info(idev.as_ref())); + let info = Self::i2c_id_info(idev).or_else(|| { + // SAFETY: `idev` matched data is of type `Self::IdInfo`. + unsafe { ::id_info(idev.as_ref()) } + }); from_result(|| { let data = T::probe(idev, info); diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index 210a815925ce..e12e88113ca5 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -100,7 +100,8 @@ impl Adapter { // // INVARIANT: `pdev` is valid for the duration of `probe_callback()`. let pdev = unsafe { &*pdev.cast::>>() }; - let info = ::id_info(pdev.as_ref()); + // SAFETY: `pdev` matched data is of type `Self::IdInfo`. + let info = unsafe { ::id_info(pdev.as_ref()) }; from_result(|| { let data = T::probe(pdev, info); diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs index 383f62b1b713..c0cd24481ee3 100644 --- a/rust/kernel/serdev.rs +++ b/rust/kernel/serdev.rs @@ -162,7 +162,8 @@ impl Adapter { // // INVARIANT: `sdev` is valid for the duration of `probe_callback()`. let sdev = unsafe { &*sdev.cast::>>() }; - let info = ::id_info(sdev.as_ref()); + // SAFETY: `sdev` matched data is of type `Self::IdInfo`. + let info = unsafe { ::id_info(sdev.as_ref()) }; from_result(|| { sdev.as_ref().set_drvdata(try_pin_init!(PrivateData:: { -- cgit From 17fcdecb14b463863890d3101457f6256f90e17a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:44 +0100 Subject: rust: driver: remove duplicate ID table Previously, `IdArray` contains both device ID table and info table so we keep a separate copy for MODULE_DEVICE_TABLE for hotplug (which needs to be just the device ID table). With the info being changed to be carried via pointers, `IdArray` is now layout compatible with raw ID table and hence there is no longer a need to keep the distinction. Deduplicate the code, and remove the redundant copy for hotplug purpose by just giving the `IdArray` instance a proper symbol name. While at it, also update the macro to use `::core::line!()` instead of just `line!()`. Signed-off-by: Gary Guo Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260629-id_info-v2-10-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/device_id.rs | 76 +++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/rust/kernel/device_id.rs b/rust/kernel/device_id.rs index 50d82bfca9b8..c81fca5b4986 100644 --- a/rust/kernel/device_id.rs +++ b/rust/kernel/device_id.rs @@ -86,28 +86,23 @@ pub unsafe trait RawDeviceIdIndex: RawDeviceId { } } -/// A zero-terminated device id array. +/// A zero-terminated device id array, followed by context data. #[repr(C)] -pub struct RawIdArray { +pub struct IdArray { // This is `MaybeUninit` so any bytes inside it can carry provenance in CTFE. // If this were `T::RawType`, integer fields would not be able to contain pointers. ids: [MaybeUninit; N], sentinel: MaybeUninit, + phantom: PhantomData<&'static U>, } -impl RawIdArray { - #[doc(hidden)] - pub const fn size(&self) -> usize { - core::mem::size_of::() - } -} +// SAFETY: device ID is plain data plus a `&'static U` and can thus be sent between threads safely +// if `&U` can. +unsafe impl Send for IdArray {} -/// A zero-terminated device id array, followed by context data. -#[repr(C)] -pub struct IdArray { - raw_ids: RawIdArray, - phantom: PhantomData<&'static U>, -} +// SAFETY: device ID is plain data plus a `&'static U` and can thus be shared between threads safely +// if `&U` can. +unsafe impl Sync for IdArray {} impl IdArray { /// Creates a new instance of the array. @@ -137,22 +132,13 @@ impl IdArray IdArray { - /// Reference to the contained [`RawIdArray`]. - pub const fn raw_ids(&self) -> &RawIdArray { - &self.raw_ids - } -} - impl IdArray { /// Creates a new instance of the array without writing index values. /// @@ -164,10 +150,8 @@ impl IdArray { core::mem::forget(ids); Self { - raw_ids: RawIdArray { - ids: raw_ids, - sentinel: MaybeUninit::zeroed(), - }, + ids: raw_ids, + sentinel: MaybeUninit::zeroed(), phantom: PhantomData, } } @@ -200,13 +184,17 @@ macro_rules! module_device_table { $table_name: ident, $id_info_type: ty, [$(($id: expr, $info:expr $(,)?)),* $(,)?] ) => { - const $table_name: $crate::device_id::IdArray< + #[export_name = + concat!("__mod_device_table__", ::core::line!(), + "__kmod_", module_path!(), + "__", $table_type, + "__", stringify!($table_name)) + ] + static $table_name: $crate::device_id::IdArray< $device_id_ty, $id_info_type, { <[$device_id_ty]>::len(&[$($id,)*]) }, > = $crate::device_id::IdArray::new([$(($id, &$info),)*]); - - $crate::module_device_table!($table_type, $table_name); }; // Case for no ID info. @@ -215,26 +203,16 @@ macro_rules! module_device_table { $table_name: ident, @none, [$($id: expr),* $(,)?] ) => { - const $table_name: $crate::device_id::IdArray< + #[export_name = + concat!("__mod_device_table__", ::core::line!(), + "__kmod_", module_path!(), + "__", $table_type, + "__", stringify!($table_name)) + ] + static $table_name: $crate::device_id::IdArray< $device_id_ty, (), { <[$device_id_ty]>::len(&[$($id,)*]) }, > = $crate::device_id::IdArray::new_without_index([$($id),*]); - - $crate::module_device_table!($table_type, $table_name); - }; - - ($table_type: literal, $table_name:ident) => { - const _: () = { - #[rustfmt::skip] - #[export_name = - concat!("__mod_device_table__", line!(), - "__kmod_", module_path!(), - "__", $table_type, - "__", stringify!($table_name)) - ] - static MOD_DEVICE_TABLE: [::core::mem::MaybeUninit; $table_name.raw_ids().size()] = - unsafe { ::core::mem::transmute_copy($table_name.raw_ids()) }; - }; }; } -- cgit From 6e33dee41d88411be38bcd58f8e85f233721476e Mon Sep 17 00:00:00 2001 From: Josef Ippisch Date: Tue, 28 Jul 2026 19:31:19 +0200 Subject: rust: debugfs: migrate debugfs traits requirements to zerocopy Migrate `BinaryWriter` and `BinaryReaderMut`'s default implementation's requirements on T from `kernel::transmute` traits to `zerocopy` traits. The additional `zerocopy::Immutable` requirement on `BinaryWriter` does not further restrict the types in practice but is rather a more explicit requirement (that the type does not have interior mutability) and is required by zerocopy for the `as_bytes()` function. Suggested-by: Joshua Liebow-Feeser Suggested-by: Miguel Ojeda Link: https://github.com/Rust-for-Linux/linux/issues/975 Link: https://github.com/Rust-for-Linux/linux/issues/1241 Signed-off-by: Josef Ippisch Link: https://patch.msgid.link/20260728-migrate-binarywriter-to-zerocopy-intobytes-v2-1-0a4ec1d3ead4@mailbox.org Signed-off-by: Greg Kroah-Hartman --- rust/kernel/debugfs/traits.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index 8c39524b6a99..b794f7dd7e14 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -18,10 +18,6 @@ use crate::{ Arc, Mutex, // }, - transmute::{ - AsBytes, - FromBytes, // - }, uaccess::{ UserSliceReader, UserSliceWriter, // @@ -36,6 +32,8 @@ use core::{ str::FromStr, }; +use zerocopy::Immutable; + /// A trait for types that can be written into a string. /// /// This works very similarly to `Debug`, and is automatically implemented if `Debug` is @@ -76,8 +74,8 @@ pub trait BinaryWriter { ) -> Result; } -// Base implementation for any `T: AsBytes`. -impl BinaryWriter for T { +// Base implementation for any `T: Immutable + IntoBytes`. +impl BinaryWriter for T { fn write_to_slice( &self, writer: &mut UserSliceWriter, @@ -147,7 +145,7 @@ where // Delegate for `Vec`. impl BinaryWriter for Vec where - T: AsBytes, + T: Immutable + IntoBytes, A: Allocator, { fn write_to_slice( @@ -157,7 +155,7 @@ where ) -> Result { let slice = self.as_slice(); - // SAFETY: `T: AsBytes` allows us to treat `&[T]` as `&[u8]`. + // SAFETY: `T: Immutable + IntoBytes` allows us to treat `&[T]` as `&[u8]`. let buffer = unsafe { core::slice::from_raw_parts(slice.as_ptr().cast(), core::mem::size_of_val(slice)) }; @@ -230,14 +228,14 @@ pub trait BinaryReaderMut { ) -> Result; } -// Base implementation for any `T: AsBytes + FromBytes`. -impl BinaryReaderMut for T { +// Base implementation for any `T: FromBytes + IntoBytes`. +impl BinaryReaderMut for T { fn read_from_slice_mut( &mut self, reader: &mut UserSliceReader, offset: &mut file::Offset, ) -> Result { - reader.read_slice_file(self.as_bytes_mut(), offset) + reader.read_slice_file(self.as_mut_bytes(), offset) } } @@ -255,7 +253,7 @@ impl BinaryReaderMut for Box { // Delegate for `Vec`: Support a `Vec` with an outer lock. impl BinaryReaderMut for Vec where - T: AsBytes + FromBytes, + T: FromBytes + IntoBytes, A: Allocator, { fn read_from_slice_mut( @@ -265,7 +263,7 @@ where ) -> Result { let slice = self.as_mut_slice(); - // SAFETY: `T: AsBytes + FromBytes` allows us to treat `&mut [T]` as `&mut [u8]`. + // SAFETY: `T: FromBytes + IntoBytes` allow us to treat `&mut [T]` as `&mut [u8]`. let buffer = unsafe { core::slice::from_raw_parts_mut( slice.as_mut_ptr().cast(), -- cgit From 68ac45aabe603f330f3b9776d6e5ddf573b1fdea Mon Sep 17 00:00:00 2001 From: Josef Ippisch Date: Tue, 28 Jul 2026 19:31:20 +0200 Subject: rust: debugfs: remove unsafe blocks from traits impl for Vec The previous implementation used an `unsafe` block to manually cast Vec's slice to a &[u8] using `core::slice::from_raw_parts`. Instead, the implementation can be implemented in safe rust using zerocopy's trait functions `as_bytes()` and `as_mut_bytes()`, respectively, and making use of deref coercion to implicitly cast Vec to &[T] as `FromBytes` and `IntoBytes` automatically are implemented on [T] when they are implemented on T. Signed-off-by: Josef Ippisch Link: https://patch.msgid.link/20260728-migrate-binarywriter-to-zerocopy-intobytes-v2-2-0a4ec1d3ead4@mailbox.org Signed-off-by: Greg Kroah-Hartman --- rust/kernel/debugfs/traits.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index b794f7dd7e14..b295f8420abd 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -153,14 +153,7 @@ where writer: &mut UserSliceWriter, offset: &mut file::Offset, ) -> Result { - let slice = self.as_slice(); - - // SAFETY: `T: Immutable + IntoBytes` allows us to treat `&[T]` as `&[u8]`. - let buffer = unsafe { - core::slice::from_raw_parts(slice.as_ptr().cast(), core::mem::size_of_val(slice)) - }; - - writer.write_slice_file(buffer, offset) + writer.write_slice_file(self.as_bytes(), offset) } } @@ -261,17 +254,7 @@ where reader: &mut UserSliceReader, offset: &mut file::Offset, ) -> Result { - let slice = self.as_mut_slice(); - - // SAFETY: `T: FromBytes + IntoBytes` allow us to treat `&mut [T]` as `&mut [u8]`. - let buffer = unsafe { - core::slice::from_raw_parts_mut( - slice.as_mut_ptr().cast(), - core::mem::size_of_val(slice), - ) - }; - - reader.read_slice_file(buffer, offset) + reader.read_slice_file(self.as_mut_bytes(), offset) } } -- cgit From f2687db25dd52319cb06ef4aa8cc2a13fa0203fa Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Mon, 27 Jul 2026 22:45:36 +0200 Subject: MAINTAINERS: add sys_soc.h to DRIVER CORE The sys_soc.h header was never added to the DRIVER CORE MAINTAINERS entry when the SoC bus was introduced. Add it now. Fixes: 74d1d82cdaae ("drivers/base: add bus for System-on-Chip devices") Signed-off-by: Danilo Krummrich Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260727204543.2286472-1-dakr@kernel.org Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 857dbe75a099..f672858996f0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7901,6 +7901,7 @@ F: include/linux/fwnode.h F: include/linux/kobj* F: include/linux/ksysfs.h F: include/linux/property.h +F: include/linux/sys_soc.h F: include/linux/sysfs.h F: kernel/ksysfs.c F: lib/kobj* -- cgit From ebb314763f975af722a9f3c86752cdf82b61c78a Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Fri, 24 Jul 2026 20:37:04 +0900 Subject: rust: io: register: dispatch shortcut rules internally A couple of shortcut rules redispatch an already normalized declaration through the public register! entry point. This is unneeded - the public rule should only be invoked by users. Dispatch directly to the appropriate internal @reg rule instead. Signed-off-by: Alexandre Courbot Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260724-registers_fix-v2-1-a0fb58b02185@nvidia.com Signed-off-by: Danilo Krummrich --- rust/kernel/io/register.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index 6cb07fc92cc3..9aa17aa520aa 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -888,7 +888,8 @@ macro_rules! register { { $($fields:tt)* } ) => { $crate::register!( - $(#[$attr])* $vis $name($storage) [ $size, stride = ::core::mem::size_of::<$storage>() ] + @reg $(#[$attr])* $vis $name($storage) + [ $size, stride = ::core::mem::size_of::<$storage>() ] @ $offset { $($fields)* } ); }; @@ -932,7 +933,8 @@ macro_rules! register { @ $base:ident + $offset:literal { $($fields:tt)* } ) => { $crate::register!( - $(#[$attr])* $vis $name($storage) [ $size, stride = ::core::mem::size_of::<$storage>() ] + @reg $(#[$attr])* $vis $name($storage) + [ $size, stride = ::core::mem::size_of::<$storage>() ] @ $base + $offset { $($fields)* } ); }; -- cgit From 78e9b438923165e13340031761d5e08b52d3b0c8 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Fri, 24 Jul 2026 20:37:05 +0900 Subject: rust: io: register: remove unused rule arguments A few arguments passed to internal rules are never used and just add unneeded complexity. Remove them to simplify the rules a bit. Signed-off-by: Alexandre Courbot Link: https://patch.msgid.link/20260724-registers_fix-v2-2-a0fb58b02185@nvidia.com Signed-off-by: Danilo Krummrich --- rust/kernel/io/register.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index 9aa17aa520aa..59908db011cd 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -832,7 +832,7 @@ macro_rules! register { ) => { $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); $crate::register!(@io_base $name($storage) @ $offset); - $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); + $crate::register!(@io_fixed $(#[$attr])* $vis $name); }; // Creates an alias register of fixed offset register `alias` with its own fields. @@ -845,7 +845,7 @@ macro_rules! register { @io_base $name($storage) @ <$alias as $crate::io::register::Register>::OFFSET ); - $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); + $crate::register!(@io_fixed $(#[$attr])* $vis $name); }; // Creates a register at a relative offset from a base address provider. @@ -855,7 +855,7 @@ macro_rules! register { ) => { $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); $crate::register!(@io_base $name($storage) @ $offset); - $crate::register!(@io_relative $vis $name($storage) @ $base); + $crate::register!(@io_relative $name @ $base); }; // Creates an alias register of relative offset register `alias` with its own fields. @@ -867,7 +867,7 @@ macro_rules! register { $crate::register!( @io_base $name($storage) @ <$alias as $crate::io::register::Register>::OFFSET ); - $crate::register!(@io_relative $vis $name($storage) @ $base); + $crate::register!(@io_relative $name @ $base); }; // Creates an array of registers at a fixed offset of the MMIO space. @@ -879,7 +879,7 @@ macro_rules! register { $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); $crate::register!(@io_base $name($storage) @ $offset); - $crate::register!(@io_array $vis $name($storage) [ $size, stride = $stride ]); + $crate::register!(@io_array $name [ $size, stride = $stride ]); }; // Shortcut for contiguous array of registers (stride == size of element). @@ -909,7 +909,7 @@ macro_rules! register { <$alias as $crate::io::register::Register>::OFFSET + $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE ); - $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); + $crate::register!(@io_fixed $(#[$attr])* $vis $name); }; // Creates an array of registers at a relative offset from a base address provider. @@ -922,9 +922,7 @@ macro_rules! register { $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); $crate::register!(@io_base $name($storage) @ $offset); - $crate::register!( - @io_relative_array $vis $name($storage) [ $size, stride = $stride ] @ $base + $offset - ); + $crate::register!(@io_relative_array $name [ $size, stride = $stride ] @ $base); }; // Shortcut for contiguous array of relative registers (stride == size of element). @@ -955,7 +953,7 @@ macro_rules! register { <$alias as $crate::io::register::Register>::OFFSET + $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE ); - $crate::register!(@io_relative $vis $name($storage) @ $base); + $crate::register!(@io_relative $name @ $base); }; // Generates the bitfield for the register. @@ -981,7 +979,7 @@ macro_rules! register { }; // Implementations of fixed registers. - (@io_fixed $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)) => { + (@io_fixed $(#[$attr:meta])* $vis:vis $name:ident) => { impl $crate::io::register::FixedRegister for $name {} $(#[$attr])* @@ -990,7 +988,7 @@ macro_rules! register { }; // Implementations of relative registers. - (@io_relative $vis:vis $name:ident ($storage:ty) @ $base:ident) => { + (@io_relative $name:ident @ $base:ident) => { impl $crate::io::register::WithBase for $name { type BaseFamily = $base; } @@ -999,7 +997,7 @@ macro_rules! register { }; // Implementations of register arrays. - (@io_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ]) => { + (@io_array $name:ident [ $size:expr, stride = $stride:expr ]) => { impl $crate::io::register::Array for $name {} impl $crate::io::register::RegisterArray for $name { @@ -1010,8 +1008,7 @@ macro_rules! register { // Implementations of relative array registers. ( - @io_relative_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ] - @ $base:ident + $offset:literal + @io_relative_array $name:ident [ $size:expr, stride = $stride:expr ] @ $base:ident ) => { impl $crate::io::register::WithBase for $name { type BaseFamily = $base; -- cgit From 2230d38828506c1c84d39da03e2158e7fc0542fe Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Fri, 24 Jul 2026 20:37:06 +0900 Subject: rust: io: register: use path fragment for alias destination The destination of an alias is always another register, i.e. a `struct` type. Replace the `ident` fragment with a `path` one in the internal rules: `path` is more accurate, and allows referencing registers using a qualified path instead of only identifiers visible from the current module. This covers all aliases, except the relative register ones which are to be removed soon. The public rule cannot be updated yet because a `+` can still be matched after the alias; add a TODO item to update it after relative registers are removed. Signed-off-by: Alexandre Courbot Link: https://patch.msgid.link/20260724-registers_fix-v2-3-a0fb58b02185@nvidia.com Signed-off-by: Danilo Krummrich --- rust/kernel/io/register.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs index 59908db011cd..03dfd2ff48c7 100644 --- a/rust/kernel/io/register.rs +++ b/rust/kernel/io/register.rs @@ -804,6 +804,9 @@ macro_rules! register { // Entry point for the macro, allowing multiple registers to be defined in one call. // It matches all possible register declaration patterns to dispatch them to corresponding // `@reg` rule that defines a single register. + // + // TODO: change `alias:ident` to `alias:path` once relative registers are replaced by I/O + // projections. ( $( $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @@ -837,7 +840,7 @@ macro_rules! register { // Creates an alias register of fixed offset register `alias` with its own fields. ( - @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:path { $($fields:tt)* } ) => { $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); @@ -896,7 +899,7 @@ macro_rules! register { // Creates an alias of register `idx` of array of registers `alias` with its own fields. ( - @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ] + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:path [ $idx:expr ] { $($fields:tt)* } ) => { $crate::build_assert::static_assert!( -- cgit From 5391b147d9fd8d4736e28c629cda00fd37c2304a Mon Sep 17 00:00:00 2001 From: Younes Akhouayri Date: Thu, 30 Jul 2026 18:34:38 +0200 Subject: rust: dma: return zero for Coherent reads past EOF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coherent::write_to_slice() calculates a zero-byte copy when the file offset is beyond the allocation, but still calls UserSliceWriter::write_dma(). The latter rejects offsets beyond the allocation even when the copy length is zero, so a debugfs read past EOF returns -ERANGE. Return before calling write_dma() when the offset is at or beyond the allocation, matching simple_read_from_buffer() EOF semantics. Fixes: 016818513936 ("rust: dma: implement BinaryWriter for Coherent<[u8]>") Cc: stable@vger.kernel.org Link: https://rust-for-linux.zulipchat.com/#narrow/channel/291566-Library/topic/.E2.9C.94.20Possible.20past-EOF.20bug.20in.20Coherent.3CT.3E.3A.3Awrite_to_slice/near/611677095 Signed-off-by: Younes Akhouayri Reviewed-by: Alexandre Courbot Reviewed-by: Onur Özkan Link: https://patch.msgid.link/20260730-fix-dma-coherent-eof-v2-1-8aff21054afa@younes.io Signed-off-by: Danilo Krummrich --- rust/kernel/dma.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs index e275f2562a5b..e8f2bb3631d6 100644 --- a/rust/kernel/dma.rs +++ b/rust/kernel/dma.rs @@ -967,7 +967,11 @@ impl debugfs::BinaryWriter for Coherent { return Ok(0); }; - let count = self.size().saturating_sub(offset_val).min(writer.len()); + if offset_val >= self.size() { + return Ok(0); + } + + let count = (self.size() - offset_val).min(writer.len()); writer.write_dma(self, offset_val, count)?; -- cgit From 928369abdd9bf21da698b86d99a7eca2a74c9bed Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 29 Jun 2026 13:39:39 +0100 Subject: rust: net/phy: remove expansion from doc The expansion serves little purpose and it can easily diverge. Acked-by: FUJITA Tomonori Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260629-id_info-v2-5-56fccbe9c5ef@garyguo.net Signed-off-by: Danilo Krummrich --- rust/kernel/net/phy.rs | 56 -------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs index cc7ec436cfca..1e86b901c391 100644 --- a/rust/kernel/net/phy.rs +++ b/rust/kernel/net/phy.rs @@ -800,62 +800,6 @@ impl DeviceMask { /// } /// # } /// ``` -/// -/// This expands to the following code: -/// -/// ```ignore -/// use kernel::net::phy::{self, DeviceId}; -/// use kernel::prelude::*; -/// -/// struct Module { -/// _reg: ::kernel::net::phy::Registration, -/// } -/// -/// module! { -/// type: Module, -/// name: "rust_sample_phy", -/// authors: ["Rust for Linux Contributors"], -/// description: "Rust sample PHYs driver", -/// license: "GPL", -/// } -/// -/// struct PhySample; -/// -/// #[vtable] -/// impl phy::Driver for PhySample { -/// const NAME: &'static CStr = c"PhySample"; -/// const PHY_DEVICE_ID: phy::DeviceId = phy::DeviceId::new_with_exact_mask(0x00000001); -/// } -/// -/// const _: () = { -/// static mut DRIVERS: [::kernel::net::phy::DriverVTable; 1] = -/// [::kernel::net::phy::create_phy_driver::()]; -/// -/// impl ::kernel::Module for Module { -/// fn init(module: &'static ::kernel::ThisModule) -> Result { -/// let drivers = unsafe { &mut DRIVERS }; -/// let mut reg = ::kernel::net::phy::Registration::register( -/// module, -/// ::core::pin::Pin::static_mut(drivers), -/// )?; -/// Ok(Module { _reg: reg }) -/// } -/// } -/// }; -/// -/// const N: usize = 1; -/// -/// const TABLE: ::kernel::device_id::IdArray<::kernel::net::phy::DeviceId, (), N> = -/// ::kernel::device_id::IdArray::new_without_index([ -/// ::kernel::net::phy::DeviceId( -/// ::kernel::bindings::mdio_device_id { -/// phy_id: 0x00000001, -/// phy_id_mask: 0xffffffff, -/// }), -/// ]); -/// -/// ::kernel::module_device_table!("mdio", phydev, TABLE); -/// ``` #[macro_export] macro_rules! module_phy_driver { (@replace_expr $_t:tt $sub:expr) => {$sub}; -- cgit From 98c63ce4d7606b2362d8702f159b07f77f75547e Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Sun, 19 Jul 2026 17:36:05 +0200 Subject: rust: irq: make Registration compatible with lifetime-bound drivers Adapt the IRQ registration to work with the Higher-Ranked Lifetime Types (HRT) device driver architecture introduced in commit 2c7c65933600 ("Merge patch series "rust: device: Higher-Ranked Lifetime Types for device drivers""). With HRT, driver structs carry a lifetime parameter tied to the device binding scope, allowing device resources such as pci::Bar<'bar> to be held directly rather than through Devres indirection. However, the IRQ abstraction required Handler: Sync + 'static, preventing handlers from embedding lifetime-parameterized resources. Remove the 'static bound from Handler and ThreadedHandler and replace the Devres indirection with direct request_irq() / free_irq() calls in the constructor and PinnedDrop. Registration<'a, T> stores the IrqRequest<'a>, which structurally ties it to the device binding scope. Also remove the &Device parameter from the handler callbacks, since handlers that need device access can embed it in their own type. IRQ handlers can now directly own device resources: struct IrqHandler<'irq> { bar: pci::Bar<'irq, BAR_SIZE>, } impl irq::Handler for IrqHandler<'_> { fn handle(&self) -> IrqReturn { let stat = self.bar.read(regs::STAT); ... } } This eliminates the indirection previously required for IRQ handlers to access device resources and aligns with the broader goal of expressing every registration scoped to a driver binding through compile-time lifetime bounds. Reviewed-by: Daniel Almeida Reviewed-by: Gary Guo Reviewed-by: Alexandre Courbot Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260719153631.559341-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/irq/request.rs | 432 +++++++++++++++++++++------------------------ rust/kernel/pci/irq.rs | 26 ++- rust/kernel/platform.rs | 48 +++-- 3 files changed, 255 insertions(+), 251 deletions(-) diff --git a/rust/kernel/irq/request.rs b/rust/kernel/irq/request.rs index f425fe12f7c8..c1c6525a676a 100644 --- a/rust/kernel/irq/request.rs +++ b/rust/kernel/irq/request.rs @@ -5,16 +5,21 @@ //! [`ThreadedRegistration`], which allow users to register handlers for a given //! IRQ line. -use core::marker::PhantomPinned; - -use crate::alloc::Allocator; -use crate::device::{Bound, Device}; -use crate::devres::Devres; -use crate::error::to_result; -use crate::irq::flags::Flags; -use crate::prelude::*; -use crate::str::CStr; -use crate::sync::Arc; +use core::marker::{ + PhantomData, + PhantomPinned, // +}; + +use crate::{ + device::{ + Bound, + Device, // + }, + error::to_result, + irq::flags::Flags, + prelude::*, + str::CStr, +}; /// The value that can be returned from a [`Handler`] or a [`ThreadedHandler`]. #[repr(u32)] @@ -27,7 +32,7 @@ pub enum IrqReturn { } /// Callbacks for an IRQ handler. -pub trait Handler: Sync + 'static { +pub trait Handler: Sync { /// The hard IRQ handler. /// /// This is executed in interrupt context, hence all corresponding @@ -36,73 +41,20 @@ pub trait Handler: Sync + 'static { /// All work that does not necessarily need to be executed from /// interrupt context, should be deferred to a threaded handler. /// See also [`ThreadedRegistration`]. - fn handle(&self, device: &Device) -> IrqReturn; -} - -impl Handler for Arc { - fn handle(&self, device: &Device) -> IrqReturn { - T::handle(self, device) - } -} - -impl Handler for Box { - fn handle(&self, device: &Device) -> IrqReturn { - T::handle(self, device) - } + fn handle(&self) -> IrqReturn; } -/// # Invariants -/// -/// - `self.irq` is the same as the one passed to `request_{threaded}_irq`. -/// - `cookie` was passed to `request_{threaded}_irq` as the cookie. It is guaranteed to be unique -/// by the type system, since each call to `new` will return a different instance of -/// `Registration`. -#[pin_data(PinnedDrop)] -struct RegistrationInner { - irq: u32, - cookie: *mut c_void, -} - -impl RegistrationInner { - fn synchronize(&self) { - // SAFETY: safe as per the invariants of `RegistrationInner` - unsafe { bindings::synchronize_irq(self.irq) }; - } -} - -#[pinned_drop] -impl PinnedDrop for RegistrationInner { - fn drop(self: Pin<&mut Self>) { - // SAFETY: - // - // Safe as per the invariants of `RegistrationInner` and: - // - // - The containing struct is `!Unpin` and was initialized using - // pin-init, so it occupied the same memory location for the entirety of - // its lifetime. - // - // Notice that this will block until all handlers finish executing, - // i.e.: at no point will &self be invalid while the handler is running. - unsafe { bindings::free_irq(self.irq, self.cookie) }; - } -} - -// SAFETY: We only use `inner` on drop, which called at most once with no -// concurrent access. -unsafe impl Sync for RegistrationInner {} - -// SAFETY: It is safe to send `RegistrationInner` across threads. -unsafe impl Send for RegistrationInner {} - /// A request for an IRQ line for a given device. /// /// # Invariants /// /// - `ìrq` is the number of an interrupt source of `dev`. -/// - `irq` has not been registered yet. +/// - `irq` has not been registered yet; this is consumed by [`Registration::new()`]. pub struct IrqRequest<'a> { - dev: &'a Device, irq: u32, + /// Proves the device is bound at registration time and ties `'a` to the device's bound + /// lifetime, ensuring the [`Registration`] cannot outlive it. + _dev: PhantomData<&'a Device>, } impl<'a> IrqRequest<'a> { @@ -111,12 +63,16 @@ impl<'a> IrqRequest<'a> { /// # Safety /// /// - `irq` should be a valid IRQ number for `dev`. - pub(crate) unsafe fn new(dev: &'a Device, irq: u32) -> Self { + pub(crate) unsafe fn new(_dev: &'a Device, irq: u32) -> Self { // INVARIANT: `irq` is a valid IRQ number for `dev`. - IrqRequest { dev, irq } + IrqRequest { + irq, + _dev: PhantomData, + } } /// Returns the IRQ number of an [`IrqRequest`]. + #[inline] pub fn irq(&self) -> u32 { self.irq } @@ -139,10 +95,18 @@ impl<'a> IrqRequest<'a> { /// [`Completion::wait_for_completion()`]: kernel::sync::Completion::wait_for_completion /// /// ``` -/// use kernel::device::{Bound, Device}; -/// use kernel::irq::{self, Flags, IrqRequest, IrqReturn, Registration}; -/// use kernel::prelude::*; -/// use kernel::sync::{Arc, Completion}; +/// use core::pin::Pin; +/// use kernel::{ +/// irq::{ +/// self, +/// Flags, +/// IrqRequest, +/// IrqReturn, +/// Registration, +/// }, +/// prelude::*, +/// sync::Completion, +/// }; /// /// // Data shared between process and IRQ context. /// #[pin_data] @@ -153,7 +117,7 @@ impl<'a> IrqRequest<'a> { /// /// impl irq::Handler for Data { /// // Executed in IRQ context. -/// fn handle(&self, _dev: &Device) -> IrqReturn { +/// fn handle(&self) -> IrqReturn { /// self.completion.complete_all(); /// IrqReturn::Handled /// } @@ -163,12 +127,21 @@ impl<'a> IrqRequest<'a> { /// // /// // This runs in process context and assumes `request` was previously acquired from a device. /// fn register_irq( -/// handler: impl PinInit, /// request: IrqRequest<'_>, -/// ) -> Result>> { -/// let registration = Registration::new(request, Flags::SHARED, c"my_device", handler); +/// ) -> Result>>> { +/// // SAFETY: The returned Registration is not leaked. +/// let registration = unsafe { +/// Registration::new( +/// request, +/// Flags::SHARED, +/// c"my_device", +/// try_pin_init!(Data { +/// completion <- Completion::new(), +/// }? Error), +/// ) +/// }; /// -/// let registration = Arc::pin_init(registration, GFP_KERNEL)?; +/// let registration = KBox::pin_init(registration, GFP_KERNEL)?; /// /// registration.handler().completion.wait_for_completion(); /// @@ -179,11 +152,10 @@ impl<'a> IrqRequest<'a> { /// /// # Invariants /// -/// * We own an irq handler whose cookie is a pointer to `Self`. -#[pin_data] -pub struct Registration { - #[pin] - inner: Devres, +/// * We own an irq handler registered via `request_irq` whose cookie is a pointer to `Self`. +#[pin_data(PinnedDrop)] +pub struct Registration<'a, T: Handler> { + request: IrqRequest<'a>, #[pin] handler: T, @@ -194,44 +166,46 @@ pub struct Registration { _pin: PhantomPinned, } -impl Registration { +impl<'a, T: Handler> Registration<'a, T> { /// Registers the IRQ handler with the system for the given IRQ number. - pub fn new<'a>( + /// + /// # Safety + /// + /// Callers must not `mem::forget()` the returned [`Registration`] or otherwise prevent its + /// [`Drop`] implementation from running. + pub unsafe fn new( request: IrqRequest<'a>, flags: Flags, name: &'static CStr, handler: impl PinInit + 'a, - ) -> impl PinInit + 'a { + ) -> impl PinInit + 'a + where + T: 'a, + { + // INVARIANT: If initialization completes successfully, we own an IRQ handler registered + // via `request_irq` whose cookie is a pointer to `Self`. try_pin_init!(&this in Self { handler <- handler, - inner <- Devres::new( - request.dev, - try_pin_init!(RegistrationInner { - // INVARIANT: `this` is a valid pointer to the `Registration` instance - cookie: this.as_ptr().cast::(), - irq: { - // SAFETY: - // - The callbacks are valid for use with request_irq. - // - If this succeeds, the slot is guaranteed to be valid until the - // destructor of Self runs, which will deregister the callbacks - // before the memory location becomes invalid. - // - When request_irq is called, everything that handle_irq_callback will - // touch has already been initialized, so it's safe for the callback to - // be called immediately. - to_result(unsafe { - bindings::request_irq( - request.irq, - Some(handle_irq_callback::), - flags.into_inner(), - name.as_char_ptr(), - this.as_ptr().cast::(), - ) - })?; - request.irq - } - }) - ), + request, _pin: PhantomPinned, + _: { + // SAFETY: + // - The callbacks are valid for use with request_irq. + // - If this succeeds, the slot is guaranteed to be valid until the destructor of + // Self runs, which will deregister the callbacks before the memory location + // becomes invalid. + // - All fields are already initialized, so it's safe for the callback to be + // called immediately. + to_result(unsafe { + bindings::request_irq( + request.irq, + Some(handle_irq_callback::), + flags.into_inner(), + name.as_char_ptr(), + this.as_ptr().cast::(), + ) + })?; + }, }) } @@ -241,19 +215,25 @@ impl Registration { } /// Wait for pending IRQ handlers on other CPUs. - /// - /// This will attempt to access the inner [`Devres`] container. - pub fn try_synchronize(&self) -> Result { - let inner = self.inner.try_access().ok_or(ENODEV)?; - inner.synchronize(); - Ok(()) + #[inline] + pub fn synchronize(&self) { + // SAFETY: `self.request.irq` is a valid registered IRQ number (type invariant). + unsafe { bindings::synchronize_irq(self.request.irq) }; } +} - /// Wait for pending IRQ handlers on other CPUs. - pub fn synchronize(&self, dev: &Device) -> Result { - let inner = self.inner.access(dev)?; - inner.synchronize(); - Ok(()) +#[pinned_drop] +impl PinnedDrop for Registration<'_, T> { + fn drop(self: Pin<&mut Self>) { + // SAFETY: The cookie was set to a pointer to `Self` in `Registration::new()`. This blocks + // until all in-flight handlers complete, so no references to `self` remain after this + // returns. + unsafe { + bindings::free_irq( + self.request.irq, + core::ptr::from_mut::(self.get_unchecked_mut()).cast::(), + ) + }; } } @@ -261,13 +241,11 @@ impl Registration { /// /// This function should be only used as the callback in `request_irq`. unsafe extern "C" fn handle_irq_callback(_irq: i32, ptr: *mut c_void) -> c_uint { - // SAFETY: `ptr` is a pointer to `Registration` set in `Registration::new` - let registration = unsafe { &*(ptr as *const Registration) }; - // SAFETY: The irq callback is removed before the device is unbound, so the fact that the irq - // callback is running implies that the device has not yet been unbound. - let device = unsafe { registration.inner.device().as_bound() }; + let ptr = ptr.cast_const().cast::>(); + // SAFETY: `ptr` is a pointer to `Registration<'_, T>` set in `Registration::new()`. + let registration = unsafe { &*ptr }; - T::handle(®istration.handler, device) as c_uint + T::handle(®istration.handler) as c_uint } /// The value that can be returned from [`ThreadedHandler::handle`]. @@ -284,7 +262,7 @@ pub enum ThreadedIrqReturn { } /// Callbacks for a threaded IRQ handler. -pub trait ThreadedHandler: Sync + 'static { +pub trait ThreadedHandler: Sync { /// The hard IRQ handler. /// /// This is executed in interrupt context, hence all corresponding @@ -293,8 +271,7 @@ pub trait ThreadedHandler: Sync + 'static { /// handler, i.e. [`ThreadedHandler::handle_threaded`]. /// /// The default implementation returns [`ThreadedIrqReturn::WakeThread`]. - #[expect(unused_variables)] - fn handle(&self, device: &Device) -> ThreadedIrqReturn { + fn handle(&self) -> ThreadedIrqReturn { ThreadedIrqReturn::WakeThread } @@ -302,27 +279,7 @@ pub trait ThreadedHandler: Sync + 'static { /// /// This is executed in process context. The kernel creates a dedicated /// `kthread` for this purpose. - fn handle_threaded(&self, device: &Device) -> IrqReturn; -} - -impl ThreadedHandler for Arc { - fn handle(&self, device: &Device) -> ThreadedIrqReturn { - T::handle(self, device) - } - - fn handle_threaded(&self, device: &Device) -> IrqReturn { - T::handle_threaded(self, device) - } -} - -impl ThreadedHandler for Box { - fn handle(&self, device: &Device) -> ThreadedIrqReturn { - T::handle(self, device) - } - - fn handle_threaded(&self, device: &Device) -> IrqReturn { - T::handle_threaded(self, device) - } + fn handle_threaded(&self) -> IrqReturn; } /// A registration of a threaded IRQ handler for a given IRQ line. @@ -339,13 +296,20 @@ impl ThreadedHandler for Bo /// [`Mutex`](kernel::sync::Mutex) to provide interior mutability. /// /// ``` -/// use kernel::device::{Bound, Device}; -/// use kernel::irq::{ -/// self, Flags, IrqRequest, IrqReturn, ThreadedHandler, ThreadedIrqReturn, -/// ThreadedRegistration, +/// use core::pin::Pin; +/// use kernel::{ +/// irq::{ +/// self, +/// Flags, +/// IrqRequest, +/// IrqReturn, +/// ThreadedHandler, +/// ThreadedIrqReturn, +/// ThreadedRegistration, +/// }, +/// prelude::*, +/// sync::Mutex, /// }; -/// use kernel::prelude::*; -/// use kernel::sync::{Arc, Mutex}; /// /// // Declare a struct that will be passed in when the interrupt fires. The u32 /// // merely serves as an example of some internal data. @@ -363,7 +327,7 @@ impl ThreadedHandler for Bo /// // This will run (in a separate kthread) if and only if /// // [`ThreadedHandler::handle`] returns [`WakeThread`], which it does by /// // default. -/// fn handle_threaded(&self, _dev: &Device) -> IrqReturn { +/// fn handle_threaded(&self) -> IrqReturn { /// let mut data = self.value.lock(); /// *data += 1; /// IrqReturn::Handled @@ -375,13 +339,21 @@ impl ThreadedHandler for Bo /// // This is executing in process context and assumes that `request` was /// // previously acquired from a device. /// fn register_threaded_irq( -/// handler: impl PinInit, /// request: IrqRequest<'_>, -/// ) -> Result>> { -/// let registration = -/// ThreadedRegistration::new(request, Flags::SHARED, c"my_device", handler); +/// ) -> Result>>> { +/// // SAFETY: The returned Registration is not leaked. +/// let registration = unsafe { +/// ThreadedRegistration::new( +/// request, +/// Flags::SHARED, +/// c"my_device", +/// try_pin_init!(Data { +/// value <- kernel::new_mutex!(0), +/// }? Error), +/// ) +/// }; /// -/// let registration = Arc::pin_init(registration, GFP_KERNEL)?; +/// let registration = KBox::pin_init(registration, GFP_KERNEL)?; /// /// { /// // The data can be accessed from process context too. @@ -396,11 +368,11 @@ impl ThreadedHandler for Bo /// /// # Invariants /// -/// * We own an irq handler whose cookie is a pointer to `Self`. -#[pin_data] -pub struct ThreadedRegistration { - #[pin] - inner: Devres, +/// * We own an irq handler registered via `request_threaded_irq` whose cookie is a pointer to +/// `Self`. +#[pin_data(PinnedDrop)] +pub struct ThreadedRegistration<'a, T: ThreadedHandler> { + request: IrqRequest<'a>, #[pin] handler: T, @@ -411,45 +383,47 @@ pub struct ThreadedRegistration { _pin: PhantomPinned, } -impl ThreadedRegistration { +impl<'a, T: ThreadedHandler> ThreadedRegistration<'a, T> { /// Registers the IRQ handler with the system for the given IRQ number. - pub fn new<'a>( + /// + /// # Safety + /// + /// Callers must not `mem::forget()` the returned [`ThreadedRegistration`] or otherwise prevent + /// its [`Drop`] implementation from running. + pub unsafe fn new( request: IrqRequest<'a>, flags: Flags, name: &'static CStr, handler: impl PinInit + 'a, - ) -> impl PinInit + 'a { + ) -> impl PinInit + 'a + where + T: 'a, + { + // INVARIANT: If initialization completes successfully, we own an IRQ handler registered + // via `request_threaded_irq` whose cookie is a pointer to `Self`. try_pin_init!(&this in Self { handler <- handler, - inner <- Devres::new( - request.dev, - try_pin_init!(RegistrationInner { - // INVARIANT: `this` is a valid pointer to the `ThreadedRegistration` instance. - cookie: this.as_ptr().cast::(), - irq: { - // SAFETY: - // - The callbacks are valid for use with request_threaded_irq. - // - If this succeeds, the slot is guaranteed to be valid until the - // destructor of Self runs, which will deregister the callbacks - // before the memory location becomes invalid. - // - When request_threaded_irq is called, everything that the two callbacks - // will touch has already been initialized, so it's safe for the - // callbacks to be called immediately. - to_result(unsafe { - bindings::request_threaded_irq( - request.irq, - Some(handle_threaded_irq_callback::), - Some(thread_fn_callback::), - flags.into_inner(), - name.as_char_ptr(), - this.as_ptr().cast::(), - ) - })?; - request.irq - } - }) - ), + request, _pin: PhantomPinned, + _: { + // SAFETY: + // - The callbacks are valid for use with request_threaded_irq. + // - If this succeeds, the slot is guaranteed to be valid until the destructor of + // Self runs, which will deregister the callbacks before the memory location + // becomes invalid. + // - All fields are already initialized, so it's safe for the callbacks to be + // called immediately. + to_result(unsafe { + bindings::request_threaded_irq( + request.irq, + Some(handle_threaded_irq_callback::), + Some(thread_fn_callback::), + flags.into_inner(), + name.as_char_ptr(), + this.as_ptr().cast::(), + ) + })?; + }, }) } @@ -459,19 +433,25 @@ impl ThreadedRegistration { } /// Wait for pending IRQ handlers on other CPUs. - /// - /// This will attempt to access the inner [`Devres`] container. - pub fn try_synchronize(&self) -> Result { - let inner = self.inner.try_access().ok_or(ENODEV)?; - inner.synchronize(); - Ok(()) + #[inline] + pub fn synchronize(&self) { + // SAFETY: `self.request.irq` is a valid registered IRQ number (type invariant). + unsafe { bindings::synchronize_irq(self.request.irq) }; } +} - /// Wait for pending IRQ handlers on other CPUs. - pub fn synchronize(&self, dev: &Device) -> Result { - let inner = self.inner.access(dev)?; - inner.synchronize(); - Ok(()) +#[pinned_drop] +impl PinnedDrop for ThreadedRegistration<'_, T> { + fn drop(self: Pin<&mut Self>) { + // SAFETY: The cookie was set to a pointer to `Self` in `ThreadedRegistration::new()`. This + // blocks until all in-flight handlers complete, so no references to `self` remain after + // this returns. + unsafe { + bindings::free_irq( + self.request.irq, + core::ptr::from_mut::(self.get_unchecked_mut()).cast::(), + ) + }; } } @@ -482,24 +462,22 @@ unsafe extern "C" fn handle_threaded_irq_callback( _irq: i32, ptr: *mut c_void, ) -> c_uint { - // SAFETY: `ptr` is a pointer to `ThreadedRegistration` set in `ThreadedRegistration::new` - let registration = unsafe { &*(ptr as *const ThreadedRegistration) }; - // SAFETY: The irq callback is removed before the device is unbound, so the fact that the irq - // callback is running implies that the device has not yet been unbound. - let device = unsafe { registration.inner.device().as_bound() }; + let ptr = ptr.cast_const().cast::>(); + // SAFETY: `ptr` is a pointer to `ThreadedRegistration<'_, T>` set in + // `ThreadedRegistration::new()`. + let registration = unsafe { &*ptr }; - T::handle(®istration.handler, device) as c_uint + T::handle(®istration.handler) as c_uint } /// # Safety /// /// This function should be only used as the callback in `request_threaded_irq`. unsafe extern "C" fn thread_fn_callback(_irq: i32, ptr: *mut c_void) -> c_uint { - // SAFETY: `ptr` is a pointer to `ThreadedRegistration` set in `ThreadedRegistration::new` - let registration = unsafe { &*(ptr as *const ThreadedRegistration) }; - // SAFETY: The irq callback is removed before the device is unbound, so the fact that the irq - // callback is running implies that the device has not yet been unbound. - let device = unsafe { registration.inner.device().as_bound() }; + let ptr = ptr.cast_const().cast::>(); + // SAFETY: `ptr` is a pointer to `ThreadedRegistration<'_, T>` set in + // `ThreadedRegistration::new()`. + let registration = unsafe { &*ptr }; - T::handle_threaded(®istration.handler, device) as c_uint + T::handle_threaded(®istration.handler) as c_uint } diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index d9230e105541..fea484dcf9cf 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -173,34 +173,44 @@ impl Drop for IrqVectorRegistration { impl Device { /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. - pub fn request_irq<'a, T: crate::irq::Handler + 'static>( + /// + /// # Safety + /// + /// Callers must not `mem::forget()` the resulting [`irq::Registration`] or otherwise prevent + /// its [`Drop`] implementation from running. + pub unsafe fn request_irq<'a, T: crate::irq::Handler + 'a>( &'a self, vector: IrqVector<'a>, flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, - ) -> impl PinInit, Error> + 'a { + ) -> impl PinInit, Error> + 'a { pin_init::pin_init_scope(move || { let request = vector.try_into()?; - Ok(irq::Registration::::new(request, flags, name, handler)) + // SAFETY: Caller guarantees the Registration will not be leaked. + Ok(unsafe { irq::Registration::::new(request, flags, name, handler) }) }) } /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. - pub fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'static>( + /// + /// # Safety + /// + /// Callers must not `mem::forget()` the resulting [`irq::ThreadedRegistration`] or otherwise + /// prevent its [`Drop`] implementation from running. + pub unsafe fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'a>( &'a self, vector: IrqVector<'a>, flags: irq::Flags, name: &'static CStr, handler: impl PinInit + 'a, - ) -> impl PinInit, Error> + 'a { + ) -> impl PinInit, Error> + 'a { pin_init::pin_init_scope(move || { let request = vector.try_into()?; - Ok(irq::ThreadedRegistration::::new( - request, flags, name, handler, - )) + // SAFETY: Caller guarantees the Registration will not be leaked. + Ok(unsafe { irq::ThreadedRegistration::::new(request, flags, name, handler) }) }) } diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs index e12e88113ca5..45204e51cdef 100644 --- a/rust/kernel/platform.rs +++ b/rust/kernel/platform.rs @@ -338,22 +338,30 @@ macro_rules! define_irq_accessor_by_index { $handler_trait:ident ) => { $(#[$meta])* - pub fn $fn_name<'a, T: irq::$handler_trait + 'static>( + /// + /// # Safety + /// + /// Callers must not `mem::forget()` the resulting registration or otherwise prevent its + /// [`Drop`] implementation from running. + pub unsafe fn $fn_name<'a, T: irq::$handler_trait + 'a>( &'a self, flags: irq::Flags, index: u32, name: &'static CStr, handler: impl PinInit + 'a, - ) -> impl PinInit, Error> + 'a { + ) -> impl PinInit, Error> + 'a { pin_init::pin_init_scope(move || { let request = self.$request_fn(index)?; - Ok(irq::$reg_type::::new( - request, - flags, - name, - handler, - )) + // SAFETY: Caller guarantees the Registration will not be leaked. + Ok(unsafe { + irq::$reg_type::::new( + request, + flags, + name, + handler, + ) + }) }) } }; @@ -367,22 +375,30 @@ macro_rules! define_irq_accessor_by_name { $handler_trait:ident ) => { $(#[$meta])* - pub fn $fn_name<'a, T: irq::$handler_trait + 'static>( + /// + /// # Safety + /// + /// Callers must not `mem::forget()` the resulting registration or otherwise prevent its + /// [`Drop`] implementation from running. + pub unsafe fn $fn_name<'a, T: irq::$handler_trait + 'a>( &'a self, flags: irq::Flags, irq_name: &'a CStr, name: &'static CStr, handler: impl PinInit + 'a, - ) -> impl PinInit, Error> + 'a { + ) -> impl PinInit, Error> + 'a { pin_init::pin_init_scope(move || { let request = self.$request_fn(irq_name)?; - Ok(irq::$reg_type::::new( - request, - flags, - name, - handler, - )) + // SAFETY: Caller guarantees the Registration will not be leaked. + Ok(unsafe { + irq::$reg_type::::new( + request, + flags, + name, + handler, + ) + }) }) } }; -- cgit From 56c193a5cb375bab9b9c94adb9da9bd8815fc106 Mon Sep 17 00:00:00 2001 From: Ethan Plant Date: Tue, 4 Aug 2026 16:24:25 -0700 Subject: rust: pci: Mark Device refcount methods inline When building the kernel, the following Rust symbols are generated: $ nm vmlinux | grep ' _R' | rustfilt | grep -E 'pci::Device.*(inc_ref|dec_ref)' ... T ::dec_ref ... T ::inc_ref These Rust symbols are trivial wrappers around pci_dev_put() and pci_dev_get(), respectively. It doesn't make sense to go through a trivial wrapper for these functions, so mark them inline. Suggested-by: Alice Ryhl Link: https://github.com/Rust-for-Linux/linux/issues/1145 Signed-off-by: Ethan Plant Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20260804-inline-wrappers-v1-1-16916db867e5@gmail.com Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index cb83e2831b9c..c6417af2bb17 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -475,11 +475,13 @@ impl<'a> crate::dma::Device<'a> for Device> {} // SAFETY: Instances of `Device` are always reference-counted. unsafe impl crate::sync::aref::AlwaysRefCounted for Device { + #[inline] fn inc_ref(&self) { // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. unsafe { bindings::pci_dev_get(self.as_raw()) }; } + #[inline] unsafe fn dec_ref(obj: NonNull) { // SAFETY: The safety requirements guarantee that the refcount is non-zero. unsafe { bindings::pci_dev_put(obj.cast().as_ptr()) } -- cgit From b48373c901951fad1a26bd7c33ad91172b3945b5 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Thu, 16 Jul 2026 13:46:01 +0530 Subject: firmware_loader: do not queue completed sysfs fallback requests fw_load_sysfs_fallback() calls device_add() before adding the fw_priv to pending_fw_head. device_add() publishes the fallback loading interface, so a userspace helper which discovers the device by scanning sysfs can write 0 to the loading attribute and complete the request before it is queued as pending. In that interleaving firmware_loading_store() calls fw_state_done() while pending_list still points to itself, so it cannot remove an entry from pending_fw_head. The subsequent unconditional list_add() then queues an already-completed fw_priv. Once the request is released, pending_fw_head can retain a pointer to freed memory and the next fallback request can fault while validating the list. Only in-flight fallback requests need suspend or reboot abort handling. If the request is already DONE after device_add(), return success from the fallback path without sending another uevent, waiting again, or queueing it as pending. This preserves the invariant that pending_fw_head contains only active fallback requests. Fixes: 75d95e2e39b2 ("firmware_loader: fix use-after-free in firmware_fallback_sysfs") Signed-off-by: Mukesh Ojha Link: https://patch.msgid.link/20260716081601.1674470-1-mukesh.ojha@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/base/firmware_loader/fallback.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 3ef0b312ae71..00d6b6e59461 100644 --- a/drivers/base/firmware_loader/fallback.c +++ b/drivers/base/firmware_loader/fallback.c @@ -95,6 +95,16 @@ static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, long timeout) retval = -EINTR; goto out; } + + /* + * device_add() exposes the loading interface before pending_list is + * linked into pending_fw_head, so fw_state_done() may run first. + */ + if (fw_state_is_done(fw_priv)) { + mutex_unlock(&fw_lock); + goto out; + } + list_add(&fw_priv->pending_list, &pending_fw_head); mutex_unlock(&fw_lock); -- cgit From 5ab289f5eabcda3b78c4083d6415711a13466436 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 28 Jul 2026 16:07:16 +0200 Subject: ACPI/APMT: use platform_device_set_fwnode() Platform devices using the split approach of calling platform_device_alloc() and platform_device_add() must not assign the firmware nodes manually but use the provided platform_device_set_fwnode() function which additionally makes sure we track the reference count of the firmware node correctly. Replace the manual assignment of the firmware node with a call to platform_device_set_fwnode(). Signed-off-by: Bartosz Golaszewski Acked-by: Sudeep Holla Link: https://patch.msgid.link/20260728-acpi-arm64-pdev-set-fwnode-v1-1-b5aff9b2235a@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/acpi/arm64/apmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/arm64/apmt.c b/drivers/acpi/arm64/apmt.c index bb010f6164e5..c927e4f08ec2 100644 --- a/drivers/acpi/arm64/apmt.c +++ b/drivers/acpi/arm64/apmt.c @@ -99,7 +99,7 @@ static int __init apmt_add_platform_device(struct acpi_apmt_node *node, if (ret) goto dev_put; - pdev->dev.fwnode = fwnode; + platform_device_set_fwnode(pdev, fwnode); ret = platform_device_add(pdev); -- cgit From 67b1e7245bd60f54f820859b9e400136a7b6d00a Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 28 Jul 2026 16:07:17 +0200 Subject: ACPI/IORT: use platform_device_set_fwnode() Platform devices using the split approach of calling platform_device_alloc() and platform_device_add() must not assign the firmware nodes manually but use the provided platform_device_set_fwnode() function which additionally makes sure we track the reference count of the firmware node correctly. Replace the manual assignment of the firmware node with a call to platform_device_set_fwnode(). Signed-off-by: Bartosz Golaszewski Acked-by: Sudeep Holla Link: https://patch.msgid.link/20260728-acpi-arm64-pdev-set-fwnode-v1-2-b5aff9b2235a@oss.qualcomm.com Signed-off-by: Danilo Krummrich --- drivers/acpi/arm64/iort.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c index af7a9b2fd5bc..68342977e8d4 100644 --- a/drivers/acpi/arm64/iort.c +++ b/drivers/acpi/arm64/iort.c @@ -1981,7 +1981,7 @@ static int __init iort_add_platform_device(struct acpi_iort_node *node, goto dev_put; } - pdev->dev.fwnode = fwnode; + platform_device_set_fwnode(pdev, fwnode); if (ops->dev_dma_configure) ops->dev_dma_configure(&pdev->dev, node); -- cgit From ddca0cd800962b4121e5f2633005167bb6216175 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 13 Aug 2026 17:24:41 +0200 Subject: rust: serdev: use ThisModule::as_ptr() instead of field access The THIS_MODULE series [1] applied to rust-next moved ThisModule from lib.rs into a module.rs submodule, making the tuple struct field private outside the module. This breaks the module.0 field access in serdev in driver-core-next. Update the call to __serdev_device_driver_register() to use the public module.as_ptr() accessor to fix the build. Link: https://lore.kernel.org/all/20260811-fix-fops-owner-v10-0-7e71776f9dbe@linux.dev/ [1] Closes: https://lore.kernel.org/all/DKNAS52KYWLD.M15VEC6U0F6R@kernel.org/ Reviewed-by: Gary Guo Reviewed-by: Markus Probst Link: https://patch.msgid.link/20260813152444.514580-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/serdev.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/kernel/serdev.rs b/rust/kernel/serdev.rs index c0cd24481ee3..eecb1117095c 100644 --- a/rust/kernel/serdev.rs +++ b/rust/kernel/serdev.rs @@ -87,7 +87,7 @@ unsafe impl driver::RegistrationOps for Adapter { } // SAFETY: `sdrv` is guaranteed to be a valid `DriverType`. - to_result(unsafe { bindings::__serdev_device_driver_register(sdrv.get(), module.0) }) + to_result(unsafe { bindings::__serdev_device_driver_register(sdrv.get(), module.as_ptr()) }) } unsafe fn unregister(sdrv: &Opaque) { -- cgit From d5e81a5650b5258c73768366db123b6d5bc48e32 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Fri, 31 Jul 2026 20:05:54 +0800 Subject: kernfs: avoid iattr allocation in listxattr kernfs_iop_listxattr() only needs to report existing xattrs, but it uses kernfs_iattrs(), which allocates kernfs_iattrs when the node does not have one yet. This makes a query operation create persistent per-node metadata even when the xattr list is empty. Use kernfs_iattrs_noalloc() instead and return an empty list when no iattrs exist. Signed-off-by: Yichong Chen Acked-by: Tejun Heo Link: https://patch.msgid.link/20260731120554.630147-1-chenyichong@uniontech.com Signed-off-by: Danilo Krummrich --- fs/kernfs/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index 2cb20294aaf5..237dcdd73fc2 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -141,9 +141,9 @@ ssize_t kernfs_iop_listxattr(struct dentry *dentry, char *buf, size_t size) struct kernfs_node *kn = kernfs_dentry_node(dentry); struct kernfs_iattrs *attrs; - attrs = kernfs_iattrs(kn); + attrs = kernfs_iattrs_noalloc(kn); if (!attrs) - return -ENOMEM; + return 0; return simple_xattr_list(d_inode(dentry), &attrs->xattrs, buf, size); } -- cgit From 17d4a399e8b8de0bb7d78cb46f92ffa9d629965d Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 13 Aug 2026 18:52:01 +0200 Subject: rust: pci: convert IrqVectorRegistration to a lifetime-managed owning type Convert IrqVectorRegistration from a devres-managed internal type to a lifetime-annotated type that owns the PCI interrupt vector allocation. Dropping it frees the vectors. IrqVector gains a reference to the IrqVectorRegistration it was derived from. Since index() borrows the registration, the compiler prevents the allocation from being dropped while any IrqVector (and hence any irq::Registration built from it) is still live. alloc_irq_vectors() returns IrqVectorRegistration<'_> directly, giving drivers explicit control over the allocation lifetime, which is needed by net and block drivers that re-allocate vectors at runtime, e.g. during queue reconfiguration or device recovery. Tested-by: John Hubbard Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260813165234.620555-2-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/pci.rs | 3 +- rust/kernel/pci/irq.rs | 128 ++++++++++++++++++++++++++----------------------- 2 files changed, 70 insertions(+), 61 deletions(-) diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs index c6417af2bb17..2757a0cc0f11 100644 --- a/rust/kernel/pci.rs +++ b/rust/kernel/pci.rs @@ -51,7 +51,8 @@ pub use self::io::{ pub use self::irq::{ IrqType, IrqTypes, - IrqVector, // + IrqVector, + IrqVectorRegistration, // }; /// An adapter for the registration of PCI drivers. diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index fea484dcf9cf..daba86505cd2 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -7,17 +7,14 @@ use crate::{ bindings, device, device::Bound, - devres, error::to_result, irq::{ self, IrqRequest, // }, - prelude::*, - str::CStr, - sync::aref::ARef, // + prelude::*, // }; -use core::ops::RangeInclusive; +use core::num::NonZero; /// IRQ type flags for PCI interrupt allocation. #[derive(Debug, Clone, Copy)] @@ -78,6 +75,7 @@ impl IrqTypes { #[derive(Clone, Copy)] pub struct IrqVector<'a> { dev: &'a Device, + reg: &'a IrqVectorRegistration<'a>, index: u32, } @@ -86,87 +84,83 @@ impl<'a> IrqVector<'a> { /// /// # Safety /// - /// - `index` must be a valid IRQ vector index for `dev`. - /// - `dev` must point to a [`Device`] that has successfully allocated IRQ vectors. - unsafe fn new(dev: &'a Device, index: u32) -> Self { - Self { dev, index } + /// - `index` must be a valid IRQ vector index for `reg`. + /// - `dev` must be the device `reg` was allocated from. + #[inline] + unsafe fn new(dev: &'a Device, reg: &'a IrqVectorRegistration<'a>, index: u32) -> Self { + Self { dev, reg, index } } /// Returns the raw vector index. fn index(&self) -> u32 { self.index } + + /// Returns the [`IrqVectorRegistration`] this vector was derived from. + #[inline] + pub fn vectors(&self) -> &'a IrqVectorRegistration<'a> { + self.reg + } } impl<'a> TryInto> for IrqVector<'a> { type Error = Error; fn try_into(self) -> Result> { - // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. + // SAFETY: `self.dev.as_raw()` returns a valid pointer to a `struct pci_dev`. let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), self.index()) }; if irq < 0 { return Err(crate::error::Error::from_errno(irq)); } - // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`. + // SAFETY: `irq` is guaranteed to be a valid IRQ number for `self.dev`. Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) }) } } -/// Represents an IRQ vector allocation for a PCI device. +/// An allocation of PCI interrupt vectors for a device. /// -/// This type ensures that IRQ vectors are properly allocated and freed by -/// tying the allocation to the lifetime of this registration object. +/// This type owns the vector allocation; dropping it frees the vectors. IRQ handlers borrow from +/// this registration and must be dropped before it is. /// /// # Invariants /// -/// The [`Device`] has successfully allocated IRQ vectors. -struct IrqVectorRegistration { - dev: ARef, +/// `dev` has an allocation of `len` interrupt vectors. +pub struct IrqVectorRegistration<'a> { + dev: &'a Device, + len: NonZero, } -impl IrqVectorRegistration { - /// Allocate and register IRQ vectors for the given PCI device. +impl<'a> IrqVectorRegistration<'a> { + /// Returns the number of allocated vectors. /// - /// Allocates IRQ vectors and registers them with devres for automatic cleanup. - /// Returns a range of valid IRQ vectors. - fn register<'a>( - dev: &'a Device, - min_vecs: u32, - max_vecs: u32, - irq_types: IrqTypes, - ) -> Result>> { - // SAFETY: - // - `dev.as_raw()` is guaranteed to be a valid pointer to a `struct pci_dev` - // by the type invariant of `Device`. - // - `pci_alloc_irq_vectors` internally validates all other parameters - // and returns error codes. - let ret = unsafe { - bindings::pci_alloc_irq_vectors(dev.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) - }; - - to_result(ret)?; - let count = ret as u32; - - // SAFETY: - // - `pci_alloc_irq_vectors` returns the number of allocated vectors on success. - // - Vectors are 0-based, so valid indices are [0, count-1]. - // - `pci_alloc_irq_vectors` guarantees `count >= min_vecs > 0`, so both `0` and - // `count - 1` are valid IRQ vector indices for `dev`. - let range = unsafe { IrqVector::new(dev, 0)..=IrqVector::new(dev, count - 1) }; + /// This is at least the `min_vecs` that [`Device::alloc_irq_vectors`] was asked for. + #[inline] + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len.get() + } - // INVARIANT: The IRQ vector allocation for `dev` above was successful. - let irq_vecs = Self { dev: dev.into() }; - devres::register(dev.as_ref(), irq_vecs, GFP_KERNEL)?; + /// Returns the [`IrqVector`] at `index`. + /// + /// Returns [`EINVAL`] if the `index` is out of bounds for the length reported by + /// [`Self::len()`]. + #[inline] + pub fn index(&self, index: usize) -> Result> { + if index >= self.len.get() { + return Err(EINVAL); + } - Ok(range) + // SAFETY: `index` is within bounds of this registration's allocation, and `self.dev` is + // the device it was allocated from. + Ok(unsafe { IrqVector::new(self.dev, self, index as u32) }) } } -impl Drop for IrqVectorRegistration { +impl Drop for IrqVectorRegistration<'_> { + #[inline] fn drop(&mut self) { - // SAFETY: - // - By the type invariant, `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. - // - `self.dev` has successfully allocated IRQ vectors. + // SAFETY: By the type invariant, `self.dev.as_raw()` is a valid pointer to a + // `struct pci_dev` that has successfully allocated IRQ vectors. unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) }; } } @@ -214,15 +208,16 @@ impl Device { }) } - /// Allocate IRQ vectors for this PCI device with automatic cleanup. + /// Allocate IRQ vectors for this PCI device. /// /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device. /// The allocation will use MSI-X, MSI, or INTx interrupts based on the `irq_types` /// parameter and hardware capabilities. When multiple types are specified, the kernel /// will try them in order of preference: MSI-X first, then MSI, then INTx interrupts. /// - /// The allocated vectors are automatically freed when the device is unbound, using the - /// devres (device resource management) system. + /// The allocated vectors are freed when the returned [`IrqVectorRegistration`] is dropped. + /// IRQ handlers registered via [`Self::request_irq`] or [`Self::request_threaded_irq`] + /// borrow from the registration, so the compiler ensures they are freed first. /// /// # Arguments /// @@ -232,8 +227,8 @@ impl Device { /// /// # Returns /// - /// Returns a range of IRQ vectors that were successfully allocated, or an error if the - /// allocation fails or cannot meet the minimum requirement. + /// Returns the IRQ vector registration, or an error if `min_vecs` vectors cannot be + /// allocated. /// /// # Examples /// @@ -256,7 +251,20 @@ impl Device { min_vecs: u32, max_vecs: u32, irq_types: IrqTypes, - ) -> Result>> { - IrqVectorRegistration::register(self, min_vecs, max_vecs, irq_types) + ) -> Result> { + // SAFETY: + // - `self.as_raw()` is guaranteed to be a valid pointer to a `struct pci_dev` + // by the type invariant of `Device`. + // - `pci_alloc_irq_vectors` internally validates all other parameters + // and returns error codes. + let ret = unsafe { + bindings::pci_alloc_irq_vectors(self.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) + }; + to_result(ret)?; + + let len = NonZero::new(ret as usize).ok_or(EINVAL)?; + + // INVARIANT: `pci_alloc_irq_vectors()` allocated `len` vectors for `self`. + Ok(IrqVectorRegistration { dev: self, len }) } } -- cgit From 2fb7755b0a7efb811d7d09f3a6ea06fd611d1ba4 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 13 Aug 2026 18:52:02 +0200 Subject: rust: pci: resolve IRQ in index() and embed IrqRequest in IrqVector Move the pci_irq_vector() call from the TryInto impl into IrqVectorRegistration::index(), so the IRQ number is resolved eagerly. IrqVector now embeds the resolved IrqRequest and a reference to the IrqVectorRegistration. The conversion to IrqRequest is infallible, which removes the need for pin_init_scope() in request_irq() / request_threaded_irq(). Tested-by: John Hubbard Inspired-by: John Hubbard Link: https://lore.kernel.org/all/20260808031120.363869-3-jhubbard@nvidia.com/ Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260813165234.620555-3-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/pci/irq.rs | 67 +++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index daba86505cd2..81b74c4c17d9 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -68,32 +68,25 @@ impl IrqTypes { } } -/// Represents an allocated IRQ vector for a specific PCI device. +/// A resolved IRQ vector from a PCI interrupt vector allocation. /// -/// This type ties an IRQ vector to the device it was allocated for, -/// ensuring the vector is only used with the correct device. -#[derive(Clone, Copy)] +/// Created by [`IrqVectorRegistration::index`] and consumed by [`Device::request_irq`] or +/// [`Device::request_threaded_irq`]. Borrows the [`IrqVectorRegistration`] it was derived from, +/// so the allocation stays live until the handler is freed. pub struct IrqVector<'a> { - dev: &'a Device, + request: IrqRequest<'a>, reg: &'a IrqVectorRegistration<'a>, - index: u32, } impl<'a> IrqVector<'a> { - /// Creates a new [`IrqVector`] for the given device and index. + /// Creates a new [`IrqVector`] with an already resolved [`IrqRequest`]. /// /// # Safety /// - /// - `index` must be a valid IRQ vector index for `reg`. - /// - `dev` must be the device `reg` was allocated from. + /// `request` must have been resolved from `reg`. #[inline] - unsafe fn new(dev: &'a Device, reg: &'a IrqVectorRegistration<'a>, index: u32) -> Self { - Self { dev, reg, index } - } - - /// Returns the raw vector index. - fn index(&self) -> u32 { - self.index + unsafe fn new(request: IrqRequest<'a>, reg: &'a IrqVectorRegistration<'a>) -> Self { + Self { request, reg } } /// Returns the [`IrqVectorRegistration`] this vector was derived from. @@ -103,17 +96,10 @@ impl<'a> IrqVector<'a> { } } -impl<'a> TryInto> for IrqVector<'a> { - type Error = Error; - - fn try_into(self) -> Result> { - // SAFETY: `self.dev.as_raw()` returns a valid pointer to a `struct pci_dev`. - let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), self.index()) }; - if irq < 0 { - return Err(crate::error::Error::from_errno(irq)); - } - // SAFETY: `irq` is guaranteed to be a valid IRQ number for `self.dev`. - Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) }) +impl<'a> From> for IrqRequest<'a> { + #[inline] + fn from(vector: IrqVector<'a>) -> Self { + vector.request } } @@ -146,13 +132,14 @@ impl<'a> IrqVectorRegistration<'a> { /// [`Self::len()`]. #[inline] pub fn index(&self, index: usize) -> Result> { - if index >= self.len.get() { - return Err(EINVAL); + // SAFETY: `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. + let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), index as u32) }; + if irq < 0 { + return Err(Error::from_errno(irq)); } - // SAFETY: `index` is within bounds of this registration's allocation, and `self.dev` is - // the device it was allocated from. - Ok(unsafe { IrqVector::new(self.dev, self, index as u32) }) + // SAFETY: `irq` is a valid IRQ number for `self.dev`, resolved from this registration. + Ok(unsafe { IrqVector::new(IrqRequest::new(self.dev.as_ref(), irq as u32), self) }) } } @@ -179,12 +166,8 @@ impl Device { name: &'static CStr, handler: impl PinInit + 'a, ) -> impl PinInit, Error> + 'a { - pin_init::pin_init_scope(move || { - let request = vector.try_into()?; - - // SAFETY: Caller guarantees the Registration will not be leaked. - Ok(unsafe { irq::Registration::::new(request, flags, name, handler) }) - }) + // SAFETY: Caller guarantees the Registration will not be leaked. + unsafe { irq::Registration::::new(vector.into(), flags, name, handler) } } /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. @@ -200,12 +183,8 @@ impl Device { name: &'static CStr, handler: impl PinInit + 'a, ) -> impl PinInit, Error> + 'a { - pin_init::pin_init_scope(move || { - let request = vector.try_into()?; - - // SAFETY: Caller guarantees the Registration will not be leaked. - Ok(unsafe { irq::ThreadedRegistration::::new(request, flags, name, handler) }) - }) + // SAFETY: Caller guarantees the Registration will not be leaked. + unsafe { irq::ThreadedRegistration::::new(vector.into(), flags, name, handler) } } /// Allocate IRQ vectors for this PCI device. -- cgit From 6ca38086b4ee457801b742862e5f3871567e2788 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 13 Aug 2026 18:52:03 +0200 Subject: rust: pci: remove request_irq() and request_threaded_irq() from Device Remove the thin wrappers on Device that only forwarded to irq::Registration::new() and irq::ThreadedRegistration::new(). With IrqVector embedding a resolved IrqRequest, the conversion is infallible and drivers call irq::Registration::new(vector.into(), ...) directly. Unlike the platform equivalents, which combine a fallible IRQ lookup with handler registration, the PCI wrappers add no value beyond namespacing. They also introduce a redundant device reference. IrqVector already carries a device borrow through its embedded IrqRequest, yet the wrappers required a second, potentially unrelated, &self receiver. Tested-by: John Hubbard Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260813165234.620555-4-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/pci/irq.rs | 48 +++++------------------------------------------- 1 file changed, 5 insertions(+), 43 deletions(-) diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index 81b74c4c17d9..b6d1699ee3eb 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -8,10 +8,7 @@ use crate::{ device, device::Bound, error::to_result, - irq::{ - self, - IrqRequest, // - }, + irq::IrqRequest, prelude::*, // }; use core::num::NonZero; @@ -70,9 +67,8 @@ impl IrqTypes { /// A resolved IRQ vector from a PCI interrupt vector allocation. /// -/// Created by [`IrqVectorRegistration::index`] and consumed by [`Device::request_irq`] or -/// [`Device::request_threaded_irq`]. Borrows the [`IrqVectorRegistration`] it was derived from, -/// so the allocation stays live until the handler is freed. +/// Created by [`IrqVectorRegistration::index`]. Convert to [`IrqRequest`] via [`From`] to register +/// a handler with [`irq::Registration::new`](crate::irq::Registration::new). pub struct IrqVector<'a> { request: IrqRequest<'a>, reg: &'a IrqVectorRegistration<'a>, @@ -153,40 +149,6 @@ impl Drop for IrqVectorRegistration<'_> { } impl Device { - /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. - /// - /// # Safety - /// - /// Callers must not `mem::forget()` the resulting [`irq::Registration`] or otherwise prevent - /// its [`Drop`] implementation from running. - pub unsafe fn request_irq<'a, T: crate::irq::Handler + 'a>( - &'a self, - vector: IrqVector<'a>, - flags: irq::Flags, - name: &'static CStr, - handler: impl PinInit + 'a, - ) -> impl PinInit, Error> + 'a { - // SAFETY: Caller guarantees the Registration will not be leaked. - unsafe { irq::Registration::::new(vector.into(), flags, name, handler) } - } - - /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. - /// - /// # Safety - /// - /// Callers must not `mem::forget()` the resulting [`irq::ThreadedRegistration`] or otherwise - /// prevent its [`Drop`] implementation from running. - pub unsafe fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'a>( - &'a self, - vector: IrqVector<'a>, - flags: irq::Flags, - name: &'static CStr, - handler: impl PinInit + 'a, - ) -> impl PinInit, Error> + 'a { - // SAFETY: Caller guarantees the Registration will not be leaked. - unsafe { irq::ThreadedRegistration::::new(vector.into(), flags, name, handler) } - } - /// Allocate IRQ vectors for this PCI device. /// /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device. @@ -195,8 +157,8 @@ impl Device { /// will try them in order of preference: MSI-X first, then MSI, then INTx interrupts. /// /// The allocated vectors are freed when the returned [`IrqVectorRegistration`] is dropped. - /// IRQ handlers registered via [`Self::request_irq`] or [`Self::request_threaded_irq`] - /// borrow from the registration, so the compiler ensures they are freed first. + /// Use [`IrqVectorRegistration::index`] to obtain an [`IrqVector`] for a given vector + /// index. /// /// # Arguments /// -- cgit From f146c7bc85a51f95c05e0f8173d484eb301ece78 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 13 Aug 2026 18:52:04 +0200 Subject: PCI: Add pci_irq_type() to query the allocated interrupt type Add a helper that returns PCI_IRQ_MSIX, PCI_IRQ_MSI, or PCI_IRQ_INTX based on the interrupt type the PCI core selected after pci_alloc_irq_vectors(). Several drivers already open-code this check against pdev->msix_enabled and pdev->msi_enabled, or even open code this helper [1]. A common helper avoids the duplication and keeps drivers from accessing the bitfield directly (see also [2]). Acked-by: Bjorn Helgaas Tested-by: John Hubbard Link: https://elixir.bootlin.com/linux/v7.1/source/drivers/net/ethernet/aquantia/atlantic/aq_pci_func.c#L196 [1] Inspired-by: John Hubbard Link: https://lore.kernel.org/all/DKKG2QM3YJYB.Z2H2B2UXJ75N@kernel.org/ [2] Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260813165234.620555-5-dakr@kernel.org [ Add missing pci_irq_type() stub for CONFIG_PCI=n. ] Signed-off-by: Danilo Krummrich --- include/linux/pci.h | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/include/linux/pci.h b/include/linux/pci.h index 64b308b6e61c..3d2c1ac645ff 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1783,6 +1783,26 @@ void pci_free_irq_vectors(struct pci_dev *dev); int pci_irq_vector(struct pci_dev *dev, unsigned int nr); const struct cpumask *pci_irq_get_affinity(struct pci_dev *pdev, int vec); +/** + * pci_irq_type - Get the interrupt type of a PCI device + * @pdev: the PCI device to operate on + * + * Discriminate the interrupt type the PCI core selected for this device + * after a successful pci_alloc_irq_vectors() call. + * + * Return: %PCI_IRQ_MSIX, %PCI_IRQ_MSI, or %PCI_IRQ_INTX. + */ +static inline unsigned int pci_irq_type(struct pci_dev *pdev) +{ + if (pdev->msix_enabled) + return PCI_IRQ_MSIX; + + if (pdev->msi_enabled) + return PCI_IRQ_MSI; + + return PCI_IRQ_INTX; +} + #else static inline int pci_msi_vec_count(struct pci_dev *dev) { return -ENOSYS; } static inline void pci_disable_msi(struct pci_dev *dev) { } @@ -1845,6 +1865,11 @@ static inline const struct cpumask *pci_irq_get_affinity(struct pci_dev *pdev, { return cpu_possible_mask; } + +static inline unsigned int pci_irq_type(struct pci_dev *pdev) +{ + return PCI_IRQ_INTX; +} #endif /** @@ -2255,6 +2280,11 @@ static inline bool pci_suspend_retains_context(struct pci_dev *pdev) { return true; } + +static inline unsigned int pci_irq_type(struct pci_dev *pdev) +{ + return 0; +} #endif /* CONFIG_PCI */ /* Include architecture-dependent settings and functions */ -- cgit From 3b5ea0f078e1b72276e99c237f3dfc2fd72938bb Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Thu, 13 Aug 2026 18:52:05 +0200 Subject: rust: pci: expose the allocated interrupt type Add irq_type() on IrqVectorRegistration and IrqVector, wrapping the new pci_irq_type() C function. A driver whose interrupt acknowledgment depends on the type (MSI-X vs MSI vs INTx) queries it here rather than assuming which type the PCI core selected. Tested-by: John Hubbard Suggested-by: John Hubbard Link: https://lore.kernel.org/all/20260808031120.363869-4-jhubbard@nvidia.com/ Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260813165234.620555-6-dakr@kernel.org Signed-off-by: Danilo Krummrich --- rust/helpers/pci.c | 5 +++++ rust/kernel/pci/irq.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/rust/helpers/pci.c b/rust/helpers/pci.c index e44905317d75..23b06becb448 100644 --- a/rust/helpers/pci.c +++ b/rust/helpers/pci.c @@ -24,6 +24,11 @@ __rust_helper bool rust_helper_dev_is_pci(const struct device *dev) return dev_is_pci(dev); } +__rust_helper unsigned int rust_helper_pci_irq_type(struct pci_dev *pdev) +{ + return pci_irq_type(pdev); +} + #ifndef CONFIG_PCI_MSI __rust_helper int rust_helper_pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs, diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs index b6d1699ee3eb..6741046ec1c0 100644 --- a/rust/kernel/pci/irq.rs +++ b/rust/kernel/pci/irq.rs @@ -33,6 +33,16 @@ impl IrqType { IrqType::MsiX => bindings::PCI_IRQ_MSIX, } } + + /// Construct from raw value. + #[inline] + const fn from_raw(raw: u32) -> Self { + match raw { + bindings::PCI_IRQ_MSIX => IrqType::MsiX, + bindings::PCI_IRQ_MSI => IrqType::Msi, + _ => IrqType::Intx, + } + } } /// Set of IRQ types that can be used for PCI interrupt allocation. @@ -90,6 +100,12 @@ impl<'a> IrqVector<'a> { pub fn vectors(&self) -> &'a IrqVectorRegistration<'a> { self.reg } + + /// Returns the interrupt type the PCI core selected for this vector's allocation. + #[inline] + pub fn irq_type(&self) -> IrqType { + self.reg.irq_type() + } } impl<'a> From> for IrqRequest<'a> { @@ -122,6 +138,13 @@ impl<'a> IrqVectorRegistration<'a> { self.len.get() } + /// Returns the interrupt type the PCI core selected for this allocation. + #[inline] + pub fn irq_type(&self) -> IrqType { + // SAFETY: `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. + IrqType::from_raw(unsafe { bindings::pci_irq_type(self.dev.as_raw()) }) + } + /// Returns the [`IrqVector`] at `index`. /// /// Returns [`EINVAL`] if the `index` is out of bounds for the length reported by -- cgit