diff options
Diffstat (limited to 'drivers/base/driver.c')
-rw-r--r-- | drivers/base/driver.c | 464 |
1 files changed, 395 insertions, 69 deletions
diff --git a/drivers/base/driver.c b/drivers/base/driver.c index eec2a2d8a2..fbc5cbebe0 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -1,20 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * driver.c - barebox driver model * * Copyright (c) 2007 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix - * - * See file CREDITS for list of people who contributed to this - * project. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ /** @@ -22,8 +10,11 @@ * @brief barebox's driver model, and devinfo command */ +#define dev_err_probe dev_err_probe + #include <common.h> #include <command.h> +#include <deep-probe.h> #include <driver.h> #include <malloc.h> #include <console.h> @@ -33,9 +24,18 @@ #include <fs.h> #include <of.h> #include <linux/list.h> +#include <linux/overflow.h> #include <linux/err.h> #include <complete.h> #include <pinctrl.h> +#include <featctrl.h> +#include <linux/clk/clk-conf.h> + +#ifdef CONFIG_DEBUG_PROBES +#define pr_report_probe pr_info +#else +#define pr_report_probe pr_debug +#endif LIST_HEAD(device_list); EXPORT_SYMBOL(device_list); @@ -43,24 +43,49 @@ EXPORT_SYMBOL(device_list); LIST_HEAD(driver_list); EXPORT_SYMBOL(driver_list); -static LIST_HEAD(active); +LIST_HEAD(active_device_list); +EXPORT_SYMBOL(active_device_list); static LIST_HEAD(deferred); -struct device_d *get_device_by_name(const char *name) +static LIST_HEAD(device_alias_list); + +struct device *find_device(const char *str) { - struct device_d *dev; + struct device *dev; + struct device_node *np; + + dev = get_device_by_name(str); + if (dev) + return dev; + + np = of_find_node_by_path_or_alias(NULL, str); + if (np) + return of_find_device_by_node(np); + + return NULL; +} + +struct device *get_device_by_name(const char *name) +{ + struct device *dev; + struct device_alias *alias; for_each_device(dev) { if(!strcmp(dev_name(dev), name)) return dev; } + list_for_each_entry(alias, &device_alias_list, list) { + if(!strcmp(alias->name, name)) + return alias->dev; + } + return NULL; } -static struct device_d *get_device_by_name_id(const char *name, int id) +static struct device *get_device_by_name_id(const char *name, int id) { - struct device_d *dev; + struct device *dev; for_each_device(dev) { if(!strcmp(dev->name, name) && id == dev->id) @@ -81,34 +106,78 @@ int get_free_deviceid(const char *name_template) }; } -int device_probe(struct device_d *dev) +static void dev_report_permanent_probe_deferral(struct device *dev) { + if (dev->deferred_probe_reason) + dev_err(dev, "probe permanently deferred (%s)\n", + dev->deferred_probe_reason); + else + dev_err(dev, "probe permanently deferred\n"); +} + +int device_probe(struct device *dev) +{ + static int depth = 0; int ret; + ret = of_feature_controller_check(dev->of_node); + if (ret < 0) + return ret; + if (ret == FEATCTRL_GATED) { + dev_dbg(dev, "feature gated, skipping probe\n"); + return -ENODEV; + } + + depth++; + + pr_report_probe("%*sprobe-> %s\n", depth * 4, "", dev_name(dev)); + pinctrl_select_state_default(dev); + of_clk_set_defaults(dev->of_node, false); + + list_add(&dev->active, &active_device_list); - list_add(&dev->active, &active); + if (dev->bus->probe) + ret = dev->bus->probe(dev); + else if (dev->driver->probe) + ret = dev->driver->probe(dev); + else + ret = 0; - ret = dev->bus->probe(dev); - if (ret == 0) + depth--; + + switch (ret) { + case 0: return 0; + case -EPROBE_DEFER: + /* + * -EPROBE_DEFER should never appear on a deep-probe machine so + * inform the user immediately. + */ + if (deep_probe_is_supported()) { + dev_report_permanent_probe_deferral(dev); + break; + } + + list_move(&dev->active, &deferred); - if (ret == -EPROBE_DEFER) { - list_del(&dev->active); - list_add(&dev->active, &deferred); dev_dbg(dev, "probe deferred\n"); - return ret; + return -EPROBE_DEFER; + case -ENODEV: + case -ENXIO: + dev_dbg(dev, "probe failed: %pe\n", ERR_PTR(ret)); + break; + default: + dev_err(dev, "probe failed: %pe\n", ERR_PTR(ret)); + break; } - list_del(&dev->active); - INIT_LIST_HEAD(&dev->active); - - dev_err(dev, "probe failed: %s\n", strerror(-ret)); + list_del_init(&dev->active); return ret; } -int device_detect(struct device_d *dev) +int device_detect(struct device *dev) { if (!dev->detect) return -ENOSYS; @@ -119,7 +188,7 @@ int device_detect_by_name(const char *__devname) { char *devname = xstrdup(__devname); char *str = devname; - struct device_d *dev; + struct device *dev; int ret = -ENODEV; while (1) { @@ -142,13 +211,13 @@ int device_detect_by_name(const char *__devname) void device_detect_all(void) { - struct device_d *dev; + struct device *dev; for_each_device(dev) device_detect(dev); } -static int match(struct driver_d *drv, struct device_d *dev) +static int match(struct driver *drv, struct device *dev) { int ret; @@ -157,7 +226,7 @@ static int match(struct driver_d *drv, struct device_d *dev) dev->driver = drv; - if (dev->bus->match(dev, drv)) + if (dev->bus->match && dev->bus->match(dev, drv)) goto err_out; ret = device_probe(dev); if (ret) @@ -169,9 +238,9 @@ err_out: return -1; } -int register_device(struct device_d *new_device) +int register_device(struct device *new_device) { - struct driver_d *drv; + struct driver *drv; if (new_device->id == DEVICE_ID_DYNAMIC) { new_device->id = get_free_deviceid(new_device->name); @@ -216,17 +285,24 @@ int register_device(struct device_d *new_device) } EXPORT_SYMBOL(register_device); -int unregister_device(struct device_d *old_dev) +int unregister_device(struct device *old_dev) { + struct device_alias *alias, *at; struct cdev *cdev, *ct; - struct device_d *child, *dt; + struct device *child, *dt; + struct device_node *np; dev_dbg(old_dev, "unregister\n"); dev_remove_parameters(old_dev); if (old_dev->driver) - old_dev->bus->remove(old_dev); + device_remove(old_dev); + + list_for_each_entry_safe(alias, at, &device_alias_list, list) { + if(alias->dev == old_dev) + list_del(&alias->list); + } list_for_each_entry_safe(child, dt, &old_dev->children, sibling) { dev_dbg(old_dev, "unregister child %s\n", dev_name(child)); @@ -234,9 +310,9 @@ int unregister_device(struct device_d *old_dev) } list_for_each_entry_safe(cdev, ct, &old_dev->cdevs, devices_list) { - if (cdev->master) { + if (cdev_is_partition(cdev)) { dev_dbg(old_dev, "unregister part %s\n", cdev->name); - devfs_del_partition(cdev->name); + cdevfs_del_partition(cdev); } } @@ -248,10 +324,45 @@ int unregister_device(struct device_d *old_dev) if (old_dev->parent) list_del(&old_dev->sibling); + np = dev_of_node(old_dev); + if (np && np->dev == old_dev) + np->dev = NULL; + return 0; } EXPORT_SYMBOL(unregister_device); +/** + * free_device_res - free dynamically allocated device members + * @dev: The device + * + * This frees dynamically allocated resources allocated during device + * lifetime, but not the device itself. + */ +void free_device_res(struct device *dev) +{ + free(dev->name); + dev->name = NULL; + free(dev->unique_name); + dev->unique_name = NULL; + free(dev->deferred_probe_reason); +} +EXPORT_SYMBOL(free_device_res); + +/** + * free_device - free a device + * @dev: The device + * + * This frees dynamically allocated resources allocated during device + * lifetime and finally the device itself. + */ +void free_device(struct device *dev) +{ + free_device_res(dev); + free(dev); +} +EXPORT_SYMBOL(free_device); + /* * Loop over list of deferred devices as long as at least one * device is successfully probed. Devices that again request @@ -261,8 +372,8 @@ EXPORT_SYMBOL(unregister_device); */ static int device_probe_deferred(void) { - struct device_d *dev, *tmp; - struct driver_d *drv; + struct device *dev, *tmp; + struct driver *drv; bool success; do { @@ -286,15 +397,15 @@ static int device_probe_deferred(void) } while (success); list_for_each_entry(dev, &deferred, active) - dev_err(dev, "probe permanently deferred\n"); + dev_report_permanent_probe_deferral(dev); return 0; } late_initcall(device_probe_deferred); -struct driver_d *get_driver_by_name(const char *name) +struct driver *get_driver_by_name(const char *name) { - struct driver_d *drv; + struct driver *drv; for_each_driver(drv) { if(!strcmp(name, drv->name)) @@ -304,9 +415,12 @@ struct driver_d *get_driver_by_name(const char *name) return NULL; } -int register_driver(struct driver_d *drv) +int register_driver(struct driver *drv) { - struct device_d *dev = NULL; + struct device *dev = NULL; + + if (!drv->name) + return -EINVAL; debug("register_driver: %s\n", drv->name); @@ -322,7 +436,24 @@ int register_driver(struct driver_d *drv) } EXPORT_SYMBOL(register_driver); -struct resource *dev_get_resource(struct device_d *dev, unsigned long type, +void unregister_driver(struct driver *drv) +{ + struct device *dev; + + list_del(&drv->list); + list_del(&drv->bus_list); + + bus_for_each_device(drv->bus, dev) { + if (dev->driver == drv) { + device_remove(dev); + dev->driver = NULL; + list_del(&dev->active); + INIT_LIST_HEAD(&dev->active); + } + } +} + +struct resource *dev_get_resource(struct device *dev, unsigned long type, int num) { int i, n = 0; @@ -339,7 +470,7 @@ struct resource *dev_get_resource(struct device_d *dev, unsigned long type, return ERR_PTR(-ENOENT); } -void *dev_get_mem_region(struct device_d *dev, int num) +void *dev_get_mem_region(struct device *dev, int num) { struct resource *res; @@ -351,7 +482,7 @@ void *dev_get_mem_region(struct device_d *dev, int num) } EXPORT_SYMBOL(dev_get_mem_region); -struct resource *dev_get_resource_by_name(struct device_d *dev, +struct resource *dev_get_resource_by_name(struct device *dev, unsigned long type, const char *name) { @@ -370,7 +501,19 @@ struct resource *dev_get_resource_by_name(struct device_d *dev, return ERR_PTR(-ENOENT); } -void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *name) +static struct resource *dev_request_iomem_resource(struct device *dev, + const struct resource *res) +{ + return request_iomem_region(dev_name(dev), res->start, res->end); +} + +int dev_request_resource(struct device *dev, const struct resource *res) +{ + return PTR_ERR_OR_ZERO(dev_request_iomem_resource(dev, res)); +} +EXPORT_SYMBOL(dev_request_resource); + +struct resource *dev_request_mem_resource_by_name(struct device *dev, const char *name) { struct resource *res; @@ -378,7 +521,16 @@ void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *n if (IS_ERR(res)) return ERR_CAST(res); - res = request_iomem_region(dev_name(dev), res->start, res->end); + return dev_request_iomem_resource(dev, res); +} +EXPORT_SYMBOL(dev_request_mem_resource_by_name); + +void __iomem *dev_request_mem_region_by_name(struct device *dev, + const char *name) +{ + struct resource *res; + + res = dev_request_mem_resource_by_name(dev, name); if (IS_ERR(res)) return ERR_CAST(res); @@ -386,7 +538,26 @@ void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *n } EXPORT_SYMBOL(dev_request_mem_region_by_name); -struct resource *dev_request_mem_resource(struct device_d *dev, int num) +void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev, + int num, + struct resource **out_res) +{ + struct resource *res; + + res = dev_request_mem_resource(dev, num); + if (IS_ERR(res)) + return IOMEM_ERR_PTR(PTR_ERR(res)); + else if (WARN_ON(IS_ERR_VALUE(res->start))) + return IOMEM_ERR_PTR(-EINVAL); + + if (out_res) + *out_res = res; + + return IOMEM(res->start); +} +EXPORT_SYMBOL(dev_platform_get_and_ioremap_resource); + +struct resource *dev_request_mem_resource(struct device *dev, int num) { struct resource *res; @@ -394,22 +565,22 @@ struct resource *dev_request_mem_resource(struct device_d *dev, int num) if (IS_ERR(res)) return ERR_CAST(res); - return request_iomem_region(dev_name(dev), res->start, res->end); + return dev_request_iomem_resource(dev, res); } -void __iomem *dev_request_mem_region_err_null(struct device_d *dev, int num) +void __iomem *dev_request_mem_region_err_null(struct device *dev, int num) { struct resource *res; res = dev_request_mem_resource(dev, num); - if (IS_ERR(res)) + if (IS_ERR(res) || WARN_ON(!res->start)) return NULL; return IOMEM(res->start); } EXPORT_SYMBOL(dev_request_mem_region_err_null); -void __iomem *dev_request_mem_region(struct device_d *dev, int num) +void __iomem *dev_request_mem_region(struct device *dev, int num) { struct resource *res; @@ -446,9 +617,9 @@ int generic_memmap_ro(struct cdev *cdev, void **map, int flags) * * NOTE: This function expects dev->name to be free()-able, so extra * precautions needs to be taken when mixing its usage with manual - * assignement of device_d.name. + * assignement of device.name. */ -int dev_set_name(struct device_d *dev, const char *fmt, ...) +int dev_set_name(struct device *dev, const char *fmt, ...) { va_list vargs; int err; @@ -467,24 +638,67 @@ int dev_set_name(struct device_d *dev, const char *fmt, ...) */ free(oldname); - WARN_ON(err < 0); - - return err; + return WARN_ON(err < 0) ? err : 0; } EXPORT_SYMBOL_GPL(dev_set_name); +/** + * dev_add_alias - add alias for device + * @dev: device + * @fmt: format string for the device's alias + */ +int dev_add_alias(struct device *dev, const char *fmt, ...) +{ + va_list va, va_copy; + unsigned int len; + struct device_alias *alias; + + va_start(va, fmt); + va_copy(va_copy, va); + len = vsnprintf(NULL, 0, fmt, va_copy); + va_end(va_copy); + + alias = malloc(struct_size(alias, name, len + 1)); + if (!alias) + return -ENOMEM; + + vsnprintf(alias->name, len + 1, fmt, va); + + va_end(va); + + alias->dev = dev; + list_add_tail(&alias->list, &device_alias_list); + + return 0; +} +EXPORT_SYMBOL_GPL(dev_add_alias); + +bool device_remove(struct device *dev) +{ + if (dev->bus && dev->bus->remove) + dev->bus->remove(dev); + else if (dev->driver->remove) + dev->driver->remove(dev); + else + return false; /* nothing to do */ + + return true; +} +EXPORT_SYMBOL_GPL(device_remove); + static void devices_shutdown(void) { - struct device_d *dev; + struct device *dev; - list_for_each_entry(dev, &active, active) { - if (dev->bus->remove) - dev->bus->remove(dev); + list_for_each_entry(dev, &active_device_list, active) { + if (device_remove(dev)) + pr_report_probe("%*sremove-> %s\n", 1 * 4, "", dev_name(dev)); + dev->driver = NULL; } } devshutdown_exitcall(devices_shutdown); -int dev_get_drvdata(struct device_d *dev, const void **data) +int dev_get_drvdata(struct device *dev, const void **data) { if (dev->of_id_entry) { *data = dev->of_id_entry->data; @@ -498,3 +712,115 @@ int dev_get_drvdata(struct device_d *dev, const void **data) return -ENODEV; } + +const void *device_get_match_data(struct device *dev) +{ + if (dev->of_id_entry) + return dev->of_id_entry->data; + + if (dev->id_entry) + return (void *)dev->id_entry->driver_data; + + return NULL; +} + +static void device_set_deferred_probe_reason(struct device *dev, + const struct va_format *vaf) +{ + char *reason; + char *last_char; + + free(dev->deferred_probe_reason); + + reason = xasprintf("%pV", vaf); + + /* drop newline char at end of reason string */ + last_char = reason + strlen(reason) - 1; + + if (*last_char == '\n') + *last_char = '\0'; + + dev->deferred_probe_reason = reason; +} + +/** + * dev_err_probe - probe error check and log helper + * @loglevel: log level configured in source file + * @dev: the pointer to the struct device + * @err: error value to test + * @fmt: printf-style format string + * @...: arguments as specified in the format string + * + * This helper implements common pattern present in probe functions for error + * checking: print debug or error message depending if the error value is + * -EPROBE_DEFER and propagate error upwards. + * + * In case of -EPROBE_DEFER it sets the device's deferred_probe_reason attribute, + * but does not report an error. The error is recorded and displayed later, if + * (and only if) the probe is permanently deferred. For all other error codes, + * it just outputs the error along with the formatted message. + * + * It replaces code sequence:: + * + * if (err != -EPROBE_DEFER) + * dev_err(dev, ...); + * else + * dev_dbg(dev, ...); + * return err; + * + * with:: + * + * return dev_err_probe(dev, err, ...); + * + * Returns @err. + * + */ +int dev_err_probe(struct device *dev, int err, const char *fmt, ...); +int dev_err_probe(struct device *dev, int err, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + + if (err == -EPROBE_DEFER) + device_set_deferred_probe_reason(dev, &vaf); + + dev_printf(err == -EPROBE_DEFER ? MSG_DEBUG : MSG_ERR, + dev, "error %pe: %pV", ERR_PTR(err), &vaf); + + va_end(args); + + return err; +} +EXPORT_SYMBOL_GPL(dev_err_probe); + +/* + * device_find_child - device iterator for locating a particular device. + * @parent: parent struct device + * @match: Callback function to check device + * @data: Data to pass to match function + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero and a reference to the + * current device can be obtained, this function will return to the caller + * and not iterate over any more devices. + */ +struct device *device_find_child(struct device *parent, void *data, + int (*match)(struct device *dev, void *data)) +{ + struct device *child; + + if (!parent) + return NULL; + + list_for_each_entry(child, &parent->children, sibling) { + if (match(child, data)) + return child; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(device_find_child); |