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

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: 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
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
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.
+96 −19
Original line number Diff line number Diff line
@@ -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;
}

	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;

	/* 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)
+1 −0
Original line number Diff line number Diff line
@@ -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;