summaryrefslogtreecommitdiff
path: root/drivers/base
diff options
context:
space:
mode:
authorSaravana Kannan <saravanak@google.com>2020-05-21 12:17:58 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-07-10 15:24:56 +0200
commit287905e68dd29873bcb7986a8290cd1e4cfde600 (patch)
tree2aea5d81358554da1110abeb3dfb34a1b25531aa /drivers/base
parentfe940d7362e6d7a5e5086581d0462e00ba766992 (diff)
driver core: Expose device link details in sysfs
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>
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/core.c211
1 files changed, 203 insertions, 8 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 67d39a90b45c..ca6403343515 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -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);