root/arch/x86/hyperv/hv_crash.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * X86 specific Hyper-V root partition kdump/crash support module
 *
 * Copyright (C) 2025, Microsoft, Inc.
 *
 * This module implements hypervisor RAM collection into vmcore for both
 * cases of the hypervisor crash and Linux root crash. Hyper-V implements
 * a disable hypercall with a 32bit protected mode ABI callback. This
 * mechanism must be used to unlock hypervisor RAM. Since the hypervisor RAM
 * is already mapped in Linux, it is automatically collected into Linux vmcore,
 * and can be examined by the crash command (raw RAM dump) or windbg.
 *
 * At a high level:
 *
 *  Hypervisor Crash:
 *    Upon crash, hypervisor goes into an emergency minimal dispatch loop, a
 *    restrictive mode with very limited hypercall and MSR support. Each cpu
 *    then injects NMIs into root vcpus. A shared page is used to check
 *    by Linux in the NMI handler if the hypervisor has crashed. This shared
 *    page is setup in hv_root_crash_init during boot.
 *
 *  Linux Crash:
 *    In case of Linux crash, the callback hv_crash_stop_other_cpus will send
 *    NMIs to all cpus, then proceed to the crash_nmi_callback where it waits
 *    for all cpus to be in NMI.
 *
 *  NMI Handler (upon quorum):
 *    Eventually, in both cases, all cpus will end up in the NMI handler.
 *    Hyper-V requires the disable hypervisor must be done from the BSP. So
 *    the BSP NMI handler saves current context, does some fixups and makes
 *    the hypercall to disable the hypervisor, ie, devirtualize. Hypervisor
 *    at that point will suspend all vcpus (except the BSP), unlock all its
 *    RAM, and return to Linux at the 32bit mode entry RIP.
 *
 *  Linux 32bit entry trampoline will then restore long mode and call C
 *  function here to restore context and continue execution to crash kexec.
 */

#include <linux/delay.h>
#include <linux/kexec.h>
#include <linux/crash_dump.h>
#include <linux/panic.h>
#include <asm/apic.h>
#include <asm/desc.h>
#include <asm/page.h>
#include <asm/pgalloc.h>
#include <asm/mshyperv.h>
#include <asm/nmi.h>
#include <asm/idtentry.h>
#include <asm/reboot.h>
#include <asm/intel_pt.h>

bool hv_crash_enabled;
EXPORT_SYMBOL_GPL(hv_crash_enabled);

struct hv_crash_ctxt {
        ulong rsp;
        ulong cr0;
        ulong cr2;
        ulong cr4;
        ulong cr8;

        u16 cs;
        u16 ss;
        u16 ds;
        u16 es;
        u16 fs;
        u16 gs;

        u16 gdt_fill;
        struct desc_ptr gdtr;
        char idt_fill[6];
        struct desc_ptr idtr;

        u64 gsbase;
        u64 efer;
        u64 pat;
};
static struct hv_crash_ctxt hv_crash_ctxt;

/* Shared hypervisor page that contains crash dump area we peek into.
 * NB: windbg looks for "hv_cda" symbol so don't change it.
 */
static struct hv_crashdump_area *hv_cda;

static u32 trampoline_pa, devirt_arg;
static atomic_t crash_cpus_wait;
static void *hv_crash_ptpgs[4];
static bool hv_has_crashed, lx_has_crashed;

static void __noreturn hv_panic_timeout_reboot(void)
{
        #define PANIC_TIMER_STEP 100

        if (panic_timeout > 0) {
                int i;

                for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP)
                        mdelay(PANIC_TIMER_STEP);
        }

        if (panic_timeout)
                native_wrmsrq(HV_X64_MSR_RESET, 1);    /* get hyp to reboot */

        for (;;)
                cpu_relax();
}

static void hv_crash_restore_tss(void)
{
        load_TR_desc();
}

static void hv_crash_clear_kernpt(void)
{
        pgd_t *pgd;
        p4d_t *p4d;

        /* Clear entry so it's not confusing to someone looking at the core */
        pgd = pgd_offset_k(trampoline_pa);
        p4d = p4d_offset(pgd, trampoline_pa);
        native_p4d_clear(p4d);
}


