Commit 8f8184d6 authored by Saravana Kannan's avatar Saravana Kannan Committed by Greg Kroah-Hartman

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: default avatarkbuild test robot <lkp@intel.com>
Signed-off-by: default avatarSaravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20190731221721.187713-5-saravanak@google.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 690ff788
...@@ -46,6 +46,8 @@ early_param("sysfs.deprecated", sysfs_deprecated_setup); ...@@ -46,6 +46,8 @@ early_param("sysfs.deprecated", sysfs_deprecated_setup);
/* Device links support. */ /* Device links support. */
static LIST_HEAD(wait_for_suppliers); static LIST_HEAD(wait_for_suppliers);
static DEFINE_MUTEX(wfs_lock); static DEFINE_MUTEX(wfs_lock);
static LIST_HEAD(deferred_sync);
static unsigned int supplier_sync_state_disabled;
#ifdef CONFIG_SRCU #ifdef CONFIG_SRCU
static DEFINE_MUTEX(device_links_lock); static DEFINE_MUTEX(device_links_lock);
...@@ -651,6 +653,62 @@ int device_links_check_suppliers(struct device *dev) ...@@ -651,6 +653,62 @@ int device_links_check_suppliers(struct device *dev)
return ret; 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();
supplier_sync_state_disabled++;
device_links_write_unlock();
}
void device_links_supplier_sync_state_resume(void)
{
struct device *dev, *tmp;
device_links_write_lock();
if (!supplier_sync_state_disabled) {
WARN(true, "Unmatched sync_state pause/resume!");
goto out;
}
supplier_sync_state_disabled--;
if (supplier_sync_state_disabled)
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 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. * device_links_driver_bound - Update device links after probing its driver.
* @dev: Device to update the links for. * @dev: Device to update the links for.
...@@ -695,6 +753,11 @@ void device_links_driver_bound(struct device *dev) ...@@ -695,6 +753,11 @@ void device_links_driver_bound(struct device *dev)
WARN_ON(link->status != DL_STATE_CONSUMER_PROBE); WARN_ON(link->status != DL_STATE_CONSUMER_PROBE);
WRITE_ONCE(link->status, DL_STATE_ACTIVE); WRITE_ONCE(link->status, DL_STATE_ACTIVE);
if (supplier_sync_state_disabled)
__device_links_supplier_defer_sync(link->supplier);
else
__device_links_supplier_sync_state(link->supplier);
} }
dev->links.status = DL_DEV_DRIVER_BOUND; dev->links.status = DL_DEV_DRIVER_BOUND;
...@@ -811,6 +874,7 @@ void device_links_driver_cleanup(struct device *dev) ...@@ -811,6 +874,7 @@ void device_links_driver_cleanup(struct device *dev)
WRITE_ONCE(link->status, DL_STATE_DORMANT); WRITE_ONCE(link->status, DL_STATE_DORMANT);
} }
list_del_init(&dev->links.defer_sync);
__device_links_no_driver(dev); __device_links_no_driver(dev);
device_links_write_unlock(); device_links_write_unlock();
...@@ -1782,6 +1846,7 @@ void device_initialize(struct device *dev) ...@@ -1782,6 +1846,7 @@ void device_initialize(struct device *dev)
INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers); INIT_LIST_HEAD(&dev->links.suppliers);
INIT_LIST_HEAD(&dev->links.needs_suppliers); INIT_LIST_HEAD(&dev->links.needs_suppliers);
INIT_LIST_HEAD(&dev->links.defer_sync);
dev->links.status = DL_DEV_NO_DRIVER; dev->links.status = DL_DEV_NO_DRIVER;
} }
EXPORT_SYMBOL_GPL(device_initialize); EXPORT_SYMBOL_GPL(device_initialize);
......
...@@ -84,6 +84,8 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *); ...@@ -84,6 +84,8 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *);
* available at the time this function is called. As in, the * available at the time this function is called. As in, the
* function should NOT stop at the first failed device link if * function should NOT stop at the first failed device link if
* other unlinked supplier devices are present in the system. * other unlinked supplier devices are present in the system.
* This is necessary for the sync_state() callback to work
* correctly.
* *
* Return 0 if device links have been successfully created to all * Return 0 if device links have been successfully created to all
* the suppliers of this device. Return an error if some of the * the suppliers of this device. Return an error if some of the
...@@ -91,6 +93,13 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *); ...@@ -91,6 +93,13 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *);
* reattempted in the future. * reattempted in the future.
* @probe: Called when a new device or driver add to this bus, and callback * @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device. * 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. * @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device. * @shutdown: Called at shut-down time to quiesce the device.
* *
...@@ -135,6 +144,7 @@ struct bus_type { ...@@ -135,6 +144,7 @@ struct bus_type {
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*add_links)(struct device *dev); int (*add_links)(struct device *dev);
int (*probe)(struct device *dev); int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
int (*remove)(struct device *dev); int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev); void (*shutdown)(struct device *dev);
...@@ -366,6 +376,13 @@ enum probe_type { ...@@ -366,6 +376,13 @@ enum probe_type {
* @probe: Called to query the existence of a specific device, * @probe: Called to query the existence of a specific device,
* whether this driver can work with it, and bind the driver * whether this driver can work with it, and bind the driver
* to a specific device. * 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 * @remove: Called when the device is removed from the system to
* unbind a device from this driver. * unbind a device from this driver.
* @shutdown: Called at shut-down time to quiesce the device. * @shutdown: Called at shut-down time to quiesce the device.
...@@ -404,6 +421,7 @@ struct device_driver { ...@@ -404,6 +421,7 @@ struct device_driver {
int (*edit_links)(struct device *dev); int (*edit_links)(struct device *dev);
int (*probe) (struct device *dev); int (*probe) (struct device *dev);
void (*sync_state)(struct device *dev);
int (*remove) (struct device *dev); int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev); void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state); int (*suspend) (struct device *dev, pm_message_t state);
...@@ -1156,12 +1174,14 @@ enum dl_dev_state { ...@@ -1156,12 +1174,14 @@ enum dl_dev_state {
* @suppliers: List of links to supplier devices. * @suppliers: List of links to supplier devices.
* @consumers: List of links to consumer devices. * @consumers: List of links to consumer devices.
* @needs_suppliers: Hook to global list of devices waiting for suppliers. * @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. * @status: Driver status information.
*/ */
struct dev_links_info { struct dev_links_info {
struct list_head suppliers; struct list_head suppliers;
struct list_head consumers; struct list_head consumers;
struct list_head needs_suppliers; struct list_head needs_suppliers;
struct list_head defer_sync;
enum dl_dev_state status; enum dl_dev_state status;
}; };
...@@ -1237,6 +1257,9 @@ struct dev_links_info { ...@@ -1237,6 +1257,9 @@ struct dev_links_info {
* device. * device.
* @has_edit_links: This device has a driver than is capable of * @has_edit_links: This device has a driver than is capable of
* editing the device links created by driver core. * editing the device links created by driver core.
* @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 * @dma_coherent: this particular device is dma coherent, even if the
* architecture supports non-coherent devices. * architecture supports non-coherent devices.
* *
...@@ -1331,6 +1354,7 @@ struct device { ...@@ -1331,6 +1354,7 @@ struct device {
bool offline:1; bool offline:1;
bool of_node_reused:1; bool of_node_reused:1;
bool has_edit_links:1; bool has_edit_links:1;
bool state_synced:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ #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) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL) defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
...@@ -1674,6 +1698,8 @@ struct device_link *device_link_add(struct device *consumer, ...@@ -1674,6 +1698,8 @@ struct device_link *device_link_add(struct device *consumer,
void device_link_del(struct device_link *link); void device_link_del(struct device_link *link);
void device_link_remove(void *consumer, struct device *supplier); void device_link_remove(void *consumer, struct device *supplier);
void device_link_remove_from_wfs(struct device *consumer); void device_link_remove_from_wfs(struct device *consumer);
void device_links_supplier_sync_state_pause(void);
void device_links_supplier_sync_state_resume(void);
#ifndef dev_fmt #ifndef dev_fmt
#define dev_fmt(fmt) fmt #define dev_fmt(fmt) fmt
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment