armv7: add new dcache and MMU setup functions
This adds new MMU setup code. Most notably, this version uses cbmem_add() to determine the translation table base address, which in turn is necessary to ensure payloads which wipe memory can tell which regions to wipe out. TODOs: - Finish cleaning up references to old cache/MMU stuff - Add L2 setup (from exynos_cache.c) - Set up ranges dynamically rather than in ramstage's main(). Change-Id: Iba5295a801e8058a3694e4ec5b94bbe9a69d3ee6 Signed-off-by: David Hendricks <dhendrix@chromium.org> Reviewed-on: http://review.coreboot.org/2877 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
This commit is contained in:
		
				
					committed by
					
						 Ronald G. Minnich
						Ronald G. Minnich
					
				
			
			
				
	
			
			
			
						parent
						
							04d352db41
						
					
				
				
					commit
					f9be756b55
				
			| @@ -53,12 +53,14 @@ void main(void) | ||||
| 	armv7_invalidate_caches(); | ||||
|  | ||||
| 	/* | ||||
| 	 * Re-enable caches and branch prediction. MMU will be set up later. | ||||
| 	 * Re-enable icache and branch prediction. MMU and dcache will be | ||||
| 	 * set up later. | ||||
| 	 * | ||||
| 	 * Note: If booting from USB, we need to disable branch prediction | ||||
| 	 * before copying from USB into RAM (FIXME: why?) | ||||
| 	 */ | ||||
| 	sctlr = read_sctlr(); | ||||
| 	sctlr |= SCTLR_C | SCTLR_Z | SCTLR_I; | ||||
| 	sctlr |= SCTLR_Z | SCTLR_I; | ||||
| 	write_sctlr(sctlr); | ||||
|  | ||||
| 	if (boot_cpu()) { | ||||
|   | ||||
| @@ -108,6 +108,32 @@ static inline void tlbiall(void) | ||||
| 	asm volatile ("mcr p15, 0, %0, c8, c7, 0" : : "r" (0)); | ||||
| } | ||||
|  | ||||
| /* write data access control register (DACR) */ | ||||
| static inline void write_dacr(uint32_t val) | ||||
| { | ||||
| 	asm volatile ("mcr p15, 0, %0, c3, c0, 0" : : "r" (val)); | ||||
| } | ||||
|  | ||||
| /* write translation table base register 0 (TTBR0) */ | ||||
| static inline void write_ttbr0(uint32_t val) | ||||
| { | ||||
| 	asm volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r" (val) : "memory"); | ||||
| } | ||||
|  | ||||
| /* read translation table base control register (TTBCR) */ | ||||
| static inline uint32_t read_ttbcr(void) | ||||
| { | ||||
| 	uint32_t val = 0; | ||||
| 	asm volatile ("mrc p15, 0, %0, c2, c0, 2" : "=r" (val)); | ||||
| 	return val; | ||||
| } | ||||
|  | ||||
| /* write translation table base control register (TTBCR) */ | ||||
| static inline void write_ttbcr(uint32_t val) | ||||
| { | ||||
| 	asm volatile ("mcr p15, 0, %0, c2, c0, 2" : : "r" (val) : "memory"); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Low-level cache maintenance operations | ||||
|  */ | ||||
| @@ -224,6 +250,12 @@ void dcache_clean_invalidate_by_mva(unsigned long addr, unsigned long len); | ||||
| /* dcache invalidate all (on current level given by CCSELR) */ | ||||
| void dcache_invalidate_all(void); | ||||
|  | ||||
| /* dcache and MMU disable */ | ||||
| void dcache_mmu_disable(void); | ||||
|  | ||||
| /* dcache and MMU enable */ | ||||
| void dcache_mmu_enable(void); | ||||
|  | ||||
| /* icache invalidate all (on current level given by CSSELR) */ | ||||
| void icache_invalidate_all(void); | ||||
|  | ||||
| @@ -237,7 +269,17 @@ void tlb_invalidate_all(void); | ||||
| /* invalidate all caches on ARMv7 */ | ||||
| void armv7_invalidate_caches(void); | ||||
|  | ||||
| /* MMU setup by modified virtual address */ | ||||
| void mmu_setup_by_mva(unsigned long start, unsigned long size); | ||||
| /* mmu initialization (set page table address, set permissions, etc) */ | ||||
| void mmu_init(void); | ||||
|  | ||||
| enum dcache_policy { | ||||
| 	DCACHE_OFF, | ||||
| 	DCACHE_WRITEBACK, | ||||
| 	DCACHE_WRITETHROUGH, | ||||
| }; | ||||
|  | ||||
| /* mmu range configuration (set dcache policy) */ | ||||
| void mmu_config_range(unsigned long start_mb, unsigned long size_mb, | ||||
| 						enum dcache_policy policy); | ||||
|  | ||||
| #endif /* ARMV7_CACHE_H */ | ||||
|   | ||||
| @@ -2,37 +2,6 @@ | ||||
| #ifndef SYSTEM_H_ | ||||
| #define SYSTEM_H_ | ||||
|  | ||||
| /* | ||||
|  * CR1 bits (CP#15 CR1) | ||||
|  */ | ||||
| #define CR_M	(1 << 0)	/* MMU enable				*/ | ||||
| #define CR_A	(1 << 1)	/* Alignment abort enable		*/ | ||||
| #define CR_C	(1 << 2)	/* Dcache enable			*/ | ||||
| #define CR_W	(1 << 3)	/* Write buffer enable			*/ | ||||
| #define CR_P	(1 << 4)	/* 32-bit exception handler		*/ | ||||
| #define CR_D	(1 << 5)	/* 32-bit data address range		*/ | ||||
| #define CR_L	(1 << 6)	/* Implementation defined		*/ | ||||
| #define CR_B	(1 << 7)	/* Big endian				*/ | ||||
| #define CR_S	(1 << 8)	/* System MMU protection		*/ | ||||
| #define CR_R	(1 << 9)	/* ROM MMU protection			*/ | ||||
| #define CR_F	(1 << 10)	/* Implementation defined		*/ | ||||
| #define CR_Z	(1 << 11)	/* Implementation defined		*/ | ||||
| #define CR_I	(1 << 12)	/* Icache enable			*/ | ||||
| #define CR_V	(1 << 13)	/* Vectors relocated to 0xffff0000	*/ | ||||
| #define CR_RR	(1 << 14)	/* Round Robin cache replacement	*/ | ||||
| #define CR_L4	(1 << 15)	/* LDR pc can set T bit			*/ | ||||
| #define CR_DT	(1 << 16) | ||||
| #define CR_IT	(1 << 18) | ||||
| #define CR_ST	(1 << 19) | ||||
| #define CR_FI	(1 << 21)	/* Fast interrupt (lower latency mode)	*/ | ||||
| #define CR_U	(1 << 22)	/* Unaligned access operation		*/ | ||||
| #define CR_XP	(1 << 23)	/* Extended page tables			*/ | ||||
| #define CR_VE	(1 << 24)	/* Vectored interrupts			*/ | ||||
| #define CR_EE	(1 << 25)	/* Exception (Big) Endian		*/ | ||||
| #define CR_TRE	(1 << 28)	/* TEX remap enable			*/ | ||||
| #define CR_AFE	(1 << 29)	/* Access flag enable			*/ | ||||
| #define CR_TE	(1 << 30)	/* Thumb exception enable		*/ | ||||
|  | ||||
| /* | ||||
|  * This is used to ensure the compiler did actually allocate the register we | ||||
|  * asked it for some inline assembly sequences.  Apparently we can't trust | ||||
| @@ -48,58 +17,6 @@ | ||||
| #define arch_align_stack(x) (x) | ||||
|  | ||||
| #ifndef __ASSEMBLER__ | ||||
| #include <arch/cache.h>	/* for isb() */ | ||||
| static inline unsigned int get_cr(void) | ||||
| { | ||||
| 	unsigned int val; | ||||
| 	asm("mrc p15, 0, %0, c1, c0, 0	@ get CR" : "=r" (val) : : "cc"); | ||||
| 	return val; | ||||
| } | ||||
|  | ||||
| static inline void set_cr(unsigned int val) | ||||
| { | ||||
| 	asm volatile("mcr p15, 0, %0, c1, c0, 0	@ set CR" | ||||
| 	  : : "r" (val) : "cc"); | ||||
| 	isb(); | ||||
| } | ||||
|  | ||||
| /* options available for data cache on each page */ | ||||
| enum dcache_option { | ||||
| 	DCACHE_OFF, | ||||
| 	DCACHE_WRITETHROUGH, | ||||
| 	DCACHE_WRITEBACK, | ||||
| }; | ||||
|  | ||||
| /* Size of an MMU section */ | ||||
| enum { | ||||
| 	MMU_SECTION_SHIFT	= 20, | ||||
| 	MMU_SECTION_SIZE	= 1 << MMU_SECTION_SHIFT, | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Change the cache settings for a region. | ||||
|  * | ||||
|  * \param start		start address of memory region to change | ||||
|  * \param size		size of memory region to change | ||||
|  * \param option	dcache option to select | ||||
|  */ | ||||
| void mmu_set_region_dcache(unsigned long start, int size, | ||||
| 			enum dcache_option option); | ||||
|  | ||||
| /** | ||||
|  * Register an update to the page tables, and flush the TLB | ||||
|  * | ||||
|  * \param start		start address of update in page table | ||||
|  * \param stop		stop address of update in page table | ||||
|  */ | ||||
| void mmu_page_table_flush(unsigned long start, unsigned long stop); | ||||
|  | ||||
| void mmu_setup(unsigned long start, unsigned long size); | ||||
|  | ||||
| void v7_inval_tlb(void); | ||||
|  | ||||
| void arm_init_before_mmu(void); | ||||
|  | ||||
|  /* | ||||
|   * FIXME: sdelay originally came from arch/arm/cpu/armv7/exynos5/setup.h in | ||||
|   * u-boot but does not seem specific to exynos5... | ||||
|   | ||||
| @@ -3,12 +3,8 @@ | ||||
| bootblock-y += syslib.c | ||||
| bootblock-$(CONFIG_EARLY_CONSOLE) += early_console.c | ||||
| bootblock-y += cache.c | ||||
| bootblock-y += cache_v7.c | ||||
| bootblock-y += cache-cp15.c | ||||
|  | ||||
| romstage-y += cache.c | ||||
| romstage-y += cache_v7.c | ||||
| romstage-y += cache-cp15.c | ||||
| romstage-y += div0.c | ||||
| romstage-y += syslib.c | ||||
| romstage-$(CONFIG_EARLY_CONSOLE) += early_console.c | ||||
| @@ -19,8 +15,7 @@ ramstage-y += div0.c | ||||
| #ramstage-y += memset.S | ||||
| ramstage-y += syslib.c | ||||
| ramstage-y += cache.c | ||||
| ramstage-y += cache_v7.c | ||||
| ramstage-y += cache-cp15.c | ||||
| ramstage-y += mmu.c | ||||
|  | ||||
| #FIXME(dhendrix): should this be a config option? | ||||
| romstage-y += eabi_compat.c | ||||
|   | ||||
| @@ -1,221 +0,0 @@ | ||||
| /* | ||||
|  * (C) Copyright 2002 | ||||
|  * Wolfgang Denk, DENX Software Engineering, wd@denx.de. | ||||
|  * | ||||
|  * See file CREDITS for list of people who contributed to this | ||||
|  * project. | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | ||||
|  * MA 02111-1307 USA | ||||
|  */ | ||||
|  | ||||
| #include <common.h> | ||||
| #include <stdlib.h> | ||||
| #include <system.h> | ||||
|  | ||||
| static unsigned int tlb_addr; | ||||
|  | ||||
| static void cp_delay (void) | ||||
| { | ||||
| 	volatile int i; | ||||
|  | ||||
| 	/* copro seems to need some delay between reading and writing */ | ||||
| 	for (i = 0; i < 100; i++) | ||||
| 		nop(); | ||||
| 	asm volatile("" : : : "memory"); | ||||
| } | ||||
|  | ||||
| static void set_section_dcache(int section, enum dcache_option option) | ||||
| { | ||||
| 	u32 value = section << MMU_SECTION_SHIFT | (3 << 10); | ||||
| 	u32 *page_table; | ||||
| 	unsigned int tlb_size = 4096 * 4; | ||||
|  | ||||
| 	/* | ||||
| 	 * FIXME(dhendrix): This calculation is from arch/arm/lib/board.c | ||||
| 	 * in u-boot. We may need to subtract more due to logging. | ||||
| 	 * FIXME(rminnich) | ||||
| 	 * The cast avoids an incorrect overflow diagnostic. | ||||
| 	 * We really need to start adding ULL to constants that are | ||||
| 	 * intrinsically unsigned. | ||||
| 	 */ | ||||
| 	tlb_addr = ((u32)CONFIG_SYS_SDRAM_BASE + (CONFIG_DRAM_SIZE_MB << 20UL)); | ||||
| 	tlb_addr -= tlb_size; | ||||
| 	/* round down to next 64KB limit */ | ||||
| 	tlb_addr &= ~(0x10000 - 1); | ||||
| 	page_table = (u32 *)tlb_addr; | ||||
|  | ||||
| 	switch (option) { | ||||
| 	case DCACHE_WRITETHROUGH: | ||||
| 		value |= 0x1a; | ||||
| 		break; | ||||
|  | ||||
| 	case DCACHE_WRITEBACK: | ||||
| 		value |= 0x1e; | ||||
| 		break; | ||||
|  | ||||
| 	case DCACHE_OFF: | ||||
| 		value |= 0x12; | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	page_table[section] = value; | ||||
| } | ||||
|  | ||||
| #if 0 | ||||
| void __mmu_page_table_flush(unsigned long start, unsigned long stop) | ||||
| { | ||||
| 	debug("%s: Warning: not implemented\n", __func__); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void mmu_set_region_dcache(unsigned long start, int size, enum dcache_option option) | ||||
| { | ||||
| 	u32 *page_table = &tlb_addr; | ||||
| 	u32 upto, end; | ||||
|  | ||||
| 	end = ALIGN(start + size, MMU_SECTION_SIZE) >> MMU_SECTION_SHIFT; | ||||
| 	start = start >> MMU_SECTION_SHIFT; | ||||
| 	debug("mmu_set_region_dcache start=%x, size=%x, option=%d\n", | ||||
| 	      start, size, option); | ||||
| 	for (upto = start; upto < end; upto++) | ||||
| 		set_section_dcache(upto, option); | ||||
| 	mmu_page_table_flush((u32)&page_table[start], (u32)&page_table[end]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * dram_bank_mmu_set - set up the data cache policy for a given dram bank | ||||
|  * | ||||
|  * @start:	virtual address start of bank | ||||
|  * @size:	size of bank (in bytes) | ||||
|  */ | ||||
| static inline void dram_bank_mmu_setup(unsigned long start, unsigned long size) | ||||
| { | ||||
| 	int	i; | ||||
|  | ||||
| 	debug("%s: bank: %d\n", __func__, bank); | ||||
| 	for (i = start >> 20; i < (start + size) >> 20; i++) { | ||||
| #if defined(CONFIG_ARM_DCACHE_POLICY_WRITEBACK) | ||||
| 		set_section_dcache(i, DCACHE_WRITEBACK); | ||||
| #elif defined(CONFIG_ARM_DCACHE_POLICY_WRITETHROUGH) | ||||
| 		set_section_dcache(i, DCACHE_WRITETHROUGH); | ||||
| #else | ||||
| #error "Must define dcache policy." | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* to activate the MMU we need to set up virtual memory: use 1M areas */ | ||||
| inline void mmu_setup(unsigned long start, unsigned long size_mb) | ||||
| { | ||||
| 	int i; | ||||
| 	u32 reg; | ||||
|  | ||||
| //	arm_init_before_mmu(); | ||||
| 	/* Set up an identity-mapping for all 4GB, rw for everyone */ | ||||
| 	for (i = 0; i < 4096; i++) | ||||
| 		set_section_dcache(i, DCACHE_OFF); | ||||
|  | ||||
| 	dram_bank_mmu_setup(start, size_mb << 20); | ||||
|  | ||||
| 	/* Copy the page table address to cp15 */ | ||||
| 	asm volatile("mcr p15, 0, %0, c2, c0, 0" | ||||
| 		     : : "r" (tlb_addr) : "memory"); | ||||
| 	/* Set the access control to all-supervisor */ | ||||
| 	asm volatile("mcr p15, 0, %0, c3, c0, 0" | ||||
| 		     : : "r" (~0)); | ||||
| 	/* and enable the mmu */ | ||||
| 	reg = get_cr();	/* get control reg. */ | ||||
| 	cp_delay(); | ||||
| 	set_cr(reg | CR_M); | ||||
| } | ||||
|  | ||||
| static int mmu_enabled(void) | ||||
| { | ||||
| 	return get_cr() & CR_M; | ||||
| } | ||||
|  | ||||
| /* cache_bit must be either CR_I or CR_C */ | ||||
| static void cache_enable(unsigned long start, unsigned long size, uint32_t cache_bit) | ||||
| { | ||||
| 	uint32_t reg; | ||||
|  | ||||
| 	/* The data cache is not active unless the mmu is enabled too */ | ||||
| 	if ((cache_bit == CR_C) && !mmu_enabled()) | ||||
| 		mmu_setup(start, size); | ||||
| 	reg = get_cr();	/* get control reg. */ | ||||
| 	cp_delay(); | ||||
| 	set_cr(reg | cache_bit); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Big hack warning! | ||||
|  * | ||||
|  * Devs like to compile with -O0 to get a nice debugging illusion. But this | ||||
|  * function does not survive that since -O0 causes the compiler to read the | ||||
|  * PC back from the stack after the dcache flush. Might it be possible to fix | ||||
|  * this by flushing the write buffer? | ||||
|  */ | ||||
| static void cache_disable(uint32_t cache_bit) __attribute__ ((optimize(2))); | ||||
|  | ||||
| /* cache_bit must be either CR_I or CR_C */ | ||||
| static void cache_disable(uint32_t cache_bit) | ||||
| { | ||||
| 	uint32_t reg; | ||||
|  | ||||
| 	if (cache_bit == CR_C) { | ||||
| 		/* if cache isn;t enabled no need to disable */ | ||||
| 		reg = get_cr(); | ||||
| 		if ((reg & CR_C) != CR_C) | ||||
| 			return; | ||||
| 		/* if disabling data cache, disable mmu too */ | ||||
| 		cache_bit |= CR_M; | ||||
| 	} | ||||
| 	reg = get_cr(); | ||||
| 	cp_delay(); | ||||
| 	if (cache_bit == (CR_C | CR_M)) | ||||
| 		flush_dcache_all(); | ||||
| 	set_cr(reg & ~cache_bit); | ||||
| } | ||||
|  | ||||
| void icache_enable(unsigned long start, unsigned long size) | ||||
| { | ||||
| 	cache_enable(start, size, CR_I); | ||||
| } | ||||
|  | ||||
| void icache_disable(void) | ||||
| { | ||||
| 	cache_disable(CR_I); | ||||
| } | ||||
|  | ||||
| int icache_status(void) | ||||
| { | ||||
| 	return (get_cr() & CR_I) != 0; | ||||
| } | ||||
|  | ||||
| void dcache_enable(unsigned long start, unsigned long size) | ||||
| { | ||||
| 	cache_enable(start, size, CR_C); | ||||
| } | ||||
|  | ||||
| void dcache_disable(void) | ||||
| { | ||||
| 	cache_disable(CR_C); | ||||
| } | ||||
|  | ||||
| int dcache_status(void) | ||||
| { | ||||
| 	return (get_cr() & CR_C) != 0; | ||||
| } | ||||
| @@ -204,6 +204,28 @@ void dcache_clean_invalidate_by_mva(unsigned long addr, unsigned long len) | ||||
| 	dcache_op_mva(addr, len, OP_DCCIMVAC); | ||||
| } | ||||
|  | ||||
|  | ||||
| void dcache_mmu_disable(void) | ||||
| { | ||||
| 	uint32_t sctlr; | ||||
|  | ||||
| 	sctlr = read_sctlr(); | ||||
| 	dcache_clean_invalidate_all(); | ||||
| 	sctlr &= ~(SCTLR_C | SCTLR_M); | ||||
| 	write_sctlr(sctlr); | ||||
| } | ||||
|  | ||||
|  | ||||
| void dcache_mmu_enable(void) | ||||
| { | ||||
| 	uint32_t sctlr; | ||||
|  | ||||
| 	sctlr = read_sctlr(); | ||||
| 	dcache_clean_invalidate_all(); | ||||
| 	sctlr |= SCTLR_C | SCTLR_M; | ||||
| 	write_sctlr(sctlr); | ||||
| } | ||||
|  | ||||
| void armv7_invalidate_caches(void) | ||||
| { | ||||
| 	uint32_t clidr; | ||||
| @@ -252,10 +274,3 @@ void armv7_invalidate_caches(void) | ||||
| 	/* Invalidate TLB */ | ||||
| 	tlb_invalidate_all(); | ||||
| } | ||||
|  | ||||
| /* FIXME: wrapper around imported mmu_setup() for now */ | ||||
| extern void mmu_setup(unsigned long start, unsigned long size); | ||||
| void mmu_setup_by_mva(unsigned long start, unsigned long size) | ||||
| { | ||||
| 	mmu_setup(start, size); | ||||
| } | ||||
|   | ||||
| @@ -1,339 +0,0 @@ | ||||
| /* | ||||
|  * (C) Copyright 2010 | ||||
|  * Texas Instruments, <www.ti.com> | ||||
|  * Aneesh V <aneesh@ti.com> | ||||
|  * | ||||
|  * See file CREDITS for list of people who contributed to this | ||||
|  * project. | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program; if not, write to the Free Software | ||||
|  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | ||||
|  * MA 02111-1307 USA | ||||
|  */ | ||||
| #include <armv7.h> | ||||
| #include <common.h> | ||||
| #include <system.h> | ||||
| #include <utils.h> | ||||
| #include <console/console.h> | ||||
|  | ||||
| #define ARMV7_DCACHE_INVAL_ALL		1 | ||||
| #define ARMV7_DCACHE_CLEAN_INVAL_ALL	2 | ||||
| #define ARMV7_DCACHE_INVAL_RANGE	3 | ||||
| #define ARMV7_DCACHE_CLEAN_INVAL_RANGE	4 | ||||
|  | ||||
| /* | ||||
|  * Write the level and type you want to Cache Size Selection Register(CSSELR) | ||||
|  * to get size details from Current Cache Size ID Register(CCSIDR) | ||||
|  */ | ||||
| static void set_csselr(u32 level, u32 type) | ||||
| {	u32 csselr = level << 1 | type; | ||||
|  | ||||
| 	/* Write to Cache Size Selection Register(CSSELR) */ | ||||
| 	asm volatile ("mcr p15, 2, %0, c0, c0, 0" : : "r" (csselr)); | ||||
| } | ||||
|  | ||||
| static u32 get_ccsidr(void) | ||||
| { | ||||
| 	u32 ccsidr; | ||||
|  | ||||
| 	/* Read current CP15 Cache Size ID Register */ | ||||
| 	asm volatile ("mrc p15, 1, %0, c0, c0, 0" : "=r" (ccsidr)); | ||||
| 	return ccsidr; | ||||
| } | ||||
|  | ||||
| static u32 get_clidr(void) | ||||
| { | ||||
| 	u32 clidr; | ||||
|  | ||||
| 	/* Read current CP15 Cache Level ID Register */ | ||||
| 	asm volatile ("mrc p15,1,%0,c0,c0,1" : "=r" (clidr)); | ||||
| 	return clidr; | ||||
| } | ||||
|  | ||||
| static void v7_inval_dcache_level_setway(u32 level, u32 num_sets, | ||||
| 					 u32 num_ways, u32 way_shift, | ||||
| 					 u32 log2_line_len) | ||||
| { | ||||
| 	int way, set, setway; | ||||
|  | ||||
| 	/* | ||||
| 	 * For optimal assembly code: | ||||
| 	 *	a. count down | ||||
| 	 *	b. have bigger loop inside | ||||
| 	 */ | ||||
| 	for (way = num_ways - 1; way >= 0 ; way--) { | ||||
| 		for (set = num_sets - 1; set >= 0; set--) { | ||||
| 			setway = (level << 1) | (set << log2_line_len) | | ||||
| 				 (way << way_shift); | ||||
| 			/* Invalidate data/unified cache line by set/way */ | ||||
| 			asm volatile ("	mcr p15, 0, %0, c7, c6, 2" | ||||
| 					: : "r" (setway)); | ||||
| 		} | ||||
| 	} | ||||
| 	/* DSB to make sure the operation is complete */ | ||||
| 	CP15DSB; | ||||
| } | ||||
|  | ||||
| static void v7_clean_inval_dcache_level_setway(u32 level, u32 num_sets, | ||||
| 					       u32 num_ways, u32 way_shift, | ||||
| 					       u32 log2_line_len) | ||||
| { | ||||
| 	int way, set, setway; | ||||
|  | ||||
| 	/* | ||||
| 	 * For optimal assembly code: | ||||
| 	 *	a. count down | ||||
| 	 *	b. have bigger loop inside | ||||
| 	 */ | ||||
| 	for (way = num_ways - 1; way >= 0 ; way--) { | ||||
| 		for (set = num_sets - 1; set >= 0; set--) { | ||||
| 			setway = (level << 1) | (set << log2_line_len) | | ||||
| 				 (way << way_shift); | ||||
| 			/* | ||||
| 			 * Clean & Invalidate data/unified | ||||
| 			 * cache line by set/way | ||||
| 			 */ | ||||
| 			asm volatile ("	mcr p15, 0, %0, c7, c14, 2" | ||||
| 					: : "r" (setway)); | ||||
| 		} | ||||
| 	} | ||||
| 	/* DSB to make sure the operation is complete */ | ||||
| 	CP15DSB; | ||||
| } | ||||
|  | ||||
| static void v7_maint_dcache_level_setway(u32 level, u32 operation) | ||||
| { | ||||
| 	u32 ccsidr; | ||||
| 	u32 num_sets, num_ways, log2_line_len, log2_num_ways; | ||||
| 	u32 way_shift; | ||||
|  | ||||
| 	set_csselr(level, ARMV7_CSSELR_IND_DATA_UNIFIED); | ||||
|  | ||||
| 	ccsidr = get_ccsidr(); | ||||
|  | ||||
| 	log2_line_len = ((ccsidr & CCSIDR_LINE_SIZE_MASK) >> | ||||
| 				CCSIDR_LINE_SIZE_OFFSET) + 2; | ||||
| 	/* Converting from words to bytes */ | ||||
| 	log2_line_len += 2; | ||||
|  | ||||
| 	num_ways  = ((ccsidr & CCSIDR_ASSOCIATIVITY_MASK) >> | ||||
| 			CCSIDR_ASSOCIATIVITY_OFFSET) + 1; | ||||
| 	num_sets  = ((ccsidr & CCSIDR_NUM_SETS_MASK) >> | ||||
| 			CCSIDR_NUM_SETS_OFFSET) + 1; | ||||
| 	/* | ||||
| 	 * According to ARMv7 ARM number of sets and number of ways need | ||||
| 	 * not be a power of 2 | ||||
| 	 */ | ||||
| 	log2_num_ways = log_2_n_round_up(num_ways); | ||||
|  | ||||
| 	way_shift = (32 - log2_num_ways); | ||||
| 	if (operation == ARMV7_DCACHE_INVAL_ALL) { | ||||
| 		v7_inval_dcache_level_setway(level, num_sets, num_ways, | ||||
| 				      way_shift, log2_line_len); | ||||
| 	} else if (operation == ARMV7_DCACHE_CLEAN_INVAL_ALL) { | ||||
| 		v7_clean_inval_dcache_level_setway(level, num_sets, num_ways, | ||||
| 						   way_shift, log2_line_len); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void v7_maint_dcache_all(u32 operation) | ||||
| { | ||||
| 	u32 level, cache_type, level_start_bit = 0; | ||||
|  | ||||
| 	u32 clidr = get_clidr(); | ||||
|  | ||||
| 	for (level = 0; level < 7; level++) { | ||||
| 		cache_type = (clidr >> level_start_bit) & 0x7; | ||||
| 		if ((cache_type == ARMV7_CLIDR_CTYPE_DATA_ONLY) || | ||||
| 		    (cache_type == ARMV7_CLIDR_CTYPE_INSTRUCTION_DATA) || | ||||
| 		    (cache_type == ARMV7_CLIDR_CTYPE_UNIFIED)) | ||||
| 			v7_maint_dcache_level_setway(level, operation); | ||||
| 		level_start_bit += 3; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void v7_dcache_clean_inval_range(u32 start, | ||||
| 					u32 stop, u32 line_len) | ||||
| { | ||||
| 	u32 mva; | ||||
|  | ||||
| 	/* Align start to cache line boundary */ | ||||
| 	start &= ~(line_len - 1); | ||||
| 	for (mva = start; mva < stop; mva = mva + line_len) { | ||||
| 		/* DCCIMVAC - Clean & Invalidate data cache by MVA to PoC */ | ||||
| 		asm volatile ("mcr p15, 0, %0, c7, c14, 1" : : "r" (mva)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void v7_dcache_inval_range(u32 start, u32 stop, u32 line_len) | ||||
| { | ||||
| 	u32 mva; | ||||
|  | ||||
| 	/* | ||||
| 	 * If start address is not aligned to cache-line do not | ||||
| 	 * invalidate the first cache-line | ||||
| 	 */ | ||||
| 	if (start & (line_len - 1)) { | ||||
| 		printk(BIOS_ERR, "%s - start address is not aligned - 0x%08x\n", | ||||
| 			__func__, start); | ||||
| 		/* move to next cache line */ | ||||
| 		start = (start + line_len - 1) & ~(line_len - 1); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * If stop address is not aligned to cache-line do not | ||||
| 	 * invalidate the last cache-line | ||||
| 	 */ | ||||
| 	if (stop & (line_len - 1)) { | ||||
| 		printk(BIOS_ERR, "%s - stop address is not aligned - 0x%08x\n", | ||||
| 			__func__, stop); | ||||
| 		/* align to the beginning of this cache line */ | ||||
| 		stop &= ~(line_len - 1); | ||||
| 	} | ||||
|  | ||||
| 	for (mva = start; mva < stop; mva = mva + line_len) { | ||||
| 		/* DCIMVAC - Invalidate data cache by MVA to PoC */ | ||||
| 		asm volatile ("mcr p15, 0, %0, c7, c6, 1" : : "r" (mva)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void v7_dcache_maint_range(u32 start, u32 stop, u32 range_op) | ||||
| { | ||||
| 	u32 line_len = dcache_get_line_size(); | ||||
|  | ||||
| 	switch (range_op) { | ||||
| 	case ARMV7_DCACHE_CLEAN_INVAL_RANGE: | ||||
| 		v7_dcache_clean_inval_range(start, stop, line_len); | ||||
| 		break; | ||||
| 	case ARMV7_DCACHE_INVAL_RANGE: | ||||
| 		v7_dcache_inval_range(start, stop, line_len); | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	/* DSB to make sure the operation is complete */ | ||||
| 	CP15DSB; | ||||
| } | ||||
|  | ||||
| /* Invalidate TLB */ | ||||
| void v7_inval_tlb(void) | ||||
| { | ||||
| 	/* Invalidate entire unified TLB */ | ||||
| 	asm volatile ("mcr p15, 0, %0, c8, c7, 0" : : "r" (0)); | ||||
| 	/* Invalidate entire data TLB */ | ||||
| 	asm volatile ("mcr p15, 0, %0, c8, c6, 0" : : "r" (0)); | ||||
| 	/* Invalidate entire instruction TLB */ | ||||
| 	asm volatile ("mcr p15, 0, %0, c8, c5, 0" : : "r" (0)); | ||||
| 	/* Full system DSB - make sure that the invalidation is complete */ | ||||
| 	CP15DSB; | ||||
| 	/* Full system ISB - make sure the instruction stream sees it */ | ||||
| 	CP15ISB; | ||||
| } | ||||
|  | ||||
| unsigned long dcache_get_line_size(void) | ||||
| { | ||||
| 	u32 line_len, ccsidr; | ||||
|  | ||||
| 	ccsidr = get_ccsidr(); | ||||
| 	line_len = ((ccsidr & CCSIDR_LINE_SIZE_MASK) >> | ||||
| 			CCSIDR_LINE_SIZE_OFFSET) + 2; | ||||
| 	/* Converting from words to bytes */ | ||||
| 	line_len += 2; | ||||
| 	/* converting from log2(linelen) to linelen */ | ||||
| 	line_len = 1 << line_len; | ||||
|  | ||||
| 	return line_len; | ||||
| } | ||||
|  | ||||
| void invalidate_dcache_all(void) | ||||
| { | ||||
| 	v7_maint_dcache_all(ARMV7_DCACHE_INVAL_ALL); | ||||
|  | ||||
| 	v7_outer_cache_inval_all(); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Performs a clean & invalidation of the entire data cache | ||||
|  * at all levels | ||||
|  */ | ||||
| void flush_dcache_all(void) | ||||
| { | ||||
| 	v7_maint_dcache_all(ARMV7_DCACHE_CLEAN_INVAL_ALL); | ||||
|  | ||||
| 	v7_outer_cache_flush_all(); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Invalidates range in all levels of D-cache/unified cache used: | ||||
|  * Affects the range [start, stop - 1] | ||||
|  */ | ||||
| void invalidate_dcache_range(unsigned long start, unsigned long stop) | ||||
| { | ||||
|  | ||||
| 	v7_dcache_maint_range(start, stop, ARMV7_DCACHE_INVAL_RANGE); | ||||
|  | ||||
| 	v7_outer_cache_inval_range(start, stop); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Flush range(clean & invalidate) from all levels of D-cache/unified | ||||
|  * cache used: | ||||
|  * Affects the range [start, stop - 1] | ||||
|  */ | ||||
| void flush_dcache_range(unsigned long start, unsigned long stop) | ||||
| { | ||||
| 	v7_dcache_maint_range(start, stop, ARMV7_DCACHE_CLEAN_INVAL_RANGE); | ||||
|  | ||||
| 	v7_outer_cache_flush_range(start, stop); | ||||
| } | ||||
|  | ||||
| void arm_init_before_mmu(void) | ||||
| { | ||||
| 	v7_outer_cache_enable(); | ||||
| 	invalidate_dcache_all(); | ||||
| 	v7_inval_tlb(); | ||||
| } | ||||
|  | ||||
| void mmu_page_table_flush(unsigned long start, unsigned long stop) | ||||
| { | ||||
| 	flush_dcache_range(start, stop); | ||||
| 	v7_inval_tlb(); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Flush range from all levels of d-cache/unified-cache used: | ||||
|  * Affects the range [start, start + size - 1] | ||||
|  */ | ||||
| void  flush_cache(unsigned long start, unsigned long size) | ||||
| { | ||||
| 	flush_dcache_range(start, start + size); | ||||
| } | ||||
|  | ||||
| /* Invalidate entire I-cache and branch predictor array */ | ||||
| void invalidate_icache_all(void) | ||||
| { | ||||
| 	/* | ||||
| 	 * Invalidate all instruction caches to PoU. | ||||
| 	 * Also flushes branch target cache. | ||||
| 	 */ | ||||
| 	asm volatile ("mcr p15, 0, %0, c7, c5, 0" : : "r" (0)); | ||||
|  | ||||
| 	/* Invalidate entire branch predictor array */ | ||||
| 	asm volatile ("mcr p15, 0, %0, c7, c5, 6" : : "r" (0)); | ||||
|  | ||||
| 	/* Full system DSB - make sure that the invalidation is complete */ | ||||
| 	CP15DSB; | ||||
|  | ||||
| 	/* ISB - make sure the instruction stream sees it */ | ||||
| 	CP15ISB; | ||||
| } | ||||
							
								
								
									
										132
									
								
								src/arch/armv7/lib/mmu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/arch/armv7/lib/mmu.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| /* | ||||
|  * This file is part of the coreboot project. | ||||
|  * | ||||
|  * Copyright 2013 Google Inc. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions | ||||
|  * are met: | ||||
|  * 1. Redistributions of source code must retain the above copyright | ||||
|  *    notice, this list of conditions and the following disclaimer. | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright | ||||
|  *    notice, this list of conditions and the following disclaimer in the | ||||
|  *    documentation and/or other materials provided with the distribution. | ||||
|  * 3. The name of the author may not be used to endorse or promote products | ||||
|  *    derived from this software without specific prior written permission. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | ||||
|  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||||
|  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||||
|  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||||
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||||
|  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||||
|  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||||
|  * SUCH DAMAGE. | ||||
|  */ | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <types.h> | ||||
|  | ||||
| #include <cbmem.h> | ||||
| #include <console/console.h> | ||||
|  | ||||
| #include <arch/cache.h> | ||||
|  | ||||
| #define L1_TLB_ENTRIES	4096	/* 1 entry for each 1MB address space */ | ||||
|  | ||||
| static uintptr_t ttb_addr; | ||||
|  | ||||
| void mmu_config_range(unsigned long start_mb, unsigned long size_mb, | ||||
| 		enum dcache_policy policy) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	uint32_t attr; | ||||
| 	uint32_t *ttb_entry = (uint32_t *)ttb_addr; | ||||
| 	const char *str = NULL; | ||||
|  | ||||
| 	/* | ||||
| 	 * Section entry bits: | ||||
| 	 * 31:20 - section base address | ||||
| 	 *    18 - 0 to indicate normal section (versus supersection) | ||||
| 	 *    17 - nG, 0 to indicate page is global | ||||
| 	 *    16 - S, 0 for non-shareable (?) | ||||
| 	 *    15 - APX, 0 for full access | ||||
| 	 * 14:12 - TEX, 0b000 for outer and inner write-back | ||||
| 	 * 11:10 - AP, 0b11 for full access | ||||
| 	 *     9 - P, ? (FIXME: not described or possibly obsolete?) | ||||
| 	 *  8: 5 - Domain | ||||
| 	 *     4 - XN, 1 to set execute-never (and also avoid prefetches) | ||||
| 	 *     3 - C, 1 for cacheable | ||||
| 	 *     2 - B, 1 for bufferable | ||||
| 	 *  1: 0 - 0b10 to indicate section entry | ||||
| 	 */ | ||||
|  | ||||
| 	switch(policy) { | ||||
| 	case DCACHE_OFF: | ||||
| 		/* XN set to avoid prefetches to uncached/unbuffered regions */ | ||||
| 		attr = (0x3 << 10) | (1 << 4) | 0x2; | ||||
| 		str = "off"; | ||||
| 		break; | ||||
| 	case DCACHE_WRITEBACK: | ||||
| 		attr =  (0x3 << 10) | (1 << 3) | (1 << 2) | 0x2; | ||||
| 		str = "writeback"; | ||||
| 		break; | ||||
| 	case DCACHE_WRITETHROUGH: | ||||
| 		attr =  (0x3 << 10) | (1 << 3) | (1 << 2) | 0x2; | ||||
| 		str = "writethrough"; | ||||
| 		break; | ||||
| 	default: | ||||
| 		printk(BIOS_ERR, "unknown dcache policy: %02x\n", policy); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	printk(BIOS_DEBUG, "Setting dcache policy: 0x%08lx:0x%08lx [%s]\n", | ||||
| 			start_mb << 20, ((start_mb + size_mb) << 20) - 1, str); | ||||
|  | ||||
| 	for (i = start_mb; i < start_mb + size_mb; i++) | ||||
| 		ttb_entry[i] = (i << 20) | attr; | ||||
| } | ||||
|  | ||||
| void mmu_init(void) | ||||
| { | ||||
| 	unsigned int ttb_size; | ||||
| 	uint32_t ttbcr; | ||||
|  | ||||
| 	/* | ||||
| 	 * For coreboot's purposes, we will create a simple L1 page table | ||||
| 	 * in RAM with 1MB section translation entries over the 4GB address | ||||
| 	 * space. | ||||
| 	 * (ref: section 10.2 and example 15-4 in Cortex-A series | ||||
| 	 * programmer's guide) | ||||
| 	 * | ||||
| 	 * FIXME: TLB needs to be aligned to 16KB, but cbmem_add() aligns to | ||||
| 	 * 512 bytes. So add double the space in cbmem and fix-up the pointer. | ||||
| 	 */ | ||||
| 	ttb_size = L1_TLB_ENTRIES * sizeof(unsigned long); | ||||
| 	ttb_addr = (uintptr_t)cbmem_add(CBMEM_ID_GDT, ttb_size * 2); | ||||
| 	ttb_addr = ALIGN(ttb_addr + ttb_size, ttb_size); | ||||
| 	printk(BIOS_DEBUG, "Translation table is @ 0x%08x\n", ttb_addr); | ||||
|  | ||||
| 	/* | ||||
| 	 * Disable TTBR1 by setting TTBCR.N to 0b000, which means the TTBR0 | ||||
| 	 * table size is 16KB and has indices VA[31:20]. | ||||
| 	 * | ||||
| 	 * ref: Arch Ref. Manual for ARMv7-A, B3.5.4, | ||||
| 	 */ | ||||
| 	ttbcr = read_ttbcr(); | ||||
| 	ttbcr &= ~(0x3); | ||||
| 	write_ttbcr(ttbcr); | ||||
|  | ||||
| 	/* | ||||
| 	 * Translation table base 0 address is in bits 31:14-N, where N is given | ||||
| 	 * by bits 2:0 in TTBCR (which we set to 0). All lower bits in this | ||||
| 	 * register should be zero for coreboot. | ||||
| 	 */ | ||||
| 	write_ttbr0(ttb_addr); | ||||
|  | ||||
| 	/* disable domain-level checking of permissions */ | ||||
| 	write_dacr(~0); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user