static void __noreturn hv_crash_handle(void)
{
        hv_crash_restore_tss();
        hv_crash_clear_kernpt();

        /* we are now fully in devirtualized normal kernel mode */
        __crash_kexec(NULL);

        hv_panic_timeout_reboot();
}

/*
 * __naked functions do not permit function calls, not even to __always_inline
 * functions that only contain asm() blocks themselves. So use a macro instead.
 */
#define hv_wrmsr(msr, val) \
        asm volatile("wrmsr" :: "c"(msr), "a"((u32)val), "d"((u32)(val >> 32)) : "memory")

/*
 * This is the C entry point from the asm glue code after the disable hypercall.
 * We enter here in IA32-e long mode, ie, full 64bit mode running on kernel
 * page tables with our below 4G page identity mapped, but using a temporary
 * GDT. ds/fs/gs/es are null. ss is not usable. bp is null. stack is not
 * available. We restore kernel GDT, and rest of the context, and continue
 * to kexec.
 */
static void __naked hv_crash_c_entry(void)
{
        /* first thing, restore kernel gdt */
        asm volatile("lgdt %0" : : "m" (hv_crash_ctxt.gdtr));

        asm volatile("movw %0, %%ss\n\t"
                     "movq %1, %%rsp"
                     :: "m"(hv_crash_ctxt.ss), "m"(hv_crash_ctxt.rsp));

        asm volatile("movw %0, %%ds" : : "m"(hv_crash_ctxt.ds));
        asm volatile("movw %0, %%es" : : "m"(hv_crash_ctxt.es));
        asm volatile("movw %0, %%fs" : : "m"(hv_crash_ctxt.fs));
        asm volatile("movw %0, %%gs" : : "m"(hv_crash_ctxt.gs));

        hv_wrmsr(MSR_IA32_CR_PAT, hv_crash_ctxt.pat);
        asm volatile("movq %0, %%cr0" : : "r"(hv_crash_ctxt.cr0));

        asm volatile("movq %0, %%cr8" : : "r"(hv_crash_ctxt.cr8));
        asm volatile("movq %0, %%cr4" : : "r"(hv_crash_ctxt.cr4));
        asm volatile("movq %0, %%cr2" : : "r"(hv_crash_ctxt.cr2));

        asm volatile("lidt %0" : : "m" (hv_crash_ctxt.idtr));
        hv_wrmsr(MSR_GS_BASE, hv_crash_ctxt.gsbase);
        hv_wrmsr(MSR_EFER, hv_crash_ctxt.efer);

        /* restore the original kernel CS now via far return */
        asm volatile("pushq %q0\n\t"
                     "pushq %q1\n\t"
                     "lretq"
                     :: "r"(hv_crash_ctxt.cs), "r"(hv_crash_handle));
}
/* Tell objtool we are using lretq long jump in the above function intentionally */
STACK_FRAME_NON_STANDARD(hv_crash_c_entry);

static void hv_mark_tss_not_busy(void)
{
        struct desc_struct *desc = get_current_gdt_rw();
        tss_desc tss;

        memcpy(&tss, &desc[GDT_ENTRY_TSS], sizeof(tss_desc));
        tss.type = 0x9;        /* available 64-bit TSS. 0xB is busy TSS */
        write_gdt_entry(desc, GDT_ENTRY_TSS, &tss, DESC_TSS);
}

/* Save essential context */
static void hv_hvcrash_ctxt_save(void)
{
        struct hv_crash_ctxt *ctxt = &hv_crash_ctxt;

        ctxt->rsp = current_stack_pointer;

        ctxt->cr0 = native_read_cr0();
        ctxt->cr4 = native_read_cr4();

        asm volatile("movq %%cr2, %0" : "=r"(ctxt->cr2));
        asm volatile("movq %%cr8, %0" : "=r"(ctxt->cr8));

        asm volatile("movw %%cs, %0" : "=m"(ctxt->cs));
        asm volatile("movw %%ss, %0" : "=m"(ctxt->ss));
        asm volatile("movw %%ds, %0" : "=m"(ctxt->ds));
        asm volatile("movw %%es, %0" : "=m"(ctxt->es));
        asm volatile("movw %%fs, %0" : "=m"(ctxt->fs));
        asm volatile("movw %%gs, %0" : "=m"(ctxt->gs));

        native_store_gdt(&ctxt->gdtr);
        store_idt(&ctxt->idtr);

        ctxt->gsbase = __rdmsr(MSR_GS_BASE);
        ctxt->efer = __rdmsr(MSR_EFER);
        ctxt->pat = __rdmsr(MSR_IA32_CR_PAT);
}

