Commit e105b8bf authored by Dan Williams's avatar Dan Williams Committed by Greg Kroah-Hartman

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: default avatarKay Sievers <kay.sievers@vrfy.org>
Reviewed-by: default avatarSL Baur <steve@xemacs.org>
Acked-by: default avatarKay Sievers <kay.sievers@vrfy.org>
Acked-by: default avatarMark Lord <lkml@rtr.ca>
Acked-by: default avatarH. Peter Anvin <hpa@zytor.com>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 93ded9b8
What: /sys/dev
Date: April 2008
KernelVersion: 2.6.26
Contact: Dan Williams <dan.j.williams@intel.com>
Description: The /sys/dev tree provides a method to look up the sysfs
path for a device using the information returned from
stat(2). There are two directories, 'block' and 'char',
beneath /sys/dev containing symbolic links with names of
the form "<major>:<minor>". These links point to the
corresponding sysfs path for the given device.
Example:
$ readlink /sys/dev/block/8:32
../../block/sdc
Entries in /sys/dev/char and /sys/dev/block will be
dynamically created and destroyed as devices enter and
leave the system.
Users: mdadm <linux-raid@vger.kernel.org>
...@@ -248,6 +248,7 @@ The top level sysfs directory looks like: ...@@ -248,6 +248,7 @@ The top level sysfs directory looks like:
block/ block/
bus/ bus/
class/ class/
dev/
devices/ devices/
firmware/ firmware/
net/ net/
...@@ -274,6 +275,11 @@ fs/ contains a directory for some filesystems. Currently each ...@@ -274,6 +275,11 @@ fs/ contains a directory for some filesystems. Currently each
filesystem wanting to export attributes must create its own hierarchy filesystem wanting to export attributes must create its own hierarchy
below fs/ (see ./fuse.txt for an example). below fs/ (see ./fuse.txt for an example).
dev/ contains two directories char/ and block/. Inside these two
directories there are symlinks named <major>:<minor>. These symlinks
point to the sysfs directory for the given device. /sys/dev provides a
quick way to lookup the sysfs interface for a device from the result of
a stat(2) operation.
More information can driver-model specific features can be found in More information can driver-model specific features can be found in
Documentation/driver-model/. Documentation/driver-model/.
......
...@@ -370,7 +370,10 @@ static struct kobject *base_probe(dev_t devt, int *part, void *data) ...@@ -370,7 +370,10 @@ static struct kobject *base_probe(dev_t devt, int *part, void *data)
static int __init genhd_device_init(void) static int __init genhd_device_init(void)
{ {
int error = class_register(&block_class); int error;
block_class.dev_kobj = sysfs_dev_block_kobj;
error = class_register(&block_class);
if (unlikely(error)) if (unlikely(error))
return error; return error;
bdev_map = kobj_map_init(base_probe, &block_class_lock); bdev_map = kobj_map_init(base_probe, &block_class_lock);
......
...@@ -148,6 +148,10 @@ int class_register(struct class *cls) ...@@ -148,6 +148,10 @@ int class_register(struct class *cls)
if (error) if (error)
return 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) #if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */ /* let the block class directory show up in the root of sysfs */
if (cls != &block_class) if (cls != &block_class)
......
...@@ -27,6 +27,9 @@ ...@@ -27,6 +27,9 @@
int (*platform_notify)(struct device *dev) = NULL; int (*platform_notify)(struct device *dev) = NULL;
int (*platform_notify_remove)(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 #ifdef CONFIG_BLOCK
static inline int device_is_not_partition(struct device *dev) static inline int device_is_not_partition(struct device *dev)
...@@ -775,6 +778,54 @@ int dev_set_name(struct device *dev, const char *fmt, ...) ...@@ -775,6 +778,54 @@ int dev_set_name(struct device *dev, const char *fmt, ...)
} }
EXPORT_SYMBOL_GPL(dev_set_name); 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. * device_add - add device to device hierarchy.
* @dev: device. * @dev: device.
...@@ -829,6 +880,10 @@ int device_add(struct device *dev) ...@@ -829,6 +880,10 @@ int device_add(struct device *dev)
error = device_create_file(dev, &devt_attr); error = device_create_file(dev, &devt_attr);
if (error) if (error)
goto ueventattrError; goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
} }
error = device_add_class_symlinks(dev); error = device_add_class_symlinks(dev);
...@@ -872,6 +927,9 @@ int device_add(struct device *dev) ...@@ -872,6 +927,9 @@ int device_add(struct device *dev)
AttrsError: AttrsError:
device_remove_class_symlinks(dev); device_remove_class_symlinks(dev);
SymlinkError: SymlinkError:
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt)) if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr); device_remove_file(dev, &devt_attr);
ueventattrError: ueventattrError:
...@@ -948,8 +1006,10 @@ void device_del(struct device *dev) ...@@ -948,8 +1006,10 @@ void device_del(struct device *dev)
device_pm_remove(dev); device_pm_remove(dev);
if (parent) if (parent)
klist_del(&dev->knode_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); device_remove_file(dev, &devt_attr);
}
if (dev->class) { if (dev->class) {
device_remove_class_symlinks(dev); device_remove_class_symlinks(dev);
...@@ -1074,7 +1134,25 @@ int __init devices_init(void) ...@@ -1074,7 +1134,25 @@ int __init devices_init(void)
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset) if (!devices_kset)
return -ENOMEM; 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; 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); EXPORT_SYMBOL_GPL(device_for_each_child);
...@@ -1447,4 +1525,7 @@ void device_shutdown(void) ...@@ -1447,4 +1525,7 @@ void device_shutdown(void)
dev->driver->shutdown(dev); dev->driver->shutdown(dev);
} }
} }
kobject_put(sysfs_dev_char_kobj);
kobject_put(sysfs_dev_block_kobj);
kobject_put(dev_kobj);
} }
...@@ -1792,6 +1792,11 @@ int __init usb_devio_init(void) ...@@ -1792,6 +1792,11 @@ int __init usb_devio_init(void)
usb_classdev_class = NULL; usb_classdev_class = NULL;
goto out; goto out;
} }
/* devices of this class shadow the major:minor of their parent
* device, so clear ->dev_kobj to prevent adding duplicate entries
* to /sys/dev
*/
usb_classdev_class->dev_kobj = NULL;
usb_register_notify(&usbdev_nb); usb_register_notify(&usbdev_nb);
#endif #endif
......
...@@ -193,6 +193,7 @@ struct class { ...@@ -193,6 +193,7 @@ struct class {
struct semaphore sem; /* locks children, devices, interfaces */ struct semaphore sem; /* locks children, devices, interfaces */
struct class_attribute *class_attrs; struct class_attribute *class_attrs;
struct device_attribute *dev_attrs; struct device_attribute *dev_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
...@@ -205,6 +206,8 @@ struct class { ...@@ -205,6 +206,8 @@ struct class {
struct pm_ops *pm; struct pm_ops *pm;
}; };
extern struct kobject *sysfs_dev_block_kobj;
extern struct kobject *sysfs_dev_char_kobj;
extern int __must_check class_register(struct class *class); extern int __must_check class_register(struct class *class);
extern void class_unregister(struct class *class); extern void class_unregister(struct class *class);
extern int class_for_each_device(struct class *class, void *data, extern int class_for_each_device(struct class *class, void *data,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment