Commit d765edbb authored by Stephen Hemminger's avatar Stephen Hemminger Committed by Greg Kroah-Hartman

vmbus: add driver_override support

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=0Signed-off-by: default avatarStephen Hemminger <sthemmin@microsoft.com>
Signed-off-by: default avatarK. Y. Srinivasan <kys@microsoft.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 83b15fed
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.
...@@ -498,6 +498,54 @@ static ssize_t device_show(struct device *dev, ...@@ -498,6 +498,54 @@ static ssize_t device_show(struct device *dev,
} }
static DEVICE_ATTR_RO(device); 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> */ /* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */
static struct attribute *vmbus_dev_attrs[] = { static struct attribute *vmbus_dev_attrs[] = {
&dev_attr_id.attr, &dev_attr_id.attr,
...@@ -528,6 +576,7 @@ static struct attribute *vmbus_dev_attrs[] = { ...@@ -528,6 +576,7 @@ static struct attribute *vmbus_dev_attrs[] = {
&dev_attr_channel_vp_mapping.attr, &dev_attr_channel_vp_mapping.attr,
&dev_attr_vendor.attr, &dev_attr_vendor.attr,
&dev_attr_device.attr, &dev_attr_device.attr,
&dev_attr_driver_override.attr,
NULL, NULL,
}; };
ATTRIBUTE_GROUPS(vmbus_dev); ATTRIBUTE_GROUPS(vmbus_dev);
...@@ -563,17 +612,26 @@ static inline bool is_null_guid(const uuid_le *guid) ...@@ -563,17 +612,26 @@ static inline bool is_null_guid(const uuid_le *guid)
return true; return true;
} }
/* static const struct hv_vmbus_device_id *
* Return a matching hv_vmbus_device_id pointer. hv_vmbus_dev_match(const struct hv_vmbus_device_id *id, const uuid_le *guid)
* If there is no match, return NULL.
*/ {
static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv, if (id == NULL)
const uuid_le *guid) 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; const struct hv_vmbus_device_id *id = NULL;
struct vmbus_dynid *dynid; struct vmbus_dynid *dynid;
/* Look at the dynamic ids first, before the static ones */
spin_lock(&drv->dynids.lock); spin_lock(&drv->dynids.lock);
list_for_each_entry(dynid, &drv->dynids.list, node) { list_for_each_entry(dynid, &drv->dynids.list, node) {
if (!uuid_le_cmp(dynid->id.guid, *guid)) { 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, ...@@ -583,18 +641,37 @@ static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv,
} }
spin_unlock(&drv->dynids.lock); spin_unlock(&drv->dynids.lock);
if (id) return id;
return id; }
id = drv->id_table; static const struct hv_vmbus_device_id vmbus_device_null = {
if (id == NULL) .guid = NULL_UUID_LE,
return NULL; /* empty device table */ };
for (; !is_null_guid(&id->guid); id++) /*
if (!uuid_le_cmp(id->guid, *guid)) * Return a matching hv_vmbus_device_id pointer.
return id; * 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 */ /* 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, ...@@ -643,7 +720,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
if (retval) if (retval)
return retval; return retval;
if (hv_vmbus_get_id(drv, &guid)) if (hv_vmbus_dynid_match(drv, &guid))
return -EEXIST; return -EEXIST;
retval = vmbus_add_dynid(drv, &guid); retval = vmbus_add_dynid(drv, &guid);
...@@ -708,7 +785,7 @@ static int vmbus_match(struct device *device, struct device_driver *driver) ...@@ -708,7 +785,7 @@ static int vmbus_match(struct device *device, struct device_driver *driver)
if (is_hvsock_channel(hv_dev->channel)) if (is_hvsock_channel(hv_dev->channel))
return drv->hvsock; return drv->hvsock;
if (hv_vmbus_get_id(drv, &hv_dev->dev_type)) if (hv_vmbus_get_id(drv, hv_dev))
return 1; return 1;
return 0; return 0;
...@@ -725,7 +802,7 @@ static int vmbus_probe(struct device *child_device) ...@@ -725,7 +802,7 @@ static int vmbus_probe(struct device *child_device)
struct hv_device *dev = device_to_hv_device(child_device); struct hv_device *dev = device_to_hv_device(child_device);
const struct hv_vmbus_device_id *dev_id; 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) { if (drv->probe) {
ret = drv->probe(dev, dev_id); ret = drv->probe(dev, dev_id);
if (ret != 0) if (ret != 0)
......
...@@ -1125,6 +1125,7 @@ struct hv_device { ...@@ -1125,6 +1125,7 @@ struct hv_device {
u16 device_id; u16 device_id;
struct device device; struct device device;
char *driver_override; /* Driver name to force a match */
struct vmbus_channel *channel; struct vmbus_channel *channel;
struct kset *channels_kset; struct kset *channels_kset;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment