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.
https://gitlab.com/driverctl/driverctl

Signed-off-by: Stephen Hemminger <sthem...@microsoft.com>
---
v2 - no changes since last version.
     this patch seems to have gotten lost.
     driver development list really needs to start using patchwork!

 Documentation/ABI/testing/sysfs-bus-vmbus |  21 ++++
 drivers/hv/vmbus_drv.c                    | 115 ++++++++++++++++++----
 include/linux/hyperv.h                    |   1 +
 3 files changed, 118 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..91e6c065973c
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-vmbus
@@ -0,0 +1,21 @@
+What:          /sys/bus/vmbus/devices/.../driver_override
+Date:          August 2019
+Contact:       Stephen Hemminger <sthem...@microsoft.com>
+Description:
+               This file allows the driver for a device to be specified which
+               will override standard static and dynamic ID matching.  When
+               specified, only a driver with a name matching the value written
+               to driver_override will have an opportunity to bind to the
+               device.  The override is specified by writing a string to the
+               driver_override file (echo uio_hv_generic > driver_override) and
+               may be cleared with an empty string (echo > driver_override).
+               This returns the device to standard matching rules binding.
+               Writing to driver_override does not automatically unbind the
+               device from its current driver or make any attempt to
+               automatically load the specified driver.  If no driver with a
+               matching name is currently loaded in the kernel, the device
+               will not bind to any driver.  This also allows devices to
+               opt-out of driver binding using a driver_override name such as
+               "none".  Only a single driver may be specified in the override,
+               there is no support for parsing delimiters.
+
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index b1b548a21f91..e6d8fdac6d8b 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -498,6 +498,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,
@@ -528,6 +576,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);
@@ -563,17 +612,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)) {
@@ -583,18 +641,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 */
@@ -643,7 +720,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);
@@ -708,7 +785,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;
@@ -725,7 +802,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 efda23cf32c7..2c3798bcb01c 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1125,6 +1125,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.18.0

_______________________________________________
devel mailing list
de...@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

Reply via email to