PCI: add remove_id sysfs entry
authorChris Wright <chrisw@sous-sol.org>
Tue, 24 Feb 2009 05:52:23 +0000 (21:52 -0800)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Fri, 20 Mar 2009 17:48:11 +0000 (10:48 -0700)
This adds a remove_id sysfs entry to allow users of new_id to later
remove the added dynid.  One use case is management tools that want to
dynamically bind/unbind devices to pci-stub driver while devices are
assigned to KVM guests.  Rather than having to track which driver was
originally bound to the driver, a mangement tool can simply:

Guest uses device

Signed-off-by: Chris Wright <chrisw@sous-sol.org>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Documentation/ABI/testing/sysfs-bus-pci
drivers/pci/pci-driver.c

index e638e15..3d29793 100644 (file)
@@ -41,6 +41,22 @@ Description:
                for the device and attempt to bind to it.  For example:
                # echo "8086 10f5" > /sys/bus/pci/drivers/foo/new_id
 
+What:          /sys/bus/pci/drivers/.../remove_id
+Date:          February 2009
+Contact:       Chris Wright <chrisw@sous-sol.org>
+Description:
+               Writing a device ID to this file will remove an ID
+               that was dynamically added via the new_id sysfs entry.
+               The format for the device ID is:
+               VVVV DDDD SVVV SDDD CCCC MMMM.  That is Vendor ID, Device
+               ID, Subsystem Vendor ID, Subsystem Device ID, Class,
+               and Class Mask.  The Vendor ID and Device ID fields are
+               required, the rest are optional.  After successfully
+               removing an ID, the driver will no longer support the
+               device.  This is useful to ensure auto probing won't
+               match the driver to the device.  For example:
+               # echo "8086 10f5" > /sys/bus/pci/drivers/foo/remove_id
+
 What:          /sys/bus/pci/devices/.../vpd
 Date:          February 2008
 Contact:       Ben Hutchings <bhutchings@solarflare.com>
index 93eac14..87a5ddb 100644 (file)
@@ -99,6 +99,52 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count)
 }
 static DRIVER_ATTR(new_id, S_IWUSR, NULL, store_new_id);
 
+/**
+ * store_remove_id - remove a PCI device ID from this driver
+ * @driver: target device driver
+ * @buf: buffer for scanning device ID data
+ * @count: input size
+ *
+ * Removes a dynamic pci device ID to this driver.
+ */
+static ssize_t
+store_remove_id(struct device_driver *driver, const char *buf, size_t count)
+{
+       struct pci_dynid *dynid, *n;
+       struct pci_driver *pdrv = to_pci_driver(driver);
+       __u32 vendor, device, subvendor = PCI_ANY_ID,
+               subdevice = PCI_ANY_ID, class = 0, class_mask = 0;
+       int fields = 0;
+       int retval = -ENODEV;
+
+       fields = sscanf(buf, "%x %x %x %x %x %x",
+                       &vendor, &device, &subvendor, &subdevice,
+                       &class, &class_mask);
+       if (fields < 2)
+               return -EINVAL;
+
+       spin_lock(&pdrv->dynids.lock);
+       list_for_each_entry_safe(dynid, n, &pdrv->dynids.list, node) {
+               struct pci_device_id *id = &dynid->id;
+               if ((id->vendor == vendor) &&
+                   (id->device == device) &&
+                   (subvendor == PCI_ANY_ID || id->subvendor == subvendor) &&
+                   (subdevice == PCI_ANY_ID || id->subdevice == subdevice) &&
+                   !((id->class ^ class) & class_mask)) {
+                       list_del(&dynid->node);
+                       kfree(dynid);
+                       retval = 0;
+                       break;
+               }
+       }
+       spin_unlock(&pdrv->dynids.lock);
+
+       if (retval)
+               return retval;
+       return count;
+}
+static DRIVER_ATTR(remove_id, S_IWUSR, NULL, store_remove_id);
+
 static void
 pci_free_dynids(struct pci_driver *drv)
 {
@@ -125,6 +171,20 @@ static void pci_remove_newid_file(struct pci_driver *drv)
 {
        driver_remove_file(&drv->driver, &driver_attr_new_id);
 }
+
+static int
+pci_create_removeid_file(struct pci_driver *drv)
+{
+       int error = 0;
+       if (drv->probe != NULL)
+               error = driver_create_file(&drv->driver,&driver_attr_remove_id);
+       return error;
+}
+
+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)
@@ -132,6 +192,11 @@ static inline int pci_create_newid_file(struct pci_driver *drv)
        return 0;
 }
 static inline void pci_remove_newid_file(struct pci_driver *drv) {}
+static inline int pci_create_removeid_file(struct pci_driver *drv)
+{
+       return 0;
+}
+static inline void pci_remove_removeid_file(struct pci_driver *drv) {}
 #endif
 
 /**
@@ -852,13 +917,23 @@ int __pci_register_driver(struct pci_driver *drv, struct module *owner,
        /* register with core */
        error = driver_register(&drv->driver);
        if (error)
-               return error;
+               goto out;
 
        error = pci_create_newid_file(drv);
        if (error)
-               driver_unregister(&drv->driver);
+               goto out_newid;
 
+       error = pci_create_removeid_file(drv);
+       if (error)
+               goto out_removeid;
+out:
        return error;
+
+out_removeid:
+       pci_remove_newid_file(drv);
+out_newid:
+       driver_unregister(&drv->driver);
+       goto out;
 }
 
 /**
@@ -874,6 +949,7 @@ int __pci_register_driver(struct pci_driver *drv, struct module *owner,
 void
 pci_unregister_driver(struct pci_driver *drv)
 {
+       pci_remove_removeid_file(drv);
        pci_remove_newid_file(drv);
        driver_unregister(&drv->driver);
        pci_free_dynids(drv);