From 4bdb35506b89cbbd150c1baa284e7c191698241f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 10 Oct 2016 14:37:56 +0200 Subject: driver core: Add a wrapper around __device_release_driver() Add an internal wrapper around __device_release_driver() that will acquire device locks and do the necessary checks before calling it. The next patch will make use of it. Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/base/dd.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/dd.c b/drivers/base/dd.c index d22a7260f42b..df4ab5509c04 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -811,6 +811,22 @@ static void __device_release_driver(struct device *dev) } } +static void device_release_driver_internal(struct device *dev, + struct device_driver *drv, + struct device *parent) +{ + if (parent) + device_lock(parent); + + device_lock(dev); + if (!drv || drv == dev->driver) + __device_release_driver(dev); + + device_unlock(dev); + if (parent) + device_unlock(parent); +} + /** * device_release_driver - manually detach device from driver. * @dev: device. @@ -825,9 +841,7 @@ void device_release_driver(struct device *dev) * within their ->remove callback for the same device, they * will deadlock right here. */ - device_lock(dev); - __device_release_driver(dev); - device_unlock(dev); + device_release_driver_internal(dev, NULL, NULL); } EXPORT_SYMBOL_GPL(device_release_driver); @@ -852,15 +866,7 @@ void driver_detach(struct device_driver *drv) dev = dev_prv->device; get_device(dev); spin_unlock(&drv->p->klist_devices.k_lock); - - if (dev->parent) /* Needed for USB */ - device_lock(dev->parent); - device_lock(dev); - if (dev->driver == drv) - __device_release_driver(dev); - device_unlock(dev); - if (dev->parent) - device_unlock(dev->parent); + device_release_driver_internal(dev, drv, dev->parent); put_device(dev); } } -- cgit v1.2.3 From 9ed9895370aedd6032af2a9181c62c394d08223b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 30 Oct 2016 17:32:16 +0100 Subject: driver core: Functional dependencies tracking support Currently, there is a problem with taking functional dependencies between devices into account. What I mean by a "functional dependency" is when the driver of device B needs device A to be functional and (generally) its driver to be present in order to work properly. This has certain consequences for power management (suspend/resume and runtime PM ordering) and shutdown ordering of these devices. In general, it also implies that the driver of A needs to be working for B to be probed successfully and it cannot be unbound from the device before the B's driver. Support for representing those functional dependencies between devices is added here to allow the driver core to track them and act on them in certain cases where applicable. The argument for doing that in the driver core is that there are quite a few distinct use cases involving device dependencies, they are relatively hard to get right in a driver (if one wants to address all of them properly) and it only gets worse if multiplied by the number of drivers potentially needing to do it. Morever, at least one case (asynchronous system suspend/resume) cannot be handled in a single driver at all, because it requires the driver of A to wait for B to suspend (during system suspend) and the driver of B to wait for A to resume (during system resume). For this reason, represent dependencies between devices as "links", with the help of struct device_link objects each containing pointers to the "linked" devices, a list node for each of them, status information, flags, and an RCU head for synchronization. Also add two new list heads, representing the lists of links to the devices that depend on the given one (consumers) and to the devices depended on by it (suppliers), and a "driver presence status" field (needed for figuring out initial states of device links) to struct device. The entire data structure consisting of all of the lists of link objects for all devices is protected by a mutex (for link object addition/removal and for list walks during device driver probing and removal) and by SRCU (for list walking in other case that will be introduced by subsequent change sets). If CONFIG_SRCU is not selected, however, an rwsem is used for protecting the entire data structure. In addition, each link object has an internal status field whose value reflects whether or not drivers are bound to the devices pointed to by the link or probing/removal of their drivers is in progress etc. That field is only modified under the device links mutex, but it may be read outside of it in some cases (introduced by subsequent change sets), so modifications of it are annotated with WRITE_ONCE(). New links are added by calling device_link_add() which takes three arguments: pointers to the devices in question and flags. In particular, if DL_FLAG_STATELESS is set in the flags, the link status is not to be taken into account for this link and the driver core will not manage it. In turn, if DL_FLAG_AUTOREMOVE is set in the flags, the driver core will remove the link automatically when the consumer device driver unbinds from it. One of the actions carried out by device_link_add() is to reorder the lists used for device shutdown and system suspend/resume to put the consumer device along with all of its children and all of its consumers (and so on, recursively) to the ends of those lists in order to ensure the right ordering between all of the supplier and consumer devices. For this reason, it is not possible to create a link between two devices if the would-be supplier device already depends on the would-be consumer device as either a direct descendant of it or a consumer of one of its direct descendants or one of its consumers and so on. There are two types of link objects, persistent and non-persistent. The persistent ones stay around until one of the target devices is deleted, while the non-persistent ones are removed automatically when the consumer driver unbinds from its device (ie. they are assumed to be valid only as long as the consumer device has a driver bound to it). Persistent links are created by default and non-persistent links are created when the DL_FLAG_AUTOREMOVE flag is passed to device_link_add(). Both persistent and non-persistent device links can be deleted with an explicit call to device_link_del(). Links created without the DL_FLAG_STATELESS flag set are managed by the driver core using a simple state machine. There are 5 states each link can be in: DORMANT (unused), AVAILABLE (the supplier driver is present and functional), CONSUMER_PROBE (the consumer driver is probing), ACTIVE (both supplier and consumer drivers are present and functional), and SUPPLIER_UNBIND (the supplier driver is unbinding). The driver core updates the link state automatically depending on what happens to the linked devices and for each link state specific actions are taken in addition to that. For example, if the supplier driver unbinds from its device, the driver core will also unbind the drivers of all of its consumers automatically under the assumption that they cannot function properly without the supplier. Analogously, the driver core will only allow the consumer driver to bind to its device if the supplier driver is present and functional (ie. the link is in the AVAILABLE state). If that's not the case, it will rely on the existing deferred probing mechanism to wait for the supplier driver to become available. Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/base/base.h | 13 ++ drivers/base/core.c | 540 +++++++++++++++++++++++++++++++++++++++++++++ drivers/base/dd.c | 41 +++- drivers/base/power/main.c | 2 + drivers/base/power/power.h | 10 + include/linux/device.h | 80 +++++++ include/linux/pm.h | 1 + 7 files changed, 682 insertions(+), 5 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/base.h b/drivers/base/base.h index e05db388bd1c..e19b1008e5fb 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -107,6 +107,9 @@ extern void bus_remove_device(struct device *dev); extern int bus_add_driver(struct device_driver *drv); extern void bus_remove_driver(struct device_driver *drv); +extern void device_release_driver_internal(struct device *dev, + struct device_driver *drv, + struct device *parent); extern void driver_detach(struct device_driver *drv); extern int driver_probe_device(struct device_driver *drv, struct device *dev); @@ -152,3 +155,13 @@ extern int devtmpfs_init(void); #else static inline int devtmpfs_init(void) { return 0; } #endif + +/* Device links support */ +extern int device_links_read_lock(void); +extern void device_links_read_unlock(int idx); +extern int device_links_check_suppliers(struct device *dev); +extern void device_links_driver_bound(struct device *dev); +extern void device_links_driver_cleanup(struct device *dev); +extern void device_links_no_driver(struct device *dev); +extern bool device_links_busy(struct device *dev); +extern void device_links_unbind_consumers(struct device *dev); diff --git a/drivers/base/core.c b/drivers/base/core.c index ce057a568673..3c5ff17f578f 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -44,6 +44,541 @@ static int __init sysfs_deprecated_setup(char *arg) early_param("sysfs.deprecated", sysfs_deprecated_setup); #endif +/* Device links support. */ + +#ifdef CONFIG_SRCU +static DEFINE_MUTEX(device_links_lock); +DEFINE_STATIC_SRCU(device_links_srcu); + +static inline void device_links_write_lock(void) +{ + mutex_lock(&device_links_lock); +} + +static inline void device_links_write_unlock(void) +{ + mutex_unlock(&device_links_lock); +} + +int device_links_read_lock(void) +{ + return srcu_read_lock(&device_links_srcu); +} + +void device_links_read_unlock(int idx) +{ + srcu_read_unlock(&device_links_srcu, idx); +} +#else /* !CONFIG_SRCU */ +static DECLARE_RWSEM(device_links_lock); + +static inline void device_links_write_lock(void) +{ + down_write(&device_links_lock); +} + +static inline void device_links_write_unlock(void) +{ + up_write(&device_links_lock); +} + +int device_links_read_lock(void) +{ + down_read(&device_links_lock); + return 0; +} + +void device_links_read_unlock(int not_used) +{ + up_read(&device_links_lock); +} +#endif /* !CONFIG_SRCU */ + +/** + * device_is_dependent - Check if one device depends on another one + * @dev: Device to check dependencies for. + * @target: Device to check against. + * + * Check if @target depends on @dev or any device dependent on it (its child or + * its consumer etc). Return 1 if that is the case or 0 otherwise. + */ +static int device_is_dependent(struct device *dev, void *target) +{ + struct device_link *link; + int ret; + + if (WARN_ON(dev == target)) + return 1; + + ret = device_for_each_child(dev, target, device_is_dependent); + if (ret) + return ret; + + list_for_each_entry(link, &dev->links.consumers, s_node) { + if (WARN_ON(link->consumer == target)) + return 1; + + ret = device_is_dependent(link->consumer, target); + if (ret) + break; + } + return ret; +} + +static int device_reorder_to_tail(struct device *dev, void *not_used) +{ + struct device_link *link; + + /* + * Devices that have not been registered yet will be put to the ends + * of the lists during the registration, so skip them here. + */ + if (device_is_registered(dev)) + devices_kset_move_last(dev); + + if (device_pm_initialized(dev)) + device_pm_move_last(dev); + + device_for_each_child(dev, NULL, device_reorder_to_tail); + list_for_each_entry(link, &dev->links.consumers, s_node) + device_reorder_to_tail(link->consumer, NULL); + + return 0; +} + +/** + * device_link_add - Create a link between two devices. + * @consumer: Consumer end of the link. + * @supplier: Supplier end of the link. + * @flags: Link flags. + * + * If the DL_FLAG_AUTOREMOVE is set, the link will be removed automatically + * when the consumer device driver unbinds from it. The combination of both + * DL_FLAG_AUTOREMOVE and DL_FLAG_STATELESS set is invalid and will cause NULL + * to be returned. + * + * A side effect of the link creation is re-ordering of dpm_list and the + * devices_kset list by moving the consumer device and all devices depending + * on it to the ends of these lists (that does not happen to devices that have + * not been registered when this function is called). + * + * The supplier device is required to be registered when this function is called + * and NULL will be returned if that is not the case. The consumer device need + * not be registerd, however. + */ +struct device_link *device_link_add(struct device *consumer, + struct device *supplier, u32 flags) +{ + struct device_link *link; + + if (!consumer || !supplier || + ((flags & DL_FLAG_STATELESS) && (flags & DL_FLAG_AUTOREMOVE))) + return NULL; + + device_links_write_lock(); + device_pm_lock(); + + /* + * If the supplier has not been fully registered yet or there is a + * reverse dependency between the consumer and the supplier already in + * the graph, return NULL. + */ + if (!device_pm_initialized(supplier) + || device_is_dependent(consumer, supplier)) { + link = NULL; + goto out; + } + + list_for_each_entry(link, &supplier->links.consumers, s_node) + if (link->consumer == consumer) + goto out; + + link = kmalloc(sizeof(*link), GFP_KERNEL); + if (!link) + goto out; + + get_device(supplier); + link->supplier = supplier; + INIT_LIST_HEAD(&link->s_node); + get_device(consumer); + link->consumer = consumer; + INIT_LIST_HEAD(&link->c_node); + link->flags = flags; + + /* Deterine the initial link state. */ + if (flags & DL_FLAG_STATELESS) { + link->status = DL_STATE_NONE; + } else { + switch (supplier->links.status) { + case DL_DEV_DRIVER_BOUND: + switch (consumer->links.status) { + case DL_DEV_PROBING: + link->status = DL_STATE_CONSUMER_PROBE; + break; + case DL_DEV_DRIVER_BOUND: + link->status = DL_STATE_ACTIVE; + break; + default: + link->status = DL_STATE_AVAILABLE; + break; + } + break; + case DL_DEV_UNBINDING: + link->status = DL_STATE_SUPPLIER_UNBIND; + break; + default: + link->status = DL_STATE_DORMANT; + break; + } + } + + /* + * Move the consumer and all of the devices depending on it to the end + * of dpm_list and the devices_kset list. + * + * It is necessary to hold dpm_list locked throughout all that or else + * we may end up suspending with a wrong ordering of it. + */ + device_reorder_to_tail(consumer, NULL); + + list_add_tail_rcu(&link->s_node, &supplier->links.consumers); + list_add_tail_rcu(&link->c_node, &consumer->links.suppliers); + + dev_info(consumer, "Linked as a consumer to %s\n", dev_name(supplier)); + + out: + device_pm_unlock(); + device_links_write_unlock(); + return link; +} +EXPORT_SYMBOL_GPL(device_link_add); + +static void device_link_free(struct device_link *link) +{ + put_device(link->consumer); + put_device(link->supplier); + kfree(link); +} + +#ifdef CONFIG_SRCU +static void __device_link_free_srcu(struct rcu_head *rhead) +{ + device_link_free(container_of(rhead, struct device_link, rcu_head)); +} + +static void __device_link_del(struct device_link *link) +{ + dev_info(link->consumer, "Dropping the link to %s\n", + dev_name(link->supplier)); + + list_del_rcu(&link->s_node); + list_del_rcu(&link->c_node); + call_srcu(&device_links_srcu, &link->rcu_head, __device_link_free_srcu); +} +#else /* !CONFIG_SRCU */ +static void __device_link_del(struct device_link *link) +{ + dev_info(link->consumer, "Dropping the link to %s\n", + dev_name(link->supplier)); + + list_del(&link->s_node); + list_del(&link->c_node); + device_link_free(link); +} +#endif /* !CONFIG_SRCU */ + +/** + * device_link_del - Delete a link between two devices. + * @link: Device link to delete. + * + * The caller must ensure proper synchronization of this function with runtime + * PM. + */ +void device_link_del(struct device_link *link) +{ + device_links_write_lock(); + device_pm_lock(); + __device_link_del(link); + device_pm_unlock(); + device_links_write_unlock(); +} +EXPORT_SYMBOL_GPL(device_link_del); + +static void device_links_missing_supplier(struct device *dev) +{ + struct device_link *link; + + list_for_each_entry(link, &dev->links.suppliers, c_node) + if (link->status == DL_STATE_CONSUMER_PROBE) + WRITE_ONCE(link->status, DL_STATE_AVAILABLE); +} + +/** + * device_links_check_suppliers - Check presence of supplier drivers. + * @dev: Consumer device. + * + * Check links from this device to any suppliers. Walk the list of the device's + * links to suppliers and see if all of them are available. If not, simply + * return -EPROBE_DEFER. + * + * We need to guarantee that the supplier will not go away after the check has + * been positive here. It only can go away in __device_release_driver() and + * that function checks the device's links to consumers. This means we need to + * mark the link as "consumer probe in progress" to make the supplier removal + * wait for us to complete (or bad things may happen). + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + */ +int device_links_check_suppliers(struct device *dev) +{ + struct device_link *link; + int ret = 0; + + device_links_write_lock(); + + list_for_each_entry(link, &dev->links.suppliers, c_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + if (link->status != DL_STATE_AVAILABLE) { + device_links_missing_supplier(dev); + ret = -EPROBE_DEFER; + break; + } + WRITE_ONCE(link->status, DL_STATE_CONSUMER_PROBE); + } + dev->links.status = DL_DEV_PROBING; + + device_links_write_unlock(); + return ret; +} + +/** + * device_links_driver_bound - Update device links after probing its driver. + * @dev: Device to update the links for. + * + * The probe has been successful, so update links from this device to any + * consumers by changing their status to "available". + * + * Also change the status of @dev's links to suppliers to "active". + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + */ +void device_links_driver_bound(struct device *dev) +{ + struct device_link *link; + + device_links_write_lock(); + + list_for_each_entry(link, &dev->links.consumers, s_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + WARN_ON(link->status != DL_STATE_DORMANT); + WRITE_ONCE(link->status, DL_STATE_AVAILABLE); + } + + list_for_each_entry(link, &dev->links.suppliers, c_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + WARN_ON(link->status != DL_STATE_CONSUMER_PROBE); + WRITE_ONCE(link->status, DL_STATE_ACTIVE); + } + + dev->links.status = DL_DEV_DRIVER_BOUND; + + device_links_write_unlock(); +} + +/** + * __device_links_no_driver - Update links of a device without a driver. + * @dev: Device without a drvier. + * + * Delete all non-persistent links from this device to any suppliers. + * + * Persistent links stay around, but their status is changed to "available", + * unless they already are in the "supplier unbind in progress" state in which + * case they need not be updated. + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + */ +static void __device_links_no_driver(struct device *dev) +{ + struct device_link *link, *ln; + + list_for_each_entry_safe_reverse(link, ln, &dev->links.suppliers, c_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + if (link->flags & DL_FLAG_AUTOREMOVE) + __device_link_del(link); + else if (link->status != DL_STATE_SUPPLIER_UNBIND) + WRITE_ONCE(link->status, DL_STATE_AVAILABLE); + } + + dev->links.status = DL_DEV_NO_DRIVER; +} + +void device_links_no_driver(struct device *dev) +{ + device_links_write_lock(); + __device_links_no_driver(dev); + device_links_write_unlock(); +} + +/** + * device_links_driver_cleanup - Update links after driver removal. + * @dev: Device whose driver has just gone away. + * + * Update links to consumers for @dev by changing their status to "dormant" and + * invoke %__device_links_no_driver() to update links to suppliers for it as + * appropriate. + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + */ +void device_links_driver_cleanup(struct device *dev) +{ + struct device_link *link; + + device_links_write_lock(); + + list_for_each_entry(link, &dev->links.consumers, s_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + WARN_ON(link->flags & DL_FLAG_AUTOREMOVE); + WARN_ON(link->status != DL_STATE_SUPPLIER_UNBIND); + WRITE_ONCE(link->status, DL_STATE_DORMANT); + } + + __device_links_no_driver(dev); + + device_links_write_unlock(); +} + +/** + * device_links_busy - Check if there are any busy links to consumers. + * @dev: Device to check. + * + * Check each consumer of the device and return 'true' if its link's status + * is one of "consumer probe" or "active" (meaning that the given consumer is + * probing right now or its driver is present). Otherwise, change the link + * state to "supplier unbind" to prevent the consumer from being probed + * successfully going forward. + * + * Return 'false' if there are no probing or active consumers. + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + */ +bool device_links_busy(struct device *dev) +{ + struct device_link *link; + bool ret = false; + + device_links_write_lock(); + + list_for_each_entry(link, &dev->links.consumers, s_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + if (link->status == DL_STATE_CONSUMER_PROBE + || link->status == DL_STATE_ACTIVE) { + ret = true; + break; + } + WRITE_ONCE(link->status, DL_STATE_SUPPLIER_UNBIND); + } + + dev->links.status = DL_DEV_UNBINDING; + + device_links_write_unlock(); + return ret; +} + +/** + * device_links_unbind_consumers - Force unbind consumers of the given device. + * @dev: Device to unbind the consumers of. + * + * Walk the list of links to consumers for @dev and if any of them is in the + * "consumer probe" state, wait for all device probes in progress to complete + * and start over. + * + * If that's not the case, change the status of the link to "supplier unbind" + * and check if the link was in the "active" state. If so, force the consumer + * driver to unbind and start over (the consumer will not re-probe as we have + * changed the state of the link already). + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + */ +void device_links_unbind_consumers(struct device *dev) +{ + struct device_link *link; + + start: + device_links_write_lock(); + + list_for_each_entry(link, &dev->links.consumers, s_node) { + enum device_link_state status; + + if (link->flags & DL_FLAG_STATELESS) + continue; + + status = link->status; + if (status == DL_STATE_CONSUMER_PROBE) { + device_links_write_unlock(); + + wait_for_device_probe(); + goto start; + } + WRITE_ONCE(link->status, DL_STATE_SUPPLIER_UNBIND); + if (status == DL_STATE_ACTIVE) { + struct device *consumer = link->consumer; + + get_device(consumer); + + device_links_write_unlock(); + + device_release_driver_internal(consumer, NULL, + consumer->parent); + put_device(consumer); + goto start; + } + } + + device_links_write_unlock(); +} + +/** + * device_links_purge - Delete existing links to other devices. + * @dev: Target device. + */ +static void device_links_purge(struct device *dev) +{ + struct device_link *link, *ln; + + /* + * Delete all of the remaining links from this device to any other + * devices (either consumers or suppliers). + */ + device_links_write_lock(); + + list_for_each_entry_safe_reverse(link, ln, &dev->links.suppliers, c_node) { + WARN_ON(link->status == DL_STATE_ACTIVE); + __device_link_del(link); + } + + list_for_each_entry_safe_reverse(link, ln, &dev->links.consumers, s_node) { + WARN_ON(link->status != DL_STATE_DORMANT && + link->status != DL_STATE_NONE); + __device_link_del(link); + } + + device_links_write_unlock(); +} + +/* Device links support end. */ + int (*platform_notify)(struct device *dev) = NULL; int (*platform_notify_remove)(struct device *dev) = NULL; static struct kobject *dev_kobj; @@ -711,6 +1246,9 @@ void device_initialize(struct device *dev) #ifdef CONFIG_GENERIC_MSI_IRQ INIT_LIST_HEAD(&dev->msi_list); #endif + INIT_LIST_HEAD(&dev->links.consumers); + INIT_LIST_HEAD(&dev->links.suppliers); + dev->links.status = DL_DEV_NO_DRIVER; } EXPORT_SYMBOL_GPL(device_initialize); @@ -1258,6 +1796,8 @@ void device_del(struct device *dev) if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_DEL_DEVICE, dev); + + device_links_purge(dev); dpm_sysfs_remove(dev); if (parent) klist_del(&dev->p->knode_parent); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index df4ab5509c04..b2bca3cf0dd2 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -244,6 +244,7 @@ static void driver_bound(struct device *dev) __func__, dev_name(dev)); klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices); + device_links_driver_bound(dev); device_pm_check_callbacks(dev); @@ -337,6 +338,10 @@ static int really_probe(struct device *dev, struct device_driver *drv) return ret; } + ret = device_links_check_suppliers(dev); + if (ret) + return ret; + atomic_inc(&probe_count); pr_debug("bus: '%s': %s: probing driver %s with device %s\n", drv->bus->name, __func__, drv->name, dev_name(dev)); @@ -415,6 +420,7 @@ probe_failed: blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_DRIVER_NOT_BOUND, dev); pinctrl_bind_failed: + device_links_no_driver(dev); devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; @@ -771,7 +777,7 @@ EXPORT_SYMBOL_GPL(driver_attach); * __device_release_driver() must be called with @dev lock held. * When called for a USB interface, @dev->parent lock must be held as well. */ -static void __device_release_driver(struct device *dev) +static void __device_release_driver(struct device *dev, struct device *parent) { struct device_driver *drv; @@ -780,6 +786,25 @@ static void __device_release_driver(struct device *dev) if (driver_allows_async_probing(drv)) async_synchronize_full(); + while (device_links_busy(dev)) { + device_unlock(dev); + if (parent) + device_unlock(parent); + + device_links_unbind_consumers(dev); + if (parent) + device_lock(parent); + + device_lock(dev); + /* + * A concurrent invocation of the same function might + * have released the driver successfully while this one + * was waiting, so check for that. + */ + if (dev->driver != drv) + return; + } + pm_runtime_get_sync(dev); driver_sysfs_remove(dev); @@ -795,6 +820,8 @@ static void __device_release_driver(struct device *dev) dev->bus->remove(dev); else if (drv->remove) drv->remove(dev); + + device_links_driver_cleanup(dev); devres_release_all(dev); dev->driver = NULL; dev_set_drvdata(dev, NULL); @@ -811,16 +838,16 @@ static void __device_release_driver(struct device *dev) } } -static void device_release_driver_internal(struct device *dev, - struct device_driver *drv, - struct device *parent) +void device_release_driver_internal(struct device *dev, + struct device_driver *drv, + struct device *parent) { if (parent) device_lock(parent); device_lock(dev); if (!drv || drv == dev->driver) - __device_release_driver(dev); + __device_release_driver(dev, parent); device_unlock(dev); if (parent) @@ -833,6 +860,10 @@ static void device_release_driver_internal(struct device *dev, * * Manually detach device from driver. * When called for a USB interface, @dev->parent lock must be held. + * + * If this function is to be called with @dev->parent lock held, ensure that + * the device's consumers are unbound in advance or that their locks can be + * acquired under the @dev->parent lock. */ void device_release_driver(struct device *dev) { diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index e44944f4be77..420914061405 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -131,6 +131,7 @@ void device_pm_add(struct device *dev) dev_warn(dev, "parent %s should not be sleeping\n", dev_name(dev->parent)); list_add_tail(&dev->power.entry, &dpm_list); + dev->power.in_dpm_list = true; mutex_unlock(&dpm_list_mtx); } @@ -145,6 +146,7 @@ void device_pm_remove(struct device *dev) complete_all(&dev->power.completion); mutex_lock(&dpm_list_mtx); list_del_init(&dev->power.entry); + dev->power.in_dpm_list = false; mutex_unlock(&dpm_list_mtx); device_wakeup_disable(dev); pm_runtime_remove(dev); diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 50e30e7b059d..0ba7842d665b 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -127,6 +127,11 @@ extern void device_pm_move_after(struct device *, struct device *); extern void device_pm_move_last(struct device *); extern void device_pm_check_callbacks(struct device *dev); +static inline bool device_pm_initialized(struct device *dev) +{ + return dev->power.in_dpm_list; +} + #else /* !CONFIG_PM_SLEEP */ static inline void device_pm_sleep_init(struct device *dev) {} @@ -146,6 +151,11 @@ static inline void device_pm_move_last(struct device *dev) {} static inline void device_pm_check_callbacks(struct device *dev) {} +static inline bool device_pm_initialized(struct device *dev) +{ + return device_is_registered(dev); +} + #endif /* !CONFIG_PM_SLEEP */ static inline void device_pm_init(struct device *dev) diff --git a/include/linux/device.h b/include/linux/device.h index bc41e87a969b..9cae2feaf5cb 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -707,6 +707,81 @@ struct device_dma_parameters { unsigned long segment_boundary_mask; }; +/** + * enum device_link_state - Device link states. + * @DL_STATE_NONE: The presence of the drivers is not being tracked. + * @DL_STATE_DORMANT: None of the supplier/consumer drivers is present. + * @DL_STATE_AVAILABLE: The supplier driver is present, but the consumer is not. + * @DL_STATE_CONSUMER_PROBE: The consumer is probing (supplier driver present). + * @DL_STATE_ACTIVE: Both the supplier and consumer drivers are present. + * @DL_STATE_SUPPLIER_UNBIND: The supplier driver is unbinding. + */ +enum device_link_state { + DL_STATE_NONE = -1, + DL_STATE_DORMANT = 0, + DL_STATE_AVAILABLE, + DL_STATE_CONSUMER_PROBE, + DL_STATE_ACTIVE, + DL_STATE_SUPPLIER_UNBIND, +}; + +/* + * Device link flags. + * + * STATELESS: The core won't track the presence of supplier/consumer drivers. + * AUTOREMOVE: Remove this link automatically on consumer driver unbind. + */ +#define DL_FLAG_STATELESS BIT(0) +#define DL_FLAG_AUTOREMOVE BIT(1) + +/** + * struct device_link - Device link representation. + * @supplier: The device on the supplier end of the link. + * @s_node: Hook to the supplier device's list of links to consumers. + * @consumer: The device on the consumer end of the link. + * @c_node: Hook to the consumer device's list of links to suppliers. + * @status: The state of the link (with respect to the presence of drivers). + * @flags: Link flags. + * @rcu_head: An RCU head to use for deferred execution of SRCU callbacks. + */ +struct device_link { + struct device *supplier; + struct list_head s_node; + struct device *consumer; + struct list_head c_node; + enum device_link_state status; + u32 flags; +#ifdef CONFIG_SRCU + struct rcu_head rcu_head; +#endif +}; + +/** + * enum dl_dev_state - Device driver presence tracking information. + * @DL_DEV_NO_DRIVER: There is no driver attached to the device. + * @DL_DEV_PROBING: A driver is probing. + * @DL_DEV_DRIVER_BOUND: The driver has been bound to the device. + * @DL_DEV_UNBINDING: The driver is unbinding from the device. + */ +enum dl_dev_state { + DL_DEV_NO_DRIVER = 0, + DL_DEV_PROBING, + DL_DEV_DRIVER_BOUND, + DL_DEV_UNBINDING, +}; + +/** + * struct dev_links_info - Device data related to device links. + * @suppliers: List of links to supplier devices. + * @consumers: List of links to consumer devices. + * @status: Driver status information. + */ +struct dev_links_info { + struct list_head suppliers; + struct list_head consumers; + enum dl_dev_state status; +}; + /** * struct device - The basic device structure * @parent: The device's "parent" device, the device to which it is attached. @@ -799,6 +874,7 @@ struct device { core doesn't touch it */ void *driver_data; /* Driver data, set and get with dev_set/get_drvdata */ + struct dev_links_info links; struct dev_pm_info power; struct dev_pm_domain *pm_domain; @@ -1116,6 +1192,10 @@ extern void device_shutdown(void); /* debugging and troubleshooting/diagnostic helpers. */ extern const char *dev_driver_string(const struct device *dev); +/* Device links interface. */ +struct device_link *device_link_add(struct device *consumer, + struct device *supplier, u32 flags); +void device_link_del(struct device_link *link); #ifdef CONFIG_PRINTK diff --git a/include/linux/pm.h b/include/linux/pm.h index 06eb353182ab..721a70241fcd 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -559,6 +559,7 @@ struct dev_pm_info { pm_message_t power_state; unsigned int can_wakeup:1; unsigned int async_suspend:1; + bool in_dpm_list:1; /* Owned by the PM core */ bool is_prepared:1; /* Owned by the PM core */ bool is_suspended:1; /* Ditto */ bool is_noirq_suspended:1; -- cgit v1.2.3 From 8c73b4288496407d91bc616df3f7c62a88356cb2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 30 Oct 2016 17:28:49 +0100 Subject: PM / sleep: Make async suspend/resume of devices use device links Make the device suspend/resume part of the core system suspend/resume code use device links to ensure that supplier and consumer devices will be suspended and resumed in the right order in case of async suspend/resume. The idea, roughly, is to use dpm_wait() to wait for all consumers before a supplier device suspend and to wait for all suppliers before a consumer device resume. Signed-off-by: Rafael J. Wysocki Tested-by: Marek Szyprowski Signed-off-by: Greg Kroah-Hartman --- drivers/base/power/main.c | 85 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 6 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 420914061405..04bcb11ed8de 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -246,6 +246,62 @@ static void dpm_wait_for_children(struct device *dev, bool async) device_for_each_child(dev, &async, dpm_wait_fn); } +static void dpm_wait_for_suppliers(struct device *dev, bool async) +{ + struct device_link *link; + int idx; + + idx = device_links_read_lock(); + + /* + * If the supplier goes away right after we've checked the link to it, + * we'll wait for its completion to change the state, but that's fine, + * because the only things that will block as a result are the SRCU + * callbacks freeing the link objects for the links in the list we're + * walking. + */ + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + if (READ_ONCE(link->status) != DL_STATE_DORMANT) + dpm_wait(link->supplier, async); + + device_links_read_unlock(idx); +} + +static void dpm_wait_for_superior(struct device *dev, bool async) +{ + dpm_wait(dev->parent, async); + dpm_wait_for_suppliers(dev, async); +} + +static void dpm_wait_for_consumers(struct device *dev, bool async) +{ + struct device_link *link; + int idx; + + idx = device_links_read_lock(); + + /* + * The status of a device link can only be changed from "dormant" by a + * probe, but that cannot happen during system suspend/resume. In + * theory it can change to "dormant" at that time, but then it is + * reasonable to wait for the target device anyway (eg. if it goes + * away, it's better to wait for it to go away completely and then + * continue instead of trying to continue in parallel with its + * unregistration). + */ + list_for_each_entry_rcu(link, &dev->links.consumers, s_node) + if (READ_ONCE(link->status) != DL_STATE_DORMANT) + dpm_wait(link->consumer, async); + + device_links_read_unlock(idx); +} + +static void dpm_wait_for_subordinate(struct device *dev, bool async) +{ + dpm_wait_for_children(dev, async); + dpm_wait_for_consumers(dev, async); +} + /** * pm_op - Return the PM operation appropriate for given PM event. * @ops: PM operations to choose from. @@ -490,7 +546,7 @@ static int device_resume_noirq(struct device *dev, pm_message_t state, bool asyn if (!dev->power.is_noirq_suspended) goto Out; - dpm_wait(dev->parent, async); + dpm_wait_for_superior(dev, async); if (dev->pm_domain) { info = "noirq power domain "; @@ -620,7 +676,7 @@ static int device_resume_early(struct device *dev, pm_message_t state, bool asyn if (!dev->power.is_late_suspended) goto Out; - dpm_wait(dev->parent, async); + dpm_wait_for_superior(dev, async); if (dev->pm_domain) { info = "early power domain "; @@ -752,7 +808,7 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) goto Complete; } - dpm_wait(dev->parent, async); + dpm_wait_for_superior(dev, async); dpm_watchdog_set(&wd, dev); device_lock(dev); @@ -1040,7 +1096,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a if (dev->power.syscore || dev->power.direct_complete) goto Complete; - dpm_wait_for_children(dev, async); + dpm_wait_for_subordinate(dev, async); if (dev->pm_domain) { info = "noirq power domain "; @@ -1187,7 +1243,7 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as if (dev->power.syscore || dev->power.direct_complete) goto Complete; - dpm_wait_for_children(dev, async); + dpm_wait_for_subordinate(dev, async); if (dev->pm_domain) { info = "late power domain "; @@ -1344,6 +1400,22 @@ static int legacy_suspend(struct device *dev, pm_message_t state, return error; } +static void dpm_clear_suppliers_direct_complete(struct device *dev) +{ + struct device_link *link; + int idx; + + idx = device_links_read_lock(); + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) { + spin_lock_irq(&link->supplier->power.lock); + link->supplier->power.direct_complete = false; + spin_unlock_irq(&link->supplier->power.lock); + } + + device_links_read_unlock(idx); +} + /** * device_suspend - Execute "suspend" callbacks for given device. * @dev: Device to handle. @@ -1360,7 +1432,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) TRACE_DEVICE(dev); TRACE_SUSPEND(0); - dpm_wait_for_children(dev, async); + dpm_wait_for_subordinate(dev, async); if (async_error) goto Complete; @@ -1456,6 +1528,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) spin_unlock_irq(&parent->power.lock); } + dpm_clear_suppliers_direct_complete(dev); } device_unlock(dev); -- cgit v1.2.3 From 21d5c57b3726166421251e94dabab047baaf8ce4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 30 Oct 2016 17:32:31 +0100 Subject: PM / runtime: Use device links Modify the runtime PM framework to use device links to ensure that supplier devices will not be suspended if any of their consumer devices are active. The idea is to reference count suppliers on the consumer's resume and drop references to them on its suspend. The information on whether or not the supplier has been reference counted by the consumer's (runtime) resume is stored in a new field (rpm_active) in the link object for each link. It may be necessary to clean up those references when the supplier is unbinding and that's why the links whose status is DEVICE_LINK_SUPPLIER_UNBIND are skipped by the runtime suspend and resume code. The above means that if the consumer device is probed in the runtime-active state, the supplier has to be resumed and reference counted by device_link_add() so the code works as expected on its (runtime) suspend. There is a new flag, DEVICE_LINK_RPM_ACTIVE, to tell device_link_add() about that (in which case the caller is responsible for making sure that the consumer really will be runtime-active when runtime PM is enabled for it). The other new link flag, DEVICE_LINK_PM_RUNTIME, tells the core whether or not the link should be used for runtime PM at all. Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 27 +++++++- drivers/base/dd.c | 3 + drivers/base/power/runtime.c | 157 +++++++++++++++++++++++++++++++++++++++++-- include/linux/device.h | 6 ++ include/linux/pm_runtime.h | 6 ++ 5 files changed, 193 insertions(+), 6 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/core.c b/drivers/base/core.c index 3c5ff17f578f..876c62b12462 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -152,6 +152,14 @@ static int device_reorder_to_tail(struct device *dev, void *not_used) * @supplier: Supplier end of the link. * @flags: Link flags. * + * The caller is responsible for the proper synchronization of the link creation + * with runtime PM. First, setting the DL_FLAG_PM_RUNTIME flag will cause the + * runtime PM framework to take the link into account. Second, if the + * DL_FLAG_RPM_ACTIVE flag is set in addition to it, the supplier devices will + * be forced into the active metastate and reference-counted upon the creation + * of the link. If DL_FLAG_PM_RUNTIME is not set, DL_FLAG_RPM_ACTIVE will be + * ignored. + * * If the DL_FLAG_AUTOREMOVE is set, the link will be removed automatically * when the consumer device driver unbinds from it. The combination of both * DL_FLAG_AUTOREMOVE and DL_FLAG_STATELESS set is invalid and will cause NULL @@ -193,10 +201,19 @@ struct device_link *device_link_add(struct device *consumer, if (link->consumer == consumer) goto out; - link = kmalloc(sizeof(*link), GFP_KERNEL); + link = kzalloc(sizeof(*link), GFP_KERNEL); if (!link) goto out; + if ((flags & DL_FLAG_PM_RUNTIME) && (flags & DL_FLAG_RPM_ACTIVE)) { + if (pm_runtime_get_sync(supplier) < 0) { + pm_runtime_put_noidle(supplier); + kfree(link); + link = NULL; + goto out; + } + link->rpm_active = true; + } get_device(supplier); link->supplier = supplier; INIT_LIST_HEAD(&link->s_node); @@ -213,6 +230,14 @@ struct device_link *device_link_add(struct device *consumer, case DL_DEV_DRIVER_BOUND: switch (consumer->links.status) { case DL_DEV_PROBING: + /* + * Balance the decrementation of the supplier's + * runtime PM usage counter after consumer probe + * in driver_probe_device(). + */ + if (flags & DL_FLAG_PM_RUNTIME) + pm_runtime_get_sync(supplier); + link->status = DL_STATE_CONSUMER_PROBE; break; case DL_DEV_DRIVER_BOUND: diff --git a/drivers/base/dd.c b/drivers/base/dd.c index b2bca3cf0dd2..43be1cc751a4 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -513,6 +513,7 @@ int driver_probe_device(struct device_driver *drv, struct device *dev) pr_debug("bus: '%s': %s: matched device %s with driver %s\n", drv->bus->name, __func__, dev_name(dev), drv->name); + pm_runtime_get_suppliers(dev); if (dev->parent) pm_runtime_get_sync(dev->parent); @@ -523,6 +524,7 @@ int driver_probe_device(struct device_driver *drv, struct device *dev) if (dev->parent) pm_runtime_put(dev->parent); + pm_runtime_put_suppliers(dev); return ret; } @@ -806,6 +808,7 @@ static void __device_release_driver(struct device *dev, struct device *parent) } pm_runtime_get_sync(dev); + pm_runtime_clean_up_links(dev); driver_sysfs_remove(dev); diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 82a081ea4317..462f90e952f8 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -12,6 +12,8 @@ #include #include #include + +#include "../base.h" #include "power.h" typedef int (*pm_callback_t)(struct device *); @@ -258,6 +260,42 @@ static int rpm_check_suspend_allowed(struct device *dev) return retval; } +static int rpm_get_suppliers(struct device *dev) +{ + struct device_link *link; + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) { + int retval; + + if (!(link->flags & DL_FLAG_PM_RUNTIME)) + continue; + + if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND || + link->rpm_active) + continue; + + retval = pm_runtime_get_sync(link->supplier); + if (retval < 0) { + pm_runtime_put_noidle(link->supplier); + return retval; + } + link->rpm_active = true; + } + return 0; +} + +static void rpm_put_suppliers(struct device *dev) +{ + struct device_link *link; + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + if (link->rpm_active && + READ_ONCE(link->status) != DL_STATE_SUPPLIER_UNBIND) { + pm_runtime_put(link->supplier); + link->rpm_active = false; + } +} + /** * __rpm_callback - Run a given runtime PM callback for a given device. * @cb: Runtime PM callback to run. @@ -266,19 +304,55 @@ static int rpm_check_suspend_allowed(struct device *dev) static int __rpm_callback(int (*cb)(struct device *), struct device *dev) __releases(&dev->power.lock) __acquires(&dev->power.lock) { - int retval; + int retval, idx; - if (dev->power.irq_safe) + if (dev->power.irq_safe) { spin_unlock(&dev->power.lock); - else + } else { spin_unlock_irq(&dev->power.lock); + /* + * Resume suppliers if necessary. + * + * The device's runtime PM status cannot change until this + * routine returns, so it is safe to read the status outside of + * the lock. + */ + if (dev->power.runtime_status == RPM_RESUMING) { + idx = device_links_read_lock(); + + retval = rpm_get_suppliers(dev); + if (retval) + goto fail; + + device_links_read_unlock(idx); + } + } + retval = cb(dev); - if (dev->power.irq_safe) + if (dev->power.irq_safe) { spin_lock(&dev->power.lock); - else + } else { + /* + * If the device is suspending and the callback has returned + * success, drop the usage counters of the suppliers that have + * been reference counted on its resume. + * + * Do that if resume fails too. + */ + if ((dev->power.runtime_status == RPM_SUSPENDING && !retval) + || (dev->power.runtime_status == RPM_RESUMING && retval)) { + idx = device_links_read_lock(); + + fail: + rpm_put_suppliers(dev); + + device_links_read_unlock(idx); + } + spin_lock_irq(&dev->power.lock); + } return retval; } @@ -1446,6 +1520,79 @@ void pm_runtime_remove(struct device *dev) pm_runtime_reinit(dev); } +/** + * pm_runtime_clean_up_links - Prepare links to consumers for driver removal. + * @dev: Device whose driver is going to be removed. + * + * Check links from this device to any consumers and if any of them have active + * runtime PM references to the device, drop the usage counter of the device + * (once per link). + * + * Links with the DL_FLAG_STATELESS flag set are ignored. + * + * Since the device is guaranteed to be runtime-active at the point this is + * called, nothing else needs to be done here. + * + * Moreover, this is called after device_links_busy() has returned 'false', so + * the status of each link is guaranteed to be DL_STATE_SUPPLIER_UNBIND and + * therefore rpm_active can't be manipulated concurrently. + */ +void pm_runtime_clean_up_links(struct device *dev) +{ + struct device_link *link; + int idx; + + idx = device_links_read_lock(); + + list_for_each_entry_rcu(link, &dev->links.consumers, s_node) { + if (link->flags & DL_FLAG_STATELESS) + continue; + + if (link->rpm_active) { + pm_runtime_put_noidle(dev); + link->rpm_active = false; + } + } + + device_links_read_unlock(idx); +} + +/** + * pm_runtime_get_suppliers - Resume and reference-count supplier devices. + * @dev: Consumer device. + */ +void pm_runtime_get_suppliers(struct device *dev) +{ + struct device_link *link; + int idx; + + idx = device_links_read_lock(); + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + if (link->flags & DL_FLAG_PM_RUNTIME) + pm_runtime_get_sync(link->supplier); + + device_links_read_unlock(idx); +} + +/** + * pm_runtime_put_suppliers - Drop references to supplier devices. + * @dev: Consumer device. + */ +void pm_runtime_put_suppliers(struct device *dev) +{ + struct device_link *link; + int idx; + + idx = device_links_read_lock(); + + list_for_each_entry_rcu(link, &dev->links.suppliers, c_node) + if (link->flags & DL_FLAG_PM_RUNTIME) + pm_runtime_put(link->supplier); + + device_links_read_unlock(idx); +} + /** * pm_runtime_force_suspend - Force a device into suspend state if needed. * @dev: Device to suspend. diff --git a/include/linux/device.h b/include/linux/device.h index 9cae2feaf5cb..49f453892ca5 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -730,9 +730,13 @@ enum device_link_state { * * STATELESS: The core won't track the presence of supplier/consumer drivers. * AUTOREMOVE: Remove this link automatically on consumer driver unbind. + * PM_RUNTIME: If set, the runtime PM framework will use this link. + * RPM_ACTIVE: Run pm_runtime_get_sync() on the supplier during link creation. */ #define DL_FLAG_STATELESS BIT(0) #define DL_FLAG_AUTOREMOVE BIT(1) +#define DL_FLAG_PM_RUNTIME BIT(2) +#define DL_FLAG_RPM_ACTIVE BIT(3) /** * struct device_link - Device link representation. @@ -742,6 +746,7 @@ enum device_link_state { * @c_node: Hook to the consumer device's list of links to suppliers. * @status: The state of the link (with respect to the presence of drivers). * @flags: Link flags. + * @rpm_active: Whether or not the consumer device is runtime-PM-active. * @rcu_head: An RCU head to use for deferred execution of SRCU callbacks. */ struct device_link { @@ -751,6 +756,7 @@ struct device_link { struct list_head c_node; enum device_link_state status; u32 flags; + bool rpm_active; #ifdef CONFIG_SRCU struct rcu_head rcu_head; #endif diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 2e14d2667b6c..c2ee87138e4a 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -55,6 +55,9 @@ extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev); extern void pm_runtime_update_max_time_suspended(struct device *dev, s64 delta_ns); extern void pm_runtime_set_memalloc_noio(struct device *dev, bool enable); +extern void pm_runtime_clean_up_links(struct device *dev); +extern void pm_runtime_get_suppliers(struct device *dev); +extern void pm_runtime_put_suppliers(struct device *dev); static inline void pm_suspend_ignore_children(struct device *dev, bool enable) { @@ -186,6 +189,9 @@ static inline unsigned long pm_runtime_autosuspend_expiration( struct device *dev) { return 0; } static inline void pm_runtime_set_memalloc_noio(struct device *dev, bool enable){} +static inline void pm_runtime_clean_up_links(struct device *dev) {} +static inline void pm_runtime_get_suppliers(struct device *dev) {} +static inline void pm_runtime_put_suppliers(struct device *dev) {} #endif /* !CONFIG_PM */ -- cgit v1.2.3 From baa8809f60971d10220dfe79248f54b2b265f003 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 30 Oct 2016 17:32:43 +0100 Subject: PM / runtime: Optimize the use of device links If the device has no links to suppliers that should be used for runtime PM (links with DEVICE_LINK_PM_RUNTIME set), there is no reason to walk the list of suppliers for that device during runtime suspend and resume. Add a simple mechanism to detect that case and possibly avoid the extra unnecessary overhead. Signed-off-by: Rafael J. Wysocki Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 20 +++++++++++++------- drivers/base/power/runtime.c | 23 ++++++++++++++++++++--- include/linux/pm.h | 1 + include/linux/pm_runtime.h | 4 ++++ 4 files changed, 38 insertions(+), 10 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/core.c b/drivers/base/core.c index 876c62b12462..d0c9df5cdd9e 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -205,14 +205,17 @@ struct device_link *device_link_add(struct device *consumer, if (!link) goto out; - if ((flags & DL_FLAG_PM_RUNTIME) && (flags & DL_FLAG_RPM_ACTIVE)) { - if (pm_runtime_get_sync(supplier) < 0) { - pm_runtime_put_noidle(supplier); - kfree(link); - link = NULL; - goto out; + if (flags & DL_FLAG_PM_RUNTIME) { + if (flags & DL_FLAG_RPM_ACTIVE) { + if (pm_runtime_get_sync(supplier) < 0) { + pm_runtime_put_noidle(supplier); + kfree(link); + link = NULL; + goto out; + } + link->rpm_active = true; } - link->rpm_active = true; + pm_runtime_new_link(consumer); } get_device(supplier); link->supplier = supplier; @@ -296,6 +299,9 @@ static void __device_link_del(struct device_link *link) dev_info(link->consumer, "Dropping the link to %s\n", dev_name(link->supplier)); + if (link->flags & DL_FLAG_PM_RUNTIME) + pm_runtime_drop_link(link->consumer); + list_del_rcu(&link->s_node); list_del_rcu(&link->c_node); call_srcu(&device_links_srcu, &link->rcu_head, __device_link_free_srcu); diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 462f90e952f8..ba7b4a8c07e5 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -305,6 +305,7 @@ static int __rpm_callback(int (*cb)(struct device *), struct device *dev) __releases(&dev->power.lock) __acquires(&dev->power.lock) { int retval, idx; + bool use_links = dev->power.links_count > 0; if (dev->power.irq_safe) { spin_unlock(&dev->power.lock); @@ -318,7 +319,7 @@ static int __rpm_callback(int (*cb)(struct device *), struct device *dev) * routine returns, so it is safe to read the status outside of * the lock. */ - if (dev->power.runtime_status == RPM_RESUMING) { + if (use_links && dev->power.runtime_status == RPM_RESUMING) { idx = device_links_read_lock(); retval = rpm_get_suppliers(dev); @@ -341,8 +342,9 @@ static int __rpm_callback(int (*cb)(struct device *), struct device *dev) * * Do that if resume fails too. */ - if ((dev->power.runtime_status == RPM_SUSPENDING && !retval) - || (dev->power.runtime_status == RPM_RESUMING && retval)) { + if (use_links + && ((dev->power.runtime_status == RPM_SUSPENDING && !retval) + || (dev->power.runtime_status == RPM_RESUMING && retval))) { idx = device_links_read_lock(); fail: @@ -1593,6 +1595,21 @@ void pm_runtime_put_suppliers(struct device *dev) device_links_read_unlock(idx); } +void pm_runtime_new_link(struct device *dev) +{ + spin_lock_irq(&dev->power.lock); + dev->power.links_count++; + spin_unlock_irq(&dev->power.lock); +} + +void pm_runtime_drop_link(struct device *dev) +{ + spin_lock_irq(&dev->power.lock); + WARN_ON(dev->power.links_count == 0); + dev->power.links_count--; + spin_unlock_irq(&dev->power.lock); +} + /** * pm_runtime_force_suspend - Force a device into suspend state if needed. * @dev: Device to suspend. diff --git a/include/linux/pm.h b/include/linux/pm.h index 721a70241fcd..ccfe00ecc7e6 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -597,6 +597,7 @@ struct dev_pm_info { unsigned int use_autosuspend:1; unsigned int timer_autosuspends:1; unsigned int memalloc_noio:1; + unsigned int links_count; enum rpm_request request; enum rpm_status runtime_status; int runtime_error; diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index c2ee87138e4a..73814877537d 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -58,6 +58,8 @@ extern void pm_runtime_set_memalloc_noio(struct device *dev, bool enable); extern void pm_runtime_clean_up_links(struct device *dev); extern void pm_runtime_get_suppliers(struct device *dev); extern void pm_runtime_put_suppliers(struct device *dev); +extern void pm_runtime_new_link(struct device *dev); +extern void pm_runtime_drop_link(struct device *dev); static inline void pm_suspend_ignore_children(struct device *dev, bool enable) { @@ -192,6 +194,8 @@ static inline void pm_runtime_set_memalloc_noio(struct device *dev, static inline void pm_runtime_clean_up_links(struct device *dev) {} static inline void pm_runtime_get_suppliers(struct device *dev) {} static inline void pm_runtime_put_suppliers(struct device *dev) {} +static inline void pm_runtime_new_link(struct device *dev) {} +static inline void pm_runtime_drop_link(struct device *dev) {} #endif /* !CONFIG_PM */ -- cgit v1.2.3 From 6751667a29d6fd64afb9ce30567ad616b68ed789 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Tue, 16 Aug 2016 14:34:18 +0100 Subject: driver core: Add deferred_probe attribute to devices in sysfs It is sometimes useful to know that a device is on the deferred probe list rather than, say, not having a driver available. Expose this information to user-space. Signed-off-by: Ben Hutchings Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-devices-deferred_probe | 12 ++++++++++++ drivers/base/base.h | 2 ++ drivers/base/core.c | 7 +++++++ drivers/base/dd.c | 13 +++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-devices-deferred_probe (limited to 'drivers/base') diff --git a/Documentation/ABI/testing/sysfs-devices-deferred_probe b/Documentation/ABI/testing/sysfs-devices-deferred_probe new file mode 100644 index 000000000000..58553d7a321f --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-deferred_probe @@ -0,0 +1,12 @@ +What: /sys/devices/.../deferred_probe +Date: August 2016 +Contact: Ben Hutchings +Description: + The /sys/devices/.../deferred_probe attribute is + present for all devices. If a driver detects during + probing a device that a related device is not yet + ready, it may defer probing of the first device. The + kernel will retry probing the first device after any + other device is successfully probed. This attribute + reads as 1 if probing of this device is currently + deferred, or 0 otherwise. diff --git a/drivers/base/base.h b/drivers/base/base.h index e19b1008e5fb..ada9dce34e6d 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -141,6 +141,8 @@ extern void device_unblock_probing(void); extern struct kset *devices_kset; extern void devices_kset_move_last(struct device *dev); +extern struct device_attribute dev_attr_deferred_probe; + #if defined(CONFIG_MODULES) && defined(CONFIG_SYSFS) extern void module_add_driver(struct module *mod, struct device_driver *drv); extern void module_remove_driver(struct device_driver *drv); diff --git a/drivers/base/core.c b/drivers/base/core.c index d0c9df5cdd9e..b8b2f6105476 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1060,8 +1060,14 @@ static int device_add_attrs(struct device *dev) goto err_remove_dev_groups; } + error = device_create_file(dev, &dev_attr_deferred_probe); + if (error) + goto err_remove_online; + return 0; + err_remove_online: + device_remove_file(dev, &dev_attr_online); err_remove_dev_groups: device_remove_groups(dev, dev->groups); err_remove_type_groups: @@ -1079,6 +1085,7 @@ static void device_remove_attrs(struct device *dev) struct class *class = dev->class; const struct device_type *type = dev->type; + device_remove_file(dev, &dev_attr_deferred_probe); device_remove_file(dev, &dev_attr_online); device_remove_groups(dev, dev->groups); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 43be1cc751a4..a48cf444eca5 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -53,6 +53,19 @@ static LIST_HEAD(deferred_probe_pending_list); static LIST_HEAD(deferred_probe_active_list); static atomic_t deferred_trigger_count = ATOMIC_INIT(0); +static ssize_t deferred_probe_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + bool value; + + mutex_lock(&deferred_probe_mutex); + value = !list_empty(&dev->p->deferred_probe); + mutex_unlock(&deferred_probe_mutex); + + return sprintf(buf, "%d\n", value); +} +DEVICE_ATTR_RO(deferred_probe); + /* * In some cases, like suspend to RAM or hibernation, It might be reasonable * to prohibit probing of devices as it could be unsafe. -- cgit v1.2.3 From 79543cf2b18ea4a35f8864849d7ad8882ea8a23d Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 29 Sep 2016 17:13:14 +0200 Subject: driver-core: add test module for asynchronous probing This test module tries to test asynchronous driver probing by having a driver that sleeps for an extended period of time (5 secs) in its probe() method. It measures the time needed to register this driver (with device already registered) and a new device (with driver already registered). The module will fail to load if the time spent in register call is more than half the probing sleep time. As a sanity check the driver will then try to synchronously register driver and device and fail if registration takes less than half of the probing sleep time. Signed-off-by: Dmitry Torokhov Reviewed-by: Olof Johansson Signed-off-by: Guenter Roeck Signed-off-by: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/base/Kconfig | 2 + drivers/base/Makefile | 2 + drivers/base/test/Kconfig | 9 ++ drivers/base/test/Makefile | 1 + drivers/base/test/test_async_driver_probe.c | 171 ++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 drivers/base/test/Kconfig create mode 100644 drivers/base/test/Makefile create mode 100644 drivers/base/test/test_async_driver_probe.c (limited to 'drivers/base') diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index d02e7c0f5bfd..8defa9d87a6f 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -224,6 +224,8 @@ config DEBUG_TEST_DRIVER_REMOVE unusable. You should say N here unless you are explicitly looking to test this functionality. +source "drivers/base/test/Kconfig" + config SYS_HYPERVISOR bool default n diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 2609ba20b396..f2816f6ff76a 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -24,5 +24,7 @@ obj-$(CONFIG_PINCTRL) += pinctrl.o obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o obj-$(CONFIG_GENERIC_MSI_IRQ_DOMAIN) += platform-msi.o +obj-y += test/ + ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/test/Kconfig b/drivers/base/test/Kconfig new file mode 100644 index 000000000000..9aa0d45a60db --- /dev/null +++ b/drivers/base/test/Kconfig @@ -0,0 +1,9 @@ +config TEST_ASYNC_DRIVER_PROBE + tristate "Build kernel module to test asynchronous driver probing" + depends on m + help + Enabling this option produces a kernel module that allows + testing asynchronous driver probing by the device core. + The module name will be test_async_driver_probe.ko + + If unsure say N. diff --git a/drivers/base/test/Makefile b/drivers/base/test/Makefile new file mode 100644 index 000000000000..90477c5fd9f9 --- /dev/null +++ b/drivers/base/test/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_TEST_ASYNC_DRIVER_PROBE) += test_async_driver_probe.o diff --git a/drivers/base/test/test_async_driver_probe.c b/drivers/base/test/test_async_driver_probe.c new file mode 100644 index 000000000000..3a71e83e5d98 --- /dev/null +++ b/drivers/base/test/test_async_driver_probe.c @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2014 Google, Inc. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#define TEST_PROBE_DELAY (5 * 1000) /* 5 sec */ +#define TEST_PROBE_THRESHOLD (TEST_PROBE_DELAY / 2) + +static int test_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "sleeping for %d msecs in probe\n", + TEST_PROBE_DELAY); + msleep(TEST_PROBE_DELAY); + dev_info(&pdev->dev, "done sleeping\n"); + + return 0; +} + +static struct platform_driver async_driver = { + .driver = { + .name = "test_async_driver", + .owner = THIS_MODULE, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = test_probe, +}; + +static struct platform_driver sync_driver = { + .driver = { + .name = "test_sync_driver", + .owner = THIS_MODULE, + .probe_type = PROBE_FORCE_SYNCHRONOUS, + }, + .probe = test_probe, +}; + +static struct platform_device *async_dev_1, *async_dev_2; +static struct platform_device *sync_dev_1; + +static int __init test_async_probe_init(void) +{ + ktime_t calltime, delta; + unsigned long long duration; + int error; + + pr_info("registering first asynchronous device...\n"); + + async_dev_1 = platform_device_register_simple("test_async_driver", 1, + NULL, 0); + if (IS_ERR(async_dev_1)) { + error = PTR_ERR(async_dev_1); + pr_err("failed to create async_dev_1: %d", error); + return error; + } + + pr_info("registering asynchronous driver...\n"); + calltime = ktime_get(); + error = platform_driver_register(&async_driver); + if (error) { + pr_err("Failed to register async_driver: %d\n", error); + goto err_unregister_async_dev_1; + } + + delta = ktime_sub(ktime_get(), calltime); + duration = (unsigned long long) ktime_to_ms(delta); + pr_info("registration took %lld msecs\n", duration); + if (duration > TEST_PROBE_THRESHOLD) { + pr_err("test failed: probe took too long\n"); + error = -ETIMEDOUT; + goto err_unregister_async_driver; + } + + pr_info("registering second asynchronous device...\n"); + calltime = ktime_get(); + async_dev_2 = platform_device_register_simple("test_async_driver", 2, + NULL, 0); + if (IS_ERR(async_dev_2)) { + error = PTR_ERR(async_dev_2); + pr_err("failed to create async_dev_2: %d", error); + goto err_unregister_async_driver; + } + + delta = ktime_sub(ktime_get(), calltime); + duration = (unsigned long long) ktime_to_ms(delta); + pr_info("registration took %lld msecs\n", duration); + if (duration > TEST_PROBE_THRESHOLD) { + pr_err("test failed: probe took too long\n"); + error = -ETIMEDOUT; + goto err_unregister_async_dev_2; + } + + pr_info("registering synchronous driver...\n"); + + error = platform_driver_register(&sync_driver); + if (error) { + pr_err("Failed to register async_driver: %d\n", error); + goto err_unregister_async_dev_2; + } + + pr_info("registering synchronous device...\n"); + calltime = ktime_get(); + sync_dev_1 = platform_device_register_simple("test_sync_driver", 1, + NULL, 0); + if (IS_ERR(async_dev_1)) { + error = PTR_ERR(sync_dev_1); + pr_err("failed to create sync_dev_1: %d", error); + goto err_unregister_sync_driver; + } + + delta = ktime_sub(ktime_get(), calltime); + duration = (unsigned long long) ktime_to_ms(delta); + pr_info("registration took %lld msecs\n", duration); + if (duration < TEST_PROBE_THRESHOLD) { + pr_err("test failed: probe was too quick\n"); + error = -ETIMEDOUT; + goto err_unregister_sync_dev_1; + } + + pr_info("completed successfully"); + + return 0; + +err_unregister_sync_dev_1: + platform_device_unregister(sync_dev_1); + +err_unregister_sync_driver: + platform_driver_unregister(&sync_driver); + +err_unregister_async_dev_2: + platform_device_unregister(async_dev_2); + +err_unregister_async_driver: + platform_driver_unregister(&async_driver); + +err_unregister_async_dev_1: + platform_device_unregister(async_dev_1); + + return error; +} +module_init(test_async_probe_init); + +static void __exit test_async_probe_exit(void) +{ + platform_driver_unregister(&async_driver); + platform_driver_unregister(&sync_driver); + platform_device_unregister(async_dev_1); + platform_device_unregister(async_dev_2); + platform_device_unregister(sync_dev_1); +} +module_exit(test_async_probe_exit); + +MODULE_DESCRIPTION("Test module for asynchronous driver probing"); +MODULE_AUTHOR("Dmitry Torokhov "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From fac51482577d5e05bbb0efa8d602a3c2111098bf Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 28 Oct 2016 09:45:28 +0100 Subject: drivers: base: cacheinfo: fix x86 with CONFIG_OF enabled With CONFIG_OF enabled on x86, we get the following error on boot: " Failed to find cpu0 device node Unable to detect cache hierarchy from DT for CPU 0 " and the cacheinfo fails to get populated in the corresponding sysfs entries. This is because cache_setup_of_node looks for of_node for setting up the shared cpu_map without checking that it's already populated in the architecture specific callback. In order to indicate that the shared cpu_map is already populated, this patch introduces a boolean `cpu_map_populated` in struct cpu_cacheinfo that can be used by the generic code to skip cache_shared_cpu_map_setup. This patch also sets that boolean for x86. Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/intel_cacheinfo.c | 2 ++ drivers/base/cacheinfo.c | 3 +++ include/linux/cacheinfo.h | 1 + 3 files changed, 6 insertions(+) (limited to 'drivers/base') diff --git a/arch/x86/kernel/cpu/intel_cacheinfo.c b/arch/x86/kernel/cpu/intel_cacheinfo.c index de6626c18e42..be6337156502 100644 --- a/arch/x86/kernel/cpu/intel_cacheinfo.c +++ b/arch/x86/kernel/cpu/intel_cacheinfo.c @@ -934,6 +934,8 @@ static int __populate_cache_leaves(unsigned int cpu) ci_leaf_init(this_leaf++, &id4_regs); __cache_cpumap_setup(cpu, idx, &id4_regs); } + this_cpu_ci->cpu_map_populated = true; + return 0; } diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index e9fd32e91668..ecde8957835a 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -106,6 +106,9 @@ static int cache_shared_cpu_map_setup(unsigned int cpu) unsigned int index; int ret; + if (this_cpu_ci->cpu_map_populated) + return 0; + ret = cache_setup_of_node(cpu); if (ret) return ret; diff --git a/include/linux/cacheinfo.h b/include/linux/cacheinfo.h index 2189935075b4..a951fd10aaaa 100644 --- a/include/linux/cacheinfo.h +++ b/include/linux/cacheinfo.h @@ -71,6 +71,7 @@ struct cpu_cacheinfo { struct cacheinfo *info_list; unsigned int num_levels; unsigned int num_leaves; + bool cpu_map_populated; }; /* -- cgit v1.2.3 From 55877ef45fbd7f975d078426866b7d1a2435dcc3 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 28 Oct 2016 09:45:29 +0100 Subject: drivers: base: cacheinfo: fix boot error message when acpi is enabled ARM64 enables both CONFIG_OF and CONFIG_ACPI and the firmware can pass both ACPI tables and the device tree. Based on the kernel parameter, one of the two will be chosen. If acpi is enabled, then device tree is not unflattened. Currently ARM64 platforms report: " Failed to find cpu0 device node Unable to detect cache hierarchy from DT for CPU 0 " which is incorrect when booting with ACPI. Also latest ACPI v6.1 has no support for cache properties/hierarchy. This patch adds check for unflattened device tree and also returns as "not supported" if ACPI is runtime enabled. It also removes the reference to DT from the error message as the cache hierarchy can be detected from the firmware(OF/DT/ACPI) Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla Signed-off-by: Greg Kroah-Hartman --- drivers/base/cacheinfo.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index ecde8957835a..70e13cf06ed0 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -16,6 +16,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include #include #include #include @@ -104,12 +105,16 @@ static int cache_shared_cpu_map_setup(unsigned int cpu) struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); struct cacheinfo *this_leaf, *sib_leaf; unsigned int index; - int ret; + int ret = 0; if (this_cpu_ci->cpu_map_populated) return 0; - ret = cache_setup_of_node(cpu); + if (of_have_populated_dt()) + ret = cache_setup_of_node(cpu); + else if (!acpi_disabled) + /* No cache property/hierarchy support yet in ACPI */ + ret = -ENOTSUPP; if (ret) return ret; @@ -206,8 +211,7 @@ static int detect_cache_attributes(unsigned int cpu) */ ret = cache_shared_cpu_map_setup(cpu); if (ret) { - pr_warn("Unable to detect cache hierarchy from DT for CPU %d\n", - cpu); + pr_warn("Unable to detect cache hierarchy for CPU %d\n", cpu); goto free_ci; } return 0; -- cgit v1.2.3 From 8e1073b1ffff5a90c1af75862309c5ed87ef545c Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 28 Oct 2016 09:45:30 +0100 Subject: drivers: base: cacheinfo: add pr_fmt logging This cleanup patch just adds pr_fmt style logging for cacheinfo. Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla Signed-off-by: Greg Kroah-Hartman --- drivers/base/cacheinfo.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index 70e13cf06ed0..f19d50bd8925 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -16,6 +16,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include -- cgit v1.2.3 From dfea747d2aba77443acf7ce6fa37caa729bd034c Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Fri, 28 Oct 2016 09:45:31 +0100 Subject: drivers: base: cacheinfo: support DT overrides for cache properties Few architectures like x86, ia64 and s390 derive the cache topology and all the properties using a specific architected mechanism while some other architectures like powerpc all those information id derived from the device tree. On ARM, both the mechanism is used. While all the cache properties can be derived in a architected way, it needs to rely on device tree to get the cache topology information. However there are few platforms where this architected mechanism is broken and the device tree properties can be used to override these incorrect values. This patch adds support for overriding the cache properties values to the values specified in the device tree. Cc: Alex Van Brunt Cc: Greg Kroah-Hartman Signed-off-by: Sudeep Holla Signed-off-by: Greg Kroah-Hartman --- drivers/base/cacheinfo.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index f19d50bd8925..2376628c599c 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -88,7 +88,120 @@ static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf, { return sib_leaf->of_node == this_leaf->of_node; } + +/* OF properties to query for a given cache type */ +struct cache_type_info { + const char *size_prop; + const char *line_size_props[2]; + const char *nr_sets_prop; +}; + +static const struct cache_type_info cache_type_info[] = { + { + .size_prop = "cache-size", + .line_size_props = { "cache-line-size", + "cache-block-size", }, + .nr_sets_prop = "cache-sets", + }, { + .size_prop = "i-cache-size", + .line_size_props = { "i-cache-line-size", + "i-cache-block-size", }, + .nr_sets_prop = "i-cache-sets", + }, { + .size_prop = "d-cache-size", + .line_size_props = { "d-cache-line-size", + "d-cache-block-size", }, + .nr_sets_prop = "d-cache-sets", + }, +}; + +static inline int get_cacheinfo_idx(enum cache_type type) +{ + if (type == CACHE_TYPE_UNIFIED) + return 0; + return type; +} + +static void cache_size(struct cacheinfo *this_leaf) +{ + const char *propname; + const __be32 *cache_size; + int ct_idx; + + ct_idx = get_cacheinfo_idx(this_leaf->type); + propname = cache_type_info[ct_idx].size_prop; + + cache_size = of_get_property(this_leaf->of_node, propname, NULL); + if (cache_size) + this_leaf->size = of_read_number(cache_size, 1); +} + +/* not cache_line_size() because that's a macro in include/linux/cache.h */ +static void cache_get_line_size(struct cacheinfo *this_leaf) +{ + const __be32 *line_size; + int i, lim, ct_idx; + + ct_idx = get_cacheinfo_idx(this_leaf->type); + lim = ARRAY_SIZE(cache_type_info[ct_idx].line_size_props); + + for (i = 0; i < lim; i++) { + const char *propname; + + propname = cache_type_info[ct_idx].line_size_props[i]; + line_size = of_get_property(this_leaf->of_node, propname, NULL); + if (line_size) + break; + } + + if (line_size) + this_leaf->coherency_line_size = of_read_number(line_size, 1); +} + +static void cache_nr_sets(struct cacheinfo *this_leaf) +{ + const char *propname; + const __be32 *nr_sets; + int ct_idx; + + ct_idx = get_cacheinfo_idx(this_leaf->type); + propname = cache_type_info[ct_idx].nr_sets_prop; + + nr_sets = of_get_property(this_leaf->of_node, propname, NULL); + if (nr_sets) + this_leaf->number_of_sets = of_read_number(nr_sets, 1); +} + +static void cache_associativity(struct cacheinfo *this_leaf) +{ + unsigned int line_size = this_leaf->coherency_line_size; + unsigned int nr_sets = this_leaf->number_of_sets; + unsigned int size = this_leaf->size; + + /* + * If the cache is fully associative, there is no need to + * check the other properties. + */ + if (!(nr_sets == 1) && (nr_sets > 0 && size > 0 && line_size > 0)) + this_leaf->ways_of_associativity = (size / nr_sets) / line_size; +} + +static void cache_of_override_properties(unsigned int cpu) +{ + int index; + struct cacheinfo *this_leaf; + struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); + + for (index = 0; index < cache_leaves(cpu); index++) { + this_leaf = this_cpu_ci->info_list + index; + cache_size(this_leaf); + cache_get_line_size(this_leaf); + cache_nr_sets(this_leaf); + cache_associativity(this_leaf); + } +} #else +static void cache_of_override_properties(unsigned int cpu) { } static inline int cache_setup_of_node(unsigned int cpu) { return 0; } static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf, struct cacheinfo *sib_leaf) @@ -171,6 +284,12 @@ static void cache_shared_cpu_map_remove(unsigned int cpu) } } +static void cache_override_properties(unsigned int cpu) +{ + if (of_have_populated_dt()) + return cache_of_override_properties(cpu); +} + static void free_cache_attributes(unsigned int cpu) { if (!per_cpu_cacheinfo(cpu)) @@ -216,6 +335,8 @@ static int detect_cache_attributes(unsigned int cpu) pr_warn("Unable to detect cache hierarchy for CPU %d\n", cpu); goto free_ci; } + + cache_override_properties(cpu); return 0; free_ci: -- cgit v1.2.3 From e22defeb9810d98dfffe9d84e036559af1742d96 Mon Sep 17 00:00:00 2001 From: Kirtika Ruchandani Date: Thu, 24 Nov 2016 17:12:00 -0800 Subject: drivers/base/memory.c: Remove unused 'first_page' variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 71fbd556adde ("memory-hotplug: remove redundant call of page_to_pfn") introduced an optimization that rendered 'struct page* first_page' useless in memory_block_action(). Compiling with W=1 gives the following warning, fix it. drivers/base/memory.c: In function ‘memory_block_action’: drivers/base/memory.c:229:15: warning: variable ‘first_page’ set but not used [-Wunused-but-set-variable] struct page *first_page; ^ This is a harmeless warning and is only being fixed to reduce the noise with W=1 in the kernel. The call to pfn_to_page() has no side effects and is safe to remove. Fixes: 71fbd556adde ("memory-hotplug: remove redundant call of page_to_pfn") Cc: Zhang Zhen Cc: Andrew Morton Cc: Linus Torvalds Signed-off-by: Kirtika Ruchandani Signed-off-by: Greg Kroah-Hartman --- drivers/base/memory.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 62c63c0c5c22..bb69e58c29f3 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -226,11 +226,9 @@ memory_block_action(unsigned long phys_index, unsigned long action, int online_t { unsigned long start_pfn; unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; - struct page *first_page; int ret; start_pfn = section_nr_to_pfn(phys_index); - first_page = pfn_to_page(start_pfn); switch (action) { case MEM_ONLINE: -- cgit v1.2.3 From 2eed70ded4d437f55ccaaffe753770987812f8a2 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 10 Nov 2016 21:19:39 +0100 Subject: driver-core: fix platform_no_drv_owner.cocci warnings Remove .owner field initialization as the core will do it. Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci CC: Dmitry Torokhov Signed-off-by: Julia Lawall Signed-off-by: Fengguang Wu Signed-off-by: Greg Kroah-Hartman --- drivers/base/test/test_async_driver_probe.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/test/test_async_driver_probe.c b/drivers/base/test/test_async_driver_probe.c index 3a71e83e5d98..1c5eddda5cc2 100644 --- a/drivers/base/test/test_async_driver_probe.c +++ b/drivers/base/test/test_async_driver_probe.c @@ -36,7 +36,6 @@ static int test_probe(struct platform_device *pdev) static struct platform_driver async_driver = { .driver = { .name = "test_async_driver", - .owner = THIS_MODULE, .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .probe = test_probe, @@ -45,7 +44,6 @@ static struct platform_driver async_driver = { static struct platform_driver sync_driver = { .driver = { .name = "test_sync_driver", - .owner = THIS_MODULE, .probe_type = PROBE_FORCE_SYNCHRONOUS, }, .probe = test_probe, -- cgit v1.2.3 From ced6473e7486702f530a49f886b73195e4977734 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 28 Nov 2016 16:41:41 +0100 Subject: driver core: class: add class_groups support struct class needs to have a set of default groups that are added, as adding individual attributes does not work well in the long run. So add support for that. Future patches will convert the existing usages of class_attrs to use class_groups and then class_attrs will go away. Signed-off-by: Greg Kroah-Hartman --- drivers/base/class.c | 15 +++++++++++++++ include/linux/device.h | 2 ++ 2 files changed, 17 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/class.c b/drivers/base/class.c index 71059e32bebc..a2b2896693d6 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -163,6 +163,18 @@ static void klist_class_dev_put(struct klist_node *n) put_device(dev); } +static int class_add_groups(struct class *cls, + const struct attribute_group **groups) +{ + return sysfs_create_groups(&cls->p->subsys.kobj, groups); +} + +static void class_remove_groups(struct class *cls, + const struct attribute_group **groups) +{ + return sysfs_remove_groups(&cls->p->subsys.kobj, groups); +} + int __class_register(struct class *cls, struct lock_class_key *key) { struct subsys_private *cp; @@ -203,6 +215,8 @@ int __class_register(struct class *cls, struct lock_class_key *key) kfree(cp); return error; } + error = class_add_groups(class_get(cls), cls->class_groups); + class_put(cls); error = add_class_attrs(class_get(cls)); class_put(cls); return error; @@ -213,6 +227,7 @@ void class_unregister(struct class *cls) { pr_debug("device class '%s': unregistering\n", cls->name); remove_class_attrs(cls); + class_remove_groups(cls, cls->class_groups); kset_unregister(&cls->p->subsys); } diff --git a/include/linux/device.h b/include/linux/device.h index 4264caacebb9..4cd8e52033b0 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -362,6 +362,7 @@ int subsys_virtual_register(struct bus_type *subsys, * @name: Name of the class. * @owner: The module owner. * @class_attrs: Default attributes of this class. + * @class_groups: Default attributes of this class. * @dev_groups: Default attributes of the devices that belong to the class. * @dev_kobj: The kobject that represents this class and links it into the hierarchy. * @dev_uevent: Called when a device is added, removed from this class, or a @@ -390,6 +391,7 @@ struct class { struct module *owner; struct class_attribute *class_attrs; + const struct attribute_group **class_groups; const struct attribute_group **dev_groups; struct kobject *dev_kobj; -- cgit v1.2.3 From f76d25275c314defb684fdd692239507001774bc Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 28 Nov 2016 16:41:58 +0100 Subject: driver core: devcoredump: convert to use class_groups Convert devcoredump to use class_groups instead of class_attrs as that's the correct way to handle lists of class attribute files. Acked-by: Johannes Berg Signed-off-by: Greg Kroah-Hartman --- drivers/base/devcoredump.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/devcoredump.c b/drivers/base/devcoredump.c index 240374fd1838..7be310f7db73 100644 --- a/drivers/base/devcoredump.c +++ b/drivers/base/devcoredump.c @@ -160,18 +160,20 @@ static ssize_t disabled_store(struct class *class, struct class_attribute *attr, return count; } +static CLASS_ATTR_RW(disabled); -static struct class_attribute devcd_class_attrs[] = { - __ATTR_RW(disabled), - __ATTR_NULL +static struct attribute *devcd_class_attrs[] = { + &class_attr_disabled.attr, + NULL, }; +ATTRIBUTE_GROUPS(devcd_class); static struct class devcd_class = { .name = "devcoredump", .owner = THIS_MODULE, .dev_release = devcd_dev_release, .dev_groups = devcd_dev_groups, - .class_attrs = devcd_class_attrs, + .class_groups = devcd_class_groups, }; static ssize_t devcd_readv(char *buffer, loff_t offset, size_t count, -- cgit v1.2.3 From 3f214cff7c6e666c6f950cd802d4239778f0d37e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 28 Nov 2016 16:42:30 +0100 Subject: driver core: firmware_class: convert to use class_groups Convert the firmware core to use class_groups instead of class_attrs as that's the correct way to handle lists of class attribute files. Cc: Ming Lei Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 22d1760a4278..98c4f28ad2a1 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -546,11 +546,13 @@ static ssize_t timeout_store(struct class *class, struct class_attribute *attr, return count; } +static CLASS_ATTR_RW(timeout); -static struct class_attribute firmware_class_attrs[] = { - __ATTR_RW(timeout), - __ATTR_NULL +static struct attribute *firmware_class_attrs[] = { + &class_attr_timeout.attr, + NULL, }; +ATTRIBUTE_GROUPS(firmware_class); static void fw_dev_release(struct device *dev) { @@ -585,7 +587,7 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env) static struct class firmware_class = { .name = "firmware", - .class_attrs = firmware_class_attrs, + .class_groups = firmware_class_groups, .dev_uevent = firmware_uevent, .dev_release = fw_dev_release, }; -- cgit v1.2.3 From 2e700f8d85975f516ccaad821278c1fe66b2cc98 Mon Sep 17 00:00:00 2001 From: Yves-Alexis Perez Date: Fri, 11 Nov 2016 11:28:40 -0800 Subject: firmware: fix usermode helper fallback loading When you use the firmware usermode helper fallback with a timeout value set to a value greater than INT_MAX (2147483647) a cast overflow issue causes the timeout value to go negative and breaks all usermode helper loading. This regression was introduced through commit 68ff2a00dbf5 ("firmware_loader: handle timeout via wait_for_completion_interruptible_timeout()") on kernel v4.0. The firmware_class drivers relies on the firmware usermode helper fallback as a mechanism to look for firmware if the direct filesystem search failed only if: a) You've enabled CONFIG_FW_LOADER_USER_HELPER_FALLBACK (not many distros): Then all of these callers will rely on the fallback mechanism in case the firmware is not found through an initial direct filesystem lookup: o request_firmware() o request_firmware_into_buf() o request_firmware_nowait() b) If you've only enabled CONFIG_FW_LOADER_USER_HELPER (most distros): Then only callers using request_firmware_nowait() with the second argument set to false, this explicitly is requesting the UMH firmware fallback to be relied on in case the first filesystem lookup fails. Using Coccinelle SmPL grammar we have identified only two drivers explicitly requesting the UMH firmware fallback mechanism: - drivers/firmware/dell_rbu.c - drivers/leds/leds-lp55xx-common.c Since most distributions only enable CONFIG_FW_LOADER_USER_HELPER the biggest impact of this regression are users of the dell_rbu and leds-lp55xx-common device driver which required the UMH to find their respective needed firmwares. The default timeout for the UMH is set to 60 seconds always, as of commit 68ff2a00dbf5 ("firmware_loader: handle timeout via wait_for_completion_interruptible_timeout()") the timeout was bumped to MAX_JIFFY_OFFSET ((LONG_MAX >> 1)-1). Additionally the MAX_JIFFY_OFFSET value was also used if the timeout was configured by a user to 0. The following works: echo 2147483647 > /sys/class/firmware/timeout But both of the following set the timeout to MAX_JIFFY_OFFSET even if we display 0 back to userspace: echo 2147483648 > /sys/class/firmware/timeout cat /sys/class/firmware/timeout 0 echo 0> /sys/class/firmware/timeout cat /sys/class/firmware/timeout 0 A max value of INT_MAX (2147483647) seconds is therefore implicit due to the another cast with simple_strtol(). This fixes the secondary cast (the first one is simple_strtol() but its an issue only by forcing an implicit limit) by re-using the timeout variable and only setting retval in appropriate cases. Lastly worth noting systemd had ripped out the UMH firmware fallback mechanism from udev since udev 2014 via commit be2ea723b1d023b3d ("udev: remove userspace firmware loading support"), so as of systemd v217. Signed-off-by: Yves-Alexis Perez Fixes: 68ff2a00dbf5 "firmware_loader: handle timeout via wait_for_completion_interruptible_timeout()" Cc: Luis R. Rodriguez Cc: Ming Lei Cc: Bjorn Andersson Cc: Greg Kroah-Hartman Cc: stable@vger.kernel.org Acked-by: Luis R. Rodriguez Reviewed-by: Bjorn Andersson [mcgrof@kernel.org: gave commit log a whole lot of love] Signed-off-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 98c4f28ad2a1..453958892a69 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -957,13 +957,14 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, timeout = MAX_JIFFY_OFFSET; } - retval = wait_for_completion_interruptible_timeout(&buf->completion, + timeout = wait_for_completion_interruptible_timeout(&buf->completion, timeout); - if (retval == -ERESTARTSYS || !retval) { + if (timeout == -ERESTARTSYS || !timeout) { + retval = timeout; mutex_lock(&fw_lock); fw_load_abort(fw_priv); mutex_unlock(&fw_lock); - } else if (retval > 0) { + } else if (timeout > 0) { retval = 0; } -- cgit v1.2.3 From f52cc379423d61ae370aefe5344c88bc88881043 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Thu, 17 Nov 2016 11:00:48 +0100 Subject: firmware: refactor loading status The firmware loader tracks the current state of the loading process via unsigned long status and a completion in struct firmware_buf. Instead of open code tracking the state, introduce data structure which encapsulate the state tracking and synchronization. While at it also separate UHM states from direct loading states, e.g. the loading_timeout is only defined when CONFIG_FW_LOADER_USER_HELPER. Cc: Ming Lei Signed-off-by: Daniel Wagner Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 127 +++++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 34 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 453958892a69..4f5105f57521 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -91,10 +91,11 @@ static inline bool fw_is_builtin_firmware(const struct firmware *fw) } #endif -enum { +enum fw_status { + FW_STATUS_UNKNOWN, FW_STATUS_LOADING, FW_STATUS_DONE, - FW_STATUS_ABORT, + FW_STATUS_ABORTED, }; static int loading_timeout = 60; /* In seconds */ @@ -104,6 +105,76 @@ static inline long firmware_loading_timeout(void) return loading_timeout > 0 ? loading_timeout * HZ : MAX_JIFFY_OFFSET; } +/* + * Concurrent request_firmware() for the same firmware need to be + * serialized. struct fw_state is simple state machine which hold the + * state of the firmware loading. + */ +struct fw_state { + struct completion completion; + unsigned long status; +}; + +static void fw_state_init(struct fw_state *fw_st) +{ + init_completion(&fw_st->completion); + fw_st->status = FW_STATUS_UNKNOWN; +} + +static int __fw_state_check(struct fw_state *fw_st, enum fw_status status) +{ + return test_bit(status, &fw_st->status); +} + +static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) +{ + long ret; + + ret = wait_for_completion_interruptible_timeout(&fw_st->completion, + timeout); + if (ret != 0 && test_bit(FW_STATUS_ABORTED, &fw_st->status)) + return -ENOENT; + + return ret; +} + +static void __fw_state_set(struct fw_state *fw_st, + enum fw_status status) +{ + set_bit(status, &fw_st->status); + + if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) { + clear_bit(FW_STATUS_LOADING, &fw_st->status); + complete_all(&fw_st->completion); + } +} + +#define fw_state_start(fw_st) \ + __fw_state_set(fw_st, FW_STATUS_LOADING) +#define fw_state_done(fw_st) \ + __fw_state_set(fw_st, FW_STATUS_DONE) +#define fw_state_is_done(fw_st) \ + __fw_state_check(fw_st, FW_STATUS_DONE) +#define fw_state_wait(fw_st) \ + __fw_state_wait_common(fw_st, MAX_SCHEDULE_TIMEOUT) + +#ifndef CONFIG_FW_LOADER_USER_HELPER + +#define fw_state_is_aborted(fw_st) false + +#else /* CONFIG_FW_LOADER_USER_HELPER */ + +#define fw_state_aborted(fw_st) \ + __fw_state_set(fw_st, FW_STATUS_ABORTED) +#define fw_state_is_loading(fw_st) \ + __fw_state_check(fw_st, FW_STATUS_LOADING) +#define fw_state_is_aborted(fw_st) \ + __fw_state_check(fw_st, FW_STATUS_ABORTED) +#define fw_state_wait_timeout(fw_st, timeout) \ + __fw_state_wait_common(fw_st, timeout) + +#endif /* CONFIG_FW_LOADER_USER_HELPER */ + /* firmware behavior options */ #define FW_OPT_UEVENT (1U << 0) #define FW_OPT_NOWAIT (1U << 1) @@ -145,9 +216,8 @@ struct firmware_cache { struct firmware_buf { struct kref ref; struct list_head list; - struct completion completion; struct firmware_cache *fwc; - unsigned long status; + struct fw_state fw_st; void *data; size_t size; size_t allocated_size; @@ -205,7 +275,7 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name, buf->fwc = fwc; buf->data = dbuf; buf->allocated_size = size; - init_completion(&buf->completion); + fw_state_init(&buf->fw_st); #ifdef CONFIG_FW_LOADER_USER_HELPER INIT_LIST_HEAD(&buf->pending_list); #endif @@ -309,8 +379,7 @@ static void fw_finish_direct_load(struct device *device, struct firmware_buf *buf) { mutex_lock(&fw_lock); - set_bit(FW_STATUS_DONE, &buf->status); - complete_all(&buf->completion); + fw_state_done(&buf->fw_st); mutex_unlock(&fw_lock); } @@ -478,12 +547,11 @@ static void __fw_load_abort(struct firmware_buf *buf) * There is a small window in which user can write to 'loading' * between loading done and disappearance of 'loading' */ - if (test_bit(FW_STATUS_DONE, &buf->status)) + if (fw_state_is_done(&buf->fw_st)) return; list_del_init(&buf->pending_list); - set_bit(FW_STATUS_ABORT, &buf->status); - complete_all(&buf->completion); + fw_state_aborted(&buf->fw_st); } static void fw_load_abort(struct firmware_priv *fw_priv) @@ -496,9 +564,6 @@ static void fw_load_abort(struct firmware_priv *fw_priv) fw_priv->buf = NULL; } -#define is_fw_load_aborted(buf) \ - test_bit(FW_STATUS_ABORT, &(buf)->status) - static LIST_HEAD(pending_fw_head); /* reboot notifier for avoid deadlock with usermode_lock */ @@ -600,7 +665,7 @@ static ssize_t firmware_loading_show(struct device *dev, mutex_lock(&fw_lock); if (fw_priv->buf) - loading = test_bit(FW_STATUS_LOADING, &fw_priv->buf->status); + loading = fw_state_is_loading(&fw_priv->buf->fw_st); mutex_unlock(&fw_lock); return sprintf(buf, "%d\n", loading); @@ -655,23 +720,20 @@ static ssize_t firmware_loading_store(struct device *dev, switch (loading) { case 1: /* discarding any previous partial load */ - if (!test_bit(FW_STATUS_DONE, &fw_buf->status)) { + if (!fw_state_is_done(&fw_buf->fw_st)) { for (i = 0; i < fw_buf->nr_pages; i++) __free_page(fw_buf->pages[i]); vfree(fw_buf->pages); fw_buf->pages = NULL; fw_buf->page_array_size = 0; fw_buf->nr_pages = 0; - set_bit(FW_STATUS_LOADING, &fw_buf->status); + fw_state_start(&fw_buf->fw_st); } break; case 0: - if (test_bit(FW_STATUS_LOADING, &fw_buf->status)) { + if (fw_state_is_loading(&fw_buf->fw_st)) { int rc; - set_bit(FW_STATUS_DONE, &fw_buf->status); - clear_bit(FW_STATUS_LOADING, &fw_buf->status); - /* * Several loading requests may be pending on * one same firmware buf, so let all requests @@ -693,10 +755,11 @@ static ssize_t firmware_loading_store(struct device *dev, */ list_del_init(&fw_buf->pending_list); if (rc) { - set_bit(FW_STATUS_ABORT, &fw_buf->status); + fw_state_aborted(&fw_buf->fw_st); written = rc; + } else { + fw_state_done(&fw_buf->fw_st); } - complete_all(&fw_buf->completion); break; } /* fallthrough */ @@ -757,7 +820,7 @@ static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, mutex_lock(&fw_lock); buf = fw_priv->buf; - if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) { + if (!buf || fw_state_is_done(&buf->fw_st)) { ret_count = -ENODEV; goto out; } @@ -844,7 +907,7 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, mutex_lock(&fw_lock); buf = fw_priv->buf; - if (!buf || test_bit(FW_STATUS_DONE, &buf->status)) { + if (!buf || fw_state_is_done(&buf->fw_st)) { retval = -ENODEV; goto out; } @@ -957,8 +1020,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, timeout = MAX_JIFFY_OFFSET; } - timeout = wait_for_completion_interruptible_timeout(&buf->completion, - timeout); + timeout = fw_state_wait_timeout(&buf->fw_st, timeout); if (timeout == -ERESTARTSYS || !timeout) { retval = timeout; mutex_lock(&fw_lock); @@ -968,7 +1030,7 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, retval = 0; } - if (is_fw_load_aborted(buf)) + if (fw_state_is_aborted(&buf->fw_st)) retval = -EAGAIN; else if (buf->is_paged_buf && !buf->data) retval = -ENOMEM; @@ -1018,9 +1080,6 @@ fw_load_from_user_helper(struct firmware *firmware, const char *name, return -ENOENT; } -/* No abort during direct loading */ -#define is_fw_load_aborted(buf) false - #ifdef CONFIG_PM_SLEEP static inline void kill_requests_without_uevent(void) { } #endif @@ -1034,13 +1093,13 @@ static int sync_cached_firmware_buf(struct firmware_buf *buf) int ret = 0; mutex_lock(&fw_lock); - while (!test_bit(FW_STATUS_DONE, &buf->status)) { - if (is_fw_load_aborted(buf)) { + while (!fw_state_is_done(&buf->fw_st)) { + if (fw_state_is_aborted(&buf->fw_st)) { ret = -ENOENT; break; } mutex_unlock(&fw_lock); - ret = wait_for_completion_interruptible(&buf->completion); + ret = fw_state_wait(&buf->fw_st); mutex_lock(&fw_lock); } mutex_unlock(&fw_lock); @@ -1098,7 +1157,7 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device, struct firmware_buf *buf = fw->priv; mutex_lock(&fw_lock); - if (!buf->size || is_fw_load_aborted(buf)) { + if (!buf->size || fw_state_is_aborted(&buf->fw_st)) { mutex_unlock(&fw_lock); return -ENOENT; } -- cgit v1.2.3 From 0430cafcc4fb632beeeab42f8817542dcf6901ce Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Thu, 17 Nov 2016 11:00:49 +0100 Subject: firmware: drop bit ops in favor of simple state machine We track the state of the firmware loading with bit ops. Since the state machine has only a few states and they are all mutual exclusive there are only a few simple state transition we can model this simplify. UNKNOWN -> LOADING -> DONE | ABORTED Because we don't use any bit ops on fw_state::status anymore we are able to change the data type to enum fw_status and update the function arguments accordingly. READ_ONCE() and WRITE_ONCE() are propably not needed because there are a lot of load and stores around fw_st->status. But let's make it explicit and not be sorry later. Cc: Ming Lei Signed-off-by: Daniel Wagner Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 4f5105f57521..45a20dd31c25 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -112,7 +112,7 @@ static inline long firmware_loading_timeout(void) */ struct fw_state { struct completion completion; - unsigned long status; + enum fw_status status; }; static void fw_state_init(struct fw_state *fw_st) @@ -123,7 +123,7 @@ static void fw_state_init(struct fw_state *fw_st) static int __fw_state_check(struct fw_state *fw_st, enum fw_status status) { - return test_bit(status, &fw_st->status); + return fw_st->status == status; } static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) @@ -132,7 +132,7 @@ static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) ret = wait_for_completion_interruptible_timeout(&fw_st->completion, timeout); - if (ret != 0 && test_bit(FW_STATUS_ABORTED, &fw_st->status)) + if (ret != 0 && READ_ONCE(fw_st->status) == FW_STATUS_ABORTED) return -ENOENT; return ret; @@ -141,12 +141,10 @@ static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) static void __fw_state_set(struct fw_state *fw_st, enum fw_status status) { - set_bit(status, &fw_st->status); + WRITE_ONCE(fw_st->status, status); - if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) { - clear_bit(FW_STATUS_LOADING, &fw_st->status); + if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) complete_all(&fw_st->completion); - } } #define fw_state_start(fw_st) \ -- cgit v1.2.3 From 5b029624948d6864053166da1263df74c0c443df Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Thu, 17 Nov 2016 11:00:50 +0100 Subject: firmware: do not use fw_lock for fw_state protection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fw_lock is to use to protect 'corner cases' inside firmware_class. It is not exactly clear what those corner cases are nor what it exactly protects. fw_state can be used without needing the fw_lock to protect its state transition and wake ups. fw_state is holds the state in status and the completion is used to wake up all waiters (in this case that is the user land helper so only one). This operation has to be 'atomic' to avoid races. We can do this by using swait which takes care we don't miss any wake up. We use also swait instead of wait because don't need all the additional features wait provides. Note there some more cleanups possible after with this change. For example for !CONFIG_FW_LOADER_USER_HELPER we don't check for the state anymore. Let's to this in the next patch instead mingling to many changes into this one. And yes you get a gcc warning "‘__fw_state_check’ defined but not used [-Wunused-function] code." for the time beeing. Cc: Ming Lei Signed-off-by: Daniel Wagner Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 51 +++++++++++++------------------------------ 1 file changed, 15 insertions(+), 36 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 45a20dd31c25..95f566c10a55 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -111,13 +112,13 @@ static inline long firmware_loading_timeout(void) * state of the firmware loading. */ struct fw_state { - struct completion completion; + struct swait_queue_head wq; enum fw_status status; }; static void fw_state_init(struct fw_state *fw_st) { - init_completion(&fw_st->completion); + init_swait_queue_head(&fw_st->wq); fw_st->status = FW_STATUS_UNKNOWN; } @@ -126,13 +127,19 @@ static int __fw_state_check(struct fw_state *fw_st, enum fw_status status) return fw_st->status == status; } +static inline bool __fw_state_is_done(enum fw_status status) +{ + return status == FW_STATUS_DONE || status == FW_STATUS_ABORTED; +} + static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) { long ret; - ret = wait_for_completion_interruptible_timeout(&fw_st->completion, - timeout); - if (ret != 0 && READ_ONCE(fw_st->status) == FW_STATUS_ABORTED) + ret = swait_event_interruptible_timeout(fw_st->wq, + __fw_state_is_done(READ_ONCE(fw_st->status)), + timeout); + if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) return -ENOENT; return ret; @@ -144,7 +151,7 @@ static void __fw_state_set(struct fw_state *fw_st, WRITE_ONCE(fw_st->status, status); if (status == FW_STATUS_DONE || status == FW_STATUS_ABORTED) - complete_all(&fw_st->completion); + swake_up(&fw_st->wq); } #define fw_state_start(fw_st) \ @@ -373,14 +380,6 @@ static const char * const fw_path[] = { module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); -static void fw_finish_direct_load(struct device *device, - struct firmware_buf *buf) -{ - mutex_lock(&fw_lock); - fw_state_done(&buf->fw_st); - mutex_unlock(&fw_lock); -} - static int fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf) { @@ -427,7 +426,7 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf) } dev_dbg(device, "direct-loading %s\n", buf->fw_id); buf->size = size; - fw_finish_direct_load(device, buf); + fw_state_done(&buf->fw_st); break; } __putname(path); @@ -1084,26 +1083,6 @@ static inline void kill_requests_without_uevent(void) { } #endif /* CONFIG_FW_LOADER_USER_HELPER */ - -/* wait until the shared firmware_buf becomes ready (or error) */ -static int sync_cached_firmware_buf(struct firmware_buf *buf) -{ - int ret = 0; - - mutex_lock(&fw_lock); - while (!fw_state_is_done(&buf->fw_st)) { - if (fw_state_is_aborted(&buf->fw_st)) { - ret = -ENOENT; - break; - } - mutex_unlock(&fw_lock); - ret = fw_state_wait(&buf->fw_st); - mutex_lock(&fw_lock); - } - mutex_unlock(&fw_lock); - return ret; -} - /* prepare firmware and firmware_buf structs; * return 0 if a firmware is already assigned, 1 if need to load one, * or a negative error code @@ -1137,7 +1116,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name, firmware->priv = buf; if (ret > 0) { - ret = sync_cached_firmware_buf(buf); + ret = fw_state_wait(&buf->fw_st); if (!ret) { fw_set_page_data(buf, firmware); return 0; /* assigned */ -- cgit v1.2.3 From fab82cb3f1e16f22a1910d380ef572e6fe0e9da9 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Thu, 17 Nov 2016 11:00:51 +0100 Subject: firmware: move fw_state_is_done() into UHM section fw_state_is_done() is only used for UHM so moved into that section. Cc: Ming Lei Signed-off-by: Daniel Wagner Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 95f566c10a55..f8ef983c8a0f 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -122,11 +122,6 @@ static void fw_state_init(struct fw_state *fw_st) fw_st->status = FW_STATUS_UNKNOWN; } -static int __fw_state_check(struct fw_state *fw_st, enum fw_status status) -{ - return fw_st->status == status; -} - static inline bool __fw_state_is_done(enum fw_status status) { return status == FW_STATUS_DONE || status == FW_STATUS_ABORTED; @@ -158,8 +153,6 @@ static void __fw_state_set(struct fw_state *fw_st, __fw_state_set(fw_st, FW_STATUS_LOADING) #define fw_state_done(fw_st) \ __fw_state_set(fw_st, FW_STATUS_DONE) -#define fw_state_is_done(fw_st) \ - __fw_state_check(fw_st, FW_STATUS_DONE) #define fw_state_wait(fw_st) \ __fw_state_wait_common(fw_st, MAX_SCHEDULE_TIMEOUT) @@ -169,8 +162,15 @@ static void __fw_state_set(struct fw_state *fw_st, #else /* CONFIG_FW_LOADER_USER_HELPER */ +static int __fw_state_check(struct fw_state *fw_st, enum fw_status status) +{ + return fw_st->status == status; +} + #define fw_state_aborted(fw_st) \ __fw_state_set(fw_st, FW_STATUS_ABORTED) +#define fw_state_is_done(fw_st) \ + __fw_state_check(fw_st, FW_STATUS_DONE) #define fw_state_is_loading(fw_st) \ __fw_state_check(fw_st, FW_STATUS_LOADING) #define fw_state_is_aborted(fw_st) \ -- cgit v1.2.3 From 0e0d3d2c5f9901dad9f375614ab4362e95e631ba Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 29 Nov 2016 22:06:42 +0100 Subject: driver core: test_async: fix up typo found by 0-day 0-day pointed out a typo in the platform device registration logic, so fix it. Reported-by: kbuild test robot Cc: Guenter Roeck Cc: Dmitry Torokhov Cc: Julia Lawall Cc: Thierry Escande Signed-off-by: Greg Kroah-Hartman --- drivers/base/test/test_async_driver_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/test/test_async_driver_probe.c b/drivers/base/test/test_async_driver_probe.c index 1c5eddda5cc2..304d5c2bd5e9 100644 --- a/drivers/base/test/test_async_driver_probe.c +++ b/drivers/base/test/test_async_driver_probe.c @@ -116,7 +116,7 @@ static int __init test_async_probe_init(void) calltime = ktime_get(); sync_dev_1 = platform_device_register_simple("test_sync_driver", 1, NULL, 0); - if (IS_ERR(async_dev_1)) { + if (IS_ERR(sync_dev_1)) { error = PTR_ERR(sync_dev_1); pr_err("failed to create sync_dev_1: %d", error); goto err_unregister_sync_driver; -- cgit v1.2.3 From cd74da957ba2d03787ede1c22bbb183d9c728aad Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 29 Nov 2016 17:21:05 -0800 Subject: drivers: base: dma-mapping: Fix typo in dmam_alloc_non_coherent comments The function we are wrapping is named dma_alloc_noncoherent, and not dma_alloc_non_coherent. Fixes: 9ac7849e35f70 ("devres: device resource management") Signed-off-by: Florian Fainelli Signed-off-by: Greg Kroah-Hartman --- drivers/base/dma-mapping.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/dma-mapping.c b/drivers/base/dma-mapping.c index 8f8b68c80986..efd71cf4fdea 100644 --- a/drivers/base/dma-mapping.c +++ b/drivers/base/dma-mapping.c @@ -108,13 +108,13 @@ void dmam_free_coherent(struct device *dev, size_t size, void *vaddr, EXPORT_SYMBOL(dmam_free_coherent); /** - * dmam_alloc_non_coherent - Managed dma_alloc_non_coherent() + * dmam_alloc_non_coherent - Managed dma_alloc_noncoherent() * @dev: Device to allocate non_coherent memory for * @size: Size of allocation * @dma_handle: Out argument for allocated DMA handle * @gfp: Allocation flags * - * Managed dma_alloc_non_coherent(). Memory allocated using this + * Managed dma_alloc_noncoherent(). Memory allocated using this * function will be automatically released on driver detach. * * RETURNS: -- cgit v1.2.3 From 88bcef508f230c05deaa6e51ddf90ccae15a2824 Mon Sep 17 00:00:00 2001 From: Silvio Fricke Date: Fri, 25 Nov 2016 15:59:47 +0100 Subject: firmware: remove warning at documentation generation time This patch removes following error at for `make htmldocs`. No functional change. ./drivers/base/firmware_class.c:1348: WARNING: Bullet list ends without a blank line; unexpected unindent. Signed-off-by: Silvio Fricke Reviewed-by: Mauro Carvalho Chehab Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index f8ef983c8a0f..eb95cf7c3b28 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -1384,9 +1384,9 @@ static void request_firmware_work_func(struct work_struct *work) * * Asynchronous variant of request_firmware() for user contexts: * - sleep for as small periods as possible since it may - * increase kernel boot time of built-in device drivers - * requesting firmware in their ->probe() methods, if - * @gfp is GFP_KERNEL. + * increase kernel boot time of built-in device drivers + * requesting firmware in their ->probe() methods, if + * @gfp is GFP_KERNEL. * * - can't sleep at all if @gfp is GFP_ATOMIC. **/ -- cgit v1.2.3 From 64df1148876e35e81e91195e01c8197edc66fcc5 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 4 Dec 2016 13:10:04 +0100 Subject: driver core: Silence device links sphinx warning Silence this warning emitted by sphinx: include/linux/device.h:938: warning: No description found for parameter 'links' While at it, fix typos in comments of device links code. Cc: Rafael J. Wysocki Cc: Jonathan Corbet Cc: Silvio Fricke Signed-off-by: Lukas Wunner Reviewed-by: Mauro Carvalho Chehab Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 4 ++-- include/linux/device.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/core.c b/drivers/base/core.c index b8b2f6105476..020ea7f05520 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -172,7 +172,7 @@ static int device_reorder_to_tail(struct device *dev, void *not_used) * * The supplier device is required to be registered when this function is called * and NULL will be returned if that is not the case. The consumer device need - * not be registerd, however. + * not be registered, however. */ struct device_link *device_link_add(struct device *consumer, struct device *supplier, u32 flags) @@ -225,7 +225,7 @@ struct device_link *device_link_add(struct device *consumer, INIT_LIST_HEAD(&link->c_node); link->flags = flags; - /* Deterine the initial link state. */ + /* Determine the initial link state. */ if (flags & DL_FLAG_STATELESS) { link->status = DL_STATE_NONE; } else { diff --git a/include/linux/device.h b/include/linux/device.h index 4cd8e52033b0..67bbbee8fe02 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -817,6 +817,7 @@ struct dev_links_info { * on. This shrinks the "Board Support Packages" (BSPs) and * minimizes board-specific #ifdefs in drivers. * @driver_data: Private pointer for driver specific info. + * @links: Links to suppliers and consumers of this device. * @power: For device power management. * See Documentation/power/devices.txt for details. * @pm_domain: Provide callbacks that are executed during system suspend, -- cgit v1.2.3 From 5d47ec02c37ea632398cb251c884e3a488dff794 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Tue, 6 Dec 2016 17:01:45 -0800 Subject: firmware: Correct handling of fw_state_wait() return value When request_firmware() finds an already open firmware object it will wait for that object to become fully loaded and then check the status. As __fw_state_wait_common() succeeds the timeout value returned will be truncated in _request_firmware_prepare() and interpreted as -EPERM. Prior to "firmware: do not use fw_lock for fw_state protection" the code did test if we where in the "done" state before sleeping, causing this particular code path to succeed, in some cases. As the callers are interested in the result of the wait and not the remaining timeout the return value of __fw_state_wait_common() is changed to signal "done" or "error", which simplifies the logic in _request_firmware_load() as well. Fixes: 5b029624948d ("firmware: do not use fw_lock for fw_state protection") Signed-off-by: Bjorn Andersson Reviewed-by: Daniel Wagner Acked-by: Luis R. Rodriguez Signed-off-by: Greg Kroah-Hartman --- drivers/base/firmware_class.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index eb95cf7c3b28..4497d263209f 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -127,7 +127,7 @@ static inline bool __fw_state_is_done(enum fw_status status) return status == FW_STATUS_DONE || status == FW_STATUS_ABORTED; } -static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) +static int __fw_state_wait_common(struct fw_state *fw_st, long timeout) { long ret; @@ -136,8 +136,10 @@ static long __fw_state_wait_common(struct fw_state *fw_st, long timeout) timeout); if (ret != 0 && fw_st->status == FW_STATUS_ABORTED) return -ENOENT; + if (!ret) + return -ETIMEDOUT; - return ret; + return ret < 0 ? ret : 0; } static void __fw_state_set(struct fw_state *fw_st, @@ -1017,14 +1019,11 @@ static int _request_firmware_load(struct firmware_priv *fw_priv, timeout = MAX_JIFFY_OFFSET; } - timeout = fw_state_wait_timeout(&buf->fw_st, timeout); - if (timeout == -ERESTARTSYS || !timeout) { - retval = timeout; + retval = fw_state_wait_timeout(&buf->fw_st, timeout); + if (retval < 0) { mutex_lock(&fw_lock); fw_load_abort(fw_priv); mutex_unlock(&fw_lock); - } else if (timeout > 0) { - retval = 0; } if (fw_state_is_aborted(&buf->fw_st)) -- cgit v1.2.3