diff options
| -rw-r--r-- | drivers/hv/vmbus_drv.c | 174 | ||||
| -rw-r--r-- | include/linux/hyperv.h | 6 |
2 files changed, 172 insertions, 8 deletions
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 0276d2ef06ee..230c62e7f567 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c | |||
| @@ -45,6 +45,11 @@ | |||
| 45 | #include <linux/random.h> | 45 | #include <linux/random.h> |
| 46 | #include "hyperv_vmbus.h" | 46 | #include "hyperv_vmbus.h" |
| 47 | 47 | ||
| 48 | struct vmbus_dynid { | ||
| 49 | struct list_head node; | ||
| 50 | struct hv_vmbus_device_id id; | ||
| 51 | }; | ||
| 52 | |||
| 48 | static struct acpi_device *hv_acpi_dev; | 53 | static struct acpi_device *hv_acpi_dev; |
| 49 | 54 | ||
| 50 | static struct completion probe_event; | 55 | static struct completion probe_event; |
| @@ -500,7 +505,7 @@ static ssize_t device_show(struct device *dev, | |||
| 500 | static DEVICE_ATTR_RO(device); | 505 | static DEVICE_ATTR_RO(device); |
| 501 | 506 | ||
| 502 | /* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */ | 507 | /* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */ |
| 503 | static struct attribute *vmbus_attrs[] = { | 508 | static struct attribute *vmbus_dev_attrs[] = { |
| 504 | &dev_attr_id.attr, | 509 | &dev_attr_id.attr, |
| 505 | &dev_attr_state.attr, | 510 | &dev_attr_state.attr, |
| 506 | &dev_attr_monitor_id.attr, | 511 | &dev_attr_monitor_id.attr, |
| @@ -528,7 +533,7 @@ static struct attribute *vmbus_attrs[] = { | |||
| 528 | &dev_attr_device.attr, | 533 | &dev_attr_device.attr, |
| 529 | NULL, | 534 | NULL, |
| 530 | }; | 535 | }; |
| 531 | ATTRIBUTE_GROUPS(vmbus); | 536 | ATTRIBUTE_GROUPS(vmbus_dev); |
| 532 | 537 | ||
| 533 | /* | 538 | /* |
| 534 | * vmbus_uevent - add uevent for our device | 539 | * vmbus_uevent - add uevent for our device |
| @@ -565,10 +570,29 @@ static inline bool is_null_guid(const uuid_le *guid) | |||
| 565 | * Return a matching hv_vmbus_device_id pointer. | 570 | * Return a matching hv_vmbus_device_id pointer. |
| 566 | * If there is no match, return NULL. | 571 | * If there is no match, return NULL. |
| 567 | */ | 572 | */ |
| 568 | static const struct hv_vmbus_device_id *hv_vmbus_get_id( | 573 | static const struct hv_vmbus_device_id *hv_vmbus_get_id(struct hv_driver *drv, |
| 569 | const struct hv_vmbus_device_id *id, | ||
| 570 | const uuid_le *guid) | 574 | const uuid_le *guid) |
| 571 | { | 575 | { |
| 576 | const struct hv_vmbus_device_id *id = NULL; | ||
| 577 | struct vmbus_dynid *dynid; | ||
| 578 | |||
| 579 | /* Look at the dynamic ids first, before the static ones */ | ||
| 580 | spin_lock(&drv->dynids.lock); | ||
| 581 | list_for_each_entry(dynid, &drv->dynids.list, node) { | ||
| 582 | if (!uuid_le_cmp(dynid->id.guid, *guid)) { | ||
| 583 | id = &dynid->id; | ||
| 584 | break; | ||
| 585 | } | ||
| 586 | } | ||
| 587 | spin_unlock(&drv->dynids.lock); | ||
| 588 | |||
| 589 | if (id) | ||
| 590 | return id; | ||
| 591 | |||
| 592 | id = drv->id_table; | ||
| 593 | if (id == NULL) | ||
| 594 | return NULL; /* empty device table */ | ||
| 595 | |||
| 572 | for (; !is_null_guid(&id->guid); id++) | 596 | for (; !is_null_guid(&id->guid); id++) |
| 573 | if (!uuid_le_cmp(id->guid, *guid)) | 597 | if (!uuid_le_cmp(id->guid, *guid)) |
| 574 | return id; | 598 | return id; |
| @@ -576,6 +600,134 @@ static const struct hv_vmbus_device_id *hv_vmbus_get_id( | |||
| 576 | return NULL; | 600 | return NULL; |
| 577 | } | 601 | } |
| 578 | 602 | ||
| 603 | /* vmbus_add_dynid - add a new device ID to this driver and re-probe devices */ | ||
| 604 | static int vmbus_add_dynid(struct hv_driver *drv, uuid_le *guid) | ||
| 605 | { | ||
| 606 | struct vmbus_dynid *dynid; | ||
| 607 | |||
| 608 | dynid = kzalloc(sizeof(*dynid), GFP_KERNEL); | ||
| 609 | if (!dynid) | ||
| 610 | return -ENOMEM; | ||
| 611 | |||
| 612 | dynid->id.guid = *guid; | ||
| 613 | |||
| 614 | spin_lock(&drv->dynids.lock); | ||
| 615 | list_add_tail(&dynid->node, &drv->dynids.list); | ||
| 616 | spin_unlock(&drv->dynids.lock); | ||
| 617 | |||
| 618 | return driver_attach(&drv->driver); | ||
| 619 | } | ||
| 620 | |||
| 621 | static void vmbus_free_dynids(struct hv_driver *drv) | ||
| 622 | { | ||
| 623 | struct vmbus_dynid *dynid, *n; | ||
| 624 | |||
| 625 | spin_lock(&drv->dynids.lock); | ||
| 626 | list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { | ||
| 627 | list_del(&dynid->node); | ||
| 628 | kfree(dynid); | ||
| 629 | } | ||
| 630 | spin_unlock(&drv->dynids.lock); | ||
| 631 | } | ||
| 632 | |||
| 633 | /* Parse string of form: 1b4e28ba-2fa1-11d2-883f-b9a761bde3f */ | ||
| 634 | static int get_uuid_le(const char *str, uuid_le *uu) | ||
| 635 | { | ||
| 636 | unsigned int b[16]; | ||
| 637 | int i; | ||
| 638 | |||
| 639 | if (strlen(str) < 37) | ||
| 640 | return -1; | ||
| 641 | |||
| 642 | for (i = 0; i < 36; i++) { | ||
| 643 | switch (i) { | ||
| 644 | case 8: case 13: case 18: case 23: | ||
| 645 | if (str[i] != '-') | ||
| 646 | return -1; | ||
| 647 | break; | ||
| 648 | default: | ||
| 649 | if (!isxdigit(str[i])) | ||
| 650 | return -1; | ||
| 651 | } | ||
| 652 | } | ||
| 653 | |||
| 654 | /* unparse little endian output byte order */ | ||
| 655 | if (sscanf(str, | ||
| 656 | "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x", | ||
| 657 | &b[3], &b[2], &b[1], &b[0], | ||
| 658 | &b[5], &b[4], &b[7], &b[6], &b[8], &b[9], | ||
| 659 | &b[10], &b[11], &b[12], &b[13], &b[14], &b[15]) != 16) | ||
| 660 | return -1; | ||
| 661 | |||
| 662 | for (i = 0; i < 16; i++) | ||
| 663 | uu->b[i] = b[i]; | ||
| 664 | return 0; | ||
| 665 | } | ||
| 666 | |||
| 667 | /* | ||
| 668 | * store_new_id - sysfs frontend to vmbus_add_dynid() | ||
| 669 | * | ||
| 670 | * Allow GUIDs to be added to an existing driver via sysfs. | ||
| 671 | */ | ||
| 672 | static ssize_t new_id_store(struct device_driver *driver, const char *buf, | ||
| 673 | size_t count) | ||
| 674 | { | ||
| 675 | struct hv_driver *drv = drv_to_hv_drv(driver); | ||
| 676 | uuid_le guid = NULL_UUID_LE; | ||
| 677 | ssize_t retval; | ||
| 678 | |||
| 679 | if (get_uuid_le(buf, &guid) != 0) | ||
| 680 | return -EINVAL; | ||
| 681 | |||
| 682 | if (hv_vmbus_get_id(drv, &guid)) | ||
| 683 | return -EEXIST; | ||
| 684 | |||
| 685 | retval = vmbus_add_dynid(drv, &guid); | ||
| 686 | if (retval) | ||
| 687 | return retval; | ||
| 688 | return count; | ||
| 689 | } | ||
| 690 | static DRIVER_ATTR_WO(new_id); | ||
| 691 | |||
| 692 | /* | ||
| 693 | * store_remove_id - remove a PCI device ID from this driver | ||
| 694 | * | ||
| 695 | * Removes a dynamic pci device ID to this driver. | ||
| 696 | */ | ||
| 697 | static ssize_t remove_id_store(struct device_driver *driver, const char *buf, | ||
| 698 | size_t count) | ||
| 699 | { | ||
| 700 | struct hv_driver *drv = drv_to_hv_drv(driver); | ||
| 701 | struct vmbus_dynid *dynid, *n; | ||
| 702 | uuid_le guid = NULL_UUID_LE; | ||
| 703 | size_t retval = -ENODEV; | ||
| 704 | |||
| 705 | if (get_uuid_le(buf, &guid)) | ||
| 706 | return -EINVAL; | ||
| 707 | |||
| 708 | spin_lock(&drv->dynids.lock); | ||
| 709 | list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) { | ||
| 710 | struct hv_vmbus_device_id *id = &dynid->id; | ||
| 711 | |||
| 712 | if (!uuid_le_cmp(id->guid, guid)) { | ||
| 713 | list_del(&dynid->node); | ||
| 714 | kfree(dynid); | ||
| 715 | retval = count; | ||
| 716 | break; | ||
| 717 | } | ||
| 718 | } | ||
| 719 | spin_unlock(&drv->dynids.lock); | ||
| 720 | |||
| 721 | return retval; | ||
| 722 | } | ||
| 723 | static DRIVER_ATTR_WO(remove_id); | ||
| 724 | |||
| 725 | static struct attribute *vmbus_drv_attrs[] = { | ||
| 726 | &driver_attr_new_id.attr, | ||
| 727 | &driver_attr_remove_id.attr, | ||
| 728 | NULL, | ||
| 729 | }; | ||
| 730 | ATTRIBUTE_GROUPS(vmbus_drv); | ||
| 579 | 731 | ||
| 580 | 732 | ||
| 581 | /* | 733 | /* |
| @@ -590,7 +742,7 @@ static int vmbus_match(struct device *device, struct device_driver *driver) | |||
| 590 | if (is_hvsock_channel(hv_dev->channel)) | 742 | if (is_hvsock_channel(hv_dev->channel)) |
| 591 | return drv->hvsock; | 743 | return drv->hvsock; |
| 592 | 744 | ||
| 593 | if (hv_vmbus_get_id(drv->id_table, &hv_dev->dev_type)) | 745 | if (hv_vmbus_get_id(drv, &hv_dev->dev_type)) |
| 594 | return 1; | 746 | return 1; |
| 595 | 747 | ||
| 596 | return 0; | 748 | return 0; |
| @@ -607,7 +759,7 @@ static int vmbus_probe(struct device *child_device) | |||
| 607 | struct hv_device *dev = device_to_hv_device(child_device); | 759 | struct hv_device *dev = device_to_hv_device(child_device); |
| 608 | const struct hv_vmbus_device_id *dev_id; | 760 | const struct hv_vmbus_device_id *dev_id; |
| 609 | 761 | ||
| 610 | dev_id = hv_vmbus_get_id(drv->id_table, &dev->dev_type); | 762 | dev_id = hv_vmbus_get_id(drv, &dev->dev_type); |
| 611 | if (drv->probe) { | 763 | if (drv->probe) { |
| 612 | ret = drv->probe(dev, dev_id); | 764 | ret = drv->probe(dev, dev_id); |
| 613 | if (ret != 0) | 765 | if (ret != 0) |
| @@ -684,7 +836,8 @@ static struct bus_type hv_bus = { | |||
| 684 | .remove = vmbus_remove, | 836 | .remove = vmbus_remove, |
| 685 | .probe = vmbus_probe, | 837 | .probe = vmbus_probe, |
| 686 | .uevent = vmbus_uevent, | 838 | .uevent = vmbus_uevent, |
| 687 | .dev_groups = vmbus_groups, | 839 | .dev_groups = vmbus_dev_groups, |
| 840 | .drv_groups = vmbus_drv_groups, | ||
| 688 | }; | 841 | }; |
| 689 | 842 | ||
| 690 | struct onmessage_work_context { | 843 | struct onmessage_work_context { |
| @@ -905,6 +1058,9 @@ int __vmbus_driver_register(struct hv_driver *hv_driver, struct module *owner, c | |||
| 905 | hv_driver->driver.mod_name = mod_name; | 1058 | hv_driver->driver.mod_name = mod_name; |
| 906 | hv_driver->driver.bus = &hv_bus; | 1059 | hv_driver->driver.bus = &hv_bus; |
| 907 | 1060 | ||
| 1061 | spin_lock_init(&hv_driver->dynids.lock); | ||
| 1062 | INIT_LIST_HEAD(&hv_driver->dynids.list); | ||
| 1063 | |||
| 908 | ret = driver_register(&hv_driver->driver); | 1064 | ret = driver_register(&hv_driver->driver); |
| 909 | 1065 | ||
| 910 | return ret; | 1066 | return ret; |
| @@ -923,8 +1079,10 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver) | |||
| 923 | { | 1079 | { |
| 924 | pr_info("unregistering driver %s\n", hv_driver->name); | 1080 | pr_info("unregistering driver %s\n", hv_driver->name); |
| 925 | 1081 | ||
| 926 | if (!vmbus_exists()) | 1082 | if (!vmbus_exists()) { |
| 927 | driver_unregister(&hv_driver->driver); | 1083 | driver_unregister(&hv_driver->driver); |
| 1084 | vmbus_free_dynids(hv_driver); | ||
| 1085 | } | ||
| 928 | } | 1086 | } |
| 929 | EXPORT_SYMBOL_GPL(vmbus_driver_unregister); | 1087 | EXPORT_SYMBOL_GPL(vmbus_driver_unregister); |
| 930 | 1088 | ||
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 35053f99522b..42fe43fb0c80 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h | |||
| @@ -1119,6 +1119,12 @@ struct hv_driver { | |||
| 1119 | 1119 | ||
| 1120 | struct device_driver driver; | 1120 | struct device_driver driver; |
| 1121 | 1121 | ||
| 1122 | /* dynamic device GUID's */ | ||
| 1123 | struct { | ||
| 1124 | spinlock_t lock; | ||
| 1125 | struct list_head list; | ||
| 1126 | } dynids; | ||
| 1127 | |||
| 1122 | int (*probe)(struct hv_device *, const struct hv_vmbus_device_id *); | 1128 | int (*probe)(struct hv_device *, const struct hv_vmbus_device_id *); |
| 1123 | int (*remove)(struct hv_device *); | 1129 | int (*remove)(struct hv_device *); |
| 1124 | void (*shutdown)(struct hv_device *); | 1130 | void (*shutdown)(struct hv_device *); |