/* Add trampoline page to the kernel pagetable for transition to kernel PT */
static void hv_crash_fixup_kernpt(void)
{
        pgd_t *pgd;
        p4d_t *p4d;

        pgd = pgd_offset_k(trampoline_pa);
        p4d = p4d_offset(pgd, trampoline_pa);

        /* trampoline_pa is below 4G, so no pre-existing entry to clobber */
        p4d_populate(&init_mm, p4d, (pud_t *)hv_crash_ptpgs[1]);
        p4d->p4d = p4d->p4d & ~(_PAGE_NX);    /* enable execute */
}

/*
 * Notify the hyp that Linux has crashed. This will cause the hyp to quiesce
 * and suspend all guest VPs.
 */
static void hv_notify_prepare_hyp(void)
{
        u64 status;
        struct hv_input_notify_partition_event *input;
        struct hv_partition_event_root_crashdump_input *cda;

        input = *this_cpu_ptr(hyperv_pcpu_input_arg);
        cda = &input->input.crashdump_input;
        memset(input, 0, sizeof(*input));
        input->event = HV_PARTITION_EVENT_ROOT_CRASHDUMP;

        cda->crashdump_action = HV_CRASHDUMP_ENTRY;
        status = hv_do_hypercall(HVCALL_NOTIFY_PARTITION_EVENT, input, NULL);
        if (!hv_result_success(status))
                return;

        cda->crashdump_action = HV_CRASHDUMP_SUSPEND_ALL_VPS;
        hv_do_hypercall(HVCALL_NOTIFY_PARTITION_EVENT, input, NULL);
}

/*
 * Common function for all cpus before devirtualization.
 *
 * Hypervisor crash: all cpus get here in NMI context.
 * Linux crash: the panicing cpu gets here at base level, all others in NMI
 *              context. Note, panicing cpu may not be the BSP.
 *
 * The function is not inlined so it will show on the stack. It is named so
 * because the crash cmd looks for certain well known function names on the
 * stack before looking into the cpu saved note in the elf section, and
 * that work is currently incomplete.
 *
 * Notes:
 *  Hypervisor crash:
 *    - the hypervisor is in a very restrictive mode at this point and any
 *      vmexit it cannot handle would result in reboot. So, no mumbo jumbo,
 *      just get to kexec as quickly as possible.
 *
 *  Devirtualization is supported from the BSP only at present.
 */
static noinline __noclone void crash_nmi_callback(struct pt_regs *regs)
{
        struct hv_input_disable_hyp_ex *input;
        int msecs = 1000, ccpu = smp_processor_id();

        if (ccpu == 0) {
                /* crash_save_cpu() will be done in the kexec path */
                cpu_emergency_stop_pt();        /* disable performance trace */
                atomic_inc(&crash_cpus_wait);
        } else {
                crash_save_cpu(regs, ccpu);
                cpu_emergency_stop_pt();        /* disable performance trace */
                atomic_inc(&crash_cpus_wait);
                for (;;)
                        cpu_relax();
        }

        while (atomic_read(&crash_cpus_wait) < num_online_cpus() && msecs--)
                mdelay(1);

        stop_nmi();
        if (!hv_has_crashed)
                hv_notify_prepare_hyp();

        if (crashing_cpu == -1)
                crashing_cpu = ccpu;            /* crash cmd uses this */

        hv_hvcrash_ctxt_save();
        hv_mark_tss_not_busy();
        hv_crash_fixup_kernpt();

        input = *this_cpu_ptr(hyperv_pcpu_input_arg);
        memset(input, 0, sizeof(*input));
        input->rip = trampoline_pa;
        input->arg = devirt_arg;

        (void)hv_do_hypercall(HVCALL_DISABLE_HYP_EX, input, NULL);

        hv_panic_timeout_reboot();
}


