vmbus: add support for dynamic device id's
authorStephen Hemminger <sthemmin@microsoft.com>
Sat, 3 Dec 2016 20:34:39 +0000 (12:34 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 6 Dec 2016 10:52:49 +0000 (11:52 +0100)
This patch adds sysfs interface to dynamically bind new UUID values
to existing VMBus device. This is useful for generic UIO driver to
act similar to uio_pci_generic.

Signed-off-by: Stephen Hemminger <sthemmin@microsoft.com>
Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/hv/vmbus_drv.c
include/linux/hyperv.h

index 0276d2e..230c62e 100644 (file)
 #include <linux/random.h>
 #include "hyperv_vmbus.h"
 
+struct vmbus_dynid {
+       struct list_head node;
+       struct hv_vmbus_device_id id;
+};
+
 static struct acpi_device  *hv_acpi_dev;
 
 static struct completion probe_event;
@@ -500,7 +505,7 @@ static ssize_t device_show(struct device *dev,
 static DEVICE_ATTR_RO(device);
 
 /* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */
-static struct attribute *vmbus_attrs[] = {
+static struct attribute *vmbus_dev_attrs[] = {
        &dev_attr_id.attr,
        &dev_attr_state.attr,
        &dev_attr_monitor_id.attr,
@@ -528,7 +533,7 @@ static struct attribute *vmbus_attrs[] = {
        &dev_attr_device.attr,
        NULL,
 };
-ATTRIBUTE_GROUPS(vmbus);
+ATTRIBUTE_GROUPS(vmbus_dev);
 
 /*
  * vmbus_uevent - add uevent for our device
@@ -565,10 +570,29 @@ static inline bool is_null_guid(const uuid_le *guid)
  * Return a matching hv_vmbus_device_id pointer.
  * If there is no match, return NULL.
  */
-static const struct hv_vmbus_device_id *hv_vmbus_get_id(
-                                       const struct hv_vmbus_device_id *id,
+static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv,
                                        const uuid_le *guid)
 {
+       const struct hv_vmbus_device_id *id = NULL;
+       struct vmbus_dynid *dynid;
+
+       /* Look at the dynamic ids first, before the static ones */
+       spin_lock(&drv->dynids.lock);
+       list_for_each_entry(dynid, &drv->dynids.list, node) {
+               if (!uuid_le_cmp(dynid->id.guid, *guid)) {
+                       id = &dynid->id;
+                       break;
+               }
+       }
+       spin_unlock(&drv->dynids.lock);
+
+       if (id)
+               return id;
+
+       id = drv->id_table;
+       if (id == NULL)
+               return NULL; /* empty device table */
+
        for (; !is_null_guid(&id->guid); id++)
                if (!uuid_le_cmp(id->guid, *guid))
                        return id;
@@ -576,6 +600,134 @@ static const struct hv_vmbus_device_id *hv_vmbus_get_id(
        return NULL;
 }
 
+/* vmbus_add_dynid - add a new device ID to this driver and re-probe devices */
+static int vmbus_add_dynid(struct hv_driver *drv, uuid_le *guid)
+{
+       struct vmbus_dynid *dynid;
+
+       dynid = kzalloc(sizeof(*dynid), GFP_KERNEL);
+       if (!dynid)
+               return -ENOMEM;
+
+       dynid->id.guid = *guid;
+
+       spin_lock(&drv->dynids.lock);
+       list_add_tail(&dynid->node, &drv->dynids.list);
+       spin_unlock(&drv->dynids.lock);
+
+       return driver_attach(&drv->driver);
+}
+
+static void vmbus_free_dynids(struct hv_driver *drv)
+{
+       struct vmbus_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);
+}
+
+/* Parse string of form: 1b4e28ba-2fa1-11d2-883f-b9a761bde3f */
+static int get_uuid_le(const char *str, uuid_le *uu)
+{
+       unsigned int b[16];
+       int i;
+
+       if (strlen(str) < 37)
+               return -1;
+
+       for (i = 0; i < 36; i++) {
+               switch (i) {
+               case 8: case 13: case 18: case 23:
+                       if (str[i] != '-')
+                               return -1;
+                       break;
+               default:
+                       if (!isxdigit(str[i]))
+                               return -1;
+               }
+       }
+
+       /* unparse little endian output byte order */
+       if (sscanf(str,
+                  "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x",
+                  &b[3], &b[2], &b[1], &b[0],
+                  &b[5], &b[4], &b[7], &b[6], &b[8], &b[9],
+                  &b[10], &b[11], &b[12], &b[13], &b[14], &b[15]) != 16)
+               return -1;
+
+       for (i = 0; i < 16; i++)
+               uu->b[i] = b[i];
+       return 0;
+}
+
+/*
+ * store_new_id - sysfs frontend to vmbus_add_dynid()
+ *
+ * Allow GUIDs to be added to an existing driver via sysfs.
+ */
+static ssize_t new_id_store(struct device_driver *driver, const char *buf,
+                           size_t count)
+{
+       struct hv_driver *drv = drv_to_hv_drv(driver);
+       uuid_le guid = NULL_UUID_LE;
+       ssize_t retval;
+
+       if (get_uuid_le(buf, &guid) != 0)
+               return -EINVAL;
+
+       if (hv_vmbus_get_id(drv, &guid))
+               return -EEXIST;
+
+       retval = vmbus_add_dynid(drv, &guid);
+       if (retval)
+               return retval;
+       return count;
+}
+static DRIVER_ATTR_WO(new_id);
+
+/*
+ * store_remove_id - remove a PCI device ID from this driver
+ *
+ * Removes a dynamic pci device ID to this driver.
+ */
+static ssize_t remove_id_store(struct device_driver *driver, const char *buf,
+                              size_t count)
+{
+       struct hv_driver *drv = drv_to_hv_drv(driver);
+       struct vmbus_dynid *dynid, *n;
+       uuid_le guid = NULL_UUID_LE;
+       size_t retval = -ENODEV;
+
+       if (get_uuid_le(buf, &guid))
+               return -EINVAL;
+
+       spin_lock(&drv->dynids.lock);
+       list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
+               struct hv_vmbus_device_id *id = &dynid->id;
+
+               if (!uuid_le_cmp(id->guid, guid)) {
+                       list_del(&dynid->node);
+                       kfree(dynid);
+                       retval = count;
+                       break;
+               }
+       }
+       spin_unlock(&drv->dynids.lock);
+
+       return retval;
+}
+static DRIVER_ATTR_WO(remove_id);
+
+static struct attribute *vmbus_drv_attrs[] = {
+       &driver_attr_new_id.attr,
+       &driver_attr_remove_id.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(vmbus_drv);
 
 
 /*
@@ -590,7 +742,7 @@ static int vmbus_match(struct device *device, struct device_driver *driver)
        if (is_hvsock_channel(hv_dev->channel))
                return drv->hvsock;
 
-       if (hv_vmbus_get_id(drv->id_table, &hv_dev->dev_type))
+       if (hv_vmbus_get_id(drv, &hv_dev->dev_type))
                return 1;
 
        return 0;
@@ -607,7 +759,7 @@ static int vmbus_probe(struct device *child_device)
        struct hv_device *dev = device_to_hv_device(child_device);
        const struct hv_vmbus_device_id *dev_id;
 
-       dev_id = hv_vmbus_get_id(drv->id_table, &dev->dev_type);
+       dev_id = hv_vmbus_get_id(drv, &dev->dev_type);
        if (drv->probe) {
                ret = drv->probe(dev, dev_id);
                if (ret != 0)
@@ -684,7 +836,8 @@ static struct bus_type  hv_bus = {
        .remove =               vmbus_remove,
        .probe =                vmbus_probe,
        .uevent =               vmbus_uevent,
-       .dev_groups =           vmbus_groups,
+       .dev_groups =           vmbus_dev_groups,
+       .drv_groups =           vmbus_drv_groups,
 };
 
 struct onmessage_work_context {
@@ -905,6 +1058,9 @@ int __vmbus_driver_register(struct hv_driver *hv_driver, struct module *owner, c
        hv_driver->driver.mod_name = mod_name;
        hv_driver->driver.bus = &hv_bus;
 
+       spin_lock_init(&hv_driver->dynids.lock);
+       INIT_LIST_HEAD(&hv_driver->dynids.list);
+
        ret = driver_register(&hv_driver->driver);
 
        return ret;
@@ -923,8 +1079,10 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver)
 {
        pr_info("unregistering driver %s\n", hv_driver->name);
 
-       if (!vmbus_exists())
+       if (!vmbus_exists()) {
                driver_unregister(&hv_driver->driver);
+               vmbus_free_dynids(hv_driver);
+       }
 }
 EXPORT_SYMBOL_GPL(vmbus_driver_unregister);
 
index 35053f9..42fe43f 100644 (file)
@@ -1119,6 +1119,12 @@ struct hv_driver {
 
        struct device_driver driver;
 
+       /* dynamic device GUID's */
+       struct  {
+               spinlock_t lock;
+               struct list_head list;
+       } dynids;
+
        int (*probe)(struct hv_device *, const struct hv_vmbus_device_id *);
        int (*remove)(struct hv_device *);
        void (*shutdown)(struct hv_device *);