summaryrefslogtreecommitdiff
path: root/arch/x86_64.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86_64.c')
-rw-r--r--arch/x86_64.c907
1 files changed, 907 insertions, 0 deletions
diff --git a/arch/x86_64.c b/arch/x86_64.c
new file mode 100644
index 0000000..2b3c0bb
--- /dev/null
+++ b/arch/x86_64.c
@@ -0,0 +1,907 @@
+/*
+ * x86_64.c
+ *
+ * Copyright (C) 2006, 2007, 2008 NEC Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifdef __x86_64__
+
+#include "../print_info.h"
+#include "../elf_info.h"
+#include "../makedumpfile.h"
+extern struct vmap_pfns *gvmem_pfns;
+extern int nr_gvmem_pfns;
+
+static unsigned long
+get_xen_p2m_mfn(void)
+{
+ if (info->xen_crash_info_v >= 2)
+ return info->xen_crash_info.v2->
+ dom0_pfn_to_mfn_frame_list_list;
+ if (info->xen_crash_info_v >= 1)
+ return info->xen_crash_info.v1->
+ dom0_pfn_to_mfn_frame_list_list;
+ return NOT_FOUND_LONG_VALUE;
+}
+
+static int
+check_5level_paging(void)
+{
+ if (NUMBER(pgtable_l5_enabled) != NOT_FOUND_NUMBER &&
+ NUMBER(pgtable_l5_enabled) != 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+unsigned long
+get_kaslr_offset_x86_64(unsigned long vaddr)
+{
+ unsigned int i;
+ char buf[BUFSIZE_FGETS], *endp;
+
+ if (!info->kaslr_offset && info->file_vmcoreinfo) {
+ if (fseek(info->file_vmcoreinfo, 0, SEEK_SET) < 0) {
+ ERRMSG("Can't seek the vmcoreinfo file(%s). %s\n",
+ info->name_vmcoreinfo, strerror(errno));
+ return FALSE;
+ }
+
+ while (fgets(buf, BUFSIZE_FGETS, info->file_vmcoreinfo)) {
+ i = strlen(buf);
+ if (!i)
+ break;
+ if (buf[i - 1] == '\n')
+ buf[i - 1] = '\0';
+ if (strncmp(buf, STR_KERNELOFFSET,
+ strlen(STR_KERNELOFFSET)) == 0)
+ info->kaslr_offset =
+ strtoul(buf+strlen(STR_KERNELOFFSET),&endp,16);
+ }
+ }
+ if (vaddr >= __START_KERNEL_map &&
+ vaddr < __START_KERNEL_map + info->kaslr_offset)
+ return info->kaslr_offset;
+ else
+ /*
+ * TODO: we need to check if it is vmalloc/vmmemmap/module
+ * address, we will have different offset
+ */
+ return 0;
+}
+
+static int
+get_page_offset_x86_64(void)
+{
+ int i;
+ unsigned long long phys_start;
+ unsigned long long virt_start;
+ unsigned long page_offset_base;
+
+ if (info->kaslr_offset && (info->fd_vmlinux != -1)) {
+ page_offset_base = get_symbol_addr("page_offset_base");
+ page_offset_base += info->kaslr_offset;
+ if (!readmem(VADDR, page_offset_base, &info->page_offset,
+ sizeof(info->page_offset))) {
+ ERRMSG("Can't read page_offset_base.\n");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ if (get_num_pt_loads()) {
+ for (i = 0;
+ get_pt_load(i, &phys_start, NULL, &virt_start, NULL);
+ i++) {
+ if (virt_start != NOT_KV_ADDR
+ && virt_start < __START_KERNEL_map
+ && phys_start != NOT_PADDR) {
+ info->page_offset = virt_start - phys_start;
+ return TRUE;
+ }
+ }
+ }
+
+ if (info->kernel_version < KERNEL_VERSION(2, 6, 27)) {
+ info->page_offset = __PAGE_OFFSET_ORIG;
+ } else if(check_5level_paging()) {
+ info->page_offset = __PAGE_OFFSET_5LEVEL;
+ } else {
+ info->page_offset = __PAGE_OFFSET_2_6_27;
+ }
+
+ return TRUE;
+}
+
+int
+get_phys_base_x86_64(void)
+{
+ int i;
+ unsigned long long phys_start;
+ unsigned long long virt_start;
+
+ /*
+ * Get the relocatable offset
+ */
+ info->phys_base = 0; /* default/traditional */
+ if (NUMBER(phys_base) != NOT_FOUND_NUMBER) {
+ info->phys_base = NUMBER(phys_base);
+ return TRUE;
+ }
+
+ /* linux-2.6.21 or older don't have phys_base, should be set to 0. */
+ if (!has_vmcoreinfo()) {
+ SYMBOL_INIT(phys_base, "phys_base");
+ if (SYMBOL(phys_base) == NOT_FOUND_SYMBOL) {
+ return TRUE;
+ }
+ }
+
+ for (i = 0; get_pt_load(i, &phys_start, NULL, &virt_start, NULL); i++) {
+ if (virt_start >= __START_KERNEL_map
+ && phys_start != NOT_PADDR) {
+
+ info->phys_base = phys_start -
+ (virt_start & ~(__START_KERNEL_map));
+
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+int
+get_machdep_info_x86_64(void)
+{
+ unsigned long p2m_mfn;
+ int i, j, mfns[MAX_X86_64_FRAMES];
+ unsigned long frame_mfn[MAX_X86_64_FRAMES];
+ unsigned long buf[MFNS_PER_FRAME];
+
+ info->section_size_bits = _SECTION_SIZE_BITS;
+
+ if (!is_xen_memory())
+ return TRUE;
+
+ /*
+ * Get the information for translating domain-0's physical
+ * address into machine address.
+ */
+ p2m_mfn = get_xen_p2m_mfn();
+ if (p2m_mfn == (unsigned long)NOT_FOUND_LONG_VALUE) {
+ ERRMSG("Can't get p2m_mfn address.\n");
+ return FALSE;
+ }
+ if (!readmem(PADDR, pfn_to_paddr(p2m_mfn),
+ &frame_mfn, PAGESIZE())) {
+ ERRMSG("Can't read p2m_mfn.\n");
+ return FALSE;
+ }
+
+ /*
+ * Count the number of p2m frame.
+ */
+ for (i = 0; i < MAX_X86_64_FRAMES; i++) {
+ mfns[i] = 0;
+ if (!frame_mfn[i])
+ break;
+
+ if (!readmem(PADDR, pfn_to_paddr(frame_mfn[i]), &buf,
+ PAGESIZE())) {
+ ERRMSG("Can't get frame_mfn[%d].\n", i);
+ return FALSE;
+ }
+ for (j = 0; j < MFNS_PER_FRAME; j++) {
+ if (!buf[j])
+ break;
+
+ mfns[i]++;
+ }
+ info->p2m_frames += mfns[i];
+ }
+ info->p2m_mfn_frame_list
+ = malloc(sizeof(unsigned long) * info->p2m_frames);
+ if (info->p2m_mfn_frame_list == NULL) {
+ ERRMSG("Can't allocate memory for p2m_mfn_frame_list. %s\n",
+ strerror(errno));
+ return FALSE;
+ }
+
+ /*
+ * Get p2m_mfn_frame_list.
+ */
+ for (i = 0; i < MAX_X86_64_FRAMES; i++) {
+ if (!frame_mfn[i])
+ break;
+
+ if (!readmem(PADDR, pfn_to_paddr(frame_mfn[i]),
+ &info->p2m_mfn_frame_list[i * MFNS_PER_FRAME],
+ mfns[i] * sizeof(unsigned long))) {
+ ERRMSG("Can't get p2m_mfn_frame_list.\n");
+ return FALSE;
+ }
+ if (mfns[i] != MFNS_PER_FRAME)
+ break;
+ }
+ return TRUE;
+}
+
+int
+get_versiondep_info_x86_64(void)
+{
+ /*
+ * On linux-2.6.26, MAX_PHYSMEM_BITS is changed to 44 from 40.
+ */
+ if (info->kernel_version < KERNEL_VERSION(2, 6, 26))
+ info->max_physmem_bits = _MAX_PHYSMEM_BITS_ORIG;
+ else if (info->kernel_version < KERNEL_VERSION(2, 6, 31))
+ info->max_physmem_bits = _MAX_PHYSMEM_BITS_2_6_26;
+ else if(check_5level_paging())
+ info->max_physmem_bits = _MAX_PHYSMEM_BITS_5LEVEL;
+ else
+ info->max_physmem_bits = _MAX_PHYSMEM_BITS_2_6_31;
+
+ if (!get_page_offset_x86_64())
+ return FALSE;
+
+ if (info->kernel_version < KERNEL_VERSION(2, 6, 31)) {
+ info->vmemmap_start = VMEMMAP_START_ORIG;
+ info->vmemmap_end = VMEMMAP_END_ORIG;
+ } else if(check_5level_paging()) {
+ info->vmemmap_start = VMEMMAP_START_5LEVEL;
+ info->vmemmap_end = VMEMMAP_END_5LEVEL;
+ } else {
+ info->vmemmap_start = VMEMMAP_START_2_6_31;
+ info->vmemmap_end = VMEMMAP_END_2_6_31;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Translate a virtual address to a physical address by using 4 levels paging.
+ */
+unsigned long long
+__vtop4_x86_64(unsigned long vaddr, unsigned long pagetable)
+{
+ unsigned long page_dir, pgd, pud_paddr, pud_pte, pmd_paddr, pmd_pte;
+ unsigned long pte_paddr, pte;
+ unsigned long p4d_paddr, p4d_pte;
+
+ /*
+ * Get PGD.
+ */
+ page_dir = pagetable;
+ if (is_xen_memory()) {
+ page_dir = ptom_xen(page_dir);
+ if (page_dir == NOT_PADDR)
+ return NOT_PADDR;
+ }
+
+ if (check_5level_paging()) {
+ page_dir += pgd5_index(vaddr) * sizeof(unsigned long);
+ if (!readmem(PADDR, page_dir, &pgd, sizeof pgd)) {
+ ERRMSG("Can't get pgd (page_dir:%lx).\n", page_dir);
+ return NOT_PADDR;
+ }
+ if (info->vaddr_for_vtop == vaddr)
+ MSG(" PGD : %16lx => %16lx\n", page_dir, pgd);
+
+ if (!(pgd & _PAGE_PRESENT)) {
+ ERRMSG("Can't get a valid pgd.\n");
+ return NOT_PADDR;
+ }
+ /*
+ * Get P4D.
+ */
+ p4d_paddr = pgd & ENTRY_MASK;
+ p4d_paddr += p4d_index(vaddr) * sizeof(unsigned long);
+ if (!readmem(PADDR, p4d_paddr, &p4d_pte, sizeof p4d_pte)) {
+ ERRMSG("Can't get p4d_pte (p4d_paddr:%lx).\n", p4d_paddr);
+ return NOT_PADDR;
+ }
+ if (info->vaddr_for_vtop == vaddr)
+ MSG(" P4D : %16lx => %16lx\n", p4d_paddr, p4d_pte);
+
+ if (!(p4d_pte & _PAGE_PRESENT)) {
+ ERRMSG("Can't get a valid p4d_pte.\n");
+ return NOT_PADDR;
+ }
+ pud_paddr = p4d_pte & ENTRY_MASK;
+ }else {
+ page_dir += pgd_index(vaddr) * sizeof(unsigned long);
+ if (!readmem(PADDR, page_dir, &pgd, sizeof pgd)) {
+ ERRMSG("Can't get pgd (page_dir:%lx).\n", page_dir);
+ return NOT_PADDR;
+ }
+ if (info->vaddr_for_vtop == vaddr)
+ MSG(" PGD : %16lx => %16lx\n", page_dir, pgd);
+
+ if (!(pgd & _PAGE_PRESENT)) {
+ ERRMSG("Can't get a valid pgd.\n");
+ return NOT_PADDR;
+ }
+ pud_paddr = pgd & ENTRY_MASK;
+ }
+
+ /*
+ * Get PUD.
+ */
+ pud_paddr += pud_index(vaddr) * sizeof(unsigned long);
+ if (!readmem(PADDR, pud_paddr, &pud_pte, sizeof pud_pte)) {
+ ERRMSG("Can't get pud_pte (pud_paddr:%lx).\n", pud_paddr);
+ return NOT_PADDR;
+ }
+ if (info->vaddr_for_vtop == vaddr)
+ MSG(" PUD : %16lx => %16lx\n", pud_paddr, pud_pte);
+
+ if (!(pud_pte & _PAGE_PRESENT)) {
+ ERRMSG("Can't get a valid pud_pte.\n");
+ return NOT_PADDR;
+ }
+ if (pud_pte & _PAGE_PSE) /* 1GB pages */
+ return (pud_pte & ENTRY_MASK & PUD_MASK) +
+ (vaddr & ~PUD_MASK);
+
+ /*
+ * Get PMD.
+ */
+ pmd_paddr = pud_pte & ENTRY_MASK;
+ pmd_paddr += pmd_index(vaddr) * sizeof(unsigned long);
+ if (!readmem(PADDR, pmd_paddr, &pmd_pte, sizeof pmd_pte)) {
+ ERRMSG("Can't get pmd_pte (pmd_paddr:%lx).\n", pmd_paddr);
+ return NOT_PADDR;
+ }
+ if (info->vaddr_for_vtop == vaddr)
+ MSG(" PMD : %16lx => %16lx\n", pmd_paddr, pmd_pte);
+
+ if (!(pmd_pte & _PAGE_PRESENT)) {
+ ERRMSG("Can't get a valid pmd_pte.\n");
+ return NOT_PADDR;
+ }
+ if (pmd_pte & _PAGE_PSE) /* 2MB pages */
+ return (pmd_pte & ENTRY_MASK & PMD_MASK) +
+ (vaddr & ~PMD_MASK);
+
+ /*
+ * Get PTE.
+ */
+ pte_paddr = pmd_pte & ENTRY_MASK;
+ pte_paddr += pte_index(vaddr) * sizeof(unsigned long);
+ if (!readmem(PADDR, pte_paddr, &pte, sizeof pte)) {
+ ERRMSG("Can't get pte (pte_paddr:%lx).\n", pte_paddr);
+ return NOT_PADDR;
+ }
+ if (info->vaddr_for_vtop == vaddr)
+ MSG(" PTE : %16lx => %16lx\n", pte_paddr, pte);
+
+ if (!(pte & _PAGE_PRESENT)) {
+ ERRMSG("Can't get a valid pte.\n");
+ return NOT_PADDR;
+ }
+ return (pte & ENTRY_MASK) + PAGEOFFSET(vaddr);
+}
+
+unsigned long long
+vtop4_x86_64(unsigned long vaddr)
+{
+ unsigned long pagetable;
+ unsigned long init_level4_pgt;
+
+ if (SYMBOL(init_level4_pgt) != NOT_FOUND_SYMBOL)
+ init_level4_pgt = SYMBOL(init_level4_pgt);
+ else if (SYMBOL(init_top_pgt) != NOT_FOUND_SYMBOL)
+ init_level4_pgt = SYMBOL(init_top_pgt);
+ else {
+ ERRMSG("Can't get the symbol of init_level4_pgt/init_top_pgt.\n");
+ return NOT_PADDR;
+ }
+
+ pagetable = init_level4_pgt - __START_KERNEL_map + info->phys_base;
+
+ return __vtop4_x86_64(vaddr, pagetable);
+}
+
+unsigned long long
+vtop4_x86_64_pagetable(unsigned long vaddr, unsigned long pagetable)
+{
+ return __vtop4_x86_64(vaddr, pagetable);
+}
+
+/*
+ * for Xen extraction
+ */
+unsigned long long
+kvtop_xen_x86_64(unsigned long kvaddr)
+{
+ unsigned long long dirp, entry;
+
+ if (!is_xen_vaddr(kvaddr))
+ return NOT_PADDR;
+
+ if (is_xen_text(kvaddr))
+ return (unsigned long)kvaddr - XEN_VIRT_START + info->xen_phys_start;
+
+ if (is_direct(kvaddr))
+ return (unsigned long)kvaddr - DIRECTMAP_VIRT_START;
+
+ if ((dirp = kvtop_xen_x86_64(SYMBOL(pgd_l4))) == NOT_PADDR)
+ return NOT_PADDR;
+
+ /*
+ * Get PGD.
+ */
+ dirp += pgd_index(kvaddr) * sizeof(unsigned long long);
+ if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
+ return NOT_PADDR;
+
+ if (!(entry & _PAGE_PRESENT))
+ return NOT_PADDR;
+
+ /*
+ * Get PUD.
+ */
+ dirp = entry & ENTRY_MASK;
+ dirp += pud_index(kvaddr) * sizeof(unsigned long long);
+ if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
+ return NOT_PADDR;
+
+ if (!(entry & _PAGE_PRESENT))
+ return NOT_PADDR;
+
+ if (entry & _PAGE_PSE) /* 1GB pages */
+ return (entry & ENTRY_MASK & PUD_MASK) +
+ (kvaddr & ~PUD_MASK);
+
+ /*
+ * Get PMD.
+ */
+ dirp = entry & ENTRY_MASK;
+ dirp += pmd_index(kvaddr) * sizeof(unsigned long long);
+ if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
+ return NOT_PADDR;
+
+ if (!(entry & _PAGE_PRESENT))
+ return NOT_PADDR;
+
+ if (entry & _PAGE_PSE) /* 2MB pages */
+ return (entry & ENTRY_MASK & PMD_MASK) +
+ (kvaddr & ~PMD_MASK);
+
+ /*
+ * Get PTE.
+ */
+ dirp = entry & ENTRY_MASK;
+ dirp += pte_index(kvaddr) * sizeof(unsigned long long);
+ if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
+ return NOT_PADDR;
+
+ if (!(entry & _PAGE_PRESENT)) {
+ return NOT_PADDR;
+ }
+
+ return (entry & ENTRY_MASK) + PAGEOFFSET(kvaddr);
+}
+
+int get_xen_basic_info_x86_64(void)
+{
+ if (!info->xen_phys_start) {
+ if (info->xen_crash_info_v < 2) {
+ ERRMSG("Can't get Xen physical start address.\n"
+ "Please use the --xen_phys_start option.");
+ return FALSE;
+ }
+ info->xen_phys_start = info->xen_crash_info.v2->xen_phys_start;
+ }
+
+ info->xen_virt_start = SYMBOL(domain_list);
+
+ /*
+ * Xen virtual mapping is aligned to 1 GiB boundary.
+ * domain_list lives in bss which sits no more than
+ * 1 GiB below beginning of virtual address space.
+ */
+ info->xen_virt_start &= 0xffffffffc0000000;
+
+ if (info->xen_crash_info.com &&
+ info->xen_crash_info.com->xen_major_version >= 4)
+ info->directmap_virt_end = DIRECTMAP_VIRT_END_V4;
+ else
+ info->directmap_virt_end = DIRECTMAP_VIRT_END_V3;
+
+ if (SYMBOL(pgd_l4) == NOT_FOUND_SYMBOL) {
+ ERRMSG("Can't get pml4.\n");
+ return FALSE;
+ }
+
+ if (SYMBOL(frame_table) != NOT_FOUND_SYMBOL) {
+ unsigned long frame_table_vaddr;
+
+ if (!readmem(VADDR_XEN, SYMBOL(frame_table),
+ &frame_table_vaddr, sizeof(frame_table_vaddr))) {
+ ERRMSG("Can't get the value of frame_table.\n");
+ return FALSE;
+ }
+ info->frame_table_vaddr = frame_table_vaddr;
+ } else {
+ if (info->xen_crash_info.com &&
+ ((info->xen_crash_info.com->xen_major_version == 4 &&
+ info->xen_crash_info.com->xen_minor_version >= 3) ||
+ info->xen_crash_info.com->xen_major_version > 4))
+ info->frame_table_vaddr = FRAMETABLE_VIRT_START_V4_3;
+ else
+ info->frame_table_vaddr = FRAMETABLE_VIRT_START_V3;
+ }
+
+ if (!info->xen_crash_info.com ||
+ info->xen_crash_info.com->xen_major_version < 4) {
+ unsigned long xen_end;
+
+ if (SYMBOL(xenheap_phys_end) == NOT_FOUND_SYMBOL) {
+ ERRMSG("Can't get the symbol of xenheap_phys_end.\n");
+ return FALSE;
+ }
+ if (!readmem(VADDR_XEN, SYMBOL(xenheap_phys_end), &xen_end,
+ sizeof(xen_end))) {
+ ERRMSG("Can't get the value of xenheap_phys_end.\n");
+ return FALSE;
+ }
+ info->xen_heap_start = 0;
+ info->xen_heap_end = paddr_to_pfn(xen_end);
+ }
+
+ return TRUE;
+}
+
+int get_xen_info_x86_64(void)
+{
+ int i;
+
+ if (info->xen_crash_info.com &&
+ (info->xen_crash_info.com->xen_major_version >= 4 ||
+ (info->xen_crash_info.com->xen_major_version == 3 &&
+ info->xen_crash_info.com->xen_minor_version >= 4))) {
+ /*
+ * cf. changeset 0858f961c77a
+ */
+ for (i = 0; i < info->num_domain; i++) {
+ info->domain_list[i].pickled_id =
+ (info->domain_list[i].domain_addr -
+ DIRECTMAP_VIRT_START) >> PAGESHIFT();
+ }
+ } else {
+ /*
+ * pickled_id == domain addr for x86_64
+ */
+ for (i = 0; i < info->num_domain; i++) {
+ info->domain_list[i].pickled_id =
+ info->domain_list[i].domain_addr;
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * Scan the kernel page table for the pfn's of the page structs
+ * Place them in array gvmem_pfns[nr_gvmem_pfns]
+ */
+int
+find_vmemmap_x86_64()
+{
+ int i;
+ int pgd_index;
+ int start_range = 1;
+ int num_pmds=0, num_pmds_valid=0;
+ int break_in_valids, break_after_invalids;
+ int do_break;
+ int last_valid=0, last_invalid=0;
+ int pagestructsize, structsperhpage, hugepagesize;
+ long page_structs_per_pud;
+ long num_puds, groups = 0;
+ long pgdindex, pudindex, pmdindex;
+ long vaddr_base;
+ long rep_pfn_start = 0, rep_pfn_end = 0;
+ unsigned long init_level4_pgt;
+ unsigned long max_paddr, high_pfn;
+ unsigned long pgd_addr, pud_addr, pmd_addr;
+ unsigned long *pgdp, *pudp, *pmdp;
+ unsigned long pud_page[PTRS_PER_PUD];
+ unsigned long pmd_page[PTRS_PER_PMD];
+ unsigned long vmap_offset_start = 0, vmap_offset_end = 0;
+ unsigned long pmd, tpfn;
+ unsigned long pvaddr = 0;
+ unsigned long data_addr = 0, last_data_addr = 0, start_data_addr = 0;
+ /*
+ * data_addr is the paddr of the page holding the page structs.
+ * We keep lists of contiguous pages and the pfn's that their
+ * page structs represent.
+ * start_data_addr and last_data_addr mark start/end of those
+ * contiguous areas.
+ * An area descriptor is vmap start/end pfn and rep start/end
+ * of the pfn's represented by the vmap start/end.
+ */
+ struct vmap_pfns *vmapp, *vmaphead = NULL, *cur, *tail;
+
+ init_level4_pgt = SYMBOL(init_level4_pgt);
+ if (init_level4_pgt == NOT_FOUND_SYMBOL)
+ init_level4_pgt = SYMBOL(init_top_pgt);
+
+ if (init_level4_pgt == NOT_FOUND_SYMBOL) {
+ ERRMSG("init_level4_pgt/init_top_pgt not found\n");
+ return FAILED;
+ }
+
+ pagestructsize = size_table.page;
+ hugepagesize = PTRS_PER_PMD * info->page_size;
+ vaddr_base = info->vmemmap_start;
+ max_paddr = get_max_paddr();
+ /*
+ * the page structures are mapped at VMEMMAP_START (info->vmemmap_start)
+ * for max_paddr >> 12 page structures
+ */
+ high_pfn = max_paddr >> 12;
+ pgd_index = pgd_index(vaddr_base);
+ pgd_addr = vaddr_to_paddr(init_level4_pgt); /* address of pgd */
+ pgd_addr += pgd_index * sizeof(unsigned long);
+ page_structs_per_pud = (PTRS_PER_PUD * PTRS_PER_PMD * info->page_size) /
+ pagestructsize;
+ num_puds = (high_pfn + page_structs_per_pud - 1) / page_structs_per_pud;
+ pvaddr = VMEMMAP_START;
+ structsperhpage = hugepagesize / pagestructsize;
+
+ /* outer loop is for pud entries in the pgd */
+ for (pgdindex = 0, pgdp = (unsigned long *)pgd_addr; pgdindex < num_puds;
+ pgdindex++, pgdp++) {
+
+ /* read the pgd one word at a time, into pud_addr */
+ if (!readmem(PADDR, (unsigned long long)pgdp, (void *)&pud_addr,
+ sizeof(unsigned long))) {
+ ERRMSG("Can't get pgd entry for slot %d.\n", pgd_index);
+ return FAILED;
+ }
+
+ /* mask the pgd entry for the address of the pud page */
+ pud_addr &= PMASK;
+ if (pud_addr == 0)
+ continue;
+ /* read the entire pud page */
+ if (!readmem(PADDR, (unsigned long long)pud_addr, (void *)pud_page,
+ PTRS_PER_PUD * sizeof(unsigned long))) {
+ ERRMSG("Can't get pud entry for pgd slot %ld.\n", pgdindex);
+ return FAILED;
+ }
+ /* step thru each pmd address in the pud page */
+ /* pudp points to an entry in the pud page */
+ for (pudp = (unsigned long *)pud_page, pudindex = 0;
+ pudindex < PTRS_PER_PUD; pudindex++, pudp++) {
+ pmd_addr = *pudp & PMASK;
+ /* read the entire pmd page */
+ if (pmd_addr == 0)
+ continue;
+ if (!readmem(PADDR, pmd_addr, (void *)pmd_page,
+ PTRS_PER_PMD * sizeof(unsigned long))) {
+ ERRMSG("Can't get pud entry for slot %ld.\n", pudindex);
+ return FAILED;
+ }
+ /* pmdp points to an entry in the pmd */
+ for (pmdp = (unsigned long *)pmd_page, pmdindex = 0;
+ pmdindex < PTRS_PER_PMD; pmdindex++, pmdp++) {
+ /* linear page position in this page table: */
+ pmd = *pmdp;
+ num_pmds++;
+ tpfn = (pvaddr - VMEMMAP_START) /
+ pagestructsize;
+ if (tpfn >= high_pfn) {
+ break;
+ }
+ /*
+ * vmap_offset_start:
+ * Starting logical position in the
+ * vmemmap array for the group stays
+ * constant until a hole in the table
+ * or a break in contiguousness.
+ */
+
+ /*
+ * Ending logical position in the
+ * vmemmap array:
+ */
+ vmap_offset_end += hugepagesize;
+ do_break = 0;
+ break_in_valids = 0;
+ break_after_invalids = 0;
+ /*
+ * We want breaks either when:
+ * - we hit a hole (invalid)
+ * - we discontiguous page is a string of valids
+ */
+ if (pmd) {
+ data_addr = (pmd & PMASK);
+ if (start_range) {
+ /* first-time kludge */
+ start_data_addr = data_addr;
+ last_data_addr = start_data_addr
+ - hugepagesize;
+ start_range = 0;
+ }
+ if (last_invalid) {
+ /* end of a hole */
+ start_data_addr = data_addr;
+ last_data_addr = start_data_addr
+ - hugepagesize;
+ /* trigger update of offset */
+ do_break = 1;
+ }
+ last_valid = 1;
+ last_invalid = 0;
+ /*
+ * we have a gap in physical
+ * contiguousness in the table.
+ */
+ /* ?? consecutive holes will have
+ same data_addr */
+ if (data_addr !=
+ last_data_addr + hugepagesize) {
+ do_break = 1;
+ break_in_valids = 1;
+ }
+ DEBUG_MSG("valid: pud %ld pmd %ld pfn %#lx"
+ " pvaddr %#lx pfns %#lx-%lx"
+ " start %#lx end %#lx\n",
+ pudindex, pmdindex,
+ data_addr >> 12,
+ pvaddr, tpfn,
+ tpfn + structsperhpage - 1,
+ vmap_offset_start,
+ vmap_offset_end);
+ num_pmds_valid++;
+ if (!(pmd & _PAGE_PSE)) {
+ printf("vmemmap pmd not huge, abort\n");
+ return FAILED;
+ }
+ } else {
+ if (last_valid) {
+ /* this a hole after some valids */
+ do_break = 1;
+ break_in_valids = 1;
+ break_after_invalids = 0;
+ }
+ last_valid = 0;
+ last_invalid = 1;
+ /*
+ * There are holes in this sparsely
+ * populated table; they are 2MB gaps
+ * represented by null pmd entries.
+ */
+ DEBUG_MSG("invalid: pud %ld pmd %ld %#lx"
+ " pfns %#lx-%lx start %#lx end"
+ " %#lx\n", pudindex, pmdindex,
+ pvaddr, tpfn,
+ tpfn + structsperhpage - 1,
+ vmap_offset_start,
+ vmap_offset_end);
+ }
+ if (do_break) {
+ /* The end of a hole is not summarized.
+ * It must be the start of a hole or
+ * hitting a discontiguous series.
+ */
+ if (break_in_valids || break_after_invalids) {
+ /*
+ * calculate that pfns
+ * represented by the current
+ * offset in the vmemmap.
+ */
+ /* page struct even partly on this page */
+ rep_pfn_start = vmap_offset_start /
+ pagestructsize;
+ /* ending page struct entirely on
+ this page */
+ rep_pfn_end = ((vmap_offset_end -
+ hugepagesize) / pagestructsize);
+ DEBUG_MSG("vmap pfns %#lx-%lx "
+ "represent pfns %#lx-%lx\n\n",
+ start_data_addr >> PAGESHIFT(),
+ last_data_addr >> PAGESHIFT(),
+ rep_pfn_start, rep_pfn_end);
+ groups++;
+ vmapp = (struct vmap_pfns *)malloc(
+ sizeof(struct vmap_pfns));
+ /* pfn of this 2MB page of page structs */
+ vmapp->vmap_pfn_start = start_data_addr
+ >> PTE_SHIFT;
+ vmapp->vmap_pfn_end = last_data_addr
+ >> PTE_SHIFT;
+ /* these (start/end) are literal pfns
+ * on this page, not start and end+1 */
+ vmapp->rep_pfn_start = rep_pfn_start;
+ vmapp->rep_pfn_end = rep_pfn_end;
+
+ if (!vmaphead) {
+ vmaphead = vmapp;
+ vmapp->next = vmapp;
+ vmapp->prev = vmapp;
+ } else {
+ tail = vmaphead->prev;
+ vmaphead->prev = vmapp;
+ tail->next = vmapp;
+ vmapp->next = vmaphead;
+ vmapp->prev = tail;
+ }
+ }
+
+ /* update logical position at every break */
+ vmap_offset_start =
+ vmap_offset_end - hugepagesize;
+ start_data_addr = data_addr;
+ }
+
+ last_data_addr = data_addr;
+ pvaddr += hugepagesize;
+ /*
+ * pvaddr is current virtual address
+ * eg 0xffffea0004200000 if
+ * vmap_offset_start is 4200000
+ */
+ }
+ }
+ tpfn = (pvaddr - VMEMMAP_START) / pagestructsize;
+ if (tpfn >= high_pfn) {
+ break;
+ }
+ }
+ rep_pfn_start = vmap_offset_start / pagestructsize;
+ rep_pfn_end = (vmap_offset_end - hugepagesize) / pagestructsize;
+ DEBUG_MSG("vmap pfns %#lx-%lx represent pfns %#lx-%lx\n\n",
+ start_data_addr >> PAGESHIFT(), last_data_addr >> PAGESHIFT(),
+ rep_pfn_start, rep_pfn_end);
+ groups++;
+ vmapp = (struct vmap_pfns *)malloc(sizeof(struct vmap_pfns));
+ vmapp->vmap_pfn_start = start_data_addr >> PTE_SHIFT;
+ vmapp->vmap_pfn_end = last_data_addr >> PTE_SHIFT;
+ vmapp->rep_pfn_start = rep_pfn_start;
+ vmapp->rep_pfn_end = rep_pfn_end;
+ if (!vmaphead) {
+ vmaphead = vmapp;
+ vmapp->next = vmapp;
+ vmapp->prev = vmapp;
+ } else {
+ tail = vmaphead->prev;
+ vmaphead->prev = vmapp;
+ tail->next = vmapp;
+ vmapp->next = vmaphead;
+ vmapp->prev = tail;
+ }
+ DEBUG_MSG("num_pmds: %d num_pmds_valid %d\n", num_pmds, num_pmds_valid);
+
+ /* transfer the linked list to an array */
+ cur = vmaphead;
+ gvmem_pfns = (struct vmap_pfns *)malloc(sizeof(struct vmap_pfns) * groups);
+ i = 0;
+ do {
+ vmapp = gvmem_pfns + i;
+ vmapp->vmap_pfn_start = cur->vmap_pfn_start;
+ vmapp->vmap_pfn_end = cur->vmap_pfn_end;
+ vmapp->rep_pfn_start = cur->rep_pfn_start;
+ vmapp->rep_pfn_end = cur->rep_pfn_end;
+ cur = cur->next;
+ free(cur->prev);
+ i++;
+ } while (cur != vmaphead);
+ nr_gvmem_pfns = i;
+ return COMPLETED;
+}
+
+#endif /* x86_64 */
+