Commit e105b8bfc769b0545b6f0f395179d1e43cbee822
Committed by
Greg Kroah-Hartman
1 parent
93ded9b8fd
Exists in
master
and in
7 other branches
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>
Showing 7 changed files with 124 additions and 2 deletions Side-by-side Diff
Documentation/ABI/testing/sysfs-dev
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> |
Documentation/filesystems/sysfs.txt
... | ... | @@ -248,6 +248,7 @@ |
248 | 248 | block/ |
249 | 249 | bus/ |
250 | 250 | class/ |
251 | +dev/ | |
251 | 252 | devices/ |
252 | 253 | firmware/ |
253 | 254 | net/ |
... | ... | @@ -274,6 +275,11 @@ |
274 | 275 | filesystem wanting to export attributes must create its own hierarchy |
275 | 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 | 284 | More information can driver-model specific features can be found in |
279 | 285 | Documentation/driver-model/. |
block/genhd.c
... | ... | @@ -370,7 +370,10 @@ |
370 | 370 | |
371 | 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 | 377 | if (unlikely(error)) |
375 | 378 | return error; |
376 | 379 | bdev_map = kobj_map_init(base_probe, &block_class_lock); |
drivers/base/class.c
... | ... | @@ -148,6 +148,10 @@ |
148 | 148 | if (error) |
149 | 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 | 155 | #if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK) |
152 | 156 | /* let the block class directory show up in the root of sysfs */ |
153 | 157 | if (cls != &block_class) |
drivers/base/core.c
... | ... | @@ -27,6 +27,9 @@ |
27 | 27 | |
28 | 28 | int (*platform_notify)(struct device *dev) = NULL; |
29 | 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 | 34 | #ifdef CONFIG_BLOCK |
32 | 35 | static inline int device_is_not_partition(struct device *dev) |
... | ... | @@ -776,6 +779,54 @@ |
776 | 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 | 830 | * device_add - add device to device hierarchy. |
780 | 831 | * @dev: device. |
781 | 832 | * |
... | ... | @@ -829,6 +880,10 @@ |
829 | 880 | error = device_create_file(dev, &devt_attr); |
830 | 881 | if (error) |
831 | 882 | goto ueventattrError; |
883 | + | |
884 | + error = device_create_sys_dev_entry(dev); | |
885 | + if (error) | |
886 | + goto devtattrError; | |
832 | 887 | } |
833 | 888 | |
834 | 889 | error = device_add_class_symlinks(dev); |
... | ... | @@ -873,6 +928,9 @@ |
873 | 928 | device_remove_class_symlinks(dev); |
874 | 929 | SymlinkError: |
875 | 930 | if (MAJOR(dev->devt)) |
931 | + device_remove_sys_dev_entry(dev); | |
932 | + devtattrError: | |
933 | + if (MAJOR(dev->devt)) | |
876 | 934 | device_remove_file(dev, &devt_attr); |
877 | 935 | ueventattrError: |
878 | 936 | device_remove_file(dev, &uevent_attr); |
879 | 937 | |
... | ... | @@ -948,8 +1006,10 @@ |
948 | 1006 | device_pm_remove(dev); |
949 | 1007 | if (parent) |
950 | 1008 | klist_del(&dev->knode_parent); |
951 | - if (MAJOR(dev->devt)) | |
1009 | + if (MAJOR(dev->devt)) { | |
1010 | + device_remove_sys_dev_entry(dev); | |
952 | 1011 | device_remove_file(dev, &devt_attr); |
1012 | + } | |
953 | 1013 | if (dev->class) { |
954 | 1014 | device_remove_class_symlinks(dev); |
955 | 1015 | |
956 | 1016 | |
... | ... | @@ -1074,7 +1134,25 @@ |
1074 | 1134 | devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); |
1075 | 1135 | if (!devices_kset) |
1076 | 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 | 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 | 1158 | EXPORT_SYMBOL_GPL(device_for_each_child); |
... | ... | @@ -1447,5 +1525,8 @@ |
1447 | 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 | } |
drivers/usb/core/devio.c
... | ... | @@ -1792,6 +1792,11 @@ |
1792 | 1792 | usb_classdev_class = NULL; |
1793 | 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 | 1801 | usb_register_notify(&usbdev_nb); |
1797 | 1802 | #endif |
include/linux/device.h
... | ... | @@ -193,6 +193,7 @@ |
193 | 193 | struct semaphore sem; /* locks children, devices, interfaces */ |
194 | 194 | struct class_attribute *class_attrs; |
195 | 195 | struct device_attribute *dev_attrs; |
196 | + struct kobject *dev_kobj; | |
196 | 197 | |
197 | 198 | int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); |
198 | 199 | |
... | ... | @@ -205,6 +206,8 @@ |
205 | 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 | 211 | extern int __must_check class_register(struct class *class); |
209 | 212 | extern void class_unregister(struct class *class); |
210 | 213 | extern int class_for_each_device(struct class *class, void *data, |