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>
This commit is contained in:
Saravana Kannan 2020-05-21 12:17:58 -07:00 committed by Greg Kroah-Hartman
parent fe940d7362
commit 287905e68d
5 changed files with 375 additions and 36 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -235,6 +235,186 @@ void device_pm_move_to_tail(struct device *dev)
device_links_read_unlock(idx); 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 | \ #define DL_MANAGED_LINK_FLAGS (DL_FLAG_AUTOREMOVE_CONSUMER | \
DL_FLAG_AUTOREMOVE_SUPPLIER | \ DL_FLAG_AUTOREMOVE_SUPPLIER | \
DL_FLAG_AUTOPROBE_CONSUMER | \ DL_FLAG_AUTOPROBE_CONSUMER | \
@ -407,13 +587,6 @@ struct device_link *device_link_add(struct device *consumer,
refcount_set(&link->rpm_active, 1); 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); get_device(supplier);
link->supplier = supplier; link->supplier = supplier;
INIT_LIST_HEAD(&link->s_node); INIT_LIST_HEAD(&link->s_node);
@ -423,6 +596,25 @@ struct device_link *device_link_add(struct device *consumer,
link->flags = flags; link->flags = flags;
kref_init(&link->kref); 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. */ /* Determine the initial link state. */
if (flags & DL_FLAG_STATELESS) if (flags & DL_FLAG_STATELESS)
link->status = DL_STATE_NONE; 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->consumer);
put_device(link->supplier); put_device(link->supplier);
kfree(link); device_unregister(&link->link_dev);
} }
#ifdef CONFIG_SRCU #ifdef CONFIG_SRCU
@ -1159,6 +1351,9 @@ static void device_links_purge(struct device *dev)
{ {
struct device_link *link, *ln; struct device_link *link, *ln;
if (dev->class == &devlink_class)
return;
mutex_lock(&wfs_lock); mutex_lock(&wfs_lock);
list_del(&dev->links.needs_suppliers); list_del(&dev->links.needs_suppliers);
mutex_unlock(&wfs_lock); mutex_unlock(&wfs_lock);

View File

@ -386,34 +386,6 @@ enum device_link_state {
#define DL_FLAG_MANAGED BIT(6) #define DL_FLAG_MANAGED BIT(6)
#define DL_FLAG_SYNC_STATE_ONLY BIT(7) #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. * enum dl_dev_state - Device driver presence tracking information.
* @DL_DEV_NO_DRIVER: There is no driver attached to the device. * @DL_DEV_NO_DRIVER: There is no driver attached to the device.
@ -624,6 +596,36 @@ struct device {
#endif #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) static inline struct device *kobj_to_dev(struct kobject *kobj)
{ {
return container_of(kobj, struct device, kobj); return container_of(kobj, struct device, kobj);