static DEFINE_SPINLOCK(hv_crash_reboot_lk);

/*
 * Generic NMI callback handler: could be called without any crash also.
 *   hv crash: hypervisor injects NMI's into all cpus
 *   lx crash: panicing cpu sends NMI to all but self via crash_stop_other_cpus
 */
static int hv_crash_nmi_local(unsigned int cmd, struct pt_regs *regs)
{
        if (!hv_has_crashed && hv_cda && hv_cda->cda_valid)
                hv_has_crashed = true;

        if (!hv_has_crashed && !lx_has_crashed)
                return NMI_DONE;        /* ignore the NMI */

        if (hv_has_crashed && !kexec_crash_loaded()) {
                if (spin_trylock(&hv_crash_reboot_lk))
                        hv_panic_timeout_reboot();
                else
                        for (;;)
                                cpu_relax();
        }

        crash_nmi_callback(regs);

        return NMI_DONE;
}

/*
 * hv_crash_stop_other_cpus() == smp_ops.crash_stop_other_cpus
 *
 * On normal Linux panic, this is called twice: first from panic and then again
 * from native_machine_crash_shutdown.
 *
 * In case of hyperv, 3 ways to get here:
 *  1. hv crash (only BSP will get here):
 *      BSP : NMI callback -> DisableHv -> hv_crash_asm32 -> hv_crash_c_entry
 *                -> __crash_kexec -> native_machine_crash_shutdown
 *                -> crash_smp_send_stop -> smp_ops.crash_stop_other_cpus
 *  Linux panic:
 *      2. panic cpu x: panic() -> crash_smp_send_stop
 *                                   -> smp_ops.crash_stop_other_cpus
 *      3. BSP: native_machine_crash_shutdown -> crash_smp_send_stop
 *
 * NB: noclone and non standard stack because of call to crash_setup_regs().
 */
static void __noclone hv_crash_stop_other_cpus(void)
{
        static bool crash_stop_done;
        struct pt_regs lregs;
        int ccpu = smp_processor_id();

        if (hv_has_crashed)
                return;         /* all cpus already in NMI handler path */

        if (!kexec_crash_loaded()) {
                hv_notify_prepare_hyp();
                hv_panic_timeout_reboot();      /* no return */
        }

        /* If the hv crashes also, we could come here again before cpus_stopped
         * is set in crash_smp_send_stop(). So use our own check.
         */
        if (crash_stop_done)
                return;
        crash_stop_done = true;

        /* Linux has crashed: hv is healthy, we can IPI safely */
        lx_has_crashed = true;
        wmb();                  /* NMI handlers look at lx_has_crashed */

        apic->send_IPI_allbutself(NMI_VECTOR);

        if (crashing_cpu == -1)
                crashing_cpu = ccpu;            /* crash cmd uses this */

        /* crash_setup_regs() happens in kexec also, but for the kexec cpu which
         * is the BSP. We could be here on non-BSP cpu, collect regs if so.
         */
        if (ccpu)
                crash_setup_regs(&lregs, NULL);

        crash_nmi_callback(&lregs);
}
STACK_FRAME_NON_STANDARD(hv_crash_stop_other_cpus);

/* This GDT is accessed in IA32-e compat mode which uses 32bits addresses */
struct hv_gdtreg_32 {
        u16 fill;
        u16 limit;
        u32 address;
} __packed;

/* We need a CS with L bit to goto IA32-e long mode from 32bit compat mode */
struct hv_crash_tramp_gdt {
        u64 null;       /* index 0, selector 0, null selector */
        u64 cs64;       /* index 1, selector 8, cs64 selector */
} __packed;

/* No stack, so jump via far ptr in memory to load the 64bit CS */
struct hv_cs_jmptgt {
        u32 address;
        u16 csval;
        u16 fill;
} __packed;

/* Linux use only, hypervisor doesn't look at this struct */
struct hv_crash_tramp_data {
        u64 tramp32_cr3;
        u64 kernel_cr3;
        struct hv_gdtreg_32 gdtr32;
        struct hv_crash_tramp_gdt tramp_gdt;
        struct hv_cs_jmptgt cs_jmptgt;
        u64 c_entry_addr;
} __packed;

