Skip to contentSkip to conversation

Driver Core patches for 2.6.10-rc1

PATCH New Greg KH 5 participants 25 messages Nov 1, 2004 – Nov 12, 2004

Subsystems (12)
lore
Share & tools
lore

Greg KH

author
#0 lore raw

PATCH 1/1 Driver Core patches for 2.6.10-rc1

Hi,

Here are some driver core patches for 2.6.10-rc1. The majority of them have all been in the -mm tree for a while. They contain:
- bugfix for the hotplug calls that was breaking all firmware downloads.
- sysfs backing patches. Finally Maneesh can rest...
- add a "driver" symlink in devices to make it easier to determine what driver is bound to a specific device.
- other bugfixes.

Please pull from:

	bk://kernel.bkbits.net/gregkh/linux/driver-2.6

thanks,

greg k-h

p.s. I'll send these as patches in response to this email to lkml for those who want to see them.

 drivers/base/bus.c             |   61 +++---
 drivers/base/power/main.c      |   11 -
 drivers/base/power/power.h     |    5 
 drivers/base/power/resume.c    |   16 +
 drivers/base/power/suspend.c   |   44 ++--
 drivers/input/input.c          |    2 
 drivers/input/serio/serio.c    |    7 
 drivers/pnp/pnpbios/core.c     |    2 
 drivers/s390/crypto/z90main.c  |    1 
 fs/sysfs/bin.c                 |   60 ++----
 fs/sysfs/dir.c                 |  379 +++++++++++++++++++++++++++++++++++------
 fs/sysfs/file.c                |   98 +++++-----
 fs/sysfs/group.c               |    4 
 fs/sysfs/inode.c               |  102 ++++++++---
 fs/sysfs/mount.c               |   12 +
 fs/sysfs/symlink.c             |   65 ++++---
 fs/sysfs/sysfs.h               |   81 ++++++++
 include/linux/device.h         |    4 
 include/linux/kmod.h           |    4 
 include/linux/kobject_uevent.h |    5 
 include/linux/sysfs.h          |   19 ++
 kernel/cpu.c                   |    1 
 kernel/kmod.c                  |   23 --
 kernel/sysctl.c                |    2 
 lib/kobject_uevent.c           |   48 +++--
 25 files changed, 742 insertions(+), 314 deletions(-)

-----

Dmitry Torokhov:
o Driver core: add driver symlink to device
o Driver core: add driver_probe_device
o Driver core: export device_attach

Kay Sievers:
o take me home, hotplug_path[]
o kobject: fix hotplug bug with seqnum

Maneesh Soni:
o sysfs backing store: stop pinning dentries/inodes for leaf entries
o sysfs backing store: use sysfs_dirent based tree in dir file operations
o sysfs backing store: use sysfs_dirent based tree in file removal
o sysfs backing store - add sysfs_direct structure
o fix oops with firmware loading
o sysfs backing store - prepare sysfs_file_operations helpers

Paul Mackerras:
o Fix deadlocks on dpm_sem

Simon Derr:
o Possible race in sysfs_read_file() and sysfs_write_file()
o Fix race in sysfs_read_file() and sysfs_write_file()

author
#1 lore raw

PATCH 1/1 Driver Core patches for 2.6.10-rc1

ChangeSet 1.2439, 2004/11/01 13:02:34-08:00, [email protected]

[PATCH] sysfs backing store - prepare sysfs_file_operations helpers

From: Maneesh Soni <[email protected]>

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.

 fs/sysfs/bin.c   |   14 +++++++-------
 fs/sysfs/file.c  |   24 ++++++++++++------------
 fs/sysfs/sysfs.h |   18 +++++++++++++++++-
 3 files changed, 36 insertions(+), 20 deletions(-)
fs/sysfs/bin.c +7 -7
diff -Nru a/fs/sysfs/bin.c b/fs/sysfs/bin.c
@@ -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->d_parent);
+	struct kobject *kobj = to_kobj(dentry);
 
 	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) 
fs/sysfs/file.c +12 -12
diff -Nru a/fs/sysfs/file.c b/fs/sysfs/file.c
@@ -67,7 +67,7 @@
 
 /**
  *	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 +75,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;
@@ -150,7 +150,7 @@
 	ssize_t retval = 0;
 
 	if (!*ppos) {
-		if ((retval = fill_read_buffer(file,buffer)))
+		if ((retval = fill_read_buffer(file->f_dentry,buffer)))
 			return retval;
 	}
 	pr_debug("%s: count = %d, ppos = %lld, buf = %s\n",
@@ -197,10 +197,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);
@@ -231,7 +231,7 @@
 
 	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;
 	return count;
@@ -240,7 +240,7 @@
 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;
@@ -321,8 +321,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) 
fs/sysfs/sysfs.h +17 -1
diff -Nru a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
@@ -16,14 +16,30 @@
 extern void sysfs_put_link(struct dentry *, struct nameidata *);
 extern struct rw_semaphore sysfs_rename_sem;
 
+static inline struct kobject * to_kobj(struct dentry * dentry)
+{
+	return ((struct kobject *) dentry->d_fsdata);
+}
+
+static inline struct attribute * to_attr(struct dentry * dentry)
+{
+	return ((struct attribute *) dentry->d_fsdata);
+}
+
+static inline struct bin_attribute * to_bin_attr(struct dentry * dentry)
+{
+	return ((struct bin_attribute *) dentry->d_fsdata);
+}
+
 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);
+		kobj = kobject_get(to_kobj(dentry));
 	spin_unlock(&dcache_lock);
 
 	return kobj;
 }
+
author
#2 lore raw

ChangeSet 1.2440, 2004/11/01 13:02:55-08:00, [email protected]

[PATCH] fix oops with firmware loading

From: Maneesh Soni <[email protected]>

My fault, a bad typo in fs/sysfs/bin.c.

 fs/sysfs/bin.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
fs/sysfs/bin.c +2 -2
diff -Nru a/fs/sysfs/bin.c b/fs/sysfs/bin.c
@@ -60,8 +60,8 @@
 static int
 flush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count)
 {
-	struct bin_attribute *attr = to_bin_attr(dentry->d_parent);
-	struct kobject *kobj = to_kobj(dentry);
+	struct bin_attribute *attr = to_bin_attr(dentry);
+	struct kobject *kobj = to_kobj(dentry->d_parent);
 
 	return attr->write(kobj, buffer, offset, count);
 }
author
#3 lore raw

ChangeSet 1.2441, 2004/11/01 13:03:13-08:00, [email protected]

[PATCH] sysfs backing store - add sysfs_direct structure

From: Maneesh Soni <[email protected]>

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.

 fs/sysfs/bin.c        |   14 ++++----
 fs/sysfs/dir.c        |   84 ++++++++++++++++++++++++++++++++++++++++++++------
 fs/sysfs/file.c       |   25 ++++++++------
 fs/sysfs/group.c      |    4 +-
 fs/sysfs/inode.c      |   10 ++++-
 fs/sysfs/mount.c      |    8 ++++
 fs/sysfs/symlink.c    |   39 ++++++++++++++++++++---
 fs/sysfs/sysfs.h      |   39 +++++++++++++++++++----
 include/linux/sysfs.h |   19 +++++++++++
 9 files changed, 204 insertions(+), 38 deletions(-)
fs/sysfs/bin.c +8 -6
diff -Nru a/fs/sysfs/bin.c b/fs/sysfs/bin.c
@@ -160,24 +160,26 @@
 {
 	struct dentry * dentry;
 	struct dentry * parent;
+	umode_t mode = (attr->attr.mode & S_IALLUGO) | S_IFREG;
 	int error = 0;
 
-	if (!kobj || !attr)
-		return -EINVAL;
+	BUG_ON(!kobj || !kobj->dentry || !attr);
 
 	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);
+		error = sysfs_create(dentry, mode, NULL);
 		if (!error) {
 			dentry->d_inode->i_size = attr->size;
 			dentry->d_inode->i_fop = &bin_fops;
+			error = sysfs_make_dirent(parent->d_fsdata, dentry,
+						  (void *) attr, mode,
+						  SYSFS_KOBJ_BIN_ATTR);
 		}
+		if (error)
+			d_drop(dentry);
 		dput(dentry);
 	} else
 		error = PTR_ERR(dentry);
fs/sysfs/dir.c +75 -9

135 lines not on this page · raw message

fs/sysfs/file.c +15 -10
diff -Nru a/fs/sysfs/file.c b/fs/sysfs/file.c
@@ -346,19 +346,22 @@
 };
 
 
-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);
+		error = sysfs_create(dentry, mode, init_file);
 		if (!error)
-			dentry->d_fsdata = (void *)attr;
+			error = sysfs_make_dirent(parent_sd, dentry,
+						(void *) attr, mode, type);
+		if (error)
+			d_drop(dentry);
 		dput(dentry);
 	} else
 		error = PTR_ERR(dentry);
@@ -375,9 +378,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 +413,8 @@
 			 */
 			dput(victim);
 			res = 0;
