diff options
| author | Miguel Ojeda <ojeda@kernel.org> | 2026-08-16 15:32:33 +0200 |
|---|---|---|
| committer | Miguel Ojeda <ojeda@kernel.org> | 2026-08-23 21:27:17 +0200 |
| commit | 5febf432df1cfa5b25d99b54c32103fafbdd0eb9 (patch) | |
| tree | 93a1ed1d337bc53adf1e572c2586d347703b286e /drivers/base/driver.c | |
| download | linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.tar.gz linux-stable-5febf432df1cfa5b25d99b54c32103fafbdd0eb9.zip | |
kbuild: rust: keep Rust objects out of Clang LTO with inline helpersgrafted
Under `CONFIG_LTO_CLANG` + `CONFIG_RUST_INLINE_HELPERS`, one may hit
`objtool` errors such as:
vmlinux.o: error: objtool: _R..._3Gsp4boot+0xd6a:
can't find jump dest instruction at .text._R..._3Gsp4boot+0x1dfd
The reason is that in such builds, the Clang invocation that compiles
the combined Rust plus helpers bitcode emits LLVM bitcode (again) --
the final code generation happens in the linker's LTO step, which the
`-mllvm` trap options passed to Clang do not reach.
This, in turn, means that unreachable traps are missing, and the
impossible paths do not merely fallthrough to the next symbol, but past
the end of their own section, since LTO builds place each function in
its own section.
Thus filter `CC_FLAGS_LTO` out of the Clang invocation, so that it always
emits machine code directly, with the traps in place.
Assisted-by: LLM
Cc: Gary Guo <gary@garyguo.net>
Cc: Boqun Feng <boqun@kernel.org>
Cc: Alice Ryhl <aliceryhl@google.com>
Cc: Matthew Maurer <mmaurer@google.com>
Cc: Josh Poimboeuf <jpoimboe@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: stable@vger.kernel.org
Fixes: 3a2486cc1da5 ("kbuild: rust: provide an option to inline C helpers into Rust")
Acked-by: Gary Guo <gary@garyguo.net>
Link: https://patch.msgid.link/20260816133233.197500-2-ojeda@kernel.org
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Diffstat (limited to 'drivers/base/driver.c')
| -rw-r--r-- | drivers/base/driver.c | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/drivers/base/driver.c b/drivers/base/driver.c new file mode 100644 index 000000000..5d9c39081 --- /dev/null +++ b/drivers/base/driver.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * driver.c - centralized device driver management + * + * Copyright (c) 2002-3 Patrick Mochel + * Copyright (c) 2002-3 Open Source Development Labs + * Copyright (c) 2007 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (c) 2007 Novell Inc. + */ + +#include <linux/device/driver.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include "base.h" + +static struct device *next_device(struct klist_iter *i) +{ + struct klist_node *n = klist_next(i); + struct device *dev = NULL; + struct device_private *dev_prv; + + if (n) { + dev_prv = to_device_private_driver(n); + dev = dev_prv->device; + } + return dev; +} + +/** + * driver_for_each_device - Iterator for devices bound to a driver. + * @drv: Driver we're iterating. + * @start: Device to begin with + * @data: Data to pass to the callback. + * @fn: Function to call for each device. + * + * Iterate over the @drv's list of devices calling @fn for each one. + */ +int driver_for_each_device(struct device_driver *drv, struct device *start, + void *data, device_iter_t fn) +{ + struct klist_iter i; + struct device *dev; + int error = 0; + + if (!drv) + return -EINVAL; + + klist_iter_init_node(&drv->p->klist_devices, &i, + start ? &start->p->knode_driver : NULL); + while (!error && (dev = next_device(&i))) + error = fn(dev, data); + klist_iter_exit(&i); + return error; +} +EXPORT_SYMBOL_GPL(driver_for_each_device); + +/** + * driver_find_device - device iterator for locating a particular device. + * @drv: The device's driver + * @start: Device to begin with + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This is similar to the driver_for_each_device() function above, but + * it returns a reference to a device that is 'found' for later use, as + * determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + */ +struct device *driver_find_device(const struct device_driver *drv, + struct device *start, const void *data, + device_match_t match) +{ + struct klist_iter i; + struct device *dev; + + if (!drv || !drv->p) + return NULL; + + klist_iter_init_node(&drv->p->klist_devices, &i, + (start ? &start->p->knode_driver : NULL)); + while ((dev = next_device(&i))) { + if (match(dev, data)) { + get_device(dev); + break; + } + } + klist_iter_exit(&i); + return dev; +} +EXPORT_SYMBOL_GPL(driver_find_device); + +/** + * driver_create_file - create sysfs file for driver. + * @drv: driver. + * @attr: driver attribute descriptor. + */ +int driver_create_file(const struct device_driver *drv, + const struct driver_attribute *attr) +{ + int error; + + if (drv) + error = sysfs_create_file(&drv->p->kobj, &attr->attr); + else + error = -EINVAL; + return error; +} +EXPORT_SYMBOL_GPL(driver_create_file); + +/** + * driver_remove_file - remove sysfs file for driver. + * @drv: driver. + * @attr: driver attribute descriptor. + */ +void driver_remove_file(const struct device_driver *drv, + const struct driver_attribute *attr) +{ + if (drv) + sysfs_remove_file(&drv->p->kobj, &attr->attr); +} +EXPORT_SYMBOL_GPL(driver_remove_file); + +int driver_add_groups(const struct device_driver *drv, + const struct attribute_group *const *groups) +{ + return sysfs_create_groups(&drv->p->kobj, groups); +} + +void driver_remove_groups(const struct device_driver *drv, + const struct attribute_group *const *groups) +{ + sysfs_remove_groups(&drv->p->kobj, groups); +} + +/** + * driver_register - register driver with bus + * @drv: driver to register + * + * We pass off most of the work to the bus_add_driver() call, + * since most of the things we have to do deal with the bus + * structures. + */ +int driver_register(struct device_driver *drv) +{ + int ret; + struct device_driver *other; + + if (!bus_is_registered(drv->bus)) { + pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n", + drv->name, drv->bus->name); + return -EINVAL; + } + + if ((drv->bus->probe && drv->probe) || + (drv->bus->remove && drv->remove) || + (drv->bus->shutdown && drv->shutdown)) + pr_warn("Driver '%s' needs updating - please use " + "bus_type methods\n", drv->name); + + other = driver_find(drv->name, drv->bus); + if (other) { + pr_err("Error: Driver '%s' is already registered, " + "aborting...\n", drv->name); + return -EBUSY; + } + + ret = bus_add_driver(drv); + if (ret) + return ret; + ret = driver_add_groups(drv, drv->groups); + if (ret) { + bus_remove_driver(drv); + return ret; + } + kobject_uevent(&drv->p->kobj, KOBJ_ADD); + deferred_probe_extend_timeout(); + + return ret; +} +EXPORT_SYMBOL_GPL(driver_register); + +/** + * driver_unregister - remove driver from system. + * @drv: driver. + * + * Again, we pass off most of the work to the bus-level call. + */ +void driver_unregister(struct device_driver *drv) +{ + if (!drv || !drv->p) { + WARN(1, "Unexpected driver unregister!\n"); + return; + } + driver_remove_groups(drv, drv->groups); + bus_remove_driver(drv); +} +EXPORT_SYMBOL_GPL(driver_unregister); |
