Skip to content

Commit

Permalink
vmbus: add support for dynamic device id's
Browse files Browse the repository at this point in the history
This patch adds sysfs interface to dynamically bind new UUID values
to existing VMBus device. This is useful for generic UIO driver to
act similar to uio_pci_generic.

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>
  • Loading branch information
Stephen Hemminger authored and Greg Kroah-Hartman committed Dec 6, 2016
1 parent 6ffc4b8 commit fc76936
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 8 deletions.
174 changes: 166 additions & 8 deletions drivers/hv/vmbus_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
#include <linux/random.h>
#include "hyperv_vmbus.h"

struct vmbus_dynid {
struct list_head node;
struct hv_vmbus_device_id id;
};

static struct acpi_device *hv_acpi_dev;

static struct completion probe_event;
Expand Down Expand Up @@ -500,7 +505,7 @@ static ssize_t device_show(struct device *dev,
static DEVICE_ATTR_RO(device);

/* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */
static struct attribute *vmbus_attrs[] = {
static struct attribute *vmbus_dev_attrs[] = {
&dev_attr_id.attr,
&dev_attr_state.attr,
&dev_attr_monitor_id.attr,
Expand Down Expand Up @@ -528,7 +533,7 @@ static struct attribute *vmbus_attrs[] = {
&dev_attr_device.attr,
NULL,
};
ATTRIBUTE_GROUPS(vmbus);
ATTRIBUTE_GROUPS(vmbus_dev);

/*
* vmbus_uevent - add uevent for our device
Expand Down Expand Up @@ -565,17 +570,164 @@ static inline bool is_null_guid(const uuid_le *guid)
* 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(
const struct hv_vmbus_device_id *id,
static const struct hv_vmbus_device_id *hv_vmbus_get_id(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)) {
id = &dynid->id;
break;
}
}
spin_unlock(&drv->dynids.lock);

if (id)
return id;

id = drv->id_table;
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;
}

/* vmbus_add_dynid - add a new device ID to this driver and re-probe devices */
static int vmbus_add_dynid(struct hv_driver *drv, uuid_le *guid)
{
struct vmbus_dynid *dynid;

dynid = kzalloc(sizeof(*dynid), GFP_KERNEL);
if (!dynid)
return -ENOMEM;

dynid->id.guid = *guid;

spin_lock(&drv->dynids.lock);
list_add_tail(&dynid->node, &drv->dynids.list);
spin_unlock(&drv->dynids.lock);

return driver_attach(&drv->driver);
}

static void vmbus_free_dynids(struct hv_driver *drv)
{
struct vmbus_dynid *dynid, *n;

spin_lock(&drv->dynids.lock);
list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
list_del(&dynid->node);
kfree(dynid);
}
spin_unlock(&drv->dynids.lock);
}

/* Parse string of form: 1b4e28ba-2fa1-11d2-883f-b9a761bde3f */
static int get_uuid_le(const char *str, uuid_le *uu)
{
unsigned int b[16];
int i;

if (strlen(str) < 37)
return -1;

for (i = 0; i < 36; i++) {
switch (i) {
case 8: case 13: case 18: case 23:
if (str[i] != '-')
return -1;
break;
default:
if (!isxdigit(str[i]))
return -1;
}
}

/* unparse little endian output byte order */
if (sscanf(str,
"%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x",
&b[3], &b[2], &b[1], &b[0],
&b[5], &b[4], &b[7], &b[6], &b[8], &b[9],
&b[10], &b[11], &b[12], &b[13], &b[14], &b[15]) != 16)
return -1;

for (i = 0; i < 16; i++)
uu->b[i] = b[i];
return 0;
}

/*
* store_new_id - sysfs frontend to vmbus_add_dynid()
*
* Allow GUIDs to be added to an existing driver via sysfs.
*/
static ssize_t new_id_store(struct device_driver *driver, const char *buf,
size_t count)
{
struct hv_driver *drv = drv_to_hv_drv(driver);
uuid_le guid = NULL_UUID_LE;
ssize_t retval;

if (get_uuid_le(buf, &guid) != 0)
return -EINVAL;

if (hv_vmbus_get_id(drv, &guid))
return -EEXIST;

retval = vmbus_add_dynid(drv, &guid);
if (retval)
return retval;
return count;
}
static DRIVER_ATTR_WO(new_id);

/*
* store_remove_id - remove a PCI device ID from this driver
*
* Removes a dynamic pci device ID to this driver.
*/
static ssize_t remove_id_store(struct device_driver *driver, const char *buf,
size_t count)
{
struct hv_driver *drv = drv_to_hv_drv(driver);
struct vmbus_dynid *dynid, *n;
uuid_le guid = NULL_UUID_LE;
size_t retval = -ENODEV;

if (get_uuid_le(buf, &guid))
return -EINVAL;

spin_lock(&drv->dynids.lock);
list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
struct hv_vmbus_device_id *id = &dynid->id;

if (!uuid_le_cmp(id->guid, guid)) {
list_del(&dynid->node);
kfree(dynid);
retval = count;
break;
}
}
spin_unlock(&drv->dynids.lock);

return retval;
}
static DRIVER_ATTR_WO(remove_id);

static struct attribute *vmbus_drv_attrs[] = {
&driver_attr_new_id.attr,
&driver_attr_remove_id.attr,
NULL,
};
ATTRIBUTE_GROUPS(vmbus_drv);


/*
Expand All @@ -590,7 +742,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->id_table, &hv_dev->dev_type))
if (hv_vmbus_get_id(drv, &hv_dev->dev_type))
return 1;

return 0;
Expand All @@ -607,7 +759,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->id_table, &dev->dev_type);
dev_id = hv_vmbus_get_id(drv, &dev->dev_type);
if (drv->probe) {
ret = drv->probe(dev, dev_id);
if (ret != 0)
Expand Down Expand Up @@ -684,7 +836,8 @@ static struct bus_type hv_bus = {
.remove = vmbus_remove,
.probe = vmbus_probe,
.uevent = vmbus_uevent,
.dev_groups = vmbus_groups,
.dev_groups = vmbus_dev_groups,
.drv_groups = vmbus_drv_groups,
};

struct onmessage_work_context {
Expand Down Expand Up @@ -905,6 +1058,9 @@ int __vmbus_driver_register(struct hv_driver *hv_driver, struct module *owner, c
hv_driver->driver.mod_name = mod_name;
hv_driver->driver.bus = &hv_bus;

spin_lock_init(&hv_driver->dynids.lock);
INIT_LIST_HEAD(&hv_driver->dynids.list);

ret = driver_register(&hv_driver->driver);

return ret;
Expand All @@ -923,8 +1079,10 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver)
{
pr_info("unregistering driver %s\n", hv_driver->name);

if (!vmbus_exists())
if (!vmbus_exists()) {
driver_unregister(&hv_driver->driver);
vmbus_free_dynids(hv_driver);
}
}
EXPORT_SYMBOL_GPL(vmbus_driver_unregister);

Expand Down
6 changes: 6 additions & 0 deletions include/linux/hyperv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,12 @@ struct hv_driver {

struct device_driver driver;

/* dynamic device GUID's */
struct {
spinlock_t lock;
struct list_head list;
} dynids;

int (*probe)(struct hv_device *, const struct hv_vmbus_device_id *);
int (*remove)(struct hv_device *);
void (*shutdown)(struct hv_device *);
Expand Down

0 comments on commit fc76936

Please sign in to comment.