diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 0276d2ef06ee..230c62e7f567 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -45,6 +45,11 @@ #include #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/ */ -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); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 35053f99522b..42fe43fb0c80 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 *);