]> git.baikalelectronics.ru Git - kernel.git/commitdiff
vmbus: add driver_override support
authorStephen Hemminger <stephen@networkplumber.org>
Fri, 10 Aug 2018 23:06:08 +0000 (23:06 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 12 Sep 2018 07:46:46 +0000 (09:46 +0200)
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://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgitlab.com%2Fdriverctl%2Fdriverctl&amp;data=02%7C01%7Ckys%40microsoft.com%7C42e803feb2c544ef6ea908d5fd538878%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636693457619960040&amp;sdata=kEyYHRIjNZCk%2B37moCSqbrZL426YccNQrsWpENcrZdw%3D&amp;reserved=0

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>
Documentation/ABI/testing/sysfs-bus-vmbus [new file with mode: 0644]
drivers/hv/vmbus_drv.c
include/linux/hyperv.h

diff --git a/Documentation/ABI/testing/sysfs-bus-vmbus b/Documentation/ABI/testing/sysfs-bus-vmbus
new file mode 100644 (file)
index 0000000..91e6c06
--- /dev/null
@@ -0,0 +1,21 @@
+What:          /sys/bus/vmbus/devices/.../driver_override
+Date:          August 2019
+Contact:       Stephen Hemminger <sthemmin@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.
+
index b1b548a21f919b31f8e5d7f670352ee6f7d25f56..e6d8fdac6d8b57111e4deba5937f86e9ed54d802 100644 (file)
@@ -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)
index efda23cf32c7b8dd56fa958ff717560a6adbfa40..2c3798bcb01c9f1c327362853d5dcc98e613493b 100644 (file)
@@ -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;