diff options
author | Dan Williams <dan.j.williams@intel.com> | 2008-04-21 13:51:07 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-07-22 00:54:40 -0400 |
commit | e105b8bfc769b0545b6f0f395179d1e43cbee822 (patch) | |
tree | 23a1f0839547829c4ca6e89cd40adf285eb9feb6 | |
parent | 93ded9b8fd42abe2c3607097963d8de6ad9117eb (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>
-rw-r--r-- | Documentation/ABI/testing/sysfs-dev | 20 | ||||
-rw-r--r-- | Documentation/filesystems/sysfs.txt | 6 | ||||
-rw-r--r-- | block/genhd.c | 5 | ||||
-rw-r--r-- | drivers/base/class.c | 4 | ||||
-rw-r--r-- | drivers/base/core.c | 83 | ||||
-rw-r--r-- | drivers/usb/core/devio.c | 5 | ||||
-rw-r--r-- | include/linux/device.h | 3 |
7 files changed, 124 insertions, 2 deletions
diff --git a/Documentation/ABI/testing/sysfs-dev b/Documentation/ABI/testing/sysfs-dev new file mode 100644 index 000000000000..a9f2b8b0530f --- /dev/null +++ b/Documentation/ABI/testing/sysfs-dev | |||
@@ -0,0 +1,20 @@ | |||
1 | What: /sys/dev | ||
2 | Date: April 2008 | ||
3 | KernelVersion: 2.6.26 | ||
4 | Contact: Dan Williams <dan.j.williams@intel.com> | ||
5 | Description: The /sys/dev tree provides a method to look up the sysfs | ||
6 | path for a device using the information returned from | ||
7 | stat(2). There are two directories, 'block' and 'char', | ||
8 | beneath /sys/dev containing symbolic links with names of | ||
9 | the form "<major>:<minor>". These links point to the | ||
10 | corresponding sysfs path for the given device. | ||
11 | |||
12 | Example: | ||
13 | $ readlink /sys/dev/block/8:32 | ||
14 | ../../block/sdc | ||
15 | |||
16 | Entries in /sys/dev/char and /sys/dev/block will be | ||
17 | dynamically created and destroyed as devices enter and | ||
18 | leave the system. | ||
19 | |||
20 | Users: mdadm <linux-raid@vger.kernel.org> | ||
diff --git a/Documentation/filesystems/sysfs.txt b/Documentation/filesystems/sysfs.txt index 7f27b8f840d0..9e9c348275a9 100644 --- a/Documentation/filesystems/sysfs.txt +++ b/Documentation/filesystems/sysfs.txt | |||
@@ -248,6 +248,7 @@ The top level sysfs directory looks like: | |||
248 | block/ | 248 | block/ |
249 | bus/ | 249 | bus/ |
250 | class/ | 250 | class/ |
251 | dev/ | ||
251 | devices/ | 252 | devices/ |
252 | firmware/ | 253 | firmware/ |
253 | net/ | 254 | net/ |
@@ -274,6 +275,11 @@ fs/ contains a directory for some filesystems. Currently each | |||
274 | filesystem wanting to export attributes must create its own hierarchy | 275 | filesystem wanting to export attributes must create its own hierarchy |
275 | below fs/ (see ./fuse.txt for an example). | 276 | below fs/ (see ./fuse.txt for an example). |
276 | 277 | ||
278 | dev/ contains two directories char/ and block/. Inside these two | ||
279 | directories there are symlinks named <major>:<minor>. These symlinks | ||
280 | point to the sysfs directory for the given device. /sys/dev provides a | ||
281 | quick way to lookup the sysfs interface for a device from the result of | ||
282 | a stat(2) operation. | ||
277 | 283 | ||
278 | More information can driver-model specific features can be found in | 284 | More information can driver-model specific features can be found in |
279 | Documentation/driver-model/. | 285 | Documentation/driver-model/. |
diff --git a/block/genhd.c b/block/genhd.c index 9074f384b097..24e3fc9095fe 100644 --- a/block/genhd.c +++ b/block/genhd.c | |||
@@ -370,7 +370,10 @@ static struct kobject *base_probe(dev_t devt, int *part, void *data) | |||
370 | 370 | ||
371 | static int __init genhd_device_init(void) | 371 | static int __init genhd_device_init(void) |
372 | { | 372 | { |
373 | int error = class_register(&block_class); | 373 | int error; |
374 | |||
375 | block_class.dev_kobj = sysfs_dev_block_kobj; | ||
376 | error = class_register(&block_class); | ||
374 | if (unlikely(error)) | 377 | if (unlikely(error)) |
375 | return error; | 378 | return error; |
376 | bdev_map = kobj_map_init(base_probe, &block_class_lock); | 379 | bdev_map = kobj_map_init(base_probe, &block_class_lock); |
diff --git a/drivers/base/class.c b/drivers/base/class.c index e085af0ff94f..71ce3ff6bdf5 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c | |||
@@ -148,6 +148,10 @@ int class_register(struct class *cls) | |||
148 | if (error) | 148 | if (error) |
149 | return error; | 149 | return error; |
150 | 150 | ||
151 | /* set the default /sys/dev directory for devices of this class */ | ||
152 | if (!cls->dev_kobj) | ||
153 | cls->dev_kobj = sysfs_dev_char_kobj; | ||
154 | |||
151 | #if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK) | 155 | #if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK) |
152 | /* let the block class directory show up in the root of sysfs */ | 156 | /* let the block class directory show up in the root of sysfs */ |
153 | if (cls != &block_class) | 157 | if (cls != &block_class) |
diff --git a/drivers/base/core.c b/drivers/base/core.c index ee0a51a3a41d..be9aba4dc2fb 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c | |||
@@ -27,6 +27,9 @@ | |||
27 | 27 | ||
28 | int (*platform_notify)(struct device *dev) = NULL; | 28 | int (*platform_notify)(struct device *dev) = NULL; |
29 | int (*platform_notify_remove)(struct device *dev) = NULL; | 29 | int (*platform_notify_remove)(struct device *dev) = NULL; |
30 | static struct kobject *dev_kobj; | ||
31 | struct kobject *sysfs_dev_char_kobj; | ||
32 | struct kobject *sysfs_dev_block_kobj; | ||
30 | 33 | ||
31 | #ifdef CONFIG_BLOCK | 34 | #ifdef CONFIG_BLOCK |
32 | static inline int device_is_not_partition(struct device *dev) | 35 | static inline int device_is_not_partition(struct device *dev) |
@@ -776,6 +779,54 @@ int dev_set_name(struct device *dev, const char *fmt, ...) | |||
776 | EXPORT_SYMBOL_GPL(dev_set_name); | 779 | EXPORT_SYMBOL_GPL(dev_set_name); |
777 | 780 | ||
778 | /** | 781 | /** |
782 | * device_to_dev_kobj - select a /sys/dev/ directory for the device | ||
783 | * @dev: device | ||
784 | * | ||
785 | * By default we select char/ for new entries. Setting class->dev_obj | ||
786 | * to NULL prevents an entry from being created. class->dev_kobj must | ||
787 | * be set (or cleared) before any devices are registered to the class | ||
788 | * otherwise device_create_sys_dev_entry() and | ||
789 | * device_remove_sys_dev_entry() will disagree about the the presence | ||
790 | * of the link. | ||
791 | */ | ||
792 | static struct kobject *device_to_dev_kobj(struct device *dev) | ||
793 | { | ||
794 | struct kobject *kobj; | ||
795 | |||
796 | if (dev->class) | ||
797 | kobj = dev->class->dev_kobj; | ||
798 | else | ||
799 | kobj = sysfs_dev_char_kobj; | ||
800 | |||
801 | return kobj; | ||
802 | } | ||
803 | |||
804 | static int device_create_sys_dev_entry(struct device *dev) | ||
805 | { | ||
806 | struct kobject *kobj = device_to_dev_kobj(dev); | ||
807 | int error = 0; | ||
808 | char devt_str[15]; | ||
809 | |||
810 | if (kobj) { | ||
811 | format_dev_t(devt_str, dev->devt); | ||
812 | error = sysfs_create_link(kobj, &dev->kobj, devt_str); | ||
813 | } | ||
814 | |||
815 | return error; | ||
816 | } | ||
817 | |||
818 | static void device_remove_sys_dev_entry(struct device *dev) | ||
819 | { | ||
820 | struct kobject *kobj = device_to_dev_kobj(dev); | ||
821 | char devt_str[15]; | ||
822 | |||
823 | if (kobj) { | ||
824 | format_dev_t(devt_str, dev->devt); | ||
825 | sysfs_remove_link(kobj, devt_str); | ||
826 | } | ||
827 | } | ||
828 | |||
829 | /** | ||
779 | * device_add - add device to device hierarchy. | 830 | * device_add - add device to device hierarchy. |
780 | * @dev: device. | 831 | * @dev: device. |
781 | * | 832 | * |
@@ -829,6 +880,10 @@ int device_add(struct device *dev) | |||
829 | error = device_create_file(dev, &devt_attr); | 880 | error = device_create_file(dev, &devt_attr); |
830 | if (error) | 881 | if (error) |
831 | goto ueventattrError; | 882 | goto ueventattrError; |
883 | |||
884 | error = device_create_sys_dev_entry(dev); | ||
885 | if (error) | ||
886 | goto devtattrError; | ||
832 | } | 887 | } |
833 | 888 | ||
834 | error = device_add_class_symlinks(dev); | 889 | error = device_add_class_symlinks(dev); |
@@ -873,6 +928,9 @@ int device_add(struct device *dev) | |||
873 | device_remove_class_symlinks(dev); | 928 | device_remove_class_symlinks(dev); |
874 | SymlinkError: | 929 | SymlinkError: |
875 | if (MAJOR(dev->devt)) | 930 | if (MAJOR(dev->devt)) |
931 | device_remove_sys_dev_entry(dev); | ||
932 | devtattrError: | ||
933 | if (MAJOR(dev->devt)) | ||
876 | device_remove_file(dev, &devt_attr); | 934 | device_remove_file(dev, &devt_attr); |
877 | ueventattrError: | 935 | ueventattrError: |
878 | device_remove_file(dev, &uevent_attr); | 936 | device_remove_file(dev, &uevent_attr); |
@@ -948,8 +1006,10 @@ void device_del(struct device *dev) | |||
948 | device_pm_remove(dev); | 1006 | device_pm_remove(dev); |
949 | if (parent) | 1007 | if (parent) |
950 | klist_del(&dev->knode_parent); | 1008 | klist_del(&dev->knode_parent); |
951 | if (MAJOR(dev->devt)) | 1009 | if (MAJOR(dev->devt)) { |
1010 | device_remove_sys_dev_entry(dev); | ||
952 | device_remove_file(dev, &devt_attr); | 1011 | device_remove_file(dev, &devt_attr); |
1012 | } | ||
953 | if (dev->class) { | 1013 | if (dev->class) { |
954 | device_remove_class_symlinks(dev); | 1014 | device_remove_class_symlinks(dev); |
955 | 1015 | ||
@@ -1074,7 +1134,25 @@ int __init devices_init(void) | |||
1074 | devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); | 1134 | devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); |
1075 | if (!devices_kset) | 1135 | if (!devices_kset) |
1076 | return -ENOMEM; | 1136 | return -ENOMEM; |
1137 | dev_kobj = kobject_create_and_add("dev", NULL); | ||
1138 | if (!dev_kobj) | ||
1139 | goto dev_kobj_err; | ||
1140 | sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj); | ||
1141 | if (!sysfs_dev_block_kobj) | ||
1142 | goto block_kobj_err; | ||
1143 | sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); | ||
1144 | if (!sysfs_dev_char_kobj) | ||
1145 | goto char_kobj_err; | ||
1146 | |||
1077 | return 0; | 1147 | return 0; |
1148 | |||
1149 | char_kobj_err: | ||
1150 | kobject_put(sysfs_dev_block_kobj); | ||
1151 | block_kobj_err: | ||
1152 | kobject_put(dev_kobj); | ||
1153 | dev_kobj_err: | ||
1154 | kset_unregister(devices_kset); | ||
1155 | return -ENOMEM; | ||
1078 | } | 1156 | } |
1079 | 1157 | ||
1080 | EXPORT_SYMBOL_GPL(device_for_each_child); | 1158 | EXPORT_SYMBOL_GPL(device_for_each_child); |
@@ -1447,4 +1525,7 @@ void device_shutdown(void) | |||
1447 | dev->driver->shutdown(dev); | 1525 | dev->driver->shutdown(dev); |
1448 | } | 1526 | } |
1449 | } | 1527 | } |
1528 | kobject_put(sysfs_dev_char_kobj); | ||
1529 | kobject_put(sysfs_dev_block_kobj); | ||
1530 | kobject_put(dev_kobj); | ||
1450 | } | 1531 | } |
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 54a350ccd033..6fbc8f5ab80c 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c | |||
@@ -1792,6 +1792,11 @@ int __init usb_devio_init(void) | |||
1792 | usb_classdev_class = NULL; | 1792 | usb_classdev_class = NULL; |
1793 | goto out; | 1793 | goto out; |
1794 | } | 1794 | } |
1795 | /* devices of this class shadow the major:minor of their parent | ||
1796 | * device, so clear ->dev_kobj to prevent adding duplicate entries | ||
1797 | * to /sys/dev | ||
1798 | */ | ||
1799 | usb_classdev_class->dev_kobj = NULL; | ||
1795 | 1800 | ||
1796 | usb_register_notify(&usbdev_nb); | 1801 | usb_register_notify(&usbdev_nb); |
1797 | #endif | 1802 | #endif |
diff --git a/include/linux/device.h b/include/linux/device.h index f71a78d123ae..e49aa74f248c 100644 --- a/include/linux/device.h +++ b/include/linux/device.h | |||
@@ -193,6 +193,7 @@ struct class { | |||
193 | struct semaphore sem; /* locks children, devices, interfaces */ | 193 | struct semaphore sem; /* locks children, devices, interfaces */ |
194 | struct class_attribute *class_attrs; | 194 | struct class_attribute *class_attrs; |
195 | struct device_attribute *dev_attrs; | 195 | struct device_attribute *dev_attrs; |
196 | struct kobject *dev_kobj; | ||
196 | 197 | ||
197 | int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); | 198 | int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); |
198 | 199 | ||
@@ -205,6 +206,8 @@ struct class { | |||
205 | struct pm_ops *pm; | 206 | struct pm_ops *pm; |
206 | }; | 207 | }; |
207 | 208 | ||
209 | extern struct kobject *sysfs_dev_block_kobj; | ||
210 | extern struct kobject *sysfs_dev_char_kobj; | ||
208 | extern int __must_check class_register(struct class *class); | 211 | extern int __must_check class_register(struct class *class); |
209 | extern void class_unregister(struct class *class); | 212 | extern void class_unregister(struct class *class); |
210 | extern int class_for_each_device(struct class *class, void *data, | 213 | extern int class_for_each_device(struct class *class, void *data, |