/* * 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)) { memmove(chunk, chunk + 1, (MEMORY_CHUNKS-i-1) * sizeof(*chunk)); memset(&mem_chunk[MEMORY_CHUNKS-1], 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; } } }