|
@@ -19,37 +19,98 @@
|
|
|
#include <linux/cpu.h>
|
|
|
#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
|
|
|
+/**
|
|
|
+ * 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 - add a new PCI device ID to this driver and re-probe devices
|
|
|
+ * 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);
|