| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Saravana Kannan <saravanak@google.com> |
| Date: Wed, 4 Sep 2019 14:11:23 -0700 |
| Subject: FROMGIT: driver core: Add sync_state driver/bus callback |
| |
| This sync_state driver/bus callback is called once all the consumers |
| of a supplier have probed successfully. |
| |
| This allows the supplier device's driver/bus to sync the supplier |
| device's state to the software state with the guarantee that all the |
| consumers are actively managing the resources provided by the supplier |
| device. |
| |
| To maintain backwards compatibility and ease transition from existing |
| frameworks and resource cleanup schemes, late_initcall_sync is the |
| earliest when the sync_state callback might be called. |
| |
| There is no upper bound on the time by which the sync_state callback |
| has to be called. This is because if a consumer device never probes, |
| the supplier has to maintain its resources in the state left by the |
| bootloader. For example, if the bootloader leaves the display |
| backlight at a fixed voltage and the backlight driver is never probed, |
| you don't want the backlight to ever be turned off after boot up. |
| |
| Also, when multiple devices are added after kernel init, some |
| suppliers could be added before their consumer devices get added. In |
| these instances, the supplier devices could get their sync_state |
| callback called right after they probe because the consumers devices |
| haven't had a chance to create device links to the suppliers. |
| |
| To handle this correctly, this change also provides APIs to |
| pause/resume sync state callbacks so that when multiple devices are |
| added, their sync_state callback evaluation can be postponed to happen |
| after all of them are added. |
| |
| kbuild test robot reported missing documentation for device.state_synced |
| Reported-by: kbuild test robot <lkp@intel.com> |
| Signed-off-by: Saravana Kannan <saravanak@google.com> |
| Link: https://lore.kernel.org/r/20190904211126.47518-5-saravanak@google.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| (cherry-picked from commit fc5a251d0fd7ca9038bab78a8c97932c8c6ca23b |
| https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core.git driver-core-next) |
| |
| Bug: 141703324 |
| Change-Id: I3e0a483c76d6ae3e5fe5aa6b3df65d0417c65ebb |
| --- |
| drivers/base/core.c | 72 ++++++++++++++++++++++++++++++++++++++++++ |
| include/linux/device.h | 24 ++++++++++++++ |
| 2 files changed, 96 insertions(+) |
| |
| diff --git a/drivers/base/core.c b/drivers/base/core.c |
| index e54fef73f469..7ea665a97da2 100644 |
| --- a/drivers/base/core.c |
| +++ b/drivers/base/core.c |
| @@ -47,6 +47,8 @@ early_param("sysfs.deprecated", sysfs_deprecated_setup); |
| /* Device links support. */ |
| static LIST_HEAD(wait_for_suppliers); |
| static DEFINE_MUTEX(wfs_lock); |
| +static LIST_HEAD(deferred_sync); |
| +static unsigned int defer_sync_state_count = 1; |
| |
| #ifdef CONFIG_SRCU |
| static DEFINE_MUTEX(device_links_lock); |
| @@ -649,6 +651,69 @@ int device_links_check_suppliers(struct device *dev) |
| return ret; |
| } |
| |
| +static void __device_links_supplier_sync_state(struct device *dev) |
| +{ |
| + struct device_link *link; |
| + |
| + if (dev->state_synced) |
| + return; |
| + |
| + list_for_each_entry(link, &dev->links.consumers, s_node) { |
| + if (!(link->flags & DL_FLAG_MANAGED)) |
| + continue; |
| + if (link->status != DL_STATE_ACTIVE) |
| + return; |
| + } |
| + |
| + if (dev->bus->sync_state) |
| + dev->bus->sync_state(dev); |
| + else if (dev->driver && dev->driver->sync_state) |
| + dev->driver->sync_state(dev); |
| + |
| + dev->state_synced = true; |
| +} |
| + |
| +void device_links_supplier_sync_state_pause(void) |
| +{ |
| + device_links_write_lock(); |
| + defer_sync_state_count++; |
| + device_links_write_unlock(); |
| +} |
| + |
| +void device_links_supplier_sync_state_resume(void) |
| +{ |
| + struct device *dev, *tmp; |
| + |
| + device_links_write_lock(); |
| + if (!defer_sync_state_count) { |
| + WARN(true, "Unmatched sync_state pause/resume!"); |
| + goto out; |
| + } |
| + defer_sync_state_count--; |
| + if (defer_sync_state_count) |
| + goto out; |
| + |
| + list_for_each_entry_safe(dev, tmp, &deferred_sync, links.defer_sync) { |
| + __device_links_supplier_sync_state(dev); |
| + list_del_init(&dev->links.defer_sync); |
| + } |
| +out: |
| + device_links_write_unlock(); |
| +} |
| + |
| +static int sync_state_resume_initcall(void) |
| +{ |
| + device_links_supplier_sync_state_resume(); |
| + return 0; |
| +} |
| +late_initcall(sync_state_resume_initcall); |
| + |
| +static void __device_links_supplier_defer_sync(struct device *sup) |
| +{ |
| + if (list_empty(&sup->links.defer_sync)) |
| + list_add_tail(&sup->links.defer_sync, &deferred_sync); |
| +} |
| + |
| /** |
| * device_links_driver_bound - Update device links after probing its driver. |
| * @dev: Device to update the links for. |
| @@ -693,6 +758,11 @@ void device_links_driver_bound(struct device *dev) |
| |
| WARN_ON(link->status != DL_STATE_CONSUMER_PROBE); |
| WRITE_ONCE(link->status, DL_STATE_ACTIVE); |
| + |
| + if (defer_sync_state_count) |
| + __device_links_supplier_defer_sync(link->supplier); |
| + else |
| + __device_links_supplier_sync_state(link->supplier); |
| } |
| |
| dev->links.status = DL_DEV_DRIVER_BOUND; |
| @@ -809,6 +879,7 @@ void device_links_driver_cleanup(struct device *dev) |
| WRITE_ONCE(link->status, DL_STATE_DORMANT); |
| } |
| |
| + list_del_init(&dev->links.defer_sync); |
| __device_links_no_driver(dev); |
| |
| device_links_write_unlock(); |
| @@ -1783,6 +1854,7 @@ void device_initialize(struct device *dev) |
| INIT_LIST_HEAD(&dev->links.consumers); |
| INIT_LIST_HEAD(&dev->links.suppliers); |
| INIT_LIST_HEAD(&dev->links.needs_suppliers); |
| + INIT_LIST_HEAD(&dev->links.defer_sync); |
| dev->links.status = DL_DEV_NO_DRIVER; |
| } |
| EXPORT_SYMBOL_GPL(device_initialize); |
| diff --git a/include/linux/device.h b/include/linux/device.h |
| index c6fb5b3431b7..6978bb471567 100644 |
| --- a/include/linux/device.h |
| +++ b/include/linux/device.h |
| @@ -80,6 +80,13 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *); |
| * that generate uevents to add the environment variables. |
| * @probe: Called when a new device or driver add to this bus, and callback |
| * the specific driver's probe to initial the matched device. |
| + * @sync_state: Called to sync device state to software state after all the |
| + * state tracking consumers linked to this device (present at |
| + * the time of late_initcall) have successfully bound to a |
| + * driver. If the device has no consumers, this function will |
| + * be called at late_initcall_sync level. If the device has |
| + * consumers that are never bound to a driver, this function |
| + * will never get called until they do. |
| * @remove: Called when a device removed from this bus. |
| * @shutdown: Called at shut-down time to quiesce the device. |
| * |
| @@ -123,6 +130,7 @@ struct bus_type { |
| int (*match)(struct device *dev, struct device_driver *drv); |
| int (*uevent)(struct device *dev, struct kobj_uevent_env *env); |
| int (*probe)(struct device *dev); |
| + void (*sync_state)(struct device *dev); |
| int (*remove)(struct device *dev); |
| void (*shutdown)(struct device *dev); |
| |
| @@ -340,6 +348,13 @@ enum probe_type { |
| * @probe: Called to query the existence of a specific device, |
| * whether this driver can work with it, and bind the driver |
| * to a specific device. |
| + * @sync_state: Called to sync device state to software state after all the |
| + * state tracking consumers linked to this device (present at |
| + * the time of late_initcall) have successfully bound to a |
| + * driver. If the device has no consumers, this function will |
| + * be called at late_initcall_sync level. If the device has |
| + * consumers that are never bound to a driver, this function |
| + * will never get called until they do. |
| * @remove: Called when the device is removed from the system to |
| * unbind a device from this driver. |
| * @shutdown: Called at shut-down time to quiesce the device. |
| @@ -379,6 +394,7 @@ struct device_driver { |
| const struct acpi_device_id *acpi_match_table; |
| |
| int (*probe) (struct device *dev); |
| + void (*sync_state)(struct device *dev); |
| int (*remove) (struct device *dev); |
| void (*shutdown) (struct device *dev); |
| int (*suspend) (struct device *dev, pm_message_t state); |
| @@ -1136,12 +1152,14 @@ enum dl_dev_state { |
| * @suppliers: List of links to supplier devices. |
| * @consumers: List of links to consumer devices. |
| * @needs_suppliers: Hook to global list of devices waiting for suppliers. |
| + * @defer_sync: Hook to global list of devices that have deferred sync_state. |
| * @status: Driver status information. |
| */ |
| struct dev_links_info { |
| struct list_head suppliers; |
| struct list_head consumers; |
| struct list_head needs_suppliers; |
| + struct list_head defer_sync; |
| enum dl_dev_state status; |
| }; |
| |
| @@ -1217,6 +1235,9 @@ struct dev_links_info { |
| * @offline: Set after successful invocation of bus type's .offline(). |
| * @of_node_reused: Set if the device-tree node is shared with an ancestor |
| * device. |
| + * @state_synced: The hardware state of this device has been synced to match |
| + * the software state of this device by calling the driver/bus |
| + * sync_state() callback. |
| * @dma_coherent: this particular device is dma coherent, even if the |
| * architecture supports non-coherent devices. |
| * |
| @@ -1313,6 +1334,7 @@ struct device { |
| bool offline_disabled:1; |
| bool offline:1; |
| bool of_node_reused:1; |
| + bool state_synced:1; |
| #if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ |
| defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ |
| defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) |
| @@ -1655,6 +1677,8 @@ struct device_link *device_link_add(struct device *consumer, |
| struct device *supplier, u32 flags); |
| void device_link_del(struct device_link *link); |
| void device_link_remove(void *consumer, struct device *supplier); |
| +void device_links_supplier_sync_state_pause(void); |
| +void device_links_supplier_sync_state_resume(void); |
| |
| #ifndef dev_fmt |
| #define dev_fmt(fmt) fmt |