|
17 | 17 | #include <linux/highmem.h>
|
18 | 18 | #include <linux/amd-iommu.h>
|
19 | 19 | #include <linux/sched.h>
|
| 20 | +#include <linux/delay.h> |
20 | 21 | #include <linux/trace_events.h>
|
21 | 22 | #include <linux/slab.h>
|
22 | 23 | #include <linux/hashtable.h>
|
@@ -248,6 +249,8 @@ static unsigned long iopm_base;
|
248 | 249 |
|
249 | 250 | DEFINE_PER_CPU(struct svm_cpu_data, svm_data);
|
250 | 251 |
|
| 252 | +static DEFINE_SPINLOCK(snp_decommision_lock); |
| 253 | + |
251 | 254 | /*
|
252 | 255 | * Only MSR_TSC_AUX is switched via the user return hook. EFER is switched via
|
253 | 256 | * the VMCB, and the SYSCALL/SYSENTER MSRs are handled by VMLOAD/VMSAVE.
|
@@ -594,9 +597,97 @@ static inline void kvm_cpu_svm_disable(void)
|
594 | 597 |
|
595 | 598 | static void svm_emergency_disable(void)
|
596 | 599 | {
|
| 600 | + static atomic_t waiting_for_cpus_synchronized; |
| 601 | + static bool synchronize_cpus_initiated; |
| 602 | + static bool snp_decommision_handled; |
| 603 | + static atomic_t cpus_synchronized; |
| 604 | + |
597 | 605 | kvm_rebooting = true;
|
598 | 606 |
|
599 | 607 | kvm_cpu_svm_disable();
|
| 608 | + |
| 609 | + if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP)) |
| 610 | + return; |
| 611 | + |
| 612 | + /* |
| 613 | + * SNP_SHUTDOWN_EX fails when SNP VMs are active as the firmware checks |
| 614 | + * every encryption-capable ASID to verify that it is not in use by a |
| 615 | + * guest and a DF_FLUSH is not required. If a DF_FLUSH is required, |
| 616 | + * the firmware returns DFFLUSH_REQUIRED. To address this, SNP_DECOMMISION |
| 617 | + * is required to shutdown all active SNP VMs, but SNP_DECOMMISION tags all |
| 618 | + * CPUs that guest was activated on to do a WBINVD. When panic notifier |
| 619 | + * is invoked all other CPUs have already been shutdown, so it is not |
| 620 | + * possible to do a wbinvd_on_all_cpus() after SNP_DECOMMISION has been |
| 621 | + * executed. This eventually causes SNP_SHUTDOWN_EX to fail after |
| 622 | + * SNP_DECOMMISION. To fix this, do SNP_DECOMMISION and subsequent WBINVD |
| 623 | + * on all CPUs during NMI shutdown of CPUs as part of disabling |
| 624 | + * virtualization on all CPUs via cpu_emergency_disable_virtualization(). |
| 625 | + */ |
| 626 | + |
| 627 | + spin_lock(&snp_decommision_lock); |
| 628 | + |
| 629 | + /* |
| 630 | + * exit early for call from native_machine_crash_shutdown() |
| 631 | + * as SNP_DECOMMISSION has already been done as part of |
| 632 | + * NMI shutdown of the CPUs. |
| 633 | + */ |
| 634 | + if (snp_decommision_handled) { |
| 635 | + spin_unlock(&snp_decommision_lock); |
| 636 | + return; |
| 637 | + } |
| 638 | + |
| 639 | + /* |
| 640 | + * Synchronize all CPUs handling NMI before issuing |
| 641 | + * SNP_DECOMMISSION. |
| 642 | + */ |
| 643 | + if (!synchronize_cpus_initiated) { |
| 644 | + /* |
| 645 | + * one CPU handling panic, the other CPU is initiator for |
| 646 | + * CPU synchronization. |
| 647 | + */ |
| 648 | + atomic_set(&waiting_for_cpus_synchronized, num_online_cpus() - 2); |
| 649 | + synchronize_cpus_initiated = true; |
| 650 | + /* |
| 651 | + * Ensure CPU synchronization parameters are setup before dropping |
| 652 | + * the lock to let other CPUs continue to reach synchronization. |
| 653 | + */ |
| 654 | + wmb(); |
| 655 | + |
| 656 | + spin_unlock(&snp_decommision_lock); |
| 657 | + |
| 658 | + /* |
| 659 | + * This will not cause system to hang forever as the CPU |
| 660 | + * handling panic waits for maximum one second for |
| 661 | + * other CPUs to stop in nmi_shootdown_cpus(). |
| 662 | + */ |
| 663 | + while (atomic_read(&waiting_for_cpus_synchronized) > 0) |
| 664 | + mdelay(1); |
| 665 | + |
| 666 | + /* Reacquire the lock once CPUs are synchronized */ |
| 667 | + spin_lock(&snp_decommision_lock); |
| 668 | + |
| 669 | + atomic_set(&cpus_synchronized, 1); |
| 670 | + } else { |
| 671 | + atomic_dec(&waiting_for_cpus_synchronized); |
| 672 | + /* |
| 673 | + * drop the lock to let other CPUs contiune to reach |
| 674 | + * synchronization. |
| 675 | + */ |
| 676 | + spin_unlock(&snp_decommision_lock); |
| 677 | + |
| 678 | + while (atomic_read(&cpus_synchronized) == 0) |
| 679 | + mdelay(1); |
| 680 | + |
| 681 | + /* Try to re-acquire lock after CPUs are synchronized */ |
| 682 | + spin_lock(&snp_decommision_lock); |
| 683 | + } |
| 684 | + |
| 685 | + if (!snp_decommision_handled) { |
| 686 | + snp_decommision_all(); |
| 687 | + snp_decommision_handled = true; |
| 688 | + } |
| 689 | + spin_unlock(&snp_decommision_lock); |
| 690 | + wbinvd(); |
600 | 691 | }
|
601 | 692 |
|
602 | 693 | static void svm_hardware_disable(void)
|
|
0 commit comments