Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.9 246 lines 6.3 kB view raw
1#include <linux/seq_file.h> 2#include <linux/debugfs.h> 3#include <linux/module.h> 4#include <linux/mm.h> 5#include <asm/sections.h> 6#include <asm/pgtable.h> 7 8static unsigned long max_addr; 9 10struct addr_marker { 11 unsigned long start_address; 12 const char *name; 13}; 14 15enum address_markers_idx { 16 IDENTITY_NR = 0, 17 KERNEL_START_NR, 18 KERNEL_END_NR, 19 VMEMMAP_NR, 20 VMALLOC_NR, 21#ifdef CONFIG_64BIT 22 MODULES_NR, 23#endif 24}; 25 26static struct addr_marker address_markers[] = { 27 [IDENTITY_NR] = {0, "Identity Mapping"}, 28 [KERNEL_START_NR] = {(unsigned long)&_stext, "Kernel Image Start"}, 29 [KERNEL_END_NR] = {(unsigned long)&_end, "Kernel Image End"}, 30 [VMEMMAP_NR] = {0, "vmemmap Area"}, 31 [VMALLOC_NR] = {0, "vmalloc Area"}, 32#ifdef CONFIG_64BIT 33 [MODULES_NR] = {0, "Modules Area"}, 34#endif 35 { -1, NULL } 36}; 37 38struct pg_state { 39 int level; 40 unsigned int current_prot; 41 unsigned long start_address; 42 unsigned long current_address; 43 const struct addr_marker *marker; 44}; 45 46static void print_prot(struct seq_file *m, unsigned int pr, int level) 47{ 48 static const char * const level_name[] = 49 { "ASCE", "PGD", "PUD", "PMD", "PTE" }; 50 51 seq_printf(m, "%s ", level_name[level]); 52 if (pr & _PAGE_INVALID) { 53 seq_printf(m, "I\n"); 54 return; 55 } 56 seq_printf(m, "%s", pr & _PAGE_RO ? "RO " : "RW "); 57 seq_printf(m, "%s", pr & _PAGE_CO ? "CO " : " "); 58 seq_putc(m, '\n'); 59} 60 61static void note_page(struct seq_file *m, struct pg_state *st, 62 unsigned int new_prot, int level) 63{ 64 static const char units[] = "KMGTPE"; 65 int width = sizeof(unsigned long) * 2; 66 const char *unit = units; 67 unsigned int prot, cur; 68 unsigned long delta; 69 70 /* 71 * If we have a "break" in the series, we need to flush the state 72 * that we have now. "break" is either changing perms, levels or 73 * address space marker. 74 */ 75 prot = new_prot; 76 cur = st->current_prot; 77 78 if (!st->level) { 79 /* First entry */ 80 st->current_prot = new_prot; 81 st->level = level; 82 st->marker = address_markers; 83 seq_printf(m, "---[ %s ]---\n", st->marker->name); 84 } else if (prot != cur || level != st->level || 85 st->current_address >= st->marker[1].start_address) { 86 /* Print the actual finished series */ 87 seq_printf(m, "0x%0*lx-0x%0*lx", 88 width, st->start_address, 89 width, st->current_address); 90 delta = (st->current_address - st->start_address) >> 10; 91 while (!(delta & 0x3ff) && unit[1]) { 92 delta >>= 10; 93 unit++; 94 } 95 seq_printf(m, "%9lu%c ", delta, *unit); 96 print_prot(m, st->current_prot, st->level); 97 if (st->current_address >= st->marker[1].start_address) { 98 st->marker++; 99 seq_printf(m, "---[ %s ]---\n", st->marker->name); 100 } 101 st->start_address = st->current_address; 102 st->current_prot = new_prot; 103 st->level = level; 104 } 105} 106 107/* 108 * The actual page table walker functions. In order to keep the implementation 109 * of print_prot() short, we only check and pass _PAGE_INVALID and _PAGE_RO 110 * flags to note_page() if a region, segment or page table entry is invalid or 111 * read-only. 112 * After all it's just a hint that the current level being walked contains an 113 * invalid or read-only entry. 114 */ 115static void walk_pte_level(struct seq_file *m, struct pg_state *st, 116 pmd_t *pmd, unsigned long addr) 117{ 118 unsigned int prot; 119 pte_t *pte; 120 int i; 121 122 for (i = 0; i < PTRS_PER_PTE && addr < max_addr; i++) { 123 st->current_address = addr; 124 pte = pte_offset_kernel(pmd, addr); 125 prot = pte_val(*pte) & (_PAGE_RO | _PAGE_INVALID); 126 note_page(m, st, prot, 4); 127 addr += PAGE_SIZE; 128 } 129} 130 131#ifdef CONFIG_64BIT 132#define _PMD_PROT_MASK (_SEGMENT_ENTRY_RO | _SEGMENT_ENTRY_CO) 133#else 134#define _PMD_PROT_MASK 0 135#endif 136 137static void walk_pmd_level(struct seq_file *m, struct pg_state *st, 138 pud_t *pud, unsigned long addr) 139{ 140 unsigned int prot; 141 pmd_t *pmd; 142 int i; 143 144 for (i = 0; i < PTRS_PER_PMD && addr < max_addr; i++) { 145 st->current_address = addr; 146 pmd = pmd_offset(pud, addr); 147 if (!pmd_none(*pmd)) { 148 if (pmd_large(*pmd)) { 149 prot = pmd_val(*pmd) & _PMD_PROT_MASK; 150 note_page(m, st, prot, 3); 151 } else 152 walk_pte_level(m, st, pmd, addr); 153 } else 154 note_page(m, st, _PAGE_INVALID, 3); 155 addr += PMD_SIZE; 156 } 157} 158 159#ifdef CONFIG_64BIT 160#define _PUD_PROT_MASK (_REGION3_ENTRY_RO | _REGION3_ENTRY_CO) 161#else 162#define _PUD_PROT_MASK 0 163#endif 164 165static void walk_pud_level(struct seq_file *m, struct pg_state *st, 166 pgd_t *pgd, unsigned long addr) 167{ 168 unsigned int prot; 169 pud_t *pud; 170 int i; 171 172 for (i = 0; i < PTRS_PER_PUD && addr < max_addr; i++) { 173 st->current_address = addr; 174 pud = pud_offset(pgd, addr); 175 if (!pud_none(*pud)) 176 if (pud_large(*pud)) { 177 prot = pud_val(*pud) & _PUD_PROT_MASK; 178 note_page(m, st, prot, 2); 179 } else 180 walk_pmd_level(m, st, pud, addr); 181 else 182 note_page(m, st, _PAGE_INVALID, 2); 183 addr += PUD_SIZE; 184 } 185} 186 187static void walk_pgd_level(struct seq_file *m) 188{ 189 unsigned long addr = 0; 190 struct pg_state st; 191 pgd_t *pgd; 192 int i; 193 194 memset(&st, 0, sizeof(st)); 195 for (i = 0; i < PTRS_PER_PGD && addr < max_addr; i++) { 196 st.current_address = addr; 197 pgd = pgd_offset_k(addr); 198 if (!pgd_none(*pgd)) 199 walk_pud_level(m, &st, pgd, addr); 200 else 201 note_page(m, &st, _PAGE_INVALID, 1); 202 addr += PGDIR_SIZE; 203 } 204 /* Flush out the last page */ 205 st.current_address = max_addr; 206 note_page(m, &st, 0, 0); 207} 208 209static int ptdump_show(struct seq_file *m, void *v) 210{ 211 walk_pgd_level(m); 212 return 0; 213} 214 215static int ptdump_open(struct inode *inode, struct file *filp) 216{ 217 return single_open(filp, ptdump_show, NULL); 218} 219 220static const struct file_operations ptdump_fops = { 221 .open = ptdump_open, 222 .read = seq_read, 223 .llseek = seq_lseek, 224 .release = single_release, 225}; 226 227static int pt_dump_init(void) 228{ 229 /* 230 * Figure out the maximum virtual address being accessible with the 231 * kernel ASCE. We need this to keep the page table walker functions 232 * from accessing non-existent entries. 233 */ 234#ifdef CONFIG_32BIT 235 max_addr = 1UL << 31; 236#else 237 max_addr = (S390_lowcore.kernel_asce & _REGION_ENTRY_TYPE_MASK) >> 2; 238 max_addr = 1UL << (max_addr * 11 + 31); 239 address_markers[MODULES_NR].start_address = MODULES_VADDR; 240#endif 241 address_markers[VMEMMAP_NR].start_address = (unsigned long) vmemmap; 242 address_markers[VMALLOC_NR].start_address = VMALLOC_START; 243 debugfs_create_file("kernel_page_tables", 0400, NULL, NULL, &ptdump_fops); 244 return 0; 245} 246device_initcall(pt_dump_init);