| .. | .. |
|---|
| 10 | 10 | #include <linux/sched.h> |
|---|
| 11 | 11 | #include <linux/tboot.h> |
|---|
| 12 | 12 | #include <linux/delay.h> |
|---|
| 13 | | -#include <linux/frame.h> |
|---|
| 13 | +#include <linux/objtool.h> |
|---|
| 14 | +#include <linux/pgtable.h> |
|---|
| 14 | 15 | #include <acpi/reboot.h> |
|---|
| 15 | 16 | #include <asm/io.h> |
|---|
| 16 | 17 | #include <asm/apic.h> |
|---|
| 17 | 18 | #include <asm/io_apic.h> |
|---|
| 18 | 19 | #include <asm/desc.h> |
|---|
| 19 | 20 | #include <asm/hpet.h> |
|---|
| 20 | | -#include <asm/pgtable.h> |
|---|
| 21 | 21 | #include <asm/proto.h> |
|---|
| 22 | 22 | #include <asm/reboot_fixups.h> |
|---|
| 23 | 23 | #include <asm/reboot.h> |
|---|
| .. | .. |
|---|
| 113 | 113 | spin_unlock(&rtc_lock); |
|---|
| 114 | 114 | |
|---|
| 115 | 115 | /* |
|---|
| 116 | | - * Switch back to the initial page table. |
|---|
| 116 | + * Switch to the trampoline page table. |
|---|
| 117 | 117 | */ |
|---|
| 118 | | -#ifdef CONFIG_X86_32 |
|---|
| 119 | | - load_cr3(initial_page_table); |
|---|
| 120 | | -#else |
|---|
| 121 | | - write_cr3(real_mode_header->trampoline_pgd); |
|---|
| 122 | | - |
|---|
| 123 | | - /* Exiting long mode will fail if CR4.PCIDE is set. */ |
|---|
| 124 | | - if (static_cpu_has(X86_FEATURE_PCID)) |
|---|
| 125 | | - cr4_clear_bits(X86_CR4_PCIDE); |
|---|
| 126 | | -#endif |
|---|
| 118 | + load_trampoline_pgtable(); |
|---|
| 127 | 119 | |
|---|
| 128 | 120 | /* Jump to the identity-mapped low memory code */ |
|---|
| 129 | 121 | #ifdef CONFIG_X86_32 |
|---|
| .. | .. |
|---|
| 536 | 528 | } |
|---|
| 537 | 529 | } |
|---|
| 538 | 530 | |
|---|
| 539 | | -static void vmxoff_nmi(int cpu, struct pt_regs *regs) |
|---|
| 540 | | -{ |
|---|
| 541 | | - cpu_emergency_vmxoff(); |
|---|
| 542 | | -} |
|---|
| 531 | +static inline void nmi_shootdown_cpus_on_restart(void); |
|---|
| 543 | 532 | |
|---|
| 544 | | -/* Use NMIs as IPIs to tell all CPUs to disable virtualization */ |
|---|
| 545 | | -static void emergency_vmx_disable_all(void) |
|---|
| 533 | +static void emergency_reboot_disable_virtualization(void) |
|---|
| 546 | 534 | { |
|---|
| 547 | 535 | /* Just make sure we won't change CPUs while doing this */ |
|---|
| 548 | 536 | local_irq_disable(); |
|---|
| 549 | 537 | |
|---|
| 550 | 538 | /* |
|---|
| 551 | | - * Disable VMX on all CPUs before rebooting, otherwise we risk hanging |
|---|
| 552 | | - * the machine, because the CPU blocks INIT when it's in VMX root. |
|---|
| 539 | + * Disable virtualization on all CPUs before rebooting to avoid hanging |
|---|
| 540 | + * the system, as VMX and SVM block INIT when running in the host. |
|---|
| 553 | 541 | * |
|---|
| 554 | 542 | * We can't take any locks and we may be on an inconsistent state, so |
|---|
| 555 | | - * use NMIs as IPIs to tell the other CPUs to exit VMX root and halt. |
|---|
| 543 | + * use NMIs as IPIs to tell the other CPUs to disable VMX/SVM and halt. |
|---|
| 556 | 544 | * |
|---|
| 557 | | - * Do the NMI shootdown even if VMX if off on _this_ CPU, as that |
|---|
| 558 | | - * doesn't prevent a different CPU from being in VMX root operation. |
|---|
| 545 | + * Do the NMI shootdown even if virtualization is off on _this_ CPU, as |
|---|
| 546 | + * other CPUs may have virtualization enabled. |
|---|
| 559 | 547 | */ |
|---|
| 560 | | - if (cpu_has_vmx()) { |
|---|
| 561 | | - /* Safely force _this_ CPU out of VMX root operation. */ |
|---|
| 562 | | - __cpu_emergency_vmxoff(); |
|---|
| 548 | + if (cpu_has_vmx() || cpu_has_svm(NULL)) { |
|---|
| 549 | + /* Safely force _this_ CPU out of VMX/SVM operation. */ |
|---|
| 550 | + cpu_emergency_disable_virtualization(); |
|---|
| 563 | 551 | |
|---|
| 564 | | - /* Halt and exit VMX root operation on the other CPUs. */ |
|---|
| 565 | | - nmi_shootdown_cpus(vmxoff_nmi); |
|---|
| 566 | | - |
|---|
| 552 | + /* Disable VMX/SVM and halt on other CPUs. */ |
|---|
| 553 | + nmi_shootdown_cpus_on_restart(); |
|---|
| 567 | 554 | } |
|---|
| 568 | 555 | } |
|---|
| 569 | 556 | |
|---|
| .. | .. |
|---|
| 599 | 586 | unsigned short mode; |
|---|
| 600 | 587 | |
|---|
| 601 | 588 | if (reboot_emergency) |
|---|
| 602 | | - emergency_vmx_disable_all(); |
|---|
| 589 | + emergency_reboot_disable_virtualization(); |
|---|
| 603 | 590 | |
|---|
| 604 | 591 | tboot_shutdown(TB_SHUTDOWN_REBOOT); |
|---|
| 605 | 592 | |
|---|
| .. | .. |
|---|
| 655 | 642 | |
|---|
| 656 | 643 | case BOOT_CF9_FORCE: |
|---|
| 657 | 644 | port_cf9_safe = true; |
|---|
| 658 | | - /* Fall through */ |
|---|
| 645 | + fallthrough; |
|---|
| 659 | 646 | |
|---|
| 660 | 647 | case BOOT_CF9_SAFE: |
|---|
| 661 | 648 | if (port_cf9_safe) { |
|---|
| .. | .. |
|---|
| 804 | 791 | /* This is the CPU performing the emergency shutdown work. */ |
|---|
| 805 | 792 | int crashing_cpu = -1; |
|---|
| 806 | 793 | |
|---|
| 794 | +/* |
|---|
| 795 | + * Disable virtualization, i.e. VMX or SVM, to ensure INIT is recognized during |
|---|
| 796 | + * reboot. VMX blocks INIT if the CPU is post-VMXON, and SVM blocks INIT if |
|---|
| 797 | + * GIF=0, i.e. if the crash occurred between CLGI and STGI. |
|---|
| 798 | + */ |
|---|
| 799 | +void cpu_emergency_disable_virtualization(void) |
|---|
| 800 | +{ |
|---|
| 801 | + cpu_emergency_vmxoff(); |
|---|
| 802 | + cpu_emergency_svm_disable(); |
|---|
| 803 | +} |
|---|
| 804 | + |
|---|
| 807 | 805 | #if defined(CONFIG_SMP) |
|---|
| 808 | 806 | |
|---|
| 809 | 807 | static nmi_shootdown_cb shootdown_callback; |
|---|
| .. | .. |
|---|
| 826 | 824 | return NMI_HANDLED; |
|---|
| 827 | 825 | local_irq_disable(); |
|---|
| 828 | 826 | |
|---|
| 829 | | - shootdown_callback(cpu, regs); |
|---|
| 827 | + if (shootdown_callback) |
|---|
| 828 | + shootdown_callback(cpu, regs); |
|---|
| 829 | + |
|---|
| 830 | + /* |
|---|
| 831 | + * Prepare the CPU for reboot _after_ invoking the callback so that the |
|---|
| 832 | + * callback can safely use virtualization instructions, e.g. VMCLEAR. |
|---|
| 833 | + */ |
|---|
| 834 | + cpu_emergency_disable_virtualization(); |
|---|
| 830 | 835 | |
|---|
| 831 | 836 | atomic_dec(&waiting_for_crash_ipi); |
|---|
| 832 | 837 | /* Assume hlt works */ |
|---|
| .. | .. |
|---|
| 837 | 842 | return NMI_HANDLED; |
|---|
| 838 | 843 | } |
|---|
| 839 | 844 | |
|---|
| 840 | | -static void smp_send_nmi_allbutself(void) |
|---|
| 841 | | -{ |
|---|
| 842 | | - apic->send_IPI_allbutself(NMI_VECTOR); |
|---|
| 843 | | -} |
|---|
| 844 | | - |
|---|
| 845 | | -/* |
|---|
| 846 | | - * Halt all other CPUs, calling the specified function on each of them |
|---|
| 845 | +/** |
|---|
| 846 | + * nmi_shootdown_cpus - Stop other CPUs via NMI |
|---|
| 847 | + * @callback: Optional callback to be invoked from the NMI handler |
|---|
| 847 | 848 | * |
|---|
| 848 | | - * This function can be used to halt all other CPUs on crash |
|---|
| 849 | | - * or emergency reboot time. The function passed as parameter |
|---|
| 850 | | - * will be called inside a NMI handler on all CPUs. |
|---|
| 849 | + * The NMI handler on the remote CPUs invokes @callback, if not |
|---|
| 850 | + * NULL, first and then disables virtualization to ensure that |
|---|
| 851 | + * INIT is recognized during reboot. |
|---|
| 852 | + * |
|---|
| 853 | + * nmi_shootdown_cpus() can only be invoked once. After the first |
|---|
| 854 | + * invocation all other CPUs are stuck in crash_nmi_callback() and |
|---|
| 855 | + * cannot respond to a second NMI. |
|---|
| 851 | 856 | */ |
|---|
| 852 | 857 | void nmi_shootdown_cpus(nmi_shootdown_cb callback) |
|---|
| 853 | 858 | { |
|---|
| 854 | 859 | unsigned long msecs; |
|---|
| 860 | + |
|---|
| 855 | 861 | local_irq_disable(); |
|---|
| 862 | + |
|---|
| 863 | + /* |
|---|
| 864 | + * Avoid certain doom if a shootdown already occurred; re-registering |
|---|
| 865 | + * the NMI handler will cause list corruption, modifying the callback |
|---|
| 866 | + * will do who knows what, etc... |
|---|
| 867 | + */ |
|---|
| 868 | + if (WARN_ON_ONCE(crash_ipi_issued)) |
|---|
| 869 | + return; |
|---|
| 856 | 870 | |
|---|
| 857 | 871 | /* Make a note of crashing cpu. Will be used in NMI callback. */ |
|---|
| 858 | 872 | crashing_cpu = safe_smp_processor_id(); |
|---|
| .. | .. |
|---|
| 870 | 884 | */ |
|---|
| 871 | 885 | wmb(); |
|---|
| 872 | 886 | |
|---|
| 873 | | - smp_send_nmi_allbutself(); |
|---|
| 887 | + apic_send_IPI_allbutself(NMI_VECTOR); |
|---|
| 874 | 888 | |
|---|
| 875 | 889 | /* Kick CPUs looping in NMI context. */ |
|---|
| 876 | 890 | WRITE_ONCE(crash_ipi_issued, 1); |
|---|
| .. | .. |
|---|
| 881 | 895 | msecs--; |
|---|
| 882 | 896 | } |
|---|
| 883 | 897 | |
|---|
| 884 | | - /* Leave the nmi callback set */ |
|---|
| 898 | + /* |
|---|
| 899 | + * Leave the nmi callback set, shootdown is a one-time thing. Clearing |
|---|
| 900 | + * the callback could result in a NULL pointer dereference if a CPU |
|---|
| 901 | + * (finally) responds after the timeout expires. |
|---|
| 902 | + */ |
|---|
| 903 | +} |
|---|
| 904 | + |
|---|
| 905 | +static inline void nmi_shootdown_cpus_on_restart(void) |
|---|
| 906 | +{ |
|---|
| 907 | + if (!crash_ipi_issued) |
|---|
| 908 | + nmi_shootdown_cpus(NULL); |
|---|
| 885 | 909 | } |
|---|
| 886 | 910 | |
|---|
| 887 | 911 | /* |
|---|
| .. | .. |
|---|
| 911 | 935 | /* No other CPUs to shoot down */ |
|---|
| 912 | 936 | } |
|---|
| 913 | 937 | |
|---|
| 938 | +static inline void nmi_shootdown_cpus_on_restart(void) { } |
|---|
| 939 | + |
|---|
| 914 | 940 | void run_crash_ipi_callback(struct pt_regs *regs) |
|---|
| 915 | 941 | { |
|---|
| 916 | 942 | } |
|---|