From: mchristi@redhat.com <mchristi@redhat.com> Date: Thu, 28 Aug 2008 13:43:32 -0500 Subject: [misc] driver core: port bus notifiers Message-id: 1219949016-15055-2-git-send-email-mchristi@redhat.com O-Subject: [RHEL 5.3 PATCH 1/5] driver core: port bus notifiers Bugzilla: 438761 RH-Acked-by: Doug Ledford <dledford@redhat.com> From: Mike Christie <mchristi@redhat.com> RHEL 5.2 doesn't have support for bus notifications, which is needed for SCSI Hardware Handlers. This ports them to with kabi workarounds. Patch checked with check-kabi patch. Upstream commit: http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=116af378201ef793424cd10508ccf18b06d8a021 The bus_type_nb related code is a workaround for KABI where for upstream the bus_notifier field was added to the bus_type: struct bus_type { struct klist klist_devices; struct klist klist_drivers; struct blocking_notifier_head bus_notifier; and we could not add it. diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 420edd1..2643bf8 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -671,6 +671,78 @@ static void klist_drivers_put(struct klist_node *n) put_driver(drv); } +#define NUM_BUCKETS 64 +#define MASK_BUCKETS (NUM_BUCKETS - 1) +static struct list_head _bus_buckets[NUM_BUCKETS]; +struct bus_type_nb { + struct list_head list; + struct bus_type *bus; + struct blocking_notifier_head notifier; +}; + +static void init_buckets(struct list_head *buckets) +{ + unsigned int i; + + for (i = 0; i < NUM_BUCKETS; i++) + INIT_LIST_HEAD(buckets + i); +} + +static unsigned int hash_str(const void *bus) +{ + const unsigned int hash_mult = 2654435387U; + unsigned int h = 0; + char *str = (char *) &bus; + int i; + + for (i = 0; i < sizeof(void *); i++) + h = (h + (unsigned int) str[i]) * hash_mult; + + return h & MASK_BUCKETS; +} + +static struct bus_type_nb *__get_cell(const void *bus) +{ + struct bus_type_nb *cell; + unsigned int h = hash_str(bus); + + list_for_each_entry(cell, _bus_buckets + h, list) + if (cell->bus == bus) + return cell; + return NULL; +} + +static struct blocking_notifier_head *alloc_save_notifier_for_bus(struct bus_type *bus) +{ + struct bus_type_nb *cell = __get_cell(bus); + + if (cell) { + printk(KERN_ERR "bus %p trying to reallocate notifier head\n", bus); + return NULL; + } + cell = kzalloc(sizeof(*cell), GFP_KERNEL); + if (!cell) + return NULL; + cell->bus = bus; + list_add(&cell->list, _bus_buckets + hash_str(bus)); + return &cell->notifier; +} + +static void free_notifier_for_bus(struct bus_type *bus) +{ + struct bus_type_nb *cell = __get_cell(bus); + if (cell) { + list_del(&cell->list); + kfree(cell); + } +} + +struct blocking_notifier_head *get_notifier_for_bus(struct bus_type *bus) +{ + struct bus_type_nb *cell = __get_cell(bus); + return cell ? &cell->notifier : NULL; +} + /** * bus_register - register a bus with the system. * @bus: bus. @@ -681,7 +753,14 @@ static void klist_drivers_put(struct klist_node *n) */ int bus_register(struct bus_type * bus) { - int retval; + int retval = -ENOMEM; + struct blocking_notifier_head *notifier_head; + + notifier_head = alloc_save_notifier_for_bus(bus); + if (!notifier_head) + goto out; + + BLOCKING_INIT_NOTIFIER_HEAD(notifier_head); retval = kobject_set_name(&bus->subsys.kset.kobj, "%s", bus->name); if (retval) @@ -720,6 +799,27 @@ out: return retval; } +int bus_register_notifier(struct bus_type *bus, struct notifier_block *nb) +{ + struct blocking_notifier_head *notifier_head; + + notifier_head = get_notifier_for_bus(bus); + if (!notifier_head) + return 0; + return blocking_notifier_chain_register(notifier_head, nb); +} +EXPORT_SYMBOL_GPL(bus_register_notifier); + +int bus_unregister_notifier(struct bus_type *bus, struct notifier_block *nb) +{ + struct blocking_notifier_head *notifier_head; + + notifier_head = get_notifier_for_bus(bus); + if (!notifier_head) + return 0; + return blocking_notifier_chain_unregister(notifier_head, nb); +} +EXPORT_SYMBOL_GPL(bus_unregister_notifier); /** * bus_unregister - remove a bus from the system @@ -731,6 +831,7 @@ out: void bus_unregister(struct bus_type * bus) { pr_debug("bus %s: unregistering\n", bus->name); + free_notifier_for_bus(bus); bus_remove_attrs(bus); kset_unregister(&bus->drivers); kset_unregister(&bus->devices); @@ -739,10 +840,10 @@ void bus_unregister(struct bus_type * bus) int __init buses_init(void) { + init_buckets(_bus_buckets); return subsystem_register(&bus_subsys); } - EXPORT_SYMBOL_GPL(bus_for_each_dev); EXPORT_SYMBOL_GPL(bus_find_device); EXPORT_SYMBOL_GPL(bus_for_each_drv); diff --git a/drivers/base/core.c b/drivers/base/core.c index fe17f00..923ebef 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -309,6 +309,16 @@ int device_add(struct device *dev) if ((error = kobject_add(&dev->kobj))) goto Error; + /* notify clients of device entry (new way) */ + if (dev->bus) { + struct blocking_notifier_head *notifier_head; + + notifier_head = get_notifier_for_bus(dev->bus); + if (notifier_head) + blocking_notifier_call_chain(notifier_head, + BUS_NOTIFY_ADD_DEVICE, dev); + } + dev->uevent_attr.attr.name = "uevent"; dev->uevent_attr.attr.mode = S_IWUSR; if (dev->driver) @@ -368,6 +378,7 @@ int device_add(struct device *dev) /* notify platform of device entry */ if (platform_notify) platform_notify(dev); + Done: kfree(class_name); put_device(dev); @@ -487,6 +498,15 @@ void device_del(struct device * dev) if (platform_notify_remove) platform_notify_remove(dev); + if (dev->bus) { + struct blocking_notifier_head *notifier_head; + + notifier_head = get_notifier_for_bus(dev->bus); + if (notifier_head) + blocking_notifier_call_chain(notifier_head, + BUS_NOTIFY_DEL_DEVICE, dev); + } + device_pm_remove(dev); kobject_uevent(&dev->kobj, KOBJ_REMOVE); kobject_del(&dev->kobj); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index e9c2e4c..bce7b65 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -45,6 +45,15 @@ void device_bind_driver(struct device * dev) pr_debug("bound device '%s' to driver '%s'\n", dev->bus_id, dev->driver->name); + if (dev->bus) { + struct blocking_notifier_head *notifier_head; + + notifier_head = get_notifier_for_bus(dev->bus); + if (notifier_head) + blocking_notifier_call_chain(notifier_head, + BUS_NOTIFY_BOUND_DRIVER, dev); + } + klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices); sysfs_create_link(&dev->driver->kobj, &dev->kobj, kobject_name(&dev->kobj)); @@ -212,6 +221,14 @@ static void __device_release_driver(struct device * dev) sysfs_remove_link(&dev->kobj, "driver"); klist_remove(&dev->knode_driver); + if (dev->bus) { + struct blocking_notifier_head *notifier_head; + + notifier_head = get_notifier_for_bus(dev->bus); + if (notifier_head) + blocking_notifier_call_chain(notifier_head, + BUS_NOTIFY_UNBIND_DRIVER, dev); + } if (dev->bus && dev->bus->remove) dev->bus->remove(dev); else if (drv->remove) diff --git a/include/linux/device.h b/include/linux/device.h index 2a00e3a..d561172 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -71,6 +71,29 @@ int bus_for_each_drv(struct bus_type * bus, struct device_driver * start, void * data, int (*fn)(struct device_driver *, void *)); +/* + * Bus notifiers: Get notified of addition/removal of devices + * and binding/unbinding of drivers to devices. + * In the long run, it should be a replacement for the platform + * notify hooks. + */ +struct notifier_block; + +extern int bus_register_notifier(struct bus_type *bus, + struct notifier_block *nb); +extern int bus_unregister_notifier(struct bus_type *bus, + struct notifier_block *nb); +extern struct blocking_notifier_head *get_notifier_for_bus(struct bus_type *bus); + +/* All 4 notifers below get called with the target struct device * + * as an argument. Note that those functions are likely to be called + * with the device semaphore held in the core, so be careful. + */ +#define BUS_NOTIFY_ADD_DEVICE 0x00000001 /* device added */ +#define BUS_NOTIFY_DEL_DEVICE 0x00000002 /* device removed */ +#define BUS_NOTIFY_BOUND_DRIVER 0x00000003 /* driver bound to device */ +#define BUS_NOTIFY_UNBIND_DRIVER 0x00000004 /* about to be unbound */ + /* driverfs interface for exporting bus attributes */ struct bus_attribute {