[PATCH v2 5/7] driver core: Add device location to "struct device" and expose it in sysfs

From: Rajat Jain
Date: Tue Jun 30 2020 - 00:50:12 EST


Add a new (optional) field to denote the physical location of a device
in the system, and expose it in sysfs. This was discussed here:
https://lore.kernel.org/linux-acpi/20200618184621.GA446639@xxxxxxxxx/

(The primary choice for attribute name i.e. "location" is already
exposed as an ABI elsewhere, so settled for "site"). Individual buses
that want to support this new attribute can opt-in by setting a flag in
bus_type, and then populating the location of device while enumerating
it.

Signed-off-by: Rajat Jain <rajatja@xxxxxxxxxx>
---
v2: (Initial version)

drivers/base/core.c | 35 +++++++++++++++++++++++++++++++
include/linux/device.h | 42 ++++++++++++++++++++++++++++++++++++++
include/linux/device/bus.h | 8 ++++++++
3 files changed, 85 insertions(+)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 67d39a90b45c7..14c815526b7fa 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -1778,6 +1778,32 @@ static ssize_t online_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RW(online);

+static ssize_t site_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ const char *site;
+
+ device_lock(dev);
+ switch (dev->site) {
+ case SITE_INTERNAL:
+ site = "INTERNAL";
+ break;
+ case SITE_EXTENDED:
+ site = "EXTENDED";
+ break;
+ case SITE_EXTERNAL:
+ site = "EXTERNAL";
+ break;
+ case SITE_UNKNOWN:
+ default:
+ site = "UNKNOWN";
+ break;
+ }
+ device_unlock(dev);
+ return sprintf(buf, "%s\n", site);
+}
+static DEVICE_ATTR_RO(site);
+
int device_add_groups(struct device *dev, const struct attribute_group **groups)
{
return sysfs_create_groups(&dev->kobj, groups);
@@ -1949,8 +1975,16 @@ static int device_add_attrs(struct device *dev)
goto err_remove_dev_groups;
}

+ if (bus_supports_site(dev->bus)) {
+ error = device_create_file(dev, &dev_attr_site);
+ if (error)
+ goto err_remove_dev_attr_online;
+ }
+
return 0;

+ err_remove_dev_attr_online:
+ device_remove_file(dev, &dev_attr_online);
err_remove_dev_groups:
device_remove_groups(dev, dev->groups);
err_remove_type_groups:
@@ -1968,6 +2002,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_site);
device_remove_file(dev, &dev_attr_online);
device_remove_groups(dev, dev->groups);

diff --git a/include/linux/device.h b/include/linux/device.h
index 15460a5ac024a..a4143735ae712 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -428,6 +428,31 @@ enum dl_dev_state {
DL_DEV_UNBINDING,
};

+/**
+ * enum device_site - Physical location of the device in the system.
+ * The semantics of values depend on subsystem / bus:
+ *
+ * @SITE_UNKNOWN: Location is Unknown (default)
+ *
+ * @SITE_INTERNAL: Device is internal to the system, and cannot be (easily)
+ * removed. E.g. SoC internal devices, onboard soldered
+ * devices, internal M.2 cards (that cannot be removed
+ * without opening the chassis).
+ * @SITE_EXTENDED: Device sits an extension of the system. E.g. devices
+ * on external PCIe trays, docking stations etc. These
+ * devices may be removable, but are generally housed
+ * internally on an extension board, so they are removed
+ * only when that whole extension board is removed.
+ * @SITE_EXTERNAL: Devices truly external to the system (i.e. plugged on
+ * an external port) that may be removed or added frequently.
+ */
+enum device_site {
+ SITE_UNKNOWN = 0,
+ SITE_INTERNAL,
+ SITE_EXTENDED,
+ SITE_EXTERNAL,
+};
+
/**
* struct dev_links_info - Device data related to device links.
* @suppliers: List of links to supplier devices.
@@ -513,6 +538,7 @@ struct dev_links_info {
* device (i.e. the bus driver that discovered the device).
* @iommu_group: IOMMU group the device belongs to.
* @iommu: Per device generic IOMMU runtime data
+ * @site: Physical location of the device w.r.t. the system
*
* @offline_disabled: If set, the device is permanently online.
* @offline: Set after successful invocation of bus type's .offline().
@@ -613,6 +639,8 @@ struct device {
struct iommu_group *iommu_group;
struct dev_iommu *iommu;

+ enum device_site site; /* Device physical location */
+
bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
@@ -806,6 +834,20 @@ static inline bool dev_has_sync_state(struct device *dev)
return false;
}

+static inline int dev_set_site(struct device *dev, enum device_site site)
+{
+ if (site < SITE_UNKNOWN || site > SITE_EXTERNAL)
+ return -EINVAL;
+
+ dev->site = site;
+ return 0;
+}
+
+static inline bool dev_is_external(struct device *dev)
+{
+ return dev->site == SITE_EXTERNAL;
+}
+
/*
* High level routines for use by the bus drivers
*/
diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h
index 1ea5e1d1545bd..e1079772e45af 100644
--- a/include/linux/device/bus.h
+++ b/include/linux/device/bus.h
@@ -69,6 +69,8 @@ struct fwnode_handle;
* @lock_key: Lock class key for use by the lock validator
* @need_parent_lock: When probing or removing a device on this bus, the
* device core should lock the device's parent.
+ * @supports_site: Bus can differentiate between internal/external devices
+ * and thus supports the device "site" attribute.
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
@@ -112,6 +114,7 @@ struct bus_type {
struct lock_class_key lock_key;

bool need_parent_lock;
+ bool supports_site;
};

extern int __must_check bus_register(struct bus_type *bus);
@@ -246,6 +249,11 @@ bus_find_device_by_acpi_dev(struct bus_type *bus, const void *adev)
}
#endif

+static inline bool bus_supports_site(struct bus_type *bus)
+{
+ return bus && bus->supports_site;
+}
+
struct device *subsys_find_device_by_id(struct bus_type *bus, unsigned int id,
struct device *hint);
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
--
2.27.0.212.ge8ba1cc988-goog