#include #include #include #include #include #include #include #include #include /** * Start and end page frame numbers of the kernel page tables. */ unsigned long __initdata table_start, table_end; /* page frame numbers */ static __init void *early_ioremap(unsigned long addr, unsigned long size) { unsigned long vaddr; pmd_t *pmd, *last_pmd; int i, pmds; pmds = ((addr & ~PMD_MASK) + size + ~PMD_MASK) / PMD_SIZE; vaddr = __START_KERNEL_map; pmd = level2_kernel_pgt; last_pmd = level2_kernel_pgt + PTRS_PER_PMD - 1; for (; pmd <= last_pmd; pmd++, vaddr += PMD_SIZE) { for (i = 0; i < pmds; i++) { if (pmd_present(pmd[i])) goto next; } vaddr += addr & ~PMD_MASK; addr &= PMD_MASK; for (i = 0; i < pmds; i++, addr += PMD_SIZE) set_pmd(pmd + i,__pmd(addr | _KERNPG_TABLE | _PAGE_PSE)); __flush_tlb(); return (void *)vaddr; next: ; } printk("early_ioremap(0x%lx, %lu) failed\n", addr, size); return NULL; } /* To avoid virtual aliases later */ static __init void early_iounmap(void *addr, unsigned long size) { unsigned long vaddr; pmd_t *pmd; int i, pmds; vaddr = (unsigned long)addr; pmds = ((vaddr & ~PMD_MASK) + size + ~PMD_MASK) / PMD_SIZE; pmd = level2_kernel_pgt + pmd_index(vaddr); for (i = 0; i < pmds; i++) pmd_clear(pmd + i); __flush_tlb(); } static __init void *alloc_low_page(unsigned long *phys) { unsigned long pfn = table_end++; void *adr; if (pfn >= end_pfn) panic("alloc_low_page: ran out of memory"); adr = early_ioremap(pfn * PAGE_SIZE, PAGE_SIZE); memset(adr, 0, PAGE_SIZE); *phys = pfn * PAGE_SIZE; return adr; } /** * Destroys a temporary mapping that was setup by alloc_low_page(). */ static void __init unmap_low_page(void *adr) { early_iounmap(adr, PAGE_SIZE); } /** * Initializes a fixmap entry to point to a given physical page. */ void __init __set_fixmap( enum fixed_addresses fixmap_index, /* fixmap entry index to setup */ unsigned long phys_addr, /* map fixmap entry to this addr */ pgprot_t prot /* page protection bits */ ) { unsigned long virt_addr; pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte, new_pte; if (fixmap_index >= __end_of_fixed_addresses) panic("Invalid FIXMAP index"); /* Calculate the virtual address of the fixmap entry */ virt_addr = __fix_to_virt(fixmap_index); /* Look up PGD entry covering the fixmap entry */ pgd = pgd_offset_k(virt_addr); if (pgd_none(*pgd)) panic("PGD FIXMAP MISSING, it should be setup in head.S!\n"); /* Look up the PMD entry covering the fixmap entry */ pud = pud_offset(pgd, virt_addr); if (pud_none(*pud)) { /* PUD entry is empty... allocate a new PMD directory for it */ pmd = (pmd_t *) alloc_bootmem_aligned(PAGE_SIZE, PAGE_SIZE); set_pud(pud, __pud(__pa(pmd) | _KERNPG_TABLE | _PAGE_USER)); BUG_ON(pmd != pmd_offset(pud, 0)); } /* Look up the PMD entry covering the fixmap entry */ pmd = pmd_offset(pud, virt_addr); if (pmd_none(*pmd)) { /* PMD entry is empty... allocate a new PTE directory for it */ pte = (pte_t *) alloc_bootmem_aligned(PAGE_SIZE, PAGE_SIZE); set_pmd(pmd, __pmd(__pa(pte) | _KERNPG_TABLE | _PAGE_USER)); BUG_ON(pte != pte_offset_kernel(pmd, 0)); } /* * Construct and install a new PTE that maps the fixmap entry * to the requested physical address. */ pte = pte_offset_kernel(pmd, virt_addr); new_pte = pfn_pte(phys_addr >> PAGE_SHIFT, prot); set_pte(pte, new_pte); __flush_tlb_one(virt_addr); } /** * Finds enough space for the kernel page tables. */ static void __init find_early_table_space(unsigned long end) { unsigned long puds; /* # of pud page tables needed */ unsigned long pmds; /* # of pmd page tables needed */ unsigned long tables; /* page table memory needed, in bytes */ unsigned long start; /* start address for kernel page tables */ /* * The kernel page tables map memory using 2 MB pages. * This means only puds and pmds are needed. */ puds = (end + PUD_SIZE - 1) >> PUD_SHIFT; pmds = (end + PMD_SIZE - 1) >> PMD_SHIFT; tables = round_up(puds * sizeof(pud_t), PAGE_SIZE) + round_up(pmds * sizeof(pmd_t), PAGE_SIZE); /* * Consult the memory map to find a region of suitable size. */ start = 0x8000; table_start = find_e820_area(start, end, tables); if (table_start == -1UL) panic("Cannot find space for the kernel page tables"); /* * Store table_start and table_end as page frame numbers. * table_end starts out as the same as table_start. * It will be incremented for each page table allocated. */ table_start >>= PAGE_SHIFT; table_end = table_start; } /** * Configures the input Page Middle Directory to map physical addresses * [address, end). PMD entries outside of this range are zeroed. * * Each PMD table maps 1 GB of memory (512 entries, each mapping 2 MB). */ static void __init phys_pmd_init(pmd_t *pmd, unsigned long address, unsigned long end) { int i; for (i = 0; i < PTRS_PER_PMD; pmd++, i++, address += PMD_SIZE) { unsigned long entry; if (address > end) { for (; i < PTRS_PER_PMD; i++, pmd++) set_pmd(pmd, __pmd(0)); break; } entry = _PAGE_NX|_PAGE_PSE|_KERNPG_TABLE|_PAGE_GLOBAL|address; entry &= __supported_pte_mask; set_pmd(pmd, __pmd(entry)); } } /** * Configures the input Page Upper Directory to map physical addresses * [address, end). PUD entries outside of this range are zeroed. * * Each PUD table maps 512 GB of memory (512 entries, each pointing to a PMD). */ static void __init phys_pud_init(pud_t *pud, unsigned long address, unsigned long end) { long i = pud_index(address); pud = pud + i; for (; i < PTRS_PER_PUD; pud++, i++) { unsigned long paddr, pmd_phys; pmd_t *pmd; paddr = (address & PGDIR_MASK) + i*PUD_SIZE; if (paddr >= end) break; if (!e820_any_mapped(paddr, paddr+PUD_SIZE, 0)) { set_pud(pud, __pud(0)); continue; } pmd = alloc_low_page(&pmd_phys); set_pud(pud, __pud(pmd_phys | _KERNPG_TABLE)); phys_pmd_init(pmd, paddr, end); unmap_low_page(pmd); } __flush_tlb(); } /** * Setup the initial kernel page tables, These map all of physical memory * (0 to the top of physical memory) starting at virtual address PAGE_OFFSET. * This runs before bootmem is initialized and therefore has to get pages * directly from physical memory. */ void __init init_kernel_pgtables(unsigned long start, unsigned long end) { unsigned long next; /* * Find a contiguous region of memory large enough to hold the * kernel page tables. */ find_early_table_space(end); /* * Calculate the start and end kernel virtual addresses * corresponding to the input physical address range. */ start = (unsigned long)__va(start); end = (unsigned long)__va(end); for (; start < end; start = next) { unsigned long pud_phys; pud_t *pud; /* * Allocate a new page for the Page Upper Directory. * * pud = kernel virtual address where the new * page can be accessed. * pud_phys = physical address of the new page. * map = cookie needed to free the temporary mapping. */ pud = alloc_low_page(&pud_phys); /* * Calculate the upper bound address for the PUD. * The PUD maps [start, next). */ next = start + PGDIR_SIZE; if (next > end) next = end; /* * Initialize the new PUD. * phys_pud_init internally calls phys_pmd_init for * each entry in the PUD. */ phys_pud_init(pud, __pa(start), __pa(next)); /* * Point the Page Global Directory at the new PUD. * The PGD is the root of the page tables. */ set_pgd(pgd_offset_k(start), mk_kernel_pgd(pud_phys)); /* Destroy the temporary kernel mapping of the new PUD */ unmap_low_page(pud); } asm volatile("movq %%cr4,%0" : "=r" (mmu_cr4_features)); __flush_tlb_all(); printk(KERN_DEBUG "Allocated %lu KB for kernel page tables [0x%lx - 0x%lx)\n", ((table_end - table_start) << PAGE_SHIFT) / 1024, table_start << PAGE_SHIFT, table_end << PAGE_SHIFT); } /** * This performs architecture-specific memory subsystem initialization. It is * called from the platform-independent memsys_init(). For x86_64, the only * thing that needs to be done is to relocate the initrd image to user memory. */ void __init arch_memsys_init(size_t kmem_size) { struct pmem_region query, result; size_t initrd_size, umem_size; if (!initrd_start) return; initrd_size = initrd_end - initrd_start; umem_size = round_up(initrd_size, PAGE_SIZE); /* Relocate the initrd image to an unallocated chunk of umem */ if (pmem_alloc_umem(umem_size, PAGE_SIZE, &result)) panic("Failed to allocate umem for initrd image."); result.type = PMEM_TYPE_INITRD; pmem_update(&result); memmove(__va(result.start), __va(initrd_start), initrd_size); /* Free the memory used by the old initrd location */ pmem_region_unset_all(&query); query.start = round_down( initrd_start, PAGE_SIZE ); query.end = round_up( initrd_end, PAGE_SIZE ); while (pmem_query(&query, &result) == 0) { result.type = (result.start < kmem_size) ? PMEM_TYPE_KMEM : PMEM_TYPE_UMEM; result.allocated = false; BUG_ON(pmem_update(&result)); query.start = result.end; } /* Update initrd_start and initrd_end to their new values */ initrd_start = result.start; initrd_end = initrd_start + initrd_size; }