root/arch/x86/boot/startup/map_kernel.c
// SPDX-License-Identifier: GPL-2.0

#include <linux/init.h>
#include <linux/linkage.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/pgtable.h>

#include <asm/init.h>
#include <asm/sections.h>
#include <asm/setup.h>
#include <asm/sev.h>

extern pmd_t early_dynamic_pgts[EARLY_DYNAMIC_PAGE_TABLES][PTRS_PER_PMD];
extern unsigned int next_early_pgt;

static inline bool check_la57_support(void)
{
        /*
         * 5-level paging is detected and enabled at kernel decompression
         * stage. Only check if it has been enabled there.
         */
        if (!(native_read_cr4() & X86_CR4_LA57))
                return false;

        __pgtable_l5_enabled    = 1;
        pgdir_shift             = 48;
        ptrs_per_p4d            = 512;

        return true;
}

static unsigned long __init sme_postprocess_startup(struct boot_params *bp,
                                                    pmdval_t *pmd,
                                                    unsigned long p2v_offset)
{
        unsigned long paddr, paddr_end;
        int i;

        /* Encrypt the kernel and related (if SME is active) */
        sme_encrypt_kernel(bp);

        /*
         * Clear the memory encryption mask from the .bss..decrypted section.
         * The bss section will be memset to zero later in the initialization so
         * there is no need to zero it after changing the memory encryption
         * attribute.
         */
        if (sme_get_me_mask()) {
                paddr = (unsigned long)rip_rel_ptr(__start_bss_decrypted);
                paddr_end = (unsigned long)rip_rel_ptr(__end_bss_decrypted);

                for (; paddr < paddr_end; paddr += PMD_SIZE) {
                        /*
                         * On SNP, transition the page to shared in the RMP table so that
                         * it is consistent with the page table attribute change.
                         *
                         * __start_bss_decrypted has a virtual address in the high range
                         * mapping (kernel .text). PVALIDATE, by way of
                         * early_snp_set_memory_shared(), requires a valid virtual
                         * address but the kernel is currently running off of the identity
                         * mapping so use the PA to get a *currently* valid virtual address.
                         */
                        early_snp_set_memory_shared(paddr, paddr, PTRS_PER_PMD);

                        i = pmd_index(paddr - p2v_offset);
                        pmd[i] -= sme_get_me_mask();
                }
        }

        /*
         * Return the SME encryption mask (if SME is active) to be used as a
         * modifier for the initial pgdir entry programmed into CR3.
         */
        return sme_get_me_mask();
}

/*
 * This code is compiled using PIC codegen because it will execute from the
 * early 1:1 mapping of memory, which deviates from the mapping expected by the
 * linker. Due to this deviation, taking the address of a global variable will
 * produce an ambiguous result when using the plain & operator.  Instead,
 * rip_rel_ptr() must be used, which will return the RIP-relative address in
 * the 1:1 mapping of memory. Kernel virtual addresses can be determined by
 * subtracting p2v_offset from the RIP-relative address.
 */
unsigned long __init __startup_64(unsigned long p2v_offset,
                                  struct boot_params *bp)
{
        pmd_t (*early_pgts)[PTRS_PER_PMD] = rip_rel_ptr(early_dynamic_pgts);
        unsigned long physaddr = (unsigned long)rip_rel_ptr(_text);
        unsigned long va_text, va_end;
        unsigned long pgtable_flags;
        unsigned long load_delta;
        pgdval_t *pgd;
        p4dval_t *p4d;
        pudval_t *pud;
        pmdval_t *pmd, pmd_entry;
        bool la57;
        int i;

        la57 = check_la57_support();

        /* Is the address too large? */
        if (physaddr >> MAX_PHYSMEM_BITS)
                for (;;);

        /*
         * Compute the delta between the address I am compiled to run at
         * and the address I am actually running at.
         */
        phys_base = load_delta = __START_KERNEL_map + p2v_offset;

        /* Is the address not 2M aligned? */
        if (load_delta & ~PMD_MASK)
                for (;;);

        va_text = physaddr - p2v_offset;
        va_end  = (unsigned long)rip_rel_ptr(_end) - p2v_offset;

        /* Include the SME encryption mask in the fixup value */
        load_delta += sme_get_me_mask();

