aboutsummaryrefslogtreecommitdiff
path: root/drivers/base
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2008-04-21 10:51:07 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2008-07-21 21:54:40 -0700
commite105b8bfc769b0545b6f0f395179d1e43cbee822 (patch)
tree23a1f0839547829c4ca6e89cd40adf285eb9feb6 /drivers/base
parent93ded9b8fd42abe2c3607097963d8de6ad9117eb (diff)
sysfs: add /sys/dev/{char,block} to lookup sysfs path by major:minor
Why?: There are occasions where userspace would like to access sysfs attributes for a device but it may not know how sysfs has named the device or the path. For example what is the sysfs path for /dev/disk/by-id/ata-ST3160827AS_5MT004CK? With this change a call to stat(2) returns the major:minor then userspace can see that /sys/dev/block/8:32 links to /sys/block/sdc. What are the alternatives?: 1/ Add an ioctl to return the path: Doable, but sysfs is meant to reduce the need to proliferate ioctl interfaces into the kernel, so this seems counter productive. 2/ Use udev to create these symlinks: Also doable, but it adds a udev dependency to utilities that might be running in a limited environment like an initramfs. 3/ Do a full-tree search of sysfs. [kay.sievers@vrfy.org: fix duplicate registrations] [kay.sievers@vrfy.org: cleanup suggestions] Cc: Neil Brown <neilb@suse.de> Cc: Tejun Heo <htejun@gmail.com> Acked-by: Kay Sievers <kay.sievers@vrfy.org> Reviewed-by: SL Baur <steve@xemacs.org> Acked-by: Kay Sievers <kay.sievers@vrfy.org> Acked-by: Mark Lord <lkml@rtr.ca> Acked-by: H. Peter Anvin <hpa@zytor.com> Signed-off-by: Dan Williams <dan.j.williams@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/class.c4
-rw-r--r--drivers/base/core.c83
2 files changed, 86 insertions, 1 deletions
diff --git a/drivers/base/class.c b/drivers/base/class.c
index e085af0ff94..71ce3ff6bdf 100644
--- a/drivers/base/class.c
+++ b/drivers/base/class.c
@@ -148,6 +148,10 @@ int class_register(struct class *cls)
if (error)
return error;
+ /* set the default /sys/dev directory for devices of this class */
+ if (!cls->dev_kobj)
+ cls->dev_kobj = sysfs_dev_char_kobj;
+
#if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (cls != &block_class)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index ee0a51a3a41..be9aba4dc2f 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -27,6 +27,9 @@
int (*platform_notify)(struct device *dev) = NULL;
int (*platform_notify_remove)(struct device *dev) = NULL;
+static struct kobject *dev_kobj;
+struct kobject *sysfs_dev_char_kobj;
+struct kobject *sysfs_dev_block_kobj;
#ifdef CONFIG_BLOCK
static inline int device_is_not_partition(struct device *dev)
@@ -776,6 +779,54 @@ int dev_set_name(struct device *dev, const char *fmt, ...)
EXPORT_SYMBOL_GPL(dev_set_name);
/**
+ * device_to_dev_kobj - select a /sys/dev/ directory for the device
+ * @dev: device
+ *
+ * By default we select char/ for new entries. Setting class->dev_obj
+ * to NULL prevents an entry from being created. class->dev_kobj must
+ * be set (or cleared) before any devices are registered to the class
+ * otherwise device_create_sys_dev_entry() and
+ * device_remove_sys_dev_entry() will disagree about the the presence
+ * of the link.
+ */
+static struct kobject *device_to_dev_kobj(struct device *dev)
+{
+ struct kobject *kobj;
+
+ if (dev->class)
+ kobj = dev->class->dev_kobj;
+ else
+ kobj = sysfs_dev_char_kobj;
+
+ return kobj;
+}
+
+static int device_create_sys_dev_entry(struct device *dev)
+{
+ struct kobject *kobj = device_to_dev_kobj(dev);
+ int error = 0;
+ char devt_str[15];
+
+ if (kobj) {
+ format_dev_t(devt_str, dev->devt);
+ error = sysfs_create_link(kobj, &dev->kobj, devt_str);
+ }
+
+ return error;
+}
+
+static void device_remove_sys_dev_entry(struct device *dev)
+{
+ struct kobject *kobj = device_to_dev_kobj(dev);
+ char devt_str[15];
+
+ if (kobj) {
+ format_dev_t(devt_str, dev->devt);
+ sysfs_remove_link(kobj, devt_str);
+ }
+}
+
+/**
* device_add - add device to device hierarchy.
* @dev: device.
*
@@ -829,6 +880,10 @@ int device_add(struct device *dev)
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
+
+ error = device_create_sys_dev_entry(dev);
+ if (error)
+ goto devtattrError;
}
error = device_add_class_symlinks(dev);
@@ -873,6 +928,9 @@ int device_add(struct device *dev)
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
+ device_remove_sys_dev_entry(dev);
+ devtattrError:
+ if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
@@ -948,8 +1006,10 @@ void device_del(struct device *dev)
device_pm_remove(dev);
if (parent)
klist_del(&dev->knode_parent);
- if (MAJOR(dev->devt))
+ if (MAJOR(dev->devt)) {
+ device_remove_sys_dev_entry(dev);
device_remove_file(dev, &devt_attr);
+ }
if (dev->class) {
device_remove_class_symlinks(dev);
@@ -1074,7 +1134,25 @@ int __init devices_init(void)
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
+ dev_kobj = kobject_create_and_add("dev", NULL);
+ if (!dev_kobj)
+ goto dev_kobj_err;
+ sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
+ if (!sysfs_dev_block_kobj)
+ goto block_kobj_err;
+ sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
+ if (!sysfs_dev_char_kobj)
+ goto char_kobj_err;
+
return 0;
+
+ char_kobj_err:
+ kobject_put(sysfs_dev_block_kobj);
+ block_kobj_err:
+ kobject_put(dev_kobj);
+ dev_kobj_err:
+ kset_unregister(devices_kset);
+ return -ENOMEM;
}
EXPORT_SYMBOL_GPL(device_for_each_child);
@@ -1447,4 +1525,7 @@ void device_shutdown(void)
dev->driver->shutdown(dev);
}
}
+ kobject_put(sysfs_dev_char_kobj);
+ kobject_put(sysfs_dev_block_kobj);
+ kobject_put(dev_kobj);
}