diff options
-rw-r--r-- | Documentation/kref.txt | 216 | ||||
-rw-r--r-- | drivers/base/class.c | 2 | ||||
-rw-r--r-- | drivers/base/core.c | 3 | ||||
-rw-r--r-- | drivers/base/firmware_class.c | 3 | ||||
-rw-r--r-- | drivers/base/platform.c | 1 | ||||
-rw-r--r-- | drivers/usb/host/hc_crisv10.c | 1 | ||||
-rw-r--r-- | fs/partitions/check.c | 2 | ||||
-rw-r--r-- | fs/sysfs/file.c | 35 | ||||
-rw-r--r-- | include/linux/debugfs.h | 13 | ||||
-rw-r--r-- | include/linux/sysfs.h | 7 | ||||
-rw-r--r-- | lib/kobject.c | 7 | ||||
-rw-r--r-- | net/bridge/br_sysfs_if.c | 2 | ||||
-rwxr-xr-x | scripts/ver_linux | 2 |
13 files changed, 286 insertions, 8 deletions
diff --git a/Documentation/kref.txt b/Documentation/kref.txt new file mode 100644 index 0000000..42fe284 --- /dev/null +++ b/Documentation/kref.txt @@ -0,0 +1,216 @@ + +krefs allow you to add reference counters to your objects. If you +have objects that are used in multiple places and passed around, and +you don't have refcounts, your code is almost certainly broken. If +you want refcounts, krefs are the way to go. + +To use a kref, add one to your data structures like: + +struct my_data +{ + . + . + struct kref refcount; + . + . +}; + +The kref can occur anywhere within the data structure. + +You must initialize the kref after you allocate it. To do this, call +kref_init as so: + + struct my_data *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + kref_init(&data->refcount); + +This sets the refcount in the kref to 1. + +Once you have an initialized kref, you must follow the following +rules: + +1) If you make a non-temporary copy of a pointer, especially if + it can be passed to another thread of execution, you must + increment the refcount with kref_get() before passing it off: + kref_get(&data->refcount); + If you already have a valid pointer to a kref-ed structure (the + refcount cannot go to zero) you may do this without a lock. + +2) When you are done with a pointer, you must call kref_put(): + kref_put(&data->refcount, data_release); + If this is the last reference to the pointer, the release + routine will be called. If the code never tries to get + a valid pointer to a kref-ed structure without already + holding a valid pointer, it is safe to do this without + a lock. + +3) If the code attempts to gain a reference to a kref-ed structure + without already holding a valid pointer, it must serialize access + where a kref_put() cannot occur during the kref_get(), and the + structure must remain valid during the kref_get(). + +For example, if you allocate some data and then pass it to another +thread to process: + +void data_release(struct kref *ref) +{ + struct my_data *data = container_of(ref, struct my_data, refcount); + kfree(data); +} + +void more_data_handling(void *cb_data) +{ + struct my_data *data = cb_data; + . + . do stuff with data here + . + kref_put(data, data_release); +} + +int my_data_handler(void) +{ + int rv = 0; + struct my_data *data; + struct task_struct *task; + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + kref_init(&data->refcount); + + kref_get(&data->refcount); + task = kthread_run(more_data_handling, data, "more_data_handling"); + if (task == ERR_PTR(-ENOMEM)) { + rv = -ENOMEM; + kref_put(&data->refcount, data_release); + goto out; + } + + . + . do stuff with data here + . + out: + kref_put(&data->refcount, data_release); + return rv; +} + +This way, it doesn't matter what order the two threads handle the +data, the kref_put() handles knowing when the data is not referenced +any more and releasing it. The kref_get() does not require a lock, +since we already have a valid pointer that we own a refcount for. The +put needs no lock because nothing tries to get the data without +already holding a pointer. + +Note that the "before" in rule 1 is very important. You should never +do something like: + + task = kthread_run(more_data_handling, data, "more_data_handling"); + if (task == ERR_PTR(-ENOMEM)) { + rv = -ENOMEM; + goto out; + } else + /* BAD BAD BAD - get is after the handoff */ + kref_get(&data->refcount); + +Don't assume you know what you are doing and use the above construct. +First of all, you may not know what you are doing. Second, you may +know what you are doing (there are some situations where locking is +involved where the above may be legal) but someone else who doesn't +know what they are doing may change the code or copy the code. It's +bad style. Don't do it. + +There are some situations where you can optimize the gets and puts. +For instance, if you are done with an object and enqueuing it for +something else or passing it off to something else, there is no reason +to do a get then a put: + + /* Silly extra get and put */ + kref_get(&obj->ref); + enqueue(obj); + kref_put(&obj->ref, obj_cleanup); + +Just do the enqueue. A comment about this is always welcome: + + enqueue(obj); + /* We are done with obj, so we pass our refcount off + to the queue. DON'T TOUCH obj AFTER HERE! */ + +The last rule (rule 3) is the nastiest one to handle. Say, for +instance, you have a list of items that are each kref-ed, and you wish +to get the first one. You can't just pull the first item off the list +and kref_get() it. That violates rule 3 because you are not already +holding a valid pointer. You must add locks or semaphores. For +instance: + +static DECLARE_MUTEX(sem); +static LIST_HEAD(q); +struct my_data +{ + struct kref refcount; + struct list_head link; +}; + +static struct my_data *get_entry() +{ + struct my_data *entry = NULL; + down(&sem); + if (!list_empty(&q)) { + entry = container_of(q.next, struct my_q_entry, link); + kref_get(&entry->refcount); + } + up(&sem); + return entry; +} + +static void release_entry(struct kref *ref) +{ + struct my_data *entry = container_of(ref, struct my_data, refcount); + + list_del(&entry->link); + kfree(entry); +} + +static void put_entry(struct my_data *entry) +{ + down(&sem); + kref_put(&entry->refcount, release_entry); + up(&sem); +} + +The kref_put() return value is useful if you do not want to hold the +lock during the whole release operation. Say you didn't want to call +kfree() with the lock held in the example above (since it is kind of +pointless to do so). You could use kref_put() as follows: + +static void release_entry(struct kref *ref) +{ + /* All work is done after the return from kref_put(). */ +} + +static void put_entry(struct my_data *entry) +{ + down(&sem); + if (kref_put(&entry->refcount, release_entry)) { + list_del(&entry->link); + up(&sem); + kfree(entry); + } else + up(&sem); +} + +This is really more useful if you have to call other routines as part +of the free operations that could take a long time or might claim the +same lock. Note that doing everything in the release routine is still +preferred as it is a little neater. + + +Corey Minyard <minyard@acm.org> + +A lot of this was lifted from Greg Kroah-Hartman's 2004 OLS paper and +presentation on krefs, which can be found at: + http://www.kroah.com/linux/talks/ols_2004_kref_paper/Reprint-Kroah-Hartman-OLS2004.pdf +and: + http://www.kroah.com/linux/talks/ols_2004_kref_talk/ + diff --git a/drivers/base/class.c b/drivers/base/class.c index 6bf650f..d2a2f8f 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -430,6 +430,7 @@ int class_device_add(struct class_device *class_dev) sysfs_create_link(&class_dev->kobj, &class_dev->dev->kobj, "device"); + kobject_hotplug(&class_dev->kobj, KOBJ_ADD); register_done: if (error && parent) class_put(parent); @@ -461,6 +462,7 @@ void class_device_del(struct class_device *class_dev) sysfs_remove_link(&class_dev->kobj, "device"); class_device_remove_attrs(class_dev); + kobject_hotplug(&class_dev->kobj, KOBJ_REMOVE); kobject_del(&class_dev->kobj); if (parent) diff --git a/drivers/base/core.c b/drivers/base/core.c index 4e6cce8..a7cedd8 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -260,6 +260,8 @@ int device_add(struct device *dev) /* notify platform of device entry */ if (platform_notify) platform_notify(dev); + + kobject_hotplug(&dev->kobj, KOBJ_ADD); Done: put_device(dev); return error; @@ -349,6 +351,7 @@ void device_del(struct device * dev) platform_notify_remove(dev); bus_remove_device(dev); device_pm_remove(dev); + kobject_hotplug(&dev->kobj, KOBJ_REMOVE); kobject_del(&dev->kobj); if (parent) put_device(parent); diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index 26c9464..97fe13f 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -102,6 +102,9 @@ firmware_class_hotplug(struct class_device *class_dev, char **envp, if (add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size, &len, "FIRMWARE=%s", fw_priv->fw_id)) return -ENOMEM; + if (add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size, &len, + "TIMEOUT=%i", loading_timeout)) + return -ENOMEM; envp[i] = NULL; diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 996cbb4..cd64539 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -341,6 +341,7 @@ EXPORT_SYMBOL_GPL(dma_get_required_mask); EXPORT_SYMBOL_GPL(platform_bus); EXPORT_SYMBOL_GPL(platform_bus_type); +EXPORT_SYMBOL_GPL(platform_add_devices); EXPORT_SYMBOL_GPL(platform_device_register); EXPORT_SYMBOL_GPL(platform_device_register_simple); EXPORT_SYMBOL_GPL(platform_device_unregister); diff --git a/drivers/usb/host/hc_crisv10.c b/drivers/usb/host/hc_crisv10.c index 4b12be8..376f8a0 100644 --- a/drivers/usb/host/hc_crisv10.c +++ b/drivers/usb/host/hc_crisv10.c @@ -4396,6 +4396,7 @@ static int __init etrax_usb_hc_init(void) device_initialize(&fake_device); kobject_set_name(&fake_device.kobj, "etrax_usb"); kobject_add(&fake_device.kobj); + kobject_hotplug(&fake_device.kobj, KOBJ_ADD); hc->bus->controller = &fake_device; usb_register_bus(hc->bus); diff --git a/fs/partitions/check.c b/fs/partitions/check.c index 31cff78..2cab98a 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -337,6 +337,7 @@ void register_disk(struct gendisk *disk) if ((err = kobject_add(&disk->kobj))) return; disk_sysfs_symlinks(disk); + kobject_hotplug(&disk->kobj, KOBJ_ADD); /* No minors to use for partitions */ if (disk->minors == 1) { @@ -441,5 +442,6 @@ void del_gendisk(struct gendisk *disk) sysfs_remove_link(&disk->driverfs_dev->kobj, "block"); put_device(disk->driverfs_dev); } + kobject_hotplug(&disk->kobj, KOBJ_REMOVE); kobject_del(&disk->kobj); } diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 352f966..da25aeb 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c @@ -428,6 +428,41 @@ int sysfs_update_file(struct kobject * kobj, const struct attribute * attr) /** + * sysfs_chmod_file - update the modified mode value on an object attribute. + * @kobj: object we're acting for. + * @attr: attribute descriptor. + * @mode: file permissions. + * + */ +int sysfs_chmod_file(struct kobject *kobj, struct attribute *attr, mode_t mode) +{ + struct dentry *dir = kobj->dentry; + struct dentry *victim; + struct sysfs_dirent *sd; + umode_t umode = (mode & S_IALLUGO) | S_IFREG; + int res = -ENOENT; + + down(&dir->d_inode->i_sem); + victim = sysfs_get_dentry(dir, attr->name); + if (!IS_ERR(victim)) { + if (victim->d_inode && + (victim->d_parent->d_inode == dir->d_inode)) { + sd = victim->d_fsdata; + attr->mode = mode; + sd->s_mode = umode; + victim->d_inode->i_mode = umode; + dput(victim); + res = 0; + } + } + up(&dir->d_inode->i_sem); + + return res; +} +EXPORT_SYMBOL_GPL(sysfs_chmod_file); + + +/** * sysfs_remove_file - remove an object attribute. * @kobj: object we're acting for. * @attr: attribute descriptor. diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index f7a7b86..a5fa6a6 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h @@ -17,6 +17,10 @@ #include <linux/fs.h> +#include <linux/types.h> + +struct file_operations; + #if defined(CONFIG_DEBUG_FS) struct dentry *debugfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, @@ -36,6 +40,9 @@ struct dentry *debugfs_create_bool(const char *name, mode_t mode, struct dentry *parent, u32 *value); #else + +#include <linux/err.h> + /* * We do not return NULL from these functions if CONFIG_DEBUG_FS is not enabled * so users have a chance to detect if there was a real error or not. We don't @@ -68,21 +75,21 @@ static inline struct dentry *debugfs_create_u8(const char *name, mode_t mode, static inline struct dentry *debugfs_create_u16(const char *name, mode_t mode, struct dentry *parent, - u8 *value) + u16 *value) { return ERR_PTR(-ENODEV); } static inline struct dentry *debugfs_create_u32(const char *name, mode_t mode, struct dentry *parent, - u8 *value) + u32 *value) { return ERR_PTR(-ENODEV); } static inline struct dentry *debugfs_create_bool(const char *name, mode_t mode, struct dentry *parent, - u8 *value) + u32 *value) { return ERR_PTR(-ENODEV); } diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index 6f502ff..38b58b3 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -99,6 +99,9 @@ sysfs_create_file(struct kobject *, const struct attribute *); extern int sysfs_update_file(struct kobject *, const struct attribute *); +extern int +sysfs_chmod_file(struct kobject *kobj, struct attribute *attr, mode_t mode); + extern void sysfs_remove_file(struct kobject *, const struct attribute *); @@ -140,6 +143,10 @@ static inline int sysfs_update_file(struct kobject * k, const struct attribute * { return 0; } +static inline int sysfs_chmod_file(struct kobject *kobj, struct attribute *attr, mode_t mode) +{ + return 0; +} static inline void sysfs_remove_file(struct kobject * k, const struct attribute * a) { diff --git a/lib/kobject.c b/lib/kobject.c index ff94919..5df8441 100644 --- a/lib/kobject.c +++ b/lib/kobject.c @@ -184,8 +184,6 @@ int kobject_add(struct kobject * kobj) unlink(kobj); if (parent) kobject_put(parent); - } else { - kobject_hotplug(kobj, KOBJ_ADD); } return error; @@ -207,7 +205,8 @@ int kobject_register(struct kobject * kobj) printk("kobject_register failed for %s (%d)\n", kobject_name(kobj),error); dump_stack(); - } + } else + kobject_hotplug(kobj, KOBJ_ADD); } else error = -EINVAL; return error; @@ -301,7 +300,6 @@ int kobject_rename(struct kobject * kobj, char *new_name) void kobject_del(struct kobject * kobj) { - kobject_hotplug(kobj, KOBJ_REMOVE); sysfs_remove_dir(kobj); unlink(kobj); } @@ -314,6 +312,7 @@ void kobject_del(struct kobject * kobj) void kobject_unregister(struct kobject * kobj) { pr_debug("kobject %s: unregistering\n",kobject_name(kobj)); + kobject_hotplug(kobj, KOBJ_REMOVE); kobject_del(kobj); kobject_put(kobj); } diff --git a/net/bridge/br_sysfs_if.c b/net/bridge/br_sysfs_if.c index 567249b..f6a19d5 100644 --- a/net/bridge/br_sysfs_if.c +++ b/net/bridge/br_sysfs_if.c @@ -248,6 +248,7 @@ int br_sysfs_addif(struct net_bridge_port *p) if (err) goto out2; + kobject_hotplug(&p->kobj, KOBJ_ADD); return 0; out2: kobject_del(&p->kobj); @@ -259,6 +260,7 @@ void br_sysfs_removeif(struct net_bridge_port *p) { pr_debug("br_sysfs_removeif\n"); sysfs_remove_link(&p->br->ifobj, p->dev->name); + kobject_hotplug(&p->kobj, KOBJ_REMOVE); kobject_del(&p->kobj); } diff --git a/scripts/ver_linux b/scripts/ver_linux index bb195a1..a28c279 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -87,7 +87,7 @@ loadkeys -V 2>&1 | awk \ expr --v 2>&1 | awk 'NR==1{print "Sh-utils ", $NF}' -udevinfo -V | awk '{print "udev ", $3}' +udevinfo -V 2>&1 | grep version | awk '{print "udev ", $3}' if [ -e /proc/modules ]; then X=`cat /proc/modules | sed -e "s/ .*$//"` |