2005-09-26 08:04:21 +02:00
|
|
|
/*
|
2005-11-14 01:06:30 +01:00
|
|
|
* Modifications by Kumar Gala (galak@kernel.crashing.org) to support
|
2005-09-26 08:04:21 +02:00
|
|
|
* E500 Book E processors.
|
|
|
|
*
|
2010-05-13 21:38:21 +02:00
|
|
|
* Copyright 2004,2010 Freescale Semiconductor, Inc.
|
2005-09-26 08:04:21 +02:00
|
|
|
*
|
|
|
|
* This file contains the routines for initializing the MMU
|
|
|
|
* on the 4xx series of chips.
|
|
|
|
* -- 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/signal.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/ptrace.h>
|
|
|
|
#include <linux/mman.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/swap.h>
|
|
|
|
#include <linux/stddef.h>
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/highmem.h>
|
2010-07-07 00:39:01 +02:00
|
|
|
#include <linux/memblock.h>
|
2005-09-26 08:04:21 +02:00
|
|
|
|
|
|
|
#include <asm/pgalloc.h>
|
|
|
|
#include <asm/prom.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/mmu_context.h>
|
|
|
|
#include <asm/pgtable.h>
|
|
|
|
#include <asm/mmu.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/smp.h>
|
|
|
|
#include <asm/machdep.h>
|
|
|
|
#include <asm/setup.h>
|
|
|
|
|
2008-04-15 21:52:21 +02:00
|
|
|
#include "mmu_decl.h"
|
|
|
|
|
2005-09-26 08:04:21 +02:00
|
|
|
unsigned int tlbcam_index;
|
|
|
|
|
2010-05-13 21:38:21 +02:00
|
|
|
#define NUM_TLBCAMS (64)
|
|
|
|
struct tlbcam TLBCAM[NUM_TLBCAMS];
|
2005-09-26 08:04:21 +02:00
|
|
|
|
|
|
|
struct tlbcamrange {
|
2009-10-15 19:49:01 +02:00
|
|
|
unsigned long start;
|
2005-09-26 08:04:21 +02:00
|
|
|
unsigned long limit;
|
|
|
|
phys_addr_t phys;
|
|
|
|
} tlbcam_addrs[NUM_TLBCAMS];
|
|
|
|
|
|
|
|
extern unsigned int tlbcam_index;
|
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
unsigned long tlbcam_sz(int idx)
|
|
|
|
{
|
|
|
|
return tlbcam_addrs[idx].limit - tlbcam_addrs[idx].start + 1;
|
|
|
|
}
|
|
|
|
|
2005-09-26 08:04:21 +02:00
|
|
|
/*
|
|
|
|
* Return PA for this VA if it is mapped by a CAM, or 0
|
|
|
|
*/
|
2009-02-10 04:08:07 +01:00
|
|
|
phys_addr_t v_mapped_by_tlbcam(unsigned long va)
|
2005-09-26 08:04:21 +02:00
|
|
|
{
|
|
|
|
int b;
|
|
|
|
for (b = 0; b < tlbcam_index; ++b)
|
|
|
|
if (va >= tlbcam_addrs[b].start && va < tlbcam_addrs[b].limit)
|
|
|
|
return tlbcam_addrs[b].phys + (va - tlbcam_addrs[b].start);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return VA for a given PA or 0 if not mapped
|
|
|
|
*/
|
2009-02-10 04:08:07 +01:00
|
|
|
unsigned long p_mapped_by_tlbcam(phys_addr_t pa)
|
2005-09-26 08:04:21 +02:00
|
|
|
{
|
|
|
|
int b;
|
|
|
|
for (b = 0; b < tlbcam_index; ++b)
|
|
|
|
if (pa >= tlbcam_addrs[b].phys
|
2009-10-15 19:49:01 +02:00
|
|
|
&& pa < (tlbcam_addrs[b].limit-tlbcam_addrs[b].start)
|
2005-09-26 08:04:21 +02:00
|
|
|
+tlbcam_addrs[b].phys)
|
|
|
|
return tlbcam_addrs[b].start+(pa-tlbcam_addrs[b].phys);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2010-06-30 12:23:31 +02:00
|
|
|
* Set up a variable-size TLB entry (tlbcam). The parameters are not checked;
|
|
|
|
* in particular size must be a power of 4 between 4k and 256M (or 1G, for cpus
|
|
|
|
* that support extended page sizes). Note that while some cpus support a
|
|
|
|
* page size of 4G, we don't allow its use here.
|
2005-09-26 08:04:21 +02:00
|
|
|
*/
|
2009-10-15 19:49:01 +02:00
|
|
|
static void settlbcam(int index, unsigned long virt, phys_addr_t phys,
|
|
|
|
unsigned long size, unsigned long flags, unsigned int pid)
|
2005-09-26 08:04:21 +02:00
|
|
|
{
|
|
|
|
unsigned int tsize, lz;
|
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
asm (PPC_CNTLZL "%0,%1" : "=r" (lz) : "r" (size));
|
2009-02-11 01:10:50 +01:00
|
|
|
tsize = 21 - lz;
|
2005-09-26 08:04:21 +02:00
|
|
|
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
if ((flags & _PAGE_NO_CACHE) == 0)
|
|
|
|
flags |= _PAGE_COHERENT;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
TLBCAM[index].MAS0 = MAS0_TLBSEL(1) | MAS0_ESEL(index) | MAS0_NV(index+1);
|
|
|
|
TLBCAM[index].MAS1 = MAS1_VALID | MAS1_IPROT | MAS1_TSIZE(tsize) | MAS1_TID(pid);
|
|
|
|
TLBCAM[index].MAS2 = virt & PAGE_MASK;
|
|
|
|
|
|
|
|
TLBCAM[index].MAS2 |= (flags & _PAGE_WRITETHRU) ? MAS2_W : 0;
|
|
|
|
TLBCAM[index].MAS2 |= (flags & _PAGE_NO_CACHE) ? MAS2_I : 0;
|
|
|
|
TLBCAM[index].MAS2 |= (flags & _PAGE_COHERENT) ? MAS2_M : 0;
|
|
|
|
TLBCAM[index].MAS2 |= (flags & _PAGE_GUARDED) ? MAS2_G : 0;
|
|
|
|
TLBCAM[index].MAS2 |= (flags & _PAGE_ENDIAN) ? MAS2_E : 0;
|
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
TLBCAM[index].MAS3 = (phys & MAS3_RPN) | MAS3_SX | MAS3_SR;
|
2005-09-26 08:04:21 +02:00
|
|
|
TLBCAM[index].MAS3 |= ((flags & _PAGE_RW) ? MAS3_SW : 0);
|
2010-04-12 18:21:50 +02:00
|
|
|
if (mmu_has_feature(MMU_FTR_BIG_PHYS))
|
2009-10-15 19:49:01 +02:00
|
|
|
TLBCAM[index].MAS7 = (u64)phys >> 32;
|
2005-09-26 08:04:21 +02:00
|
|
|
|
powerpc: Fix invalid page flags in create TLB CAM path for PTE_64BIT
There exists a four line chunk of code, which when configured for
64 bit address space, can incorrectly set certain page flags during
the TLB creation. It turns out that this is code which isn't used,
but might still serve a purpose. Since it isn't obvious why it exists
or why it causes problems, the below description covers both in detail.
For powerpc bootstrap, the physical memory (at most 768M), is mapped
into the kernel space via the following path:
MMU_init()
|
+ adjust_total_lowmem()
|
+ map_mem_in_cams()
|
+ settlbcam(i, virt, phys, cam_sz, PAGE_KERNEL_X, 0);
On settlbcam(), the kernel will create TLB entries according to the flag,
PAGE_KERNEL_X.
settlbcam()
{
...
TLBCAM[index].MAS1 = MAS1_VALID
| MAS1_IPROT | MAS1_TSIZE(tsize) | MAS1_TID(pid);
^
These entries cannot be invalidated by the
kernel since MAS1_IPROT is set on TLB property.
...
if (flags & _PAGE_USER) {
TLBCAM[index].MAS3 |= MAS3_UX | MAS3_UR;
TLBCAM[index].MAS3 |= ((flags & _PAGE_RW) ? MAS3_UW : 0);
}
For classic BookE (flags & _PAGE_USER) is 'zero' so it's fine.
But on boards like the the Freescale P4080, we want to support 36-bit
physical address on it. So the following options may be set:
CONFIG_FSL_BOOKE=y
CONFIG_PTE_64BIT=y
CONFIG_PHYS_64BIT=y
As a result, boards like the P4080 will introduce PTE format as Book3E.
As per the file: arch/powerpc/include/asm/pgtable-ppc32.h
* #elif defined(CONFIG_FSL_BOOKE) && defined(CONFIG_PTE_64BIT)
* #include <asm/pte-book3e.h>
So PAGE_KERNEL_X is __pgprot(_PAGE_BASE | _PAGE_KERNEL_RWX) and the
book3E version of _PAGE_KERNEL_RWX is defined with:
(_PAGE_BAP_SW | _PAGE_BAP_SR | _PAGE_DIRTY | _PAGE_BAP_SX)
Note the _PAGE_BAP_SR, which is also defined in the book3E _PAGE_USER:
#define _PAGE_USER (_PAGE_BAP_UR | _PAGE_BAP_SR) /* Can be read */
So the possibility exists to wrongly assign the user MAS3_U<RWX> bits
to kernel (PAGE_KERNEL_X) address space via the following code fragment:
if (flags & _PAGE_USER) {
TLBCAM[index].MAS3 |= MAS3_UX | MAS3_UR;
TLBCAM[index].MAS3 |= ((flags & _PAGE_RW) ? MAS3_UW : 0);
}
Here is a dump of the TLB info from Simics with the above code present:
------
L2 TLB1
GT SSS UUU V I
Row Logical Physical SS TLPID TID WIMGE XWR XWR F P V
----- ----------------- ------------------- -- ----- ----- ----- --- --- - - -
0 c0000000-cfffffff 000000000-00fffffff 00 0 0 M XWR XWR 0 1 1
1 d0000000-dfffffff 010000000-01fffffff 00 0 0 M XWR XWR 0 1 1
2 e0000000-efffffff 020000000-02fffffff 00 0 0 M XWR XWR 0 1 1
Actually this conditional code was used for two legacy functions:
1: support KGDB to set break point.
KGDB already dropped this; now uses its core write to set break point.
2: io_block_mapping() to create TLB in segmentation size (not PAGE_SIZE)
for device IO space.
This use case is also removed from the latest PowerPC kernel.
However, there may still be a use case for it in the future, like
large user pages, so we can't remove it entirely. As an alternative,
we match on all bits of _PAGE_USER instead of just any bits, so the
case where just _PAGE_BAP_SR is set can't sneak through.
With this done, the TLB appears without U having XWR as below:
-------
L2 TLB1
GT SSS UUU V I
Row Logical Physical SS TLPID TID WIMGE XWR XWR F P V
----- ----------------- ------------------- -- ----- ----- ----- --- --- - - -
0 c0000000-cfffffff 000000000-00fffffff 00 0 0 M XWR 0 1 1
1 d0000000-dfffffff 010000000-01fffffff 00 0 0 M XWR 0 1 1
2 e0000000-efffffff 020000000-02fffffff 00 0 0 M XWR 0 1 1
Signed-off-by: Tiejun Chen <tiejun.chen@windriver.com>
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
2010-09-24 18:44:52 +02:00
|
|
|
/* Below is unlikely -- only for large user pages or similar */
|
|
|
|
if (pte_user(flags)) {
|
2005-09-26 08:04:21 +02:00
|
|
|
TLBCAM[index].MAS3 |= MAS3_UX | MAS3_UR;
|
|
|
|
TLBCAM[index].MAS3 |= ((flags & _PAGE_RW) ? MAS3_UW : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
tlbcam_addrs[index].start = virt;
|
|
|
|
tlbcam_addrs[index].limit = virt + size - 1;
|
|
|
|
tlbcam_addrs[index].phys = phys;
|
|
|
|
|
|
|
|
loadcam_entry(index);
|
|
|
|
}
|
|
|
|
|
2011-09-16 17:39:59 +02:00
|
|
|
unsigned long calc_cam_sz(unsigned long ram, unsigned long virt,
|
|
|
|
phys_addr_t phys)
|
|
|
|
{
|
|
|
|
unsigned int camsize = __ilog2(ram) & ~1U;
|
|
|
|
unsigned int align = __ffs(virt | phys) & ~1U;
|
|
|
|
unsigned long max_cam = (mfspr(SPRN_TLB1CFG) >> 16) & 0xf;
|
|
|
|
|
|
|
|
/* Convert (4^max) kB to (2^max) bytes */
|
|
|
|
max_cam = max_cam * 2 + 10;
|
|
|
|
|
|
|
|
if (camsize > align)
|
|
|
|
camsize = align;
|
|
|
|
if (camsize > max_cam)
|
|
|
|
camsize = max_cam;
|
|
|
|
|
|
|
|
return 1UL << camsize;
|
|
|
|
}
|
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
unsigned long map_mem_in_cams(unsigned long ram, int max_cam_idx)
|
2005-09-26 08:04:21 +02:00
|
|
|
{
|
2009-10-15 19:49:01 +02:00
|
|
|
int i;
|
2008-12-09 04:34:57 +01:00
|
|
|
unsigned long virt = PAGE_OFFSET;
|
|
|
|
phys_addr_t phys = memstart_addr;
|
2009-10-15 19:49:01 +02:00
|
|
|
unsigned long amount_mapped = 0;
|
2008-12-09 04:34:57 +01:00
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
/* Calculate CAM values */
|
|
|
|
for (i = 0; ram && i < max_cam_idx; i++) {
|
|
|
|
unsigned long cam_sz;
|
|
|
|
|
2011-09-16 17:39:59 +02:00
|
|
|
cam_sz = calc_cam_sz(ram, virt, phys);
|
2009-10-15 19:49:01 +02:00
|
|
|
settlbcam(i, virt, phys, cam_sz, PAGE_KERNEL_X, 0);
|
|
|
|
|
|
|
|
ram -= cam_sz;
|
|
|
|
amount_mapped += cam_sz;
|
|
|
|
virt += cam_sz;
|
|
|
|
phys += cam_sz;
|
2005-09-26 08:04:21 +02:00
|
|
|
}
|
2009-10-15 19:49:01 +02:00
|
|
|
tlbcam_index = i;
|
|
|
|
|
|
|
|
return amount_mapped;
|
|
|
|
}
|
2008-12-09 04:34:57 +01:00
|
|
|
|
2009-10-17 01:48:40 +02:00
|
|
|
#ifdef CONFIG_PPC32
|
|
|
|
|
|
|
|
#if defined(CONFIG_LOWMEM_CAM_NUM_BOOL) && (CONFIG_LOWMEM_CAM_NUM >= NUM_TLBCAMS)
|
|
|
|
#error "LOWMEM_CAM_NUM must be less than NUM_TLBCAMS"
|
|
|
|
#endif
|
|
|
|
|
2009-12-16 22:26:53 +01:00
|
|
|
unsigned long __init mmu_mapin_ram(unsigned long top)
|
2009-10-15 19:49:01 +02:00
|
|
|
{
|
|
|
|
return tlbcam_addrs[tlbcam_index - 1].limit - PAGE_OFFSET + 1;
|
2005-09-26 08:04:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MMU_init_hw does the chip-specific initialization of the MMU hardware.
|
|
|
|
*/
|
|
|
|
void __init MMU_init_hw(void)
|
|
|
|
{
|
|
|
|
flush_instruction_cache();
|
|
|
|
}
|
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
void __init adjust_total_lowmem(void)
|
2005-09-26 08:04:21 +02:00
|
|
|
{
|
2009-10-15 19:49:01 +02:00
|
|
|
unsigned long ram;
|
2008-12-09 04:34:57 +01:00
|
|
|
int i;
|
2005-09-26 08:04:21 +02:00
|
|
|
|
2008-12-09 04:34:57 +01:00
|
|
|
/* adjust lowmem size to __max_low_memory */
|
|
|
|
ram = min((phys_addr_t)__max_low_memory, (phys_addr_t)total_lowmem);
|
2005-09-26 08:04:21 +02:00
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
__max_low_memory = map_mem_in_cams(ram, CONFIG_LOWMEM_CAM_NUM);
|
2008-12-09 04:34:59 +01:00
|
|
|
|
2009-10-15 19:49:01 +02:00
|
|
|
pr_info("Memory CAM mapping: ");
|
|
|
|
for (i = 0; i < tlbcam_index - 1; i++)
|
|
|
|
pr_cont("%lu/", tlbcam_sz(i) >> 20);
|
|
|
|
pr_cont("%lu Mb, residual: %dMb\n", tlbcam_sz(tlbcam_index - 1) >> 20,
|
2009-02-12 16:39:23 +01:00
|
|
|
(unsigned int)((total_lowmem - __max_low_memory) >> 20));
|
2009-10-15 19:49:01 +02:00
|
|
|
|
2010-07-07 00:39:01 +02:00
|
|
|
memblock_set_current_limit(memstart_addr + __max_low_memory);
|
2005-09-26 08:04:21 +02:00
|
|
|
}
|
2010-07-07 00:39:02 +02:00
|
|
|
|
|
|
|
void setup_initial_memory_limit(phys_addr_t first_memblock_base,
|
|
|
|
phys_addr_t first_memblock_size)
|
|
|
|
{
|
|
|
|
phys_addr_t limit = first_memblock_base + first_memblock_size;
|
|
|
|
|
|
|
|
/* 64M mapped initially according to head_fsl_booke.S */
|
|
|
|
memblock_set_current_limit(min_t(u64, limit, 0x04000000));
|
|
|
|
}
|
2009-10-17 01:48:40 +02:00
|
|
|
#endif
|