OSDN Git Service

driver core: Expose device link details in sysfs
authorSaravana Kannan <saravanak@google.com>
Thu, 21 May 2020 19:17:58 +0000 (12:17 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 10 Jul 2020 13:24:56 +0000 (15:24 +0200)
It's helpful to be able to look at device link details from sysfs. So,
expose it in sysfs.

Say device-A is supplier of device-B. These are the additional files
this patch would create:

/sys/class/devlink/device-A:device-B/
auto_remove_on
consumer/ -> .../device-B/
runtime_pm
status
supplier/ -> .../device-A/
sync_state_only

/sys/devices/.../device-A/
consumer:device-B/ -> /sys/class/devlink/device-A:device-B/

/sys/devices/.../device-B/
supplier:device-A/ -> /sys/class/devlink/device-A:device-B/

That way:
To get a list of all the device link in the system:
ls /sys/class/devlink/

To get the consumer names and links of a device:
ls -d /sys/devices/.../device-X/consumer:*

To get the supplier names and links of a device:
ls -d /sys/devices/.../device-X/supplier:*

Signed-off-by: Saravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20200521191800.136035-2-saravanak@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-class-devlink [new file with mode: 0644]
Documentation/ABI/testing/sysfs-devices-consumer [new file with mode: 0644]
Documentation/ABI/testing/sysfs-devices-supplier [new file with mode: 0644]
drivers/base/core.c
include/linux/device.h

diff --git a/Documentation/ABI/testing/sysfs-class-devlink b/Documentation/ABI/testing/sysfs-class-devlink
new file mode 100644 (file)
index 0000000..3a24973
--- /dev/null
@@ -0,0 +1,126 @@
+What:          /sys/class/devlink/.../
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               Provide a place in sysfs for the device link objects in the
+               kernel at any given time.  The name of a device link directory,
+               denoted as ... above, is of the form <supplier>:<consumer>
+               where <supplier> is the supplier device name and <consumer> is
+               the consumer device name.
+
+What:          /sys/class/devlink/.../auto_remove_on
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               This file indicates if the device link will ever be
+               automatically removed by the driver core when the consumer and
+               supplier devices themselves are still present.
+
+               This will be one of the following strings:
+
+               'consumer unbind'
+               'supplier unbind'
+               'never'
+
+               'consumer unbind' means the device link will be removed when
+               the consumer's driver is unbound from the consumer device.
+
+               'supplier unbind' means the device link will be removed when
+               the supplier's driver is unbound from the supplier device.
+
+               'never' means the device link will not be automatically removed
+               when as long as the supplier and consumer devices themselves
+               are still present.
+
+What:          /sys/class/devlink/.../consumer
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               This file is a symlink to the consumer device's sysfs directory.
+
+What:          /sys/class/devlink/.../runtime_pm
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               This file indicates if the device link has any impact on the
+               runtime power management behavior of the consumer and supplier
+               devices. For example: Making sure the supplier doesn't enter
+               runtime suspend while the consumer is active.
+
+               This will be one of the following strings:
+
+               '0' - Does not affect runtime power management
+               '1' - Affects runtime power management
+
+What:          /sys/class/devlink/.../status
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               This file indicates the status of the device link. The status
+               of a device link is affected by whether the supplier and
+               consumer devices have been bound to their corresponding
+               drivers. The status of a device link also affects the binding
+               and unbinding of the supplier and consumer devices with their
+               drivers and also affects whether the software state of the
+               supplier device is synced with the hardware state of the
+               supplier device after boot up.
+               See also: sysfs-devices-state_synced.
+
+               This will be one of the following strings:
+
+               'not tracked'
+               'dormant'
+               'available'
+               'consumer probing'
+               'active'
+               'supplier unbinding'
+               'unknown'
+
+               'not tracked' means this device link does not track the status
+               and has no impact on the binding, unbinding and syncing the
+               hardware and software device state.
+
+               'dormant' means the supplier and the consumer devices have not
+               bound to their driver.
+
+               'available' means the supplier has bound to its driver and is
+               available to supply resources to the consumer device.
+
+               'consumer probing' means the consumer device is currently
+               trying to bind to its driver.
+
+               'active' means the supplier and consumer devices have both
+               bound successfully to their drivers.
+
+               'supplier unbinding' means the supplier devices is currently in
+               the process of unbinding from its driver.
+
+               'unknown' means the state of the device link is not any of the
+               above. If this is ever the value, there's a bug in the kernel.
+
+What:          /sys/class/devlink/.../supplier
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               This file is a symlink to the supplier device's sysfs directory.
+
+What:          /sys/class/devlink/.../sync_state_only
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               This file indicates if the device link is limited to only
+               affecting the syncing of the hardware and software state of the
+               supplier device.
+
+               This will be one of the following strings:
+
+               '0'
+               '1' - Affects runtime power management
+
+               '0' means the device link can affect other device behaviors
+               like binding/unbinding, suspend/resume, runtime power
+               management, etc.
+
+               '1' means the device link will only affect the syncing of
+               hardware and software state of the supplier device after boot
+               up and doesn't not affect other behaviors of the devices.
diff --git a/Documentation/ABI/testing/sysfs-devices-consumer b/Documentation/ABI/testing/sysfs-devices-consumer
new file mode 100644 (file)
index 0000000..1f06d74
--- /dev/null
@@ -0,0 +1,8 @@
+What:          /sys/devices/.../consumer:<consumer>
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               The /sys/devices/.../consumer:<consumer> are symlinks to device
+               links where this device is the supplier. <consumer> denotes the
+               name of the consumer in that device link. There can be zero or
+               more of these symlinks for a given device.
diff --git a/Documentation/ABI/testing/sysfs-devices-supplier b/Documentation/ABI/testing/sysfs-devices-supplier
new file mode 100644 (file)
index 0000000..a919e0d
--- /dev/null
@@ -0,0 +1,8 @@
+What:          /sys/devices/.../supplier:<supplier>
+Date:          May 2020
+Contact:       Saravana Kannan <saravanak@google.com>
+Description:
+               The /sys/devices/.../supplier:<supplier> are symlinks to device
+               links where this device is the consumer. <supplier> denotes the
+               name of the supplier in that device link. There can be zero or
+               more of these symlinks for a given device.
index 67d39a9..ca64033 100644 (file)
@@ -235,6 +235,186 @@ void device_pm_move_to_tail(struct device *dev)
        device_links_read_unlock(idx);
 }
 