/*
 * Setup a temporary gdt to allow the asm code to switch to the long mode.
 * Since the asm code is relocated/copied to a below 4G page, it cannot use rip
 * relative addressing, hence we must use trampoline_pa here. Also, save other
 * info like jmp and C entry targets for same reasons.
 *
 * Returns: 0 on success, -1 on error
 */
static int hv_crash_setup_trampdata(u64 trampoline_va)
{
        int size, offs;
        void *dest;
        struct hv_crash_tramp_data *tramp;

        /* These must match exactly the ones in the corresponding asm file */
        BUILD_BUG_ON(offsetof(struct hv_crash_tramp_data, tramp32_cr3) != 0);
        BUILD_BUG_ON(offsetof(struct hv_crash_tramp_data, kernel_cr3) != 8);
        BUILD_BUG_ON(offsetof(struct hv_crash_tramp_data, gdtr32.limit) != 18);
        BUILD_BUG_ON(offsetof(struct hv_crash_tramp_data,
                                                     cs_jmptgt.address) != 40);
        BUILD_BUG_ON(offsetof(struct hv_crash_tramp_data, c_entry_addr) != 48);

        /* hv_crash_asm_end is beyond last byte by 1 */
        size = &hv_crash_asm_end - &hv_crash_asm32;
        if (size + sizeof(struct hv_crash_tramp_data) > PAGE_SIZE) {
                pr_err("%s: trampoline page overflow\n", __func__);
                return -1;
        }

        dest = (void *)trampoline_va;
        memcpy(dest, &hv_crash_asm32, size);

        dest += size;
        dest = (void *)round_up((ulong)dest, 16);
        tramp = (struct hv_crash_tramp_data *)dest;

        /* see MAX_ASID_AVAILABLE in tlb.c: "PCID 0 is reserved for use by
         * non-PCID-aware users". Build cr3 with pcid 0
         */
        tramp->tramp32_cr3 = __sme_pa(hv_crash_ptpgs[0]);

        /* Note, when restoring X86_CR4_PCIDE, cr3[11:0] must be zero */
        tramp->kernel_cr3 = __sme_pa(init_mm.pgd);

        tramp->gdtr32.limit = sizeof(struct hv_crash_tramp_gdt);
        tramp->gdtr32.address = trampoline_pa +
                                   (ulong)&tramp->tramp_gdt - trampoline_va;

         /* base:0 limit:0xfffff type:b dpl:0 P:1 L:1 D:0 avl:0 G:1 */
        tramp->tramp_gdt.cs64 = 0x00af9a000000ffff;

        tramp->cs_jmptgt.csval = 0x8;
        offs = (ulong)&hv_crash_asm64 - (ulong)&hv_crash_asm32;
        tramp->cs_jmptgt.address = trampoline_pa + offs;

        tramp->c_entry_addr = (u64)&hv_crash_c_entry;

        devirt_arg = trampoline_pa + (ulong)dest - trampoline_va;

        return 0;
}

/*
 * Build 32bit trampoline page table for transition from protected mode
 * non-paging to long-mode paging. This transition needs pagetables below 4G.
 */
static void hv_crash_build_tramp_pt(void)
{
        p4d_t *p4d;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;
        u64 pa, addr = trampoline_pa;

        p4d = hv_crash_ptpgs[0] + pgd_index(addr) * sizeof(p4d);
        pa = virt_to_phys(hv_crash_ptpgs[1]);
        set_p4d(p4d, __p4d(_PAGE_TABLE | pa));
        p4d->p4d &= ~(_PAGE_NX);        /* enable execute */

        pud = hv_crash_ptpgs[1] + pud_index(addr) * sizeof(pud);
        pa = virt_to_phys(hv_crash_ptpgs[2]);
        set_pud(pud, __pud(_PAGE_TABLE | pa));

        pmd = hv_crash_ptpgs[2] + pmd_index(addr) * sizeof(pmd);
        pa = virt_to_phys(hv_crash_ptpgs[3]);
        set_pmd(pmd, __pmd(_PAGE_TABLE | pa));

        pte = hv_crash_ptpgs[3] + pte_index(addr) * sizeof(pte);
        set_pte(pte, pfn_pte(addr >> PAGE_SHIFT, PAGE_KERNEL_EXEC));
}