-		}
+		} else
+			d_drop(victim);
 		
 		/**
 		 * Drop the reference acquired from sysfs_get_dentry() above.
fs/sysfs/group.c +3 -1
diff -Nru a/fs/sysfs/group.c b/fs/sysfs/group.c
@@ -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);
fs/sysfs/inode.c +8 -2
diff -Nru a/fs/sysfs/inode.c b/fs/sysfs/inode.c
@@ -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 = {
@@ -91,6 +93,7 @@
 void sysfs_hash_and_remove(struct dentry * dir, const char * name)
 {
 	struct dentry * victim;
+	struct sysfs_dirent * sd;
 
 	down(&dir->d_inode->i_sem);
 	victim = sysfs_get_dentry(dir,name);
@@ -101,14 +104,17 @@
 			pr_debug("sysfs: Removing %s (%d)\n", victim->d_name.name,
 				 atomic_read(&victim->d_count));
 
+			sd = victim->d_fsdata;
 			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);
+				kobject_put(sd->s_element);
+			list_del_init(&sd->s_sibling);
 			simple_unlink(dir->d_inode,victim);
-		}
+		} else
+			d_drop(victim);
 		/*
 		 * Drop reference from sysfs_get_dentry() above.
 		 */
fs/sysfs/mount.c +8 -0
diff -Nru a/fs/sysfs/mount.c b/fs/sysfs/mount.c
@@ -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;
@@ -50,6 +57,7 @@
 		iput(inode);
 		return -ENOMEM;
 	}
+	root->d_fsdata = &sysfs_root;
 	sb->s_root = root;
 	return 0;
 }
fs/sysfs/symlink.c +35 -4
diff -Nru a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c
@@ -55,6 +55,36 @@
 	}
 }
 
+static int sysfs_add_link(struct dentry * dentry, char * name, struct kobject * target)
+{
+	struct sysfs_dirent * parent_sd = dentry->d_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, dentry, 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.
@@ -67,15 +97,16 @@
 	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);
+			error = sysfs_add_link(d, name, target);
+		if (error)
+			d_drop(d);
 		dput(d);
 	} else 
 		error = PTR_ERR(d);
fs/sysfs/sysfs.h +33 -6
diff -Nru a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
@@ -4,9 +4,11 @@
 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 **);
@@ -16,19 +18,27 @@
 extern void sysfs_put_link(struct dentry *, struct nameidata *);
 extern struct rw_semaphore sysfs_rename_sem;
 
+struct sysfs_symlink {
+	char * link_name;
+	struct kobject * target_kobj;
+};
+
 static inline struct kobject * to_kobj(struct dentry * dentry)
 {
-	return ((struct kobject *) dentry->d_fsdata);
+	struct sysfs_dirent * sd = dentry->d_fsdata;
+	return ((struct kobject *) sd->s_element);
 }
 
 static inline struct attribute * to_attr(struct dentry * dentry)
 {
-	return ((struct attribute *) dentry->d_fsdata);
+	struct sysfs_dirent * sd = dentry->d_fsdata;
+	return ((struct attribute *) sd->s_element);
 }
 
 static inline struct bin_attribute * to_bin_attr(struct dentry * dentry)
 {
-	return ((struct bin_attribute *) dentry->d_fsdata);
+	struct sysfs_dirent * sd = dentry->d_fsdata;
+	return ((struct bin_attribute *) sd->s_element);
 }
 
 static inline struct kobject *sysfs_get_kobject(struct dentry *dentry)
@@ -36,10 +46,27 @@
 	struct kobject * kobj = NULL;
 
 	spin_lock(&dcache_lock);
-	if (!d_unhashed(dentry))
-		kobj = kobject_get(to_kobj(dentry));
+	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);
 }
 
include/linux/sysfs.h +19 -0
diff -Nru a/include/linux/sysfs.h b/include/linux/sysfs.h
@@ -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
 
author
#4 lore raw

ChangeSet 1.2442, 2004/11/01 13:03:33-08:00, [email protected]

[PATCH] sysfs backing store: use sysfs_dirent based tree in file removal

From: Maneesh Soni <[email protected]>

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.

 fs/sysfs/dir.c   |   48 ++++++----------------------
 fs/sysfs/inode.c |   92 +++++++++++++++++++++++++++++++++++++++----------------
 fs/sysfs/sysfs.h |   18 ++++++++++
 3 files changed, 95 insertions(+), 63 deletions(-)
fs/sysfs/dir.c +11 -37
diff -Nru a/fs/sysfs/dir.c b/fs/sysfs/dir.c
@@ -19,7 +19,7 @@
 	if (sd) {
 		BUG_ON(sd->s_dentry != dentry);
 		sd->s_dentry = NULL;
-		release_sysfs_dirent(sd);
+		sysfs_put(sd);
 	}
 	iput(inode);
 }
