/* 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 static jpcsp.HLE.kernel.managers.VplManager.PSP_VPL_ATTR_ADDR_HIGH; import java.util.HashMap; import jpcsp.Memory; import jpcsp.HLE.Modules; import jpcsp.HLE.kernel.managers.SceUidManager; import jpcsp.HLE.kernel.managers.ThreadWaitingList; import jpcsp.HLE.kernel.managers.VplManager; import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo; import jpcsp.util.Utilities; public class SceKernelVplInfo extends pspAbstractMemoryMappedStructureVariableLength { // PSP info public final String name; public final int attr; public final int poolSize; public int freeSize; public final ThreadWaitingList threadWaitingList; public static final int vplHeaderSize = 32; public static final int vplBlockHeaderSize = 8; public static final int vplAddrAlignment = 7; private final SysMemInfo sysMemInfo; // Internal info public final int uid; public final int partitionid; private final int allocAddress; private HashMap<Integer, Integer> dataBlockMap; //Hash map to store each data address and respective size. private MemoryChunkList freeMemoryChunks; private SceKernelVplInfo(String name, int partitionid, int attr, int size, int memType) { this.name = name; this.attr = attr; // Strange, the PSP is allocating a size of 0x1000 when requesting a size lower than 0x30... if (size <= 0x30) { size = 0x1000; } poolSize = size - vplHeaderSize; // 32 bytes overhead per VPL freeSize = poolSize; dataBlockMap = new HashMap<Integer, Integer>(); uid = SceUidManager.getNewUid("ThreadMan-Vpl"); threadWaitingList = ThreadWaitingList.createThreadWaitingList(SceKernelThreadInfo.PSP_WAIT_VPL, uid, attr, VplManager.PSP_VPL_ATTR_PRIORITY); this.partitionid = partitionid; // Reserve psp memory int totalVplSize = Utilities.alignUp(size, vplAddrAlignment); // 8-byte align sysMemInfo = Modules.SysMemUserForUserModule.malloc(partitionid, String.format("ThreadMan-Vpl-0x%x-%s", uid, name), memType, totalVplSize, 0); if (sysMemInfo == null) throw new RuntimeException("SceKernelVplInfo: not enough free mem"); int addr = sysMemInfo.addr; // 24 byte header, probably not necessary to mimick this Memory mem = Memory.getInstance(); mem.write32(addr, addr - 1); mem.write32(addr + 4, size - 8); mem.write32(addr + 8, 0); // based on number of allocations mem.write32(addr + 12, addr + size - 16); mem.write32(addr + 16, 0); // based on allocations/fragmentation mem.write32(addr + 20, 0); // based on created size? magic? allocAddress = addr; MemoryChunk initialMemoryChunk = new MemoryChunk(addr + vplHeaderSize, totalVplSize - vplHeaderSize); freeMemoryChunks = new MemoryChunkList(initialMemoryChunk); } public static SceKernelVplInfo tryCreateVpl(String name, int partitionid, int attr, int size, int memType) { SceKernelVplInfo info = null; int totalVplSize = Utilities.alignUp(size, vplAddrAlignment); // 8-byte align int maxFreeSize = Modules.SysMemUserForUserModule.maxFreeMemSize(partitionid); if (totalVplSize <= maxFreeSize) { info = new SceKernelVplInfo(name, partitionid, attr, totalVplSize, memType); } else { VplManager.log.warn(String.format("tryCreateVpl not enough free mem (want=%d ,free=%d, diff=%d)", totalVplSize, maxFreeSize, totalVplSize - maxFreeSize)); } return info; } public void delete() { Modules.SysMemUserForUserModule.free(sysMemInfo); } @Override protected void write() { super.write(); writeStringNZ(32, name); write32(attr); write32(poolSize); write32(freeSize); write32(getNumWaitingThreads()); } /** @return true on success */ public boolean free(int addr) { if (!dataBlockMap.containsKey(addr)) { // Address is not in valid range. if (VplManager.log.isDebugEnabled()) { VplManager.log.debug(String.format("Free VPL 0x%08X address not allocated", addr)); } return false; } // Check block header. Memory mem = Memory.getInstance(); int top = mem.read32(addr - vplBlockHeaderSize); if (top != allocAddress) { VplManager.log.warn(String.format("Free VPL 0x%08X corrupted header", addr)); return false; } // Recover free size from deallocated block. int deallocSize = dataBlockMap.remove(addr); // Free the allocated block freeSize += deallocSize; MemoryChunk memoryChunk = new MemoryChunk(addr - vplBlockHeaderSize, deallocSize); freeMemoryChunks.add(memoryChunk); if (VplManager.log.isDebugEnabled()) { VplManager.log.debug(String.format("Free VPL: Block 0x%08X with size=%d freed", addr, deallocSize)); } return true; } public int alloc(int size) { int addr = 0; int allocSize = Utilities.alignUp(size, vplAddrAlignment) + vplBlockHeaderSize; if (allocSize <= freeSize) { if ((attr & PSP_VPL_ATTR_ADDR_HIGH) == PSP_VPL_ATTR_ADDR_HIGH) { addr = freeMemoryChunks.allocHigh(allocSize, vplAddrAlignment); } else { addr = freeMemoryChunks.allocLow(allocSize, vplAddrAlignment); } if (addr != 0) { // 8-byte header per data block. Memory mem = Memory.getInstance(); mem.write32(addr, allocAddress); mem.write32(addr + 4, 0); addr += vplBlockHeaderSize; freeSize -= allocSize; dataBlockMap.put(addr, allocSize); } } return addr; } public int getNumWaitingThreads() { return threadWaitingList.getNumWaitingThreads(); } @Override public String toString() { return String.format("SceKernelVplInfo[uid=0x%X, name='%s', attr=0x%X, poolSize=0x%X, freeSize=0x%X, numWaitingThreads=%d]", uid, name, attr, poolSize, freeSize, getNumWaitingThreads()); } }