* [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables
@ 2016-03-21 22:04 Rashmica Gupta
2016-03-21 22:04 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Rashmica Gupta @ 2016-03-21 22:04 UTC (permalink / raw)
To: linuxppc-dev, mpe
Useful to be able to dump the kernels page tables to check permissions
and memory types - derived from arm64's implementation.
Add a debugfs file to check the page tables. To use this the PPC_PTDUMP
config option must be selected.
Signed-off-by: Rashmica Gupta <rashmicy@gmail.com>
---
arch/powerpc/Kconfig.debug | 12 ++
arch/powerpc/mm/Makefile | 1 +
arch/powerpc/mm/dump_linuxpagetables.c | 377 +++++++++++++++++++++++++++++++++
3 files changed, 390 insertions(+)
create mode 100644 arch/powerpc/mm/dump_linuxpagetables.c
diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug
index 638f9ce740f5..26a60effea1a 100644
--- a/arch/powerpc/Kconfig.debug
+++ b/arch/powerpc/Kconfig.debug
@@ -344,4 +344,16 @@ config FAIL_IOMMU
If you are unsure, say N.
+config PPC_PTDUMP
+ bool "Export kernel pagetable layout to userspace via debugfs"
+ depends on DEBUG_KERNEL
+ select DEBUG_FS
+ help
+ This option exports the state of the kernel pagetables to a
+ debugfs file. This is only useful for kernel developers who are
+ working in architecture specific areas of the kernel - probably
+ not a good idea to enable this feature in a production kernel.
+
+ If you are unsure, say N.
+
endmenu
diff --git a/arch/powerpc/mm/Makefile b/arch/powerpc/mm/Makefile
index adfee3f1aeb9..6935c6204fbc 100644
--- a/arch/powerpc/mm/Makefile
+++ b/arch/powerpc/mm/Makefile
@@ -41,3 +41,4 @@ obj-$(CONFIG_NOT_COHERENT_CACHE) += dma-noncoherent.o
obj-$(CONFIG_HIGHMEM) += highmem.o
obj-$(CONFIG_PPC_COPRO_BASE) += copro_fault.o
obj-$(CONFIG_SPAPR_TCE_IOMMU) += mmu_context_iommu.o
+obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o
diff --git a/arch/powerpc/mm/dump_linuxpagetables.c b/arch/powerpc/mm/dump_linuxpagetables.c
new file mode 100644
index 000000000000..f97fbfdac4b9
--- /dev/null
+++ b/arch/powerpc/mm/dump_linuxpagetables.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2016, Rashmica Gupta, IBM Corp.
+ *
+ * This traverses the kernel pagetables and dumps the
+ * information about the used sections of memory to
+ * /sys/kernel/debug/kernel_pagetables.
+ *
+ * Derived from the arm64 implementation:
+ * Copyright (c) 2014, The Linux Foundation, Laura Abbott.
+ * (C) Copyright 2008 Intel Corporation, Arjan van de Ven.
+ *
+ * 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; version 2
+ * of the License.
+ */
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <asm/fixmap.h>
+#include <asm/pgtable.h>
+#include <linux/const.h>
+#include <asm/page.h>
+#include <asm/pgalloc.h>
+
+struct addr_marker {
+ unsigned long start_address;
+ const char *name;
+};
+
+static struct addr_marker address_markers[] = {
+ { VMALLOC_START, "vmalloc() Area" },
+ { VMALLOC_END, "vmalloc() End" },
+ { ISA_IO_BASE, "isa I/O start" },
+ { ISA_IO_END, "isa I/O end" },
+ { PHB_IO_BASE, "phb I/O start" },
+ { PHB_IO_END, "phb I/O end" },
+ { IOREMAP_BASE, "I/O remap start" },
+ { IOREMAP_END, "I/O remap end" },
+ { -1, NULL },
+};
+
+/*
+ * To visualise what is happening,
+ *
+ * - PTRS_PER_P** = how many entries there are in the corresponding P**
+ * - P**_SHIFT = how many bits of the address we use to index into the
+ * corresponding P**
+ * - P**_SIZE is how much memory we can access through the table - not the
+ * size of the table itself.
+ * P**={PGD, PUD, PMD, PTE}
+ *
+ *
+ * Each entry of the PGD points to a PUD. Each entry of a PUD points to a
+ * PMD. Each entry of a PMD points to a PTE. And every PTE entry points to
+ * a page.
+ *
+ * In the case where there are only 3 levels, the PUD is folded into the
+ * PGD: every PUD has only one entry which points to the PMD.
+ *
+ * The page dumper groups page table entries of the same type into a single
+ * description. It uses pg_state to track the range information while
+ * iterating over the PTE entries. When the continuity is broken it then
+ * dumps out a description of the range - ie PTEs that are virtually contiguous
+ * with the same PTE flags are chunked together. This is to make it clear how
+ * different areas of the kernel virtual memory are used.
+ *
+ */
+struct pg_state {
+ struct seq_file *seq;
+ const struct addr_marker *marker;
+ unsigned long start_address;
+ unsigned level;
+ u64 current_flags;
+};
+
+struct flag_info {
+ u64 mask;
+ u64 val;
+ const char *set;
+ const char *clear;
+};
+
+static const struct flag_info flag_array[] = {
+ {
+ .mask = _PAGE_USER,
+ .val = _PAGE_USER,
+ .set = "user",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_RW,
+ .val = _PAGE_RW,
+ .set = "rw",
+ .clear = "ro",
+ }, {
+ .mask = _PAGE_EXEC,
+ .val = _PAGE_EXEC,
+ .set = " X ",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_PTE,
+ .val = _PAGE_PTE,
+ .set = "pte",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_PRESENT,
+ .val = _PAGE_PRESENT,
+ .set = "present",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_HASHPTE,
+ .val = _PAGE_HASHPTE,
+ .set = "hpte",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_GUARDED,
+ .val = _PAGE_GUARDED,
+ .set = "guarded",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_DIRTY,
+ .val = _PAGE_DIRTY,
+ .set = "dirty",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_ACCESSED,
+ .val = _PAGE_ACCESSED,
+ .set = "accessed",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_WRITETHRU,
+ .val = _PAGE_WRITETHRU,
+ .set = "write through",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_NO_CACHE,
+ .val = _PAGE_NO_CACHE,
+ .set = "no cache",
+ .clear = " ",
+ }, {
+ .mask = _PAGE_BUSY,
+ .val = _PAGE_BUSY,
+ .set = "busy",
+ }, {
+ .mask = _PAGE_COMBO,
+ .val = _PAGE_COMBO,
+ .set = "combo",
+ }, {
+ .mask = _PAGE_F_GIX,
+ .val = _PAGE_F_GIX,
+ .set = "f_gix",
+ }, {
+ .mask = _PAGE_F_SECOND,
+ .val = _PAGE_F_SECOND,
+ .set = "f_second",
+ }, {
+ .mask = _PAGE_SPECIAL,
+ .val = _PAGE_SPECIAL,
+ .set = "special",
+ }, {
+ .mask = _PAGE_4K_PFN,
+ .val = _PAGE_4K_PFN,
+ .set = "4K_pfn"
+ }
+};
+
+struct pgtable_level {
+ const struct flag_info *flag;
+ size_t num;
+ u64 mask;
+};
+
+static struct pgtable_level pg_level[] = {
+ {
+ }, { /* pgd */
+ .flag = flag_array,
+ .num = ARRAY_SIZE(flag_array),
+ }, { /* pud */
+ .flag = flag_array,
+ .num = ARRAY_SIZE(flag_array),
+ }, { /* pmd */
+ .flag = flag_array,
+ .num = ARRAY_SIZE(flag_array),
+ }, { /* pte */
+ .flag = flag_array,
+ .num = ARRAY_SIZE(flag_array),
+ },
+};
+
+static void dump_flag(struct pg_state *st, const struct flag_info *flag,
+ size_t num)
+{
+ unsigned i;
+
+ for (i = 0; i < num; i++, flag++) {
+ const char *s;
+
+ if ((st->current_flags & flag->mask) == flag->val)
+ s = flag->set;
+ else
+ s = flag->clear;
+
+ if (s)
+ seq_printf(st->seq, " %s", s);
+ }
+}
+
+static void dump_addr(struct pg_state *st, unsigned long addr)
+{
+ static const char units[] = "KMGTPE";
+ const char *unit = units;
+ unsigned long delta;
+
+ seq_printf(st->seq, "0x%016lx-0x%016lx ", st->start_address, addr-1);
+ delta = (addr - st->start_address) >> 10;
+ /* Work out what appropriate unit to use */
+ while (!(delta & 1023) && unit[1]) {
+ delta >>= 10;
+ unit++;
+ }
+ seq_printf(st->seq, "%9lu%c", delta, *unit);
+
+}
+
+static void note_page(struct pg_state *st, unsigned long addr, unsigned level,
+ u64 val)
+{
+ u64 flag = val & pg_level[level].mask;
+
+ /* At first no level is set */
+ if (!st->level) {
+ st->level = level;
+ st->current_flags = flag;
+ st->start_address = addr;
+ seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
+ /*
+ * Dump the section of virtual memory when:
+ * - the PTE flags from one entry to the next differs.
+ * - we change levels in the tree.
+ * - the address is in a different section of memory and is thus
+ * used for a different purpose, regardless of the flags.
+ */
+ } else if (flag != st->current_flags || level != st->level ||
+ addr >= st->marker[1].start_address) {
+
+ /* Check the PTE flags */
+ if (st->current_flags) {
+ dump_addr(st, addr);
+
+ /* Dump all the flags */
+ if (pg_level[st->level].flag)
+ dump_flag(st, pg_level[st->level].flag,
+ pg_level[st->level].num);
+ seq_puts(st->seq, "\n");
+ }
+
+ /* Address indicates we have passed the end of the
+ * current section of virtual memory
+ */
+ while (addr >= st->marker[1].start_address) {
+ st->marker++;
+ seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
+ }
+ st->start_address = addr;
+ st->current_flags = flag;
+ st->level = level;
+ }
+}
+
+static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start)
+{
+ pte_t *pte = pte_offset_kernel(pmd, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
+ addr = start + i * PAGE_SIZE;
+ note_page(st, addr, 4, pte_val(*pte));
+ }
+}
+
+static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
+{
+ pmd_t *pmd = pmd_offset(pud, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
+ addr = start + i * PMD_SIZE;
+ if (!pmd_none(*pmd))
+ /* pmd exists */
+ walk_pte(st, pmd, addr);
+ else
+ note_page(st, addr, 3, pmd_val(*pmd));
+ }
+}
+
+static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
+{
+ pud_t *pud = pud_offset(pgd, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
+ addr = start + i * PUD_SIZE;
+ if (!pud_none(*pud))
+ /* pud exists */
+ walk_pmd(st, pud, addr);
+ else
+ note_page(st, addr, 2, pud_val(*pud));
+ }
+}
+
+static void walk_pgd(struct pg_state *st, unsigned long start)
+{
+ pgd_t *pgd = pgd_offset_k(0UL);
+ unsigned i;
+ unsigned long addr;
+
+ for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
+ addr = start + i * PGDIR_SIZE;
+ if (!pgd_none(*pgd))
+ /* pgd exists */
+ walk_pud(st, pgd, addr);
+ else
+ note_page(st, addr, 1, pgd_val(*pgd));
+ }
+}
+
+static int ptdump_show(struct seq_file *m, void *v)
+{
+ struct pg_state st = {
+ .seq = m,
+ .start_address = KERN_VIRT_START,
+ .marker = address_markers,
+ };
+ /* Traverse kernel page tables */
+ walk_pgd(&st, KERN_VIRT_START);
+ note_page(&st, 0, 0, 0);
+ return 0;
+}
+
+static int ptdump_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ptdump_show, NULL);
+}
+
+static const struct file_operations ptdump_fops = {
+ .open = ptdump_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void build_pgtable_complete_mask(void)
+{
+ unsigned i, j;
+
+ for (i = 0; i < ARRAY_SIZE(pg_level); i++)
+ if (pg_level[i].flag)
+ for (j = 0; j < pg_level[i].num; j++)
+ pg_level[i].mask |= pg_level[i].flag[j].mask;
+}
+
+static int ptdump_init(void)
+{
+ struct dentry *debugfs_file;
+
+ build_pgtable_complete_mask();
+ debugfs_file = debugfs_create_file("kernel_pagetables", 0400, NULL,
+ NULL, &ptdump_fops);
+ return debugfs_file ? 0 : -ENOMEM;
+}
+device_initcall(ptdump_init);
--
2.5.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable
2016-03-21 22:04 [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables Rashmica Gupta
@ 2016-03-21 22:04 ` Rashmica Gupta
2016-03-23 4:17 ` kbuild test robot
2016-03-30 3:32 ` Balbir Singh
2016-03-22 23:43 ` [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables kbuild test robot
2016-03-30 3:24 ` Balbir Singh
2 siblings, 2 replies; 7+ messages in thread
From: Rashmica Gupta @ 2016-03-21 22:04 UTC (permalink / raw)
To: linuxppc-dev, mpe
Useful to be able to dump the kernel hash page table to check
which pages are hashed along with their sizes and other details.
Add a debugfs file to check the page tables. To use this the PPC_PTDUMP
config option must be selected.
Signed-off-by: Rashmica Gupta <rashmicy@gmail.com>
---
arch/powerpc/mm/Makefile | 3 +-
arch/powerpc/mm/dump_hashpagetable.c | 488 +++++++++++++++++++++++++++++++++++
2 files changed, 490 insertions(+), 1 deletion(-)
create mode 100644 arch/powerpc/mm/dump_hashpagetable.c
diff --git a/arch/powerpc/mm/Makefile b/arch/powerpc/mm/Makefile
index 6935c6204fbc..0bd48b345ff3 100644
--- a/arch/powerpc/mm/Makefile
+++ b/arch/powerpc/mm/Makefile
@@ -41,4 +41,5 @@ obj-$(CONFIG_NOT_COHERENT_CACHE) += dma-noncoherent.o
obj-$(CONFIG_HIGHMEM) += highmem.o
obj-$(CONFIG_PPC_COPRO_BASE) += copro_fault.o
obj-$(CONFIG_SPAPR_TCE_IOMMU) += mmu_context_iommu.o
-obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o
+obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o \
+ dump_hashpagetable.o
diff --git a/arch/powerpc/mm/dump_hashpagetable.c b/arch/powerpc/mm/dump_hashpagetable.c
new file mode 100644
index 000000000000..044fd13e4d07
--- /dev/null
+++ b/arch/powerpc/mm/dump_hashpagetable.c
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2016, Rashmica Gupta, IBM Corp.
+ *
+ * This traverses the kernel virtual memory and dumps the pages that are in
+ * the hash pagetable, along with their flags to
+ * /sys/kernel/debug/kernel_hash_pagetable.
+ *
+ * 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; version 2
+ * of the License.
+ */
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <asm/fixmap.h>
+#include <asm/pgtable.h>
+#include <linux/const.h>
+#include <asm/page.h>
+#include <asm/pgalloc.h>
+#include <asm/plpar_wrappers.h>
+#include <linux/memblock.h>
+#include <asm/firmware.h>
+
+struct addr_marker {
+ unsigned long start_address;
+ const char *name;
+};
+
+static struct addr_marker address_markers[] = {
+ { PAGE_OFFSET, "Start of kernel VM"},
+ { VMALLOC_START, "vmalloc() Area" },
+ { VMALLOC_END, "vmalloc() End" },
+ { ISA_IO_BASE, "isa I/O start" },
+ { ISA_IO_END, "isa I/O end" },
+ { PHB_IO_BASE, "phb I/O start" },
+ { PHB_IO_END, "phb I/O end" },
+ { IOREMAP_BASE, "I/O remap start" },
+ { IOREMAP_END, "I/O remap end" },
+ { VMEMMAP_BASE, "vmemmap start" },
+ { -1, NULL },
+};
+
+struct pg_state {
+ struct seq_file *seq;
+ const struct addr_marker *marker;
+ unsigned long start_address;
+ unsigned level;
+ u64 current_flags;
+};
+
+struct flag_info {
+ u64 mask;
+ u64 val;
+ const char *set;
+ const char *clear;
+ bool is_val;
+};
+
+static const struct flag_info v_flag_array[] = {
+ {
+ .mask = SLB_VSID_B,
+ .val = SLB_VSID_B_256M,
+ .set = "ssize: 256M",
+ .clear = "ssize: 1T ",
+ }, {
+ .mask = HPTE_V_SECONDARY,
+ .val = HPTE_V_SECONDARY,
+ .set = "secondary",
+ .clear = "primary ",
+ }, {
+ .mask = HPTE_V_VALID,
+ .val = HPTE_V_VALID,
+ .set = "valid ",
+ .clear = "invalid",
+ }, {
+ .mask = HPTE_V_BOLTED,
+ .val = HPTE_V_BOLTED,
+ .set = "bolted",
+ .clear = "",
+ }
+};
+
+static const struct flag_info r_flag_array[] = {
+ {
+ .mask = HPTE_R_PP0 | HPTE_R_PP,
+ .val = HPTE_R_PP0 | HPTE_R_PP,
+ .set = "prot",
+ .clear = "",
+ .is_val = true,
+ }, {
+ .mask = HPTE_R_KEY_HI | HPTE_R_KEY_LO,
+ .val = HPTE_R_KEY_HI | HPTE_R_KEY_LO,
+ .set = "key",
+ .clear = "",
+ .is_val = true,
+ }, {
+ .mask = HPTE_R_R,
+ .val = HPTE_R_R,
+ .set = "ref",
+ .clear = " ",
+ }, {
+ .mask = HPTE_R_C,
+ .val = HPTE_R_C,
+ .set = "changed",
+ .clear = " ",
+ }, {
+ .mask = HPTE_R_N,
+ .val = HPTE_R_N,
+ .set = "no execute",
+ .clear = "",
+ }, {
+ .mask = HPTE_R_WIMG,
+ .val = HPTE_R_W,
+ .set = "writethru",
+ .clear = "",
+ }, {
+ .mask = HPTE_R_WIMG,
+ .val = HPTE_R_I,
+ .set = "no cache",
+ .clear = "",
+ }, {
+ .mask = HPTE_R_WIMG,
+ .val = HPTE_R_G,
+ .set = "guarded",
+ .clear = "",
+ }
+};
+
+static void dump_flag_info(struct pg_state *st, const struct flag_info
+ *flag, unsigned long pte, int num)
+{
+ unsigned i;
+
+ for (i = 0; i < num; i++, flag++) {
+ const char *s = NULL;
+
+ if (flag->is_val) {
+ seq_printf(st->seq, " %s:%llx", flag->set, pte &
+ flag->val);
+ } else {
+ if ((pte & flag->mask) == flag->val)
+ s = flag->set;
+ else
+ s = flag->clear;
+ seq_printf(st->seq, " %s", s);
+ }
+ }
+}
+
+static void dump_hpte_info(struct pg_state *st, unsigned long ea, unsigned long
+ v, unsigned long r, unsigned long rpn, int bps, int aps,
+ unsigned long lp)
+{
+ static const char units[] = "BKMGTPE";
+ const char *unit = units;
+
+ while (ea >= st->marker[1].start_address) {
+ st->marker++;
+ seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
+ }
+ seq_printf(st->seq, "0x%lx:\t", ea);
+ seq_printf(st->seq, "AVPN:%lx\t", HPTE_V_AVPN_VAL(v));
+ dump_flag_info(st, v_flag_array, v, ARRAY_SIZE(v_flag_array));
+ seq_printf(st->seq, " rpn: %lx\t", rpn);
+ dump_flag_info(st, r_flag_array, r, ARRAY_SIZE(r_flag_array));
+
+ while (bps > 9 && unit[1]) {
+ bps -= 10;
+ unit++;
+ }
+ seq_printf(st->seq, "base_ps: %i%c\t", 1<<bps, *unit);
+ while (aps > 9 && unit[1]) {
+ aps -= 10;
+ unit++;
+ }
+ seq_printf(st->seq, "actual_ps: %i%c", 1<<aps, *unit);
+ if (lp != -1)
+ seq_printf(st->seq, "\tLP enc: %lx", lp);
+ seq_puts(st->seq, "\n");
+}
+
+static int native_find(unsigned long ea, int psize, bool primary, unsigned long
+ *v, unsigned long *r)
+{
+ struct hash_pte *hptep;
+ unsigned long hash, vsid, vpn, hpte_group, want_v, hpte_v;
+ int i, ssize = mmu_kernel_ssize;
+ unsigned long shift = mmu_psize_defs[psize].shift;
+
+ /* calculate hash */
+ vsid = get_kernel_vsid(ea, ssize);
+ vpn = hpt_vpn(ea, vsid, ssize);
+ hash = hpt_hash(vpn, shift, ssize);
+ want_v = hpte_encode_avpn(vpn, psize, ssize);
+
+ /* to check in the secondary hash table, we invert the hash */
+ if (!primary)
+ hash = ~hash;
+ hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP;
+ for (i = 0; i < HPTES_PER_GROUP; i++) {
+ hptep = htab_address + hpte_group;
+ hpte_v = be64_to_cpu(hptep->v);
+
+ if (HPTE_V_COMPARE(hpte_v, want_v) && (hpte_v & HPTE_V_VALID)) {
+ /* HPTE matches */
+ *v = be64_to_cpu(hptep->v);
+ *r = be64_to_cpu(hptep->r);
+ return 0;
+ }
+ ++hpte_group;
+ }
+ return -1;
+}
+
+static int pseries_find(unsigned long ea, int psize, bool primary, unsigned
+ long *v, unsigned long *r)
+{
+ struct hash_pte ptes[4];
+ unsigned long vsid, vpn, hash, hpte_group, want_v;
+ int i, j, ssize = mmu_kernel_ssize;
+ long lpar_rc = 0;
+ unsigned long shift = mmu_psize_defs[psize].shift;
+
+ /* calculate hash */
+ vsid = get_kernel_vsid(ea, ssize);
+ vpn = hpt_vpn(ea, vsid, ssize);
+ hash = hpt_hash(vpn, shift, ssize);
+ want_v = hpte_encode_avpn(vpn, psize, ssize);
+
+ /* to check in the secondary hash table, we invert the hash */
+ if (!primary)
+ hash = ~hash;
+ hpte_group = ((hash & htab_hash_mask) * HPTES_PER_GROUP) & ~0x7UL;
+ /* see if we can find an entry in the hpte with this hash */
+ for (i = 0; i < HPTES_PER_GROUP; i += 4, hpte_group += 4) {
+ lpar_rc = plpar_pte_read_4(0, hpte_group, (void *)ptes);
+
+ if (lpar_rc != H_SUCCESS)
+ continue;
+ for (j = 0; j < 4; j++) {
+ if (HPTE_V_COMPARE(ptes[j].v, want_v) &&
+ (ptes[j].v & HPTE_V_VALID)) {
+ /* HPTE matches */
+ *v = ptes[j].v;
+ *r = ptes[j].r;
+ return 0;
+ }
+ }
+ }
+ return -1;
+}
+
+static void decode_r(int bps, unsigned long r, unsigned long *rpn, int *aps,
+ unsigned long *lp_bits)
+{
+ struct mmu_psize_def entry;
+ unsigned long arpn, mask, lp;
+ int penc = -2, idx = 0, shift;
+
+ /*.
+ * The LP field has 8 bits. Depending on the actual page size, some of
+ * these bits are concatenated with the APRN to get the RPN. The rest
+ * of the bits in the LP field is the LP value and is an encoding for
+ * the base page size and the actual page size.
+ *
+ * - find the mmu entry for our base page size
+ * - go through all page encodings and use the associated mask to
+ * find an encoding that matches our encoding in the LP field.
+ */
+ arpn = (r & HPTE_R_RPN) >> HPTE_R_RPN_SHIFT;
+ lp = arpn & 0xff;
+
+ entry = mmu_psize_defs[bps];
+ while (idx < MMU_PAGE_COUNT) {
+ penc = entry.penc[idx];
+ if ((penc != -1) && (mmu_psize_defs[idx].shift)) {
+ shift = mmu_psize_defs[idx].shift - HPTE_R_RPN_SHIFT;
+ mask = (0x1 << (shift)) - 1;
+ if ((lp & mask) == penc) {
+ *aps = mmu_psize_to_shift(idx);
+ *lp_bits = lp & mask;
+ *rpn = arpn >> shift;
+ return;
+ }
+ }
+ idx++;
+ }
+}
+
+static unsigned long hpte_find(struct pg_state *st, unsigned long ea, int psize)
+{
+ unsigned long slot;
+ unsigned long v = 0, r = 0, rpn, lp_bits;
+ int base_psize = 0, actual_psize = 0;
+
+ if (ea <= PAGE_OFFSET)
+ return -1;
+
+ /* Look in primary table */
+ if (firmware_has_feature(FW_FEATURE_LPAR))
+ slot = pseries_find(ea, psize, true, &v, &r);
+ else
+ slot = native_find(ea, psize, true, &v, &r);
+
+ /* Look in secondary table */
+ if (slot == -1) {
+ if (firmware_has_feature(FW_FEATURE_LPAR))
+ slot = pseries_find(ea, psize, false, &v, &r);
+ else
+ slot = native_find(ea, psize, false, &v, &r);
+ }
+
+ /* No entry found */
+ if (slot == -1)
+ return -1;
+
+ /* We found an entry in the hash page table:
+ * - check that this has the same base page
+ * - find the actual page size
+ * - find the RPN
+ */
+ base_psize = mmu_psize_to_shift(psize);
+
+ if ((v & HPTE_V_LARGE) == HPTE_V_LARGE) {
+ decode_r(psize, r, &rpn, &actual_psize, &lp_bits);
+ } else {
+ /* 4K actual page size */
+ actual_psize = 12;
+ rpn = (r & HPTE_R_RPN) >> HPTE_R_RPN_SHIFT;
+ /* In this case there are no LP bits */
+ lp_bits = -1;
+ }
+ /* We didn't find a matching encoding, so the PTE we found isn't for
+ * this address.
+ */
+ if (actual_psize == -1)
+ return -1;
+
+ dump_hpte_info(st, ea, v, r, rpn, base_psize, actual_psize, lp_bits);
+ return 0;
+}
+
+static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start)
+{
+ pte_t *pte = pte_offset_kernel(pmd, 0);
+ unsigned long addr, pteval, psize;
+ int i, status;
+
+ for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
+ addr = start + i * PAGE_SIZE;
+ pteval = pte_val(*pte);
+
+ if (addr < VMALLOC_END)
+ psize = mmu_vmalloc_psize;
+ else
+ psize = mmu_io_psize;
+
+ /* check for secret 4K mappings */
+ if (((pteval & _PAGE_COMBO) == _PAGE_COMBO) ||
+ ((pteval & _PAGE_4K_PFN) == _PAGE_4K_PFN))
+ psize = mmu_io_psize;
+
+ /* check for hashpte */
+ status = hpte_find(st, addr, psize);
+
+ if (((pteval & _PAGE_HASHPTE) != _PAGE_HASHPTE)
+ && (status != -1)) {
+ /* found a hpte that is not in the linux page tables */
+ seq_printf(st->seq, "page probably bolted before linux"
+ " pagetables were set: addr:%lx, pteval:%lx\n",
+ addr, pteval);
+ }
+ }
+}
+
+static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
+{
+ pmd_t *pmd = pmd_offset(pud, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
+ addr = start + i * PMD_SIZE;
+ if (!pmd_none(*pmd))
+ /* pmd exists */
+ walk_pte(st, pmd, addr);
+ }
+}
+
+static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
+{
+ pud_t *pud = pud_offset(pgd, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
+ addr = start + i * PUD_SIZE;
+ if (!pud_none(*pud))
+ /* pud exists */
+ walk_pmd(st, pud, addr);
+ }
+}
+
+static void walk_linearmapping(struct pg_state *st)
+{
+ unsigned long addr;
+
+ /* Traverse the linear mapping section of virtual memory and dump pages
+ * that are in the hash pagetable.
+ */
+ for (addr = PAGE_OFFSET; addr < PAGE_OFFSET +
+ memblock_phys_mem_size(); addr += PAGE_SIZE)
+ hpte_find(st, addr, mmu_linear_psize);
+}
+
+static void walk_pagetables(struct pg_state *st)
+{
+ pgd_t *pgd = pgd_offset_k(0UL);
+ unsigned i;
+ unsigned long addr;
+
+ /* Traverse the linux pagetable structure and dump pages that are in
+ * the hash pagetable.
+ */
+ for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
+ addr = VMALLOC_START + i * PGDIR_SIZE;
+ if (!pgd_none(*pgd))
+ /* pgd exists */
+ walk_pud(st, pgd, addr);
+ }
+}
+
+static void walk_vmemmap(struct pg_state *st)
+{
+ struct vmemmap_backing *ptr = vmemmap_list;
+
+ /* Traverse the vmemmaped memory and dump pages that are in the hash
+ * pagetable.
+ */
+ while (ptr->list) {
+ hpte_find(st, ptr->virt_addr, mmu_vmemmap_psize);
+ ptr = ptr->list;
+ }
+ seq_puts(st->seq, "---[ vmemmap end ]---\n");
+}
+
+static int ptdump_show(struct seq_file *m, void *v)
+{
+ struct pg_state st = {
+ .seq = m,
+ .start_address = PAGE_OFFSET,
+ .marker = address_markers,
+ };
+ /* Traverse the 0xc, 0xd and 0xf areas of the kernel virtual memory and
+ * dump pages that are in the hash pagetable.
+ */
+ walk_linearmapping(&st);
+ walk_pagetables(&st);
+ walk_vmemmap(&st);
+ return 0;
+}
+
+static int ptdump_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ptdump_show, NULL);
+}
+
+static const struct file_operations ptdump_fops = {
+ .open = ptdump_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+
+static int ptdump_init(void)
+{
+ struct dentry *debugfs_file;
+
+ debugfs_file = debugfs_create_file("kernel_hash_pagetable", 0400,
+ NULL, NULL, &ptdump_fops);
+ return debugfs_file ? 0 : -ENOMEM;
+}
+device_initcall(ptdump_init);
--
2.5.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables
2016-03-21 22:04 [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables Rashmica Gupta
2016-03-21 22:04 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
@ 2016-03-22 23:43 ` kbuild test robot
2016-03-30 3:24 ` Balbir Singh
2 siblings, 0 replies; 7+ messages in thread
From: kbuild test robot @ 2016-03-22 23:43 UTC (permalink / raw)
To: Rashmica Gupta; +Cc: kbuild-all, linuxppc-dev, mpe
[-- Attachment #1: Type: text/plain, Size: 1371 bytes --]
Hi Rashmica,
[auto build test ERROR on powerpc/next]
[also build test ERROR on v4.5 next-20160322]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/Rashmica-Gupta/powerpc-pagetable-Add-option-to-dump-the-linux-pagetables/20160322-060934
base: https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git next
config: powerpc-allyesconfig (attached as .config)
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=powerpc
All errors (new ones prefixed by >>):
>> arch/powerpc/mm/dump_linuxpagetables.c:148:11: error: '_PAGE_COMBO' undeclared here (not in a function)
.mask = _PAGE_COMBO,
^
vim +/_PAGE_COMBO +148 arch/powerpc/mm/dump_linuxpagetables.c
142 .clear = " ",
143 }, {
144 .mask = _PAGE_BUSY,
145 .val = _PAGE_BUSY,
146 .set = "busy",
147 }, {
> 148 .mask = _PAGE_COMBO,
149 .val = _PAGE_COMBO,
150 .set = "combo",
151 }, {
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
[-- Attachment #2: .config.gz --]
[-- Type: application/octet-stream, Size: 47891 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable
2016-03-21 22:04 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
@ 2016-03-23 4:17 ` kbuild test robot
2016-03-30 3:32 ` Balbir Singh
1 sibling, 0 replies; 7+ messages in thread
From: kbuild test robot @ 2016-03-23 4:17 UTC (permalink / raw)
To: Rashmica Gupta; +Cc: kbuild-all, linuxppc-dev, mpe
[-- Attachment #1: Type: text/plain, Size: 1687 bytes --]
Hi Rashmica,
[auto build test ERROR on powerpc/next]
[also build test ERROR on v4.5 next-20160322]
[if your patch is applied to the wrong git tree, please drop us a note to help improving the system]
url: https://github.com/0day-ci/linux/commits/Rashmica-Gupta/powerpc-pagetable-Add-option-to-dump-the-linux-pagetables/20160322-060934
base: https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git next
config: powerpc-allyesconfig (attached as .config)
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=powerpc
All errors (new ones prefixed by >>):
arch/powerpc/mm/dump_hashpagetable.c: In function 'walk_pte':
>> arch/powerpc/mm/dump_hashpagetable.c:363:18: error: '_PAGE_COMBO' undeclared (first use in this function)
if (((pteval & _PAGE_COMBO) == _PAGE_COMBO) ||
^
arch/powerpc/mm/dump_hashpagetable.c:363:18: note: each undeclared identifier is reported only once for each function it appears in
vim +/_PAGE_COMBO +363 arch/powerpc/mm/dump_hashpagetable.c
357 if (addr < VMALLOC_END)
358 psize = mmu_vmalloc_psize;
359 else
360 psize = mmu_io_psize;
361
362 /* check for secret 4K mappings */
> 363 if (((pteval & _PAGE_COMBO) == _PAGE_COMBO) ||
364 ((pteval & _PAGE_4K_PFN) == _PAGE_4K_PFN))
365 psize = mmu_io_psize;
366
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
[-- Attachment #2: .config.gz --]
[-- Type: application/octet-stream, Size: 47891 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable
2016-03-29 2:37 [v2 PATCH " Rashmica Gupta
@ 2016-03-29 2:37 ` Rashmica Gupta
0 siblings, 0 replies; 7+ messages in thread
From: Rashmica Gupta @ 2016-03-29 2:37 UTC (permalink / raw)
To: linuxppc-dev, mpe
Useful to be able to dump the kernel hash page table to check
which pages are hashed along with their sizes and other details.
Add a debugfs file to check the page tables. To use this the PPC_PTDUMP
config option must be selected.
Signed-off-by: Rashmica Gupta <rashmicy@gmail.com>
---
arch/powerpc/mm/Makefile | 3 +-
arch/powerpc/mm/dump_hashpagetable.c | 501 +++++++++++++++++++++++++++++++++++
2 files changed, 503 insertions(+), 1 deletion(-)
create mode 100644 arch/powerpc/mm/dump_hashpagetable.c
diff --git a/arch/powerpc/mm/Makefile b/arch/powerpc/mm/Makefile
index 6935c6204fbc..0bd48b345ff3 100644
--- a/arch/powerpc/mm/Makefile
+++ b/arch/powerpc/mm/Makefile
@@ -41,4 +41,5 @@ obj-$(CONFIG_NOT_COHERENT_CACHE) += dma-noncoherent.o
obj-$(CONFIG_HIGHMEM) += highmem.o
obj-$(CONFIG_PPC_COPRO_BASE) += copro_fault.o
obj-$(CONFIG_SPAPR_TCE_IOMMU) += mmu_context_iommu.o
-obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o
+obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o \
+ dump_hashpagetable.o
diff --git a/arch/powerpc/mm/dump_hashpagetable.c b/arch/powerpc/mm/dump_hashpagetable.c
new file mode 100644
index 000000000000..fbd746ffe26a
--- /dev/null
+++ b/arch/powerpc/mm/dump_hashpagetable.c
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2016, Rashmica Gupta, IBM Corp.
+ *
+ * This traverses the kernel virtual memory and dumps the pages that are in
+ * the hash pagetable, along with their flags to
+ * /sys/kernel/debug/kernel_hash_pagetable.
+ *
+ * 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; version 2
+ * of the License.
+ */
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <asm/fixmap.h>
+#include <asm/pgtable.h>
+#include <linux/const.h>
+#include <asm/page.h>
+#include <asm/pgalloc.h>
+#include <asm/plpar_wrappers.h>
+#include <linux/memblock.h>
+#include <asm/firmware.h>
+
+struct addr_marker {
+ unsigned long start_address;
+ const char *name;
+};
+
+static struct addr_marker address_markers[] = {
+ { PAGE_OFFSET, "Start of kernel VM"},
+ { VMALLOC_START, "vmalloc() Area" },
+ { VMALLOC_END, "vmalloc() End" },
+ { ISA_IO_BASE, "isa I/O start" },
+ { ISA_IO_END, "isa I/O end" },
+ { PHB_IO_BASE, "phb I/O start" },
+ { PHB_IO_END, "phb I/O end" },
+ { IOREMAP_BASE, "I/O remap start" },
+ { IOREMAP_END, "I/O remap end" },
+ { VMEMMAP_BASE, "vmemmap start" },
+ { -1, NULL },
+};
+
+struct pg_state {
+ struct seq_file *seq;
+ const struct addr_marker *marker;
+ unsigned long start_address;
+ unsigned level;
+ u64 current_flags;
+};
+
+struct flag_info {
+ u64 mask;
+ u64 val;
+ const char *set;
+ const char *clear;
+ bool is_val;
+};
+
+static const struct flag_info v_flag_array[] = {
+ {
+ .mask = SLB_VSID_B,
+ .val = SLB_VSID_B_256M,
+ .set = "ssize: 256M",
+ .clear = "ssize: 1T ",
+ }, {
+ .mask = HPTE_V_SECONDARY,
+ .val = HPTE_V_SECONDARY,
+ .set = "secondary",
+ .clear = "primary ",
+ }, {
+ .mask = HPTE_V_VALID,
+ .val = HPTE_V_VALID,
+ .set = "valid ",
+ .clear = "invalid",
+ }, {
+ .mask = HPTE_V_BOLTED,
+ .val = HPTE_V_BOLTED,
+ .set = "bolted",
+ .clear = "",
+ }
+};
+
+static const struct flag_info r_flag_array[] = {
+ {
+ .mask = HPTE_R_PP0 | HPTE_R_PP,
+ .val = PP_RWXX,
+ .set = "prot:RW--",
+ }, {
+ .mask = HPTE_R_PP0 | HPTE_R_PP,
+ .val = PP_RWRX,
+ .set = "prot:RWR-",
+ }, {
+ .mask = HPTE_R_PP0 | HPTE_R_PP,
+ .val = PP_RWRW,
+ .set = "prot:RWRW",
+ }, {
+ .mask = HPTE_R_PP0 | HPTE_R_PP,
+ .val = PP_RXRX,
+ .set = "prot:R-R-",
+ }, {
+ .mask = HPTE_R_PP0 | HPTE_R_PP,
+ .val = PP_RXXX,
+ .set = "prot:R---",
+ }, {
+ .mask = HPTE_R_KEY_HI | HPTE_R_KEY_LO,
+ .val = HPTE_R_KEY_HI | HPTE_R_KEY_LO,
+ .set = "key",
+ .clear = "",
+ .is_val = true,
+ }, {
+ .mask = HPTE_R_R,
+ .val = HPTE_R_R,
+ .set = "ref",
+ .clear = " ",
+ }, {
+ .mask = HPTE_R_C,
+ .val = HPTE_R_C,
+ .set = "changed",
+ .clear = " ",
+ }, {
+ .mask = HPTE_R_N,
+ .val = HPTE_R_N,
+ .set = "no execute",
+ }, {
+ .mask = HPTE_R_WIMG,
+ .val = HPTE_R_W,
+ .set = "writethru",
+ }, {
+ .mask = HPTE_R_WIMG,
+ .val = HPTE_R_I,
+ .set = "no cache",
+ }, {
+ .mask = HPTE_R_WIMG,
+ .val = HPTE_R_G,
+ .set = "guarded",
+ }
+};
+
+static void dump_flag_info(struct pg_state *st, const struct flag_info
+ *flag, u64 pte, int num)
+{
+ unsigned i;
+
+ for (i = 0; i < num; i++, flag++) {
+ const char *s = NULL;
+
+ if (flag->is_val) {
+ seq_printf(st->seq, " %s:%llx", flag->set, pte &
+ flag->val);
+ } else {
+ if ((pte & flag->mask) == flag->val)
+ s = flag->set;
+ else
+ s = flag->clear;
+ if (s)
+ seq_printf(st->seq, " %s", s);
+ }
+ }
+}
+
+static void dump_hpte_info(struct pg_state *st, unsigned long ea, u64 v, u64 r,
+ unsigned long rpn, int bps, int aps, unsigned long lp)
+{
+ static const char units[] = "BKMGTPE";
+ const char *unit = units;
+
+ while (ea >= st->marker[1].start_address) {
+ st->marker++;
+ seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
+ }
+ seq_printf(st->seq, "0x%lx:\t", ea);
+ seq_printf(st->seq, "AVPN:%llx\t", HPTE_V_AVPN_VAL(v));
+ dump_flag_info(st, v_flag_array, v, ARRAY_SIZE(v_flag_array));
+ seq_printf(st->seq, " rpn: %lx\t", rpn);
+ dump_flag_info(st, r_flag_array, r, ARRAY_SIZE(r_flag_array));
+
+ while (bps > 9 && unit[1]) {
+ bps -= 10;
+ unit++;
+ }
+ seq_printf(st->seq, " base_ps: %i%c\t", 1<<bps, *unit);
+ unit = units;
+ while (aps > 9 && unit[1]) {
+ aps -= 10;
+ unit++;
+ }
+ seq_printf(st->seq, "actual_ps: %i%c", 1<<aps, *unit);
+ if (aps != 2)
+ seq_printf(st->seq, "\tLP enc: %lx", lp);
+ seq_puts(st->seq, "\n");
+}
+
+static int native_find(unsigned long ea, int psize, bool primary, u64 *v, u64
+ *r)
+{
+ struct hash_pte *hptep;
+ unsigned long hash, vsid, vpn, hpte_group, want_v, hpte_v;
+ int i, ssize = mmu_kernel_ssize;
+ unsigned long shift = mmu_psize_defs[psize].shift;
+
+ /* calculate hash */
+ vsid = get_kernel_vsid(ea, ssize);
+ vpn = hpt_vpn(ea, vsid, ssize);
+ hash = hpt_hash(vpn, shift, ssize);
+ want_v = hpte_encode_avpn(vpn, psize, ssize);
+
+ /* to check in the secondary hash table, we invert the hash */
+ if (!primary)
+ hash = ~hash;
+ hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP;
+ for (i = 0; i < HPTES_PER_GROUP; i++) {
+ hptep = htab_address + hpte_group;
+ hpte_v = be64_to_cpu(hptep->v);
+
+ if (HPTE_V_COMPARE(hpte_v, want_v) && (hpte_v & HPTE_V_VALID)) {
+ /* HPTE matches */
+ *v = be64_to_cpu(hptep->v);
+ *r = be64_to_cpu(hptep->r);
+ return 0;
+ }
+ ++hpte_group;
+ }
+ return -1;
+}
+
+static int pseries_find(unsigned long ea, int psize, bool primary, u64 *v, u64
+ *r)
+{
+ struct hash_pte ptes[4];
+ unsigned long vsid, vpn, hash, hpte_group, want_v;
+ int i, j, ssize = mmu_kernel_ssize;
+ long lpar_rc = 0;
+ unsigned long shift = mmu_psize_defs[psize].shift;
+
+ /* calculate hash */
+ vsid = get_kernel_vsid(ea, ssize);
+ vpn = hpt_vpn(ea, vsid, ssize);
+ hash = hpt_hash(vpn, shift, ssize);
+ want_v = hpte_encode_avpn(vpn, psize, ssize);
+
+ /* to check in the secondary hash table, we invert the hash */
+ if (!primary)
+ hash = ~hash;
+ hpte_group = ((hash & htab_hash_mask) * HPTES_PER_GROUP) & ~0x7UL;
+ /* see if we can find an entry in the hpte with this hash */
+ for (i = 0; i < HPTES_PER_GROUP; i += 4, hpte_group += 4) {
+ lpar_rc = plpar_pte_read_4(0, hpte_group, (void *)ptes);
+
+ if (lpar_rc != H_SUCCESS)
+ continue;
+ for (j = 0; j < 4; j++) {
+ if (HPTE_V_COMPARE(ptes[j].v, want_v) &&
+ (ptes[j].v & HPTE_V_VALID)) {
+ /* HPTE matches */
+ *v = ptes[j].v;
+ *r = ptes[j].r;
+ return 0;
+ }
+ }
+ }
+ return -1;
+}
+
+static void decode_r(int bps, unsigned long r, unsigned long *rpn, int *aps,
+ unsigned long *lp_bits)
+{
+ struct mmu_psize_def entry;
+ unsigned long arpn, mask, lp;
+ int penc = -2, idx = 0, shift;
+
+ /*.
+ * The LP field has 8 bits. Depending on the actual page size, some of
+ * these bits are concatenated with the APRN to get the RPN. The rest
+ * of the bits in the LP field is the LP value and is an encoding for
+ * the base page size and the actual page size.
+ *
+ * - find the mmu entry for our base page size
+ * - go through all page encodings and use the associated mask to
+ * find an encoding that matches our encoding in the LP field.
+ */
+ arpn = (r & HPTE_R_RPN) >> HPTE_R_RPN_SHIFT;
+ lp = arpn & 0xff;
+
+ entry = mmu_psize_defs[bps];
+ while (idx < MMU_PAGE_COUNT) {
+ penc = entry.penc[idx];
+ if ((penc != -1) && (mmu_psize_defs[idx].shift)) {
+ shift = mmu_psize_defs[idx].shift - HPTE_R_RPN_SHIFT;
+ mask = (0x1 << (shift)) - 1;
+ if ((lp & mask) == penc) {
+ *aps = mmu_psize_to_shift(idx);
+ *lp_bits = lp & mask;
+ *rpn = arpn >> shift;
+ return;
+ }
+ }
+ idx++;
+ }
+}
+
+static unsigned long hpte_find(struct pg_state *st, unsigned long ea, int psize)
+{
+ unsigned long slot;
+ u64 v = 0, r = 0;
+ unsigned long rpn, lp_bits;
+ int base_psize = 0, actual_psize = 0;
+
+ if (ea <= PAGE_OFFSET)
+ return -1;
+
+ /* Look in primary table */
+ if (firmware_has_feature(FW_FEATURE_LPAR))
+ slot = pseries_find(ea, psize, true, &v, &r);
+ else
+ slot = native_find(ea, psize, true, &v, &r);
+
+ /* Look in secondary table */
+ if (slot == -1) {
+ if (firmware_has_feature(FW_FEATURE_LPAR))
+ slot = pseries_find(ea, psize, false, &v, &r);
+ else
+ slot = native_find(ea, psize, false, &v, &r);
+ }
+
+ /* No entry found */
+ if (slot == -1)
+ return -1;
+
+ /* We found an entry in the hash page table:
+ * - check that this has the same base page
+ * - find the actual page size
+ * - find the RPN
+ */
+ base_psize = mmu_psize_to_shift(psize);
+
+ if ((v & HPTE_V_LARGE) == HPTE_V_LARGE) {
+ decode_r(psize, r, &rpn, &actual_psize, &lp_bits);
+ } else {
+ /* 4K actual page size */
+ actual_psize = 12;
+ rpn = (r & HPTE_R_RPN) >> HPTE_R_RPN_SHIFT;
+ /* In this case there are no LP bits */
+ lp_bits = -1;
+ }
+ /* We didn't find a matching encoding, so the PTE we found isn't for
+ * this address.
+ */
+ if (actual_psize == -1)
+ return -1;
+
+ dump_hpte_info(st, ea, v, r, rpn, base_psize, actual_psize, lp_bits);
+ return 0;
+}
+
+static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start)
+{
+ pte_t *pte = pte_offset_kernel(pmd, 0);
+ unsigned long addr, pteval, psize;
+ int i, status;
+
+ for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
+ addr = start + i * PAGE_SIZE;
+ pteval = pte_val(*pte);
+
+ if (addr < VMALLOC_END)
+ psize = mmu_vmalloc_psize;
+ else
+ psize = mmu_io_psize;
+#ifdef CONFIG_PPC_64K_PAGES
+ /* check for secret 4K mappings */
+ if (((pteval & _PAGE_COMBO) == _PAGE_COMBO) ||
+ ((pteval & _PAGE_4K_PFN) == _PAGE_4K_PFN))
+ psize = mmu_io_psize;
+#endif
+ /* check for hashpte */
+ status = hpte_find(st, addr, psize);
+
+ if (((pteval & _PAGE_HASHPTE) != _PAGE_HASHPTE)
+ && (status != -1)) {
+ /* found a hpte that is not in the linux page tables */
+ seq_printf(st->seq, "page probably bolted before linux"
+ " pagetables were set: addr:%lx, pteval:%lx\n",
+ addr, pteval);
+ }
+ }
+}
+
+static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
+{
+ pmd_t *pmd = pmd_offset(pud, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
+ addr = start + i * PMD_SIZE;
+ if (!pmd_none(*pmd))
+ /* pmd exists */
+ walk_pte(st, pmd, addr);
+ }
+}
+
+static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
+{
+ pud_t *pud = pud_offset(pgd, 0);
+ unsigned long addr;
+ unsigned i;
+
+ for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
+ addr = start + i * PUD_SIZE;
+ if (!pud_none(*pud))
+ /* pud exists */
+ walk_pmd(st, pud, addr);
+ }
+}
+
+static void walk_linearmapping(struct pg_state *st)
+{
+ unsigned long addr;
+
+ /* Traverse the linear mapping section of virtual memory and dump pages
+ * that are in the hash pagetable.
+ */
+unsigned long psize = 1 << mmu_psize_defs[mmu_linear_psize].shift;
+ for (addr = PAGE_OFFSET; addr < PAGE_OFFSET +
+ memblock_phys_mem_size(); addr += psize)
+ hpte_find(st, addr, mmu_linear_psize);
+}
+
+static void walk_pagetables(struct pg_state *st)
+{
+ pgd_t *pgd = pgd_offset_k(0UL);
+ unsigned i;
+ unsigned long addr;
+
+ /* Traverse the linux pagetable structure and dump pages that are in
+ * the hash pagetable.
+ */
+ for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
+ addr = VMALLOC_START + i * PGDIR_SIZE;
+ if (!pgd_none(*pgd))
+ /* pgd exists */
+ walk_pud(st, pgd, addr);
+ }
+}
+
+static void walk_vmemmap(struct pg_state *st)
+{
+ struct vmemmap_backing *ptr = vmemmap_list;
+
+ /* Traverse the vmemmaped memory and dump pages that are in the hash
+ * pagetable.
+ */
+ while (ptr->list) {
+ hpte_find(st, ptr->virt_addr, mmu_vmemmap_psize);
+ ptr = ptr->list;
+ }
+ seq_puts(st->seq, "---[ vmemmap end ]---\n");
+}
+
+static int ptdump_show(struct seq_file *m, void *v)
+{
+ struct pg_state st = {
+ .seq = m,
+ .start_address = PAGE_OFFSET,
+ .marker = address_markers,
+ };
+ /* Traverse the 0xc, 0xd and 0xf areas of the kernel virtual memory and
+ * dump pages that are in the hash pagetable.
+ */
+ walk_linearmapping(&st);
+ walk_pagetables(&st);
+ walk_vmemmap(&st);
+ return 0;
+}
+
+static int ptdump_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ptdump_show, NULL);
+}
+
+static const struct file_operations ptdump_fops = {
+ .open = ptdump_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+
+static int ptdump_init(void)
+{
+ struct dentry *debugfs_file;
+
+ debugfs_file = debugfs_create_file("kernel_hash_pagetable", 0400,
+ NULL, NULL, &ptdump_fops);
+ return debugfs_file ? 0 : -ENOMEM;
+}
+device_initcall(ptdump_init);
--
2.5.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables
2016-03-21 22:04 [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables Rashmica Gupta
2016-03-21 22:04 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
2016-03-22 23:43 ` [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables kbuild test robot
@ 2016-03-30 3:24 ` Balbir Singh
2 siblings, 0 replies; 7+ messages in thread
From: Balbir Singh @ 2016-03-30 3:24 UTC (permalink / raw)
To: linuxppc-dev
On 22/03/16 09:04, Rashmica Gupta wrote:
> Useful to be able to dump the kernels page tables to check permissions
> and memory types - derived from arm64's implementation.
>
> Add a debugfs file to check the page tables. To use this the PPC_PTDUMP
> config option must be selected.
>
> Signed-off-by: Rashmica Gupta <rashmicy@gmail.com>
> ---
> arch/powerpc/Kconfig.debug | 12 ++
> arch/powerpc/mm/Makefile | 1 +
> arch/powerpc/mm/dump_linuxpagetables.c | 377 +++++++++++++++++++++++++++++++++
> 3 files changed, 390 insertions(+)
> create mode 100644 arch/powerpc/mm/dump_linuxpagetables.c
>
> diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug
> index 638f9ce740f5..26a60effea1a 100644
> --- a/arch/powerpc/Kconfig.debug
> +++ b/arch/powerpc/Kconfig.debug
> @@ -344,4 +344,16 @@ config FAIL_IOMMU
>
> If you are unsure, say N.
>
> +config PPC_PTDUMP
> + bool "Export kernel pagetable layout to userspace via debugfs"
> + depends on DEBUG_KERNEL
> + select DEBUG_FS
> + help
> + This option exports the state of the kernel pagetables to a
> + debugfs file. This is only useful for kernel developers who are
> + working in architecture specific areas of the kernel - probably
> + not a good idea to enable this feature in a production kernel.
> +
> + If you are unsure, say N.
> +
Some minor comments below, but otherwise
Acked-by: Balbir Singh <bsingharora@gmail.com>
> endmenu
> diff --git a/arch/powerpc/mm/Makefile b/arch/powerpc/mm/Makefile
> index adfee3f1aeb9..6935c6204fbc 100644
> --- a/arch/powerpc/mm/Makefile
> +++ b/arch/powerpc/mm/Makefile
> @@ -41,3 +41,4 @@ obj-$(CONFIG_NOT_COHERENT_CACHE) += dma-noncoherent.o
> obj-$(CONFIG_HIGHMEM) += highmem.o
> obj-$(CONFIG_PPC_COPRO_BASE) += copro_fault.o
> obj-$(CONFIG_SPAPR_TCE_IOMMU) += mmu_context_iommu.o
> +obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o
> diff --git a/arch/powerpc/mm/dump_linuxpagetables.c b/arch/powerpc/mm/dump_linuxpagetables.c
> new file mode 100644
> index 000000000000..f97fbfdac4b9
> --- /dev/null
> +++ b/arch/powerpc/mm/dump_linuxpagetables.c
> @@ -0,0 +1,377 @@
> +/*
> + * Copyright 2016, Rashmica Gupta, IBM Corp.
> + *
> + * This traverses the kernel pagetables and dumps the
> + * information about the used sections of memory to
> + * /sys/kernel/debug/kernel_pagetables.
> + *
> + * Derived from the arm64 implementation:
> + * Copyright (c) 2014, The Linux Foundation, Laura Abbott.
> + * (C) Copyright 2008 Intel Corporation, Arjan van de Ven.
> + *
> + * 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; version 2
> + * of the License.
> + */
> +#include <linux/debugfs.h>
> +#include <linux/fs.h>
> +#include <linux/io.h>
> +#include <linux/mm.h>
> +#include <linux/sched.h>
> +#include <linux/seq_file.h>
> +#include <asm/fixmap.h>
> +#include <asm/pgtable.h>
> +#include <linux/const.h>
> +#include <asm/page.h>
> +#include <asm/pgalloc.h>
> +
> +struct addr_marker {
> + unsigned long start_address;
> + const char *name;
> +};
> +
> +static struct addr_marker address_markers[] = {
> + { VMALLOC_START, "vmalloc() Area" },
> + { VMALLOC_END, "vmalloc() End" },
> + { ISA_IO_BASE, "isa I/O start" },
> + { ISA_IO_END, "isa I/O end" },
> + { PHB_IO_BASE, "phb I/O start" },
> + { PHB_IO_END, "phb I/O end" },
> + { IOREMAP_BASE, "I/O remap start" },
> + { IOREMAP_END, "I/O remap end" },
> + { -1, NULL },
> +};
> +
> +/*
> + * To visualise what is happening,
> + *
> + * - PTRS_PER_P** = how many entries there are in the corresponding P**
> + * - P**_SHIFT = how many bits of the address we use to index into the
> + * corresponding P**
> + * - P**_SIZE is how much memory we can access through the table - not the
> + * size of the table itself.
> + * P**={PGD, PUD, PMD, PTE}
> + *
> + *
> + * Each entry of the PGD points to a PUD. Each entry of a PUD points to a
> + * PMD. Each entry of a PMD points to a PTE. And every PTE entry points to
> + * a page.
> + *
> + * In the case where there are only 3 levels, the PUD is folded into the
> + * PGD: every PUD has only one entry which points to the PMD.
> + *
> + * The page dumper groups page table entries of the same type into a single
> + * description. It uses pg_state to track the range information while
> + * iterating over the PTE entries. When the continuity is broken it then
> + * dumps out a description of the range - ie PTEs that are virtually contiguous
> + * with the same PTE flags are chunked together. This is to make it clear how
> + * different areas of the kernel virtual memory are used.
> + *
> + */
> +struct pg_state {
> + struct seq_file *seq;
> + const struct addr_marker *marker;
> + unsigned long start_address;
> + unsigned level;
> + u64 current_flags;
> +};
> +
> +struct flag_info {
> + u64 mask;
> + u64 val;
> + const char *set;
> + const char *clear;
> +};
> +
> +static const struct flag_info flag_array[] = {
> + {
> + .mask = _PAGE_USER,
> + .val = _PAGE_USER,
> + .set = "user",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_RW,
> + .val = _PAGE_RW,
> + .set = "rw",
> + .clear = "ro",
> + }, {
> + .mask = _PAGE_EXEC,
> + .val = _PAGE_EXEC,
> + .set = " X ",
Can we make this small "x" to sort of go in line with "rw"/"ro"
> + .clear = " ",
> + }, {
> + .mask = _PAGE_PTE,
> + .val = _PAGE_PTE,
> + .set = "pte",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_PRESENT,
> + .val = _PAGE_PRESENT,
> + .set = "present",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_HASHPTE,
> + .val = _PAGE_HASHPTE,
> + .set = "hpte",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_GUARDED,
> + .val = _PAGE_GUARDED,
> + .set = "guarded",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_DIRTY,
> + .val = _PAGE_DIRTY,
> + .set = "dirty",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_ACCESSED,
> + .val = _PAGE_ACCESSED,
> + .set = "accessed",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_WRITETHRU,
> + .val = _PAGE_WRITETHRU,
> + .set = "write through",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_NO_CACHE,
> + .val = _PAGE_NO_CACHE,
> + .set = "no cache",
> + .clear = " ",
> + }, {
> + .mask = _PAGE_BUSY,
> + .val = _PAGE_BUSY,
> + .set = "busy",
> + }, {
> + .mask = _PAGE_COMBO,
> + .val = _PAGE_COMBO,
> + .set = "combo",
> + }, {
> + .mask = _PAGE_F_GIX,
> + .val = _PAGE_F_GIX,
> + .set = "f_gix",
Can we use "primary"?
> + }, {
> + .mask = _PAGE_F_SECOND,
> + .val = _PAGE_F_SECOND,
> + .set = "f_second",
"secondary idx"
> + }, {
> + .mask = _PAGE_SPECIAL,
> + .val = _PAGE_SPECIAL,
> + .set = "special",
> + }, {
> + .mask = _PAGE_4K_PFN,
> + .val = _PAGE_4K_PFN,
> + .set = "4K_pfn"
> + }
> +};
> +
> +struct pgtable_level {
> + const struct flag_info *flag;
> + size_t num;
> + u64 mask;
> +};
> +
> +static struct pgtable_level pg_level[] = {
> + {
> + }, { /* pgd */
> + .flag = flag_array,
> + .num = ARRAY_SIZE(flag_array),
> + }, { /* pud */
> + .flag = flag_array,
> + .num = ARRAY_SIZE(flag_array),
> + }, { /* pmd */
> + .flag = flag_array,
> + .num = ARRAY_SIZE(flag_array),
> + }, { /* pte */
> + .flag = flag_array,
> + .num = ARRAY_SIZE(flag_array),
> + },
> +};
> +
> +static void dump_flag(struct pg_state *st, const struct flag_info *flag,
> + size_t num)
> +{
> + unsigned i;
> +
> + for (i = 0; i < num; i++, flag++) {
> + const char *s;
> +
> + if ((st->current_flags & flag->mask) == flag->val)
> + s = flag->set;
> + else
> + s = flag->clear;
> +
> + if (s)
> + seq_printf(st->seq, " %s", s);
> + }
> +}
> +
> +static void dump_addr(struct pg_state *st, unsigned long addr)
> +{
> + static const char units[] = "KMGTPE";
> + const char *unit = units;
> + unsigned long delta;
> +
> + seq_printf(st->seq, "0x%016lx-0x%016lx ", st->start_address, addr-1);
> + delta = (addr - st->start_address) >> 10;
> + /* Work out what appropriate unit to use */
> + while (!(delta & 1023) && unit[1]) {
1023, I think ((1 << 10) - 1) is easier to understand but again you may find a better way of encapsulating this
> + delta >>= 10;
> + unit++;
> + }
> + seq_printf(st->seq, "%9lu%c", delta, *unit);
> +
> +}
> +
> +static void note_page(struct pg_state *st, unsigned long addr, unsigned level,
> + u64 val)
> +{
> + u64 flag = val & pg_level[level].mask;
> +
> + /* At first no level is set */
> + if (!st->level) {
> + st->level = level;
> + st->current_flags = flag;
> + st->start_address = addr;
> + seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
> + /*
> + * Dump the section of virtual memory when:
> + * - the PTE flags from one entry to the next differs.
> + * - we change levels in the tree.
> + * - the address is in a different section of memory and is thus
> + * used for a different purpose, regardless of the flags.
> + */
> + } else if (flag != st->current_flags || level != st->level ||
> + addr >= st->marker[1].start_address) {
> +
> + /* Check the PTE flags */
> + if (st->current_flags) {
> + dump_addr(st, addr);
> +
> + /* Dump all the flags */
> + if (pg_level[st->level].flag)
> + dump_flag(st, pg_level[st->level].flag,
> + pg_level[st->level].num);
> + seq_puts(st->seq, "\n");
> + }
> +
> + /* Address indicates we have passed the end of the
> + * current section of virtual memory
> + */
> + while (addr >= st->marker[1].start_address) {
> + st->marker++;
> + seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
> + }
> + st->start_address = addr;
> + st->current_flags = flag;
> + st->level = level;
> + }
> +}
> +
> +static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start)
> +{
> + pte_t *pte = pte_offset_kernel(pmd, 0);
> + unsigned long addr;
> + unsigned i;
> +
> + for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
> + addr = start + i * PAGE_SIZE;
> + note_page(st, addr, 4, pte_val(*pte));
> + }
> +}
> +
> +static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
> +{
> + pmd_t *pmd = pmd_offset(pud, 0);
> + unsigned long addr;
> + unsigned i;
> +
> + for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
> + addr = start + i * PMD_SIZE;
> + if (!pmd_none(*pmd))
> + /* pmd exists */
> + walk_pte(st, pmd, addr);
> + else
> + note_page(st, addr, 3, pmd_val(*pmd));
> + }
> +}
> +
> +static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
> +{
> + pud_t *pud = pud_offset(pgd, 0);
> + unsigned long addr;
> + unsigned i;
> +
> + for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
> + addr = start + i * PUD_SIZE;
> + if (!pud_none(*pud))
> + /* pud exists */
> + walk_pmd(st, pud, addr);
> + else
> + note_page(st, addr, 2, pud_val(*pud));
> + }
> +}
> +
> +static void walk_pgd(struct pg_state *st, unsigned long start)
> +{
> + pgd_t *pgd = pgd_offset_k(0UL);
> + unsigned i;
> + unsigned long addr;
> +
> + for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
> + addr = start + i * PGDIR_SIZE;
> + if (!pgd_none(*pgd))
> + /* pgd exists */
> + walk_pud(st, pgd, addr);
> + else
> + note_page(st, addr, 1, pgd_val(*pgd));
> + }
> +}
> +
> +static int ptdump_show(struct seq_file *m, void *v)
> +{
> + struct pg_state st = {
> + .seq = m,
> + .start_address = KERN_VIRT_START,
> + .marker = address_markers,
> + };
> + /* Traverse kernel page tables */
> + walk_pgd(&st, KERN_VIRT_START);
> + note_page(&st, 0, 0, 0);
> + return 0;
> +}
> +
> +static int ptdump_open(struct inode *inode, struct file *file)
> +{
> + return single_open(file, ptdump_show, NULL);
> +}
> +
> +static const struct file_operations ptdump_fops = {
> + .open = ptdump_open,
> + .read = seq_read,
> + .llseek = seq_lseek,
> + .release = single_release,
> +};
> +
> +static void build_pgtable_complete_mask(void)
> +{
> + unsigned i, j;
> +
> + for (i = 0; i < ARRAY_SIZE(pg_level); i++)
> + if (pg_level[i].flag)
> + for (j = 0; j < pg_level[i].num; j++)
> + pg_level[i].mask |= pg_level[i].flag[j].mask;
> +}
> +
> +static int ptdump_init(void)
> +{
> + struct dentry *debugfs_file;
> +
> + build_pgtable_complete_mask();
> + debugfs_file = debugfs_create_file("kernel_pagetables", 0400, NULL,
> + NULL, &ptdump_fops);
> + return debugfs_file ? 0 : -ENOMEM;
> +}
> +device_initcall(ptdump_init);
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable
2016-03-21 22:04 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
2016-03-23 4:17 ` kbuild test robot
@ 2016-03-30 3:32 ` Balbir Singh
1 sibling, 0 replies; 7+ messages in thread
From: Balbir Singh @ 2016-03-30 3:32 UTC (permalink / raw)
To: linuxppc-dev
On 22/03/16 09:04, Rashmica Gupta wrote:
> Useful to be able to dump the kernel hash page table to check
> which pages are hashed along with their sizes and other details.
>
> Add a debugfs file to check the page tables. To use this the PPC_PTDUMP
> config option must be selected.
>
> Signed-off-by: Rashmica Gupta <rashmicy@gmail.com>
> ---
> arch/powerpc/mm/Makefile | 3 +-
> arch/powerpc/mm/dump_hashpagetable.c | 488 +++++++++++++++++++++++++++++++++++
> 2 files changed, 490 insertions(+), 1 deletion(-)
> create mode 100644 arch/powerpc/mm/dump_hashpagetable.c
>
> diff --git a/arch/powerpc/mm/Makefile b/arch/powerpc/mm/Makefile
> index 6935c6204fbc..0bd48b345ff3 100644
> --- a/arch/powerpc/mm/Makefile
> +++ b/arch/powerpc/mm/Makefile
> @@ -41,4 +41,5 @@ obj-$(CONFIG_NOT_COHERENT_CACHE) += dma-noncoherent.o
> obj-$(CONFIG_HIGHMEM) += highmem.o
> obj-$(CONFIG_PPC_COPRO_BASE) += copro_fault.o
> obj-$(CONFIG_SPAPR_TCE_IOMMU) += mmu_context_iommu.o
> -obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o
> +obj-$(CONFIG_PPC_PTDUMP) += dump_linuxpagetables.o \
> + dump_hashpagetable.o
> diff --git a/arch/powerpc/mm/dump_hashpagetable.c b/arch/powerpc/mm/dump_hashpagetable.c
> new file mode 100644
> index 000000000000..044fd13e4d07
> --- /dev/null
> +++ b/arch/powerpc/mm/dump_hashpagetable.c
> @@ -0,0 +1,488 @@
> +/*
> + * Copyright 2016, Rashmica Gupta, IBM Corp.
> + *
> + * This traverses the kernel virtual memory and dumps the pages that are in
> + * the hash pagetable, along with their flags to
> + * /sys/kernel/debug/kernel_hash_pagetable.
> + *
> + * 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; version 2
> + * of the License.
> + */
> +#include <linux/debugfs.h>
> +#include <linux/fs.h>
> +#include <linux/io.h>
> +#include <linux/mm.h>
> +#include <linux/sched.h>
> +#include <linux/seq_file.h>
> +#include <asm/fixmap.h>
> +#include <asm/pgtable.h>
> +#include <linux/const.h>
> +#include <asm/page.h>
> +#include <asm/pgalloc.h>
> +#include <asm/plpar_wrappers.h>
> +#include <linux/memblock.h>
> +#include <asm/firmware.h>
> +
> +struct addr_marker {
> + unsigned long start_address;
> + const char *name;
> +};
> +
> +static struct addr_marker address_markers[] = {
> + { PAGE_OFFSET, "Start of kernel VM"},
> + { VMALLOC_START, "vmalloc() Area" },
> + { VMALLOC_END, "vmalloc() End" },
> + { ISA_IO_BASE, "isa I/O start" },
> + { ISA_IO_END, "isa I/O end" },
> + { PHB_IO_BASE, "phb I/O start" },
> + { PHB_IO_END, "phb I/O end" },
> + { IOREMAP_BASE, "I/O remap start" },
> + { IOREMAP_END, "I/O remap end" },
> + { VMEMMAP_BASE, "vmemmap start" },
> + { -1, NULL },
> +};
> +
> +struct pg_state {
> + struct seq_file *seq;
> + const struct addr_marker *marker;
> + unsigned long start_address;
> + unsigned level;
> + u64 current_flags;
> +};
> +
> +struct flag_info {
> + u64 mask;
> + u64 val;
> + const char *set;
> + const char *clear;
> + bool is_val;
> +};
> +
> +static const struct flag_info v_flag_array[] = {
> + {
> + .mask = SLB_VSID_B,
> + .val = SLB_VSID_B_256M,
> + .set = "ssize: 256M",
> + .clear = "ssize: 1T ",
> + }, {
> + .mask = HPTE_V_SECONDARY,
> + .val = HPTE_V_SECONDARY,
> + .set = "secondary",
> + .clear = "primary ",
> + }, {
> + .mask = HPTE_V_VALID,
> + .val = HPTE_V_VALID,
> + .set = "valid ",
> + .clear = "invalid",
> + }, {
> + .mask = HPTE_V_BOLTED,
> + .val = HPTE_V_BOLTED,
> + .set = "bolted",
> + .clear = "",
> + }
> +};
> +
> +static const struct flag_info r_flag_array[] = {
> + {
> + .mask = HPTE_R_PP0 | HPTE_R_PP,
> + .val = HPTE_R_PP0 | HPTE_R_PP,
> + .set = "prot",
> + .clear = "",
> + .is_val = true,
> + }, {
> + .mask = HPTE_R_KEY_HI | HPTE_R_KEY_LO,
> + .val = HPTE_R_KEY_HI | HPTE_R_KEY_LO,
> + .set = "key",
> + .clear = "",
> + .is_val = true,
> + }, {
> + .mask = HPTE_R_R,
> + .val = HPTE_R_R,
> + .set = "ref",
> + .clear = " ",
> + }, {
> + .mask = HPTE_R_C,
> + .val = HPTE_R_C,
> + .set = "changed",
> + .clear = " ",
> + }, {
> + .mask = HPTE_R_N,
> + .val = HPTE_R_N,
> + .set = "no execute",
> + .clear = "",
> + }, {
> + .mask = HPTE_R_WIMG,
> + .val = HPTE_R_W,
> + .set = "writethru",
> + .clear = "",
> + }, {
> + .mask = HPTE_R_WIMG,
> + .val = HPTE_R_I,
> + .set = "no cache",
> + .clear = "",
> + }, {
> + .mask = HPTE_R_WIMG,
> + .val = HPTE_R_G,
> + .set = "guarded",
> + .clear = "",
> + }
> +};
> +
> +static void dump_flag_info(struct pg_state *st, const struct flag_info
> + *flag, unsigned long pte, int num)
> +{
> + unsigned i;
> +
> + for (i = 0; i < num; i++, flag++) {
> + const char *s = NULL;
> +
> + if (flag->is_val) {
> + seq_printf(st->seq, " %s:%llx", flag->set, pte &
> + flag->val);
> + } else {
> + if ((pte & flag->mask) == flag->val)
> + s = flag->set;
> + else
> + s = flag->clear;
> + seq_printf(st->seq, " %s", s);
> + }
> + }
> +}
> +
> +static void dump_hpte_info(struct pg_state *st, unsigned long ea, unsigned long
> + v, unsigned long r, unsigned long rpn, int bps, int aps,
> + unsigned long lp)
> +{
> + static const char units[] = "BKMGTPE";
> + const char *unit = units;
> +
> + while (ea >= st->marker[1].start_address) {
> + st->marker++;
> + seq_printf(st->seq, "---[ %s ]---\n", st->marker->name);
> + }
> + seq_printf(st->seq, "0x%lx:\t", ea);
> + seq_printf(st->seq, "AVPN:%lx\t", HPTE_V_AVPN_VAL(v));
> + dump_flag_info(st, v_flag_array, v, ARRAY_SIZE(v_flag_array));
> + seq_printf(st->seq, " rpn: %lx\t", rpn);
> + dump_flag_info(st, r_flag_array, r, ARRAY_SIZE(r_flag_array));
> +
> + while (bps > 9 && unit[1]) {
> + bps -= 10;
> + unit++;
> + }
> + seq_printf(st->seq, "base_ps: %i%c\t", 1<<bps, *unit);
> + while (aps > 9 && unit[1]) {
> + aps -= 10;
> + unit++;
> + }
> + seq_printf(st->seq, "actual_ps: %i%c", 1<<aps, *unit);
> + if (lp != -1)
> + seq_printf(st->seq, "\tLP enc: %lx", lp);
> + seq_puts(st->seq, "\n");
> +}
> +
> +static int native_find(unsigned long ea, int psize, bool primary, unsigned long
> + *v, unsigned long *r)
You probably need a #ifdef around this
> +{
> + struct hash_pte *hptep;
> + unsigned long hash, vsid, vpn, hpte_group, want_v, hpte_v;
> + int i, ssize = mmu_kernel_ssize;
> + unsigned long shift = mmu_psize_defs[psize].shift;
> +
> + /* calculate hash */
> + vsid = get_kernel_vsid(ea, ssize);
> + vpn = hpt_vpn(ea, vsid, ssize);
> + hash = hpt_hash(vpn, shift, ssize);
> + want_v = hpte_encode_avpn(vpn, psize, ssize);
> +
> + /* to check in the secondary hash table, we invert the hash */
> + if (!primary)
> + hash = ~hash;
> + hpte_group = (hash & htab_hash_mask) * HPTES_PER_GROUP;
> + for (i = 0; i < HPTES_PER_GROUP; i++) {
> + hptep = htab_address + hpte_group;
> + hpte_v = be64_to_cpu(hptep->v);
> +
> + if (HPTE_V_COMPARE(hpte_v, want_v) && (hpte_v & HPTE_V_VALID)) {
> + /* HPTE matches */
> + *v = be64_to_cpu(hptep->v);
> + *r = be64_to_cpu(hptep->r);
> + return 0;
> + }
> + ++hpte_group;
> + }
> + return -1;
> +}
> +
> +static int pseries_find(unsigned long ea, int psize, bool primary, unsigned
> + long *v, unsigned long *r)
> +{
Same here, should compile only if PLATFORM pseries is set, you need an #ifdef around this
> + struct hash_pte ptes[4];
> + unsigned long vsid, vpn, hash, hpte_group, want_v;
> + int i, j, ssize = mmu_kernel_ssize;
> + long lpar_rc = 0;
> + unsigned long shift = mmu_psize_defs[psize].shift;
> +
> + /* calculate hash */
> + vsid = get_kernel_vsid(ea, ssize);
> + vpn = hpt_vpn(ea, vsid, ssize);
> + hash = hpt_hash(vpn, shift, ssize);
> + want_v = hpte_encode_avpn(vpn, psize, ssize);
> +
> + /* to check in the secondary hash table, we invert the hash */
> + if (!primary)
> + hash = ~hash;
> + hpte_group = ((hash & htab_hash_mask) * HPTES_PER_GROUP) & ~0x7UL;
> + /* see if we can find an entry in the hpte with this hash */
> + for (i = 0; i < HPTES_PER_GROUP; i += 4, hpte_group += 4) {
> + lpar_rc = plpar_pte_read_4(0, hpte_group, (void *)ptes);
> +
> + if (lpar_rc != H_SUCCESS)
> + continue;
> + for (j = 0; j < 4; j++) {
> + if (HPTE_V_COMPARE(ptes[j].v, want_v) &&
> + (ptes[j].v & HPTE_V_VALID)) {
> + /* HPTE matches */
> + *v = ptes[j].v;
> + *r = ptes[j].r;
> + return 0;
> + }
> + }
> + }
> + return -1;
> +}
> +
> +static void decode_r(int bps, unsigned long r, unsigned long *rpn, int *aps,
> + unsigned long *lp_bits)
> +{
> + struct mmu_psize_def entry;
> + unsigned long arpn, mask, lp;
> + int penc = -2, idx = 0, shift;
> +
> + /*.
> + * The LP field has 8 bits. Depending on the actual page size, some of
> + * these bits are concatenated with the APRN to get the RPN. The rest
> + * of the bits in the LP field is the LP value and is an encoding for
> + * the base page size and the actual page size.
> + *
> + * - find the mmu entry for our base page size
> + * - go through all page encodings and use the associated mask to
> + * find an encoding that matches our encoding in the LP field.
> + */
> + arpn = (r & HPTE_R_RPN) >> HPTE_R_RPN_SHIFT;
> + lp = arpn & 0xff;
> +
> + entry = mmu_psize_defs[bps];
> + while (idx < MMU_PAGE_COUNT) {
> + penc = entry.penc[idx];
> + if ((penc != -1) && (mmu_psize_defs[idx].shift)) {
> + shift = mmu_psize_defs[idx].shift - HPTE_R_RPN_SHIFT;
> + mask = (0x1 << (shift)) - 1;
> + if ((lp & mask) == penc) {
> + *aps = mmu_psize_to_shift(idx);
> + *lp_bits = lp & mask;
> + *rpn = arpn >> shift;
> + return;
> + }
> + }
> + idx++;
> + }
> +}
> +
> +static unsigned long hpte_find(struct pg_state *st, unsigned long ea, int psize)
> +{
> + unsigned long slot;
> + unsigned long v = 0, r = 0, rpn, lp_bits;
> + int base_psize = 0, actual_psize = 0;
> +
> + if (ea <= PAGE_OFFSET)
> + return -1;
> +
> + /* Look in primary table */
> + if (firmware_has_feature(FW_FEATURE_LPAR))
> + slot = pseries_find(ea, psize, true, &v, &r);
> + else
> + slot = native_find(ea, psize, true, &v, &r);
> +
> + /* Look in secondary table */
> + if (slot == -1) {
> + if (firmware_has_feature(FW_FEATURE_LPAR))
> + slot = pseries_find(ea, psize, false, &v, &r);
> + else
> + slot = native_find(ea, psize, false, &v, &r);
> + }
> +
> + /* No entry found */
> + if (slot == -1)
> + return -1;
> +
> + /* We found an entry in the hash page table:
> + * - check that this has the same base page
> + * - find the actual page size
> + * - find the RPN
> + */
> + base_psize = mmu_psize_to_shift(psize);
> +
> + if ((v & HPTE_V_LARGE) == HPTE_V_LARGE) {
> + decode_r(psize, r, &rpn, &actual_psize, &lp_bits);
> + } else {
> + /* 4K actual page size */
> + actual_psize = 12;
> + rpn = (r & HPTE_R_RPN) >> HPTE_R_RPN_SHIFT;
> + /* In this case there are no LP bits */
> + lp_bits = -1;
> + }
> + /* We didn't find a matching encoding, so the PTE we found isn't for
> + * this address.
> + */
> + if (actual_psize == -1)
> + return -1;
> +
> + dump_hpte_info(st, ea, v, r, rpn, base_psize, actual_psize, lp_bits);
> + return 0;
> +}
> +
> +static void walk_pte(struct pg_state *st, pmd_t *pmd, unsigned long start)
> +{
> + pte_t *pte = pte_offset_kernel(pmd, 0);
> + unsigned long addr, pteval, psize;
> + int i, status;
> +
> + for (i = 0; i < PTRS_PER_PTE; i++, pte++) {
> + addr = start + i * PAGE_SIZE;
> + pteval = pte_val(*pte);
> +
> + if (addr < VMALLOC_END)
> + psize = mmu_vmalloc_psize;
> + else
> + psize = mmu_io_psize;
> +
> + /* check for secret 4K mappings */
> + if (((pteval & _PAGE_COMBO) == _PAGE_COMBO) ||
> + ((pteval & _PAGE_4K_PFN) == _PAGE_4K_PFN))
> + psize = mmu_io_psize;
> +
> + /* check for hashpte */
> + status = hpte_find(st, addr, psize);
> +
> + if (((pteval & _PAGE_HASHPTE) != _PAGE_HASHPTE)
> + && (status != -1)) {
> + /* found a hpte that is not in the linux page tables */
> + seq_printf(st->seq, "page probably bolted before linux"
> + " pagetables were set: addr:%lx, pteval:%lx\n",
> + addr, pteval);
> + }
> + }
> +}
> +
> +static void walk_pmd(struct pg_state *st, pud_t *pud, unsigned long start)
> +{
> + pmd_t *pmd = pmd_offset(pud, 0);
> + unsigned long addr;
> + unsigned i;
> +
> + for (i = 0; i < PTRS_PER_PMD; i++, pmd++) {
> + addr = start + i * PMD_SIZE;
> + if (!pmd_none(*pmd))
> + /* pmd exists */
> + walk_pte(st, pmd, addr);
> + }
> +}
> +
> +static void walk_pud(struct pg_state *st, pgd_t *pgd, unsigned long start)
> +{
> + pud_t *pud = pud_offset(pgd, 0);
> + unsigned long addr;
> + unsigned i;
> +
> + for (i = 0; i < PTRS_PER_PUD; i++, pud++) {
> + addr = start + i * PUD_SIZE;
> + if (!pud_none(*pud))
> + /* pud exists */
> + walk_pmd(st, pud, addr);
> + }
> +}
> +
> +static void walk_linearmapping(struct pg_state *st)
> +{
> + unsigned long addr;
> +
> + /* Traverse the linear mapping section of virtual memory and dump pages
> + * that are in the hash pagetable.
> + */
> + for (addr = PAGE_OFFSET; addr < PAGE_OFFSET +
> + memblock_phys_mem_size(); addr += PAGE_SIZE)
> + hpte_find(st, addr, mmu_linear_psize);
> +}
> +
> +static void walk_pagetables(struct pg_state *st)
> +{
> + pgd_t *pgd = pgd_offset_k(0UL);
> + unsigned i;
> + unsigned long addr;
> +
> + /* Traverse the linux pagetable structure and dump pages that are in
> + * the hash pagetable.
> + */
> + for (i = 0; i < PTRS_PER_PGD; i++, pgd++) {
> + addr = VMALLOC_START + i * PGDIR_SIZE;
> + if (!pgd_none(*pgd))
> + /* pgd exists */
> + walk_pud(st, pgd, addr);
> + }
> +}
> +
> +static void walk_vmemmap(struct pg_state *st)
> +{
> + struct vmemmap_backing *ptr = vmemmap_list;
> +
> + /* Traverse the vmemmaped memory and dump pages that are in the hash
> + * pagetable.
> + */
> + while (ptr->list) {
> + hpte_find(st, ptr->virt_addr, mmu_vmemmap_psize);
> + ptr = ptr->list;
> + }
> + seq_puts(st->seq, "---[ vmemmap end ]---\n");
> +}
> +
> +static int ptdump_show(struct seq_file *m, void *v)
> +{
> + struct pg_state st = {
> + .seq = m,
> + .start_address = PAGE_OFFSET,
> + .marker = address_markers,
> + };
> + /* Traverse the 0xc, 0xd and 0xf areas of the kernel virtual memory and
> + * dump pages that are in the hash pagetable.
> + */
> + walk_linearmapping(&st);
> + walk_pagetables(&st);
> + walk_vmemmap(&st);
> + return 0;
> +}
> +
> +static int ptdump_open(struct inode *inode, struct file *file)
> +{
> + return single_open(file, ptdump_show, NULL);
> +}
> +
> +static const struct file_operations ptdump_fops = {
> + .open = ptdump_open,
> + .read = seq_read,
> + .llseek = seq_lseek,
> + .release = single_release,
> +};
> +
> +
> +static int ptdump_init(void)
> +{
> + struct dentry *debugfs_file;
> +
> + debugfs_file = debugfs_create_file("kernel_hash_pagetable", 0400,
> + NULL, NULL, &ptdump_fops);
> + return debugfs_file ? 0 : -ENOMEM;
> +}
> +device_initcall(ptdump_init);
Otherwise (also check the _PAGE_COMBO build failure reports)
Acked-by: Balbir Singh <bsingharora@gmail.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2016-03-30 3:32 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-03-21 22:04 [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables Rashmica Gupta
2016-03-21 22:04 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
2016-03-23 4:17 ` kbuild test robot
2016-03-30 3:32 ` Balbir Singh
2016-03-22 23:43 ` [PATCH 1/2] powerpc/pagetable: Add option to dump the linux pagetables kbuild test robot
2016-03-30 3:24 ` Balbir Singh
-- strict thread matches above, loose matches on Subject: below --
2016-03-29 2:37 [v2 PATCH " Rashmica Gupta
2016-03-29 2:37 ` [PATCH 2/2] powerpc/pagetable: Add option to dump kernel hashpagetable Rashmica Gupta
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.