1*b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2e76e82d7SHeiko Carstens #include <linux/seq_file.h> 3e76e82d7SHeiko Carstens #include <linux/debugfs.h> 4549f2bf5SHeiko Carstens #include <linux/sched.h> 5e76e82d7SHeiko Carstens #include <linux/mm.h> 6e76e82d7SHeiko Carstens #include <asm/sections.h> 7e76e82d7SHeiko Carstens #include <asm/pgtable.h> 8e76e82d7SHeiko Carstens 9e76e82d7SHeiko Carstens static unsigned long max_addr; 10e76e82d7SHeiko Carstens 11e76e82d7SHeiko Carstens struct addr_marker { 12e76e82d7SHeiko Carstens unsigned long start_address; 13e76e82d7SHeiko Carstens const char *name; 14e76e82d7SHeiko Carstens }; 15e76e82d7SHeiko Carstens 16e76e82d7SHeiko Carstens enum address_markers_idx { 17e76e82d7SHeiko Carstens IDENTITY_NR = 0, 18e76e82d7SHeiko Carstens KERNEL_START_NR, 19e76e82d7SHeiko Carstens KERNEL_END_NR, 20e76e82d7SHeiko Carstens VMEMMAP_NR, 21e76e82d7SHeiko Carstens VMALLOC_NR, 22c972cc60SHeiko Carstens MODULES_NR, 23e76e82d7SHeiko Carstens }; 24e76e82d7SHeiko Carstens 25e76e82d7SHeiko Carstens static struct addr_marker address_markers[] = { 26e76e82d7SHeiko Carstens [IDENTITY_NR] = {0, "Identity Mapping"}, 27e76e82d7SHeiko Carstens [KERNEL_START_NR] = {(unsigned long)&_stext, "Kernel Image Start"}, 28e76e82d7SHeiko Carstens [KERNEL_END_NR] = {(unsigned long)&_end, "Kernel Image End"}, 29e76e82d7SHeiko Carstens [VMEMMAP_NR] = {0, "vmemmap Area"}, 30e76e82d7SHeiko Carstens [VMALLOC_NR] = {0, "vmalloc Area"}, 31c972cc60SHeiko Carstens [MODULES_NR] = {0, "Modules Area"}, 32e76e82d7SHeiko Carstens { -1, NULL } 33e76e82d7SHeiko Carstens }; 34e76e82d7SHeiko Carstens 35e76e82d7SHeiko Carstens struct pg_state { 36e76e82d7SHeiko Carstens int level; 37e76e82d7SHeiko Carstens unsigned int current_prot; 38e76e82d7SHeiko Carstens unsigned long start_address; 39e76e82d7SHeiko Carstens unsigned long current_address; 40e76e82d7SHeiko Carstens const struct addr_marker *marker; 41e76e82d7SHeiko Carstens }; 42e76e82d7SHeiko Carstens 43e76e82d7SHeiko Carstens static void print_prot(struct seq_file *m, unsigned int pr, int level) 44e76e82d7SHeiko Carstens { 45e76e82d7SHeiko Carstens static const char * const level_name[] = 46e76e82d7SHeiko Carstens { "ASCE", "PGD", "PUD", "PMD", "PTE" }; 47e76e82d7SHeiko Carstens 48e76e82d7SHeiko Carstens seq_printf(m, "%s ", level_name[level]); 491819ed1fSHeiko Carstens if (pr & _PAGE_INVALID) { 50e76e82d7SHeiko Carstens seq_printf(m, "I\n"); 511819ed1fSHeiko Carstens return; 521819ed1fSHeiko Carstens } 5357d7f939SMartin Schwidefsky seq_puts(m, (pr & _PAGE_PROTECT) ? "RO " : "RW "); 5457d7f939SMartin Schwidefsky seq_puts(m, (pr & _PAGE_NOEXEC) ? "NX\n" : "X\n"); 55e76e82d7SHeiko Carstens } 56e76e82d7SHeiko Carstens 57e76e82d7SHeiko Carstens static void note_page(struct seq_file *m, struct pg_state *st, 58e76e82d7SHeiko Carstens unsigned int new_prot, int level) 59e76e82d7SHeiko Carstens { 60e76e82d7SHeiko Carstens static const char units[] = "KMGTPE"; 61e76e82d7SHeiko Carstens int width = sizeof(unsigned long) * 2; 62e76e82d7SHeiko Carstens const char *unit = units; 63e76e82d7SHeiko Carstens unsigned int prot, cur; 64e76e82d7SHeiko Carstens unsigned long delta; 65e76e82d7SHeiko Carstens 66e76e82d7SHeiko Carstens /* 67e76e82d7SHeiko Carstens * If we have a "break" in the series, we need to flush the state 68e76e82d7SHeiko Carstens * that we have now. "break" is either changing perms, levels or 69e76e82d7SHeiko Carstens * address space marker. 70e76e82d7SHeiko Carstens */ 71e76e82d7SHeiko Carstens prot = new_prot; 72e76e82d7SHeiko Carstens cur = st->current_prot; 73e76e82d7SHeiko Carstens 74e76e82d7SHeiko Carstens if (!st->level) { 75e76e82d7SHeiko Carstens /* First entry */ 76e76e82d7SHeiko Carstens st->current_prot = new_prot; 77e76e82d7SHeiko Carstens st->level = level; 78e76e82d7SHeiko Carstens st->marker = address_markers; 79e76e82d7SHeiko Carstens seq_printf(m, "---[ %s ]---\n", st->marker->name); 80e76e82d7SHeiko Carstens } else if (prot != cur || level != st->level || 81e76e82d7SHeiko Carstens st->current_address >= st->marker[1].start_address) { 82e76e82d7SHeiko Carstens /* Print the actual finished series */ 83e76e82d7SHeiko Carstens seq_printf(m, "0x%0*lx-0x%0*lx", 84e76e82d7SHeiko Carstens width, st->start_address, 85e76e82d7SHeiko Carstens width, st->current_address); 86e76e82d7SHeiko Carstens delta = (st->current_address - st->start_address) >> 10; 87e76e82d7SHeiko Carstens while (!(delta & 0x3ff) && unit[1]) { 88e76e82d7SHeiko Carstens delta >>= 10; 89e76e82d7SHeiko Carstens unit++; 90e76e82d7SHeiko Carstens } 91e76e82d7SHeiko Carstens seq_printf(m, "%9lu%c ", delta, *unit); 92e76e82d7SHeiko Carstens print_prot(m, st->current_prot, st->level); 93e76e82d7SHeiko Carstens if (st->current_address >= st->marker[1].start_address) { 94e76e82d7SHeiko Carstens st->marker++; 95e76e82d7SHeiko Carstens seq_printf(m, "---[ %s ]---\n", st->marker->name); 96e76e82d7SHeiko Carstens } 97e76e82d7SHeiko Carstens st->start_address = st->current_address; 98e76e82d7SHeiko Carstens st->current_prot = new_prot; 99e76e82d7SHeiko Carstens st->level = level; 100e76e82d7SHeiko Carstens } 101e76e82d7SHeiko Carstens } 102e76e82d7SHeiko Carstens 103e76e82d7SHeiko Carstens /* 104e5098611SMartin Schwidefsky * The actual page table walker functions. In order to keep the 105e5098611SMartin Schwidefsky * implementation of print_prot() short, we only check and pass 106e5098611SMartin Schwidefsky * _PAGE_INVALID and _PAGE_PROTECT flags to note_page() if a region, 107e5098611SMartin Schwidefsky * segment or page table entry is invalid or read-only. 108e5098611SMartin Schwidefsky * After all it's just a hint that the current level being walked 109e5098611SMartin Schwidefsky * contains an invalid or read-only entry. 110e76e82d7SHeiko Carstens */ 111e76e82d7SHeiko Carstens static void walk_pte_level(struct seq_file *m, struct pg_state *st, 112e76e82d7SHeiko Carstens pmd_t *pmd, unsigned long addr) 113e76e82d7SHeiko Carstens { 114e76e82d7SHeiko Carstens unsigned int prot; 115e76e82d7SHeiko Carstens pte_t *pte; 116e76e82d7SHeiko Carstens int i; 117e76e82d7SHeiko Carstens 118e76e82d7SHeiko Carstens for (i = 0; i < PTRS_PER_PTE && addr < max_addr; i++) { 119e76e82d7SHeiko Carstens st->current_address = addr; 120e76e82d7SHeiko Carstens pte = pte_offset_kernel(pmd, addr); 12157d7f939SMartin Schwidefsky prot = pte_val(*pte) & 12257d7f939SMartin Schwidefsky (_PAGE_PROTECT | _PAGE_INVALID | _PAGE_NOEXEC); 123e76e82d7SHeiko Carstens note_page(m, st, prot, 4); 124e76e82d7SHeiko Carstens addr += PAGE_SIZE; 125e76e82d7SHeiko Carstens } 126e76e82d7SHeiko Carstens } 127e76e82d7SHeiko Carstens 128e76e82d7SHeiko Carstens static void walk_pmd_level(struct seq_file *m, struct pg_state *st, 129e76e82d7SHeiko Carstens pud_t *pud, unsigned long addr) 130e76e82d7SHeiko Carstens { 131e76e82d7SHeiko Carstens unsigned int prot; 132e76e82d7SHeiko Carstens pmd_t *pmd; 133e76e82d7SHeiko Carstens int i; 134e76e82d7SHeiko Carstens 135e76e82d7SHeiko Carstens for (i = 0; i < PTRS_PER_PMD && addr < max_addr; i++) { 136e76e82d7SHeiko Carstens st->current_address = addr; 137e76e82d7SHeiko Carstens pmd = pmd_offset(pud, addr); 138e76e82d7SHeiko Carstens if (!pmd_none(*pmd)) { 139e76e82d7SHeiko Carstens if (pmd_large(*pmd)) { 14057d7f939SMartin Schwidefsky prot = pmd_val(*pmd) & 14157d7f939SMartin Schwidefsky (_SEGMENT_ENTRY_PROTECT | 14257d7f939SMartin Schwidefsky _SEGMENT_ENTRY_NOEXEC); 143e76e82d7SHeiko Carstens note_page(m, st, prot, 3); 144e76e82d7SHeiko Carstens } else 145e76e82d7SHeiko Carstens walk_pte_level(m, st, pmd, addr); 146e76e82d7SHeiko Carstens } else 147e76e82d7SHeiko Carstens note_page(m, st, _PAGE_INVALID, 3); 148e76e82d7SHeiko Carstens addr += PMD_SIZE; 149e76e82d7SHeiko Carstens } 150e76e82d7SHeiko Carstens } 151e76e82d7SHeiko Carstens 152e76e82d7SHeiko Carstens static void walk_pud_level(struct seq_file *m, struct pg_state *st, 1531aea9b3fSMartin Schwidefsky p4d_t *p4d, unsigned long addr) 154e76e82d7SHeiko Carstens { 15518da2369SHeiko Carstens unsigned int prot; 156e76e82d7SHeiko Carstens pud_t *pud; 157e76e82d7SHeiko Carstens int i; 158e76e82d7SHeiko Carstens 159e76e82d7SHeiko Carstens for (i = 0; i < PTRS_PER_PUD && addr < max_addr; i++) { 160e76e82d7SHeiko Carstens st->current_address = addr; 1611aea9b3fSMartin Schwidefsky pud = pud_offset(p4d, addr); 162e76e82d7SHeiko Carstens if (!pud_none(*pud)) 16318da2369SHeiko Carstens if (pud_large(*pud)) { 16457d7f939SMartin Schwidefsky prot = pud_val(*pud) & 16557d7f939SMartin Schwidefsky (_REGION_ENTRY_PROTECT | 16657d7f939SMartin Schwidefsky _REGION_ENTRY_NOEXEC); 16718da2369SHeiko Carstens note_page(m, st, prot, 2); 16818da2369SHeiko Carstens } else 169e76e82d7SHeiko Carstens walk_pmd_level(m, st, pud, addr); 170e76e82d7SHeiko Carstens else 171e76e82d7SHeiko Carstens note_page(m, st, _PAGE_INVALID, 2); 172e76e82d7SHeiko Carstens addr += PUD_SIZE; 173e76e82d7SHeiko Carstens } 174e76e82d7SHeiko Carstens } 175e76e82d7SHeiko Carstens 1761aea9b3fSMartin Schwidefsky static void walk_p4d_level(struct seq_file *m, struct pg_state *st, 1771aea9b3fSMartin Schwidefsky pgd_t *pgd, unsigned long addr) 1781aea9b3fSMartin Schwidefsky { 1791aea9b3fSMartin Schwidefsky p4d_t *p4d; 1801aea9b3fSMartin Schwidefsky int i; 1811aea9b3fSMartin Schwidefsky 1821aea9b3fSMartin Schwidefsky for (i = 0; i < PTRS_PER_P4D && addr < max_addr; i++) { 1831aea9b3fSMartin Schwidefsky st->current_address = addr; 1841aea9b3fSMartin Schwidefsky p4d = p4d_offset(pgd, addr); 1851aea9b3fSMartin Schwidefsky if (!p4d_none(*p4d)) 1861aea9b3fSMartin Schwidefsky walk_pud_level(m, st, p4d, addr); 1871aea9b3fSMartin Schwidefsky else 1881aea9b3fSMartin Schwidefsky note_page(m, st, _PAGE_INVALID, 2); 1891aea9b3fSMartin Schwidefsky addr += P4D_SIZE; 1901aea9b3fSMartin Schwidefsky } 1911aea9b3fSMartin Schwidefsky } 1921aea9b3fSMartin Schwidefsky 193e76e82d7SHeiko Carstens static void walk_pgd_level(struct seq_file *m) 194e76e82d7SHeiko Carstens { 195e76e82d7SHeiko Carstens unsigned long addr = 0; 196e76e82d7SHeiko Carstens struct pg_state st; 197e76e82d7SHeiko Carstens pgd_t *pgd; 198e76e82d7SHeiko Carstens int i; 199e76e82d7SHeiko Carstens 200e76e82d7SHeiko Carstens memset(&st, 0, sizeof(st)); 201e76e82d7SHeiko Carstens for (i = 0; i < PTRS_PER_PGD && addr < max_addr; i++) { 202e76e82d7SHeiko Carstens st.current_address = addr; 203e76e82d7SHeiko Carstens pgd = pgd_offset_k(addr); 204e76e82d7SHeiko Carstens if (!pgd_none(*pgd)) 2051aea9b3fSMartin Schwidefsky walk_p4d_level(m, &st, pgd, addr); 206e76e82d7SHeiko Carstens else 207e76e82d7SHeiko Carstens note_page(m, &st, _PAGE_INVALID, 1); 208e76e82d7SHeiko Carstens addr += PGDIR_SIZE; 209549f2bf5SHeiko Carstens cond_resched(); 210e76e82d7SHeiko Carstens } 211e76e82d7SHeiko Carstens /* Flush out the last page */ 212e76e82d7SHeiko Carstens st.current_address = max_addr; 213e76e82d7SHeiko Carstens note_page(m, &st, 0, 0); 214e76e82d7SHeiko Carstens } 215e76e82d7SHeiko Carstens 216e76e82d7SHeiko Carstens static int ptdump_show(struct seq_file *m, void *v) 217e76e82d7SHeiko Carstens { 218e76e82d7SHeiko Carstens walk_pgd_level(m); 219e76e82d7SHeiko Carstens return 0; 220e76e82d7SHeiko Carstens } 221e76e82d7SHeiko Carstens 222e76e82d7SHeiko Carstens static int ptdump_open(struct inode *inode, struct file *filp) 223e76e82d7SHeiko Carstens { 224e76e82d7SHeiko Carstens return single_open(filp, ptdump_show, NULL); 225e76e82d7SHeiko Carstens } 226e76e82d7SHeiko Carstens 227e76e82d7SHeiko Carstens static const struct file_operations ptdump_fops = { 228e76e82d7SHeiko Carstens .open = ptdump_open, 229e76e82d7SHeiko Carstens .read = seq_read, 230e76e82d7SHeiko Carstens .llseek = seq_lseek, 231e76e82d7SHeiko Carstens .release = single_release, 232e76e82d7SHeiko Carstens }; 233e76e82d7SHeiko Carstens 234e76e82d7SHeiko Carstens static int pt_dump_init(void) 235e76e82d7SHeiko Carstens { 236e76e82d7SHeiko Carstens /* 237e76e82d7SHeiko Carstens * Figure out the maximum virtual address being accessible with the 238e76e82d7SHeiko Carstens * kernel ASCE. We need this to keep the page table walker functions 239e76e82d7SHeiko Carstens * from accessing non-existent entries. 240e76e82d7SHeiko Carstens */ 241e76e82d7SHeiko Carstens max_addr = (S390_lowcore.kernel_asce & _REGION_ENTRY_TYPE_MASK) >> 2; 242e76e82d7SHeiko Carstens max_addr = 1UL << (max_addr * 11 + 31); 243c972cc60SHeiko Carstens address_markers[MODULES_NR].start_address = MODULES_VADDR; 244e76e82d7SHeiko Carstens address_markers[VMEMMAP_NR].start_address = (unsigned long) vmemmap; 245e76e82d7SHeiko Carstens address_markers[VMALLOC_NR].start_address = VMALLOC_START; 246e76e82d7SHeiko Carstens debugfs_create_file("kernel_page_tables", 0400, NULL, NULL, &ptdump_fops); 247e76e82d7SHeiko Carstens return 0; 248e76e82d7SHeiko Carstens } 249e76e82d7SHeiko Carstens device_initcall(pt_dump_init); 250