diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:42:18 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:42:18 -0700 |
| commit | 59e6295fac26b8e85c1ea859cdd89fa1e47519d7 (patch) | |
| tree | 7c071f8bab440eddbb569e84e9f31c80f8a4e529 /drivers/base | |
| parent | c6cf4441a3a05bb7273ed022f3e56c4fc591da08 (diff) | |
| parent | 3b5ea0f078e1b72276e99c237f3dfc2fd72938bb (diff) | |
| download | linux-stable-59e6295fac26b8e85c1ea859cdd89fa1e47519d7.tar.gz linux-stable-59e6295fac26b8e85c1ea859cdd89fa1e47519d7.zip | |
Merge tag 'driver-core-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core
Pull driver core updates from Danilo Krummrich:
"container_of:
- Apply typeof_member(), remove the local __mptr variable to
eliminate variable shadowing warnings on nested container_of()
calls, and remove unnecessary parentheses
core:
- Add driver name to probe debug print for initcall_debug
- Avoid repeatedly printing the same 'Fixed dependency cycle' log
- Unwind device_add() on attribute creation failure in
attribute_container_add_class_device()
- Remove statistics group if encryption group creation fails in
transport_add_class_device()
debugfs:
- Fix lockdown check for mmap_prepare()
- Warn if file creation failed due to uninitialized debugfs
device property:
- Implement fw_devlink support for software nodes by adding
software_node_add_links(), which creates fwnode links from
DEV_PROP_REF properties to enable automatic probe ordering. Add
kunit-managed fwnode helpers and test coverage
- Fix infinite loop in fwnode_for_each_child_node() when the
secondary fwnode has more than one child. Add test cases
- Fix out-of-bounds access in software_node_get_reference_args() when
called with index -1 (UINT_MAX)
- Refactor to use RAII approach with __free()
- Add Bartosz Golaszewski as software node reviewer
firmware loader:
- Fix race where a sysfs fallback request can complete before being
queued as pending, leading to a use-after-free on the next fallback
request
- Reject 0-size built-in firmware and fail the build on empty
firmware files in CONFIG_EXTRA_FIRMWARE
kobject:
- Provide __KOBJ_ATTR() and __KOBJ_ATTR_RO/WO() initialization macros
and allow the constification of kobject attributes, enabling them
to reside in read-only memory
platform:
- Provide platform_device_set_of_node(), platform_device_set_fwnode(),
and platform_device_set_of_node_from_dev() helpers that encapsulate
firmware node reference counting for dynamically allocated platform
devices
Convert all in-tree users that manually assigned dev.of_node or
dev.fwnode, fixing a pre-existing refcount bug in powermac. Switch
to counting references of all firmware node types, not only OF
nodes
- Unify the release path for dynamically allocated platform devices
by removing platform_device_release_full(). Amend the fwnode setter
API contract to warn if a primary software node is overwritten. Add
KUnit tests for correct software node removal on device
unregistration
Rust:
- Auxiliary:
- Add registration_data_with() closure-based API for invariant
ForLt types
- Debugfs:
- Migrate BinaryWriter and BinaryReaderMut trait requirements
from kernel::transmute traits to zerocopy traits
- Device:
- Add BoundInternal device context and InternalBoundContext trait
for bus abstractions that need internal access to a bound
device.
- Make the lifetime on Core and CoreInternal invariant to prevent
coercion to shorter lifetimes
- Devres:
- Fix race between concurrent revokers where the losing revoker
could return before the winning revoker finished dropping the
inner data, causing use-after-free.
- Ensure revocation is complete before the device finishes
unbinding by making the synchronization bidirectional.
- Add DevresLt<F: ForLt>, a wrapper around Devres that shortens
'static back to the caller's borrow scope. Implement ForLt and
CovariantForLt for Bar, IoMem, and ExclusiveIoMem
- Driver:
- Switch from index-based to pointer-based device ID info lookup,
storing static references in driver_data. Centralize device ID
handling in device_id.rs, removing the open-coded ACPI/OF
matching logic and duplicate ID table from driver.rs
- I/O:
- Make I/O regions typed (with a dynamically-sized Region type
for the existing untyped case), create view types representing
subregions of a mapped I/O region, and add io_project!() for
safely creating subviews.
- Split Io into a base trait (IoBase) and an extension trait (Io)
with a blanket implementation, preventing implementers from
overriding provided methods that unsafe code relies on.
- Add a SysMem backend for shared system memory with volatile
access, and make Coherent implement Io via an I/O view type.
Add IoSysMap as sum type of Mmio and SysMem. Add copying
methods (memcpy_{from,to}io()) and read_val()/write_val() for
typed access.
- Replace dma_read!()/dma_write!() with io_read!()/io_write!()
for primitives and copying methods for aggregates; drop the old
macros. Convert nova-core to use I/O projection.
- Fix internal shortcut rule dispatch in the register!() macro,
remove unused rule arguments, and use path fragments for alias
destinations
- IRQ:
- Make irq::Registration compatible with lifetime-bound drivers
by removing the 'static bound on Handler/ThreadedHandler and
replacing Devres<RegistrationInner> with direct
request_irq()/free_irq() calls. Handlers can now directly own
lifetime-bound device resources
- PCI:
- Convert IrqVectorRegistration to a lifetime-annotated owning
type, giving drivers explicit control over the allocation
lifetime. IrqVector embeds a resolved IrqRequest, making the
conversion infallible. Remove the redundant
request_irq()/request_threaded_irq() wrappers from pci::Device.
- Add pci_irq_type() C helper and expose it via irq_type() on
IrqVectorRegistration and IrqVector, returning PCI_IRQ_MSIX,
PCI_IRQ_MSI, or PCI_IRQ_INTX.
- Mark pci::Device refcount methods inline
- Serdev:
- Add Rust abstractions for the serial device bus, including
serdev::Driver trait, serdev::Device wrapping struct
serdev_device, and serdev::Adapter implementing
RegistrationOps. Includes a sample driver. Markus Probst takes
over as serdev maintainer for both C and Rust code
- Misc:
- Split ForLt into a base trait (providing the Of<'a> GAT) and an
unsafe CovariantForLt subtrait guaranteeing covariance,
enabling invariant types (e.g. those containing Mutex<&'bound T>)
to participate in the ForLt abstraction.
- Fix Coherent read past EOF returning -ERANGE instead of zero.
- Fix firmware example UB by avoiding null-pointer ARef
misc:
- Avoid iattr allocation in kernfs listxattr by using
kernfs_iattrs_noalloc().
- Unregister SoC bus on early device registration failure.
- Remove unused DMA_FENCE_TRACE Kconfig symbol.
- Fix /sys/module path in comment.
- Refactor ISA bus init to remove nested blocks.
- Remove redundant nodemask clears in numa_init().
- Add kernel-doc for fwnode_operations and sys_soc.h, mark
internal property data as private for kernel-doc, and add
property.h/fwnode.h to driver-api infrastructure docs.
- Add MAINTAINERS entry for sys_soc.h"
* tag 'driver-core-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core: (129 commits)
rust: pci: expose the allocated interrupt type
PCI: Add pci_irq_type() to query the allocated interrupt type
rust: pci: remove request_irq() and request_threaded_irq() from Device
rust: pci: resolve IRQ in index() and embed IrqRequest in IrqVector
rust: pci: convert IrqVectorRegistration to a lifetime-managed owning type
kernfs: avoid iattr allocation in listxattr
rust: serdev: use ThisModule::as_ptr() instead of field access
ACPI/IORT: use platform_device_set_fwnode()
ACPI/APMT: use platform_device_set_fwnode()
firmware_loader: do not queue completed sysfs fallback requests
rust: pci: Mark Device refcount methods inline
rust: irq: make Registration compatible with lifetime-bound drivers
rust: net/phy: remove expansion from doc
rust: dma: return zero for Coherent reads past EOF
rust: io: register: use path fragment for alias destination
rust: io: register: remove unused rule arguments
rust: io: register: dispatch shortcut rules internally
MAINTAINERS: add sys_soc.h to DRIVER CORE
rust: debugfs: remove unsafe blocks from traits impl for Vec
rust: debugfs: migrate debugfs traits requirements to zerocopy
...
Diffstat (limited to 'drivers/base')
| -rw-r--r-- | drivers/base/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/base/arch_numa.c | 4 | ||||
| -rw-r--r-- | drivers/base/attribute_container.c | 7 | ||||
| -rw-r--r-- | drivers/base/core.c | 8 | ||||
| -rw-r--r-- | drivers/base/dd.c | 4 | ||||
| -rw-r--r-- | drivers/base/firmware_loader/builtin/Makefile | 32 | ||||
| -rw-r--r-- | drivers/base/firmware_loader/builtin/main.c | 2 | ||||
| -rw-r--r-- | drivers/base/firmware_loader/fallback.c | 10 | ||||
| -rw-r--r-- | drivers/base/isa.c | 16 | ||||
| -rw-r--r-- | drivers/base/module.c | 2 | ||||
| -rw-r--r-- | drivers/base/platform.c | 90 | ||||
| -rw-r--r-- | drivers/base/property.c | 41 | ||||
| -rw-r--r-- | drivers/base/soc.c | 15 | ||||
| -rw-r--r-- | drivers/base/swnode.c | 2 | ||||
| -rw-r--r-- | drivers/base/test/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/base/test/platform-device-test.c | 180 | ||||
| -rw-r--r-- | drivers/base/test/property-entry-test.c | 137 | ||||
| -rw-r--r-- | drivers/base/transport_class.c | 5 |
18 files changed, 493 insertions, 72 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index f7d385cbd..43f20ca95 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 diff --git a/drivers/base/arch_numa.c b/drivers/base/arch_numa.c index d4e426aa5..6476227b7 100644 --- a/drivers/base/arch_numa.c +++ b/drivers/base/arch_numa.c @@ -228,10 +228,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; diff --git a/drivers/base/attribute_container.c b/drivers/base/attribute_container.c index 4ad26b8dd..3c4563742 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; } /** diff --git a/drivers/base/core.c b/drivers/base/core.c index 4d0266829..4c0c37399 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(); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 60c005223..f6525a7ee 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; } diff --git a/drivers/base/firmware_loader/builtin/Makefile b/drivers/base/firmware_loader/builtin/Makefile index 6c067dedc..c0264b15d 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 diff --git a/drivers/base/firmware_loader/builtin/main.c b/drivers/base/firmware_loader/builtin/main.c index d36befebb..1dcebe8e7 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; diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c index 3ef0b312a..00d6b6e59 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); diff --git a/drivers/base/isa.c b/drivers/base/isa.c index 5887e4211..4e9f68080 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); diff --git a/drivers/base/module.c b/drivers/base/module.c index 218aaa096..6789dca48 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 */ diff --git a/drivers/base/platform.c b/drivers/base/platform.c index fb9120b0b..66f9ec73d 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); - of_node_put(pa->pdev.dev.of_node); + 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 @@ -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) { @@ -694,6 +701,70 @@ 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. 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) +{ + platform_device_set_fwnode(pdev, of_fwnode_handle(np)); +} +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. 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)); +} +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. 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); +} +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 * @@ -868,8 +939,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) { @@ -890,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); diff --git a/drivers/base/property.c b/drivers/base/property.c index e08eadd66..b136c339d 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -7,10 +7,10 @@ * Mika Westerberg <mika.westerberg@linux.intel.com> */ +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/err.h> #include <linux/export.h> -#include <linux/kconfig.h> #include <linux/of.h> #include <linux/property.h> #include <linux/phy.h> @@ -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); @@ -807,18 +804,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); @@ -1115,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 @@ -1134,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); diff --git a/drivers/base/soc.c b/drivers/base/soc.c index 65ce72d49..af7d71393 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); diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c index 1f2315858..84a77ffa3 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; diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig index 1ecf07912..542ce0753 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/platform-device-test.c b/drivers/base/test/platform-device-test.c index 6355a2231..9ce563f76 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 <kunit/fwnode.h> #include <kunit/platform_device.h> #include <kunit/resource.h> #include <linux/device.h> #include <linux/device/bus.h> +#include <linux/fwnode.h> #include <linux/of_platform.h> #include <linux/platform_device.h> +#include <linux/property.h> #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"); diff --git a/drivers/base/test/property-entry-test.c b/drivers/base/test/property-entry-test.c index a8657eb06..855e73b9b 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 <kunit/test.h> + +#include <linux/of.h> #include <linux/property.h> #include <linux/types.h> @@ -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 <dtor@chromium.org>"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); MODULE_LICENSE("GPL"); diff --git a/drivers/base/transport_class.c b/drivers/base/transport_class.c index 416e9f819..351c3d3ce 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: |
