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 <paulus@samba.org>
#   
#   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 <paulus@samba.org>
#   Signed-off-by: Andrew Morton <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <Simon.Derr@bull.net>
#   
#   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 <simon.derr@bull.net>
#   Signed-off-by: Andrew Morton <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <Simon.Derr@bull.net>
#   
#   - 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 <simon.derr@bull.net>
#   Signed-off-by: Andrew Morton <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <maneesh@in.ibm.com>
#   
#   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 <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <maneesh@in.ibm.com>
#   
#   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 <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <maneesh@in.ibm.com>
#   
#   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 <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <maneesh@in.ibm.com>
#   
#   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 <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <maneesh@in.ibm.com>
#   
#   My fault, a bad typo in fs/sysfs/bin.c.
#   
#   Signed-off-by: Andrew Morton <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <maneesh@in.ibm.com>
#   
#   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 <akpm@osdl.org>
#   Signed-off-by: Greg Kroah-Hartman <greg@kroah.com>
# 
# 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 <greg@kroah.com>
# 
# 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 <greg@kroah.com>
# 
# 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 <greg@kroah.com>
# 
# 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 <greg@kroah.com>
# 
# 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 <linux/dnotify.h>
 #include <linux/kobject.h>
 #include <asm/uaccess.h>
+#include <asm/semaphore.h>
 
 #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 <linux/pagemap.h>
 #include <linux/namei.h>
 #include <linux/backing-dev.h>
+#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 <asm/atomic.h>
+
 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
 