@@ -61,7 +61,7 @@
 	sd->s_mode = mode;
 	sd->s_type = type;
 	sd->s_dentry = dentry;
-	dentry->d_fsdata = sd;
+	dentry->d_fsdata = sysfs_get(sd);
 	dentry->d_op = &sysfs_dentry_ops;
 
 	return 0;
@@ -146,6 +146,7 @@
 	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);
 
@@ -173,49 +174,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)) {
-			struct sysfs_dirent * sd = d->d_fsdata;
-			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(sd->s_element);
-			
-			list_del_init(&sd->s_sibling);
-			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);
fs/sysfs/inode.c +66 -26

116 lines not on this page · raw message

fs/sysfs/sysfs.h +18 -0
diff -Nru a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
@@ -14,6 +14,9 @@
 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;
@@ -68,5 +71,20 @@
 		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);
 }
 
author
#5 lore raw

ChangeSet 1.2443, 2004/11/01 13:03:53-08:00, [email protected]

[PATCH] sysfs backing store: use sysfs_dirent based tree in dir file operations

From: Maneesh Soni <[email protected]>

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.

 fs/sysfs/dir.c   |  141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 fs/sysfs/mount.c |    2 
 fs/sysfs/sysfs.h |    2 
 3 files changed, 143 insertions(+), 2 deletions(-)
fs/sysfs/dir.c +140 -1
diff -Nru a/fs/sysfs/dir.c b/fs/sysfs/dir.c
@@ -70,7 +70,7 @@
 static int init_dir(struct inode * inode)
 {
 	inode->i_op = &simple_dir_inode_operations;
-	inode->i_fop = &simple_dir_operations;
+	inode->i_fop = &sysfs_dir_operations;
 
 	/* directory inodes start off with i_nlink == 2 (for "." entry) */
 	inode->i_nlink++;
@@ -232,6 +232,145 @@
 
 	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);
+	sysfs_put(file->private_data);
+
+	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);
fs/sysfs/mount.c +1 -1
diff -Nru a/fs/sysfs/mount.c b/fs/sysfs/mount.c
@@ -43,7 +43,7 @@
 	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_fop = &sysfs_dir_operations;
 		/* directory inodes start off with i_nlink == 2 (for "." entry) */
 		inode->i_nlink++;	
 	} else {
fs/sysfs/sysfs.h +2 -0
diff -Nru a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
@@ -20,6 +20,8 @@
 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;
 
 struct sysfs_symlink {
 	char * link_name;
author
#6 lore raw

ChangeSet 1.2444, 2004/11/01 13:04:09-08:00, [email protected]

[PATCH] sysfs backing store: stop pinning dentries/inodes for leaf entries

From: Maneesh Soni <[email protected]>

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.

 fs/sysfs/bin.c     |   28 +-------------
 fs/sysfs/dir.c     |  106 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 fs/sysfs/file.c    |   25 +-----------
 fs/sysfs/mount.c   |    2 -
 fs/sysfs/symlink.c |   26 ++-----------
 fs/sysfs/sysfs.h   |    4 ++
 6 files changed, 115 insertions(+), 76 deletions(-)
fs/sysfs/bin.c +2 -26
diff -Nru a/fs/sysfs/bin.c b/fs/sysfs/bin.c
@@ -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,33 +158,9 @@
 
 int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr)
 {
-	struct dentry * dentry;
-	struct dentry * parent;
-	umode_t mode = (attr->attr.mode & S_IALLUGO) | S_IFREG;
-	int error = 0;
-
 	BUG_ON(!kobj || !kobj->dentry || !attr);
 
-	parent = kobj->dentry;
-
-	down(&parent->d_inode->i_sem);
-	dentry = sysfs_get_dentry(parent,attr->attr.name);
-	if (!IS_ERR(dentry)) {
-		error = sysfs_create(dentry, mode, NULL);
-		if (!error) {
-			dentry->d_inode->i_size = attr->size;
-			dentry->d_inode->i_fop = &bin_fops;
-			error = sysfs_make_dirent(parent->d_fsdata, dentry,
-						  (void *) attr, mode,
-						  SYSFS_KOBJ_BIN_ATTR);
-		}
-		if (error)
-			d_drop(dentry);
-		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);
 }
 
 
fs/sysfs/dir.c +100 -6

150 lines not on this page · raw message

fs/sysfs/file.c +3 -22
diff -Nru a/fs/sysfs/file.c b/fs/sysfs/file.c
@@ -9,15 +9,6 @@
 
 #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)
 
@@ -337,7 +328,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,
@@ -348,24 +339,14 @@
 
 int sysfs_add_file(struct dentry * dir, const struct attribute * attr, int type)
 {
-	struct dentry * dentry;
 	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, mode, init_file);
-		if (!error)
-			error = sysfs_make_dirent(parent_sd, dentry,
-						(void *) attr, mode, type);
-		if (error)
-			d_drop(dentry);
-		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;
 }
 
fs/sysfs/mount.c +1 -1
diff -Nru a/fs/sysfs/mount.c b/fs/sysfs/mount.c
@@ -42,7 +42,7 @@
 
 	inode = sysfs_new_inode(S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO);
 	if (inode) {
-		inode->i_op = &simple_dir_inode_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++;	
fs/sysfs/symlink.c +5 -21
diff -Nru a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c
@@ -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,9 +49,9 @@
 	}
 }
 
-static int sysfs_add_link(struct dentry * dentry, char * name, struct kobject * target)
+static int sysfs_add_link(struct dentry * parent, char * name, struct kobject * target)
 {
-	struct sysfs_dirent * parent_sd = dentry->d_parent->d_fsdata;
+	struct sysfs_dirent * parent_sd = parent->d_fsdata;
 	struct sysfs_symlink * sl;
 	int error = 0;
 
@@ -73,7 +67,7 @@
 	strcpy(sl->link_name, name);
 	sl->target_kobj = kobject_get(target);
 
-	error = sysfs_make_dirent(parent_sd, dentry, sl, S_IFLNK|S_IRWXUGO,
+	error = sysfs_make_dirent(parent_sd, NULL, sl, S_IFLNK|S_IRWXUGO,
 				SYSFS_KOBJ_LINK);
 	if (!error)
 		return 0;
@@ -94,22 +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)
-			error = sysfs_add_link(d, name, target);
-		if (error)
-			d_drop(d);
-		dput(d);
-	} else 
-		error = PTR_ERR(d);
+	error = sysfs_add_link(dentry, name, target);
 	up(&dentry->d_inode->i_sem);
 	return error;
 }
fs/sysfs/sysfs.h +4 -0
diff -Nru a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h
@@ -22,6 +22,10 @@
 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;
author
#7 lore raw

ChangeSet 1.2445, 2004/11/01 13:04:30-08:00, [email protected]

[PATCH] Fix race in sysfs_read_file() and sysfs_write_file()

From: Simon Derr <[email protected]>

