From: Stephen Hemminger <sthemmin@xxxxxxxxxxxxx> This patch adds sysfs interface to dynamically bind new UUID values to existing VMBus device. This is useful for a generic UIO driver to act similar to uio_pci_generic. Signed-off-by: Stephen Hemminger <sthemmin@xxxxxxxxxxxxx> --- drivers/hv/vmbus_drv.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/hyperv.h | 6 ++ 2 files changed, 168 insertions(+), 7 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index a259e18..74ce2e5 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -45,6 +45,11 @@ #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 @@ -576,7 +581,139 @@ 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); + + pr_info("%s: added id %pUl\n", drv->name, &dynid->id.guid.b); + + 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 store_new_id(struct device_driver *driver, const char *buf, + size_t count) +{ + struct hv_driver *drv = drv_to_hv_drv(driver); + const struct hv_vmbus_device_id *ids = drv->id_table; + uuid_le guid = NULL_UUID_LE; + ssize_t retval; + + if (get_uuid_le(buf, &guid) != 0) + return -EINVAL; + + if (hv_vmbus_get_id(ids, &guid)) + return -EEXIST; + + retval = vmbus_add_dynid(drv, &guid); + if (retval) + return retval; + return count; +} +static DRIVER_ATTR(new_id, S_IWUSR, NULL, store_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 store_remove_id(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; + + pr_info("%s: removed id %pUl\n", drv->name, &guid.b); + break; + } + } + spin_unlock(&drv->dynids.lock); + + return retval; +} +static DRIVER_ATTR(remove_id, S_IWUSR, NULL, store_remove_id); + +static struct attribute *vmbus_drv_attrs[] = { + &driver_attr_new_id.attr, + &driver_attr_remove_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(vmbus_drv); /* * vmbus_match - Attempt to match the specified device to the specified driver @@ -585,15 +722,27 @@ static int vmbus_match(struct device *device, struct device_driver *driver) { struct hv_driver *drv = drv_to_hv_drv(driver); struct hv_device *hv_dev = device_to_hv_device(device); + const struct hv_vmbus_device_id *found_id = NULL; + struct vmbus_dynid *dynid; /* The hv_sock driver handles all hv_sock offers. */ if (is_hvsock_channel(hv_dev->channel)) return drv->hvsock; - if (hv_vmbus_get_id(drv->id_table, &hv_dev->dev_type)) - return 1; + /* 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, hv_dev->dev_type)) { + found_id = &dynid->id; + break; + } + } + spin_unlock(&drv->dynids.lock); - return 0; + if (!found_id) + found_id = hv_vmbus_get_id(drv->id_table, &hv_dev->dev_type); + + return found_id != NULL; } /* @@ -684,7 +833,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 +1055,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 +1076,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); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 6824556..737ac0d 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -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 *); -- 2.9.3 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel