diff options
Diffstat (limited to 'arch/s390/mm/mem_detect.c')
| -rw-r--r-- | arch/s390/mm/mem_detect.c | 134 | 
1 files changed, 134 insertions, 0 deletions
diff --git a/arch/s390/mm/mem_detect.c b/arch/s390/mm/mem_detect.c new file mode 100644 index 00000000000..3cbd3b8bf31 --- /dev/null +++ b/arch/s390/mm/mem_detect.c @@ -0,0 +1,134 @@ +/* + * Copyright IBM Corp. 2008, 2009 + * + * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/ipl.h> +#include <asm/sclp.h> +#include <asm/setup.h> + +#define ADDR2G (1ULL << 31) + +static void find_memory_chunks(struct mem_chunk chunk[], unsigned long maxsize) +{ +	unsigned long long memsize, rnmax, rzm; +	unsigned long addr = 0, size; +	int i = 0, type; + +	rzm = sclp_get_rzm(); +	rnmax = sclp_get_rnmax(); +	memsize = rzm * rnmax; +	if (!rzm) +		rzm = 1ULL << 17; +	if (sizeof(long) == 4) { +		rzm = min(ADDR2G, rzm); +		memsize = memsize ? min(ADDR2G, memsize) : ADDR2G; +	} +	if (maxsize) +		memsize = memsize ? min((unsigned long)memsize, maxsize) : maxsize; +	do { +		size = 0; +		type = tprot(addr); +		do { +			size += rzm; +			if (memsize && addr + size >= memsize) +				break; +		} while (type == tprot(addr + size)); +		if (type == CHUNK_READ_WRITE || type == CHUNK_READ_ONLY) { +			if (memsize && (addr + size > memsize)) +				size = memsize - addr; +			chunk[i].addr = addr; +			chunk[i].size = size; +			chunk[i].type = type; +			i++; +		} +		addr += size; +	} while (addr < memsize && i < MEMORY_CHUNKS); +} + +/** + * detect_memory_layout - fill mem_chunk array with memory layout data + * @chunk: mem_chunk array to be filled + * @maxsize: maximum address where memory detection should stop + * + * Fills the passed in memory chunk array with the memory layout of the + * machine. The array must have a size of at least MEMORY_CHUNKS and will + * be fully initialized afterwards. + * If the maxsize paramater has a value > 0 memory detection will stop at + * that address. It is guaranteed that all chunks have an ending address + * that is smaller than maxsize. + * If maxsize is 0 all memory will be detected. + */ +void detect_memory_layout(struct mem_chunk chunk[], unsigned long maxsize) +{ +	unsigned long flags, flags_dat, cr0; + +	memset(chunk, 0, MEMORY_CHUNKS * sizeof(struct mem_chunk)); +	/* +	 * Disable IRQs, DAT and low address protection so tprot does the +	 * right thing and we don't get scheduled away with low address +	 * protection disabled. +	 */ +	local_irq_save(flags); +	flags_dat = __arch_local_irq_stnsm(0xfb); +	/* +	 * In case DAT was enabled, make sure chunk doesn't reside in vmalloc +	 * space. We have disabled DAT and any access to vmalloc area will +	 * cause an exception. +	 * If DAT was disabled we are called from early ipl code. +	 */ +	if (test_bit(5, &flags_dat)) { +		if (WARN_ON_ONCE(is_vmalloc_or_module_addr(chunk))) +			goto out; +	} +	__ctl_store(cr0, 0, 0); +	__ctl_clear_bit(0, 28); +	find_memory_chunks(chunk, maxsize); +	__ctl_load(cr0, 0, 0); +out: +	__arch_local_irq_ssm(flags_dat); +	local_irq_restore(flags); +} +EXPORT_SYMBOL(detect_memory_layout); + +/* + * Create memory hole with given address and size. + */ +void create_mem_hole(struct mem_chunk mem_chunk[], unsigned long addr, +		     unsigned long size) +{ +	int i; + +	for (i = 0; i < MEMORY_CHUNKS; i++) { +		struct mem_chunk *chunk = &mem_chunk[i]; + +		if (chunk->size == 0) +			continue; +		if (addr > chunk->addr + chunk->size) +			continue; +		if (addr + size <= chunk->addr) +			continue; +		/* Split */ +		if ((addr > chunk->addr) && +		    (addr + size < chunk->addr + chunk->size)) { +			struct mem_chunk *new = chunk + 1; + +			memmove(new, chunk, (MEMORY_CHUNKS-i-1) * sizeof(*new)); +			new->addr = addr + size; +			new->size = chunk->addr + chunk->size - new->addr; +			chunk->size = addr - chunk->addr; +			continue; +		} else if ((addr <= chunk->addr) && +			   (addr + size >= chunk->addr + chunk->size)) { +			memset(chunk, 0 , sizeof(*chunk)); +		} else if (addr + size < chunk->addr + chunk->size) { +			chunk->size =  chunk->addr + chunk->size - addr - size; +			chunk->addr = addr + size; +		} else if (addr > chunk->addr) { +			chunk->size = addr - chunk->addr; +		} +	} +}  |