mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-18 00:56:49 +07:00
6bf752daca
This patch implements CONFIG_DEBUG_VIRTUAL to warn about incorrect use of virt_to_phys() and page_to_phys() Below is the result of test_debug_virtual: [ 1.438746] WARNING: CPU: 0 PID: 1 at ./arch/powerpc/include/asm/io.h:808 test_debug_virtual_init+0x3c/0xd4 [ 1.448156] CPU: 0 PID: 1 Comm: swapper Not tainted 4.20.0-rc5-00560-g6bfb52e23a00-dirty #532 [ 1.457259] NIP: c066c550 LR: c0650ccc CTR: c066c514 [ 1.462257] REGS: c900bdb0 TRAP: 0700 Not tainted (4.20.0-rc5-00560-g6bfb52e23a00-dirty) [ 1.471184] MSR: 00029032 <EE,ME,IR,DR,RI> CR: 48000422 XER: 20000000 [ 1.477811] [ 1.477811] GPR00: c0650ccc c900be60 c60d0000 00000000 006000c0 c9000000 00009032 c7fa0020 [ 1.477811] GPR08: 00002400 00000001 09000000 00000000 c07b5d04 00000000 c00037d8 00000000 [ 1.477811] GPR16: 00000000 00000000 00000000 00000000 c0760000 c0740000 00000092 c0685bb0 [ 1.477811] GPR24: c065042c c068a734 c0685b8c 00000006 00000000 c0760000 c075c3c0 ffffffff [ 1.512711] NIP [c066c550] test_debug_virtual_init+0x3c/0xd4 [ 1.518315] LR [c0650ccc] do_one_initcall+0x8c/0x1cc [ 1.523163] Call Trace: [ 1.525595] [c900be60] [c0567340] 0xc0567340 (unreliable) [ 1.530954] [c900be90] [c0650ccc] do_one_initcall+0x8c/0x1cc [ 1.536551] [c900bef0] [c0651000] kernel_init_freeable+0x1f4/0x2cc [ 1.542658] [c900bf30] [c00037ec] kernel_init+0x14/0x110 [ 1.547913] [c900bf40] [c000e1d0] ret_from_kernel_thread+0x14/0x1c [ 1.553971] Instruction dump: [ 1.556909] 3ca50100 bfa10024 54a5000e 3fa0c076 7c0802a6 3d454000 813dc204 554893be [ 1.564566] 7d294010 7d294910 90010034 39290001 <0f090000> 7c3e0b78 955e0008 3fe0c062 [ 1.572425] ---[ end trace 6f6984225b280ad6 ]--- [ 1.577467] PA: 0x09000000 for VA: 0xc9000000 [ 1.581799] PA: 0x061e8f50 for VA: 0xc61e8f50 Signed-off-by: Christophe Leroy <christophe.leroy@c-s.fr> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
397 lines
9.5 KiB
C
397 lines
9.5 KiB
C
/*
|
|
* This file contains the routines setting up the linux page tables.
|
|
* -- paulus
|
|
*
|
|
* Derived from arch/ppc/mm/init.c:
|
|
* Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
|
|
*
|
|
* Modifications by Paul Mackerras (PowerMac) (paulus@cs.anu.edu.au)
|
|
* and Cort Dougan (PReP) (cort@cs.nmt.edu)
|
|
* Copyright (C) 1996 Paul Mackerras
|
|
*
|
|
* Derived from "arch/i386/mm/init.c"
|
|
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
|
|
*
|
|
* 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; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/init.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
#include <asm/pgalloc.h>
|
|
#include <asm/fixmap.h>
|
|
#include <asm/io.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/sections.h>
|
|
|
|
#include "mmu_decl.h"
|
|
|
|
unsigned long ioremap_bot;
|
|
EXPORT_SYMBOL(ioremap_bot); /* aka VMALLOC_END */
|
|
|
|
extern char etext[], _stext[], _sinittext[], _einittext[];
|
|
|
|
__ref pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address)
|
|
{
|
|
if (!slab_is_available())
|
|
return memblock_alloc(PTE_FRAG_SIZE, PTE_FRAG_SIZE);
|
|
|
|
return (pte_t *)pte_fragment_alloc(mm, address, 1);
|
|
}
|
|
|
|
pgtable_t pte_alloc_one(struct mm_struct *mm, unsigned long address)
|
|
{
|
|
return (pgtable_t)pte_fragment_alloc(mm, address, 0);
|
|
}
|
|
|
|
void __iomem *
|
|
ioremap(phys_addr_t addr, unsigned long size)
|
|
{
|
|
pgprot_t prot = pgprot_noncached(PAGE_KERNEL);
|
|
|
|
return __ioremap_caller(addr, size, prot, __builtin_return_address(0));
|
|
}
|
|
EXPORT_SYMBOL(ioremap);
|
|
|
|
void __iomem *
|
|
ioremap_wc(phys_addr_t addr, unsigned long size)
|
|
{
|
|
pgprot_t prot = pgprot_noncached_wc(PAGE_KERNEL);
|
|
|
|
return __ioremap_caller(addr, size, prot, __builtin_return_address(0));
|
|
}
|
|
EXPORT_SYMBOL(ioremap_wc);
|
|
|
|
void __iomem *
|
|
ioremap_wt(phys_addr_t addr, unsigned long size)
|
|
{
|
|
pgprot_t prot = pgprot_cached_wthru(PAGE_KERNEL);
|
|
|
|
return __ioremap_caller(addr, size, prot, __builtin_return_address(0));
|
|
}
|
|
EXPORT_SYMBOL(ioremap_wt);
|
|
|
|
void __iomem *
|
|
ioremap_coherent(phys_addr_t addr, unsigned long size)
|
|
{
|
|
pgprot_t prot = pgprot_cached(PAGE_KERNEL);
|
|
|
|
return __ioremap_caller(addr, size, prot, __builtin_return_address(0));
|
|
}
|
|
EXPORT_SYMBOL(ioremap_coherent);
|
|
|
|
void __iomem *
|
|
ioremap_prot(phys_addr_t addr, unsigned long size, unsigned long flags)
|
|
{
|
|
pte_t pte = __pte(flags);
|
|
|
|
/* writeable implies dirty for kernel addresses */
|
|
if (pte_write(pte))
|
|
pte = pte_mkdirty(pte);
|
|
|
|
/* we don't want to let _PAGE_USER and _PAGE_EXEC leak out */
|
|
pte = pte_exprotect(pte);
|
|
pte = pte_mkprivileged(pte);
|
|
|
|
return __ioremap_caller(addr, size, pte_pgprot(pte), __builtin_return_address(0));
|
|
}
|
|
EXPORT_SYMBOL(ioremap_prot);
|
|
|
|
void __iomem *
|
|
__ioremap(phys_addr_t addr, unsigned long size, unsigned long flags)
|
|
{
|
|
return __ioremap_caller(addr, size, __pgprot(flags), __builtin_return_address(0));
|
|
}
|
|
|
|
void __iomem *
|
|
__ioremap_caller(phys_addr_t addr, unsigned long size, pgprot_t prot, void *caller)
|
|
{
|
|
unsigned long v, i;
|
|
phys_addr_t p;
|
|
int err;
|
|
|
|
/*
|
|
* Choose an address to map it to.
|
|
* Once the vmalloc system is running, we use it.
|
|
* Before then, we use space going down from IOREMAP_TOP
|
|
* (ioremap_bot records where we're up to).
|
|
*/
|
|
p = addr & PAGE_MASK;
|
|
size = PAGE_ALIGN(addr + size) - p;
|
|
|
|
/*
|
|
* If the address lies within the first 16 MB, assume it's in ISA
|
|
* memory space
|
|
*/
|
|
if (p < 16*1024*1024)
|
|
p += _ISA_MEM_BASE;
|
|
|
|
#ifndef CONFIG_CRASH_DUMP
|
|
/*
|
|
* Don't allow anybody to remap normal RAM that we're using.
|
|
* mem_init() sets high_memory so only do the check after that.
|
|
*/
|
|
if (slab_is_available() && p <= virt_to_phys(high_memory - 1) &&
|
|
page_is_ram(__phys_to_pfn(p))) {
|
|
printk("__ioremap(): phys addr 0x%llx is RAM lr %ps\n",
|
|
(unsigned long long)p, __builtin_return_address(0));
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
if (size == 0)
|
|
return NULL;
|
|
|
|
/*
|
|
* Is it already mapped? Perhaps overlapped by a previous
|
|
* mapping.
|
|
*/
|
|
v = p_block_mapped(p);
|
|
if (v)
|
|
goto out;
|
|
|
|
if (slab_is_available()) {
|
|
struct vm_struct *area;
|
|
area = get_vm_area_caller(size, VM_IOREMAP, caller);
|
|
if (area == 0)
|
|
return NULL;
|
|
area->phys_addr = p;
|
|
v = (unsigned long) area->addr;
|
|
} else {
|
|
v = (ioremap_bot -= size);
|
|
}
|
|
|
|
/*
|
|
* Should check if it is a candidate for a BAT mapping
|
|
*/
|
|
|
|
err = 0;
|
|
for (i = 0; i < size && err == 0; i += PAGE_SIZE)
|
|
err = map_kernel_page(v + i, p + i, prot);
|
|
if (err) {
|
|
if (slab_is_available())
|
|
vunmap((void *)v);
|
|
return NULL;
|
|
}
|
|
|
|
out:
|
|
return (void __iomem *) (v + ((unsigned long)addr & ~PAGE_MASK));
|
|
}
|
|
EXPORT_SYMBOL(__ioremap);
|
|
|
|
void iounmap(volatile void __iomem *addr)
|
|
{
|
|
/*
|
|
* If mapped by BATs then there is nothing to do.
|
|
* Calling vfree() generates a benign warning.
|
|
*/
|
|
if (v_block_mapped((unsigned long)addr))
|
|
return;
|
|
|
|
if (addr > high_memory && (unsigned long) addr < ioremap_bot)
|
|
vunmap((void *) (PAGE_MASK & (unsigned long)addr));
|
|
}
|
|
EXPORT_SYMBOL(iounmap);
|
|
|
|
int map_kernel_page(unsigned long va, phys_addr_t pa, pgprot_t prot)
|
|
{
|
|
pmd_t *pd;
|
|
pte_t *pg;
|
|
int err = -ENOMEM;
|
|
|
|
/* Use upper 10 bits of VA to index the first level map */
|
|
pd = pmd_offset(pud_offset(pgd_offset_k(va), va), va);
|
|
/* Use middle 10 bits of VA to index the second-level map */
|
|
pg = pte_alloc_kernel(pd, va);
|
|
if (pg != 0) {
|
|
err = 0;
|
|
/* The PTE should never be already set nor present in the
|
|
* hash table
|
|
*/
|
|
BUG_ON((pte_present(*pg) | pte_hashpte(*pg)) && pgprot_val(prot));
|
|
set_pte_at(&init_mm, va, pg, pfn_pte(pa >> PAGE_SHIFT, prot));
|
|
}
|
|
smp_wmb();
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Map in a chunk of physical memory starting at start.
|
|
*/
|
|
static void __init __mapin_ram_chunk(unsigned long offset, unsigned long top)
|
|
{
|
|
unsigned long v, s;
|
|
phys_addr_t p;
|
|
int ktext;
|
|
|
|
s = offset;
|
|
v = PAGE_OFFSET + s;
|
|
p = memstart_addr + s;
|
|
for (; s < top; s += PAGE_SIZE) {
|
|
ktext = ((char *)v >= _stext && (char *)v < etext) ||
|
|
((char *)v >= _sinittext && (char *)v < _einittext);
|
|
map_kernel_page(v, p, ktext ? PAGE_KERNEL_TEXT : PAGE_KERNEL);
|
|
#ifdef CONFIG_PPC_BOOK3S_32
|
|
if (ktext)
|
|
hash_preload(&init_mm, v, false, 0x300);
|
|
#endif
|
|
v += PAGE_SIZE;
|
|
p += PAGE_SIZE;
|
|
}
|
|
}
|
|
|
|
void __init mapin_ram(void)
|
|
{
|
|
unsigned long s, top;
|
|
|
|
#ifndef CONFIG_WII
|
|
top = total_lowmem;
|
|
s = mmu_mapin_ram(top);
|
|
__mapin_ram_chunk(s, top);
|
|
#else
|
|
if (!wii_hole_size) {
|
|
s = mmu_mapin_ram(total_lowmem);
|
|
__mapin_ram_chunk(s, total_lowmem);
|
|
} else {
|
|
top = wii_hole_start;
|
|
s = mmu_mapin_ram(top);
|
|
__mapin_ram_chunk(s, top);
|
|
|
|
top = memblock_end_of_DRAM();
|
|
s = wii_mmu_mapin_mem2(top);
|
|
__mapin_ram_chunk(s, top);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Scan the real Linux page tables and return a PTE pointer for
|
|
* a virtual address in a context.
|
|
* Returns true (1) if PTE was found, zero otherwise. The pointer to
|
|
* the PTE pointer is unmodified if PTE is not found.
|
|
*/
|
|
static int
|
|
get_pteptr(struct mm_struct *mm, unsigned long addr, pte_t **ptep, pmd_t **pmdp)
|
|
{
|
|
pgd_t *pgd;
|
|
pud_t *pud;
|
|
pmd_t *pmd;
|
|
pte_t *pte;
|
|
int retval = 0;
|
|
|
|
pgd = pgd_offset(mm, addr & PAGE_MASK);
|
|
if (pgd) {
|
|
pud = pud_offset(pgd, addr & PAGE_MASK);
|
|
if (pud && pud_present(*pud)) {
|
|
pmd = pmd_offset(pud, addr & PAGE_MASK);
|
|
if (pmd_present(*pmd)) {
|
|
pte = pte_offset_map(pmd, addr & PAGE_MASK);
|
|
if (pte) {
|
|
retval = 1;
|
|
*ptep = pte;
|
|
if (pmdp)
|
|
*pmdp = pmd;
|
|
/* XXX caller needs to do pte_unmap, yuck */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return(retval);
|
|
}
|
|
|
|
static int __change_page_attr_noflush(struct page *page, pgprot_t prot)
|
|
{
|
|
pte_t *kpte;
|
|
pmd_t *kpmd;
|
|
unsigned long address;
|
|
|
|
BUG_ON(PageHighMem(page));
|
|
address = (unsigned long)page_address(page);
|
|
|
|
if (v_block_mapped(address))
|
|
return 0;
|
|
if (!get_pteptr(&init_mm, address, &kpte, &kpmd))
|
|
return -EINVAL;
|
|
__set_pte_at(&init_mm, address, kpte, mk_pte(page, prot), 0);
|
|
pte_unmap(kpte);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Change the page attributes of an page in the linear mapping.
|
|
*
|
|
* THIS DOES NOTHING WITH BAT MAPPINGS, DEBUG USE ONLY
|
|
*/
|
|
static int change_page_attr(struct page *page, int numpages, pgprot_t prot)
|
|
{
|
|
int i, err = 0;
|
|
unsigned long flags;
|
|
struct page *start = page;
|
|
|
|
local_irq_save(flags);
|
|
for (i = 0; i < numpages; i++, page++) {
|
|
err = __change_page_attr_noflush(page, prot);
|
|
if (err)
|
|
break;
|
|
}
|
|
wmb();
|
|
local_irq_restore(flags);
|
|
flush_tlb_kernel_range((unsigned long)page_address(start),
|
|
(unsigned long)page_address(page));
|
|
return err;
|
|
}
|
|
|
|
void mark_initmem_nx(void)
|
|
{
|
|
struct page *page = virt_to_page(_sinittext);
|
|
unsigned long numpages = PFN_UP((unsigned long)_einittext) -
|
|
PFN_DOWN((unsigned long)_sinittext);
|
|
|
|
change_page_attr(page, numpages, PAGE_KERNEL);
|
|
}
|
|
|
|
#ifdef CONFIG_STRICT_KERNEL_RWX
|
|
void mark_rodata_ro(void)
|
|
{
|
|
struct page *page;
|
|
unsigned long numpages;
|
|
|
|
page = virt_to_page(_stext);
|
|
numpages = PFN_UP((unsigned long)_etext) -
|
|
PFN_DOWN((unsigned long)_stext);
|
|
|
|
change_page_attr(page, numpages, PAGE_KERNEL_ROX);
|
|
/*
|
|
* mark .rodata as read only. Use __init_begin rather than __end_rodata
|
|
* to cover NOTES and EXCEPTION_TABLE.
|
|
*/
|
|
page = virt_to_page(__start_rodata);
|
|
numpages = PFN_UP((unsigned long)__init_begin) -
|
|
PFN_DOWN((unsigned long)__start_rodata);
|
|
|
|
change_page_attr(page, numpages, PAGE_KERNEL_RO);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEBUG_PAGEALLOC
|
|
void __kernel_map_pages(struct page *page, int numpages, int enable)
|
|
{
|
|
if (PageHighMem(page))
|
|
return;
|
|
|
|
change_page_attr(page, numpages, enable ? PAGE_KERNEL : __pgprot(0));
|
|
}
|
|
#endif /* CONFIG_DEBUG_PAGEALLOC */
|