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&data=02%7C01%7Ckys%40microsoft.com%7C42e803feb2c544ef6ea908d5fd538878%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636693457619960040&sdata=kEyYHRIjNZCk%2B37moCSqbrZL426YccNQrsWpENcrZdw%3D&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>
This commit is contained in:
parent
83b15fed91
commit
d765edbb30
21
Documentation/ABI/testing/sysfs-bus-vmbus
Normal file
21
Documentation/ABI/testing/sysfs-bus-vmbus
Normal file
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user