bk://kernel.bkbits.net/gregkh/linux/driver-2.6 greg@kroah.com|ChangeSet|20041028215459|41500 greg # This is a BitKeeper generated diff -Nru style patch. # # ChangeSet # 2004/10/28 17:39:42-05:00 akpm@osdl.org # [PATCH] Fix deadlocks on dpm_sem # # From: Paul Mackerras # # Currently the device_pm_foo() functions are rather prone to deadlocks # during suspend/resume. This is because the dpm_sem is held for the # duration of device_suspend() and device_resume() as well as device_pm_add() # and device_pm_remove(). If for any reason you get a device addition or # removal triggered by a device's suspend or resume code, you get a deadlock. # (The classic example is a USB host adaptor resuming and discovering that # the mouse you used to have plugged in has gone away.) # # This patch fixes the problem by using a separate semaphore, called # dpm_list_sem, to cover the places where we need the device pm lists to be # stable, and by being careful about how we traverse the lists on suspend and # resume. I have analysed the various cases that can occur and I am # confident that I have handled them all correctly. I posted this patch # together with a detailed analysis 10 days ago. # # Signed-off-by Paul Mackerras # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # drivers/base/power/suspend.c # 2004/10/21 23:24:28-05:00 akpm@osdl.org +25 -19 # Fix deadlocks on dpm_sem # # drivers/base/power/resume.c # 2004/10/21 23:24:28-05:00 akpm@osdl.org +13 -3 # Fix deadlocks on dpm_sem # # drivers/base/power/power.h # 2004/10/21 23:24:28-05:00 akpm@osdl.org +5 -0 # Fix deadlocks on dpm_sem # # drivers/base/power/main.c # 2004/10/21 23:24:28-05:00 akpm@osdl.org +6 -5 # Fix deadlocks on dpm_sem # # ChangeSet # 2004/10/28 17:38:59-05:00 akpm@osdl.org # [PATCH] Possible race in sysfs_read_file() and sysfs_write_file() # # From: Simon Derr # # Add a `needs_read_fill' field in sysfs_buffer so that reading after a write in # a sysfs file returns valid data. # # (instead of the data that have been written, that may be invalid or at the # wrong offset) # # Signed-off-by: Simon Derr # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/file.c # 2004/10/18 23:43:24-05:00 akpm@osdl.org +5 -1 # Possible race in sysfs_read_file() and sysfs_write_file() # # ChangeSet # 2004/10/28 17:38:01-05:00 akpm@osdl.org # [PATCH] Fix race in sysfs_read_file() and sysfs_write_file() # # From: Simon Derr # # - fixes the race between threads by adding a semaphore in sysfs_buffer # # - allocates the buffer upon call to pread(). We still call again # fill_read_buffer() if the file is "rewinded" to offset zero. # # - fixes the comparison in flush_read_buffer(). # # Signed-off-by: Simon Derr # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/file.c # 2004/10/18 23:47:25-05:00 akpm@osdl.org +15 -3 # Fix race in sysfs_read_file() and sysfs_write_file() # # ChangeSet # 2004/10/28 17:32:46-05:00 akpm@osdl.org # [PATCH] sysfs backing store: stop pinning dentries/inodes for leaf entries # # From: Maneesh Soni # # o This patch stops the pinning of non-directory or leaf dentries and inodes. # The leaf dentries and inodes are created during lookup based on the # entries on sysfs_dirent tree. These leaves are removed from the dcache # through the VFS dentry ageing process during shrink dcache operations. Thus # reducing about 80% of sysfs lowmem needs. # # o This implments the ->lookup() for sysfs directory inodes and allocates # dentry and inode if the lookup is successful and avoids the need of # allocating and pinning of dentry and inodes during the creation of # corresponding sysfs leaf entry. As of now the implementation has not # required negative dentry creation on failed lookup. As sysfs is still a # RAM based filesystem, negative dentries are not of any use IMO. # # o The leaf dentry allocated after successful lookup is connected to the # existing corresponding sysfs_dirent through the d_fsdata field. # # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/sysfs.h # 2004/10/18 23:42:34-05:00 akpm@osdl.org +4 -0 # sysfs backing store: stop pinning dentries/inodes for leaf entries # # fs/sysfs/symlink.c # 2004/10/18 23:42:34-05:00 akpm@osdl.org +5 -21 # sysfs backing store: stop pinning dentries/inodes for leaf entries # # fs/sysfs/mount.c # 2004/10/18 23:42:34-05:00 akpm@osdl.org +1 -1 # sysfs backing store: stop pinning dentries/inodes for leaf entries # # fs/sysfs/file.c # 2004/10/18 23:47:25-05:00 akpm@osdl.org +3 -22 # sysfs backing store: stop pinning dentries/inodes for leaf entries # # fs/sysfs/dir.c # 2004/10/18 23:47:25-05:00 akpm@osdl.org +100 -6 # sysfs backing store: stop pinning dentries/inodes for leaf entries # # fs/sysfs/bin.c # 2004/10/18 23:42:34-05:00 akpm@osdl.org +2 -26 # sysfs backing store: stop pinning dentries/inodes for leaf entries # # ChangeSet # 2004/10/28 17:31:57-05:00 akpm@osdl.org # [PATCH] sysfs backing store: use sysfs_dirent based tree in dir file operations # # From: Maneesh Soni # # o This patch implements the sysfs_dir_operations file_operations strucutre for # sysfs directories. It uses the sysfs_dirent based tree for ->readdir() and # ->lseek() methods instead of simple_dir_operations which use dentry based # tree. # # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/sysfs.h # 2004/10/18 23:47:26-05:00 akpm@osdl.org +2 -0 # sysfs backing store: use sysfs_dirent based tree in dir file operations # # fs/sysfs/mount.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +1 -1 # sysfs backing store: use sysfs_dirent based tree in dir file operations # # fs/sysfs/dir.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +140 -1 # sysfs backing store: use sysfs_dirent based tree in dir file operations # # ChangeSet # 2004/10/28 17:31:11-05:00 akpm@osdl.org # [PATCH] sysfs backing store: use sysfs_dirent based tree in file removal # # From: Maneesh Soni # # o This patch uses the sysfs_dirent based tree while removing sysfs files # and directories. This avoids holding dcache_lock by not using dentry # based vfs tree. Thus simplyfying the removal logic in sysfs. # # o It uses two helper routines sysfs_get_name(), to get the name for # sysfs element and sysfs_drop_dentry() to delete the dentry given a # sysfs_dirent. # # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/sysfs.h # 2004/10/18 23:47:26-05:00 akpm@osdl.org +18 -0 # sysfs backing store: use sysfs_dirent based tree in file removal # # fs/sysfs/inode.c # 2004/10/18 23:42:33-05:00 akpm@osdl.org +66 -26 # sysfs backing store: use sysfs_dirent based tree in file removal # # fs/sysfs/dir.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +11 -37 # sysfs backing store: use sysfs_dirent based tree in file removal # # ChangeSet # 2004/10/28 17:30:07-05:00 akpm@osdl.org # [PATCH] sysfs backing store - add sysfs_direct structure # # From: Maneesh Soni # # o This patch introduces the new sysfs_dirent data structure. The sysfs_dirent # is added to the dentry corresponding to each of the element which can be # represented in sysfs like, kobject (directory), text or binary attributes # (files), attribute groups (directory) and symlinks. # # o It uses dentry's d_fsdata field to attach the corresponding sysfs_dirent. # # o The sysfs_dirents are maintained in a tree of all the sysfs entries using the # s_children and s_sibling list pointers. # # o This patch also changes how we access attributes and kobjects in # file_operations from a given dentry, basically introducing one more level of # indirection. # # o The sysfs_dirents are freed and the sysfs_dirent tree is updated accordingly # upon the deletion of corresponding dentry. The sysfs dirents are kept alive # as long as there is corresponding dentry around. The are freed when the # dentry is finally out of dcache using the ->d_iput() method. # # o This also fixes the dentry leaks in case of error paths after sysfs has # got a newly alocated (and hashed) dentry from sysfs_get_dentry() by # d_drop()'ing the dentry. # # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # include/linux/sysfs.h # 2004/10/18 23:42:33-05:00 akpm@osdl.org +19 -0 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/sysfs.h # 2004/10/18 23:47:26-05:00 akpm@osdl.org +33 -6 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/symlink.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +35 -4 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/mount.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +8 -0 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/inode.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +8 -2 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/group.c # 2004/10/18 23:42:33-05:00 akpm@osdl.org +3 -1 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/file.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +15 -10 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/dir.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +75 -9 # sysfs backing store - add sysfs_direct structure # # fs/sysfs/bin.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +8 -6 # sysfs backing store - add sysfs_direct structure # # ChangeSet # 2004/10/28 17:29:22-05:00 akpm@osdl.org # [PATCH] fix oops with firmware loading # # From: Maneesh Soni # # My fault, a bad typo in fs/sysfs/bin.c. # # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/bin.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +2 -2 # fix oops with firmware loading # # ChangeSet # 2004/10/28 17:28:22-05:00 akpm@osdl.org # [PATCH] sysfs backing store - prepare sysfs_file_operations helpers # # From: Maneesh Soni # # o The following patch provides dumb helpers to access the corresponding # kobject, attribute or binary attribute given a dentry and prepare the # sysfs_file_operation methods for using sysfs_dirents. # # Signed-off-by: Andrew Morton # Signed-off-by: Greg Kroah-Hartman # # fs/sysfs/sysfs.h # 2004/10/18 23:47:26-05:00 akpm@osdl.org +17 -1 # sysfs backing store - prepare sysfs_file_operations helpers # # fs/sysfs/file.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +12 -12 # sysfs backing store - prepare sysfs_file_operations helpers # # fs/sysfs/bin.c # 2004/10/18 23:47:26-05:00 akpm@osdl.org +7 -7 # sysfs backing store - prepare sysfs_file_operations helpers # # ChangeSet # 2004/10/25 19:17:25-07:00 akpm@bix.(none) # Merge bix.(none):/usr/src/bk25 into bix.(none):/usr/src/bk-driver-core # # include/linux/module.h # 2004/10/25 19:17:19-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/10/24 22:03:33-07:00 akpm@bix.(none) # Merge bix.(none):/usr/src/bk25 into bix.(none):/usr/src/bk-driver-core # # lib/Makefile # 2004/10/24 22:03:29-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/10/22 21:07:02-07:00 akpm@bix.(none) # Merge bix.(none):/usr/src/bk25 into bix.(none):/usr/src/bk-driver-core # # lib/Makefile # 2004/10/22 21:06:58-07:00 akpm@bix.(none) +0 -0 # Auto merged # # drivers/usb/core/usb.c # 2004/10/22 21:06:58-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/10/22 17:01:27-07:00 greg@kroah.com # cpu: mark symbol EXPORT_SYMBOL_GPL # # Signed-off-by: Greg Kroah-Hartman # # drivers/base/cpu.c # 2004/10/22 17:01:16-07:00 greg@kroah.com +1 -1 # cpu: mark symbol EXPORT_SYMBOL_GPL # # Signed-off-by: Greg Kroah-Hartman # # ChangeSet # 2004/10/22 16:50:27-07:00 greg@kroah.com # class simple: mark exports as EXPORT_SYMBOL_GPL as these are core kernel functions. # # Signed-off-by: Greg Kroah-Hartman # # drivers/base/class_simple.c # 2004/10/22 16:50:14-07:00 greg@kroah.com +5 -5 # class simple: mark exports as EXPORT_SYMBOL_GPL as these are core kernel functions. # # Signed-off-by: Greg Kroah-Hartman # # ChangeSet # 2004/10/21 14:19:34-07:00 akpm@bix.(none) # Merge bix.(none):/usr/src/bk25 into bix.(none):/usr/src/bk-driver-core # # lib/Makefile # 2004/10/21 14:19:30-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/10/21 00:25:11-07:00 akpm@bix.(none) # Merge bix.(none):/usr/src/bk25 into bix.(none):/usr/src/bk-driver-core # # include/linux/module.h # 2004/10/21 00:25:06-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/10/19 21:39:11-07:00 akpm@bix.(none) # Merge bix.(none):/usr/src/bk25 into bix.(none):/usr/src/bk-driver-core # # kernel/Makefile # 2004/10/19 21:39:07-07:00 akpm@bix.(none) +0 -0 # Auto merged # # include/linux/module.h # 2004/10/19 21:39:07-07:00 akpm@bix.(none) +0 -0 # Auto merged # # drivers/usb/core/usb.c # 2004/10/19 21:39:07-07:00 akpm@bix.(none) +0 -0 # Auto merged # # ChangeSet # 2004/10/19 17:57:14-07:00 akpm@bix.(none) # foo # # kernel/Makefile # 2004/10/19 17:57:07-07:00 akpm@bix.(none) +0 -1 # foo # # include/linux/module.h # 2004/10/19 17:54:19-07:00 akpm@bix.(none) +0 -0 # Auto merged # # drivers/usb/core/usb.c # 2004/10/19 17:54:19-07:00 akpm@bix.(none) +0 -0 # Auto merged # diff -Nru a/drivers/base/class_simple.c b/drivers/base/class_simple.c --- a/drivers/base/class_simple.c 2004-10-28 22:35:28 -07:00 +++ b/drivers/base/class_simple.c 2004-10-28 22:35:28 -07:00 @@ -91,7 +91,7 @@ kfree(cs); return ERR_PTR(retval); } -EXPORT_SYMBOL(class_simple_create); +EXPORT_SYMBOL_GPL(class_simple_create); /** * class_simple_destroy - destroys a struct class_simple structure @@ -107,7 +107,7 @@ class_unregister(&cs->class); } -EXPORT_SYMBOL(class_simple_destroy); +EXPORT_SYMBOL_GPL(class_simple_destroy); /** * class_simple_device_add - adds a class device to sysfs for a character driver @@ -166,7 +166,7 @@ kfree(s_dev); return ERR_PTR(retval); } -EXPORT_SYMBOL(class_simple_device_add); +EXPORT_SYMBOL_GPL(class_simple_device_add); /** * class_simple_set_hotplug - set the hotplug callback in the embedded struct class @@ -184,7 +184,7 @@ cs->class.hotplug = hotplug; return 0; } -EXPORT_SYMBOL(class_simple_set_hotplug); +EXPORT_SYMBOL_GPL(class_simple_set_hotplug); /** * class_simple_device_remove - removes a class device that was created with class_simple_device_add() @@ -213,4 +213,4 @@ spin_unlock(&simple_dev_list_lock); } } -EXPORT_SYMBOL(class_simple_device_remove); +EXPORT_SYMBOL_GPL(class_simple_device_remove); diff -Nru a/drivers/base/cpu.c b/drivers/base/cpu.c --- a/drivers/base/cpu.c 2004-10-28 22:35:28 -07:00 +++ b/drivers/base/cpu.c 2004-10-28 22:35:28 -07:00 @@ -13,7 +13,7 @@ struct sysdev_class cpu_sysdev_class = { set_kset_name("cpu"), }; -EXPORT_SYMBOL(cpu_sysdev_class); +EXPORT_SYMBOL_GPL(cpu_sysdev_class); #ifdef CONFIG_HOTPLUG_CPU static ssize_t show_online(struct sys_device *dev, char *buf) diff -Nru a/drivers/base/power/main.c b/drivers/base/power/main.c --- a/drivers/base/power/main.c 2004-10-28 22:35:28 -07:00 +++ b/drivers/base/power/main.c 2004-10-28 22:35:28 -07:00 @@ -28,6 +28,7 @@ LIST_HEAD(dpm_off_irq); DECLARE_MUTEX(dpm_sem); +DECLARE_MUTEX(dpm_list_sem); /* * PM Reference Counting. @@ -75,12 +76,12 @@ pr_debug("PM: Adding info for %s:%s\n", dev->bus ? dev->bus->name : "No Bus", dev->kobj.name); atomic_set(&dev->power.pm_users, 0); - down(&dpm_sem); + down(&dpm_list_sem); list_add_tail(&dev->power.entry, &dpm_active); device_pm_set_parent(dev, dev->parent); if ((error = dpm_sysfs_add(dev))) list_del(&dev->power.entry); - up(&dpm_sem); + up(&dpm_list_sem); return error; } @@ -88,11 +89,11 @@ { pr_debug("PM: Removing info for %s:%s\n", dev->bus ? dev->bus->name : "No Bus", dev->kobj.name); - down(&dpm_sem); + down(&dpm_list_sem); dpm_sysfs_remove(dev); device_pm_release(dev->power.pm_parent); - list_del(&dev->power.entry); - up(&dpm_sem); + list_del_init(&dev->power.entry); + up(&dpm_list_sem); } diff -Nru a/drivers/base/power/power.h b/drivers/base/power/power.h --- a/drivers/base/power/power.h 2004-10-28 22:35:28 -07:00 +++ b/drivers/base/power/power.h 2004-10-28 22:35:28 -07:00 @@ -28,6 +28,11 @@ extern struct semaphore dpm_sem; /* + * Used to serialize changes to the dpm_* lists. + */ +extern struct semaphore dpm_list_sem; + +/* * The PM lists. */ extern struct list_head dpm_active; diff -Nru a/drivers/base/power/resume.c b/drivers/base/power/resume.c --- a/drivers/base/power/resume.c 2004-10-28 22:35:28 -07:00 +++ b/drivers/base/power/resume.c 2004-10-28 22:35:28 -07:00 @@ -31,16 +31,22 @@ void dpm_resume(void) { + down(&dpm_list_sem); while(!list_empty(&dpm_off)) { struct list_head * entry = dpm_off.next; struct device * dev = to_device(entry); + + get_device(dev); list_del_init(entry); + list_add_tail(entry, &dpm_active); + up(&dpm_list_sem); if (!dev->power.prev_state) resume_device(dev); - - list_add_tail(entry, &dpm_active); + down(&dpm_list_sem); + put_device(dev); } + up(&dpm_list_sem); } @@ -76,9 +82,13 @@ { while(!list_empty(&dpm_off_irq)) { struct list_head * entry = dpm_off_irq.next; + struct device * dev = to_device(entry); + + get_device(dev); list_del_init(entry); - resume_device(to_device(entry)); list_add_tail(entry, &dpm_active); + resume_device(dev); + put_device(dev); } } diff -Nru a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c --- a/drivers/base/power/suspend.c 2004-10-28 22:35:28 -07:00 +++ b/drivers/base/power/suspend.c 2004-10-28 22:35:28 -07:00 @@ -63,11 +63,6 @@ * If we hit a failure with any of the devices, call device_resume() * above to bring the suspended devices back to life. * - * Note this function leaves dpm_sem held to - * a) block other devices from registering. - * b) prevent other PM operations from happening after we've begun. - * c) make sure we're exclusive when we disable interrupts. - * */ int device_suspend(u32 state) @@ -75,29 +70,40 @@ int error = 0; down(&dpm_sem); - while(!list_empty(&dpm_active)) { + down(&dpm_list_sem); + while (!list_empty(&dpm_active) && error == 0) { struct list_head * entry = dpm_active.prev; struct device * dev = to_device(entry); + + get_device(dev); + up(&dpm_list_sem); + error = suspend_device(dev, state); - if (!error) { - list_del(&dev->power.entry); - list_add(&dev->power.entry, &dpm_off); - } else if (error == -EAGAIN) { - list_del(&dev->power.entry); - list_add(&dev->power.entry, &dpm_off_irq); - } else { + down(&dpm_list_sem); + + /* Check if the device got removed */ + if (!list_empty(&dev->power.entry)) { + /* Move it to the dpm_off or dpm_off_irq list */ + if (!error) { + list_del(&dev->power.entry); + list_add(&dev->power.entry, &dpm_off); + } else if (error == -EAGAIN) { + list_del(&dev->power.entry); + list_add(&dev->power.entry, &dpm_off_irq); + error = 0; + } + } + if (error) printk(KERN_ERR "Could not suspend device %s: " "error %d\n", kobject_name(&dev->kobj), error); - goto Error; - } + put_device(dev); } - Done: + up(&dpm_list_sem); + if (error) + dpm_resume(); up(&dpm_sem); return error; - Error: - dpm_resume(); - goto Done; } EXPORT_SYMBOL_GPL(device_suspend); diff -Nru a/fs/sysfs/bin.c b/fs/sysfs/bin.c --- a/fs/sysfs/bin.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/bin.c 2004-10-28 22:35:28 -07:00 @@ -17,8 +17,8 @@ static int fill_read(struct dentry *dentry, char *buffer, loff_t off, size_t count) { - struct bin_attribute * attr = dentry->d_fsdata; - struct kobject * kobj = dentry->d_parent->d_fsdata; + struct bin_attribute * attr = to_bin_attr(dentry); + struct kobject * kobj = to_kobj(dentry->d_parent); return attr->read(kobj, buffer, off, count); } @@ -60,8 +60,8 @@ static int flush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count) { - struct bin_attribute *attr = dentry->d_fsdata; - struct kobject *kobj = dentry->d_parent->d_fsdata; + struct bin_attribute *attr = to_bin_attr(dentry); + struct kobject *kobj = to_kobj(dentry->d_parent); return attr->write(kobj, buffer, offset, count); } @@ -95,7 +95,7 @@ static int open(struct inode * inode, struct file * file) { struct kobject *kobj = sysfs_get_kobject(file->f_dentry->d_parent); - struct bin_attribute * attr = file->f_dentry->d_fsdata; + struct bin_attribute * attr = to_bin_attr(file->f_dentry); int error = -EINVAL; if (!kobj || !attr) @@ -130,8 +130,8 @@ static int release(struct inode * inode, struct file * file) { - struct kobject * kobj = file->f_dentry->d_parent->d_fsdata; - struct bin_attribute * attr = file->f_dentry->d_fsdata; + struct kobject * kobj = to_kobj(file->f_dentry->d_parent); + struct bin_attribute * attr = to_bin_attr(file->f_dentry); u8 * buffer = file->private_data; if (kobj) @@ -141,7 +141,7 @@ return 0; } -static struct file_operations bin_fops = { +struct file_operations bin_fops = { .read = read, .write = write, .llseek = generic_file_llseek, @@ -158,31 +158,9 @@ int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr) { - struct dentry * dentry; - struct dentry * parent; - int error = 0; + BUG_ON(!kobj || !kobj->dentry || !attr); - if (!kobj || !attr) - return -EINVAL; - - parent = kobj->dentry; - - down(&parent->d_inode->i_sem); - dentry = sysfs_get_dentry(parent,attr->attr.name); - if (!IS_ERR(dentry)) { - dentry->d_fsdata = (void *)attr; - error = sysfs_create(dentry, - (attr->attr.mode & S_IALLUGO) | S_IFREG, - NULL); - if (!error) { - dentry->d_inode->i_size = attr->size; - dentry->d_inode->i_fop = &bin_fops; - } - dput(dentry); - } else - error = PTR_ERR(dentry); - up(&parent->d_inode->i_sem); - return error; + return sysfs_add_file(kobj->dentry, &attr->attr, SYSFS_KOBJ_BIN_ATTR); } diff -Nru a/fs/sysfs/dir.c b/fs/sysfs/dir.c --- a/fs/sysfs/dir.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/dir.c 2004-10-28 22:35:28 -07:00 @@ -12,32 +12,107 @@ DECLARE_RWSEM(sysfs_rename_sem); +static void sysfs_d_iput(struct dentry * dentry, struct inode * inode) +{ + struct sysfs_dirent * sd = dentry->d_fsdata; + + if (sd) { + BUG_ON(sd->s_dentry != dentry); + sd->s_dentry = NULL; + sysfs_put(sd); + } + iput(inode); +} + +static struct dentry_operations sysfs_dentry_ops = { + .d_iput = sysfs_d_iput, +}; + +/* + * Allocates a new sysfs_dirent and links it to the parent sysfs_dirent + */ +static struct sysfs_dirent * sysfs_new_dirent(struct sysfs_dirent * parent_sd, + void * element) +{ + struct sysfs_dirent * sd; + + sd = kmalloc(sizeof(*sd), GFP_KERNEL); + if (!sd) + return ERR_PTR(-ENOMEM); + + memset(sd, 0, sizeof(*sd)); + atomic_set(&sd->s_count, 1); + INIT_LIST_HEAD(&sd->s_children); + list_add(&sd->s_sibling, &parent_sd->s_children); + sd->s_element = element; + + return sd; +} + +int sysfs_make_dirent(struct sysfs_dirent * parent_sd, struct dentry * dentry, + void * element, umode_t mode, int type) +{ + struct sysfs_dirent * sd; + + sd = sysfs_new_dirent(parent_sd, element); + if (!sd) + return -ENOMEM; + + sd->s_mode = mode; + sd->s_type = type; + sd->s_dentry = dentry; + if (dentry) { + dentry->d_fsdata = sysfs_get(sd); + dentry->d_op = &sysfs_dentry_ops; + } + + return 0; +} + static int init_dir(struct inode * inode) { - inode->i_op = &simple_dir_inode_operations; - inode->i_fop = &simple_dir_operations; + inode->i_op = &sysfs_dir_inode_operations; + inode->i_fop = &sysfs_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inode->i_nlink++; return 0; } +static int init_file(struct inode * inode) +{ + inode->i_size = PAGE_SIZE; + inode->i_fop = &sysfs_file_operations; + return 0; +} + +static int init_symlink(struct inode * inode) +{ + inode->i_op = &sysfs_symlink_inode_operations; + return 0; +} static int create_dir(struct kobject * k, struct dentry * p, const char * n, struct dentry ** d) { int error; + umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO; down(&p->d_inode->i_sem); *d = sysfs_get_dentry(p,n); if (!IS_ERR(*d)) { - error = sysfs_create(*d, - S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO, - init_dir); + error = sysfs_create(*d, mode, init_dir); if (!error) { - (*d)->d_fsdata = k; - p->d_inode->i_nlink++; + error = sysfs_make_dirent(p->d_fsdata, *d, k, mode, + SYSFS_DIR); + if (!error) { + p->d_inode->i_nlink++; + (*d)->d_op = &sysfs_dentry_ops; + d_rehash(*d); + } } + if (error) + d_drop(*d); dput(*d); } else error = PTR_ERR(*d); @@ -63,8 +138,7 @@ struct dentry * parent; int error = 0; - if (!kobj) - return -EINVAL; + BUG_ON(!kobj); if (kobj->parent) parent = kobj->parent->dentry; @@ -79,12 +153,93 @@ return error; } +/* attaches attribute's sysfs_dirent to the dentry corresponding to the + * attribute file + */ +static int sysfs_attach_attr(struct sysfs_dirent * sd, struct dentry * dentry) +{ + struct attribute * attr = NULL; + struct bin_attribute * bin_attr = NULL; + int (* init) (struct inode *) = NULL; + int error = 0; + + if (sd->s_type & SYSFS_KOBJ_BIN_ATTR) { + bin_attr = sd->s_element; + attr = &bin_attr->attr; + } else { + attr = sd->s_element; + init = init_file; + } + + error = sysfs_create(dentry, (attr->mode & S_IALLUGO) | S_IFREG, init); + if (error) + return error; + + if (bin_attr) { + dentry->d_inode->i_size = bin_attr->size; + dentry->d_inode->i_fop = &bin_fops; + } + dentry->d_op = &sysfs_dentry_ops; + dentry->d_fsdata = sysfs_get(sd); + sd->s_dentry = dentry; + d_rehash(dentry); + + return 0; +} + +static int sysfs_attach_link(struct sysfs_dirent * sd, struct dentry * dentry) +{ + int err = 0; + + err = sysfs_create(dentry, S_IFLNK|S_IRWXUGO, init_symlink); + if (!err) { + dentry->d_op = &sysfs_dentry_ops; + dentry->d_fsdata = sysfs_get(sd); + sd->s_dentry = dentry; + d_rehash(dentry); + } + return err; +} + +struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata; + struct sysfs_dirent * sd; + int err = 0; + + list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { + if (sd->s_type & SYSFS_NOT_PINNED) { + const unsigned char * name = sysfs_get_name(sd); + + if (strcmp(name, dentry->d_name.name)) + continue; + + if (sd->s_type & SYSFS_KOBJ_LINK) + err = sysfs_attach_link(sd, dentry); + else + err = sysfs_attach_attr(sd, dentry); + break; + } + } + + return ERR_PTR(err); +} + +struct inode_operations sysfs_dir_inode_operations = { + .lookup = sysfs_lookup, +}; static void remove_dir(struct dentry * d) { struct dentry * parent = dget(d->d_parent); + struct sysfs_dirent * sd; + down(&parent->d_inode->i_sem); d_delete(d); + sd = d->d_fsdata; + list_del_init(&sd->s_sibling); + sysfs_put(sd); if (d->d_inode) simple_rmdir(parent->d_inode,d); @@ -112,47 +267,22 @@ void sysfs_remove_dir(struct kobject * kobj) { - struct list_head * node; struct dentry * dentry = dget(kobj->dentry); + struct sysfs_dirent * parent_sd = dentry->d_fsdata; + struct sysfs_dirent * sd, * tmp; if (!dentry) return; pr_debug("sysfs %s: removing dir\n",dentry->d_name.name); down(&dentry->d_inode->i_sem); - - spin_lock(&dcache_lock); -restart: - node = dentry->d_subdirs.next; - while (node != &dentry->d_subdirs) { - struct dentry * d = list_entry(node,struct dentry,d_child); - - node = node->next; - pr_debug(" o %s (%d): ",d->d_name.name,atomic_read(&d->d_count)); - if (!d_unhashed(d) && (d->d_inode)) { - d = dget_locked(d); - pr_debug("removing"); - - /** - * Unlink and unhash. - */ - __d_drop(d); - spin_unlock(&dcache_lock); - /* release the target kobject in case of - * a symlink - */ - if (S_ISLNK(d->d_inode->i_mode)) - kobject_put(d->d_fsdata); - - simple_unlink(dentry->d_inode,d); - dput(d); - pr_debug(" done\n"); - spin_lock(&dcache_lock); - /* re-acquired dcache_lock, need to restart */ - goto restart; - } + list_for_each_entry_safe(sd, tmp, &parent_sd->s_children, s_sibling) { + if (!sd->s_element) + continue; + list_del_init(&sd->s_sibling); + sysfs_drop_dentry(sd, dentry); + sysfs_put(sd); } - spin_unlock(&dcache_lock); up(&dentry->d_inode->i_sem); remove_dir(dentry); @@ -182,9 +312,14 @@ if (!IS_ERR(new_dentry)) { if (!new_dentry->d_inode) { error = kobject_set_name(kobj, "%s", new_name); - if (!error) + if (!error) { + d_add(new_dentry, NULL); d_move(kobj->dentry, new_dentry); - } + } + else + d_drop(new_dentry); + } else + error = -EEXIST; dput(new_dentry); } up(&parent->d_inode->i_sem); @@ -192,6 +327,144 @@ return error; } + +static int sysfs_dir_open(struct inode *inode, struct file *file) +{ + struct dentry * dentry = file->f_dentry; + struct sysfs_dirent * parent_sd = dentry->d_fsdata; + + down(&dentry->d_inode->i_sem); + file->private_data = sysfs_new_dirent(parent_sd, NULL); + up(&dentry->d_inode->i_sem); + + return file->private_data ? 0 : -ENOMEM; + +} + +static int sysfs_dir_close(struct inode *inode, struct file *file) +{ + struct dentry * dentry = file->f_dentry; + struct sysfs_dirent * cursor = file->private_data; + + down(&dentry->d_inode->i_sem); + list_del_init(&cursor->s_sibling); + up(&dentry->d_inode->i_sem); + + return 0; +} + +/* Relationship between s_mode and the DT_xxx types */ +static inline unsigned char dt_type(struct sysfs_dirent *sd) +{ + return (sd->s_mode >> 12) & 15; +} + +static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) +{ + struct dentry *dentry = filp->f_dentry; + struct sysfs_dirent * parent_sd = dentry->d_fsdata; + struct sysfs_dirent *cursor = filp->private_data; + struct list_head *p, *q = &cursor->s_sibling; + ino_t ino; + int i = filp->f_pos; + + switch (i) { + case 0: + ino = dentry->d_inode->i_ino; + if (filldir(dirent, ".", 1, i, ino, DT_DIR) < 0) + break; + filp->f_pos++; + i++; + /* fallthrough */ + case 1: + ino = parent_ino(dentry); + if (filldir(dirent, "..", 2, i, ino, DT_DIR) < 0) + break; + filp->f_pos++; + i++; + /* fallthrough */ + default: + if (filp->f_pos == 2) { + list_del(q); + list_add(q, &parent_sd->s_children); + } + for (p=q->next; p!= &parent_sd->s_children; p=p->next) { + struct sysfs_dirent *next; + const char * name; + int len; + + next = list_entry(p, struct sysfs_dirent, + s_sibling); + if (!next->s_element) + continue; + + name = sysfs_get_name(next); + len = strlen(name); + if (next->s_dentry) + ino = next->s_dentry->d_inode->i_ino; + else + ino = iunique(sysfs_sb, 2); + + if (filldir(dirent, name, len, filp->f_pos, ino, + dt_type(next)) < 0) + return 0; + + list_del(q); + list_add(q, p); + p = q; + filp->f_pos++; + } + } + return 0; +} + +static loff_t sysfs_dir_lseek(struct file * file, loff_t offset, int origin) +{ + struct dentry * dentry = file->f_dentry; + + down(&dentry->d_inode->i_sem); + switch (origin) { + case 1: + offset += file->f_pos; + case 0: + if (offset >= 0) + break; + default: + up(&file->f_dentry->d_inode->i_sem); + return -EINVAL; + } + if (offset != file->f_pos) { + file->f_pos = offset; + if (file->f_pos >= 2) { + struct sysfs_dirent *sd = dentry->d_fsdata; + struct sysfs_dirent *cursor = file->private_data; + struct list_head *p; + loff_t n = file->f_pos - 2; + + list_del(&cursor->s_sibling); + p = sd->s_children.next; + while (n && p != &sd->s_children) { + struct sysfs_dirent *next; + next = list_entry(p, struct sysfs_dirent, + s_sibling); + if (next->s_element) + n--; + p = p->next; + } + list_add_tail(&cursor->s_sibling, p); + } + } + up(&dentry->d_inode->i_sem); + return offset; +} + +struct file_operations sysfs_dir_operations = { + .open = sysfs_dir_open, + .release = sysfs_dir_close, + .llseek = sysfs_dir_lseek, + .read = generic_read_dir, + .readdir = sysfs_readdir, +}; EXPORT_SYMBOL_GPL(sysfs_create_dir); EXPORT_SYMBOL_GPL(sysfs_remove_dir); diff -Nru a/fs/sysfs/file.c b/fs/sysfs/file.c --- a/fs/sysfs/file.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/file.c 2004-10-28 22:35:28 -07:00 @@ -6,18 +6,10 @@ #include #include #include +#include #include "sysfs.h" -static struct file_operations sysfs_file_operations; - -static int init_file(struct inode * inode) -{ - inode->i_size = PAGE_SIZE; - inode->i_fop = &sysfs_file_operations; - return 0; -} - #define to_subsys(k) container_of(k,struct subsystem,kset.kobj) #define to_sattr(a) container_of(a,struct subsys_attribute,attr) @@ -62,12 +54,14 @@ loff_t pos; char * page; struct sysfs_ops * ops; + struct semaphore sem; + int needs_read_fill; }; /** * fill_read_buffer - allocate and fill buffer from object. - * @file: file pointer. + * @dentry: dentry pointer. * @buffer: data buffer for file. * * Allocate @buffer->page, if it hasn't been already, then call the @@ -75,10 +69,10 @@ * data. * This is called only once, on the file's first read. */ -static int fill_read_buffer(struct file * file, struct sysfs_buffer * buffer) +static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer) { - struct attribute * attr = file->f_dentry->d_fsdata; - struct kobject * kobj = file->f_dentry->d_parent->d_fsdata; + struct attribute * attr = to_attr(dentry); + struct kobject * kobj = to_kobj(dentry->d_parent); struct sysfs_ops * ops = buffer->ops; int ret = 0; ssize_t count; @@ -89,6 +83,7 @@ return -ENOMEM; count = ops->show(kobj,attr,buffer->page); + buffer->needs_read_fill = 0; BUG_ON(count > (ssize_t)PAGE_SIZE); if (count >= 0) buffer->count = count; @@ -115,6 +110,9 @@ { int error; + if (*ppos > buffer->count) + return 0; + if (count > (buffer->count - *ppos)) count = buffer->count - *ppos; @@ -149,13 +147,17 @@ struct sysfs_buffer * buffer = file->private_data; ssize_t retval = 0; - if (!*ppos) { - if ((retval = fill_read_buffer(file,buffer))) - return retval; + down(&buffer->sem); + if (buffer->needs_read_fill) { + if ((retval = fill_read_buffer(file->f_dentry,buffer))) + goto out; } pr_debug("%s: count = %d, ppos = %lld, buf = %s\n", __FUNCTION__,count,*ppos,buffer->page); - return flush_read_buffer(buffer,buf,count,ppos); + retval = flush_read_buffer(buffer,buf,count,ppos); +out: + up(&buffer->sem); + return retval; } @@ -182,6 +184,7 @@ if (count >= PAGE_SIZE) count = PAGE_SIZE - 1; error = copy_from_user(buffer->page,buf,count); + buffer->needs_read_fill = 1; return error ? -EFAULT : count; } @@ -197,10 +200,10 @@ */ static int -flush_write_buffer(struct file * file, struct sysfs_buffer * buffer, size_t count) +flush_write_buffer(struct dentry * dentry, struct sysfs_buffer * buffer, size_t count) { - struct attribute * attr = file->f_dentry->d_fsdata; - struct kobject * kobj = file->f_dentry->d_parent->d_fsdata; + struct attribute * attr = to_attr(dentry); + struct kobject * kobj = to_kobj(dentry->d_parent); struct sysfs_ops * ops = buffer->ops; return ops->store(kobj,attr,buffer->page,count); @@ -229,18 +232,20 @@ { struct sysfs_buffer * buffer = file->private_data; + down(&buffer->sem); count = fill_write_buffer(buffer,buf,count); if (count > 0) - count = flush_write_buffer(file,buffer,count); + count = flush_write_buffer(file->f_dentry,buffer,count); if (count > 0) *ppos += count; + up(&buffer->sem); return count; } static int check_perm(struct inode * inode, struct file * file) { struct kobject *kobj = sysfs_get_kobject(file->f_dentry->d_parent); - struct attribute * attr = file->f_dentry->d_fsdata; + struct attribute * attr = to_attr(file->f_dentry); struct sysfs_buffer * buffer; struct sysfs_ops * ops = NULL; int error = 0; @@ -296,6 +301,8 @@ buffer = kmalloc(sizeof(struct sysfs_buffer),GFP_KERNEL); if (buffer) { memset(buffer,0,sizeof(struct sysfs_buffer)); + init_MUTEX(&buffer->sem); + buffer->needs_read_fill = 1; buffer->ops = ops; file->private_data = buffer; } else @@ -321,8 +328,8 @@ static int sysfs_release(struct inode * inode, struct file * filp) { - struct kobject * kobj = filp->f_dentry->d_parent->d_fsdata; - struct attribute * attr = filp->f_dentry->d_fsdata; + struct kobject * kobj = to_kobj(filp->f_dentry->d_parent); + struct attribute * attr = to_attr(filp->f_dentry); struct sysfs_buffer * buffer = filp->private_data; if (kobj) @@ -337,7 +344,7 @@ return 0; } -static struct file_operations sysfs_file_operations = { +struct file_operations sysfs_file_operations = { .read = sysfs_read_file, .write = sysfs_write_file, .llseek = generic_file_llseek, @@ -346,23 +353,16 @@ }; -int sysfs_add_file(struct dentry * dir, const struct attribute * attr) +int sysfs_add_file(struct dentry * dir, const struct attribute * attr, int type) { - struct dentry * dentry; - int error; + struct sysfs_dirent * parent_sd = dir->d_fsdata; + umode_t mode = (attr->mode & S_IALLUGO) | S_IFREG; + int error = 0; down(&dir->d_inode->i_sem); - dentry = sysfs_get_dentry(dir,attr->name); - if (!IS_ERR(dentry)) { - error = sysfs_create(dentry, - (attr->mode & S_IALLUGO) | S_IFREG, - init_file); - if (!error) - dentry->d_fsdata = (void *)attr; - dput(dentry); - } else - error = PTR_ERR(dentry); + error = sysfs_make_dirent(parent_sd, NULL, (void *) attr, mode, type); up(&dir->d_inode->i_sem); + return error; } @@ -375,9 +375,10 @@ int sysfs_create_file(struct kobject * kobj, const struct attribute * attr) { - if (kobj && attr) - return sysfs_add_file(kobj->dentry,attr); - return -EINVAL; + BUG_ON(!kobj || !kobj->dentry || !attr); + + return sysfs_add_file(kobj->dentry, attr, SYSFS_KOBJ_ATTR); + } @@ -409,7 +410,8 @@ */ dput(victim); res = 0; - } + } else + d_drop(victim); /** * Drop the reference acquired from sysfs_get_dentry() above. diff -Nru a/fs/sysfs/group.c b/fs/sysfs/group.c --- a/fs/sysfs/group.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/group.c 2004-10-28 22:35:28 -07:00 @@ -31,7 +31,7 @@ int error = 0; for (attr = grp->attrs; *attr && !error; attr++) { - error = sysfs_add_file(dir,*attr); + error = sysfs_add_file(dir, *attr, SYSFS_KOBJ_ATTR); } if (error) remove_files(dir,grp); @@ -44,6 +44,8 @@ { struct dentry * dir; int error; + + BUG_ON(!kobj || !kobj->dentry); if (grp->name) { error = sysfs_create_subdir(kobj,grp->name,&dir); diff -Nru a/fs/sysfs/inode.c b/fs/sysfs/inode.c --- a/fs/sysfs/inode.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/inode.c 2004-10-28 22:35:28 -07:00 @@ -11,6 +11,8 @@ #include #include #include +#include "sysfs.h" + extern struct super_block * sysfs_sb; static struct address_space_operations sysfs_aops = { @@ -29,8 +31,8 @@ struct inode * inode = new_inode(sysfs_sb); if (inode) { inode->i_mode = mode; - inode->i_uid = current->fsuid; - inode->i_gid = current->fsgid; + inode->i_uid = 0; + inode->i_gid = 0; inode->i_blksize = PAGE_CACHE_SIZE; inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; @@ -66,7 +68,8 @@ error = init(inode); if (!error) { d_instantiate(dentry, inode); - dget(dentry); /* Extra count - pin the dentry in core */ + if (S_ISDIR(mode)) + dget(dentry); /* pin only directory dentry in core */ } else iput(inode); Done: @@ -88,31 +91,74 @@ return lookup_hash(&qstr,parent); } +/* + * Get the name for corresponding element represented by the given sysfs_dirent + */ +const unsigned char * sysfs_get_name(struct sysfs_dirent *sd) +{ + struct attribute * attr; + struct bin_attribute * bin_attr; + struct sysfs_symlink * sl; + + if (!sd || !sd->s_element) + BUG(); + + switch (sd->s_type) { + case SYSFS_DIR: + /* Always have a dentry so use that */ + return sd->s_dentry->d_name.name; + + case SYSFS_KOBJ_ATTR: + attr = sd->s_element; + return attr->name; + + case SYSFS_KOBJ_BIN_ATTR: + bin_attr = sd->s_element; + return bin_attr->attr.name; + + case SYSFS_KOBJ_LINK: + sl = sd->s_element; + return sl->link_name; + } + return NULL; +} + + +/* + * Unhashes the dentry corresponding to given sysfs_dirent + * Called with parent inode's i_sem held. + */ +void sysfs_drop_dentry(struct sysfs_dirent * sd, struct dentry * parent) +{ + struct dentry * dentry = sd->s_dentry; + + if (dentry) { + spin_lock(&dcache_lock); + if (!(d_unhashed(dentry) && dentry->d_inode)) { + dget_locked(dentry); + __d_drop(dentry); + spin_unlock(&dcache_lock); + simple_unlink(parent->d_inode, dentry); + } else + spin_unlock(&dcache_lock); + } +} + void sysfs_hash_and_remove(struct dentry * dir, const char * name) { - struct dentry * victim; + struct sysfs_dirent * sd; + struct sysfs_dirent * parent_sd = dir->d_fsdata; down(&dir->d_inode->i_sem); - victim = sysfs_get_dentry(dir,name); - if (!IS_ERR(victim)) { - /* make sure dentry is really there */ - if (victim->d_inode && - (victim->d_parent->d_inode == dir->d_inode)) { - pr_debug("sysfs: Removing %s (%d)\n", victim->d_name.name, - atomic_read(&victim->d_count)); - - d_drop(victim); - /* release the target kobject in case of - * a symlink - */ - if (S_ISLNK(victim->d_inode->i_mode)) - kobject_put(victim->d_fsdata); - simple_unlink(dir->d_inode,victim); + list_for_each_entry(sd, &parent_sd->s_children, s_sibling) { + if (!sd->s_element) + continue; + if (!strcmp(sysfs_get_name(sd), name)) { + list_del_init(&sd->s_sibling); + sysfs_drop_dentry(sd, dir); + sysfs_put(sd); + break; } - /* - * Drop reference from sysfs_get_dentry() above. - */ - dput(victim); } up(&dir->d_inode->i_sem); } diff -Nru a/fs/sysfs/mount.c b/fs/sysfs/mount.c --- a/fs/sysfs/mount.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/mount.c 2004-10-28 22:35:28 -07:00 @@ -22,6 +22,13 @@ .drop_inode = generic_delete_inode, }; +struct sysfs_dirent sysfs_root = { + .s_sibling = LIST_HEAD_INIT(sysfs_root.s_sibling), + .s_children = LIST_HEAD_INIT(sysfs_root.s_children), + .s_element = NULL, + .s_type = SYSFS_ROOT, +}; + static int sysfs_fill_super(struct super_block *sb, void *data, int silent) { struct inode *inode; @@ -35,8 +42,8 @@ inode = sysfs_new_inode(S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO); if (inode) { - inode->i_op = &simple_dir_inode_operations; - inode->i_fop = &simple_dir_operations; + inode->i_op = &sysfs_dir_inode_operations; + inode->i_fop = &sysfs_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inode->i_nlink++; } else { @@ -50,6 +57,7 @@ iput(inode); return -ENOMEM; } + root->d_fsdata = &sysfs_root; sb->s_root = root; return 0; } diff -Nru a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c --- a/fs/sysfs/symlink.c 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/symlink.c 2004-10-28 22:35:28 -07:00 @@ -9,18 +9,12 @@ #include "sysfs.h" -static struct inode_operations sysfs_symlink_inode_operations = { +struct inode_operations sysfs_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = sysfs_follow_link, .put_link = sysfs_put_link, }; -static int init_symlink(struct inode * inode) -{ - inode->i_op = &sysfs_symlink_inode_operations; - return 0; -} - static int object_depth(struct kobject * kobj) { struct kobject * p = kobj; @@ -55,6 +49,36 @@ } } +static int sysfs_add_link(struct dentry * parent, char * name, struct kobject * target) +{ + struct sysfs_dirent * parent_sd = parent->d_fsdata; + struct sysfs_symlink * sl; + int error = 0; + + error = -ENOMEM; + sl = kmalloc(sizeof(*sl), GFP_KERNEL); + if (!sl) + goto exit1; + + sl->link_name = kmalloc(strlen(name) + 1, GFP_KERNEL); + if (!sl->link_name) + goto exit2; + + strcpy(sl->link_name, name); + sl->target_kobj = kobject_get(target); + + error = sysfs_make_dirent(parent_sd, NULL, sl, S_IFLNK|S_IRWXUGO, + SYSFS_KOBJ_LINK); + if (!error) + return 0; + + kfree(sl->link_name); +exit2: + kfree(sl); +exit1: + return error; +} + /** * sysfs_create_link - create symlink between two objects. * @kobj: object whose directory we're creating the link in. @@ -64,21 +88,12 @@ int sysfs_create_link(struct kobject * kobj, struct kobject * target, char * name) { struct dentry * dentry = kobj->dentry; - struct dentry * d; int error = 0; + BUG_ON(!kobj || !kobj->dentry || !name); + down(&dentry->d_inode->i_sem); - d = sysfs_get_dentry(dentry,name); - if (!IS_ERR(d)) { - error = sysfs_create(d, S_IFLNK|S_IRWXUGO, init_symlink); - if (!error) - /* - * associate the link dentry with the target kobject - */ - d->d_fsdata = kobject_get(target); - dput(d); - } else - error = PTR_ERR(d); + error = sysfs_add_link(dentry, name, target); up(&dentry->d_inode->i_sem); return error; } diff -Nru a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h --- a/fs/sysfs/sysfs.h 2004-10-28 22:35:28 -07:00 +++ b/fs/sysfs/sysfs.h 2004-10-28 22:35:28 -07:00 @@ -4,26 +4,93 @@ extern struct inode * sysfs_new_inode(mode_t mode); extern int sysfs_create(struct dentry *, int mode, int (*init)(struct inode *)); +extern int sysfs_make_dirent(struct sysfs_dirent *, struct dentry *, void *, + umode_t, int); extern struct dentry * sysfs_get_dentry(struct dentry *, const char *); -extern int sysfs_add_file(struct dentry * dir, const struct attribute * attr); +extern int sysfs_add_file(struct dentry *, const struct attribute *, int); extern void sysfs_hash_and_remove(struct dentry * dir, const char * name); extern int sysfs_create_subdir(struct kobject *, const char *, struct dentry **); extern void sysfs_remove_subdir(struct dentry *); +extern const unsigned char * sysfs_get_name(struct sysfs_dirent *sd); +extern void sysfs_drop_dentry(struct sysfs_dirent *sd, struct dentry *parent); + extern int sysfs_follow_link(struct dentry *, struct nameidata *); extern void sysfs_put_link(struct dentry *, struct nameidata *); extern struct rw_semaphore sysfs_rename_sem; +extern struct super_block * sysfs_sb; +extern struct file_operations sysfs_dir_operations; +extern struct file_operations sysfs_file_operations; +extern struct file_operations bin_fops; +extern struct inode_operations sysfs_dir_inode_operations; +extern struct inode_operations sysfs_symlink_inode_operations; + +struct sysfs_symlink { + char * link_name; + struct kobject * target_kobj; +}; + +static inline struct kobject * to_kobj(struct dentry * dentry) +{ + struct sysfs_dirent * sd = dentry->d_fsdata; + return ((struct kobject *) sd->s_element); +} + +static inline struct attribute * to_attr(struct dentry * dentry) +{ + struct sysfs_dirent * sd = dentry->d_fsdata; + return ((struct attribute *) sd->s_element); +} + +static inline struct bin_attribute * to_bin_attr(struct dentry * dentry) +{ + struct sysfs_dirent * sd = dentry->d_fsdata; + return ((struct bin_attribute *) sd->s_element); +} static inline struct kobject *sysfs_get_kobject(struct dentry *dentry) { struct kobject * kobj = NULL; spin_lock(&dcache_lock); - if (!d_unhashed(dentry)) - kobj = kobject_get(dentry->d_fsdata); + if (!d_unhashed(dentry)) { + struct sysfs_dirent * sd = dentry->d_fsdata; + if (sd->s_type & SYSFS_KOBJ_LINK) { + struct sysfs_symlink * sl = sd->s_element; + kobj = kobject_get(sl->target_kobj); + } else + kobj = kobject_get(sd->s_element); + } spin_unlock(&dcache_lock); return kobj; } + +static inline void release_sysfs_dirent(struct sysfs_dirent * sd) +{ + if (sd->s_type & SYSFS_KOBJ_LINK) { + struct sysfs_symlink * sl = sd->s_element; + kfree(sl->link_name); + kobject_put(sl->target_kobj); + kfree(sl); + } + kfree(sd); +} + +static inline struct sysfs_dirent * sysfs_get(struct sysfs_dirent * sd) +{ + if (sd) { + WARN_ON(!atomic_read(&sd->s_count)); + atomic_inc(&sd->s_count); + } + return sd; +} + +static inline void sysfs_put(struct sysfs_dirent * sd) +{ + if (atomic_dec_and_test(&sd->s_count)) + release_sysfs_dirent(sd); +} + diff -Nru a/include/linux/sysfs.h b/include/linux/sysfs.h --- a/include/linux/sysfs.h 2004-10-28 22:35:28 -07:00 +++ b/include/linux/sysfs.h 2004-10-28 22:35:28 -07:00 @@ -9,6 +9,8 @@ #ifndef _SYSFS_H_ #define _SYSFS_H_ +#include + struct kobject; struct module; @@ -56,6 +58,23 @@ ssize_t (*show)(struct kobject *, struct attribute *,char *); ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); }; + +struct sysfs_dirent { + atomic_t s_count; + struct list_head s_sibling; + struct list_head s_children; + void * s_element; + int s_type; + umode_t s_mode; + struct dentry * s_dentry; +}; + +#define SYSFS_ROOT 0x0001 +#define SYSFS_DIR 0x0002 +#define SYSFS_KOBJ_ATTR 0x0004 +#define SYSFS_KOBJ_BIN_ATTR 0x0008 +#define SYSFS_KOBJ_LINK 0x0020 +#define SYSFS_NOT_PINNED (SYSFS_KOBJ_ATTR | SYSFS_KOBJ_BIN_ATTR | SYSFS_KOBJ_LINK) #ifdef CONFIG_SYSFS