- 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().

 fs/sysfs/file.c |   18 +++++++++++++++---
 1 files changed, 15 insertions(+), 3 deletions(-)
fs/sysfs/file.c +15 -3
diff -Nru a/fs/sysfs/file.c b/fs/sysfs/file.c
@@ -6,6 +6,7 @@
 #include <linux/dnotify.h>
 #include <linux/kobject.h>
 #include <asm/uaccess.h>
+#include <asm/semaphore.h>
 
 #include "sysfs.h"
 
@@ -53,6 +54,7 @@
 	loff_t			pos;
 	char			* page;
 	struct sysfs_ops	* ops;
+	struct semaphore	sem;
 };
 
 
@@ -106,6 +108,9 @@
 {
 	int error;
 
+	if (*ppos > buffer->count)
+		return 0;
+
 	if (count > (buffer->count - *ppos))
 		count = buffer->count - *ppos;
 
@@ -140,13 +145,17 @@
 	struct sysfs_buffer * buffer = file->private_data;
 	ssize_t retval = 0;
 
-	if (!*ppos) {
+	down(&buffer->sem);
+	if ((!*ppos) || (!buffer->page)) {
 		if ((retval = fill_read_buffer(file->f_dentry,buffer)))
-			return retval;
+			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;
 }
 
 
@@ -220,11 +229,13 @@
 {
 	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->f_dentry,buffer,count);
 	if (count > 0)
 		*ppos += count;
+	up(&buffer->sem);
 	return count;
 }
 
@@ -287,6 +298,7 @@
 	buffer = kmalloc(sizeof(struct sysfs_buffer),GFP_KERNEL);
 	if (buffer) {
 		memset(buffer,0,sizeof(struct sysfs_buffer));
+		init_MUTEX(&buffer->sem);
 		buffer->ops = ops;
 		file->private_data = buffer;
 	} else
author
#8 lore raw

ChangeSet 1.2446, 2004/11/01 13:04:46-08:00, [email protected]

[PATCH] Possible race in sysfs_read_file() and sysfs_write_file()

From: Simon Derr <[email protected]>

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)

 fs/sysfs/file.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletion(-)
fs/sysfs/file.c +5 -1
diff -Nru a/fs/sysfs/file.c b/fs/sysfs/file.c
@@ -55,6 +55,7 @@
 	char			* page;
 	struct sysfs_ops	* ops;
 	struct semaphore	sem;
+	int			needs_read_fill;
 };
 
 
@@ -82,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;
@@ -146,7 +148,7 @@
 	ssize_t retval = 0;
 
 	down(&buffer->sem);
-	if ((!*ppos) || (!buffer->page)) {
+	if (buffer->needs_read_fill) {
 		if ((retval = fill_read_buffer(file->f_dentry,buffer)))
 			goto out;
 	}
@@ -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;
 }
 
@@ -299,6 +302,7 @@
 	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
author
#9 lore raw

ChangeSet 1.2447, 2004/11/01 13:05:06-08:00, [email protected]

[PATCH] Fix deadlocks on dpm_sem

From: Paul Mackerras <[email protected]>

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 <[email protected]>

 drivers/base/power/main.c    |   11 +++++-----
 drivers/base/power/power.h   |    5 ++++
 drivers/base/power/resume.c  |   16 ++++++++++++---
 drivers/base/power/suspend.c |   44 ++++++++++++++++++++++++-------------------
 4 files changed, 49 insertions(+), 27 deletions(-)
drivers/base/power/main.c +6 -5
diff -Nru a/drivers/base/power/main.c b/drivers/base/power/main.c
@@ -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);
 }
 
 
drivers/base/power/power.h +5 -0
diff -Nru a/drivers/base/power/power.h b/drivers/base/power/power.h
@@ -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;
drivers/base/power/resume.c +13 -3
diff -Nru a/drivers/base/power/resume.c b/drivers/base/power/resume.c
@@ -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);
 	}
 }
 
drivers/base/power/suspend.c +25 -19

65 lines not on this page · raw message

author
#10 lore raw

ChangeSet 1.2448, 2004/11/01 13:05:24-08:00, [email protected]

[PATCH] Driver core: export device_attach

Driver core: make device_attach() global and export it and
driver_attach() so subsystems can have finer control over binding process.

 drivers/base/bus.c     |    4 +++-
 include/linux/device.h |    1 +
 2 files changed, 4 insertions(+), 1 deletion(-)
drivers/base/bus.c +3 -1
diff -Nru a/drivers/base/bus.c b/drivers/base/bus.c
@@ -288,7 +288,7 @@
  *	Walk the list of drivers that the bus has and call bus_match()
  *	for each pair. If a compatible pair is found, break out and return.
  */