        /* Fixup the physical addresses in the page table */

        pgd = rip_rel_ptr(early_top_pgt);
        pgd[pgd_index(__START_KERNEL_map)] += load_delta;

        if (la57) {
                p4d = (p4dval_t *)rip_rel_ptr(level4_kernel_pgt);
                p4d[MAX_PTRS_PER_P4D - 1] += load_delta;

                pgd[pgd_index(__START_KERNEL_map)] = (pgdval_t)p4d | _PAGE_TABLE;
        }

        level3_kernel_pgt[PTRS_PER_PUD - 2].pud += load_delta;
        level3_kernel_pgt[PTRS_PER_PUD - 1].pud += load_delta;

        for (i = FIXMAP_PMD_TOP; i > FIXMAP_PMD_TOP - FIXMAP_PMD_NUM; i--)
                level2_fixmap_pgt[i].pmd += load_delta;

        /*
         * Set up the identity mapping for the switchover.  These
         * entries should *NOT* have the global bit set!  This also
         * creates a bunch of nonsense entries but that is fine --
         * it avoids problems around wraparound.
         */

        pud = &early_pgts[0]->pmd;
        pmd = &early_pgts[1]->pmd;
        next_early_pgt = 2;

        pgtable_flags = _KERNPG_TABLE_NOENC + sme_get_me_mask();

        if (la57) {
                p4d = &early_pgts[next_early_pgt++]->pmd;

                i = (physaddr >> PGDIR_SHIFT) % PTRS_PER_PGD;
                pgd[i + 0] = (pgdval_t)p4d + pgtable_flags;
                pgd[i + 1] = (pgdval_t)p4d + pgtable_flags;

                i = physaddr >> P4D_SHIFT;
                p4d[(i + 0) % PTRS_PER_P4D] = (pgdval_t)pud + pgtable_flags;
                p4d[(i + 1) % PTRS_PER_P4D] = (pgdval_t)pud + pgtable_flags;
        } else {
                i = (physaddr >> PGDIR_SHIFT) % PTRS_PER_PGD;
                pgd[i + 0] = (pgdval_t)pud + pgtable_flags;
                pgd[i + 1] = (pgdval_t)pud + pgtable_flags;
        }

        i = physaddr >> PUD_SHIFT;
        pud[(i + 0) % PTRS_PER_PUD] = (pudval_t)pmd + pgtable_flags;
        pud[(i + 1) % PTRS_PER_PUD] = (pudval_t)pmd + pgtable_flags;

        pmd_entry = __PAGE_KERNEL_LARGE_EXEC & ~_PAGE_GLOBAL;
        pmd_entry += sme_get_me_mask();
        pmd_entry +=  physaddr;

        for (i = 0; i < DIV_ROUND_UP(va_end - va_text, PMD_SIZE); i++) {
                int idx = i + (physaddr >> PMD_SHIFT);

                pmd[idx % PTRS_PER_PMD] = pmd_entry + i * PMD_SIZE;
        }

        /*
         * Fixup the kernel text+data virtual addresses. Note that
         * we might write invalid pmds, when the kernel is relocated
         * cleanup_highmap() fixes this up along with the mappings
         * beyond _end.
         *
         * Only the region occupied by the kernel image has so far
         * been checked against the table of usable memory regions
         * provided by the firmware, so invalidate pages outside that
         * region. A page table entry that maps to a reserved area of
         * memory would allow processor speculation into that area,
         * and on some hardware (particularly the UV platform) even
         * speculative access to some reserved areas is caught as an
         * error, causing the BIOS to halt the system.
         */

        pmd = rip_rel_ptr(level2_kernel_pgt);

        /* invalidate pages before the kernel image */
        for (i = 0; i < pmd_index(va_text); i++)
                pmd[i] &= ~_PAGE_PRESENT;

        /* fixup pages that are part of the kernel image */
        for (; i <= pmd_index(va_end); i++)
                if (pmd[i] & _PAGE_PRESENT)
                        pmd[i] += load_delta;

        /* invalidate pages after the kernel image */
        for (; i < PTRS_PER_PMD; i++)
                pmd[i] &= ~_PAGE_PRESENT;

        return sme_postprocess_startup(bp, pmd, p2v_offset);
}