blob: 493461159462149fdced97e82c80e2b0f4042fec [file] [log] [blame]
Laura Abbottc9465b42014-11-26 00:28:39 +00001/*
2 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
3 * Debug helper to dump the current kernel pagetables of the system
4 * so that we can see what the various memory ranges are set to.
5 *
6 * Derived from x86 and arm implementation:
7 * (C) Copyright 2008 Intel Corporation
8 *
9 * Author: Arjan van de Ven <arjan@linux.intel.com>
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; version 2
14 * of the License.
15 */
16#include <linux/debugfs.h>
Mark Rutland764011c2015-01-22 18:20:36 +000017#include <linux/errno.h>
Laura Abbottc9465b42014-11-26 00:28:39 +000018#include <linux/fs.h>
Mark Brown284be282015-01-22 20:52:10 +000019#include <linux/io.h>
Mark Rutland764011c2015-01-22 18:20:36 +000020#include <linux/init.h>
Laura Abbottc9465b42014-11-26 00:28:39 +000021#include <linux/mm.h>
22#include <linux/sched.h>
23#include <linux/seq_file.h>
24
25#include <asm/fixmap.h>
Mark Rutland764011c2015-01-22 18:20:36 +000026#include <asm/memory.h>
Laura Abbottc9465b42014-11-26 00:28:39 +000027#include <asm/pgtable.h>
Mark Rutland764011c2015-01-22 18:20:36 +000028#include <asm/pgtable-hwdef.h>
Laura Abbottc9465b42014-11-26 00:28:39 +000029
Laura Abbottc9465b42014-11-26 00:28:39 +000030struct addr_marker {
31 unsigned long start_address;
32 const char *name;
33};
34
Ard Biesheuvelc8f8cca2016-04-22 18:48:03 +020035static const struct addr_marker address_markers[] = {
36 { MODULES_VADDR, "Modules start" },
37 { MODULES_END, "Modules end" },
38 { VMALLOC_START, "vmalloc() Area" },
39 { VMALLOC_END, "vmalloc() End" },
40 { FIXADDR_START, "Fixmap start" },
41 { FIXADDR_TOP, "Fixmap end" },
42 { PCI_IO_START, "PCI I/O start" },
43 { PCI_IO_END, "PCI I/O end" },
Ard Biesheuvel3e1907d2016-03-30 16:46:00 +020044#ifdef CONFIG_SPARSEMEM_VMEMMAP
Ard Biesheuvelc8f8cca2016-04-22 18:48:03 +020045 { VMEMMAP_START, "vmemmap start" },
46 { VMEMMAP_START + VMEMMAP_SIZE, "vmemmap end" },
Ard Biesheuvel3e1907d2016-03-30 16:46:00 +020047#endif
Ard Biesheuvelc8f8cca2016-04-22 18:48:03 +020048 { PAGE_OFFSET, "Linear Mapping" },
49 { -1, NULL },
Laura Abbottc9465b42014-11-26 00:28:39 +000050};
51
Jeremy Linton202e41a2015-10-07 12:00:23 -050052/*
53 * The page dumper groups page table entries of the same type into a single
54 * description. It uses pg_state to track the range information while
55 * iterating over the pte entries. When the continuity is broken it then
56 * dumps out a description of the range.
57 */
Laura Abbottc9465b42014-11-26 00:28:39 +000058struct pg_state {
59 struct seq_file *seq;
60 const struct addr_marker *marker;
61 unsigned long start_address;
62 unsigned level;
63 u64 current_prot;
64};
65
66struct prot_bits {
67 u64 mask;
68 u64 val;
69 const char *set;
70 const char *clear;
71};
72
73static const struct prot_bits pte_bits[] = {
74 {
Laura Abbottd7e9d592016-02-05 16:24:48 -080075 .mask = PTE_VALID,
76 .val = PTE_VALID,
77 .set = " ",
78 .clear = "F",
79 }, {
Laura Abbottc9465b42014-11-26 00:28:39 +000080 .mask = PTE_USER,
81 .val = PTE_USER,
82 .set = "USR",
83 .clear = " ",
84 }, {
85 .mask = PTE_RDONLY,
86 .val = PTE_RDONLY,
87 .set = "ro",
88 .clear = "RW",
89 }, {
90 .mask = PTE_PXN,
91 .val = PTE_PXN,
92 .set = "NX",
93 .clear = "x ",
94 }, {
95 .mask = PTE_SHARED,
96 .val = PTE_SHARED,
97 .set = "SHD",
98 .clear = " ",
99 }, {
100 .mask = PTE_AF,
101 .val = PTE_AF,
102 .set = "AF",
103 .clear = " ",
104 }, {
105 .mask = PTE_NG,
106 .val = PTE_NG,
107 .set = "NG",
108 .clear = " ",
109 }, {
Jeremy Linton202e41a2015-10-07 12:00:23 -0500110 .mask = PTE_CONT,
111 .val = PTE_CONT,
112 .set = "CON",
113 .clear = " ",
114 }, {
115 .mask = PTE_TABLE_BIT,
116 .val = PTE_TABLE_BIT,
117 .set = " ",
118 .clear = "BLK",
119 }, {
Laura Abbottc9465b42014-11-26 00:28:39 +0000120 .mask = PTE_UXN,
121 .val = PTE_UXN,
122 .set = "UXN",
123 }, {
124 .mask = PTE_ATTRINDX_MASK,
125 .val = PTE_ATTRINDX(MT_DEVICE_nGnRnE),
126 .set = "DEVICE/nGnRnE",
127 }, {
128 .mask = PTE_ATTRINDX_MASK,
129 .val = PTE_ATTRINDX(MT_DEVICE_nGnRE),
130 .set = "DEVICE/nGnRE",
131 }, {
132 .mask = PTE_ATTRINDX_MASK,
133 .val = PTE_ATTRINDX(MT_DEVICE_GRE),
134 .set = "DEVICE/GRE",
135 }, {
136 .mask = PTE_ATTRINDX_MASK,
137 .val = PTE_ATTRINDX(MT_NORMAL_NC),
138 .set = "MEM/NORMAL-NC",
139 }, {
140 .mask = PTE_ATTRINDX_MASK,
141 .val = PTE_ATTRINDX(MT_NORMAL),
142 .set = "MEM/NORMAL",
143 }
144};
145
146struct pg_level {
147 const struct prot_bits *bits;
148 size_t num;
149 u64 mask;
150};
151
152static struct pg_level pg_level[] = {
153 {
154 }, { /* pgd */
155 .bits = pte_bits,
156 .num = ARRAY_SIZE(pte_bits),
157 }, { /* pud */
158 .bits = pte_bits,
159 .num = ARRAY_SIZE(pte_bits),
160 }, { /* pmd */
161 .bits = pte_bits,
162 .num = ARRAY_SIZE(pte_bits),
163 }, { /* pte */
164 .bits = pte_bits,
165 .num = ARRAY_SIZE(pte_bits),
166 },
167};
168
169static void dump_prot(struct pg_state *st, const struct prot_bits *bits,
170 size_t num)
171{
172 unsigned i;
173
174 for (i = 0; i < num; i++, bits++) {
175 const char *s;
176
177 if ((st->current_prot & bits->mask) == bits->val)
178 s = bits->set;
179 else
180 s = bits->clear;
181
182 if (s)
183 seq_printf(st->seq, " %s", s);
184 }
185}
186
187static void note_page(struct pg_state *st, unsigned long addr, unsigned level,
188 u64 val)
189{
190 static const char units[] = "KMGTPE";
191 u64 prot = val & pg_level[level].mask;
192
Laura Abbottc9465b42014-11-26 00:28:39 +0000193 if (!st->level) {
194 st->level = level;
195 st->current_prot = prot;
196 st->start_address = addr;
197 seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
198 } else if (prot != st->current_prot || level != st->level ||
199 addr >= st->marker[1].start_address) {
200 const char *unit = units;
201 unsigned long delta;
202
203 if (st->current_prot) {
Jeremy Linton202e41a2015-10-07 12:00:23 -0500204 seq_printf(st->seq, "0x%016lx-0x%016lx ",
Laura Abbottc9465b42014-11-26 00:28:39 +0000205 st->start_address, addr);
206
207 delta = (addr - st->start_address) >> 10;
208 while (!(delta & 1023) && unit[1]) {
209 delta >>= 10;
210 unit++;
211 }
212 seq_printf(st->seq, "%9lu%c", delta, *unit);
213 if (pg_level[st->level].bits)
214 dump_prot(st, pg_level[st->level].bits,
215 pg_level[st->level].num);
216 seq_puts(st->seq, "\n");
217 }
218
219 if (addr >= st->marker[1].start_address) {
220 st->marker++;
221 seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
222 }
223
224 st->start_address = addr;
225 st->current_prot = prot;
226 st->level = level;
227 }
228
229 if (addr >= st->marker[1].start_address) {
230 st->marker++;
231 seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
232 }
233
234}
235
236static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start)
237{
238 pte_t *pte = pte_offset_kernel(pmd, 0);
239 unsigned long addr;
240 unsigned i;
241
242 for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
243 addr = start + i * PAGE_SIZE;
244 note_page(st, addr, 4, pte_val(*pte));
245 }
246}
247
248static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
249{
250 pmd_t *pmd = pmd_offset(pud, 0);
251 unsigned long addr;
252 unsigned i;
253
254 for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
255 addr = start + i * PMD_SIZE;
Mark Rutlanda1c76572015-01-27 16:36:30 +0000256 if (pmd_none(*pmd) || pmd_sect(*pmd)) {
Laura Abbottc9465b42014-11-26 00:28:39 +0000257 note_page(st, addr, 3, pmd_val(*pmd));
Mark Rutlanda1c76572015-01-27 16:36:30 +0000258 } else {
259 BUG_ON(pmd_bad(*pmd));
Laura Abbottc9465b42014-11-26 00:28:39 +0000260 walk_pte(st, pmd, addr);
Mark Rutlanda1c76572015-01-27 16:36:30 +0000261 }
Laura Abbottc9465b42014-11-26 00:28:39 +0000262 }
263}
264
265static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
266{
267 pud_t *pud = pud_offset(pgd, 0);
268 unsigned long addr;
269 unsigned i;
270
271 for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
272 addr = start + i * PUD_SIZE;
Mark Rutlanda1c76572015-01-27 16:36:30 +0000273 if (pud_none(*pud) || pud_sect(*pud)) {
Laura Abbottc9465b42014-11-26 00:28:39 +0000274 note_page(st, addr, 2, pud_val(*pud));
Mark Rutlanda1c76572015-01-27 16:36:30 +0000275 } else {
276 BUG_ON(pud_bad(*pud));
Laura Abbottc9465b42014-11-26 00:28:39 +0000277 walk_pmd(st, pud, addr);
Mark Rutlanda1c76572015-01-27 16:36:30 +0000278 }
Laura Abbottc9465b42014-11-26 00:28:39 +0000279 }
280}
281
282static void walk_pgd(struct pg_state *st, struct mm_struct *mm, unsigned long start)
283{
Mark Rutland35545f0c2014-12-05 12:34:54 +0000284 pgd_t *pgd = pgd_offset(mm, 0UL);
Laura Abbottc9465b42014-11-26 00:28:39 +0000285 unsigned i;
286 unsigned long addr;
287
288 for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
289 addr = start + i * PGDIR_SIZE;
Mark Rutlanda1c76572015-01-27 16:36:30 +0000290 if (pgd_none(*pgd)) {
Laura Abbottc9465b42014-11-26 00:28:39 +0000291 note_page(st, addr, 1, pgd_val(*pgd));
Mark Rutlanda1c76572015-01-27 16:36:30 +0000292 } else {
293 BUG_ON(pgd_bad(*pgd));
Laura Abbottc9465b42014-11-26 00:28:39 +0000294 walk_pud(st, pgd, addr);
Mark Rutlanda1c76572015-01-27 16:36:30 +0000295 }
Laura Abbottc9465b42014-11-26 00:28:39 +0000296 }
297}
298
299static int ptdump_show(struct seq_file *m, void *v)
300{
301 struct pg_state st = {
302 .seq = m,
303 .marker = address_markers,
304 };
305
Kefeng Wangcc30e6b2016-01-30 15:55:21 +0800306 walk_pgd(&st, &init_mm, VA_START);
Laura Abbottc9465b42014-11-26 00:28:39 +0000307
308 note_page(&st, 0, 0, 0);
309 return 0;
310}
311
312static int ptdump_open(struct inode *inode, struct file *file)
313{
314 return single_open(file, ptdump_show, NULL);
315}
316
317static const struct file_operations ptdump_fops = {
318 .open = ptdump_open,
319 .read = seq_read,
320 .llseek = seq_lseek,
321 .release = single_release,
322};
323
324static int ptdump_init(void)
325{
326 struct dentry *pe;
327 unsigned i, j;
328
329 for (i = 0; i < ARRAY_SIZE(pg_level); i++)
330 if (pg_level[i].bits)
331 for (j = 0; j < pg_level[i].num; j++)
332 pg_level[i].mask |= pg_level[i].bits[j].mask;
333
Laura Abbottc9465b42014-11-26 00:28:39 +0000334 pe = debugfs_create_file("kernel_page_tables", 0400, NULL, NULL,
335 &ptdump_fops);
336 return pe ? 0 : -ENOMEM;
337}
338device_initcall(ptdump_init);