diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index f99bc7f089f1..c66dc4341fa0 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -19,37 +19,98 @@ #include #include "pci.h" -/* - * Dynamic device IDs are disabled for !CONFIG_HOTPLUG - */ - struct pci_dynid { struct list_head node; struct pci_device_id id; }; -#ifdef CONFIG_HOTPLUG - /** - * store_new_id - add a new PCI device ID to this driver and re-probe devices + * pci_add_dynid - add a new PCI device ID to this driver and re-probe devices + * @drv: target pci driver + * @vendor: PCI vendor ID + * @device: PCI device ID + * @subvendor: PCI subvendor ID + * @subdevice: PCI subdevice ID + * @class: PCI class + * @class_mask: PCI class mask + * @driver_data: private driver data + * + * Adds a new dynamic pci device ID to this driver and causes the + * driver to probe for all devices again. @drv must have been + * registered prior to calling this function. + * + * CONTEXT: + * Does GFP_KERNEL allocation. + * + * RETURNS: + * 0 on success, -errno on failure. + */ +int pci_add_dynid(struct pci_driver *drv, + unsigned int vendor, unsigned int device, + unsigned int subvendor, unsigned int subdevice, + unsigned int class, unsigned int class_mask, + unsigned long driver_data) +{ + struct pci_dynid *dynid; + int retval; + + dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); + if (!dynid) + return -ENOMEM; + + dynid->id.vendor = vendor; + dynid->id.device = device; + dynid->id.subvendor = subvendor; + dynid->id.subdevice = subdevice; + dynid->id.class = class; + dynid->id.class_mask = class_mask; + dynid->id.driver_data = driver_data; + + spin_lock(&drv->dynids.lock); + list_add_tail(&dynid->node, &drv->dynids.list); + spin_unlock(&drv->dynids.lock); + + get_driver(&drv->driver); + retval = driver_attach(&drv->driver); + put_driver(&drv->driver); + + return retval; +} + +static void pci_free_dynids(struct pci_driver *drv) +{ + struct pci_dynid *dynid, *n; + + spin_lock(&drv->dynids.lock); + list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { + list_del(&dynid->node); + kfree(dynid); + } + spin_unlock(&drv->dynids.lock); +} + +/* + * Dynamic device ID manipulation via sysfs is disabled for !CONFIG_HOTPLUG + */ +#ifdef CONFIG_HOTPLUG +/** + * store_new_id - sysfs frontend to pci_add_dynid() * @driver: target device driver * @buf: buffer for scanning device ID data * @count: input size * - * Adds a new dynamic pci device ID to this driver, - * and causes the driver to probe for all devices again. + * Allow PCI IDs to be added to an existing driver via sysfs. */ static ssize_t store_new_id(struct device_driver *driver, const char *buf, size_t count) { - struct pci_dynid *dynid; struct pci_driver *pdrv = to_pci_driver(driver); const struct pci_device_id *ids = pdrv->id_table; __u32 vendor, device, subvendor=PCI_ANY_ID, subdevice=PCI_ANY_ID, class=0, class_mask=0; unsigned long driver_data=0; int fields=0; - int retval=0; + int retval; fields = sscanf(buf, "%x %x %x %x %x %x %lx", &vendor, &device, &subvendor, &subdevice, @@ -72,27 +133,8 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) return retval; } - dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); - if (!dynid) - return -ENOMEM; - - dynid->id.vendor = vendor; - dynid->id.device = device; - dynid->id.subvendor = subvendor; - dynid->id.subdevice = subdevice; - dynid->id.class = class; - dynid->id.class_mask = class_mask; - dynid->id.driver_data = driver_data; - - spin_lock(&pdrv->dynids.lock); - list_add_tail(&dynid->node, &pdrv->dynids.list); - spin_unlock(&pdrv->dynids.lock); - - if (get_driver(&pdrv->driver)) { - retval = driver_attach(&pdrv->driver); - put_driver(&pdrv->driver); - } - + retval = pci_add_dynid(pdrv, vendor, device, subvendor, subdevice, + class, class_mask, driver_data); if (retval) return retval; return count; @@ -145,19 +187,6 @@ store_remove_id(struct device_driver *driver, const char *buf, size_t count) } static DRIVER_ATTR(remove_id, S_IWUSR, NULL, store_remove_id); -static void -pci_free_dynids(struct pci_driver *drv) -{ - struct pci_dynid *dynid, *n; - - spin_lock(&drv->dynids.lock); - list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { - list_del(&dynid->node); - kfree(dynid); - } - spin_unlock(&drv->dynids.lock); -} - static int pci_create_newid_file(struct pci_driver *drv) { @@ -186,7 +215,6 @@ static void pci_remove_removeid_file(struct pci_driver *drv) driver_remove_file(&drv->driver, &driver_attr_remove_id); } #else /* !CONFIG_HOTPLUG */ -static inline void pci_free_dynids(struct pci_driver *drv) {} static inline int pci_create_newid_file(struct pci_driver *drv) { return 0; @@ -1106,6 +1134,7 @@ static int __init pci_driver_init(void) postcore_initcall(pci_driver_init); +EXPORT_SYMBOL_GPL(pci_add_dynid); EXPORT_SYMBOL(pci_match_id); EXPORT_SYMBOL(__pci_register_driver); EXPORT_SYMBOL(pci_unregister_driver); diff --git a/include/linux/pci.h b/include/linux/pci.h index da90217a7b0e..d75668317705 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -796,6 +796,11 @@ int __must_check __pci_register_driver(struct pci_driver *, struct module *, void pci_unregister_driver(struct pci_driver *dev); void pci_remove_behind_bridge(struct pci_dev *dev); struct pci_driver *pci_dev_driver(const struct pci_dev *dev); +int pci_add_dynid(struct pci_driver *drv, + unsigned int vendor, unsigned int device, + unsigned int subvendor, unsigned int subdevice, + unsigned int class, unsigned int class_mask, + unsigned long driver_data); const struct pci_device_id *pci_match_id(const struct pci_device_id *ids, struct pci_dev *dev); int pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max,