From 65d20c546504e697fd06dca3dd62459416e7762c Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Tue, 15 Oct 2002 20:18:11 -0700 Subject: driver model: make device_unregister() only mark device as !present. There are races WRT walking the lists of the buses and classes and the device being unregistered while it's the current node we're accessing. If the node was deleted, ->next would point to itself, and we'd sit in an inifinite loop (until that memory was freed from underneath us and we Oops'd). This changes device_unregister() to only mark the device as not present. put_device() deletes the device from all the lists it belongs to, while holding device_lock, then drops it to call device_del(). This should guarantee that everything in the device is valid until the last reference goes away, regardless of whether it's still physically present or not. --- drivers/base/core.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/base/core.c b/drivers/base/core.c index 83c31723d844..2e3e862472fc 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -259,34 +259,22 @@ struct device * get_device(struct device * dev) */ void put_device(struct device * dev) { - struct device * parent; if (!atomic_dec_and_lock(&dev->refcount,&device_lock)) return; - parent = dev->parent; - dev->parent = NULL; + list_del_init(&dev->node); + list_del_init(&dev->g_list); + list_del_init(&dev->bus_list); + list_del_init(&dev->driver_list); spin_unlock(&device_lock); BUG_ON(dev->present); - if (dev->release) - dev->release(dev); - - if (parent) - put_device(parent); + device_del(dev); } void device_del(struct device * dev) { - spin_lock(&device_lock); - dev->present = 0; - list_del_init(&dev->node); - list_del_init(&dev->g_list); - list_del_init(&dev->bus_list); - list_del_init(&dev->driver_list); - spin_unlock(&device_lock); - - pr_debug("DEV: Unregistering device. ID = '%s', name = '%s'\n", - dev->bus_id,dev->name); + struct device * parent = dev->parent; /* Notify the platform of the removal, in case they * need to do anything... @@ -302,6 +290,12 @@ void device_del(struct device * dev) /* remove the driverfs directory */ device_remove_dir(dev); + + if (dev->release) + dev->release(dev); + + if (parent) + put_device(parent); } /** @@ -315,7 +309,12 @@ void device_del(struct device * dev) */ void device_unregister(struct device * dev) { - device_del(dev); + spin_lock(&device_lock); + dev->present = 0; + spin_unlock(&device_lock); + + pr_debug("DEV: Unregistering device. ID = '%s', name = '%s'\n", + dev->bus_id,dev->name); put_device(dev); } -- cgit v1.2.3 From 473c71058e2bfdbe091652b9a82572aa621bfffa Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Tue, 15 Oct 2002 20:32:25 -0700 Subject: driver model: make driverfs an implicit initcall. --- drivers/base/core.c | 12 ------------ fs/driverfs/inode.c | 4 +++- include/linux/driverfs_fs.h | 2 -- 3 files changed, 3 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/base/core.c b/drivers/base/core.c index 2e3e862472fc..b6f2104ae038 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -318,18 +318,6 @@ void device_unregister(struct device * dev) put_device(dev); } -static int __init device_init(void) -{ - int error; - - error = init_driverfs_fs(); - if (error) - panic("DEV: could not initialize driverfs"); - return 0; -} - -core_initcall(device_init); - EXPORT_SYMBOL(device_register); EXPORT_SYMBOL(device_unregister); EXPORT_SYMBOL(get_device); diff --git a/fs/driverfs/inode.c b/fs/driverfs/inode.c index 0f35e27e669c..b4b5aa9f6a55 100644 --- a/fs/driverfs/inode.c +++ b/fs/driverfs/inode.c @@ -509,11 +509,13 @@ static void put_mount(void) DBG("driverfs: mount_count = %d\n",mount_count); } -int __init init_driverfs_fs(void) +static int __init driverfs_init(void) { return register_filesystem(&driverfs_fs_type); } +core_initcall(driverfs_init); + static struct dentry * get_dentry(struct dentry * parent, const char * name) { struct qstr qstr; diff --git a/include/linux/driverfs_fs.h b/include/linux/driverfs_fs.h index d859f8c2e041..b4270e947a1e 100644 --- a/include/linux/driverfs_fs.h +++ b/include/linux/driverfs_fs.h @@ -65,6 +65,4 @@ driverfs_create_symlink(struct driver_dir_entry * parent, extern void driverfs_remove_file(struct driver_dir_entry *, const char * name); -extern int init_driverfs_fs(void); - #endif /* _DDFS_H_ */ -- cgit v1.2.3 From 4278bccc2d9707c9c4ee1769b464bffe4dfd820a Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Tue, 15 Oct 2002 20:54:20 -0700 Subject: driver model: change bus refcounting scheme to match devices'. Based on recent changes to device refcounting/unregistration. struct bus_type gets a ->present field, which is set when the bus is registered. bus_unregister() is added, which only clears ->present and calls put_bus(). get_bus() checks the ->present and returns NULL if clear. This allows all current references to buses to keep valid structures, while preventing any new references to the bus structure. --- drivers/base/bus.c | 36 +++++++++++++++++++++++++++++++----- include/linux/device.h | 10 +++------- 2 files changed, 34 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 7daffbfd9913..1343c64c5a38 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -136,12 +136,35 @@ void bus_remove_device(struct device * dev) } } +struct bus_type * get_bus(struct bus_type * bus) +{ + struct bus_type * ret = bus; + spin_lock(&device_lock); + if (bus && bus->present && atomic_read(&bus->refcount)) + atomic_inc(&bus->refcount); + else + ret = NULL; + spin_unlock(&device_lock); + return ret; +} + +void put_bus(struct bus_type * bus) +{ + if (!atomic_dec_and_lock(&bus->refcount,&device_lock)) + return; + list_del_init(&bus->node); + spin_unlock(&device_lock); + BUG_ON(bus->present); + bus_remove_dir(bus); +} + int bus_register(struct bus_type * bus) { rwlock_init(&bus->lock); INIT_LIST_HEAD(&bus->devices); INIT_LIST_HEAD(&bus->drivers); atomic_set(&bus->refcount,2); + bus->present = 1; spin_lock(&device_lock); list_add_tail(&bus->node,&bus_driver_list); @@ -156,13 +179,14 @@ int bus_register(struct bus_type * bus) return 0; } -void put_bus(struct bus_type * bus) +void bus_unregister(struct bus_type * bus) { - if (!atomic_dec_and_lock(&bus->refcount,&device_lock)) - return; - list_del_init(&bus->node); + spin_lock(&device_lock); + bus->present = 0; spin_unlock(&device_lock); - bus_remove_dir(bus); + + pr_debug("bus %s: unregistering\n",bus->name); + put_bus(bus); } EXPORT_SYMBOL(bus_for_each_dev); @@ -170,4 +194,6 @@ EXPORT_SYMBOL(bus_for_each_drv); EXPORT_SYMBOL(bus_add_device); EXPORT_SYMBOL(bus_remove_device); EXPORT_SYMBOL(bus_register); +EXPORT_SYMBOL(bus_unregister); +EXPORT_SYMBOL(get_bus); EXPORT_SYMBOL(put_bus); diff --git a/include/linux/device.h b/include/linux/device.h index 80a63939f924..03c7d995e96c 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -56,6 +56,7 @@ struct bus_type { char * name; rwlock_t lock; atomic_t refcount; + u32 present; struct list_head node; struct list_head devices; @@ -73,14 +74,9 @@ struct bus_type { extern int bus_register(struct bus_type * bus); +extern void bus_unregister(struct bus_type * bus); -static inline struct bus_type * get_bus(struct bus_type * bus) -{ - BUG_ON(!atomic_read(&bus->refcount)); - atomic_inc(&bus->refcount); - return bus; -} - +extern struct bus_type * get_bus(struct bus_type * bus); extern void put_bus(struct bus_type * bus); extern int bus_for_each_dev(struct bus_type * bus, void * data, -- cgit v1.2.3 From 2884fae053fb6a58f4f7c5985d574532b7ddd3d2 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Tue, 15 Oct 2002 21:11:26 -0700 Subject: driver model: make driver refcounting similar to devices'. In the spirit of devices and buses, change driver refcounting model to match the way that devices and buses are done. struct device_driver gets a ->present field, which is set on registration and cleared in driver_unregister(). get_device() checks the state of this flag and returns NULL if cleared. Note that the horribly wrong remove_driver() is deprecated and simply BUG()s when called. Please convert callers to use driver_unregister(). Updates to callers will be coming soon. Note also that this still doesn't fix the race in which a driver module can be removed while the refcount on a driver > 1. Near future work should help to remedy it, but no solutions are guaranteed.. --- drivers/base/driver.c | 74 ++++++++++++++++++++++++++++++-------------------- include/linux/device.h | 11 ++------ 2 files changed, 48 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 221e525736bd..b19b06201dd5 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -39,6 +39,43 @@ int driver_for_each_dev(struct device_driver * drv, void * data, return error; } +struct device_driver * get_driver(struct device_driver * drv) +{ + struct device_driver * ret = drv; + spin_lock(&device_lock); + if (drv && drv->present && atomic_read(&drv->refcount) > 0) + atomic_inc(&drv->refcount); + else + ret = NULL; + spin_unlock(&device_lock); + return ret; +} + + +void remove_driver(struct device_driver * drv) +{ + BUG(); +} + +/** + * put_driver - decrement driver's refcount and clean up if necessary + * @drv: driver in question + */ +void put_driver(struct device_driver * drv) +{ + struct bus_type * bus = drv->bus; + if (!atomic_dec_and_lock(&drv->refcount,&device_lock)) + return; + list_del_init(&drv->bus_list); + spin_unlock(&device_lock); + BUG_ON(drv->present); + driver_detach(drv); + driver_remove_dir(drv); + if (drv->release) + drv->release(drv); + put_bus(bus); +} + /** * driver_register - register driver with bus * @drv: driver to register @@ -50,12 +87,13 @@ int driver_register(struct device_driver * drv) if (!drv->bus) return -EINVAL; - pr_debug("Registering driver '%s' with bus '%s'\n",drv->name,drv->bus->name); + pr_debug("driver %s:%s: registering\n",drv->bus->name,drv->name); get_bus(drv->bus); atomic_set(&drv->refcount,2); rwlock_init(&drv->lock); INIT_LIST_HEAD(&drv->devices); + drv->present = 1; spin_lock(&device_lock); list_add(&drv->bus_list,&drv->bus->drivers); spin_unlock(&device_lock); @@ -65,39 +103,17 @@ int driver_register(struct device_driver * drv) return 0; } -static void __remove_driver(struct device_driver * drv) -{ - pr_debug("Unregistering driver '%s' from bus '%s'\n",drv->name,drv->bus->name); - driver_detach(drv); - driver_remove_dir(drv); - if (drv->release) - drv->release(drv); - put_bus(drv->bus); -} - -void remove_driver(struct device_driver * drv) +void driver_unregister(struct device_driver * drv) { spin_lock(&device_lock); - atomic_set(&drv->refcount,0); - list_del_init(&drv->bus_list); - spin_unlock(&device_lock); - __remove_driver(drv); -} - -/** - * put_driver - decrement driver's refcount and clean up if necessary - * @drv: driver in question - */ -void put_driver(struct device_driver * drv) -{ - if (!atomic_dec_and_lock(&drv->refcount,&device_lock)) - return; - list_del_init(&drv->bus_list); + drv->present = 0; spin_unlock(&device_lock); - __remove_driver(drv); + pr_debug("driver %s:%s: unregistering\n",drv->bus->name,drv->name); + put_driver(drv); } EXPORT_SYMBOL(driver_for_each_dev); EXPORT_SYMBOL(driver_register); +EXPORT_SYMBOL(driver_unregister); +EXPORT_SYMBOL(get_driver); EXPORT_SYMBOL(put_driver); -EXPORT_SYMBOL(remove_driver); diff --git a/include/linux/device.h b/include/linux/device.h index 03c7d995e96c..4b6b0cbe77ed 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -110,6 +110,7 @@ struct device_driver { rwlock_t lock; atomic_t refcount; + u32 present; struct list_head bus_list; struct list_head class_list; @@ -127,16 +128,10 @@ struct device_driver { }; - extern int driver_register(struct device_driver * drv); +extern void driver_unregister(struct device_driver * drv); -static inline struct device_driver * get_driver(struct device_driver * drv) -{ - BUG_ON(!atomic_read(&drv->refcount)); - atomic_inc(&drv->refcount); - return drv; -} - +extern struct device_driver * get_driver(struct device_driver * drv); extern void put_driver(struct device_driver * drv); extern void remove_driver(struct device_driver * drv); -- cgit v1.2.3 From 1067efac8c64c2608ececdca87e1329ba3ffa073 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Tue, 15 Oct 2002 21:26:48 -0700 Subject: driver model: change class reference counting to be like devices'. device classes join the club of devices, buses, and drivers. They get a ->present flag, which is set on registration and cleared on unregistration. They also get get_devclass() and put_devclass(), which, you guessed it, bump the reference count. get_...() of course checks the present flag and returns NULL if clear. --- drivers/base/class.c | 36 +++++++++++++++++++++++++++++++----- include/linux/device.h | 6 ++++++ 2 files changed, 37 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/base/class.c b/drivers/base/class.c index dfef9793871a..3c7024cc3efb 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -81,29 +81,55 @@ void devclass_remove_device(struct device * dev) } } +struct device_class * get_devclass(struct device_class * cls) +{ + struct device_class * ret = cls; + spin_lock(&device_lock); + if (cls && cls->present && atomic_read(&cls->refcount) > 0) + atomic_inc(&cls->refcount); + else + ret = NULL; + spin_unlock(&device_lock); + return ret; +} + +void put_devclass(struct device_class * cls) +{ + if (atomic_dec_and_lock(&cls->refcount,&device_lock)) { + list_del_init(&cls->node); + spin_unlock(&device_lock); + devclass_remove_dir(cls); + } +} + + int devclass_register(struct device_class * cls) { INIT_LIST_HEAD(&cls->drivers); INIT_LIST_HEAD(&cls->intf_list); - - pr_debug("registering device class '%s'\n",cls->name); + atomic_set(&cls->refcount,2); + cls->present = 1; + pr_debug("device class '%s': registering\n",cls->name); spin_lock(&device_lock); list_add_tail(&cls->node,&class_list); spin_unlock(&device_lock); devclass_make_dir(cls); + put_devclass(cls); return 0; } void devclass_unregister(struct device_class * cls) { - pr_debug("unregistering device class '%s'\n",cls->name); - devclass_remove_dir(cls); spin_lock(&device_lock); - list_del_init(&class_list); + cls->present = 0; spin_unlock(&device_lock); + pr_debug("device class '%s': unregistering\n",cls->name); + put_devclass(cls); } EXPORT_SYMBOL(devclass_register); EXPORT_SYMBOL(devclass_unregister); +EXPORT_SYMBOL(get_devclass); +EXPORT_SYMBOL(put_devclass); diff --git a/include/linux/device.h b/include/linux/device.h index 4b6b0cbe77ed..112fca2dd2d8 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -163,6 +163,9 @@ extern void driver_remove_file(struct device_driver *, struct driver_attribute * */ struct device_class { char * name; + atomic_t refcount; + u32 present; + u32 devnum; struct list_head node; @@ -180,6 +183,9 @@ struct device_class { extern int devclass_register(struct device_class *); extern void devclass_unregister(struct device_class *); +extern struct device_class * get_devclass(struct device_class *); +extern void put_devclass(struct device_class *); + struct devclass_attribute { struct attribute attr; -- cgit v1.2.3 From ba809e8ac59030f8b8c1822f0ce6b3161cabc60b Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 01:28:28 -0700 Subject: driver model: replace rwlock in struct bus_type with a rwsem. Synchronize all walks of the device and driver lists of a bus with an rwsem wrapped around the entire iterator, instead of using device_lock and dropping it after we grabbed each node. Note this also prevents deadlock when walking the list of drivers and calling get_driver(), since get_driver() tries to take device_lock while we already have it held. --- drivers/base/bus.c | 24 +++++++++++------------- drivers/base/core.c | 1 - include/linux/device.h | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 1343c64c5a38..559956c29763 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -47,23 +47,21 @@ int bus_for_each_dev(struct bus_type * bus, void * data, int error = 0; get_bus(bus); - spin_lock(&device_lock); + down_write(&bus->rwsem); list_for_each(node,&bus->devices) { - struct device * dev = get_device_locked(to_dev(node)); + struct device * dev = get_device(to_dev(node)); if (dev) { - spin_unlock(&device_lock); error = callback(dev,data); if (prev) put_device(prev); prev = dev; - spin_lock(&device_lock); if (error) break; } } - spin_unlock(&device_lock); if (prev) put_device(prev); + up_write(&bus->rwsem); put_bus(bus); return error; } @@ -77,24 +75,21 @@ int bus_for_each_drv(struct bus_type * bus, void * data, /* pin bus in memory */ get_bus(bus); - - spin_lock(&device_lock); + down_write(&bus->rwsem); list_for_each(node,&bus->drivers) { struct device_driver * drv = get_driver(to_drv(node)); if (drv) { - spin_unlock(&device_lock); error = callback(drv,data); if (prev) put_driver(prev); prev = drv; - spin_lock(&device_lock); if (error) break; } } - spin_unlock(&device_lock); if (prev) put_driver(prev); + up_write(&bus->rwsem); put_bus(bus); return error; } @@ -111,11 +106,11 @@ int bus_for_each_drv(struct bus_type * bus, void * data, int bus_add_device(struct device * dev) { if (dev->bus) { + down_write(&dev->bus->rwsem); pr_debug("registering %s with bus '%s'\n",dev->bus_id,dev->bus->name); get_bus(dev->bus); - spin_lock(&device_lock); list_add_tail(&dev->bus_list,&dev->bus->devices); - spin_unlock(&device_lock); + up_write(&dev->bus->rwsem); device_bus_link(dev); } return 0; @@ -131,7 +126,10 @@ int bus_add_device(struct device * dev) void bus_remove_device(struct device * dev) { if (dev->bus) { + down_write(&dev->bus->rwsem); + list_del_init(&dev->bus_list); device_remove_symlink(&dev->bus->device_dir,dev->bus_id); + up_write(&dev->bus->rwsem); put_bus(dev->bus); } } @@ -160,7 +158,7 @@ void put_bus(struct bus_type * bus) int bus_register(struct bus_type * bus) { - rwlock_init(&bus->lock); + init_rwsem(&bus->rwsem); INIT_LIST_HEAD(&bus->devices); INIT_LIST_HEAD(&bus->drivers); atomic_set(&bus->refcount,2); diff --git a/drivers/base/core.c b/drivers/base/core.c index b6f2104ae038..a1a41f4b0a07 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -263,7 +263,6 @@ void put_device(struct device * dev) return; list_del_init(&dev->node); list_del_init(&dev->g_list); - list_del_init(&dev->bus_list); list_del_init(&dev->driver_list); spin_unlock(&device_lock); diff --git a/include/linux/device.h b/include/linux/device.h index 112fca2dd2d8..c0f8d6c5e4bf 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -54,7 +54,7 @@ struct device_class; struct bus_type { char * name; - rwlock_t lock; + struct rw_semaphore rwsem; atomic_t refcount; u32 present; -- cgit v1.2.3 From 5f330849a1cd4ca64c94c12ecc941f97697c6ded Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 02:35:40 -0700 Subject: driver model: make sure device has driver when we add it to the class. There is a chance devclass_add_device() could get passed a device with no driver, so we need to check that. --- drivers/base/class.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/base/class.c b/drivers/base/class.c index 3c7024cc3efb..21860a1380f7 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -53,16 +53,20 @@ static void unenum_device(struct device_class * cls, struct device * dev) int devclass_add_device(struct device * dev) { - struct device_class * cls = dev->driver->devclass; + struct device_class * cls; int error = 0; - if (cls) { - pr_debug("adding device '%s' to class '%s'\n", - dev->name,cls->name); - if (cls->add_device) - error = cls->add_device(dev); - if (!error) { - enum_device(cls,dev); - interface_add(cls,dev); + + if (dev->driver) { + cls = dev->driver->devclass; + if (cls) { + pr_debug("adding device '%s' to class '%s'\n", + dev->name,cls->name); + if (cls->add_device) + error = cls->add_device(dev); + if (!error) { + enum_device(cls,dev); + interface_add(cls,dev); + } } } return error; -- cgit v1.2.3 From 007aa5530e2cbae97d4e149cfceeff730d4636bb Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 02:43:55 -0700 Subject: driver model: simplify device/driver binding. - move list walking and matching to bus.c (since it's a function of the bus driver) - do specialized walks of the bus's lists when binding; no more callbacks being passed to bus_for_each_*. - take rwsem when adding and removing both devices and drivers. lists of each are now fully protected by that rwsem. It's also taken before we walk each list. - move calls of device_{de,at}tach() to bus_{add,remove}_device() and calls of driver_{de,at}tach() to bus_{add,remove}_driver(). --- drivers/base/base.h | 6 +-- drivers/base/bus.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++-- drivers/base/core.c | 131 +----------------------------------------------- drivers/base/driver.c | 10 +--- 4 files changed, 136 insertions(+), 145 deletions(-) (limited to 'drivers') diff --git a/drivers/base/base.h b/drivers/base/base.h index 0c05ae058971..a95361aafe3d 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -20,6 +20,9 @@ extern void device_remove_dir(struct device * dev); extern int bus_make_dir(struct bus_type * bus); extern void bus_remove_dir(struct bus_type * bus); +extern int bus_add_driver(struct device_driver *); +extern void bus_remove_driver(struct device_driver *); + extern int driver_make_dir(struct device_driver * drv); extern void driver_remove_dir(struct device_driver * drv); @@ -48,9 +51,6 @@ extern int interface_add(struct device_class *, struct device *); extern void interface_remove(struct device_class *, struct device *); -extern int driver_attach(struct device_driver * drv); -extern void driver_detach(struct device_driver * drv); - #ifdef CONFIG_HOTPLUG extern int dev_hotplug(struct device *dev, const char *action); #else diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 559956c29763..1f3b6471a582 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -94,6 +94,104 @@ int bus_for_each_drv(struct bus_type * bus, void * data, return error; } +static void attach(struct device * dev) +{ + pr_debug("bound device '%s' to driver '%s'\n", + dev->bus_id,dev->driver->name); + list_add_tail(&dev->driver_list,&dev->driver->devices); +} + +static int bus_match(struct device * dev, struct device_driver * drv) +{ + int error = 0; + if (dev->bus->match(dev,drv)) { + dev->driver = drv; + if (drv->probe) { + if (!(error = drv->probe(dev))) + attach(dev); + else + dev->driver = NULL; + } + } + return error; +} + +static int device_attach(struct device * dev) +{ + struct bus_type * bus = dev->bus; + struct list_head * entry; + int error = 0; + + if (dev->driver) { + attach(dev); + return 0; + } + + if (!bus->match) + return 0; + + list_for_each(entry,&bus->drivers) { + struct device_driver * drv = + get_driver(container_of(entry,struct device_driver,bus_list)); + if (!drv) + continue; + error = bus_match(dev,drv); + put_driver(drv); + if (!error) + break; + } + return error; +} + +static int driver_attach(struct device_driver * drv) +{ + struct bus_type * bus = drv->bus; + struct list_head * entry; + int error = 0; + + if (!bus->match) + return 0; + + list_for_each(entry,&bus->devices) { + struct device * dev = container_of(entry,struct device,bus_list); + if (get_device(dev)) { + if (!bus_match(dev,drv) && dev->driver) + devclass_add_device(dev); + put_device(dev); + } + } + return error; +} + +static void detach(struct device * dev, struct device_driver * drv) +{ + if (drv) { + list_del_init(&dev->driver_list); + devclass_remove_device(dev); + if (drv->remove) + drv->remove(dev); + dev->driver = NULL; + } +} + +static void device_detach(struct device * dev) +{ + detach(dev,dev->driver); +} + +static void driver_detach(struct device_driver * drv) +{ + struct list_head * entry; + list_for_each(entry,&drv->devices) { + struct device * dev = container_of(entry,struct device,driver_list); + if (get_device(dev)) { + detach(dev,drv); + put_device(dev); + } + } + +} + /** * bus_add_device - add device to bus * @dev: device being added @@ -105,11 +203,12 @@ int bus_for_each_drv(struct bus_type * bus, void * data, */ int bus_add_device(struct device * dev) { - if (dev->bus) { + struct bus_type * bus = get_bus(dev->bus); + if (bus) { down_write(&dev->bus->rwsem); - pr_debug("registering %s with bus '%s'\n",dev->bus_id,dev->bus->name); - get_bus(dev->bus); + pr_debug("bus %s: add device %s\n",bus->name,dev->bus_id); list_add_tail(&dev->bus_list,&dev->bus->devices); + device_attach(dev); up_write(&dev->bus->rwsem); device_bus_link(dev); } @@ -127,13 +226,40 @@ void bus_remove_device(struct device * dev) { if (dev->bus) { down_write(&dev->bus->rwsem); - list_del_init(&dev->bus_list); + pr_debug("bus %s: remove device %s\n",dev->bus->name,dev->bus_id); device_remove_symlink(&dev->bus->device_dir,dev->bus_id); + device_detach(dev); + list_del_init(&dev->bus_list); up_write(&dev->bus->rwsem); put_bus(dev->bus); } } +int bus_add_driver(struct device_driver * drv) +{ + struct bus_type * bus = get_bus(drv->bus); + if (bus) { + down_write(&bus->rwsem); + pr_debug("bus %s: add driver %s\n",bus->name,drv->name); + list_add_tail(&drv->bus_list,&bus->drivers); + driver_attach(drv); + up_write(&bus->rwsem); + driver_make_dir(drv); + } + return 0; +} + +void bus_remove_driver(struct device_driver * drv) +{ + if (drv->bus) { + down_write(&drv->bus->rwsem); + pr_debug("bus %s: remove driver %s\n",drv->bus->name,drv->name); + driver_detach(drv); + list_del_init(&drv->bus_list); + up_write(&drv->bus->rwsem); + } +} + struct bus_type * get_bus(struct bus_type * bus) { struct bus_type * ret = bus; diff --git a/drivers/base/core.c b/drivers/base/core.c index a1a41f4b0a07..0b7248cf1280 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -23,132 +23,6 @@ spinlock_t device_lock = SPIN_LOCK_UNLOCKED; #define to_dev(node) container_of(node,struct device,driver_list) -static int probe(struct device * dev, struct device_driver * drv) -{ - dev->driver = drv; - return drv->probe ? drv->probe(dev) : 0; -} - -static void attach(struct device * dev) -{ - spin_lock(&device_lock); - list_add_tail(&dev->driver_list,&dev->driver->devices); - spin_unlock(&device_lock); - devclass_add_device(dev); -} - -/** - * found_match - do actual binding of device to driver - * @dev: device - * @drv: driver - * - * We're here because the bus's match callback returned success for this - * pair. We call the driver's probe callback to verify they're really a - * match made in heaven. - * - * In the future, we may want to notify userspace of the binding. (But, - * we might not want to do it here). - * - * We may also want to create a symlink in the driver's directory to the - * device's physical directory. - */ -static int found_match(struct device * dev, struct device_driver * drv) -{ - int error = 0; - - if (!(error = probe(dev,get_driver(drv)))) { - pr_debug("bound device '%s' to driver '%s'\n", - dev->bus_id,drv->name); - attach(dev); - } else { - put_driver(drv); - dev->driver = NULL; - } - return error; -} - -/** - * device_attach - try to associated device with a driver - * @drv: current driver to try - * @data: device in disguise - * - * This function is used as a callback to bus_for_each_drv. - * It calls the bus's match callback to check if the driver supports - * the device. If so, it calls the found_match() function above to - * take care of all the details. - */ -static int do_device_attach(struct device_driver * drv, void * data) -{ - struct device * dev = (struct device *)data; - int error = 0; - - if (drv->bus->match && drv->bus->match(dev,drv)) - error = found_match(dev,drv); - return error; -} - -static int device_attach(struct device * dev) -{ - int error = 0; - if (!dev->driver) { - if (dev->bus) - error = bus_for_each_drv(dev->bus,dev,do_device_attach); - } else - attach(dev); - return error; -} - -static void device_detach(struct device * dev) -{ - struct device_driver * drv = dev->driver; - - if (drv) { - devclass_remove_device(dev); - if (drv && drv->remove) - drv->remove(dev); - dev->driver = NULL; - } -} - -static int do_driver_attach(struct device * dev, void * data) -{ - struct device_driver * drv = (struct device_driver *)data; - int error = 0; - - if (!dev->driver) { - if (dev->bus->match && dev->bus->match(dev,drv)) - error = found_match(dev,drv); - } - return error; -} - -int driver_attach(struct device_driver * drv) -{ - return bus_for_each_dev(drv->bus,drv,do_driver_attach); -} - -void driver_detach(struct device_driver * drv) -{ - struct list_head * node; - struct device * prev = NULL; - - spin_lock(&device_lock); - list_for_each(node,&drv->devices) { - struct device * dev = get_device_locked(to_dev(node)); - if (dev) { - if (prev) - list_del_init(&prev->driver_list); - spin_unlock(&device_lock); - device_detach(dev); - if (prev) - put_device(prev); - prev = dev; - spin_lock(&device_lock); - } - } - spin_unlock(&device_lock); -} - int device_add(struct device *dev) { int error; @@ -173,9 +47,6 @@ int device_add(struct device *dev) bus_add_device(dev); - /* bind to driver */ - device_attach(dev); - /* notify platform of device entry */ if (platform_notify) platform_notify(dev); @@ -183,6 +54,7 @@ int device_add(struct device *dev) /* notify userspace of device entry */ dev_hotplug(dev, "add"); + devclass_add_device(dev); register_done: if (error) { spin_lock(&device_lock); @@ -284,7 +156,6 @@ void device_del(struct device * dev) /* notify userspace that this device is about to disappear */ dev_hotplug (dev, "remove"); - device_detach(dev); bus_remove_device(dev); /* remove the driverfs directory */ diff --git a/drivers/base/driver.c b/drivers/base/driver.c index b19b06201dd5..2c0412400500 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -66,11 +66,9 @@ void put_driver(struct device_driver * drv) struct bus_type * bus = drv->bus; if (!atomic_dec_and_lock(&drv->refcount,&device_lock)) return; - list_del_init(&drv->bus_list); spin_unlock(&device_lock); BUG_ON(drv->present); - driver_detach(drv); - driver_remove_dir(drv); + bus_remove_driver(drv); if (drv->release) drv->release(drv); put_bus(bus); @@ -94,11 +92,7 @@ int driver_register(struct device_driver * drv) rwlock_init(&drv->lock); INIT_LIST_HEAD(&drv->devices); drv->present = 1; - spin_lock(&device_lock); - list_add(&drv->bus_list,&drv->bus->drivers); - spin_unlock(&device_lock); - driver_make_dir(drv); - driver_attach(drv); + bus_add_driver(drv); put_driver(drv); return 0; } -- cgit v1.2.3 From f21c8fc316b1cf3c537656508fd463522d58cdf3 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 02:45:26 -0700 Subject: driver model: make sure we call device_unregister() and not put_device() when removing system and platform devices. --- drivers/base/platform.c | 2 +- drivers/base/sys.c | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 29c90e34e67e..3856bc52b411 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -39,7 +39,7 @@ int platform_device_register(struct platform_device * pdev) void platform_device_unregister(struct platform_device * pdev) { if (pdev) - put_device(&pdev->dev); + device_unregister(&pdev->dev); } static int platform_match(struct device * dev, struct device_driver * drv) diff --git a/drivers/base/sys.c b/drivers/base/sys.c index a61fea973d36..cd681d1190bd 100644 --- a/drivers/base/sys.c +++ b/drivers/base/sys.c @@ -10,6 +10,8 @@ * add themselves as children of the system bus. */ +#define DEBUG 1 + #include #include #include @@ -76,8 +78,8 @@ int sys_register_root(struct sys_root * root) */ void sys_unegister_root(struct sys_root * root) { - put_device(&root->sysdev); - put_device(&root->dev); + device_unregister(&root->sysdev); + device_unregister(&root->dev); } /** @@ -125,7 +127,7 @@ int sys_device_register(struct sys_device * sysdev) void sys_device_unregister(struct sys_device * sysdev) { if (sysdev) - put_device(&sysdev->dev); + device_unregister(&sysdev->dev); } struct bus_type system_bus_type = { -- cgit v1.2.3 From d9b24a116f72056c7ac8bb2cdcd9dfd84ce85513 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 03:12:42 -0700 Subject: driver model: add per-class rwsem and protect list accesses with it. This is similar to struct bus_type's rwsem, though classes aren't doing anything nearly as fancy with it. We just make sure to take it when ever we add or remove any devices or drivers. --- drivers/base/class.c | 80 ++++++++++++++++++++++++++++++++++---------------- include/linux/device.h | 2 ++ 2 files changed, 57 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/base/class.c b/drivers/base/class.c index 21860a1380f7..7328f3231a65 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -10,27 +10,29 @@ static LIST_HEAD(class_list); int devclass_add_driver(struct device_driver * drv) { - if (drv->devclass) { - pr_debug("Registering driver %s:%s with class %s\n", - drv->bus->name,drv->name,drv->devclass->name); - - spin_lock(&device_lock); - list_add_tail(&drv->class_list,&drv->devclass->drivers); - spin_unlock(&device_lock); + struct device_class * cls = get_devclass(drv->devclass); + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: adding driver %s:%s\n", + cls->name,drv->bus->name,drv->name); + list_add_tail(&drv->class_list,&cls->drivers); devclass_drv_link(drv); + up_write(&cls->rwsem); } return 0; } void devclass_remove_driver(struct device_driver * drv) { - if (drv->devclass) { - pr_debug("Removing driver %s:%s:%s\n", - drv->devclass->name,drv->bus->name,drv->name); - spin_lock(&device_lock); + struct device_class * cls = drv->devclass; + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: removing driver %s:%s\n", + cls->name,drv->bus->name,drv->name); list_del_init(&drv->class_list); - spin_unlock(&device_lock); devclass_drv_unlink(drv); + up_write(&cls->rwsem); + put_devclass(cls); } } @@ -38,9 +40,7 @@ void devclass_remove_driver(struct device_driver * drv) static void enum_device(struct device_class * cls, struct device * dev) { u32 val; - spin_lock(&device_lock); val = cls->devnum++; - spin_unlock(&device_lock); dev->class_num = val; devclass_dev_link(cls,dev); } @@ -51,22 +51,44 @@ static void unenum_device(struct device_class * cls, struct device * dev) dev->class_num = 0; } +/** + * devclass_add_device - register device with device class + * @dev: device to be registered + * + * This is called when a device is either registered with the + * core, or after the a driver module is loaded and bound to + * the device. + * The class is determined by looking at @dev's driver, so one + * way or another, it must be bound to something. Once the + * class is determined, it's set to prevent against concurrent + * calls for the same device stomping on each other. + * + * /sbin/hotplug should be called once the device is added to + * class and all the interfaces. + */ int devclass_add_device(struct device * dev) { struct device_class * cls; int error = 0; if (dev->driver) { - cls = dev->driver->devclass; + cls = get_devclass(dev->driver->devclass); if (cls) { - pr_debug("adding device '%s' to class '%s'\n", - dev->name,cls->name); + down_write(&cls->rwsem); + pr_debug("device class %s: adding device %s\n", + cls->name,dev->name); if (cls->add_device) error = cls->add_device(dev); if (!error) { enum_device(cls,dev); interface_add(cls,dev); } + + /* notify userspace (call /sbin/hotplug) here */ + + up_write(&cls->rwsem); + if (error) + put_devclass(cls); } } return error; @@ -74,14 +96,21 @@ int devclass_add_device(struct device * dev) void devclass_remove_device(struct device * dev) { - struct device_class * cls = dev->driver->devclass; - if (cls) { - pr_debug("removing device '%s' from class '%s'\n", - dev->name,cls->name); - interface_remove(cls,dev); - unenum_device(cls,dev); - if (cls->remove_device) - cls->remove_device(dev); + struct device_class * cls; + + if (dev->driver) { + cls = dev->driver->devclass; + if (cls) { + down_write(&cls->rwsem); + pr_debug("device class %s: removing device %s\n", + cls->name,dev->name); + interface_remove(cls,dev); + unenum_device(cls,dev); + if (cls->remove_device) + cls->remove_device(dev); + up_write(&cls->rwsem); + put_devclass(cls); + } } } @@ -111,6 +140,7 @@ int devclass_register(struct device_class * cls) { INIT_LIST_HEAD(&cls->drivers); INIT_LIST_HEAD(&cls->intf_list); + init_rwsem(&cls->rwsem); atomic_set(&cls->refcount,2); cls->present = 1; pr_debug("device class '%s': registering\n",cls->name); diff --git a/include/linux/device.h b/include/linux/device.h index c0f8d6c5e4bf..fdcdf0077027 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -163,6 +163,8 @@ extern void driver_remove_file(struct device_driver *, struct driver_attribute * */ struct device_class { char * name; + struct rw_semaphore rwsem; + atomic_t refcount; u32 present; -- cgit v1.2.3 From 557aebd24f59a1e90093661233f8e79e78a563f9 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 21:10:04 -0700 Subject: driver model: protect drivers' device list accesses with bus's rwsem. Drivers must belong to bus, and each bus has an rwsem. Instead of mucking with the device_lock spinlock, and dropping it on each iteration of the loop, we take the bus's lock (read, so multiple drivers can access their list at the same time) around the entire walk of the list. --- drivers/base/driver.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 2c0412400500..4bf4a005b918 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -16,26 +16,23 @@ int driver_for_each_dev(struct device_driver * drv, void * data, int (*callback)(struct device *, void * )) { struct list_head * node; - struct device * prev = NULL; int error = 0; - get_driver(drv); - spin_lock(&device_lock); - list_for_each(node,&drv->devices) { - struct device * dev = get_device_locked(to_dev(node)); - if (dev) { - spin_unlock(&device_lock); - error = callback(dev,data); - if (prev) - put_device(prev); - prev = dev; - spin_lock(&device_lock); - if (error) - break; + drv = get_driver(drv); + if (drv) { + down_read(&drv->bus->rwsem); + list_for_each(node,&drv->devices) { + struct device * dev = get_device(to_dev(node)); + if (dev) { + error = callback(dev,data); + put_device(dev); + if (error) + break; + } } + up_read(&drv->bus->rwsem); + put_driver(drv); } - spin_unlock(&device_lock); - put_driver(drv); return error; } -- cgit v1.2.3 From ec0a28d2ce23c7118ca42b7ab0f5017e709eceea Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 21:13:28 -0700 Subject: driver model: clean up bus_for_each_{dev,drv} - make sure we check what we get back from get_bus() - do down_read() instead of down_write(), so multiple people can do iterations concurrently. - Note that devices and drivers are removed from these while doing a down_write(), so all removals will be stalled until the iterators are done. --- drivers/base/bus.c | 59 ++++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 33 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 1f3b6471a582..7ef3111f0091 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -43,26 +43,23 @@ int bus_for_each_dev(struct bus_type * bus, void * data, int (*callback)(struct device * dev, void * data)) { struct list_head * node; - struct device * prev = NULL; int error = 0; - get_bus(bus); - down_write(&bus->rwsem); - list_for_each(node,&bus->devices) { - struct device * dev = get_device(to_dev(node)); - if (dev) { - error = callback(dev,data); - if (prev) - put_device(prev); - prev = dev; - if (error) - break; + bus = get_bus(bus); + if (bus) { + down_read(&bus->rwsem); + list_for_each(node,&bus->devices) { + struct device * dev = get_device(to_dev(node)); + if (dev) { + error = callback(dev,data); + put_device(dev); + if (error) + break; + } } + up_read(&bus->rwsem); + put_bus(bus); } - if (prev) - put_device(prev); - up_write(&bus->rwsem); - put_bus(bus); return error; } @@ -70,27 +67,23 @@ int bus_for_each_drv(struct bus_type * bus, void * data, int (*callback)(struct device_driver * drv, void * data)) { struct list_head * node; - struct device_driver * prev = NULL; int error = 0; - /* pin bus in memory */ - get_bus(bus); - down_write(&bus->rwsem); - list_for_each(node,&bus->drivers) { - struct device_driver * drv = get_driver(to_drv(node)); - if (drv) { - error = callback(drv,data); - if (prev) - put_driver(prev); - prev = drv; - if (error) - break; + bus = get_bus(bus); + if (bus) { + down_read(&bus->rwsem); + list_for_each(node,&bus->drivers) { + struct device_driver * drv = get_driver(to_drv(node)); + if (drv) { + error = callback(drv,data); + put_driver(drv); + if (error) + break; + } } + up_read(&bus->rwsem); + put_bus(bus); } - if (prev) - put_driver(prev); - up_write(&bus->rwsem); - put_bus(bus); return error; } -- cgit v1.2.3 From 1e510b8fed8c72356c8915a7b0450a4d5271f170 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 21:16:59 -0700 Subject: driver model: introduce device_sem to protect global device list instead of device_lock. - device_sem is added to make the global list walks easier (for power mgmt and shutdown), so they don't have to take and drop device_lock a lot. - Change list modifications to take device_sem instead of device_lock. - Ditto for device refcount modifications. - Kill get_device_locked(), as all the users are now gone. --- drivers/base/base.h | 1 + drivers/base/core.c | 35 ++++++++++++++++------------------- 2 files changed, 17 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/base/base.h b/drivers/base/base.h index a95361aafe3d..3f294f8cafff 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -8,6 +8,7 @@ extern struct list_head global_device_list; extern spinlock_t device_lock; +extern struct semaphore device_sem; extern struct device * get_device_locked(struct device *); diff --git a/drivers/base/core.c b/drivers/base/core.c index 0b7248cf1280..c7f2fcfd480c 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -19,6 +19,8 @@ LIST_HEAD(global_device_list); int (*platform_notify)(struct device * dev) = NULL; int (*platform_notify_remove)(struct device * dev) = NULL; +DECLARE_MUTEX(device_sem); + spinlock_t device_lock = SPIN_LOCK_UNLOCKED; #define to_dev(node) container_of(node,struct device,driver_list) @@ -30,14 +32,14 @@ int device_add(struct device *dev) if (!dev || !strlen(dev->bus_id)) return -EINVAL; - spin_lock(&device_lock); + down(&device_sem); dev->present = 1; if (dev->parent) { list_add_tail(&dev->g_list,&dev->parent->g_list); list_add_tail(&dev->node,&dev->parent->children); } else list_add_tail(&dev->g_list,&global_device_list); - spin_unlock(&device_lock); + up(&device_sem); pr_debug("DEV: registering device: ID = '%s', name = %s\n", dev->bus_id, dev->name); @@ -57,10 +59,10 @@ int device_add(struct device *dev) devclass_add_device(dev); register_done: if (error) { - spin_lock(&device_lock); + up(&device_sem); list_del_init(&dev->g_list); list_del_init(&dev->node); - spin_unlock(&device_lock); + up(&device_sem); } return error; } @@ -106,22 +108,15 @@ int device_register(struct device *dev) return error; } -struct device * get_device_locked(struct device * dev) +struct device * get_device(struct device * dev) { struct device * ret = dev; + down(&device_sem); if (dev && dev->present && atomic_read(&dev->refcount) > 0) atomic_inc(&dev->refcount); else ret = NULL; - return ret; -} - -struct device * get_device(struct device * dev) -{ - struct device * ret; - spin_lock(&device_lock); - ret = get_device_locked(dev); - spin_unlock(&device_lock); + up(&device_sem); return ret; } @@ -131,12 +126,14 @@ struct device * get_device(struct device * dev) */ void put_device(struct device * dev) { - if (!atomic_dec_and_lock(&dev->refcount,&device_lock)) + down(&device_sem); + if (!atomic_dec_and_test(&dev->refcount)) { + up(&device_sem); return; + } list_del_init(&dev->node); list_del_init(&dev->g_list); - list_del_init(&dev->driver_list); - spin_unlock(&device_lock); + up(&device_sem); BUG_ON(dev->present); @@ -179,9 +176,9 @@ void device_del(struct device * dev) */ void device_unregister(struct device * dev) { - spin_lock(&device_lock); + down(&device_sem); dev->present = 0; - spin_unlock(&device_lock); + up(&device_sem); pr_debug("DEV: Unregistering device. ID = '%s', name = '%s'\n", dev->bus_id,dev->name); -- cgit v1.2.3 From 6b1febf70889295545f50cc0d05312eaf6daa0a2 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 21:22:36 -0700 Subject: driver model: power management/shutdown cleanup. - take device_sem around all global list walks. - don't modify refcount, as get_device()/put_device() also take device_sem - But, make sure we check device_present() to make sure device is still around. (Note device removal will block until device_sem is dropped, so list will remain intact.) - Separate out device_shutdown() walk from device_suspend() walk. Even though the code is nearly identical, it's a lot clearer as to what is going on when they are autonomous. It was my bad for originally putting that FIXME in there, encouraging the consolidation. - Add debugging hooks for my convenience. :) - Call ->shutdown() when shutting down device, instead of ->remove(). (See ChangeSet 1.799 for description and semantics). --- drivers/base/power.c | 68 ++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power.c b/drivers/base/power.c index c98984dfd8ab..176be19b72d1 100644 --- a/drivers/base/power.c +++ b/drivers/base/power.c @@ -8,6 +8,8 @@ * */ +#define DEBUG 0 + #include #include #include "base.h" @@ -28,34 +30,21 @@ int device_suspend(u32 state, u32 level) { struct list_head * node; - struct device * prev = NULL; int error = 0; - if(level == SUSPEND_POWER_DOWN) - printk(KERN_EMERG "Shutting down devices\n"); - else - printk(KERN_EMERG "Suspending devices\n"); + printk(KERN_EMERG "Suspending devices\n"); - spin_lock(&device_lock); + down(&device_sem); list_for_each(node,&global_device_list) { - struct device * dev = get_device_locked(to_dev(node)); - if (dev) { - spin_unlock(&device_lock); - if(dev->driver) { - if(level == SUSPEND_POWER_DOWN) { - if(dev->driver->remove) - dev->driver->remove(dev); - } else if(dev->driver->suspend) - error = dev->driver->suspend(dev,state,level); - } - if (prev) - put_device(prev); - prev = dev; - spin_lock(&device_lock); + struct device * dev = to_dev(node); + if (device_present(dev) && dev->driver && dev->driver->suspend) { + pr_debug("suspending device %s\n",dev->name); + error = dev->driver->suspend(dev,state,level); + if (error) + printk(KERN_ERR "%s: suspend returned %d\n",dev->name,error); } } - spin_unlock(&device_lock); - + up(&device_sem); return error; } @@ -70,33 +59,38 @@ int device_suspend(u32 state, u32 level) void device_resume(u32 level) { struct list_head * node; - struct device * prev = NULL; - spin_lock(&device_lock); + down(&device_sem); list_for_each_prev(node,&global_device_list) { - struct device * dev = get_device_locked(to_dev(node)); - if (dev) { - spin_unlock(&device_lock); - if (dev->driver && dev->driver->resume) - dev->driver->resume(dev,level); - if (prev) - put_device(prev); - prev = dev; - spin_lock(&device_lock); + struct device * dev = to_dev(node); + if (device_present(dev) && dev->driver && dev->driver->resume) { + pr_debug("resuming device %s\n",dev->name); + dev->driver->resume(dev,level); } } - spin_unlock(&device_lock); + up(&device_sem); printk(KERN_EMERG "Devices Resumed\n"); } /** - * device_shutdown - call device_suspend with status set to shutdown, to - * cause all devices to remove themselves cleanly + * device_shutdown - call ->remove() on each device to shutdown. */ void device_shutdown(void) { - device_suspend(4, SUSPEND_POWER_DOWN); + struct list_head * entry; + + printk(KERN_EMERG "Shutting down devices\n"); + + down(&device_sem); + list_for_each(entry,&global_device_list) { + struct device * dev = to_dev(entry); + if (device_present(dev) && dev->driver && dev->driver->shutdown) { + pr_debug("shutting down %s\n",dev->name); + dev->driver->shutdown(dev); + } + } + up(&device_sem); } EXPORT_SYMBOL(device_suspend); -- cgit v1.2.3 From be0b9c71e35ebee75fc694daa9f2a268b504ca50 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 22:07:11 -0700 Subject: driver model: change struct device::present to enumerated value with multiple states. Adapted from Greg KH: - add multiple possible enumerated states a device can be in: UNINITIALIZED, INITIALIZED, REGISTERED, and GONE. - Check whether device is INITIALIZED or REGISTERED in device_present(). - Change struct device::current_state to ::power_state to better reflect what it is. --- drivers/base/core.c | 9 +++++---- drivers/base/interface.c | 2 +- include/linux/device.h | 13 ++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/base/core.c b/drivers/base/core.c index c7f2fcfd480c..bd82a792d877 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -33,7 +33,7 @@ int device_add(struct device *dev) return -EINVAL; down(&device_sem); - dev->present = 1; + dev->state = DEVICE_REGISTERED; if (dev->parent) { list_add_tail(&dev->g_list,&dev->parent->g_list); list_add_tail(&dev->node,&dev->parent->children); @@ -77,6 +77,7 @@ void device_initialize(struct device *dev) INIT_LIST_HEAD(&dev->intf_list); spin_lock_init(&dev->lock); atomic_set(&dev->refcount,1); + dev->state = DEVICE_INITIALIZED; if (dev->parent) get_device(dev->parent); } @@ -112,7 +113,7 @@ struct device * get_device(struct device * dev) { struct device * ret = dev; down(&device_sem); - if (dev && dev->present && atomic_read(&dev->refcount) > 0) + if (device_present(dev) && atomic_read(&dev->refcount) > 0) atomic_inc(&dev->refcount); else ret = NULL; @@ -135,7 +136,7 @@ void put_device(struct device * dev) list_del_init(&dev->g_list); up(&device_sem); - BUG_ON(dev->present); + BUG_ON((dev->state != DEVICE_GONE)); device_del(dev); } @@ -177,7 +178,7 @@ void device_del(struct device * dev) void device_unregister(struct device * dev) { down(&device_sem); - dev->present = 0; + dev->state = DEVICE_GONE; up(&device_sem); pr_debug("DEV: Unregistering device. ID = '%s', name = '%s'\n", diff --git a/drivers/base/interface.c b/drivers/base/interface.c index a585b729c5c4..8a5c6e6bc226 100644 --- a/drivers/base/interface.c +++ b/drivers/base/interface.c @@ -19,7 +19,7 @@ static DEVICE_ATTR(name,S_IRUGO,device_read_name,NULL); static ssize_t device_read_power(struct device * dev, char * page, size_t count, loff_t off) { - return off ? 0 : sprintf(page,"%d\n",dev->current_state); + return off ? 0 : sprintf(page,"%d\n",dev->power_state); } static ssize_t diff --git a/include/linux/device.h b/include/linux/device.h index d34142925760..3dd9a4081298 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -48,6 +48,13 @@ enum { RESUME_ENABLE, }; +enum device_state { + DEVICE_UNINITIALIZED = 0, + DEVICE_INITIALIZED = 1, + DEVICE_REGISTERED = 2, + DEVICE_GONE = 3, +}; + struct device; struct device_driver; struct device_class; @@ -288,8 +295,8 @@ struct device { void *platform_data; /* Platform specific data (e.g. ACPI, BIOS data relevant to device) */ - u32 present; - u32 current_state; /* Current operating state. In + enum device_state state; + u32 power_state; /* Current operating state. In ACPI-speak, this is D0-D3, D0 being fully functional, and D3 being off. */ @@ -363,7 +370,7 @@ extern int (*platform_notify_remove)(struct device * dev); static inline int device_present(struct device * dev) { - return (dev && dev->present == 1); + return (dev && (dev->state == DEVICE_INITIALIZED || dev->state == DEVICE_REGISTERED)); } /* device and bus locking helpers. -- cgit v1.2.3 From 16733e6a9717d30f38d461bab9f11fd829df8e5c Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 22:35:29 -0700 Subject: driver model (pci): change call of remove_driver to driver_unregister. --- drivers/pci/pci-driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 040cc588981b..3ef7d9eb6e84 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -141,7 +141,7 @@ pci_register_driver(struct pci_driver *drv) void pci_unregister_driver(struct pci_driver *drv) { - remove_driver(&drv->driver); + driver_unregister(&drv->driver); } static struct pci_driver pci_compat_driver = { -- cgit v1.2.3 From 10e01163e17b2c9508d93e1cabe3583e44f5b4e1 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 22:36:22 -0700 Subject: driver model (scsi): change calls of remove_driver() to driver_unregister(). --- drivers/scsi/scsi_debug.c | 3 +-- drivers/scsi/sd.c | 2 +- drivers/scsi/sg.c | 2 +- drivers/scsi/sr.c | 2 +- drivers/scsi/st.c | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c index 44628796d12e..62a3c94e3efe 100644 --- a/drivers/scsi/scsi_debug.c +++ b/drivers/scsi/scsi_debug.c @@ -861,8 +861,7 @@ static int scsi_debug_release(struct Scsi_Host * hpnt) if (++num_releases == num_present) { #ifdef DRIVERFS_SUPPORT do_remove_driverfs_files(); - remove_driver(&sdebug_driverfs_driver); - // driver_unregister(&sdebug_driverfs_driver); + driver_unregister(&sdebug_driverfs_driver); #endif vfree(fake_storep); vfree(devInfop); diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 1b7abd00b167..ef66a15ce361 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -1549,7 +1549,7 @@ static void __exit exit_sd(void) for (k = 0; k < N_USED_SD_MAJORS; k++) blk_dev[SD_MAJOR(k)].queue = NULL; sd_template.dev_max = 0; - remove_driver(&sd_template.scsi_driverfs_driver); + driver_unregister(&sd_template.scsi_driverfs_driver); unregister_reboot_notifier(&sd_notifier_block); } diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c index a18bf6db8fcf..8efd01149936 100644 --- a/drivers/scsi/sg.c +++ b/drivers/scsi/sg.c @@ -1659,7 +1659,7 @@ exit_sg(void) sg_dev_arr = NULL; } sg_template.dev_max = 0; - remove_driver(&sg_template.scsi_driverfs_driver); + driver_unregister(&sg_template.scsi_driverfs_driver); } static int diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c index 39af5cce16f0..9cc3aedf0a3d 100644 --- a/drivers/scsi/sr.c +++ b/drivers/scsi/sr.c @@ -839,7 +839,7 @@ static void __exit exit_sr(void) kfree(scsi_CDs); sr_template.dev_max = 0; - remove_driver(&sr_template.scsi_driverfs_driver); + driver_unregister(&sr_template.scsi_driverfs_driver); } module_init(init_sr); diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c index 194077d101ba..a8f6217c5604 100644 --- a/drivers/scsi/st.c +++ b/drivers/scsi/st.c @@ -3994,7 +3994,7 @@ static void __exit exit_st(void) kfree(scsi_tapes); } st_template.dev_max = 0; - remove_driver(&st_template.scsi_driverfs_driver); + driver_unregister(&st_template.scsi_driverfs_driver); printk(KERN_INFO "st: Unloaded.\n"); } -- cgit v1.2.3 From 04013dc6090d48e8c77696b567044bb4f2376f38 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 22:37:05 -0700 Subject: driver model(usb): change calls of remove_driver() to driver_unregister(). --- drivers/usb/core/usb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 556dbacb7c23..e31a270ce931 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -208,7 +208,7 @@ void usb_deregister(struct usb_driver *driver) { info("deregistering driver %s", driver->name); - remove_driver (&driver->driver); + driver_unregister (&driver->driver); usbfs_update_special(); } @@ -1394,7 +1394,7 @@ static void __exit usb_exit(void) if (nousb) return; - remove_driver(&usb_generic_driver); + driver_unregister(&usb_generic_driver); usb_major_cleanup(); usbfs_cleanup(); usb_hub_cleanup(); -- cgit v1.2.3 From 7b97a9fd22a391fdc5891f1ef48dbd7f0245d0dc Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 22:39:17 -0700 Subject: driver model (arm): change calls of remove_driver() to driver_unregister(). --- arch/arm/kernel/ecard.c | 2 +- drivers/input/serio/sa1111ps2.c | 2 +- drivers/pcmcia/sa1111_generic.c | 2 +- drivers/usb/host/ohci-sa1111.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/arch/arm/kernel/ecard.c b/arch/arm/kernel/ecard.c index 4cb68c31b86e..2b291c31ff8b 100644 --- a/arch/arm/kernel/ecard.c +++ b/arch/arm/kernel/ecard.c @@ -1114,7 +1114,7 @@ int ecard_register_driver(struct ecard_driver *drv) void ecard_remove_driver(struct ecard_driver *drv) { - remove_driver(&drv->drv); + driver_unregister(&drv->drv); } static int ecard_match(struct device *_dev, struct device_driver *_drv) diff --git a/drivers/input/serio/sa1111ps2.c b/drivers/input/serio/sa1111ps2.c index 502d3c90b864..01dc3c0681f2 100644 --- a/drivers/input/serio/sa1111ps2.c +++ b/drivers/input/serio/sa1111ps2.c @@ -354,7 +354,7 @@ static int __init ps2_init(void) static void __exit ps2_exit(void) { - remove_driver(&ps2_driver.drv); + driver_unregister(&ps2_driver.drv); } module_init(ps2_init); diff --git a/drivers/pcmcia/sa1111_generic.c b/drivers/pcmcia/sa1111_generic.c index 9bf1a6c392d4..7afea0c7b3aa 100644 --- a/drivers/pcmcia/sa1111_generic.c +++ b/drivers/pcmcia/sa1111_generic.c @@ -297,7 +297,7 @@ static int __init sa1111_drv_pcmcia_init(void) static void __exit sa1111_drv_pcmcia_exit(void) { - remove_driver(&pcmcia_driver.drv); + driver_unregister(&pcmcia_driver.drv); } module_init(sa1111_drv_pcmcia_init); diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c index 98996cd62d6e..e5b729e5ad39 100644 --- a/drivers/usb/host/ohci-sa1111.c +++ b/drivers/usb/host/ohci-sa1111.c @@ -413,7 +413,7 @@ static int __init ohci_hcd_sa1111_init (void) static void __exit ohci_hcd_sa1111_cleanup (void) { - remove_driver(&ohci_hcd_sa1111_driver.drv); + driver_unregister(&ohci_hcd_sa1111_driver.drv); } module_init (ohci_hcd_sa1111_init); -- cgit v1.2.3 From cdca2b1ca897470af06970ee5ad0f9270d5cb16d Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Wed, 16 Oct 2002 23:51:44 -0700 Subject: driver model: use list_for_each_safe() when removing devices from a driver. Duh: in driver_detach(), one of the first things we do is remove the device from the driver's list of devices. So, we obviously cannot use ->next. --- drivers/base/bus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 7ef3111f0091..e87a9e825a86 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -174,8 +174,8 @@ static void device_detach(struct device * dev) static void driver_detach(struct device_driver * drv) { - struct list_head * entry; - list_for_each(entry,&drv->devices) { + struct list_head * entry, * next; + list_for_each_safe(entry,next,&drv->devices) { struct device * dev = container_of(entry,struct device,driver_list); if (get_device(dev)) { detach(dev,drv); -- cgit v1.2.3 From 91cfad75250ff827389f735565673c8e45df3f52 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Thu, 17 Oct 2002 21:23:30 -0700 Subject: driver model: make sure we only try to bind drivers to devices that don't have a driver. --- drivers/base/bus.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index e87a9e825a86..49683a1d3898 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -148,8 +148,10 @@ static int driver_attach(struct device_driver * drv) list_for_each(entry,&bus->devices) { struct device * dev = container_of(entry,struct device,bus_list); if (get_device(dev)) { - if (!bus_match(dev,drv) && dev->driver) - devclass_add_device(dev); + if (!dev->driver) { + if (!bus_match(dev,drv) && dev->driver) + devclass_add_device(dev); + } put_device(dev); } } -- cgit v1.2.3