/* This file is part of jpcsp. Jpcsp 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 3 of the License, or (at your option) any later version. Jpcsp 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 Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.kernel.types; import org.apache.log4j.Logger; import jpcsp.HLE.modules.SysMemUserForUser; import jpcsp.util.Utilities; public class MemoryChunkList { protected static Logger log = SysMemUserForUser.log; // The MemoryChunk objects are linked and kept sorted by address. // // low: MemoryChunk with the lowest address. // Start point to scan list by increasing address private MemoryChunk low; // high: MemoryChunk with the highest address. // Start point to scan the list by decreasing address private MemoryChunk high; public MemoryChunkList(MemoryChunk initialMemoryChunk) { low = initialMemoryChunk; high = initialMemoryChunk; } /** * Remove a MemoryChunk from the list. * * @param memoryChunk the MemoryChunk to be removed */ public void remove(MemoryChunk memoryChunk) { if (memoryChunk.previous != null) { memoryChunk.previous.next = memoryChunk.next; } if (memoryChunk.next != null) { memoryChunk.next.previous = memoryChunk.previous; } if (low == memoryChunk) { low = memoryChunk.next; } if (high == memoryChunk) { high = memoryChunk.previous; } } /** * Allocate a memory at the lowest address. * * @param size the size of the memory to be allocated * @param addrAlignment base address alignment of the requested block * @return the base address of the allocated memory, * 0 if the memory could not be allocated */ public int allocLow(int size, int addrAlignment) { for (MemoryChunk memoryChunk = low; memoryChunk != null; memoryChunk = memoryChunk.next) { if (memoryChunk.isAvailable(size, addrAlignment)) { return allocLow(memoryChunk, size, addrAlignment); } } return 0; } /** * Allocate a memory at the highest address. * * @param size the size of the memory to be allocated * @param addrAlignment base address alignment of the requested block * @return the base address of the allocated memory * 0 if the memory could not be allocated */ public int allocHigh(int size, int addrAlignment) { for (MemoryChunk memoryChunk = high; memoryChunk != null; memoryChunk = memoryChunk.previous) { if (memoryChunk.isAvailable(size, addrAlignment)) { return allocHigh(memoryChunk, size, addrAlignment); } } return 0; } /** * Allocate a memory at the given base address. * * @param addr the base address of the memory to be allocated * @param size the size of the memory to be allocated * @return the base address of the allocated memory, * 0 if the memory could not be allocated */ public int alloc(int addr, int size) { for (MemoryChunk memoryChunk = low; memoryChunk != null; memoryChunk = memoryChunk.next) { if (memoryChunk.addr <= addr && addr < memoryChunk.addr + memoryChunk.size) { return alloc(memoryChunk, addr, size); } } return 0; } /** * Allocate a memory from the MemoryChunk, at its lowest address. * The MemoryChunk is updated accordingly or is removed if it stays empty. * * @param memoryChunk the MemoryChunk where the memory should be allocated * @param size the size of the memory to be allocated * @param addrAlignment base address alignment of the requested block * @return the base address of the allocated memory */ private int allocLow(MemoryChunk memoryChunk, int size, int addrAlignment) { int addr = Utilities.alignUp(memoryChunk.addr, addrAlignment); return alloc(memoryChunk, addr, size); } /** * Allocate a memory from the MemoryChunk, at its highest address. * The MemoryChunk is updated accordingly or is removed if it stays empty. * * @param memoryChunk the MemoryChunk where the memory should be allocated * @param size the size of the memory to be allocated * @param addrAlignment base address alignment of the requested block * @return the base address of the allocated memory */ private int allocHigh(MemoryChunk memoryChunk, int size, int addrAlignment) { int addr = Utilities.alignDown(memoryChunk.addr + memoryChunk.size, addrAlignment) - size; return alloc(memoryChunk, addr, size); } /** * Allocate a memory from the MemoryChunk, given the base address. * The base address must be inside the MemoryChunk * The MemoryChunk is updated accordingly, is removed if it stays empty or * is split into 2 remaining free parts. * * @param memoryChunk the MemoryChunk where the memory should be allocated * @param addr the base address of the memory to be allocated * @param size the size of the memory to be allocated * @return the base address of the allocated memory, or 0 * if the MemoryChunk is too small to allocate the desired size. */ private int alloc(MemoryChunk memoryChunk, int addr, int size) { if (addr < memoryChunk.addr || memoryChunk.addr + memoryChunk.size < addr + size) { // The MemoryChunk is too small to allocate the desired size // are the requested address is outside the MemoryChunk return 0; } else if (memoryChunk.size == size) { // Allocate the complete MemoryChunk remove(memoryChunk); } else if (memoryChunk.addr == addr) { // Allocate at the lowest address memoryChunk.size -= size; memoryChunk.addr += size; } else if (memoryChunk.addr + memoryChunk.size == addr + size) { // Allocate at the highest address memoryChunk.size -= size; } else { // Allocate in the middle of a MemoryChunk: it must be split // in 2 parts: one for lowest part and one for the highest part. // Update memoryChunk to contain the lowest part, // and create a new MemoryChunk to contain to highest part. int lowSize = addr - memoryChunk.addr; int highSize = memoryChunk.size - lowSize - size; MemoryChunk highMemoryChunk = new MemoryChunk(addr + size, highSize); memoryChunk.size = lowSize; addAfter(highMemoryChunk, memoryChunk); } sanityChecks(); return addr; } /** * Add a new MemoryChunk after another one. * This method does not check if the addresses are kept ordered. * * @param memoryChunk the MemoryChunk to be added * @param reference memoryChunk has to be added after this reference */ private void addAfter(MemoryChunk memoryChunk, MemoryChunk reference) { memoryChunk.previous = reference; memoryChunk.next = reference.next; reference.next = memoryChunk; if (memoryChunk.next != null) { memoryChunk.next.previous = memoryChunk; } if (high == reference) { high = memoryChunk; } } /** * Add a new MemoryChunk before another one. * This method does not check if the addresses are kept ordered. * * @param memoryChunk the MemoryChunk to be added * @param reference memoryChunk has to be added before this reference */ private void addBefore(MemoryChunk memoryChunk, MemoryChunk reference) { memoryChunk.previous = reference.previous; memoryChunk.next = reference; reference.previous = memoryChunk; if (memoryChunk.previous != null) { memoryChunk.previous.next = memoryChunk; } if (low == reference) { low = memoryChunk; } } /** * Add a new MemoryChunk to the list. It is added in the list so that * the addresses are kept in increasing order. * The MemoryChunk might be merged into another adjacent MemoryChunk. * * @param memoryChunk the MemoryChunk to be added */ public void add(MemoryChunk memoryChunk) { // Scan the list to find the insertion point to keep the elements // ordered by increasing address. for (MemoryChunk scanChunk = low; scanChunk != null; scanChunk = scanChunk.next) { // Merge the MemoryChunk if it is adjacent to other elements in the list if (scanChunk.addr + scanChunk.size == memoryChunk.addr) { // The MemoryChunk is adjacent at its lowest address, // merge it into the previous one. scanChunk.size += memoryChunk.size; // Check if the gap to the next chunk has not been closed, // in which case, we can also merge the next chunk. MemoryChunk nextChunk = scanChunk.next; if (nextChunk != null) { if (scanChunk.addr + scanChunk.size == nextChunk.addr) { // Merge with nextChunk scanChunk.size += nextChunk.size; remove(nextChunk); } } return; } else if (memoryChunk.addr + memoryChunk.size == scanChunk.addr) { // The MemoryChunk is adjacent at its highest address, // merge it into the next one. scanChunk.addr = memoryChunk.addr; scanChunk.size += memoryChunk.size; // Check if the gap to the previous chunk has not been closed, // in which case, we can also merge the previous chunk. MemoryChunk previousChunk = scanChunk.previous; if (previousChunk != null) { if (previousChunk.addr + previousChunk.size == scanChunk.addr) { // Merge with previousChunk previousChunk.size += scanChunk.size; remove(scanChunk); } } return; } else if (scanChunk.addr > memoryChunk.addr) { // We have found the insertion point for the MemoryChunk, // add it before this element to keep the addresses in // increasing order. addBefore(memoryChunk, scanChunk); sanityChecks(); return; } } // The MemoryChunk has not yet been added, add it at the very end // of the list. if (high == null && low == null) { // The list is empty, add the element high = memoryChunk; low = memoryChunk; } else { addAfter(memoryChunk, high); } sanityChecks(); } public MemoryChunk getLowMemoryChunk() { return low; } public MemoryChunk getHighMemoryChunk() { return high; } private void sanityChecks() { // Perform sanity checks only when the DEBUG log level is enabled if (!log.isDebugEnabled()) { return; } if (low != null) { int addr = low.addr; for (MemoryChunk memoryChunk = low; memoryChunk != null; memoryChunk = memoryChunk.next) { if (memoryChunk.addr < addr) { log.error(String.format("MemoryChunkList has overlapping memory chunks at 0x%08X: %s", addr, memoryChunk)); } addr = memoryChunk.addr + memoryChunk.size; } } } @Override public String toString() { StringBuilder result = new StringBuilder(); for (MemoryChunk memoryChunk = low; memoryChunk != null; memoryChunk = memoryChunk.next) { if (result.length() > 0) { result.append(", "); } result.append(memoryChunk.toString()); } return result.toString(); } }