Add support for overriding the default driver for a VMBus device in the same way that it can be done for PCI devices. This patch adds the /sys/bus/vmbus/devices/.../driver_override file and the logic for matching. This is used by driverctl tool to do driver override for VMBus devices. https://gitlab.com/driverctl/driverctl Signed-off-by: Stephen Hemminger <sthemmin@xxxxxxxxxxxxx> --- Documentation/ABI/testing/sysfs-bus-vmbus | 6 ++ drivers/hv/vmbus_drv.c | 115 +++++++++++++++++++++++++----- include/linux/hyperv.h | 1 + 3 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-bus-vmbus diff --git a/Documentation/ABI/testing/sysfs-bus-vmbus b/Documentation/ABI/testing/sysfs-bus-vmbus new file mode 100644 index 000000000000..4f57b8f8b767 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-vmbus @@ -0,0 +1,6 @@ +What: /sys/bus/vmbus/devices/.../driver_override +Date: April 2018 +Contact: Stephen Hemminger <sthemmin@xxxxxxxxxxxxx> +Description: + This file allows the driver for a device to be specified which + will override standard static and dynamic ID matching. diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index bc65c4d79c1f..41e33170649a 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -483,6 +483,54 @@ static ssize_t device_show(struct device *dev, } static DEVICE_ATTR_RO(device); +static ssize_t driver_override_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hv_device *hv_dev = device_to_hv_device(dev); + char *driver_override, *old, *cp; + + /* We need to keep extra room for a newline */ + if (count >= (PAGE_SIZE - 1)) + return -EINVAL; + + driver_override = kstrndup(buf, count, GFP_KERNEL); + if (!driver_override) + return -ENOMEM; + + cp = strchr(driver_override, '\n'); + if (cp) + *cp = '\0'; + + device_lock(dev); + old = hv_dev->driver_override; + if (strlen(driver_override)) { + hv_dev->driver_override = driver_override; + } else { + kfree(driver_override); + hv_dev->driver_override = NULL; + } + device_unlock(dev); + + kfree(old); + + return count; +} + +static ssize_t driver_override_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hv_device *hv_dev = device_to_hv_device(dev); + ssize_t len; + + device_lock(dev); + len = snprintf(buf, PAGE_SIZE, "%s\n", hv_dev->driver_override); + device_unlock(dev); + + return len; +} +static DEVICE_ATTR_RW(driver_override); + /* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */ static struct attribute *vmbus_dev_attrs[] = { &dev_attr_id.attr, @@ -510,6 +558,7 @@ static struct attribute *vmbus_dev_attrs[] = { &dev_attr_channel_vp_mapping.attr, &dev_attr_vendor.attr, &dev_attr_device.attr, + &dev_attr_driver_override.attr, NULL, }; ATTRIBUTE_GROUPS(vmbus_dev); @@ -545,17 +594,26 @@ static inline bool is_null_guid(const uuid_le *guid) return true; } -/* - * 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(struct hv_driver *drv, - const uuid_le *guid) +static const struct hv_vmbus_device_id * +hv_vmbus_dev_match(const struct hv_vmbus_device_id *id, const uuid_le *guid) + +{ + if (id == NULL) + return NULL; /* empty device table */ + + for (; !is_null_guid(&id->guid); id++) + if (!uuid_le_cmp(id->guid, *guid)) + return id; + + return NULL; +} + +static const struct hv_vmbus_device_id * +hv_vmbus_dynid_match(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)) { @@ -565,18 +623,37 @@ static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv, } spin_unlock(&drv->dynids.lock); - if (id) - return id; + return id; +} - id = drv->id_table; - if (id == NULL) - return NULL; /* empty device table */ +static const struct hv_vmbus_device_id vmbus_device_null = { + .guid = NULL_UUID_LE, +}; - for (; !is_null_guid(&id->guid); id++) - if (!uuid_le_cmp(id->guid, *guid)) - return id; +/* + * 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(struct hv_driver *drv, + struct hv_device *dev) +{ + const uuid_le *guid = &dev->dev_type; + const struct hv_vmbus_device_id *id; - return NULL; + /* When driver_override is set, only bind to the matching driver */ + if (dev->driver_override && strcmp(dev->driver_override, drv->name)) + return NULL; + + /* Look at the dynamic ids first, before the static ones */ + id = hv_vmbus_dynid_match(drv, guid); + if (!id) + id = hv_vmbus_dev_match(drv->id_table, guid); + + /* driver_override will always match, send a dummy id */ + if (!id && dev->driver_override) + id = &vmbus_device_null; + + return id; } /* vmbus_add_dynid - add a new device ID to this driver and re-probe devices */ @@ -625,7 +702,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf, if (retval) return retval; - if (hv_vmbus_get_id(drv, &guid)) + if (hv_vmbus_dynid_match(drv, &guid)) return -EEXIST; retval = vmbus_add_dynid(drv, &guid); @@ -690,7 +767,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, &hv_dev->dev_type)) + if (hv_vmbus_get_id(drv, hv_dev)) return 1; return 0; @@ -707,7 +784,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, &dev->dev_type); + dev_id = hv_vmbus_get_id(drv, dev); if (drv->probe) { ret = drv->probe(dev, dev_id); if (ret != 0) diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2048f3c3b68a..92ad9bfd2ff0 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1075,6 +1075,7 @@ struct hv_device { u16 device_id; struct device device; + char *driver_override; /* Driver name to force a match */ struct vmbus_channel *channel; struct kset *channels_kset; -- 2.16.3 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel