s390/mm,ptdump: convert to generic page table dumper

Make use of generic ptdump infrastructure.

Reviewed-by: Vasily Gorbik <gor@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
This commit is contained in:
Heiko Carstens 2020-09-04 17:41:27 +02:00 committed by Vasily Gorbik
parent 0d574ad33e
commit 9d719d39aa
6 changed files with 51 additions and 201 deletions

View File

@ -120,6 +120,7 @@ config S390
select GENERIC_CPU_VULNERABILITIES
select GENERIC_FIND_FIRST_BIT
select GENERIC_GETTIMEOFDAY
select GENERIC_PTDUMP
select GENERIC_SMP_IDLE_THREAD
select GENERIC_TIME_VSYSCALL
select HAVE_ALIGNED_STRUCT_PAGE if SLUB

View File

@ -3,17 +3,5 @@
config TRACE_IRQFLAGS_SUPPORT
def_bool y
config S390_PTDUMP
bool "Export kernel pagetable layout to userspace via debugfs"
depends on DEBUG_KERNEL
select DEBUG_FS
help
Say Y here if you want to show the kernel pagetable layout in a
debugfs file. This information is only useful for kernel developers
who are working in architecture specific areas of the kernel.
It is probably not a good idea to enable this feature in a production
kernel.
If in doubt, say "N"
config EARLY_PRINTK
def_bool y

View File

@ -774,6 +774,7 @@ CONFIG_MAGIC_SYSRQ=y
CONFIG_DEBUG_PAGEALLOC=y
CONFIG_PAGE_OWNER=y
CONFIG_DEBUG_RODATA_TEST=y
CONFIG_PTDUMP_DEBUGFS=y
CONFIG_DEBUG_OBJECTS=y
CONFIG_DEBUG_OBJECTS_SELFTEST=y
CONFIG_DEBUG_OBJECTS_FREE=y
@ -819,7 +820,6 @@ CONFIG_SCHED_TRACER=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_BLK_DEV_IO_TRACE=y
CONFIG_HIST_TRIGGERS=y
CONFIG_S390_PTDUMP=y
CONFIG_NOTIFIER_ERROR_INJECTION=m
CONFIG_NETDEV_NOTIFIER_ERROR_INJECT=m
CONFIG_FAULT_INJECTION=y

View File

@ -758,6 +758,7 @@ CONFIG_GDB_SCRIPTS=y
CONFIG_FRAME_WARN=1024
CONFIG_DEBUG_SECTION_MISMATCH=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_PTDUMP_DEBUGFS=y
CONFIG_DEBUG_MEMORY_INIT=y
CONFIG_PANIC_ON_OOPS=y
CONFIG_TEST_LOCKUP=m
@ -772,7 +773,6 @@ CONFIG_SCHED_TRACER=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_BLK_DEV_IO_TRACE=y
CONFIG_HIST_TRIGGERS=y
CONFIG_S390_PTDUMP=y
CONFIG_LKDTM=m
CONFIG_PERCPU_TEST=m
CONFIG_ATOMIC64_SELFTEST=y

View File

