diff options
Diffstat (limited to 'drivers/hv/vmbus_drv.c')
-rw-r--r-- | drivers/hv/vmbus_drv.c | 136 |
1 files changed, 122 insertions, 14 deletions
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index f518b8d7a5b5..c85235e9f245 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c | |||
@@ -33,9 +33,12 @@ | |||
33 | #include <linux/hyperv.h> | 33 | #include <linux/hyperv.h> |
34 | #include <linux/kernel_stat.h> | 34 | #include <linux/kernel_stat.h> |
35 | #include <linux/clockchips.h> | 35 | #include <linux/clockchips.h> |
36 | #include <linux/cpu.h> | ||
36 | #include <asm/hyperv.h> | 37 | #include <asm/hyperv.h> |
37 | #include <asm/hypervisor.h> | 38 | #include <asm/hypervisor.h> |
38 | #include <asm/mshyperv.h> | 39 | #include <asm/mshyperv.h> |
40 | #include <linux/notifier.h> | ||
41 | #include <linux/ptrace.h> | ||
39 | #include "hyperv_vmbus.h" | 42 | #include "hyperv_vmbus.h" |
40 | 43 | ||
41 | static struct acpi_device *hv_acpi_dev; | 44 | static struct acpi_device *hv_acpi_dev; |
@@ -44,6 +47,31 @@ static struct tasklet_struct msg_dpc; | |||
44 | static struct completion probe_event; | 47 | static struct completion probe_event; |
45 | static int irq; | 48 | static int irq; |
46 | 49 | ||
50 | |||
51 | static int hyperv_panic_event(struct notifier_block *nb, | ||
52 | unsigned long event, void *ptr) | ||
53 | { | ||
54 | struct pt_regs *regs; | ||
55 | |||
56 | regs = current_pt_regs(); | ||
57 | |||
58 | wrmsrl(HV_X64_MSR_CRASH_P0, regs->ip); | ||
59 | wrmsrl(HV_X64_MSR_CRASH_P1, regs->ax); | ||
60 | wrmsrl(HV_X64_MSR_CRASH_P2, regs->bx); | ||
61 | wrmsrl(HV_X64_MSR_CRASH_P3, regs->cx); | ||
62 | wrmsrl(HV_X64_MSR_CRASH_P4, regs->dx); | ||
63 | |||
64 | /* | ||
65 | * Let Hyper-V know there is crash data available | ||
66 | */ | ||
67 | wrmsrl(HV_X64_MSR_CRASH_CTL, HV_CRASH_CTL_CRASH_NOTIFY); | ||
68 | return NOTIFY_DONE; | ||
69 | } | ||
70 | |||
71 | static struct notifier_block hyperv_panic_block = { | ||
72 | .notifier_call = hyperv_panic_event, | ||
73 | }; | ||
74 | |||
47 | struct resource hyperv_mmio = { | 75 | struct resource hyperv_mmio = { |
48 | .name = "hyperv mmio", | 76 | .name = "hyperv mmio", |
49 | .flags = IORESOURCE_MEM, | 77 | .flags = IORESOURCE_MEM, |
@@ -507,14 +535,26 @@ static int vmbus_probe(struct device *child_device) | |||
507 | */ | 535 | */ |
508 | static int vmbus_remove(struct device *child_device) | 536 | static int vmbus_remove(struct device *child_device) |
509 | { | 537 | { |
510 | struct hv_driver *drv = drv_to_hv_drv(child_device->driver); | 538 | struct hv_driver *drv; |
511 | struct hv_device *dev = device_to_hv_device(child_device); | 539 | struct hv_device *dev = device_to_hv_device(child_device); |
512 | 540 | u32 relid = dev->channel->offermsg.child_relid; | |
513 | if (drv->remove) | 541 | |
514 | drv->remove(dev); | 542 | if (child_device->driver) { |
515 | else | 543 | drv = drv_to_hv_drv(child_device->driver); |
516 | pr_err("remove not set for driver %s\n", | 544 | if (drv->remove) |
517 | dev_name(child_device)); | 545 | drv->remove(dev); |
546 | else { | ||
547 | hv_process_channel_removal(dev->channel, relid); | ||
548 | pr_err("remove not set for driver %s\n", | ||
549 | dev_name(child_device)); | ||
550 | } | ||
551 | } else { | ||
552 | /* | ||
553 | * We don't have a driver for this device; deal with the | ||
554 | * rescind message by removing the channel. | ||
555 | */ | ||
556 | hv_process_channel_removal(dev->channel, relid); | ||
557 | } | ||
518 | 558 | ||
519 | return 0; | 559 | return 0; |
520 | } | 560 | } |
@@ -573,6 +613,10 @@ static void vmbus_onmessage_work(struct work_struct *work) | |||
573 | { | 613 | { |
574 | struct onmessage_work_context *ctx; | 614 | struct onmessage_work_context *ctx; |
575 | 615 | ||
616 | /* Do not process messages if we're in DISCONNECTED state */ | ||
617 | if (vmbus_connection.conn_state == DISCONNECTED) | ||
618 | return; | ||
619 | |||
576 | ctx = container_of(work, struct onmessage_work_context, | 620 | ctx = container_of(work, struct onmessage_work_context, |
577 | work); | 621 | work); |
578 | vmbus_onmessage(&ctx->msg); | 622 | vmbus_onmessage(&ctx->msg); |
@@ -613,21 +657,36 @@ static void vmbus_on_msg_dpc(unsigned long data) | |||
613 | void *page_addr = hv_context.synic_message_page[cpu]; | 657 | void *page_addr = hv_context.synic_message_page[cpu]; |
614 | struct hv_message *msg = (struct hv_message *)page_addr + | 658 | struct hv_message *msg = (struct hv_message *)page_addr + |
615 | VMBUS_MESSAGE_SINT; | 659 | VMBUS_MESSAGE_SINT; |
660 | struct vmbus_channel_message_header *hdr; | ||
661 | struct vmbus_channel_message_table_entry *entry; | ||
616 | struct onmessage_work_context *ctx; | 662 | struct onmessage_work_context *ctx; |
617 | 663 | ||
618 | while (1) { | 664 | while (1) { |
619 | if (msg->header.message_type == HVMSG_NONE) { | 665 | if (msg->header.message_type == HVMSG_NONE) |
620 | /* no msg */ | 666 | /* no msg */ |
621 | break; | 667 | break; |
622 | } else { | 668 | |
669 | hdr = (struct vmbus_channel_message_header *)msg->u.payload; | ||
670 | |||
671 | if (hdr->msgtype >= CHANNELMSG_COUNT) { | ||
672 | WARN_ONCE(1, "unknown msgtype=%d\n", hdr->msgtype); | ||
673 | goto msg_handled; | ||
674 | } | ||
675 | |||
676 | entry = &channel_message_table[hdr->msgtype]; | ||
677 | if (entry->handler_type == VMHT_BLOCKING) { | ||
623 | ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC); | 678 | ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC); |
624 | if (ctx == NULL) | 679 | if (ctx == NULL) |
625 | continue; | 680 | continue; |
681 | |||
626 | INIT_WORK(&ctx->work, vmbus_onmessage_work); | 682 | INIT_WORK(&ctx->work, vmbus_onmessage_work); |
627 | memcpy(&ctx->msg, msg, sizeof(*msg)); | 683 | memcpy(&ctx->msg, msg, sizeof(*msg)); |
684 | |||
628 | queue_work(vmbus_connection.work_queue, &ctx->work); | 685 | queue_work(vmbus_connection.work_queue, &ctx->work); |
629 | } | 686 | } else |
687 | entry->message_handler(hdr); | ||
630 | 688 | ||
689 | msg_handled: | ||
631 | msg->header.message_type = HVMSG_NONE; | 690 | msg->header.message_type = HVMSG_NONE; |
632 | 691 | ||
633 | /* | 692 | /* |
@@ -704,6 +763,39 @@ static void vmbus_isr(void) | |||
704 | } | 763 | } |
705 | } | 764 | } |
706 | 765 | ||
766 | #ifdef CONFIG_HOTPLUG_CPU | ||
767 | static int hyperv_cpu_disable(void) | ||
768 | { | ||
769 | return -ENOSYS; | ||
770 | } | ||
771 | |||
772 | static void hv_cpu_hotplug_quirk(bool vmbus_loaded) | ||
773 | { | ||
774 | static void *previous_cpu_disable; | ||
775 | |||
776 | /* | ||
777 | * Offlining a CPU when running on newer hypervisors (WS2012R2, Win8, | ||
778 | * ...) is not supported at this moment as channel interrupts are | ||
779 | * distributed across all of them. | ||
780 | */ | ||
781 | |||
782 | if ((vmbus_proto_version == VERSION_WS2008) || | ||
783 | (vmbus_proto_version == VERSION_WIN7)) | ||
784 | return; | ||
785 | |||
786 | if (vmbus_loaded) { | ||
787 | previous_cpu_disable = smp_ops.cpu_disable; | ||
788 | smp_ops.cpu_disable = hyperv_cpu_disable; | ||
789 | pr_notice("CPU offlining is not supported by hypervisor\n"); | ||
790 | } else if (previous_cpu_disable) | ||
791 | smp_ops.cpu_disable = previous_cpu_disable; | ||
792 | } | ||
793 | #else | ||
794 | static void hv_cpu_hotplug_quirk(bool vmbus_loaded) | ||
795 | { | ||
796 | } | ||
797 | #endif | ||
798 | |||
707 | /* | 799 | /* |
708 | * vmbus_bus_init -Main vmbus driver initialization routine. | 800 | * vmbus_bus_init -Main vmbus driver initialization routine. |
709 | * | 801 | * |
@@ -744,6 +836,16 @@ static int vmbus_bus_init(int irq) | |||
744 | if (ret) | 836 | if (ret) |
745 | goto err_alloc; | 837 | goto err_alloc; |
746 | 838 | ||
839 | hv_cpu_hotplug_quirk(true); | ||
840 | |||
841 | /* | ||
842 | * Only register if the crash MSRs are available | ||
843 | */ | ||
844 | if (ms_hyperv.features & HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE) { | ||
845 | atomic_notifier_chain_register(&panic_notifier_list, | ||
846 | &hyperv_panic_block); | ||
847 | } | ||
848 | |||
747 | vmbus_request_offers(); | 849 | vmbus_request_offers(); |
748 | 850 | ||
749 | return 0; | 851 | return 0; |
@@ -840,10 +942,8 @@ int vmbus_device_register(struct hv_device *child_device_obj) | |||
840 | { | 942 | { |
841 | int ret = 0; | 943 | int ret = 0; |
842 | 944 | ||
843 | static atomic_t device_num = ATOMIC_INIT(0); | 945 | dev_set_name(&child_device_obj->device, "vmbus_%d", |
844 | 946 | child_device_obj->channel->id); | |
845 | dev_set_name(&child_device_obj->device, "vmbus_0_%d", | ||
846 | atomic_inc_return(&device_num)); | ||
847 | 947 | ||
848 | child_device_obj->device.bus = &hv_bus; | 948 | child_device_obj->device.bus = &hv_bus; |
849 | child_device_obj->device.parent = &hv_acpi_dev->dev; | 949 | child_device_obj->device.parent = &hv_acpi_dev->dev; |
@@ -992,11 +1092,19 @@ cleanup: | |||
992 | 1092 | ||
993 | static void __exit vmbus_exit(void) | 1093 | static void __exit vmbus_exit(void) |
994 | { | 1094 | { |
1095 | int cpu; | ||
1096 | |||
1097 | vmbus_connection.conn_state = DISCONNECTED; | ||
1098 | hv_synic_clockevents_cleanup(); | ||
995 | hv_remove_vmbus_irq(); | 1099 | hv_remove_vmbus_irq(); |
996 | vmbus_free_channels(); | 1100 | vmbus_free_channels(); |
997 | bus_unregister(&hv_bus); | 1101 | bus_unregister(&hv_bus); |
998 | hv_cleanup(); | 1102 | hv_cleanup(); |
1103 | for_each_online_cpu(cpu) | ||
1104 | smp_call_function_single(cpu, hv_synic_cleanup, NULL, 1); | ||
999 | acpi_bus_unregister_driver(&vmbus_acpi_driver); | 1105 | acpi_bus_unregister_driver(&vmbus_acpi_driver); |
1106 | hv_cpu_hotplug_quirk(false); | ||
1107 | vmbus_disconnect(); | ||
1000 | } | 1108 | } |
1001 | 1109 | ||
1002 | 1110 | ||