-static int device_attach(struct device * dev)
+int device_attach(struct device * dev)
 {
  	struct bus_type * bus = dev->bus;
 	struct list_head * entry;
@@ -728,6 +728,8 @@
 
 EXPORT_SYMBOL_GPL(device_bind_driver);
 EXPORT_SYMBOL_GPL(device_release_driver);
+EXPORT_SYMBOL_GPL(device_attach);
+EXPORT_SYMBOL_GPL(driver_attach);
 
 EXPORT_SYMBOL_GPL(bus_add_device);
 EXPORT_SYMBOL_GPL(bus_remove_device);
include/linux/device.h +1 -0
diff -Nru a/include/linux/device.h b/include/linux/device.h
@@ -327,6 +327,7 @@
  */
 extern void device_bind_driver(struct device * dev);
 extern void device_release_driver(struct device * dev);
+extern int  device_attach(struct device * dev);
 extern void driver_attach(struct device_driver * drv);
 
 
author
#11 lore raw

ChangeSet 1.2449, 2004/11/01 13:05:43-08:00, [email protected]

[PATCH] Driver core: add driver_probe_device

Driver core: rename bus_match into driver_probe_device and export
it so subsystems can bind an individual device to a specific driver without getting involved with driver core internals.

 drivers/base/bus.c     |   55 +++++++++++++++++++++++++------------------------
 include/linux/device.h |    3 +-
 2 files changed, 31 insertions(+), 27 deletions(-)
drivers/base/bus.c +29 -26
diff -Nru a/drivers/base/bus.c b/drivers/base/bus.c
@@ -250,34 +250,35 @@
 
 
 /**
- *	bus_match - check compatibility between device & driver.
- *	@dev:	device.
+ *	driver_probe_device - attempt to bind device & driver.
  *	@drv:	driver.
+ *	@dev:	device.
  *
- *	First, we call the bus's match function, which should compare
- *	the device IDs the driver supports with the device IDs of the
- *	device. Note we don't do this ourselves because we don't know
- *	the format of the ID structures, nor what is to be considered
- *	a match and what is not.
+ *	First, we call the bus's match function, if one present, which
+ *	should compare the device IDs the driver supports with the
+ *	device IDs of the device. Note we don't do this ourselves
+ *	because we don't know the format of the ID structures, nor what
+ *	is to be considered a match and what is not.
  *
  *	If we find a match, we call @drv->probe(@dev) if it exists, and
- *	call attach() above.
+ *	call device_bind_driver() above.
  */
-static int bus_match(struct device * dev, struct device_driver * drv)
+int driver_probe_device(struct device_driver * drv, struct device * dev)
 {
-	int error = -ENODEV;
-	if (dev->bus->match(dev, drv)) {
-		dev->driver = drv;
-		if (drv->probe) {
-			if ((error = drv->probe(dev))) {
-				dev->driver = NULL;
-				return error;
-			}
+	if (drv->bus->match && !drv->bus->match(dev, drv))
+		return -ENODEV;
+
+	dev->driver = drv;
+	if (drv->probe) {
+		int error = drv->probe(dev);
+		if (error) {
+			dev->driver = NULL;
+			return error;
 		}
-		device_bind_driver(dev);
-		error = 0;
 	}
-	return error;
+
+	device_bind_driver(dev);
+	return 0;
 }
 
 
@@ -285,8 +286,9 @@
  *	device_attach - try to attach device to a driver.
  *	@dev:	device.
  *
- *	Walk the list of drivers that the bus has and call bus_match()
- *	for each pair. If a compatible pair is found, break out and return.
+ *	Walk the list of drivers that the bus has and call
+ *	driver_probe_device() for each pair. If a compatible
+ *	pair is found, break out and return.
  */
 int device_attach(struct device * dev)
 {
@@ -302,7 +304,7 @@
 	if (bus->match) {
 		list_for_each(entry, &bus->drivers.list) {
 			struct device_driver * drv = to_drv(entry);
-			error = bus_match(dev, drv);
+			error = driver_probe_device(drv, dev);
 			if (!error)
 				/* success, driver matched */
 				return 1;
@@ -327,8 +329,8 @@
  *	If bus_match() returns 0 and the @dev->driver is set, we've found
  *	a compatible pair.
  *
- *	Note that we ignore the -ENODEV error from bus_match(), since it's
- *	perfectly valid for a driver not to bind to any devices.
+ *	Note that we ignore the -ENODEV error from driver_probe_device(),
+ *	since it's perfectly valid for a driver not to bind to any devices.
  */
 void driver_attach(struct device_driver * drv)
 {
@@ -342,7 +344,7 @@
 	list_for_each(entry, &bus->devices.list) {
 		struct device * dev = container_of(entry, struct device, bus_list);
 		if (!dev->driver) {
-			error = bus_match(dev, drv);
+			error = driver_probe_device(drv, dev);
 			if (error && (error != -ENODEV))
 				/* driver matched but the probe failed */
 				printk(KERN_WARNING
@@ -726,6 +728,7 @@
 EXPORT_SYMBOL_GPL(bus_for_each_dev);
 EXPORT_SYMBOL_GPL(bus_for_each_drv);
 
+EXPORT_SYMBOL_GPL(driver_probe_device);
 EXPORT_SYMBOL_GPL(device_bind_driver);
 EXPORT_SYMBOL_GPL(device_release_driver);
 EXPORT_SYMBOL_GPL(device_attach);
include/linux/device.h +2 -1
diff -Nru a/include/linux/device.h b/include/linux/device.h
@@ -322,9 +322,10 @@
 		     int (*fn)(struct device *, void *));
 
 /*
- * Manual binding of a device to driver. See drivers/base/bus.c 
+ * Manual binding of a device to driver. See drivers/base/bus.c
  * for information on use.
  */
+extern int  driver_probe_device(struct device_driver * drv, struct device * dev);
 extern void device_bind_driver(struct device * dev);
 extern void device_release_driver(struct device * dev);
 extern int  device_attach(struct device * dev);
author
#12 lore raw

ChangeSet 1.2450, 2004/11/01 13:06:02-08:00, [email protected]

[PATCH] kobject: fix hotplug bug with seqnum

Quoted 41 lines · Kay Sievers
Quoted 27 lines · Kay Sievers
Quoted 26 lines · Kay Sievers
Quoted 11 lines · Greg KH
Quoted 10 lines · Kay Sievers
Quoted 3 lines

But there might still be a problem. With this change, the sequence number is not sent out the kevent message. Kay, do you think this is an issue? I don't think we can get netlink messages out of order, right?

Right, especially not the events with the same DEVPATH, like "remove" beating an "add". But I'm not sure if the number isn't useful. Whatever we may do with the hotplug over netlink in the future, we will only have /sbin/hotplug for the early boot and it may be nice to know, what events we have already handled...

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

I'll hold off on applying this patch until we figure this out...

How about just reserving 20 bytes for the number (u64 will never be more than that), save the pointer to that field, and fill the number in later?

Ah, something like this instead? I like it, it's even smaller than the previous patch. Compile tested only...

I like that. How about the following. It will keep the buffer clean from random chars, cause the kevent does not have the vector and relies on the '\0' to separate the strings from each other.
I've tested it. The netlink-hotplug message looks like this:

Quoted 6 lines
Quoted 1 line

recv(3, "remove@/class/input/mouse2\0ACTION=remove\0DEVPATH=/class/input/mouse2\0SUBSYSTEM=input\0SEQNUM=961 \0", 1024, 0) = 113

Hmm, these trailing spaces are just bad, sorry. I'll better pass the envp array over to send_uevent() and clean up the keys while copying the env values into the skb buffer. This will make the event payload more safe too. So your first version looks better.

How about this? We copy over key by key into the skb buffer and the netlink message can get the envp array without depending on a single continuous buffer.

The netlink message looks nice like this now:

Quoted 13 lines

recv(3, "
add@/devices/pci0000:00/0000:00:1d.1/usb3/3-2/3-2:1.0\0 HOME=/\0
PATH=/sbin:/bin:/usr/sbin:/usr/bin\0
ACTION=add\0 DEVPATH=/devices/pci0000:00/0000:00:1d.1/usb3/3-2/3-2:1.0\0 SUBSYSTEM=usb\0
SEQNUM=991\0
DEVICE=/proc/bus/usb/003/008\0 PRODUCT=46d/c03e/2000\0
TYPE=0/0/0\0
INTERFACE=3/1/2\0
", 1024, 0) = 268

Here is an improved version that uses skb_put() to fill the skb buffer, instead of trimming the buffer to the final size after we've copied over all keys.

 lib/kobject_uevent.c |   47 ++++++++++++++++++++++++++++++-----------------
 1 files changed, 30 insertions(+), 17 deletions(-)
lib/kobject_uevent.c +30 -17
diff -Nru a/lib/kobject_uevent.c b/lib/kobject_uevent.c
@@ -23,6 +23,9 @@
 #include <linux/kobject.h>
 #include <net/sock.h>
 
+#define BUFFER_SIZE	1024	/* buffer for the hotplug env */
+#define NUM_ENVP	32	/* number of env pointers */
+
 #if defined(CONFIG_KOBJECT_UEVENT) || defined(CONFIG_HOTPLUG)
 static char *action_to_string(enum kobject_action action)
 {
@@ -53,12 +56,11 @@
  *
  * @signal: signal name
  * @obj: object path (kobject)
- * @buf: buffer used to pass auxiliary data like the hotplug environment
- * @buflen:
- * gfp_mask:
+ * @envp: possible hotplug environment to pass with the message
+ * @gfp_mask:
  */
-static int send_uevent(const char *signal, const char *obj, const void *buf,
-			int buflen, int gfp_mask)
+static int send_uevent(const char *signal, const char *obj,
+		       char **envp, int gfp_mask)
 {
 	struct sk_buff *skb;
 	char *pos;
@@ -69,16 +71,25 @@
 
 	len = strlen(signal) + 1;
 	len += strlen(obj) + 1;
-	len += buflen;
 
-	skb = alloc_skb(len, gfp_mask);
+	/* allocate buffer with the maximum possible message size */
+	skb = alloc_skb(len + BUFFER_SIZE, gfp_mask);
 	if (!skb)
 		return -ENOMEM;
 
 	pos = skb_put(skb, len);
+	sprintf(pos, "%s@%s", signal, obj);
 
-	pos += sprintf(pos, "%s@%s", signal, obj) + 1;
-	memcpy(pos, buf, buflen);
+	/* copy the environment key by key to our continuous buffer */
+	if (envp) {
+		int i;
+
+		for (i = 2; envp[i]; i++) {
+			len = strlen(envp[i]) + 1;
+			pos = skb_put(skb, len);
+			strcpy(pos, envp[i]);
+		}
+	}
 
 	return netlink_broadcast(uevent_sock, skb, 0, 1, gfp_mask);
 }
@@ -107,10 +118,10 @@
 		if (!attrpath)
 			goto exit;
 		sprintf(attrpath, "%s/%s", path, attr->name);
-		rc = send_uevent(signal, attrpath, NULL, 0, gfp_mask);
+		rc = send_uevent(signal, attrpath, NULL, gfp_mask);
 		kfree(attrpath);
 	} else {
-		rc = send_uevent(signal, path, NULL, 0, gfp_mask);
+		rc = send_uevent(signal, path, NULL, gfp_mask);
 	}
 
 exit:
@@ -169,8 +180,6 @@
 u64 hotplug_seqnum;
 static spinlock_t sequence_lock = SPIN_LOCK_UNLOCKED;
 
-#define BUFFER_SIZE	1024	/* should be enough memory for the env */
-#define NUM_ENVP	32	/* number of env pointers */
 /**
  * kobject_hotplug - notify userspace by executing /sbin/hotplug
  *
@@ -182,6 +191,7 @@
 	char *argv [3];
 	char **envp = NULL;
 	char *buffer = NULL;
+	char *seq_buff;
 	char *scratch;
 	int i = 0;
 	int retval;
@@ -258,6 +268,11 @@
 	envp [i++] = scratch;
 	scratch += sprintf(scratch, "SUBSYSTEM=%s", name) + 1;
 
+	/* reserve space for the sequence,
+	 * put the real one in after the hotplug call */
+	envp[i++] = seq_buff = scratch;
+	scratch += strlen("SEQNUM=18446744073709551616") + 1;
+
 	if (hotplug_ops->hotplug) {
 		/* have the kset specific function add its stuff */
 		retval = hotplug_ops->hotplug (kset, kobj,
@@ -273,15 +288,13 @@
 	spin_lock(&sequence_lock);
 	seq = ++hotplug_seqnum;
 	spin_unlock(&sequence_lock);
-
-	envp [i++] = scratch;
-	scratch += sprintf(scratch, "SEQNUM=%lld", (long long)seq) + 1;
+	sprintf(seq_buff, "SEQNUM=%lld", (long long)seq);
 
 	pr_debug ("%s: %s %s seq=%lld %s %s %s %s %s\n",
 		  __FUNCTION__, argv[0], argv[1], (long long)seq,
 		  envp[0], envp[1], envp[2], envp[3], envp[4]);
 
-	send_uevent(action_string, kobj_path, buffer, scratch - buffer, GFP_KERNEL);
+	send_uevent(action_string, kobj_path, envp, GFP_KERNEL);
 
 	if (!hotplug_path[0])
 		goto exit;
author
#13 lore raw

ChangeSet 1.2451, 2004/11/01 13:06:31-08:00, [email protected]

[PATCH] Driver core: add driver symlink to device

Driver core: when binding device to a driver create "driver"
symlink in device's directory. Rename serio's "driver" attribute to "drvctl" (write-only)

 drivers/base/bus.c          |    2 ++
 drivers/input/serio/serio.c |    7 +------
 2 files changed, 3 insertions(+), 6 deletions(-)
drivers/base/bus.c +2 -0
diff -Nru a/drivers/base/bus.c b/drivers/base/bus.c
@@ -246,6 +246,7 @@
 	list_add_tail(&dev->driver_list, &dev->driver->devices);
 	sysfs_create_link(&dev->driver->kobj, &dev->kobj,
 			  kobject_name(&dev->kobj));
+	sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
 }
 
 
@@ -370,6 +371,7 @@
 	struct device_driver * drv = dev->driver;
 	if (drv) {
 		sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
+		sysfs_remove_link(&dev->kobj, "driver");
 		list_del_init(&dev->driver_list);
 		device_detach_shutdown(dev);
 		if (drv->remove)
drivers/input/serio/serio.c +1 -6
diff -Nru a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c
@@ -246,11 +246,6 @@
 	return sprintf(buf, "%s\n", serio->name);
 }
 
-static ssize_t serio_show_driver(struct device *dev, char *buf)
-{
-	return sprintf(buf, "%s\n", dev->driver ? dev->driver->name : "(none)");
-}
-
 static ssize_t serio_rebind_driver(struct device *dev, const char *buf, size_t count)
 {
 	struct serio *serio = to_serio_port(dev);
@@ -307,7 +302,7 @@
 
 static struct device_attribute serio_device_attrs[] = {
 	__ATTR(description, S_IRUGO, serio_show_description, NULL),
-	__ATTR(driver, S_IWUSR | S_IRUGO, serio_show_driver, serio_rebind_driver),
+	__ATTR(drvctl, S_IWUSR, NULL, serio_rebind_driver),
 	__ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode),
 	__ATTR_NULL
 };
author
#14 lore raw

ChangeSet 1.2452, 2004/11/01 13:06:49-08:00, [email protected]

[PATCH] take me home, hotplug_path[]

Move hotplug_path[] out of kmod.[ch] to kobject_uevent.[ch] where it belongs now. At some time in the future we should fix the remaining bad hotplug calls (no SEQNUM, no netlink uevent):

./drivers/input/input.c (no DEVPATH on some hotplug events!) ./drivers/pnp/pnpbios/core.c ./drivers/s390/crypto/z90main.c

 drivers/input/input.c          |    2 +-
 drivers/pnp/pnpbios/core.c     |    2 +-
 drivers/s390/crypto/z90main.c  |    1 +
 include/linux/kmod.h           |    4 ----
 include/linux/kobject_uevent.h |    5 +++++
 kernel/cpu.c                   |    1 -
 kernel/kmod.c                  |   23 -----------------------
 kernel/sysctl.c                |    2 +-
 lib/kobject_uevent.c           |    1 +
 9 files changed, 10 insertions(+), 31 deletions(-)
drivers/input/input.c +1 -1
diff -Nru a/drivers/input/input.c b/drivers/input/input.c
@@ -19,7 +19,7 @@
 #include <linux/major.h>
 #include <linux/pm.h>
 #include <linux/proc_fs.h>
-#include <linux/kmod.h>
+#include <linux/kobject_uevent.h>
 #include <linux/interrupt.h>
 #include <linux/poll.h>
 #include <linux/device.h>
drivers/pnp/pnpbios/core.c +1 -1
diff -Nru a/drivers/pnp/pnpbios/core.c b/drivers/pnp/pnpbios/core.c
@@ -56,7 +56,7 @@
 #include <linux/mm.h>
 #include <linux/smp.h>
 #include <linux/slab.h>
-#include <linux/kmod.h>
+#include <linux/kobject_uevent.h>
 #include <linux/completion.h>
 #include <linux/spinlock.h>
 #include <linux/dmi.h>
drivers/s390/crypto/z90main.c +1 -0
diff -Nru a/drivers/s390/crypto/z90main.c b/drivers/s390/crypto/z90main.c
@@ -33,6 +33,7 @@
 #include <linux/ioctl32.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
+#include <linux/kobject_uevent.h>
 #include <linux/proc_fs.h>
 #include <linux/syscalls.h>
 #include <linux/version.h>
include/linux/kmod.h +0 -4
diff -Nru a/include/linux/kmod.h b/include/linux/kmod.h
@@ -37,8 +37,4 @@
 extern int call_usermodehelper(char *path, char *argv[], char *envp[], int wait);
 extern void usermodehelper_init(void);
 
-#ifdef CONFIG_HOTPLUG
-extern char hotplug_path [];
-#endif
-
 #endif /* __LINUX_KMOD_H__ */
include/linux/kobject_uevent.h +5 -0
diff -Nru a/include/linux/kobject_uevent.h b/include/linux/kobject_uevent.h
@@ -11,6 +11,11 @@
 #ifndef _KOBJECT_EVENT_H_
 #define _KOBJECT_EVENT_H_
 
+#define HOTPLUG_PATH_LEN	256
+
+/* path to the hotplug userspace helper executed on an event */
+extern char hotplug_path[];
+
 /*
  * If you add an action here, you must also add the proper string to the
  * lib/kobject_uevent.c file.
kernel/cpu.c +0 -1
diff -Nru a/kernel/cpu.c b/kernel/cpu.c
@@ -11,7 +11,6 @@
 #include <linux/unistd.h>
 #include <linux/cpu.h>
 #include <linux/module.h>
-#include <linux/kmod.h>		/* for hotplug_path */
 #include <linux/kthread.h>
 #include <linux/stop_machine.h>
 #include <asm/semaphore.h>
kernel/kmod.c +0 -23
diff -Nru a/kernel/kmod.c b/kernel/kmod.c
@@ -115,29 +115,6 @@
 EXPORT_SYMBOL(request_module);
 #endif /* CONFIG_KMOD */
 
-#ifdef CONFIG_HOTPLUG
-/*
-	hotplug path is set via /proc/sys
-	invoked by hotplug-aware bus drivers,
-	with call_usermodehelper
-
-	argv [0] = hotplug_path;
-	argv [1] = "usb", "scsi", "pci", "network", etc;
-	... plus optional type-specific parameters
-	argv [n] = 0;
-
-	envp [*] = HOME, PATH; optional type-specific parameters
-
-	a hotplug bus should invoke this for device add/remove
-	events.  the command is expected to load drivers when
-	necessary, and may perform additional system setup.
-*/
-char hotplug_path[KMOD_PATH_LEN] = "/sbin/hotplug";
-
-EXPORT_SYMBOL(hotplug_path);
-
-#endif /* CONFIG_HOTPLUG */
-
 struct subprocess_info {
 	struct completion *complete;
 	char *path;
kernel/sysctl.c +1 -1
diff -Nru a/kernel/sysctl.c b/kernel/sysctl.c
@@ -394,7 +394,7 @@
 		.ctl_name	= KERN_HOTPLUG,
 		.procname	= "hotplug",
 		.data		= &hotplug_path,
-		.maxlen		= KMOD_PATH_LEN,
+		.maxlen		= HOTPLUG_PATH_LEN,
 		.mode		= 0644,
 		.proc_handler	= &proc_dostring,
 		.strategy	= &sysctl_string,
lib/kobject_uevent.c +1 -0
diff -Nru a/lib/kobject_uevent.c b/lib/kobject_uevent.c
@@ -177,6 +177,7 @@
 
 
 #ifdef CONFIG_HOTPLUG
+char hotplug_path[HOTPLUG_PATH_LEN] = "/sbin/hotplug";
 u64 hotplug_seqnum;
 static spinlock_t sequence_lock = SPIN_LOCK_UNLOCKED;
 
#15 lore raw
In reply to Greg KH
Quoted 6 lines · Greg KH

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.

Does this mean that a device driver can have its suspend or resume methods called in the middle of a probe or remove on a different CPU ? (note: x86 APM does not freeze all processes last time I checked...)

If yes, has anyone audited the drivers to ensure that they're correct in respect of this?

Signature · 4 lines
Russell King
 Linux kernel    2.6 ARM Linux   - http://www.arm.linux.org.uk/
 maintainer of:  2.6 PCMCIA      - http://pcmcia.arm.linux.org.uk/
                 2.6 Serial core
#16 lore raw
Quoted 8 lines · Russell King
Quoted 6 lines · Greg KH

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.

Quoted 6 lines

Does this mean that a device driver can have its suspend or resume methods called in the middle of a probe or remove on a different CPU ? (note: x86 APM does not freeze all processes last time I checked...)

If yes, has anyone audited the drivers to ensure that they're correct in respect of this?

I'll repost the above question since it's of fundamental importance.

Thanks.

Signature · 4 lines
Russell King
 Linux kernel    2.6 ARM Linux   - http://www.arm.linux.org.uk/
 maintainer of:  2.6 PCMCIA      - http://pcmcia.arm.linux.org.uk/
                 2.6 Serial core
author
#17 lore raw
Quoted 9 lines · Russell King
Quoted 8 lines · Russell King
Quoted 6 lines · Greg KH

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.

Quoted 8 lines
Quoted 6 lines

Does this mean that a device driver can have its suspend or resume methods called in the middle of a probe or remove on a different CPU ? (note: x86 APM does not freeze all processes last time I checked...)

If yes, has anyone audited the drivers to ensure that they're correct in respect of this?

I'll repost the above question since it's of fundamental importance.

Paul sent in this change, so I'll let him address this.

thanks,

greg k-h

#18 lore raw
Quoted 11 lines · Greg KH
Quoted 10 lines · Russell King
Quoted 7 lines · Russell King

Does this mean that a device driver can have its suspend or resume methods called in the middle of a probe or remove on a different CPU ? (note: x86 APM does not freeze all processes last time I checked...)

If yes, has anyone audited the drivers to ensure that they're correct in respect of this?

I'll repost the above question since it's of fundamental importance.

I didn't change anything that would affect that.

Unless I am misunderstanding the question, it has always been the case that a bus's suspend or resume method could be called concurrently with a driver probe call. device_add() calls device_pm_add() followed by bus_add_device(). device_pm_add() puts the device on the dpm_active list, and once it is there, device_suspend will see it, and will call the bus's suspend function. bus_add_device() calls device_attach() which either calls device_bind_driver() or driver_probe_device(), and driver_probe_device() calls the driver's probe function.

So we can get a driver's probe method called concurrently with its bus's suspend or resume method. Whether the driver's suspend/resume method gets called depends on the bus-specific code. In the case of PCI, it looks like pci_device_suspend won't call the driver's suspend function unless pci_dev->driver is set. That gets set in pci_device_probe_{dynamic,static} after the drv->probe() call, so that probably mostly excludes concurrency with the suspend/resume calls (except that there is no memory barrier in there). I don't know whether driver_probe_device() can ever get called for PCI devices. If it can I then don't know how/where pci_dev->driver would get set (as opposed to dev->driver).

I would stress that none of this analysis is any different before or after my patch though. The question (of concurrency between probe/remove and suspend/resume) is an interesting one, but it's not something that's affected one way or the other by my patch.

Greg, do you have an analysis (or set of rules) for concurrency between the various driver functions?

Paul.

#19 lore raw
Quoted 2 lines · Paul Mackerras

So we can get a driver's probe method called concurrently with its bus's suspend or resume method.

If correct, we probably have rather a lot of buggy drivers, because I certainly was not aware that this could happen. I suspect the average driver writer also would not be aware of that.

Signature · 4 lines
Russell King
 Linux kernel    2.6 ARM Linux   - http://www.arm.linux.org.uk/
 maintainer of:  2.6 PCMCIA      - http://pcmcia.arm.linux.org.uk/
                 2.6 Serial core
#20 lore raw
Quoted 7 lines · Russell King
Quoted 2 lines · Paul Mackerras

So we can get a driver's probe method called concurrently with its bus's suspend or resume method.

If correct, we probably have rather a lot of buggy drivers, because I certainly was not aware that this could happen. I suspect the average driver writer also would not be aware of that.

No doubt. I'd still like to hear from Greg or Pat about what the concurrency rules are supposed to be, or were intended to be.

Note also that I said the *bus's* suspend/resume method. For PCI, we have some protection since the PCI suspend/resume methods won't call the individual pci driver's suspend/resume until pci_dev->driver is set, which happens after the pci driver's probe routine has returned (but of course the store to pci_dev->driver can get reordered on some architectures since there is no barrier).

However, pci_dev->driver only gets cleared *after* the driver's remove method returns. Thus it would be quite possible for a PCI device to have its suspend/resume methods called while another CPU is in its remove method.

I think that what has saved us to some extent is that we only do suspend/resume on UP machines so far.

Regards,
Paul.

#21 lore raw

Hi.

Quoted 8 lines · Paul Mackerras

Russell King writes:
However, pci_dev->driver only gets cleared *after* the driver's remove method returns. Thus it would be quite possible for a PCI device to have its suspend/resume methods called while another CPU is in its remove method.

I think that what has saved us to some extent is that we only do suspend/resume on UP machines so far.

I'm suspending and resuming all the time using HT. FWIW, I think it's more likely that you're not seeing the issue for two reasons.

1) User space has been frozen when the suspend/resume methods are called (I'm right in assuming that only swsusp/pmdisk, suspend-to-ram and suspend2 would be doing suspends, aren't I?).
2) People tend to wait until the machine has powered down before unplugging things, and plug things in before powering on.

Please excuse me if I'm speaking nonsense :>

Regards,

Nigel

Signature · 7 lines
Nigel Cunningham
Pastoral Worker
Christian Reformed Church of Tuggeranong
PO Box 1004, Tuggeranong, ACT 2901

You see, at just the right time, when we were still powerless, Christ
died for the ungodly.		-- Romans 5:6
#22 lore raw
In reply to Paul Mackerras
Quoted 2 lines · Paul Mackerras

I think that what has saved us to some extent is that we only do suspend/resume on UP machines so far.

and probably without kernel preemption enabled. Kernel preemption should in theory at least open the same can of worms in this area as SMP does.

Signature · 4 lines
Russell King
 Linux kernel    2.6 ARM Linux   - http://www.arm.linux.org.uk/
 maintainer of:  2.6 PCMCIA      - http://pcmcia.arm.linux.org.uk/
                 2.6 Serial core
author
#23 lore raw
In reply to Paul Mackerras
Quoted 6 lines · Paul Mackerras
Quoted 4 lines · Russell King
Quoted 2 lines · Paul Mackerras

So we can get a driver's probe method called concurrently with its bus's suspend or resume method.

Quoted 6 lines
Quoted 3 lines

If correct, we probably have rather a lot of buggy drivers, because I certainly was not aware that this could happen. I suspect the average driver writer also would not be aware of that.

No doubt. I'd still like to hear from Greg or Pat about what the concurrency rules are supposed to be, or were intended to be.

They are still under flux as to what they should be :)

I'm currently working on reworking all of the locking in the driver core, to document, and fix the number of issues that suspend, and manual disconnect, and manual attach are causing. It should also fix the issue that I know you want Russell, namely adding a new device from a probe() callback.

See the other threads about manual disconnect in the driver model on lkml for some other details. I'll post more here when I have some working code.

thanks,

greg k-h

#24 lore raw
In reply to Greg KH

fs/sysfs/bin.c line 163

Quoted 8 lines · Greg KH

diff -Nru a/fs/sysfs/bin.c b/fs/sysfs/bin.c

fs/sysfs/bin.c +1 -0
@@ -160,24 +160,26 @@
 {
 	struct dentry * dentry;
 	struct dentry * parent;
+	umode_t mode = (attr->attr.mode & S_IALLUGO) | S_IFREG;
                        ^^^^^^^^^^^^^^^

fs/sysfs/bin.c line 166

Quoted 5 lines
fs/sysfs/bin.c +1 -2
 	int error = 0;
 
-	if (!kobj || !attr)
-		return -EINVAL;
+	BUG_ON(!kobj || !kobj->dentry || !attr);
                                         ^^^^^

attr is already dereferenced, maybe this check should be moved before it.

Signature · 1 line
pozsy