/*
 * Setup trampoline for devirtualization:
 *  - a page below 4G, ie 32bit addr containing asm glue code that hyp jmps to
 *    in protected mode.
 *  - 4 pages for a temporary page table that asm code uses to turn paging on
 *  - a temporary gdt to use in the compat mode.
 *
 *  Returns: 0 on success
 */
static int hv_crash_trampoline_setup(void)
{
        int i, rc, order;
        struct page *page;
        u64 trampoline_va;
        gfp_t flags32 = GFP_KERNEL | GFP_DMA32 | __GFP_ZERO;

        /* page for 32bit trampoline assembly code + hv_crash_tramp_data */
        page = alloc_page(flags32);
        if (page == NULL) {
                pr_err("%s: failed to alloc asm stub page\n", __func__);
                return -1;
        }

        trampoline_va = (u64)page_to_virt(page);
        trampoline_pa = (u32)page_to_phys(page);

        order = 2;         /* alloc 2^2 pages */
        page = alloc_pages(flags32, order);
        if (page == NULL) {
                pr_err("%s: failed to alloc pt pages\n", __func__);
                free_page(trampoline_va);
                return -1;
        }

        for (i = 0; i < 4; i++, page++)
                hv_crash_ptpgs[i] = page_to_virt(page);

        hv_crash_build_tramp_pt();

        rc = hv_crash_setup_trampdata(trampoline_va);
        if (rc)
                goto errout;

        return 0;

errout:
        free_page(trampoline_va);
        free_pages((ulong)hv_crash_ptpgs[0], order);

        return rc;
}

/* Setup for kdump kexec to collect hypervisor RAM when running as root */
void hv_root_crash_init(void)
{
        int rc;
        struct hv_input_get_system_property *input;
        struct hv_output_get_system_property *output;
        unsigned long flags;
        u64 status;
        union hv_pfn_range cda_info;

        if (pgtable_l5_enabled()) {
                pr_err("Hyper-V: crash dump not yet supported on 5level PTs\n");
                return;
        }

        rc = register_nmi_handler(NMI_LOCAL, hv_crash_nmi_local, NMI_FLAG_FIRST,
                                  "hv_crash_nmi");
        if (rc) {
                pr_err("Hyper-V: failed to register crash nmi handler\n");
                return;
        }

        local_irq_save(flags);
        input = *this_cpu_ptr(hyperv_pcpu_input_arg);
        output = *this_cpu_ptr(hyperv_pcpu_output_arg);

        memset(input, 0, sizeof(*input));
        input->property_id = HV_SYSTEM_PROPERTY_CRASHDUMPAREA;

        status = hv_do_hypercall(HVCALL_GET_SYSTEM_PROPERTY, input, output);
        cda_info.as_uint64 = output->hv_cda_info.as_uint64;
        local_irq_restore(flags);

        if (!hv_result_success(status)) {
                pr_err("Hyper-V: %s: property:%d %s\n", __func__,
                       input->property_id, hv_result_to_string(status));
                goto err_out;
        }

        if (cda_info.base_pfn == 0) {
                pr_err("Hyper-V: hypervisor crash dump area pfn is 0\n");
                goto err_out;
        }

        hv_cda = phys_to_virt(cda_info.base_pfn << HV_HYP_PAGE_SHIFT);

        rc = hv_crash_trampoline_setup();
        if (rc)
                goto err_out;

#ifdef CONFIG_SMP
        smp_ops.crash_stop_other_cpus = hv_crash_stop_other_cpus;
#endif

        crash_kexec_post_notifiers = true;
        hv_crash_enabled = true;
        pr_info("Hyper-V: both linux and hypervisor kdump support enabled\n");

        return;

err_out:
        unregister_nmi_handler(NMI_LOCAL, "hv_crash_nmi");
        pr_err("Hyper-V: only linux root kdump support enabled\n");
}