+#define to_devlink(dev)        container_of((dev), struct device_link, link_dev)
+
+static ssize_t status_show(struct device *dev,
+                         struct device_attribute *attr, char *buf)
+{
+       char *status;
+
+       switch (to_devlink(dev)->status) {
+       case DL_STATE_NONE:
+               status = "not tracked"; break;
+       case DL_STATE_DORMANT:
+               status = "dormant"; break;
+       case DL_STATE_AVAILABLE:
+               status = "available"; break;
+       case DL_STATE_CONSUMER_PROBE:
+               status = "consumer probing"; break;
+       case DL_STATE_ACTIVE:
+               status = "active"; break;
+       case DL_STATE_SUPPLIER_UNBIND:
+               status = "supplier unbinding"; break;
+       default:
+               status = "unknown"; break;
+       }
+       return sprintf(buf, "%s\n", status);
+}
+static DEVICE_ATTR_RO(status);
+
+static ssize_t auto_remove_on_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct device_link *link = to_devlink(dev);
+       char *str;
+
+       if (link->flags & DL_FLAG_AUTOREMOVE_SUPPLIER)
+               str = "supplier unbind";
+       else if (link->flags & DL_FLAG_AUTOREMOVE_CONSUMER)
+               str = "consumer unbind";
+       else
+               str = "never";
+
+       return sprintf(buf, "%s\n", str);
+}
+static DEVICE_ATTR_RO(auto_remove_on);
+
+static ssize_t runtime_pm_show(struct device *dev,
+                              struct device_attribute *attr, char *buf)
+{
+       struct device_link *link = to_devlink(dev);
+
+       return sprintf(buf, "%d\n", !!(link->flags & DL_FLAG_PM_RUNTIME));
+}
+static DEVICE_ATTR_RO(runtime_pm);
+
+static ssize_t sync_state_only_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct device_link *link = to_devlink(dev);
+
+       return sprintf(buf, "%d\n", !!(link->flags & DL_FLAG_SYNC_STATE_ONLY));
+}
+static DEVICE_ATTR_RO(sync_state_only);
+
+static struct attribute *devlink_attrs[] = {
+       &dev_attr_status.attr,
+       &dev_attr_auto_remove_on.attr,
+       &dev_attr_runtime_pm.attr,
+       &dev_attr_sync_state_only.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(devlink);
+
+static void devlink_dev_release(struct device *dev)
+{
+       kfree(to_devlink(dev));
+}
+
+static struct class devlink_class = {
+       .name = "devlink",
+       .owner = THIS_MODULE,
+       .dev_groups = devlink_groups,
+       .dev_release = devlink_dev_release,
+};
+
+static int devlink_add_symlinks(struct device *dev,
+                               struct class_interface *class_intf)
+{
+       int ret;
+       size_t len;
+       struct device_link *link = to_devlink(dev);
+       struct device *sup = link->supplier;
+       struct device *con = link->consumer;
+       char *buf;
+
+       len = max(strlen(dev_name(sup)), strlen(dev_name(con)));
+       len += strlen("supplier:") + 1;
+       buf = kzalloc(len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = sysfs_create_link(&link->link_dev.kobj, &sup->kobj, "supplier");
+       if (ret)
+               goto out;
+
+       ret = sysfs_create_link(&link->link_dev.kobj, &con->kobj, "consumer");
+       if (ret)
+               goto err_con;
+
+       snprintf(buf, len, "consumer:%s", dev_name(con));
+       ret = sysfs_create_link(&sup->kobj, &link->link_dev.kobj, buf);
+       if (ret)
+               goto err_con_dev;
+
+       snprintf(buf, len, "supplier:%s", dev_name(sup));
+       ret = sysfs_create_link(&con->kobj, &link->link_dev.kobj, buf);
+       if (ret)
+               goto err_sup_dev;
+
+       goto out;
+
+err_sup_dev:
+       snprintf(buf, len, "consumer:%s", dev_name(con));
+       sysfs_remove_link(&sup->kobj, buf);
+err_con_dev:
+       sysfs_remove_link(&link->link_dev.kobj, "consumer");
+err_con:
+       sysfs_remove_link(&link->link_dev.kobj, "supplier");
+out:
+       kfree(buf);
+       return ret;
+}
+
+static void devlink_remove_symlinks(struct device *dev,
+                                  struct class_interface *class_intf)
+{
+       struct device_link *link = to_devlink(dev);
+       size_t len;
+       struct device *sup = link->supplier;
+       struct device *con = link->consumer;
+       char *buf;
+
+       sysfs_remove_link(&link->link_dev.kobj, "consumer");
+       sysfs_remove_link(&link->link_dev.kobj, "supplier");
+
+       len = max(strlen(dev_name(sup)), strlen(dev_name(con)));
+       len += strlen("supplier:") + 1;
+       buf = kzalloc(len, GFP_KERNEL);
+       if (!buf) {
+               WARN(1, "Unable to properly free device link symlinks!\n");
+               return;
+       }
+
+       snprintf(buf, len, "supplier:%s", dev_name(sup));
+       sysfs_remove_link(&con->kobj, buf);
+       snprintf(buf, len, "consumer:%s", dev_name(con));
+       sysfs_remove_link(&sup->kobj, buf);
+       kfree(buf);
+}
+
+static struct class_interface devlink_class_intf = {
+       .class = &devlink_class,
+       .add_dev = devlink_add_symlinks,
+       .remove_dev = devlink_remove_symlinks,
+};
+
+static int __init devlink_class_init(void)
+{
+       int ret;
+
+       ret = class_register(&devlink_class);
+       if (ret)
+               return ret;
+
+       ret = class_interface_register(&devlink_class_intf);
+       if (ret)
+               class_unregister(&devlink_class);
+
+       return ret;
+}
+postcore_initcall(devlink_class_init);
+
 #define DL_MANAGED_LINK_FLAGS (DL_FLAG_AUTOREMOVE_CONSUMER | \
                               DL_FLAG_AUTOREMOVE_SUPPLIER | \
                               DL_FLAG_AUTOPROBE_CONSUMER  | \
@@ -407,13 +587,6 @@ struct device_link *device_link_add(struct device *consumer,
 
        refcount_set(&link->rpm_active, 1);
 
-       if (flags & DL_FLAG_PM_RUNTIME) {
-               if (flags & DL_FLAG_RPM_ACTIVE)
-                       refcount_inc(&link->rpm_active);
-
-               pm_runtime_new_link(consumer);
-       }
-
        get_device(supplier);
        link->supplier = supplier;
        INIT_LIST_HEAD(&link->s_node);
@@ -423,6 +596,25 @@ struct device_link *device_link_add(struct device *consumer,
        link->flags = flags;
        kref_init(&link->kref);
 
+       link->link_dev.class = &devlink_class;
+       device_set_pm_not_required(&link->link_dev);
+       dev_set_name(&link->link_dev, "%s:%s",
+                    dev_name(supplier), dev_name(consumer));
+       if (device_register(&link->link_dev)) {
+               put_device(consumer);
+               put_device(supplier);
+               kfree(link);
+               link = NULL;
+               goto out;
+       }
+
+       if (flags & DL_FLAG_PM_RUNTIME) {
+               if (flags & DL_FLAG_RPM_ACTIVE)
+                       refcount_inc(&link->rpm_active);
+
+               pm_runtime_new_link(consumer);
+       }
+
        /* Determine the initial link state. */
        if (flags & DL_FLAG_STATELESS)
                link->status = DL_STATE_NONE;
@@ -545,7 +737,7 @@ static void device_link_free(struct device_link *link)
 
        put_device(link->consumer);
        put_device(link->supplier);
-       kfree(link);
+       device_unregister(&link->link_dev);
 }
 
 #ifdef CONFIG_SRCU
@@ -1159,6 +1351,9 @@ static void device_links_purge(struct device *dev)
 {
        struct device_link *link, *ln;
 
+       if (dev->class == &devlink_class)
+               return;
+
        mutex_lock(&wfs_lock);
        list_del(&dev->links.needs_suppliers);
        mutex_unlock(&wfs_lock);
index 9a62f7f..efad96e 100644 (file)
@@ -387,34 +387,6 @@ enum device_link_state {
 #define DL_FLAG_SYNC_STATE_ONLY                BIT(7)
 
 /**
- * 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.
- * @rpm_active: Whether or not the consumer device is runtime-PM-active.
- * @kref: Count repeated addition of the same link.
- * @rcu_head: An RCU head to use for deferred execution of SRCU callbacks.
- * @supplier_preactivated: Supplier has been made active before consumer probe.
- */
-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;
-       refcount_t rpm_active;
-       struct kref kref;
-#ifdef CONFIG_SRCU
-       struct rcu_head rcu_head;
-#endif
-       bool supplier_preactivated; /* Owned by consumer probe. */
-};
-
-/**
  * 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.
@@ -624,6 +596,36 @@ struct device {
 #endif
 };
 
+/**
+ * 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.
+ * @link_dev: device used to expose link details in sysfs
+ * @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.
+ * @kref: Count repeated addition of the same link.
+ * @rcu_head: An RCU head to use for deferred execution of SRCU callbacks.
+ * @supplier_preactivated: Supplier has been made active before consumer probe.
+ */
+struct device_link {
+       struct device *supplier;
+       struct list_head s_node;
+       struct device *consumer;
+       struct list_head c_node;
+       struct device link_dev;
+       enum device_link_state status;
+       u32 flags;
+       refcount_t rpm_active;
+       struct kref kref;
+#ifdef CONFIG_SRCU
+       struct rcu_head rcu_head;
+#endif
+       bool supplier_preactivated; /* Owned by consumer probe. */
+};
+
 static inline struct device *kobj_to_dev(struct kobject *kobj)
 {
        return container_of(kobj, struct device, kobj);