summaryrefslogtreecommitdiff
path: root/drivers/base/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/core.c')
-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);