diff options
Diffstat (limited to 'drivers/base/driver.c')
-rw-r--r-- | drivers/base/driver.c | 335 |
1 files changed, 256 insertions, 79 deletions
diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 303ca061ce..fbc5cbebe0 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -24,9 +24,11 @@ #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 @@ -45,21 +47,45 @@ 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 *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_d *dev; + 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) @@ -80,53 +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->device_node, false); + of_clk_set_defaults(dev->of_node, false); list_add(&dev->active, &active_device_list); - ret = dev->bus->probe(dev); - if (ret == 0) - goto out; + if (dev->bus->probe) + ret = dev->bus->probe(dev); + else if (dev->driver->probe) + ret = dev->driver->probe(dev); + else + ret = 0; - if (ret == -EPROBE_DEFER) { - list_del(&dev->active); - list_add(&dev->active, &deferred); + 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_err(dev, "probe deferred\n"); - else - dev_dbg(dev, "probe deferred\n"); - goto out; - } + if (deep_probe_is_supported()) { + dev_report_permanent_probe_deferral(dev); + break; + } - list_del(&dev->active); - INIT_LIST_HEAD(&dev->active); + list_move(&dev->active, &deferred); + + dev_dbg(dev, "probe deferred\n"); + 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; + } - if (ret == -ENODEV || ret == -ENXIO) - dev_dbg(dev, "probe failed: %s\n", strerror(-ret)); - else - dev_err(dev, "probe failed: %s\n", strerror(-ret)); + list_del_init(&dev->active); -out: - depth--; return ret; } -int device_detect(struct device_d *dev) +int device_detect(struct device *dev) { if (!dev->detect) return -ENOSYS; @@ -137,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) { @@ -160,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; @@ -175,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) @@ -187,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); @@ -234,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)); @@ -252,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); } } @@ -266,6 +324,10 @@ 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); @@ -277,12 +339,13 @@ EXPORT_SYMBOL(unregister_device); * This frees dynamically allocated resources allocated during device * lifetime, but not the device itself. */ -void free_device_res(struct device_d *dev) +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); @@ -293,7 +356,7 @@ EXPORT_SYMBOL(free_device_res); * This frees dynamically allocated resources allocated during device * lifetime and finally the device itself. */ -void free_device(struct device_d *dev) +void free_device(struct device *dev) { free_device_res(dev); free(dev); @@ -309,8 +372,8 @@ EXPORT_SYMBOL(free_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 { @@ -334,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)) @@ -352,9 +415,9 @@ 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; @@ -373,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; @@ -390,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; @@ -402,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) { @@ -421,7 +501,19 @@ struct resource *dev_get_resource_by_name(struct device_d *dev, return ERR_PTR(-ENOENT); } -struct resource *dev_request_mem_resource_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; @@ -429,11 +521,12 @@ struct resource *dev_request_mem_resource_by_name(struct device_d *dev, const ch 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); } EXPORT_SYMBOL(dev_request_mem_resource_by_name); -void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *name) +void __iomem *dev_request_mem_region_by_name(struct device *dev, + const char *name) { struct resource *res; @@ -445,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; @@ -453,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; @@ -505,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; @@ -530,24 +642,63 @@ int dev_set_name(struct device_d *dev, const char *fmt, ...) } 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; - int depth = 0; + struct device *dev; list_for_each_entry(dev, &active_device_list, active) { - if (dev->bus->remove) { - depth++; - pr_report_probe("%*sremove-> %s\n", depth * 4, "", dev_name(dev)); - dev->bus->remove(dev); - dev->driver = NULL; - depth--; - } + 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; @@ -562,7 +713,7 @@ int dev_get_drvdata(struct device_d *dev, const void **data) return -ENODEV; } -const void *device_get_match_data(struct device_d *dev) +const void *device_get_match_data(struct device *dev) { if (dev->of_id_entry) return dev->of_id_entry->data; @@ -573,6 +724,25 @@ const void *device_get_match_data(struct device_d *dev) 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 @@ -584,8 +754,12 @@ const void *device_get_match_data(struct device_d *dev) * 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 also defer probe reason, which can be - * checked later by reading devices_deferred debugfs attribute. + * + * 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) @@ -601,8 +775,8 @@ const void *device_get_match_data(struct device_d *dev) * Returns @err. * */ -int dev_err_probe(const struct device_d *dev, int err, const char *fmt, ...); -int dev_err_probe(const struct device_d *dev, int err, const char *fmt, ...) +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; @@ -611,6 +785,9 @@ int dev_err_probe(const struct device_d *dev, int err, const char *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); @@ -622,7 +799,7 @@ EXPORT_SYMBOL_GPL(dev_err_probe); /* * device_find_child - device iterator for locating a particular device. - * @parent: parent struct device_d + * @parent: parent struct device * @match: Callback function to check device * @data: Data to pass to match function * @@ -631,10 +808,10 @@ EXPORT_SYMBOL_GPL(dev_err_probe); * current device can be obtained, this function will return to the caller * and not iterate over any more devices. */ -struct device_d *device_find_child(struct device_d *parent, void *data, - int (*match)(struct device_d *dev, void *data)) +struct device *device_find_child(struct device *parent, void *data, + int (*match)(struct device *dev, void *data)) { - struct device_d *child; + struct device *child; if (!parent) return NULL; |