|
@@ -44,6 +44,572 @@ 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.
|
|
|
+ *
|
|
|
+ * 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
|
|
|
+ * 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 registered, 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 = kzalloc(sizeof(*link), GFP_KERNEL);
|
|
|
+ if (!link)
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ pm_runtime_new_link(consumer);
|
|
|
+ }
|
|
|
+ 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;
|
|
|
+
|
|
|
+ /* Determine 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:
|
|
|
+ /*
|
|
|
+ * 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:
|
|
|
+ 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));
|
|
|
+
|
|
|
+ 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);
|
|
|
+}
|
|
|
+#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;
|
|
@@ -494,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:
|
|
@@ -513,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);
|
|
|
|
|
@@ -711,6 +1284,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 +1834,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);
|