From d6022129c2de4e47f258013743809a3da69edce4 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Mon, 3 Jun 2002 05:48:44 -0700 Subject: device model udpate: - make sure drv->devices is initialized on registration (from Peter Osterlund) - add remove_driver for forcing removal of driver There was a potential race with the module unload code. When a pci driver was unloaded, it would call pci_unregister_driver, which would simply call put_driver. If the driver's refcount wasn't 0, it wouldn't unbind it from devices, but the module unload would still continue. If something tried to access the driver later (since everyone thinks its still there), Bad Things would happen. This fixes it until there can be tighter integration between the device model and module unload code. --- drivers/base/driver.c | 35 ++++++++++++++++++++++++++--------- include/linux/device.h | 1 + 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/drivers/base/driver.c b/drivers/base/driver.c index b640347eeeda..820d4bac5651 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -3,6 +3,8 @@ * */ +#define DEBUG 0 + #include #include #include @@ -67,6 +69,7 @@ int driver_register(struct device_driver * drv) get_bus(drv->bus); atomic_set(&drv->refcount,2); rwlock_init(&drv->lock); + INIT_LIST_HEAD(&drv->devices); write_lock(&drv->bus->lock); list_add(&drv->bus_list,&drv->bus->drivers); write_unlock(&drv->bus->lock); @@ -76,16 +79,8 @@ int driver_register(struct device_driver * drv) return 0; } -/** - * put_driver - decrement driver's refcount and clean up if necessary - * @drv: driver in question - */ -void put_driver(struct device_driver * drv) +static void __remove_driver(struct device_driver * drv) { - if (!atomic_dec_and_lock(&drv->refcount,&device_lock)) - return; - spin_unlock(&device_lock); - if (drv->bus) { pr_debug("Unregistering driver '%s' from bus '%s'\n",drv->name,drv->bus->name); @@ -101,6 +96,28 @@ void put_driver(struct device_driver * drv) drv->release(drv); } +void remove_driver(struct device_driver * drv) +{ + spin_lock(&device_lock); + atomic_set(&drv->refcount,0); + 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; + spin_unlock(&device_lock); + + __remove_driver(drv); +} + EXPORT_SYMBOL(driver_for_each_dev); EXPORT_SYMBOL(driver_register); EXPORT_SYMBOL(put_driver); +EXPORT_SYMBOL(remove_driver); diff --git a/include/linux/device.h b/include/linux/device.h index 2186f85a8beb..1c35f87abc9e 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -118,6 +118,7 @@ static inline 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); extern int driver_for_each_dev(struct device_driver * drv, void * data, int (*callback)(struct device * dev, void * data)); -- cgit v1.2.3 From 96d36ec0ebe75b6a759b899813c74eeb011af7c2 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Mon, 3 Jun 2002 05:51:12 -0700 Subject: Do manual traversing of drivers' devices list when unbinding the driver. driver_unbind was called when drv->refcount == 0. It would call driver_for_each_dev to do the unbinding The first thing that would do was get_device, which... BUG()'d if drv->refcount == 0. Duh. --- drivers/base/core.c | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/drivers/base/core.c b/drivers/base/core.c index 573719ba5e55..5b29a4943f7f 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -5,6 +5,8 @@ * 2002 Open Source Development Lab */ +#define DEBUG 0 + #include #include #include @@ -118,9 +120,8 @@ int driver_bind(struct device_driver * drv) return bus_for_each_dev(drv->bus,drv,do_driver_bind); } -static int do_driver_unbind(struct device * dev, void * data) +static int do_driver_unbind(struct device * dev, struct device_driver * drv) { - struct device_driver * drv = (struct device_driver *)data; lock_device(dev); if (dev->driver == drv) { dev->driver = NULL; @@ -134,7 +135,31 @@ static int do_driver_unbind(struct device * dev, void * data) void driver_unbind(struct device_driver * drv) { - driver_for_each_dev(drv,drv,do_driver_unbind); + struct device * next; + struct device * dev = NULL; + struct list_head * node; + int error = 0; + + read_lock(&drv->lock); + node = drv->devices.next; + while (node != &drv->devices) { + next = list_entry(node,struct device,driver_list); + get_device(next); + read_unlock(&drv->lock); + + if (dev) + put_device(dev); + dev = next; + if ((error = do_driver_unbind(dev,drv))) { + put_device(dev); + break; + } + read_lock(&drv->lock); + node = dev->driver_list.next; + } + read_unlock(&drv->lock); + if (dev) + put_device(dev); } /** @@ -178,8 +203,8 @@ int device_register(struct device *dev) } spin_unlock(&device_lock); - DBG("DEV: registering device: ID = '%s', name = %s\n", - dev->bus_id, dev->name); + pr_debug("DEV: registering device: ID = '%s', name = %s\n", + dev->bus_id, dev->name); if ((error = device_make_dir(dev))) goto register_done; @@ -212,8 +237,8 @@ void put_device(struct device * dev) list_del_init(&dev->g_list); spin_unlock(&device_lock); - DBG("DEV: Unregistering device. ID = '%s', name = '%s'\n", - dev->bus_id,dev->name); + pr_debug("DEV: Unregistering device. ID = '%s', name = '%s'\n", + dev->bus_id,dev->name); /* Notify the platform of the removal, in case they * need to do anything... -- cgit v1.2.3 From 52d16b41d81e7597369a62278e9b272f0a20d625 Mon Sep 17 00:00:00 2001 From: Patrick Mochel Date: Mon, 3 Jun 2002 05:53:50 -0700 Subject: PCI driver mgmt: - Make sure proper pci id is passed to probe() - make sure pci_dev->driver is set and reset on driver registration/unregistration - call remove_driver to force unload of driver on unregistration --- drivers/pci/pci-driver.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 0109b2fad2c6..e5bad37436f9 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -38,23 +38,35 @@ pci_match_device(const struct pci_device_id *ids, const struct pci_dev *dev) static int pci_device_probe(struct device * dev) { int error = 0; - - struct pci_driver * drv = list_entry(dev->driver,struct pci_driver,driver); - struct pci_dev * pci_dev = list_entry(dev,struct pci_dev,dev); - - if (drv->probe) - error = drv->probe(pci_dev,drv->id_table); - return error > 0 ? 0 : -ENODEV; + struct pci_driver *drv; + struct pci_dev *pci_dev; + + drv = list_entry(dev->driver, struct pci_driver, driver); + pci_dev = list_entry(dev, struct pci_dev, dev); + + if (drv->probe) { + const struct pci_device_id *id; + + id = pci_match_device(drv->id_table, pci_dev); + if (id) + error = drv->probe(pci_dev, id); + if (error >= 0) { + pci_dev->driver = drv; + error = 0; + } + } + return error; } static int pci_device_remove(struct device * dev) { struct pci_dev * pci_dev = list_entry(dev,struct pci_dev,dev); + struct pci_driver * drv = pci_dev->driver; - if (dev->driver) { - struct pci_driver * drv = list_entry(dev->driver,struct pci_driver,driver); + if (drv) { if (drv->remove) drv->remove(pci_dev); + pci_dev->driver = NULL; } return 0; } @@ -124,7 +136,7 @@ pci_register_driver(struct pci_driver *drv) void pci_unregister_driver(struct pci_driver *drv) { - put_driver(&drv->driver); + remove_driver(&drv->driver); } static struct pci_driver pci_compat_driver = { -- cgit v1.2.3