@ -8,7 +8,7 @@ obj-y += page-states.o pageattr.o pgtable.o pgalloc.o
obj-$(CONFIG_CMM) += cmm.o
obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o
obj-$(CONFIG_S390_PTDUMP) += dump_pagetables.o
obj-$(CONFIG_PTDUMP_DEBUGFS) += dump_pagetables.o
obj-$(CONFIG_PGSTE) += gmap.o
KASAN_SANITIZE_kasan_init.o := n

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ptdump.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/kasan.h>
#include <asm/kasan.h>
@ -42,10 +42,11 @@ static struct addr_marker address_markers[] = {
};
struct pg_state {
struct ptdump_state ptdump;
struct seq_file *seq;
int level;
unsigned int current_prot;
unsigned long start_address;
unsigned long current_address;
const struct addr_marker *marker;
};
@ -63,215 +64,75 @@ static void print_prot(struct seq_file *m, unsigned int pr, int level)
seq_puts(m, (pr & _PAGE_NOEXEC) ? "NX\n" : "X\n");
}
static void note_page(struct seq_file *m, struct pg_state *st,
unsigned int new_prot, int level)
static void note_page(struct ptdump_state *pt_st, unsigned long addr, int level, u64 val)
{
static const char units[] = "KMGTPE";
int width = sizeof(unsigned long) * 2;
static const char units[] = "KMGTPE";
const char *unit = units;
unsigned int prot, cur;
unsigned long delta;
struct pg_state *st;
struct seq_file *m;
unsigned int prot;
/*
* If we have a "break" in the series, we need to flush the state
* that we have now. "break" is either changing perms, levels or
* address space marker.
*/
prot = new_prot;
cur = st->current_prot;
if (!st->level) {
/* First entry */
st->current_prot = new_prot;
st->level = level;
st->marker = address_markers;
st = container_of(pt_st, struct pg_state, ptdump);
m = st->seq;
prot = val & (_PAGE_PROTECT | _PAGE_NOEXEC);
if (level == 4 && (val & _PAGE_INVALID))
prot = _PAGE_INVALID;
/* For pmd_none() & friends val gets passed as zero. */
if (level != 4 && !val)
prot = _PAGE_INVALID;
/* Final flush from generic code. */
if (level == -1)
addr = max_addr;
if (st->level == -1) {
seq_printf(m, "---[ %s ]---\n", st->marker->name);
} else if (prot != cur || level != st->level ||
st->current_address >= st->marker[1].start_address) {
/* Print the actual finished series */
st->start_address = addr;
st->current_prot = prot;
st->level = level;
} else if (prot != st->current_prot || level != st->level ||
addr >= st->marker[1].start_address) {
seq_printf(m, "0x%0*lx-0x%0*lx ",
width, st->start_address,
width, st->current_address);
delta = (st->current_address - st->start_address) >> 10;
width, addr);
delta = (addr - st->start_address) >> 10;
while (!(delta & 0x3ff) && unit[1]) {
delta >>= 10;
unit++;
}
seq_printf(m, "%9lu%c ", delta, *unit);
print_prot(m, st->current_prot, st->level);
while (st->current_address >= st->marker[1].start_address) {
while (addr >= st->marker[1].start_address) {
st->marker++;
seq_printf(m, "---[ %s ]---\n", st->marker->name);
}
st->start_address = st->current_address;
st->current_prot = new_prot;
st->start_address = addr;
st->current_prot = prot;
st->level = level;
}
}
#ifdef CONFIG_KASAN
static void note_kasan_early_shadow_page(struct seq_file *m,
struct pg_state *st)
{
unsigned int prot;
prot = pte_val(*kasan_early_shadow_pte) &
(_PAGE_PROTECT | _PAGE_INVALID | _PAGE_NOEXEC);
note_page(m, st, prot, 4);
}
#endif
/*
* The actual page table walker functions. In order to keep the
* implementation of print_prot() short, we only check and pass
* _PAGE_INVALID and _PAGE_PROTECT flags to note_page() if a region,
* segment or page table entry is invalid or read-only.
* After all it's just a hint that the current level being walked
* contains an invalid or read-only entry.
*/
static void walk_pte_level(struct seq_file *m, struct pg_state *st,
pmd_t *pmd, unsigned long addr)
{
unsigned int prot;
pte_t *pte;
int i;
for (i = 0; i < PTRS_PER_PTE && addr < max_addr; i++) {
st->current_address = addr;
pte = pte_offset_kernel(pmd, addr);
prot = pte_val(*pte) &
(_PAGE_PROTECT | _PAGE_INVALID | _PAGE_NOEXEC);
note_page(m, st, prot, 4);
addr += PAGE_SIZE;
}
}
static void walk_pmd_level(struct seq_file *m, struct pg_state *st,
pud_t *pud, unsigned long addr)
{
unsigned int prot;
pmd_t *pmd;
int i;
#ifdef CONFIG_KASAN
if ((pud_val(*pud) & PAGE_MASK) == __pa(kasan_early_shadow_pmd)) {
note_kasan_early_shadow_page(m, st);
return;
}
#endif
pmd = pmd_offset(pud, addr);
for (i = 0; i < PTRS_PER_PMD && addr < max_addr; i++, pmd++) {
st->current_address = addr;
if (!pmd_none(*pmd)) {
if (pmd_large(*pmd)) {
prot = pmd_val(*pmd) &
(_SEGMENT_ENTRY_PROTECT |
_SEGMENT_ENTRY_NOEXEC);
note_page(m, st, prot, 3);
} else
walk_pte_level(m, st, pmd, addr);
} else
note_page(m, st, _PAGE_INVALID, 3);
addr += PMD_SIZE;
}
}
static void walk_pud_level(struct seq_file *m, struct pg_state *st,
p4d_t *p4d, unsigned long addr)
{
unsigned int prot;
pud_t *pud;
int i;
#ifdef CONFIG_KASAN
if ((p4d_val(*p4d) & PAGE_MASK) == __pa(kasan_early_shadow_pud)) {
note_kasan_early_shadow_page(m, st);
return;
}
#endif
pud = pud_offset(p4d, addr);
for (i = 0; i < PTRS_PER_PUD && addr < max_addr; i++, pud++) {
st->current_address = addr;
if (!pud_none(*pud))
if (pud_large(*pud)) {
prot = pud_val(*pud) &
(_REGION_ENTRY_PROTECT |
_REGION_ENTRY_NOEXEC);
note_page(m, st, prot, 2);
} else
walk_pmd_level(m, st, pud, addr);
else
note_page(m, st, _PAGE_INVALID, 2);
addr += PUD_SIZE;
}
}
static void walk_p4d_level(struct seq_file *m, struct pg_state *st,
pgd_t *pgd, unsigned long addr)
{
p4d_t *p4d;
int i;
#ifdef CONFIG_KASAN
if ((pgd_val(*pgd) & PAGE_MASK) == __pa(kasan_early_shadow_p4d)) {
note_kasan_early_shadow_page(m, st);
return;
}
#endif
p4d = p4d_offset(pgd, addr);
for (i = 0; i < PTRS_PER_P4D && addr < max_addr; i++, p4d++) {
st->current_address = addr;
if (!p4d_none(*p4d))
walk_pud_level(m, st, p4d, addr);
else
note_page(m, st, _PAGE_INVALID, 2);
addr += P4D_SIZE;
}
}
static void walk_pgd_level(struct seq_file *m)
{
unsigned long addr = 0;
struct pg_state st;
pgd_t *pgd;
int i;
memset(&st, 0, sizeof(st));
for (i = 0; i < PTRS_PER_PGD && addr < max_addr; i++) {
st.current_address = addr;
pgd = pgd_offset_k(addr);
if (!pgd_none(*pgd))
walk_p4d_level(m, &st, pgd, addr);
else
note_page(m, &st, _PAGE_INVALID, 1);
addr += PGDIR_SIZE;
cond_resched();
}
/* Flush out the last page */
st.current_address = max_addr;
note_page(m, &st, 0, 0);
}
static int ptdump_show(struct seq_file *m, void *v)
{
walk_pgd_level(m);
struct pg_state st = {
.ptdump = {
.note_page = note_page,
.range = (struct ptdump_range[]) {
{.start = 0, .end = max_addr},
{.start = 0, .end = 0},
}
},
.seq = m,
.level = -1,
.current_prot = 0,
.start_address = 0,
.marker = address_markers,
};
ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
return 0;
}
static int ptdump_open(struct inode *inode, struct file *filp)
{
return single_open(filp, ptdump_show, NULL);
}
static const struct file_operations ptdump_fops = {
.open = ptdump_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
DEFINE_SHOW_ATTRIBUTE(ptdump);
static int pt_dump_init(void)
{