diff options
Diffstat (limited to 'arch/metag/mm')
| -rw-r--r-- | arch/metag/mm/Kconfig | 146 | ||||
| -rw-r--r-- | arch/metag/mm/Makefile | 19 | ||||
| -rw-r--r-- | arch/metag/mm/cache.c | 521 | ||||
| -rw-r--r-- | arch/metag/mm/extable.c | 15 | ||||
| -rw-r--r-- | arch/metag/mm/fault.c | 243 | ||||
| -rw-r--r-- | arch/metag/mm/highmem.c | 133 | ||||
| -rw-r--r-- | arch/metag/mm/hugetlbpage.c | 259 | ||||
| -rw-r--r-- | arch/metag/mm/init.c | 407 | ||||
| -rw-r--r-- | arch/metag/mm/ioremap.c | 89 | ||||
| -rw-r--r-- | arch/metag/mm/l2cache.c | 192 | ||||
| -rw-r--r-- | arch/metag/mm/maccess.c | 68 | ||||
| -rw-r--r-- | arch/metag/mm/mmu-meta1.c | 157 | ||||
| -rw-r--r-- | arch/metag/mm/mmu-meta2.c | 207 | ||||
| -rw-r--r-- | arch/metag/mm/numa.c | 82 | 
14 files changed, 2538 insertions, 0 deletions
diff --git a/arch/metag/mm/Kconfig b/arch/metag/mm/Kconfig new file mode 100644 index 00000000000..03fb8f1555a --- /dev/null +++ b/arch/metag/mm/Kconfig @@ -0,0 +1,146 @@ +menu "Memory management options" + +config PAGE_OFFSET +	hex "Kernel page offset address" +	default "0x40000000" +	help +	  This option allows you to set the virtual address at which the +	  kernel will be mapped to. +endmenu + +config KERNEL_4M_PAGES +	bool "Map kernel with 4MB pages" +	depends on METAG_META21_MMU +	default y +	help +	  Map the kernel with large pages to reduce TLB pressure. + +choice +	prompt "User page size" +	default PAGE_SIZE_4K + +config PAGE_SIZE_4K +	bool "4kB" +	help +	  This is the default page size used by all Meta cores. + +config PAGE_SIZE_8K +	bool "8kB" +	depends on METAG_META21_MMU +	help +	  This enables 8kB pages as supported by Meta 2.x and later MMUs. + +config PAGE_SIZE_16K +	bool "16kB" +	depends on METAG_META21_MMU +	help +	  This enables 16kB pages as supported by Meta 2.x and later MMUs. + +endchoice + +config NUMA +	bool "Non Uniform Memory Access (NUMA) Support" +	select ARCH_WANT_NUMA_VARIABLE_LOCALITY +	help +	  Some Meta systems have MMU-mappable on-chip memories with +	  lower latencies than main memory. This enables support for +	  these blocks by binding them to nodes and allowing +	  memory policies to be used for prioritizing and controlling +	  allocation behaviour. + +config FORCE_MAX_ZONEORDER +	int "Maximum zone order" +	range 10 32 +	default "10" +	help +	  The kernel memory allocator divides physically contiguous memory +	  blocks into "zones", where each zone is a power of two number of +	  pages.  This option selects the largest power of two that the kernel +	  keeps in the memory allocator.  If you need to allocate very large +	  blocks of physically contiguous memory, then you may need to +	  increase this value. + +	  This config option is actually maximum order plus one. For example, +	  a value of 11 means that the largest free memory block is 2^10 pages. + +	  The page size is not necessarily 4KB.  Keep this in mind +	  when choosing a value for this option. + +config METAG_L2C +	bool "Level 2 Cache Support" +	depends on METAG_META21 +	help +	  Press y here to enable support for the Meta Level 2 (L2) cache. This +	  will enable the cache at start up if it hasn't already been enabled +	  by the bootloader. + +	  If the bootloader enables the L2 you must press y here to ensure the +	  kernel takes the appropriate actions to keep the cache coherent. + +config NODES_SHIFT +	int +	default "1" +	depends on NEED_MULTIPLE_NODES + +config ARCH_FLATMEM_ENABLE +	def_bool y +	depends on !NUMA + +config ARCH_SPARSEMEM_ENABLE +	def_bool y +	select SPARSEMEM_STATIC + +config ARCH_SPARSEMEM_DEFAULT +	def_bool y + +config ARCH_SELECT_MEMORY_MODEL +	def_bool y + +config SYS_SUPPORTS_HUGETLBFS +	def_bool y +	depends on METAG_META21_MMU + +choice +	prompt "HugeTLB page size" +	depends on METAG_META21_MMU && HUGETLB_PAGE +	default HUGETLB_PAGE_SIZE_1M + +config HUGETLB_PAGE_SIZE_8K +	bool "8kB" +	depends on PAGE_SIZE_4K + +config HUGETLB_PAGE_SIZE_16K +	bool "16kB" +	depends on PAGE_SIZE_4K || PAGE_SIZE_8K + +config HUGETLB_PAGE_SIZE_32K +	bool "32kB" + +config HUGETLB_PAGE_SIZE_64K +	bool "64kB" + +config HUGETLB_PAGE_SIZE_128K +	bool "128kB" + +config HUGETLB_PAGE_SIZE_256K +	bool "256kB" + +config HUGETLB_PAGE_SIZE_512K +	bool "512kB" + +config HUGETLB_PAGE_SIZE_1M +	bool "1MB" + +config HUGETLB_PAGE_SIZE_2M +	bool "2MB" + +config HUGETLB_PAGE_SIZE_4M +	bool "4MB" + +endchoice + +config METAG_COREMEM +	bool +	default y if SUSPEND + +source "mm/Kconfig" diff --git a/arch/metag/mm/Makefile b/arch/metag/mm/Makefile new file mode 100644 index 00000000000..99433116412 --- /dev/null +++ b/arch/metag/mm/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for the linux Meta-specific parts of the memory manager. +# + +obj-y				+= cache.o +obj-y				+= extable.o +obj-y				+= fault.o +obj-y				+= init.o +obj-y				+= ioremap.o +obj-y				+= maccess.o + +mmu-y				:= mmu-meta1.o +mmu-$(CONFIG_METAG_META21_MMU)	:= mmu-meta2.o +obj-y				+= $(mmu-y) + +obj-$(CONFIG_HIGHMEM)		+= highmem.o +obj-$(CONFIG_HUGETLB_PAGE)	+= hugetlbpage.o +obj-$(CONFIG_METAG_L2C)		+= l2cache.o +obj-$(CONFIG_NUMA)		+= numa.o diff --git a/arch/metag/mm/cache.c b/arch/metag/mm/cache.c new file mode 100644 index 00000000000..a62285284ab --- /dev/null +++ b/arch/metag/mm/cache.c @@ -0,0 +1,521 @@ +/* + * arch/metag/mm/cache.c + * + * Copyright (C) 2001, 2002, 2005, 2007, 2012 Imagination Technologies. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by the + * Free Software Foundation. + * + * Cache control code + */ + +#include <linux/export.h> +#include <linux/io.h> +#include <asm/cacheflush.h> +#include <asm/core_reg.h> +#include <asm/global_lock.h> +#include <asm/metag_isa.h> +#include <asm/metag_mem.h> +#include <asm/metag_regs.h> + +#define DEFAULT_CACHE_WAYS_LOG2	2 + +/* + * Size of a set in the caches. Initialised for default 16K stride, adjusted + * according to values passed through TBI global heap segment via LDLK (on ATP) + * or config registers (on HTP/MTP) + */ +static int dcache_set_shift = METAG_TBI_CACHE_SIZE_BASE_LOG2 +					- DEFAULT_CACHE_WAYS_LOG2; +static int icache_set_shift = METAG_TBI_CACHE_SIZE_BASE_LOG2 +					- DEFAULT_CACHE_WAYS_LOG2; +/* + * The number of sets in the caches. Initialised for HTP/ATP, adjusted + * according to NOMMU setting in config registers + */ +static unsigned char dcache_sets_log2 = DEFAULT_CACHE_WAYS_LOG2; +static unsigned char icache_sets_log2 = DEFAULT_CACHE_WAYS_LOG2; + +#ifndef CONFIG_METAG_META12 +/** + * metag_lnkget_probe() - Probe whether lnkget/lnkset go around the cache + */ +static volatile u32 lnkget_testdata[16] __initdata __aligned(64); + +#define LNKGET_CONSTANT 0xdeadbeef + +static void __init metag_lnkget_probe(void) +{ +	int temp; +	long flags; + +	/* +	 * It's conceivable the user has configured a globally coherent cache +	 * shared with non-Linux hardware threads, so use LOCK2 to prevent them +	 * from executing and causing cache eviction during the test. +	 */ +	__global_lock2(flags); + +	/* read a value to bring it into the cache */ +	(void)lnkget_testdata[0]; +	lnkget_testdata[0] = 0; + +	/* lnkget/lnkset it to modify it */ +	asm volatile( +		"1:	LNKGETD %0, [%1]\n" +		"	LNKSETD [%1], %2\n" +		"	DEFR	%0, TXSTAT\n" +		"	ANDT	%0, %0, #HI(0x3f000000)\n" +		"	CMPT	%0, #HI(0x02000000)\n" +		"	BNZ	1b\n" +		: "=&d" (temp) +		: "da" (&lnkget_testdata[0]), "bd" (LNKGET_CONSTANT) +		: "cc"); + +	/* re-read it to see if the cached value changed */ +	temp = lnkget_testdata[0]; + +	__global_unlock2(flags); + +	/* flush the cache line to fix any incoherency */ +	__builtin_dcache_flush((void *)&lnkget_testdata[0]); + +#if defined(CONFIG_METAG_LNKGET_AROUND_CACHE) +	/* if the cache is right, LNKGET_AROUND_CACHE is unnecessary */ +	if (temp == LNKGET_CONSTANT) +		pr_info("LNKGET/SET go through cache but CONFIG_METAG_LNKGET_AROUND_CACHE=y\n"); +#elif defined(CONFIG_METAG_ATOMICITY_LNKGET) +	/* +	 * if the cache is wrong, LNKGET_AROUND_CACHE is really necessary +	 * because the kernel is configured to use LNKGET/SET for atomicity +	 */ +	WARN(temp != LNKGET_CONSTANT, +	     "LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n" +	     "Expect kernel failure as it's used for atomicity primitives\n"); +#elif defined(CONFIG_SMP) +	/* +	 * if the cache is wrong, LNKGET_AROUND_CACHE should be used or the +	 * gateway page won't flush and userland could break. +	 */ +	WARN(temp != LNKGET_CONSTANT, +	     "LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n" +	     "Expect userland failure as it's used for user gateway page\n"); +#else +	/* +	 * if the cache is wrong, LNKGET_AROUND_CACHE is set wrong, but it +	 * doesn't actually matter as it doesn't have any effect on !SMP && +	 * !ATOMICITY_LNKGET. +	 */ +	if (temp != LNKGET_CONSTANT) +		pr_warn("LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n"); +#endif +} +#endif /* !CONFIG_METAG_META12 */ + +/** + * metag_cache_probe() - Probe L1 cache configuration. + * + * Probe the L1 cache configuration to aid the L1 physical cache flushing + * functions. + */ +void __init metag_cache_probe(void) +{ +#ifndef CONFIG_METAG_META12 +	int coreid = metag_in32(METAC_CORE_ID); +	int config = metag_in32(METAC_CORE_CONFIG2); +	int cfgcache = coreid & METAC_COREID_CFGCACHE_BITS; + +	if (cfgcache == METAC_COREID_CFGCACHE_TYPE0 || +	    cfgcache == METAC_COREID_CFGCACHE_PRIVNOMMU) { +		icache_sets_log2 = 1; +		dcache_sets_log2 = 1; +	} + +	/* For normal size caches, the smallest size is 4Kb. +	   For small caches, the smallest size is 64b */ +	icache_set_shift = (config & METAC_CORECFG2_ICSMALL_BIT) +				? 6 : 12; +	icache_set_shift += (config & METAC_CORE_C2ICSZ_BITS) +				>> METAC_CORE_C2ICSZ_S; +	icache_set_shift -= icache_sets_log2; + +	dcache_set_shift = (config & METAC_CORECFG2_DCSMALL_BIT) +				? 6 : 12; +	dcache_set_shift += (config & METAC_CORECFG2_DCSZ_BITS) +				>> METAC_CORECFG2_DCSZ_S; +	dcache_set_shift -= dcache_sets_log2; + +	metag_lnkget_probe(); +#else +	/* Extract cache sizes from global heap segment */ +	unsigned long val, u; +	int width, shift, addend; +	PTBISEG seg; + +	seg = __TBIFindSeg(NULL, TBID_SEG(TBID_THREAD_GLOBAL, +					  TBID_SEGSCOPE_GLOBAL, +					  TBID_SEGTYPE_HEAP)); +	if (seg != NULL) { +		val = seg->Data[1]; + +		/* Work out width of I-cache size bit-field */ +		u = ((unsigned long) METAG_TBI_ICACHE_SIZE_BITS) +		       >> METAG_TBI_ICACHE_SIZE_S; +		width = 0; +		while (u & 1) { +			width++; +			u >>= 1; +		} +		/* Extract sign-extended size addend value */ +		shift = 32 - (METAG_TBI_ICACHE_SIZE_S + width); +		addend = (long) ((val & METAG_TBI_ICACHE_SIZE_BITS) +				 << shift) +			>> (shift + METAG_TBI_ICACHE_SIZE_S); +		/* Now calculate I-cache set size */ +		icache_set_shift = (METAG_TBI_CACHE_SIZE_BASE_LOG2 +				    - DEFAULT_CACHE_WAYS_LOG2) +					+ addend; + +		/* Similarly for D-cache */ +		u = ((unsigned long) METAG_TBI_DCACHE_SIZE_BITS) +		       >> METAG_TBI_DCACHE_SIZE_S; +		width = 0; +		while (u & 1) { +			width++; +			u >>= 1; +		} +		shift = 32 - (METAG_TBI_DCACHE_SIZE_S + width); +		addend = (long) ((val & METAG_TBI_DCACHE_SIZE_BITS) +				 << shift) +			>> (shift + METAG_TBI_DCACHE_SIZE_S); +		dcache_set_shift = (METAG_TBI_CACHE_SIZE_BASE_LOG2 +				    - DEFAULT_CACHE_WAYS_LOG2) +					+ addend; +	} +#endif +} + +static void metag_phys_data_cache_flush(const void *start) +{ +	unsigned long flush0, flush1, flush2, flush3; +	int loops, step; +	int thread; +	int part, offset; +	int set_shift; + +	/* Use a sequence of writes to flush the cache region requested */ +	thread = (__core_reg_get(TXENABLE) & TXENABLE_THREAD_BITS) +					  >> TXENABLE_THREAD_S; + +	/* Cache is broken into sets which lie in contiguous RAMs */ +	set_shift = dcache_set_shift; + +	/* Move to the base of the physical cache flush region */ +	flush0 = LINSYSCFLUSH_DCACHE_LINE; +	step   = 64; + +	/* Get partition data for this thread */ +	part = metag_in32(SYSC_DCPART0 + +			      (SYSC_xCPARTn_STRIDE * thread)); + +	if ((int)start < 0) +		/* Access Global vs Local partition */ +		part >>= SYSC_xCPARTG_AND_S +			- SYSC_xCPARTL_AND_S; + +	/* Extract offset and move SetOff */ +	offset = (part & SYSC_xCPARTL_OR_BITS) +			>> SYSC_xCPARTL_OR_S; +	flush0 += (offset << (set_shift - 4)); + +	/* Shrink size */ +	part = (part & SYSC_xCPARTL_AND_BITS) +			>> SYSC_xCPARTL_AND_S; +	loops = ((part + 1) << (set_shift - 4)); + +	/* Reduce loops by step of cache line size */ +	loops /= step; + +	flush1 = flush0 + (1 << set_shift); +	flush2 = flush0 + (2 << set_shift); +	flush3 = flush0 + (3 << set_shift); + +	if (dcache_sets_log2 == 1) { +		flush2 = flush1; +		flush3 = flush1 + step; +		flush1 = flush0 + step; +		step  <<= 1; +		loops >>= 1; +	} + +	/* Clear loops ways in cache */ +	while (loops-- != 0) { +		/* Clear the ways. */ +#if 0 +		/* +		 * GCC doesn't generate very good code for this so we +		 * provide inline assembly instead. +		 */ +		metag_out8(0, flush0); +		metag_out8(0, flush1); +		metag_out8(0, flush2); +		metag_out8(0, flush3); + +		flush0 += step; +		flush1 += step; +		flush2 += step; +		flush3 += step; +#else +		asm volatile ( +			"SETB\t[%0+%4++],%5\n" +			"SETB\t[%1+%4++],%5\n" +			"SETB\t[%2+%4++],%5\n" +			"SETB\t[%3+%4++],%5\n" +			: "+e" (flush0), +			  "+e" (flush1), +			  "+e" (flush2), +			  "+e" (flush3) +			: "e" (step), "a" (0)); +#endif +	} +} + +void metag_data_cache_flush_all(const void *start) +{ +	if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_DC_ON_BIT) == 0) +		/* No need to flush the data cache it's not actually enabled */ +		return; + +	metag_phys_data_cache_flush(start); +} + +void metag_data_cache_flush(const void *start, int bytes) +{ +	unsigned long flush0; +	int loops, step; + +	if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_DC_ON_BIT) == 0) +		/* No need to flush the data cache it's not actually enabled */ +		return; + +	if (bytes >= 4096) { +		metag_phys_data_cache_flush(start); +		return; +	} + +	/* Use linear cache flush mechanism on META IP */ +	flush0 = (int)start; +	loops  = ((int)start & (DCACHE_LINE_BYTES - 1)) + bytes + +					(DCACHE_LINE_BYTES - 1); +	loops  >>= DCACHE_LINE_S; + +#define PRIM_FLUSH(addr, offset) do {			\ +	int __addr = ((int) (addr)) + ((offset) * 64);	\ +	__builtin_dcache_flush((void *)(__addr));	\ +	} while (0) + +#define LOOP_INC (4*64) + +	do { +		/* By default stop */ +		step = 0; + +		switch (loops) { +		/* Drop Thru Cases! */ +		default: +			PRIM_FLUSH(flush0, 3); +			loops -= 4; +			step = 1; +		case 3: +			PRIM_FLUSH(flush0, 2); +		case 2: +			PRIM_FLUSH(flush0, 1); +		case 1: +			PRIM_FLUSH(flush0, 0); +			flush0 += LOOP_INC; +		case 0: +			break; +		} +	} while (step); +} +EXPORT_SYMBOL(metag_data_cache_flush); + +static void metag_phys_code_cache_flush(const void *start, int bytes) +{ +	unsigned long flush0, flush1, flush2, flush3, end_set; +	int loops, step; +	int thread; +	int set_shift, set_size; +	int part, offset; + +	/* Use a sequence of writes to flush the cache region requested */ +	thread = (__core_reg_get(TXENABLE) & TXENABLE_THREAD_BITS) +					  >> TXENABLE_THREAD_S; +	set_shift = icache_set_shift; + +	/* Move to the base of the physical cache flush region */ +	flush0 = LINSYSCFLUSH_ICACHE_LINE; +	step   = 64; + +	/* Get partition code for this thread */ +	part = metag_in32(SYSC_ICPART0 + +			  (SYSC_xCPARTn_STRIDE * thread)); + +	if ((int)start < 0) +		/* Access Global vs Local partition */ +		part >>= SYSC_xCPARTG_AND_S-SYSC_xCPARTL_AND_S; + +	/* Extract offset and move SetOff */ +	offset = (part & SYSC_xCPARTL_OR_BITS) +			>> SYSC_xCPARTL_OR_S; +	flush0 += (offset << (set_shift - 4)); + +	/* Shrink size */ +	part = (part & SYSC_xCPARTL_AND_BITS) +			>> SYSC_xCPARTL_AND_S; +	loops = ((part + 1) << (set_shift - 4)); + +	/* Where does the Set end? */ +	end_set = flush0 + loops; +	set_size = loops; + +#ifdef CONFIG_METAG_META12 +	if ((bytes < 4096) && (bytes < loops)) { +		/* Unreachable on HTP/MTP */ +		/* Only target the sets that could be relavent */ +		flush0 += (loops - step) & ((int) start); +		loops = (((int) start) & (step-1)) + bytes + step - 1; +	} +#endif + +	/* Reduce loops by step of cache line size */ +	loops /= step; + +	flush1 = flush0 + (1<<set_shift); +	flush2 = flush0 + (2<<set_shift); +	flush3 = flush0 + (3<<set_shift); + +	if (icache_sets_log2 == 1) { +		flush2 = flush1; +		flush3 = flush1 + step; +		flush1 = flush0 + step; +#if 0 +		/* flush0 will stop one line early in this case +		 * (flush1 will do the final line). +		 * However we don't correct end_set here at the moment +		 * because it will never wrap on HTP/MTP +		 */ +		end_set -= step; +#endif +		step  <<= 1; +		loops >>= 1; +	} + +	/* Clear loops ways in cache */ +	while (loops-- != 0) { +#if 0 +		/* +		 * GCC doesn't generate very good code for this so we +		 * provide inline assembly instead. +		 */ +		/* Clear the ways */ +		metag_out8(0, flush0); +		metag_out8(0, flush1); +		metag_out8(0, flush2); +		metag_out8(0, flush3); + +		flush0 += step; +		flush1 += step; +		flush2 += step; +		flush3 += step; +#else +		asm volatile ( +			"SETB\t[%0+%4++],%5\n" +			"SETB\t[%1+%4++],%5\n" +			"SETB\t[%2+%4++],%5\n" +			"SETB\t[%3+%4++],%5\n" +			: "+e" (flush0), +			  "+e" (flush1), +			  "+e" (flush2), +			  "+e" (flush3) +			: "e" (step), "a" (0)); +#endif + +		if (flush0 == end_set) { +			/* Wrap within Set 0 */ +			flush0 -= set_size; +			flush1 -= set_size; +			flush2 -= set_size; +			flush3 -= set_size; +		} +	} +} + +void metag_code_cache_flush_all(const void *start) +{ +	if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_IC_ON_BIT) == 0) +		/* No need to flush the code cache it's not actually enabled */ +		return; + +	metag_phys_code_cache_flush(start, 4096); +} +EXPORT_SYMBOL(metag_code_cache_flush_all); + +void metag_code_cache_flush(const void *start, int bytes) +{ +#ifndef CONFIG_METAG_META12 +	void *flush; +	int loops, step; +#endif /* !CONFIG_METAG_META12 */ + +	if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_IC_ON_BIT) == 0) +		/* No need to flush the code cache it's not actually enabled */ +		return; + +#ifdef CONFIG_METAG_META12 +	/* CACHEWD isn't available on Meta1, so always do full cache flush */ +	metag_phys_code_cache_flush(start, bytes); + +#else /* CONFIG_METAG_META12 */ +	/* If large size do full physical cache flush */ +	if (bytes >= 4096) { +		metag_phys_code_cache_flush(start, bytes); +		return; +	} + +	/* Use linear cache flush mechanism on META IP */ +	flush = (void *)((int)start & ~(ICACHE_LINE_BYTES-1)); +	loops  = ((int)start & (ICACHE_LINE_BYTES-1)) + bytes + +		(ICACHE_LINE_BYTES-1); +	loops  >>= ICACHE_LINE_S; + +#define PRIM_IFLUSH(addr, offset) \ +	__builtin_meta2_cachewd(((addr) + ((offset) * 64)), CACHEW_ICACHE_BIT) + +#define LOOP_INC (4*64) + +	do { +		/* By default stop */ +		step = 0; + +		switch (loops) { +		/* Drop Thru Cases! */ +		default: +			PRIM_IFLUSH(flush, 3); +			loops -= 4; +			step = 1; +		case 3: +			PRIM_IFLUSH(flush, 2); +		case 2: +			PRIM_IFLUSH(flush, 1); +		case 1: +			PRIM_IFLUSH(flush, 0); +			flush += LOOP_INC; +		case 0: +			break; +		} +	} while (step); +#endif /* !CONFIG_METAG_META12 */ +} +EXPORT_SYMBOL(metag_code_cache_flush); diff --git a/arch/metag/mm/extable.c b/arch/metag/mm/extable.c new file mode 100644 index 00000000000..2a21eaebe84 --- /dev/null +++ b/arch/metag/mm/extable.c @@ -0,0 +1,15 @@ + +#include <linux/module.h> +#include <linux/uaccess.h> + +int fixup_exception(struct pt_regs *regs) +{ +	const struct exception_table_entry *fixup; +	unsigned long pc = instruction_pointer(regs); + +	fixup = search_exception_tables(pc); +	if (fixup) +		regs->ctx.CurrPC = fixup->fixup; + +	return fixup != NULL; +} diff --git a/arch/metag/mm/fault.c b/arch/metag/mm/fault.c new file mode 100644 index 00000000000..332680e5ebf --- /dev/null +++ b/arch/metag/mm/fault.c @@ -0,0 +1,243 @@ +/* + *  Meta page fault handling. + * + *  Copyright (C) 2005-2012 Imagination Technologies Ltd. + */ + +#include <linux/mman.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/ptrace.h> +#include <linux/interrupt.h> +#include <linux/uaccess.h> + +#include <asm/tlbflush.h> +#include <asm/mmu.h> +#include <asm/traps.h> + +/* Clear any pending catch buffer state. */ +static void clear_cbuf_entry(struct pt_regs *regs, unsigned long addr, +			     unsigned int trapno) +{ +	PTBICTXEXTCB0 cbuf = regs->extcb0; + +	switch (trapno) { +		/* Instruction fetch faults leave no catch buffer state. */ +	case TBIXXF_SIGNUM_IGF: +	case TBIXXF_SIGNUM_IPF: +		return; +	default: +		if (cbuf[0].CBAddr == addr) { +			cbuf[0].CBAddr = 0; +			cbuf[0].CBFlags &= ~TXCATCH0_FAULT_BITS; + +			/* And, as this is the ONLY catch entry, we +			 * need to clear the cbuf bit from the context! +			 */ +			regs->ctx.SaveMask &= ~(TBICTX_CBUF_BIT | +						TBICTX_XCBF_BIT); + +			return; +		} +		pr_err("Failed to clear cbuf entry!\n"); +	} +} + +int show_unhandled_signals = 1; + +int do_page_fault(struct pt_regs *regs, unsigned long address, +		  unsigned int write_access, unsigned int trapno) +{ +	struct task_struct *tsk; +	struct mm_struct *mm; +	struct vm_area_struct *vma, *prev_vma; +	siginfo_t info; +	int fault; +	unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE; + +	tsk = current; + +	if ((address >= VMALLOC_START) && (address < VMALLOC_END)) { +		/* +		 * Synchronize this task's top level page-table +		 * with the 'reference' page table. +		 * +		 * Do _not_ use "tsk" here. We might be inside +		 * an interrupt in the middle of a task switch.. +		 */ +		int offset = pgd_index(address); +		pgd_t *pgd, *pgd_k; +		pud_t *pud, *pud_k; +		pmd_t *pmd, *pmd_k; +		pte_t *pte_k; + +		pgd = ((pgd_t *)mmu_get_base()) + offset; +		pgd_k = swapper_pg_dir + offset; + +		/* This will never happen with the folded page table. */ +		if (!pgd_present(*pgd)) { +			if (!pgd_present(*pgd_k)) +				goto bad_area_nosemaphore; +			set_pgd(pgd, *pgd_k); +			return 0; +		} + +		pud = pud_offset(pgd, address); +		pud_k = pud_offset(pgd_k, address); +		if (!pud_present(*pud_k)) +			goto bad_area_nosemaphore; +		set_pud(pud, *pud_k); + +		pmd = pmd_offset(pud, address); +		pmd_k = pmd_offset(pud_k, address); +		if (!pmd_present(*pmd_k)) +			goto bad_area_nosemaphore; +		set_pmd(pmd, *pmd_k); + +		pte_k = pte_offset_kernel(pmd_k, address); +		if (!pte_present(*pte_k)) +			goto bad_area_nosemaphore; + +		/* May only be needed on Chorus2 */ +		flush_tlb_all(); +		return 0; +	} + +	mm = tsk->mm; + +	if (in_atomic() || !mm) +		goto no_context; + +	if (user_mode(regs)) +		flags |= FAULT_FLAG_USER; +retry: +	down_read(&mm->mmap_sem); + +	vma = find_vma_prev(mm, address, &prev_vma); + +	if (!vma || address < vma->vm_start) +		goto check_expansion; + +good_area: +	if (write_access) { +		if (!(vma->vm_flags & VM_WRITE)) +			goto bad_area; +		flags |= FAULT_FLAG_WRITE; +	} else { +		if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE))) +			goto bad_area; +	} + +	/* +	 * If for any reason at all we couldn't handle the fault, +	 * make sure we exit gracefully rather than endlessly redo +	 * the fault. +	 */ +	fault = handle_mm_fault(mm, vma, address, flags); + +	if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) +		return 0; + +	if (unlikely(fault & VM_FAULT_ERROR)) { +		if (fault & VM_FAULT_OOM) +			goto out_of_memory; +		else if (fault & VM_FAULT_SIGBUS) +			goto do_sigbus; +		BUG(); +	} +	if (flags & FAULT_FLAG_ALLOW_RETRY) { +		if (fault & VM_FAULT_MAJOR) +			tsk->maj_flt++; +		else +			tsk->min_flt++; +		if (fault & VM_FAULT_RETRY) { +			flags &= ~FAULT_FLAG_ALLOW_RETRY; +			flags |= FAULT_FLAG_TRIED; + +			/* +			 * No need to up_read(&mm->mmap_sem) as we would +			 * have already released it in __lock_page_or_retry +			 * in mm/filemap.c. +			 */ + +			goto retry; +		} +	} + +	up_read(&mm->mmap_sem); +	return 0; + +check_expansion: +	vma = prev_vma; +	if (vma && (expand_stack(vma, address) == 0)) +		goto good_area; + +bad_area: +	up_read(&mm->mmap_sem); + +bad_area_nosemaphore: +	if (user_mode(regs)) { +		info.si_signo = SIGSEGV; +		info.si_errno = 0; +		info.si_code = SEGV_MAPERR; +		info.si_addr = (__force void __user *)address; +		info.si_trapno = trapno; + +		if (show_unhandled_signals && unhandled_signal(tsk, SIGSEGV) && +		    printk_ratelimit()) { +			pr_info("%s%s[%d]: segfault at %lx pc %08x sp %08x write %d trap %#x (%s)", +			       task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG, +			       tsk->comm, task_pid_nr(tsk), address, +			       regs->ctx.CurrPC, regs->ctx.AX[0].U0, +			       write_access, trapno, trap_name(trapno)); +			print_vma_addr(" in ", regs->ctx.CurrPC); +			print_vma_addr(" rtp in ", regs->ctx.DX[4].U1); +			printk("\n"); +			show_regs(regs); +		} +		force_sig_info(SIGSEGV, &info, tsk); +		return 1; +	} +	goto no_context; + +do_sigbus: +	up_read(&mm->mmap_sem); + +	/* +	 * Send a sigbus, regardless of whether we were in kernel +	 * or user mode. +	 */ +	info.si_signo = SIGBUS; +	info.si_errno = 0; +	info.si_code = BUS_ADRERR; +	info.si_addr = (__force void __user *)address; +	info.si_trapno = trapno; +	force_sig_info(SIGBUS, &info, tsk); + +	/* Kernel mode? Handle exceptions or die */ +	if (!user_mode(regs)) +		goto no_context; + +	return 1; + +	/* +	 * We ran out of memory, or some other thing happened to us that made +	 * us unable to handle the page fault gracefully. +	 */ +out_of_memory: +	up_read(&mm->mmap_sem); +	if (user_mode(regs)) { +		pagefault_out_of_memory(); +		return 1; +	} + +no_context: +	/* Are we prepared to handle this kernel fault?  */ +	if (fixup_exception(regs)) { +		clear_cbuf_entry(regs, address, trapno); +		return 1; +	} + +	die("Oops", regs, (write_access << 15) | trapno, address); +	do_exit(SIGKILL); +} diff --git a/arch/metag/mm/highmem.c b/arch/metag/mm/highmem.c new file mode 100644 index 00000000000..d71f621a2c0 --- /dev/null +++ b/arch/metag/mm/highmem.c @@ -0,0 +1,133 @@ +#include <linux/export.h> +#include <linux/highmem.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/interrupt.h> +#include <asm/fixmap.h> +#include <asm/tlbflush.h> + +static pte_t *kmap_pte; + +unsigned long highstart_pfn, highend_pfn; + +void *kmap(struct page *page) +{ +	might_sleep(); +	if (!PageHighMem(page)) +		return page_address(page); +	return kmap_high(page); +} +EXPORT_SYMBOL(kmap); + +void kunmap(struct page *page) +{ +	BUG_ON(in_interrupt()); +	if (!PageHighMem(page)) +		return; +	kunmap_high(page); +} +EXPORT_SYMBOL(kunmap); + +/* + * kmap_atomic/kunmap_atomic is significantly faster than kmap/kunmap because + * no global lock is needed and because the kmap code must perform a global TLB + * invalidation when the kmap pool wraps. + * + * However when holding an atomic kmap is is not legal to sleep, so atomic + * kmaps are appropriate for short, tight code paths only. + */ + +void *kmap_atomic(struct page *page) +{ +	enum fixed_addresses idx; +	unsigned long vaddr; +	int type; + +	/* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */ +	pagefault_disable(); +	if (!PageHighMem(page)) +		return page_address(page); + +	type = kmap_atomic_idx_push(); +	idx = type + KM_TYPE_NR * smp_processor_id(); +	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); +#ifdef CONFIG_DEBUG_HIGHMEM +	BUG_ON(!pte_none(*(kmap_pte - idx))); +#endif +	set_pte(kmap_pte - idx, mk_pte(page, PAGE_KERNEL)); + +	return (void *)vaddr; +} +EXPORT_SYMBOL(kmap_atomic); + +void __kunmap_atomic(void *kvaddr) +{ +	unsigned long vaddr = (unsigned long) kvaddr & PAGE_MASK; +	int idx, type; + +	if (kvaddr >= (void *)FIXADDR_START) { +		type = kmap_atomic_idx(); +		idx = type + KM_TYPE_NR * smp_processor_id(); + +		/* +		 * Force other mappings to Oops if they'll try to access this +		 * pte without first remap it.  Keeping stale mappings around +		 * is a bad idea also, in case the page changes cacheability +		 * attributes or becomes a protected page in a hypervisor. +		 */ +		pte_clear(&init_mm, vaddr, kmap_pte-idx); +		flush_tlb_kernel_range(vaddr, vaddr + PAGE_SIZE); + +		kmap_atomic_idx_pop(); +	} + +	pagefault_enable(); +} +EXPORT_SYMBOL(__kunmap_atomic); + +/* + * This is the same as kmap_atomic() but can map memory that doesn't + * have a struct page associated with it. + */ +void *kmap_atomic_pfn(unsigned long pfn) +{ +	enum fixed_addresses idx; +	unsigned long vaddr; +	int type; + +	pagefault_disable(); + +	type = kmap_atomic_idx_push(); +	idx = type + KM_TYPE_NR * smp_processor_id(); +	vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); +#ifdef CONFIG_DEBUG_HIGHMEM +	BUG_ON(!pte_none(*(kmap_pte - idx))); +#endif +	set_pte(kmap_pte - idx, pfn_pte(pfn, PAGE_KERNEL)); +	flush_tlb_kernel_range(vaddr, vaddr + PAGE_SIZE); + +	return (void *)vaddr; +} + +struct page *kmap_atomic_to_page(void *ptr) +{ +	unsigned long vaddr = (unsigned long)ptr; +	int idx; +	pte_t *pte; + +	if (vaddr < FIXADDR_START) +		return virt_to_page(ptr); + +	idx = virt_to_fix(vaddr); +	pte = kmap_pte - (idx - FIX_KMAP_BEGIN); +	return pte_page(*pte); +} + +void __init kmap_init(void) +{ +	unsigned long kmap_vstart; + +	/* cache the first kmap pte */ +	kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN); +	kmap_pte = kmap_get_fixmap_pte(kmap_vstart); +} diff --git a/arch/metag/mm/hugetlbpage.c b/arch/metag/mm/hugetlbpage.c new file mode 100644 index 00000000000..3c52fa6d0f8 --- /dev/null +++ b/arch/metag/mm/hugetlbpage.c @@ -0,0 +1,259 @@ +/* + * arch/metag/mm/hugetlbpage.c + * + * METAG HugeTLB page support. + * + * Cloned from SuperH + * + * Cloned from sparc64 by Paul Mundt. + * + * Copyright (C) 2002, 2003 David S. Miller (davem@redhat.com) + */ + +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/hugetlb.h> +#include <linux/pagemap.h> +#include <linux/sysctl.h> + +#include <asm/mman.h> +#include <asm/pgalloc.h> +#include <asm/tlb.h> +#include <asm/tlbflush.h> +#include <asm/cacheflush.h> + +/* + * If the arch doesn't supply something else, assume that hugepage + * size aligned regions are ok without further preparation. + */ +int prepare_hugepage_range(struct file *file, unsigned long addr, +						unsigned long len) +{ +	struct mm_struct *mm = current->mm; +	struct hstate *h = hstate_file(file); +	struct vm_area_struct *vma; + +	if (len & ~huge_page_mask(h)) +		return -EINVAL; +	if (addr & ~huge_page_mask(h)) +		return -EINVAL; +	if (TASK_SIZE - len < addr) +		return -EINVAL; + +	vma = find_vma(mm, ALIGN_HUGEPT(addr)); +	if (vma && !(vma->vm_flags & MAP_HUGETLB)) +		return -EINVAL; + +	vma = find_vma(mm, addr); +	if (vma) { +		if (addr + len > vma->vm_start) +			return -EINVAL; +		if (!(vma->vm_flags & MAP_HUGETLB) && +		    (ALIGN_HUGEPT(addr + len) > vma->vm_start)) +			return -EINVAL; +	} +	return 0; +} + +pte_t *huge_pte_alloc(struct mm_struct *mm, +			unsigned long addr, unsigned long sz) +{ +	pgd_t *pgd; +	pud_t *pud; +	pmd_t *pmd; +	pte_t *pte; + +	pgd = pgd_offset(mm, addr); +	pud = pud_offset(pgd, addr); +	pmd = pmd_offset(pud, addr); +	pte = pte_alloc_map(mm, NULL, pmd, addr); +	pgd->pgd &= ~_PAGE_SZ_MASK; +	pgd->pgd |= _PAGE_SZHUGE; + +	return pte; +} + +pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr) +{ +	pgd_t *pgd; +	pud_t *pud; +	pmd_t *pmd; +	pte_t *pte = NULL; + +	pgd = pgd_offset(mm, addr); +	pud = pud_offset(pgd, addr); +	pmd = pmd_offset(pud, addr); +	pte = pte_offset_kernel(pmd, addr); + +	return pte; +} + +int huge_pmd_unshare(struct mm_struct *mm, unsigned long *addr, pte_t *ptep) +{ +	return 0; +} + +struct page *follow_huge_addr(struct mm_struct *mm, +			      unsigned long address, int write) +{ +	return ERR_PTR(-EINVAL); +} + +int pmd_huge(pmd_t pmd) +{ +	return pmd_page_shift(pmd) > PAGE_SHIFT; +} + +int pud_huge(pud_t pud) +{ +	return 0; +} + +struct page *follow_huge_pmd(struct mm_struct *mm, unsigned long address, +			     pmd_t *pmd, int write) +{ +	return NULL; +} + +#ifdef HAVE_ARCH_HUGETLB_UNMAPPED_AREA + +/* + * Look for an unmapped area starting after another hugetlb vma. + * There are guaranteed to be no huge pte's spare if all the huge pages are + * full size (4MB), so in that case compile out this search. + */ +#if HPAGE_SHIFT == HUGEPT_SHIFT +static inline unsigned long +hugetlb_get_unmapped_area_existing(unsigned long len) +{ +	return 0; +} +#else +static unsigned long +hugetlb_get_unmapped_area_existing(unsigned long len) +{ +	struct mm_struct *mm = current->mm; +	struct vm_area_struct *vma; +	unsigned long start_addr, addr; +	int after_huge; + +	if (mm->context.part_huge) { +		start_addr = mm->context.part_huge; +		after_huge = 1; +	} else { +		start_addr = TASK_UNMAPPED_BASE; +		after_huge = 0; +	} +new_search: +	addr = start_addr; + +	for (vma = find_vma(mm, addr); ; vma = vma->vm_next) { +		if ((!vma && !after_huge) || TASK_SIZE - len < addr) { +			/* +			 * Start a new search - just in case we missed +			 * some holes. +			 */ +			if (start_addr != TASK_UNMAPPED_BASE) { +				start_addr = TASK_UNMAPPED_BASE; +				goto new_search; +			} +			return 0; +		} +		/* skip ahead if we've aligned right over some vmas */ +		if (vma && vma->vm_end <= addr) +			continue; +		/* space before the next vma? */ +		if (after_huge && (!vma || ALIGN_HUGEPT(addr + len) +			    <= vma->vm_start)) { +			unsigned long end = addr + len; +			if (end & HUGEPT_MASK) +				mm->context.part_huge = end; +			else if (addr == mm->context.part_huge) +				mm->context.part_huge = 0; +			return addr; +		} +		if (vma && (vma->vm_flags & MAP_HUGETLB)) { +			/* space after a huge vma in 2nd level page table? */ +			if (vma->vm_end & HUGEPT_MASK) { +				after_huge = 1; +				/* no need to align to the next PT block */ +				addr = vma->vm_end; +				continue; +			} +		} +		after_huge = 0; +		addr = ALIGN_HUGEPT(vma->vm_end); +	} +} +#endif + +/* Do a full search to find an area without any nearby normal pages. */ +static unsigned long +hugetlb_get_unmapped_area_new_pmd(unsigned long len) +{ +	struct vm_unmapped_area_info info; + +	info.flags = 0; +	info.length = len; +	info.low_limit = TASK_UNMAPPED_BASE; +	info.high_limit = TASK_SIZE; +	info.align_mask = PAGE_MASK & HUGEPT_MASK; +	info.align_offset = 0; +	return vm_unmapped_area(&info); +} + +unsigned long +hugetlb_get_unmapped_area(struct file *file, unsigned long addr, +		unsigned long len, unsigned long pgoff, unsigned long flags) +{ +	struct hstate *h = hstate_file(file); + +	if (len & ~huge_page_mask(h)) +		return -EINVAL; +	if (len > TASK_SIZE) +		return -ENOMEM; + +	if (flags & MAP_FIXED) { +		if (prepare_hugepage_range(file, addr, len)) +			return -EINVAL; +		return addr; +	} + +	if (addr) { +		addr = ALIGN(addr, huge_page_size(h)); +		if (!prepare_hugepage_range(file, addr, len)) +			return addr; +	} + +	/* +	 * Look for an existing hugetlb vma with space after it (this is to to +	 * minimise fragmentation caused by huge pages. +	 */ +	addr = hugetlb_get_unmapped_area_existing(len); +	if (addr) +		return addr; + +	/* +	 * Find an unmapped naturally aligned set of 4MB blocks that we can use +	 * for huge pages. +	 */ +	return hugetlb_get_unmapped_area_new_pmd(len); +} + +#endif /*HAVE_ARCH_HUGETLB_UNMAPPED_AREA*/ + +/* necessary for boot time 4MB huge page allocation */ +static __init int setup_hugepagesz(char *opt) +{ +	unsigned long ps = memparse(opt, &opt); +	if (ps == (1 << HPAGE_SHIFT)) { +		hugetlb_add_hstate(HPAGE_SHIFT - PAGE_SHIFT); +	} else { +		pr_err("hugepagesz: Unsupported page size %lu M\n", +		       ps >> 20); +		return 0; +	} +	return 1; +} +__setup("hugepagesz=", setup_hugepagesz); diff --git a/arch/metag/mm/init.c b/arch/metag/mm/init.c new file mode 100644 index 00000000000..11fa51c8961 --- /dev/null +++ b/arch/metag/mm/init.c @@ -0,0 +1,407 @@ +/* + *  Copyright (C) 2005,2006,2007,2008,2009,2010 Imagination Technologies + * + */ + +#include <linux/export.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/init.h> +#include <linux/bootmem.h> +#include <linux/pagemap.h> +#include <linux/percpu.h> +#include <linux/memblock.h> +#include <linux/initrd.h> + +#include <asm/setup.h> +#include <asm/page.h> +#include <asm/pgalloc.h> +#include <asm/mmu.h> +#include <asm/mmu_context.h> +#include <asm/sections.h> +#include <asm/tlb.h> +#include <asm/user_gateway.h> +#include <asm/mmzone.h> +#include <asm/fixmap.h> + +unsigned long pfn_base; +EXPORT_SYMBOL(pfn_base); + +pgd_t swapper_pg_dir[PTRS_PER_PGD] __page_aligned_data; + +unsigned long empty_zero_page; +EXPORT_SYMBOL(empty_zero_page); + +extern char __user_gateway_start; +extern char __user_gateway_end; + +void *gateway_page; + +/* + * Insert the gateway page into a set of page tables, creating the + * page tables if necessary. + */ +static void insert_gateway_page(pgd_t *pgd, unsigned long address) +{ +	pud_t *pud; +	pmd_t *pmd; +	pte_t *pte; + +	BUG_ON(!pgd_present(*pgd)); + +	pud = pud_offset(pgd, address); +	BUG_ON(!pud_present(*pud)); + +	pmd = pmd_offset(pud, address); +	if (!pmd_present(*pmd)) { +		pte = alloc_bootmem_pages(PAGE_SIZE); +		set_pmd(pmd, __pmd(_PAGE_TABLE | __pa(pte))); +	} + +	pte = pte_offset_kernel(pmd, address); +	set_pte(pte, pfn_pte(__pa(gateway_page) >> PAGE_SHIFT, PAGE_READONLY)); +} + +/* Alloc and map a page in a known location accessible to userspace. */ +static void __init user_gateway_init(void) +{ +	unsigned long address = USER_GATEWAY_PAGE; +	int offset = pgd_index(address); +	pgd_t *pgd; + +	gateway_page = alloc_bootmem_pages(PAGE_SIZE); + +	pgd = swapper_pg_dir + offset; +	insert_gateway_page(pgd, address); + +#ifdef CONFIG_METAG_META12 +	/* +	 * Insert the gateway page into our current page tables even +	 * though we've already inserted it into our reference page +	 * table (swapper_pg_dir). This is because with a META1 mmu we +	 * copy just the user address range and not the gateway page +	 * entry on context switch, see switch_mmu(). +	 */ +	pgd = (pgd_t *)mmu_get_base() + offset; +	insert_gateway_page(pgd, address); +#endif /* CONFIG_METAG_META12 */ + +	BUG_ON((&__user_gateway_end - &__user_gateway_start) > PAGE_SIZE); + +	gateway_page += (address & ~PAGE_MASK); + +	memcpy(gateway_page, &__user_gateway_start, +	       &__user_gateway_end - &__user_gateway_start); + +	/* +	 * We don't need to flush the TLB here, there should be no mapping +	 * present at boot for this address and only valid mappings are in +	 * the TLB (apart from on Meta 1.x, but those cached invalid +	 * mappings should be impossible to hit here). +	 * +	 * We don't flush the code cache here even though we have written +	 * code through the data cache and they may not be coherent. At +	 * this point we assume there is no stale data in the code cache +	 * for this address so there is no need to flush. +	 */ +} + +static void __init allocate_pgdat(unsigned int nid) +{ +	unsigned long start_pfn, end_pfn; +#ifdef CONFIG_NEED_MULTIPLE_NODES +	unsigned long phys; +#endif + +	get_pfn_range_for_nid(nid, &start_pfn, &end_pfn); + +#ifdef CONFIG_NEED_MULTIPLE_NODES +	phys = __memblock_alloc_base(sizeof(struct pglist_data), +				SMP_CACHE_BYTES, end_pfn << PAGE_SHIFT); +	/* Retry with all of system memory */ +	if (!phys) +		phys = __memblock_alloc_base(sizeof(struct pglist_data), +					     SMP_CACHE_BYTES, +					     memblock_end_of_DRAM()); +	if (!phys) +		panic("Can't allocate pgdat for node %d\n", nid); + +	NODE_DATA(nid) = __va(phys); +	memset(NODE_DATA(nid), 0, sizeof(struct pglist_data)); + +	NODE_DATA(nid)->bdata = &bootmem_node_data[nid]; +#endif + +	NODE_DATA(nid)->node_start_pfn = start_pfn; +	NODE_DATA(nid)->node_spanned_pages = end_pfn - start_pfn; +} + +static void __init bootmem_init_one_node(unsigned int nid) +{ +	unsigned long total_pages, paddr; +	unsigned long end_pfn; +	struct pglist_data *p; + +	p = NODE_DATA(nid); + +	/* Nothing to do.. */ +	if (!p->node_spanned_pages) +		return; + +	end_pfn = pgdat_end_pfn(p); +#ifdef CONFIG_HIGHMEM +	if (end_pfn > max_low_pfn) +		end_pfn = max_low_pfn; +#endif + +	total_pages = bootmem_bootmap_pages(end_pfn - p->node_start_pfn); + +	paddr = memblock_alloc(total_pages << PAGE_SHIFT, PAGE_SIZE); +	if (!paddr) +		panic("Can't allocate bootmap for nid[%d]\n", nid); + +	init_bootmem_node(p, paddr >> PAGE_SHIFT, p->node_start_pfn, end_pfn); + +	free_bootmem_with_active_regions(nid, end_pfn); + +	/* +	 * XXX Handle initial reservations for the system memory node +	 * only for the moment, we'll refactor this later for handling +	 * reservations in other nodes. +	 */ +	if (nid == 0) { +		struct memblock_region *reg; + +		/* Reserve the sections we're already using. */ +		for_each_memblock(reserved, reg) { +			unsigned long size = reg->size; + +#ifdef CONFIG_HIGHMEM +			/* ...but not highmem */ +			if (PFN_DOWN(reg->base) >= highstart_pfn) +				continue; + +			if (PFN_UP(reg->base + size) > highstart_pfn) +				size = (highstart_pfn - PFN_DOWN(reg->base)) +				       << PAGE_SHIFT; +#endif + +			reserve_bootmem(reg->base, size, BOOTMEM_DEFAULT); +		} +	} + +	sparse_memory_present_with_active_regions(nid); +} + +static void __init do_init_bootmem(void) +{ +	struct memblock_region *reg; +	int i; + +	/* Add active regions with valid PFNs. */ +	for_each_memblock(memory, reg) { +		unsigned long start_pfn, end_pfn; +		start_pfn = memblock_region_memory_base_pfn(reg); +		end_pfn = memblock_region_memory_end_pfn(reg); +		memblock_set_node(PFN_PHYS(start_pfn), +				  PFN_PHYS(end_pfn - start_pfn), +				  &memblock.memory, 0); +	} + +	/* All of system RAM sits in node 0 for the non-NUMA case */ +	allocate_pgdat(0); +	node_set_online(0); + +	soc_mem_setup(); + +	for_each_online_node(i) +		bootmem_init_one_node(i); + +	sparse_init(); +} + +extern char _heap_start[]; + +static void __init init_and_reserve_mem(void) +{ +	unsigned long start_pfn, heap_start; +	u64 base = min_low_pfn << PAGE_SHIFT; +	u64 size = (max_low_pfn << PAGE_SHIFT) - base; + +	heap_start = (unsigned long) &_heap_start; + +	memblock_add(base, size); + +	/* +	 * Partially used pages are not usable - thus +	 * we are rounding upwards: +	 */ +	start_pfn = PFN_UP(__pa(heap_start)); + +	/* +	 * Reserve the kernel text. +	 */ +	memblock_reserve(base, (PFN_PHYS(start_pfn) + PAGE_SIZE - 1) - base); + +#ifdef CONFIG_HIGHMEM +	/* +	 * Add & reserve highmem, so page structures are initialised. +	 */ +	base = highstart_pfn << PAGE_SHIFT; +	size = (highend_pfn << PAGE_SHIFT) - base; +	if (size) { +		memblock_add(base, size); +		memblock_reserve(base, size); +	} +#endif +} + +#ifdef CONFIG_HIGHMEM +/* + * Ensure we have allocated page tables in swapper_pg_dir for the + * fixed mappings range from 'start' to 'end'. + */ +static void __init allocate_pgtables(unsigned long start, unsigned long end) +{ +	pgd_t *pgd; +	pmd_t *pmd; +	pte_t *pte; +	int i, j; +	unsigned long vaddr; + +	vaddr = start; +	i = pgd_index(vaddr); +	j = pmd_index(vaddr); +	pgd = swapper_pg_dir + i; + +	for ( ; (i < PTRS_PER_PGD) && (vaddr != end); pgd++, i++) { +		pmd = (pmd_t *)pgd; +		for (; (j < PTRS_PER_PMD) && (vaddr != end); pmd++, j++) { +			vaddr += PMD_SIZE; + +			if (!pmd_none(*pmd)) +				continue; + +			pte = (pte_t *)alloc_bootmem_low_pages(PAGE_SIZE); +			pmd_populate_kernel(&init_mm, pmd, pte); +		} +		j = 0; +	} +} + +static void __init fixedrange_init(void) +{ +	unsigned long vaddr, end; +	pgd_t *pgd; +	pud_t *pud; +	pmd_t *pmd; +	pte_t *pte; + +	/* +	 * Fixed mappings: +	 */ +	vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK; +	end = (FIXADDR_TOP + PMD_SIZE - 1) & PMD_MASK; +	allocate_pgtables(vaddr, end); + +	/* +	 * Permanent kmaps: +	 */ +	vaddr = PKMAP_BASE; +	allocate_pgtables(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP); + +	pgd = swapper_pg_dir + pgd_index(vaddr); +	pud = pud_offset(pgd, vaddr); +	pmd = pmd_offset(pud, vaddr); +	pte = pte_offset_kernel(pmd, vaddr); +	pkmap_page_table = pte; +} +#endif /* CONFIG_HIGHMEM */ + +/* + * paging_init() continues the virtual memory environment setup which + * was begun by the code in arch/metag/kernel/setup.c. + */ +void __init paging_init(unsigned long mem_end) +{ +	unsigned long max_zone_pfns[MAX_NR_ZONES]; +	int nid; + +	init_and_reserve_mem(); + +	memblock_allow_resize(); + +	memblock_dump_all(); + +	nodes_clear(node_online_map); + +	init_new_context(&init_task, &init_mm); + +	memset(swapper_pg_dir, 0, sizeof(swapper_pg_dir)); + +	do_init_bootmem(); +	mmu_init(mem_end); + +#ifdef CONFIG_HIGHMEM +	fixedrange_init(); +	kmap_init(); +#endif + +	/* Initialize the zero page to a bootmem page, already zeroed. */ +	empty_zero_page = (unsigned long)alloc_bootmem_pages(PAGE_SIZE); + +	user_gateway_init(); + +	memset(max_zone_pfns, 0, sizeof(max_zone_pfns)); + +	for_each_online_node(nid) { +		pg_data_t *pgdat = NODE_DATA(nid); +		unsigned long low, start_pfn; + +		start_pfn = pgdat->bdata->node_min_pfn; +		low = pgdat->bdata->node_low_pfn; + +		if (max_zone_pfns[ZONE_NORMAL] < low) +			max_zone_pfns[ZONE_NORMAL] = low; + +#ifdef CONFIG_HIGHMEM +		max_zone_pfns[ZONE_HIGHMEM] = highend_pfn; +#endif +		pr_info("Node %u: start_pfn = 0x%lx, low = 0x%lx\n", +			nid, start_pfn, low); +	} + +	free_area_init_nodes(max_zone_pfns); +} + +void __init mem_init(void) +{ +#ifdef CONFIG_HIGHMEM +	unsigned long tmp; + +	/* +	 * Explicitly reset zone->managed_pages because highmem pages are +	 * freed before calling free_all_bootmem(); +	 */ +	reset_all_zones_managed_pages(); +	for (tmp = highstart_pfn; tmp < highend_pfn; tmp++) +		free_highmem_page(pfn_to_page(tmp)); +#endif /* CONFIG_HIGHMEM */ + +	free_all_bootmem(); +	mem_init_print_info(NULL); +	show_mem(0); +} + +void free_initmem(void) +{ +	free_initmem_default(POISON_FREE_INITMEM); +} + +#ifdef CONFIG_BLK_DEV_INITRD +void free_initrd_mem(unsigned long start, unsigned long end) +{ +	free_reserved_area((void *)start, (void *)end, POISON_FREE_INITMEM, +			   "initrd"); +} +#endif diff --git a/arch/metag/mm/ioremap.c b/arch/metag/mm/ioremap.c new file mode 100644 index 00000000000..a136a435fda --- /dev/null +++ b/arch/metag/mm/ioremap.c @@ -0,0 +1,89 @@ +/* + * Re-map IO memory to kernel address space so that we can access it. + * Needed for memory-mapped I/O devices mapped outside our normal DRAM + * window (that is, all memory-mapped I/O devices). + * + * Copyright (C) 1995,1996 Linus Torvalds + * + * Meta port based on CRIS-port by Axis Communications AB + */ + +#include <linux/vmalloc.h> +#include <linux/io.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/mm.h> + +#include <asm/pgtable.h> + +/* + * Remap an arbitrary physical address space into the kernel virtual + * address space. Needed when the kernel wants to access high addresses + * directly. + * + * NOTE! We need to allow non-page-aligned mappings too: we will obviously + * have to convert them into an offset in a page-aligned mapping, but the + * caller shouldn't need to know that small detail. + */ +void __iomem *__ioremap(unsigned long phys_addr, size_t size, +			unsigned long flags) +{ +	unsigned long addr; +	struct vm_struct *area; +	unsigned long offset, last_addr; +	pgprot_t prot; + +	/* Don't allow wraparound or zero size */ +	last_addr = phys_addr + size - 1; +	if (!size || last_addr < phys_addr) +		return NULL; + +	/* Custom region addresses are accessible and uncached by default. */ +	if (phys_addr >= LINSYSCUSTOM_BASE && +	    phys_addr < (LINSYSCUSTOM_BASE + LINSYSCUSTOM_LIMIT)) +		return (__force void __iomem *) phys_addr; + +	/* +	 * Mappings have to be page-aligned +	 */ +	offset = phys_addr & ~PAGE_MASK; +	phys_addr &= PAGE_MASK; +	size = PAGE_ALIGN(last_addr+1) - phys_addr; +	prot = __pgprot(_PAGE_PRESENT | _PAGE_WRITE | _PAGE_DIRTY | +			_PAGE_ACCESSED | _PAGE_KERNEL | _PAGE_CACHE_WIN0 | +			flags); + +	/* +	 * Ok, go for it.. +	 */ +	area = get_vm_area(size, VM_IOREMAP); +	if (!area) +		return NULL; +	area->phys_addr = phys_addr; +	addr = (unsigned long) area->addr; +	if (ioremap_page_range(addr, addr + size, phys_addr, prot)) { +		vunmap((void *) addr); +		return NULL; +	} +	return (__force void __iomem *) (offset + (char *)addr); +} +EXPORT_SYMBOL(__ioremap); + +void __iounmap(void __iomem *addr) +{ +	struct vm_struct *p; + +	if ((__force unsigned long)addr >= LINSYSCUSTOM_BASE && +	    (__force unsigned long)addr < (LINSYSCUSTOM_BASE + +					   LINSYSCUSTOM_LIMIT)) +		return; + +	p = remove_vm_area((void *)(PAGE_MASK & (unsigned long __force)addr)); +	if (unlikely(!p)) { +		pr_err("iounmap: bad address %p\n", addr); +		return; +	} + +	kfree(p); +} +EXPORT_SYMBOL(__iounmap); diff --git a/arch/metag/mm/l2cache.c b/arch/metag/mm/l2cache.c new file mode 100644 index 00000000000..c64ee615cf9 --- /dev/null +++ b/arch/metag/mm/l2cache.c @@ -0,0 +1,192 @@ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/delay.h> + +#include <asm/l2cache.h> +#include <asm/metag_isa.h> + +/* If non-0, then initialise the L2 cache */ +static int l2cache_init = 1; +/* If non-0, then initialise the L2 cache prefetch */ +static int l2cache_init_pf = 1; + +int l2c_pfenable; + +static volatile u32 l2c_testdata[16] __initdata __aligned(64); + +static int __init parse_l2cache(char *p) +{ +	char *cp = p; + +	if (get_option(&cp, &l2cache_init) != 1) { +		pr_err("Bad l2cache parameter (%s)\n", p); +		return 1; +	} +	return 0; +} +early_param("l2cache", parse_l2cache); + +static int __init parse_l2cache_pf(char *p) +{ +	char *cp = p; + +	if (get_option(&cp, &l2cache_init_pf) != 1) { +		pr_err("Bad l2cache_pf parameter (%s)\n", p); +		return 1; +	} +	return 0; +} +early_param("l2cache_pf", parse_l2cache_pf); + +static int __init meta_l2c_setup(void) +{ +	/* +	 * If the L2 cache isn't even present, don't do anything, but say so in +	 * the log. +	 */ +	if (!meta_l2c_is_present()) { +		pr_info("L2 Cache: Not present\n"); +		return 0; +	} + +	/* +	 * Check whether the line size is recognised. +	 */ +	if (!meta_l2c_linesize()) { +		pr_warn_once("L2 Cache: unknown line size id (config=0x%08x)\n", +			     meta_l2c_config()); +	} + +	/* +	 * Initialise state. +	 */ +	l2c_pfenable = _meta_l2c_pf_is_enabled(); + +	/* +	 * Enable the L2 cache and print to log whether it was already enabled +	 * by the bootloader. +	 */ +	if (l2cache_init) { +		pr_info("L2 Cache: Enabling... "); +		if (meta_l2c_enable()) +			pr_cont("already enabled\n"); +		else +			pr_cont("done\n"); +	} else { +		pr_info("L2 Cache: Not enabling\n"); +	} + +	/* +	 * Enable L2 cache prefetch. +	 */ +	if (l2cache_init_pf) { +		pr_info("L2 Cache: Enabling prefetch... "); +		if (meta_l2c_pf_enable(1)) +			pr_cont("already enabled\n"); +		else +			pr_cont("done\n"); +	} else { +		pr_info("L2 Cache: Not enabling prefetch\n"); +	} + +	return 0; +} +core_initcall(meta_l2c_setup); + +int meta_l2c_disable(void) +{ +	unsigned long flags; +	int en; + +	if (!meta_l2c_is_present()) +		return 1; + +	/* +	 * Prevent other threads writing during the writeback, otherwise the +	 * writes will get "lost" when the L2 is disabled. +	 */ +	__global_lock2(flags); +	en = meta_l2c_is_enabled(); +	if (likely(en)) { +		_meta_l2c_pf_enable(0); +		wr_fence(); +		_meta_l2c_purge(); +		_meta_l2c_enable(0); +	} +	__global_unlock2(flags); + +	return !en; +} + +int meta_l2c_enable(void) +{ +	unsigned long flags; +	int en; + +	if (!meta_l2c_is_present()) +		return 0; + +	/* +	 * Init (clearing the L2) can happen while the L2 is disabled, so other +	 * threads are safe to continue executing, however we must not init the +	 * cache if it's already enabled (dirty lines would be discarded), so +	 * this operation should still be atomic with other threads. +	 */ +	__global_lock1(flags); +	en = meta_l2c_is_enabled(); +	if (likely(!en)) { +		_meta_l2c_init(); +		_meta_l2c_enable(1); +		_meta_l2c_pf_enable(l2c_pfenable); +	} +	__global_unlock1(flags); + +	return en; +} + +int meta_l2c_pf_enable(int pfenable) +{ +	unsigned long flags; +	int en = l2c_pfenable; + +	if (!meta_l2c_is_present()) +		return 0; + +	/* +	 * We read modify write the enable register, so this operation must be +	 * atomic with other threads. +	 */ +	__global_lock1(flags); +	en = l2c_pfenable; +	l2c_pfenable = pfenable; +	if (meta_l2c_is_enabled()) +		_meta_l2c_pf_enable(pfenable); +	__global_unlock1(flags); + +	return en; +} + +int meta_l2c_flush(void) +{ +	unsigned long flags; +	int en; + +	/* +	 * Prevent other threads writing during the writeback. This also +	 * involves read modify writes. +	 */ +	__global_lock2(flags); +	en = meta_l2c_is_enabled(); +	if (likely(en)) { +		_meta_l2c_pf_enable(0); +		wr_fence(); +		_meta_l2c_purge(); +		_meta_l2c_enable(0); +		_meta_l2c_init(); +		_meta_l2c_enable(1); +		_meta_l2c_pf_enable(l2c_pfenable); +	} +	__global_unlock2(flags); + +	return !en; +} diff --git a/arch/metag/mm/maccess.c b/arch/metag/mm/maccess.c new file mode 100644 index 00000000000..eba2cfc935b --- /dev/null +++ b/arch/metag/mm/maccess.c @@ -0,0 +1,68 @@ +/* + * safe read and write memory routines callable while atomic + * + * Copyright 2012 Imagination Technologies + */ + +#include <linux/uaccess.h> +#include <asm/io.h> + +/* + * The generic probe_kernel_write() uses the user copy code which can split the + * writes if the source is unaligned, and repeats writes to make exceptions + * precise. We override it here to avoid these things happening to memory mapped + * IO memory where they could have undesired effects. + * Due to the use of CACHERD instruction this only works on Meta2 onwards. + */ +#ifdef CONFIG_METAG_META21 +long probe_kernel_write(void *dst, const void *src, size_t size) +{ +	unsigned long ldst = (unsigned long)dst; +	void __iomem *iodst = (void __iomem *)dst; +	unsigned long lsrc = (unsigned long)src; +	const u8 *psrc = (u8 *)src; +	unsigned int pte, i; +	u8 bounce[8] __aligned(8); + +	if (!size) +		return 0; + +	/* Use the write combine bit to decide is the destination is MMIO. */ +	pte = __builtin_meta2_cacherd(dst); + +	/* Check the mapping is valid and writeable. */ +	if ((pte & (MMCU_ENTRY_WR_BIT | MMCU_ENTRY_VAL_BIT)) +	    != (MMCU_ENTRY_WR_BIT | MMCU_ENTRY_VAL_BIT)) +		return -EFAULT; + +	/* Fall back to generic version for cases we're not interested in. */ +	if (pte & MMCU_ENTRY_WRC_BIT	|| /* write combined memory */ +	    (ldst & (size - 1))		|| /* destination unaligned */ +	    size > 8			|| /* more than max write size */ +	    (size & (size - 1)))	   /* non power of 2 size */ +		return __probe_kernel_write(dst, src, size); + +	/* If src is unaligned, copy to the aligned bounce buffer first. */ +	if (lsrc & (size - 1)) { +		for (i = 0; i < size; ++i) +			bounce[i] = psrc[i]; +		psrc = bounce; +	} + +	switch (size) { +	case 1: +		writeb(*psrc, iodst); +		break; +	case 2: +		writew(*(const u16 *)psrc, iodst); +		break; +	case 4: +		writel(*(const u32 *)psrc, iodst); +		break; +	case 8: +		writeq(*(const u64 *)psrc, iodst); +		break; +	} +	return 0; +} +#endif diff --git a/arch/metag/mm/mmu-meta1.c b/arch/metag/mm/mmu-meta1.c new file mode 100644 index 00000000000..91f4255bcb5 --- /dev/null +++ b/arch/metag/mm/mmu-meta1.c @@ -0,0 +1,157 @@ +/* + *  Copyright (C) 2005,2006,2007,2008,2009 Imagination Technologies + * + * Meta 1 MMU handling code. + * + */ + +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/io.h> + +#include <asm/mmu.h> + +#define DM3_BASE (LINSYSDIRECT_BASE + (MMCU_DIRECTMAPn_ADDR_SCALE * 3)) + +/* + * This contains the physical address of the top level 2k pgd table. + */ +static unsigned long mmu_base_phys; + +/* + * Given a physical address, return a mapped virtual address that can be used + * to access that location. + * In practice, we use the DirectMap region to make this happen. + */ +static unsigned long map_addr(unsigned long phys) +{ +	static unsigned long dm_base = 0xFFFFFFFF; +	int offset; + +	offset = phys - dm_base; + +	/* Are we in the current map range ? */ +	if ((offset < 0) || (offset >= MMCU_DIRECTMAPn_ADDR_SCALE)) { +		/* Calculate new DM area */ +		dm_base = phys & ~(MMCU_DIRECTMAPn_ADDR_SCALE - 1); + +		/* Actually map it in! */ +		metag_out32(dm_base, MMCU_DIRECTMAP3_ADDR); + +		/* And calculate how far into that area our reference is */ +		offset = phys - dm_base; +	} + +	return DM3_BASE + offset; +} + +/* + * Return the physical address of the base of our pgd table. + */ +static inline unsigned long __get_mmu_base(void) +{ +	unsigned long base_phys; +	unsigned int stride; + +	if (is_global_space(PAGE_OFFSET)) +		stride = 4; +	else +		stride = hard_processor_id();	/* [0..3] */ + +	base_phys = metag_in32(MMCU_TABLE_PHYS_ADDR); +	base_phys += (0x800 * stride); + +	return base_phys; +} + +/* Given a virtual address, return the virtual address of the relevant pgd */ +static unsigned long pgd_entry_addr(unsigned long virt) +{ +	unsigned long pgd_phys; +	unsigned long pgd_virt; + +	if (!mmu_base_phys) +		mmu_base_phys = __get_mmu_base(); + +	/* +	 * Are we trying to map a global address.  If so, then index +	 * the global pgd table instead of our local one. +	 */ +	if (is_global_space(virt)) { +		/* Scale into 2gig map */ +		virt &= ~0x80000000; +	} + +	/* Base of the pgd table plus our 4Meg entry, 4bytes each */ +	pgd_phys = mmu_base_phys + ((virt >> PGDIR_SHIFT) * 4); + +	pgd_virt = map_addr(pgd_phys); + +	return pgd_virt; +} + +/* Given a virtual address, return the virtual address of the relevant pte */ +static unsigned long pgtable_entry_addr(unsigned long virt) +{ +	unsigned long pgtable_phys; +	unsigned long pgtable_virt, pte_virt; + +	/* Find the physical address of the 4MB page table*/ +	pgtable_phys = metag_in32(pgd_entry_addr(virt)) & MMCU_ENTRY_ADDR_BITS; + +	/* Map it to a virtual address */ +	pgtable_virt = map_addr(pgtable_phys); + +	/* And index into it for our pte */ +	pte_virt = pgtable_virt + ((virt >> PAGE_SHIFT) & 0x3FF) * 4; + +	return pte_virt; +} + +unsigned long mmu_read_first_level_page(unsigned long vaddr) +{ +	return metag_in32(pgd_entry_addr(vaddr)); +} + +unsigned long mmu_read_second_level_page(unsigned long vaddr) +{ +	return metag_in32(pgtable_entry_addr(vaddr)); +} + +unsigned long mmu_get_base(void) +{ +	static unsigned long __base; + +	/* Find the base of our MMU pgd table */ +	if (!__base) +		__base = pgd_entry_addr(0); + +	return __base; +} + +void __init mmu_init(unsigned long mem_end) +{ +	unsigned long entry, addr; +	pgd_t *p_swapper_pg_dir; + +	/* +	 * Now copy over any MMU pgd entries already in the mmu page tables +	 * over to our root init process (swapper_pg_dir) map.  This map is +	 * then inherited by all other processes, which means all processes +	 * inherit a map of the kernel space. +	 */ +	addr = PAGE_OFFSET; +	entry = pgd_index(PAGE_OFFSET); +	p_swapper_pg_dir = pgd_offset_k(0) + entry; + +	while (addr <= META_MEMORY_LIMIT) { +		unsigned long pgd_entry; +		/* copy over the current MMU value */ +		pgd_entry = mmu_read_first_level_page(addr); +		pgd_val(*p_swapper_pg_dir) = pgd_entry; + +		p_swapper_pg_dir++; +		addr += PGDIR_SIZE; +		entry++; +	} +} diff --git a/arch/metag/mm/mmu-meta2.c b/arch/metag/mm/mmu-meta2.c new file mode 100644 index 00000000000..81dcbb0bba3 --- /dev/null +++ b/arch/metag/mm/mmu-meta2.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008,2009,2010,2011 Imagination Technologies Ltd. + * + * Meta 2 enhanced mode MMU handling code. + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/bootmem.h> +#include <linux/syscore_ops.h> + +#include <asm/mmu.h> +#include <asm/mmu_context.h> + +unsigned long mmu_read_first_level_page(unsigned long vaddr) +{ +	unsigned int cpu = hard_processor_id(); +	unsigned long offset, linear_base, linear_limit; +	unsigned int phys0; +	pgd_t *pgd, entry; + +	if (is_global_space(vaddr)) +		vaddr &= ~0x80000000; + +	offset = vaddr >> PGDIR_SHIFT; + +	phys0 = metag_in32(mmu_phys0_addr(cpu)); + +	/* Top bit of linear base is always zero. */ +	linear_base = (phys0 >> PGDIR_SHIFT) & 0x1ff; + +	/* Limit in the range 0 (4MB) to 9 (2GB). */ +	linear_limit = 1 << ((phys0 >> 8) & 0xf); +	linear_limit += linear_base; + +	/* +	 * If offset is below linear base or above the limit then no +	 * mapping exists. +	 */ +	if (offset < linear_base || offset > linear_limit) +		return 0; + +	offset -= linear_base; +	pgd = (pgd_t *)mmu_get_base(); +	entry = pgd[offset]; + +	return pgd_val(entry); +} + +unsigned long mmu_read_second_level_page(unsigned long vaddr) +{ +	return __builtin_meta2_cacherd((void *)(vaddr & PAGE_MASK)); +} + +unsigned long mmu_get_base(void) +{ +	unsigned int cpu = hard_processor_id(); +	unsigned long stride; + +	stride = cpu * LINSYSMEMTnX_STRIDE; + +	/* +	 * Bits 18:2 of the MMCU_TnLocal_TABLE_PHYS1 register should be +	 * used as an offset to the start of the top-level pgd table. +	 */ +	stride += (metag_in32(mmu_phys1_addr(cpu)) & 0x7fffc); + +	if (is_global_space(PAGE_OFFSET)) +		stride += LINSYSMEMTXG_OFFSET; + +	return LINSYSMEMT0L_BASE + stride; +} + +#define FIRST_LEVEL_MASK	0xffffffc0 +#define SECOND_LEVEL_MASK	0xfffff000 +#define SECOND_LEVEL_ALIGN	64 + +static void repriv_mmu_tables(void) +{ +	unsigned long phys0_addr; +	unsigned int g; + +	/* +	 * Check that all the mmu table regions are priv protected, and if not +	 * fix them and emit a warning. If we left them without priv protection +	 * then userland processes would have access to a 2M window into +	 * physical memory near where the page tables are. +	 */ +	phys0_addr = MMCU_T0LOCAL_TABLE_PHYS0; +	for (g = 0; g < 2; ++g) { +		unsigned int t, phys0; +		unsigned long flags; +		for (t = 0; t < 4; ++t) { +			__global_lock2(flags); +			phys0 = metag_in32(phys0_addr); +			if ((phys0 & _PAGE_PRESENT) && !(phys0 & _PAGE_PRIV)) { +				pr_warn("Fixing priv protection on T%d %s MMU table region\n", +					t, +					g ? "global" : "local"); +				phys0 |= _PAGE_PRIV; +				metag_out32(phys0, phys0_addr); +			} +			__global_unlock2(flags); + +			phys0_addr += MMCU_TnX_TABLE_PHYSX_STRIDE; +		} + +		phys0_addr += MMCU_TXG_TABLE_PHYSX_OFFSET +			    - 4*MMCU_TnX_TABLE_PHYSX_STRIDE; +	} +} + +#ifdef CONFIG_METAG_SUSPEND_MEM +static void mmu_resume(void) +{ +	/* +	 * If a full suspend to RAM has happened then the original bad MMU table +	 * priv may have been restored, so repriv them again. +	 */ +	repriv_mmu_tables(); +} +#else +#define mmu_resume NULL +#endif	/* CONFIG_METAG_SUSPEND_MEM */ + +static struct syscore_ops mmu_syscore_ops = { +	.resume  = mmu_resume, +}; + +void __init mmu_init(unsigned long mem_end) +{ +	unsigned long entry, addr; +	pgd_t *p_swapper_pg_dir; +#ifdef CONFIG_KERNEL_4M_PAGES +	unsigned long mem_size = mem_end - PAGE_OFFSET; +	unsigned int pages = DIV_ROUND_UP(mem_size, 1 << 22); +	unsigned int second_level_entry = 0; +	unsigned long *second_level_table; +#endif + +	/* +	 * Now copy over any MMU pgd entries already in the mmu page tables +	 * over to our root init process (swapper_pg_dir) map.  This map is +	 * then inherited by all other processes, which means all processes +	 * inherit a map of the kernel space. +	 */ +	addr = META_MEMORY_BASE; +	entry = pgd_index(META_MEMORY_BASE); +	p_swapper_pg_dir = pgd_offset_k(0) + entry; + +	while (entry < (PTRS_PER_PGD - pgd_index(META_MEMORY_BASE))) { +		unsigned long pgd_entry; +		/* copy over the current MMU value */ +		pgd_entry = mmu_read_first_level_page(addr); +		pgd_val(*p_swapper_pg_dir) = pgd_entry; + +		p_swapper_pg_dir++; +		addr += PGDIR_SIZE; +		entry++; +	} + +#ifdef CONFIG_KERNEL_4M_PAGES +	/* +	 * At this point we can also map the kernel with 4MB pages to +	 * reduce TLB pressure. +	 */ +	second_level_table = alloc_bootmem_pages(SECOND_LEVEL_ALIGN * pages); + +	addr = PAGE_OFFSET; +	entry = pgd_index(PAGE_OFFSET); +	p_swapper_pg_dir = pgd_offset_k(0) + entry; + +	while (pages > 0) { +		unsigned long phys_addr, second_level_phys; +		pte_t *pte = (pte_t *)&second_level_table[second_level_entry]; + +		phys_addr = __pa(addr); + +		second_level_phys = __pa(pte); + +		pgd_val(*p_swapper_pg_dir) = ((second_level_phys & +					       FIRST_LEVEL_MASK) | +					      _PAGE_SZ_4M | +					      _PAGE_PRESENT); + +		pte_val(*pte) = ((phys_addr & SECOND_LEVEL_MASK) | +				 _PAGE_PRESENT | _PAGE_DIRTY | +				 _PAGE_ACCESSED | _PAGE_WRITE | +				 _PAGE_CACHEABLE | _PAGE_KERNEL); + +		p_swapper_pg_dir++; +		addr += PGDIR_SIZE; +		/* Second level pages must be 64byte aligned. */ +		second_level_entry += (SECOND_LEVEL_ALIGN / +				       sizeof(unsigned long)); +		pages--; +	} +	load_pgd(swapper_pg_dir, hard_processor_id()); +	flush_tlb_all(); +#endif + +	repriv_mmu_tables(); +	register_syscore_ops(&mmu_syscore_ops); +} diff --git a/arch/metag/mm/numa.c b/arch/metag/mm/numa.c new file mode 100644 index 00000000000..67b46c29507 --- /dev/null +++ b/arch/metag/mm/numa.c @@ -0,0 +1,82 @@ +/* + *  Multiple memory node support for Meta machines + * + *  Copyright (C) 2007  Paul Mundt + *  Copyright (C) 2010  Imagination Technologies Ltd. + * + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/export.h> +#include <linux/bootmem.h> +#include <linux/memblock.h> +#include <linux/mm.h> +#include <linux/numa.h> +#include <linux/pfn.h> +#include <asm/sections.h> + +struct pglist_data *node_data[MAX_NUMNODES] __read_mostly; +EXPORT_SYMBOL_GPL(node_data); + +extern char _heap_start[]; + +/* + * On Meta machines the conventional approach is to stash system RAM + * in node 0, and other memory blocks in to node 1 and up, ordered by + * latency. Each node's pgdat is node-local at the beginning of the node, + * immediately followed by the node mem map. + */ +void __init setup_bootmem_node(int nid, unsigned long start, unsigned long end) +{ +	unsigned long bootmap_pages, bootmem_paddr; +	unsigned long start_pfn, end_pfn; +	unsigned long pgdat_paddr; + +	/* Don't allow bogus node assignment */ +	BUG_ON(nid >= MAX_NUMNODES || nid <= 0); + +	start_pfn = start >> PAGE_SHIFT; +	end_pfn = end >> PAGE_SHIFT; + +	memblock_add(start, end - start); + +	memblock_set_node(PFN_PHYS(start_pfn), +			  PFN_PHYS(end_pfn - start_pfn), +			  &memblock.memory, nid); + +	/* Node-local pgdat */ +	pgdat_paddr = memblock_alloc_base(sizeof(struct pglist_data), +					  SMP_CACHE_BYTES, end); +	NODE_DATA(nid) = __va(pgdat_paddr); +	memset(NODE_DATA(nid), 0, sizeof(struct pglist_data)); + +	NODE_DATA(nid)->bdata = &bootmem_node_data[nid]; +	NODE_DATA(nid)->node_start_pfn = start_pfn; +	NODE_DATA(nid)->node_spanned_pages = end_pfn - start_pfn; + +	/* Node-local bootmap */ +	bootmap_pages = bootmem_bootmap_pages(end_pfn - start_pfn); +	bootmem_paddr = memblock_alloc_base(bootmap_pages << PAGE_SHIFT, +					    PAGE_SIZE, end); +	init_bootmem_node(NODE_DATA(nid), bootmem_paddr >> PAGE_SHIFT, +			  start_pfn, end_pfn); + +	free_bootmem_with_active_regions(nid, end_pfn); + +	/* Reserve the pgdat and bootmap space with the bootmem allocator */ +	reserve_bootmem_node(NODE_DATA(nid), pgdat_paddr & PAGE_MASK, +			     sizeof(struct pglist_data), BOOTMEM_DEFAULT); +	reserve_bootmem_node(NODE_DATA(nid), bootmem_paddr, +			     bootmap_pages << PAGE_SHIFT, BOOTMEM_DEFAULT); + +	/* It's up */ +	node_set_online(nid); + +	/* Kick sparsemem */ +	sparse_memory_present_with_active_regions(nid); +} + +void __init __weak soc_mem_setup(void) +{ +}  | 
