2008-01-15 20:27:44 +00:00
|
|
|
#include "IopBios.h"
|
|
|
|
#include "../COP_SCU.h"
|
|
|
|
#include "../Log.h"
|
2008-10-20 04:14:13 +00:00
|
|
|
#include "../ElfFile.h"
|
2012-05-29 01:01:02 +00:00
|
|
|
#include "../Ps2Const.h"
|
2008-10-20 04:14:13 +00:00
|
|
|
#include "PtrStream.h"
|
2008-10-28 01:48:21 +00:00
|
|
|
#include "Iop_Intc.h"
|
2008-11-25 02:00:42 +00:00
|
|
|
#include "lexical_cast_ex.h"
|
2009-01-26 02:53:10 +00:00
|
|
|
#include <boost/lexical_cast.hpp>
|
2009-02-03 19:17:16 +00:00
|
|
|
#include <vector>
|
2008-11-28 02:56:27 +00:00
|
|
|
#include "xml/FilteringNodeIterator.h"
|
2009-02-06 05:10:51 +00:00
|
|
|
#include "../StructCollectionStateFile.h"
|
2008-10-20 04:14:13 +00:00
|
|
|
|
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
2008-01-25 23:47:09 +00:00
|
|
|
#include "Iop_Cdvdfsv.h"
|
2008-01-23 23:18:20 +00:00
|
|
|
#include "Iop_McServ.h"
|
2008-11-12 01:34:01 +00:00
|
|
|
#include "Iop_FileIo.h"
|
2008-10-20 04:14:13 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "Iop_SifManNull.h"
|
|
|
|
#include "Iop_Sysclib.h"
|
|
|
|
#include "Iop_Loadcore.h"
|
2008-01-15 20:27:44 +00:00
|
|
|
#include "Iop_Thbase.h"
|
|
|
|
#include "Iop_Thsema.h"
|
2012-06-30 02:31:22 +00:00
|
|
|
#include "Iop_Thmsgbx.h"
|
2008-01-15 20:27:44 +00:00
|
|
|
#include "Iop_Thevent.h"
|
|
|
|
#include "Iop_Timrman.h"
|
|
|
|
#include "Iop_Intrman.h"
|
2008-12-01 04:00:36 +00:00
|
|
|
#include "Iop_Vblank.h"
|
2008-01-15 20:27:44 +00:00
|
|
|
|
|
|
|
#define LOGNAME "iop_bios"
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
#define STATE_MODULES ("iopbios/dyn_modules.xml")
|
|
|
|
#define STATE_MODULE_IMPORT_TABLE_ADDRESS ("ImportTableAddress")
|
2009-02-06 05:10:51 +00:00
|
|
|
|
2012-09-18 03:21:43 +00:00
|
|
|
#define BIOS_THREAD_LINK_HEAD_BASE (CIopBios::CONTROL_BLOCK_START + 0x0000)
|
|
|
|
#define BIOS_CURRENT_THREAD_ID_BASE (CIopBios::CONTROL_BLOCK_START + 0x0008)
|
|
|
|
#define BIOS_CURRENT_TIME_BASE (CIopBios::CONTROL_BLOCK_START + 0x0010)
|
|
|
|
#define BIOS_MODULELOADREQUEST_HEAD_BASE (CIopBios::CONTROL_BLOCK_START + 0x0018)
|
|
|
|
#define BIOS_MODULELOADREQUEST_FREE_BASE (CIopBios::CONTROL_BLOCK_START + 0x0020)
|
|
|
|
#define BIOS_HANDLERS_BASE (CIopBios::CONTROL_BLOCK_START + 0x0100)
|
|
|
|
#define BIOS_HANDLERS_END (BIOS_THREADS_BASE - 1)
|
|
|
|
#define BIOS_THREADS_BASE (CIopBios::CONTROL_BLOCK_START + 0x0200)
|
|
|
|
#define BIOS_THREADS_SIZE (sizeof(CIopBios::THREAD) * CIopBios::MAX_THREAD)
|
|
|
|
#define BIOS_SEMAPHORES_BASE (BIOS_THREADS_BASE + BIOS_THREADS_SIZE)
|
|
|
|
#define BIOS_SEMAPHORES_SIZE (sizeof(CIopBios::SEMAPHORE) * CIopBios::MAX_SEMAPHORE)
|
|
|
|
#define BIOS_EVENTFLAGS_BASE (BIOS_SEMAPHORES_BASE + BIOS_SEMAPHORES_SIZE)
|
|
|
|
#define BIOS_EVENTFLAGS_SIZE (sizeof(CIopBios::EVENTFLAG) * CIopBios::MAX_EVENTFLAG)
|
|
|
|
#define BIOS_INTRHANDLER_BASE (BIOS_EVENTFLAGS_BASE + BIOS_EVENTFLAGS_SIZE)
|
|
|
|
#define BIOS_INTRHANDLER_SIZE (sizeof(CIopBios::INTRHANDLER) * CIopBios::MAX_INTRHANDLER)
|
|
|
|
#define BIOS_MESSAGEBOX_BASE (BIOS_INTRHANDLER_BASE + BIOS_INTRHANDLER_SIZE)
|
|
|
|
#define BIOS_MESSAGEBOX_SIZE (sizeof(CIopBios::MESSAGEBOX) * CIopBios::MAX_MESSAGEBOX)
|
|
|
|
#define BIOS_HEAPBLOCK_BASE (BIOS_MESSAGEBOX_BASE + BIOS_MESSAGEBOX_SIZE)
|
|
|
|
#define BIOS_HEAPBLOCK_SIZE (sizeof(Iop::CSysmem::BLOCK) * Iop::CSysmem::MAX_BLOCKS)
|
|
|
|
#define BIOS_MODULELOADREQUEST_BASE (BIOS_HEAPBLOCK_BASE + BIOS_HEAPBLOCK_SIZE)
|
|
|
|
#define BIOS_MODULELOADREQUEST_SIZE (sizeof(CIopBios::MODULELOADREQUEST) * CIopBios::MAX_MODULELOADREQUEST)
|
2014-07-05 01:12:41 -04:00
|
|
|
#define BIOS_LOADEDMODULENAME_BASE (BIOS_MODULELOADREQUEST_BASE + BIOS_MODULELOADREQUEST_SIZE)
|
|
|
|
#define BIOS_LOADEDMODULENAME_SIZE (sizeof(CIopBios::LOADEDMODULENAME) * CIopBios::MAX_LOADEDMODULENAME)
|
|
|
|
#define BIOS_CALCULATED_END (BIOS_LOADEDMODULENAME_BASE + BIOS_LOADEDMODULENAME_SIZE)
|
2012-09-18 03:21:43 +00:00
|
|
|
|
|
|
|
#define SYSCALL_EXITTHREAD 0x666
|
|
|
|
#define SYSCALL_RETURNFROMEXCEPTION 0x667
|
|
|
|
#define SYSCALL_RESCHEDULE 0x668
|
|
|
|
#define SYSCALL_SLEEPTHREAD 0x669
|
|
|
|
#define SYSCALL_PROCESSMODULELOAD 0x66A
|
2014-07-10 23:43:29 -04:00
|
|
|
#define SYSCALL_FINISHMODULELOAD 0x66B
|
|
|
|
#define SYSCALL_DELAYTHREADTICKS 0x66C
|
2009-02-04 02:15:56 +00:00
|
|
|
|
2015-04-09 01:02:46 -04:00
|
|
|
//This is the space needed to preserve at most four arguments in the stack frame (as per MIPS calling convention)
|
2012-09-23 02:44:14 +00:00
|
|
|
#define STACK_FRAME_RESERVE_SIZE 0x10
|
|
|
|
|
2012-05-29 01:01:02 +00:00
|
|
|
CIopBios::CIopBios(CMIPS& cpu, uint8* ram, uint32 ramSize)
|
|
|
|
: m_cpu(cpu)
|
|
|
|
, m_ram(ram)
|
|
|
|
, m_ramSize(ramSize)
|
2012-10-21 02:37:23 +00:00
|
|
|
, m_sifMan(nullptr)
|
|
|
|
, m_sifCmd(nullptr)
|
|
|
|
, m_stdio(nullptr)
|
|
|
|
, m_sysmem(nullptr)
|
|
|
|
, m_ioman(nullptr)
|
|
|
|
, m_modload(nullptr)
|
|
|
|
, m_cdvdman(nullptr)
|
2012-10-24 06:39:29 +00:00
|
|
|
, m_loadcore(nullptr)
|
2008-10-20 04:14:13 +00:00
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
2012-10-21 02:37:23 +00:00
|
|
|
, m_padman(nullptr)
|
|
|
|
, m_cdvdfsv(nullptr)
|
2008-10-20 04:14:13 +00:00
|
|
|
#endif
|
2012-05-29 01:01:02 +00:00
|
|
|
, m_rescheduleNeeded(false)
|
|
|
|
, m_threadFinishAddress(0)
|
2012-09-18 03:21:43 +00:00
|
|
|
, m_returnFromExceptionAddress(0)
|
|
|
|
, m_idleFunctionAddress(0)
|
|
|
|
, m_moduleLoaderThreadProcAddress(0)
|
|
|
|
, m_moduleLoaderThreadId(0)
|
2012-10-21 02:37:23 +00:00
|
|
|
, m_alarmThreadProcAddress(0)
|
2012-05-29 01:01:02 +00:00
|
|
|
, m_threads(reinterpret_cast<THREAD*>(&m_ram[BIOS_THREADS_BASE]), 1, MAX_THREAD)
|
|
|
|
, m_semaphores(reinterpret_cast<SEMAPHORE*>(&m_ram[BIOS_SEMAPHORES_BASE]), 1, MAX_SEMAPHORE)
|
|
|
|
, m_eventFlags(reinterpret_cast<EVENTFLAG*>(&m_ram[BIOS_EVENTFLAGS_BASE]), 1, MAX_EVENTFLAG)
|
|
|
|
, m_intrHandlers(reinterpret_cast<INTRHANDLER*>(&m_ram[BIOS_INTRHANDLER_BASE]), 1, MAX_INTRHANDLER)
|
2012-06-30 02:31:22 +00:00
|
|
|
, m_messageBoxes(reinterpret_cast<MESSAGEBOX*>(&m_ram[BIOS_MESSAGEBOX_BASE]), 1, MAX_MESSAGEBOX)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
static_assert(BIOS_CALCULATED_END <= CIopBios::CONTROL_BLOCK_END, "Control block size is too small");
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CIopBios::~CIopBios()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
DeleteModules();
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2008-11-25 02:00:42 +00:00
|
|
|
void CIopBios::Reset(Iop::CSifMan* sifMan)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2009-01-28 23:06:08 +00:00
|
|
|
//Assemble handlers
|
2008-10-20 22:19:56 +00:00
|
|
|
{
|
2009-01-26 02:53:10 +00:00
|
|
|
CMIPSAssembler assembler(reinterpret_cast<uint32*>(&m_ram[BIOS_HANDLERS_BASE]));
|
2008-10-20 22:19:56 +00:00
|
|
|
m_threadFinishAddress = AssembleThreadFinish(assembler);
|
2008-10-28 21:32:18 +00:00
|
|
|
m_returnFromExceptionAddress = AssembleReturnFromException(assembler);
|
|
|
|
m_idleFunctionAddress = AssembleIdleFunction(assembler);
|
2012-09-18 03:21:43 +00:00
|
|
|
m_moduleLoaderThreadProcAddress = AssembleModuleLoaderThreadProc(assembler);
|
2012-10-21 02:37:23 +00:00
|
|
|
m_alarmThreadProcAddress = AssembleAlarmThreadProc(assembler);
|
2009-01-28 23:06:08 +00:00
|
|
|
assert(BIOS_HANDLERS_END > ((assembler.GetProgramSize() * 4) + BIOS_HANDLERS_BASE));
|
2008-10-20 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2008-12-05 17:13:25 +00:00
|
|
|
//0xBE00000 = Stupid constant to make FFX PSF happy
|
2009-01-26 02:53:10 +00:00
|
|
|
CurrentTime() = 0xBE00000;
|
2012-03-30 05:41:11 +00:00
|
|
|
ThreadLinkHead() = 0;
|
|
|
|
CurrentThreadId() = -1;
|
2008-12-05 17:13:25 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_cpu.m_State.nCOP0[CCOP_SCU::STATUS] |= CMIPS::STATUS_INT;
|
2008-10-28 01:48:21 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_threads.FreeAll();
|
2009-01-30 22:10:15 +00:00
|
|
|
m_semaphores.FreeAll();
|
2012-03-30 05:41:11 +00:00
|
|
|
m_intrHandlers.FreeAll();
|
2014-07-04 02:53:10 -04:00
|
|
|
#ifdef DEBUGGER_INCLUDED
|
2012-03-30 05:41:11 +00:00
|
|
|
m_moduleTags.clear();
|
2014-07-04 02:53:10 -04:00
|
|
|
#endif
|
2012-03-30 05:41:11 +00:00
|
|
|
|
|
|
|
DeleteModules();
|
|
|
|
|
|
|
|
if(sifMan == NULL)
|
|
|
|
{
|
|
|
|
m_sifMan = new Iop::CSifManNull();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_sifMan = sifMan;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Register built-in modules
|
|
|
|
{
|
|
|
|
m_ioman = new Iop::CIoman(m_ram);
|
|
|
|
RegisterModule(m_ioman);
|
|
|
|
}
|
2014-08-02 21:23:25 -04:00
|
|
|
{
|
|
|
|
m_stdio = new Iop::CStdio(m_ram, *m_ioman);
|
|
|
|
RegisterModule(m_stdio);
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
2014-11-08 21:53:56 -05:00
|
|
|
m_sysmem = new Iop::CSysmem(m_ram, CONTROL_BLOCK_END, m_ramSize, BIOS_HEAPBLOCK_BASE, *m_stdio, *m_ioman, *m_sifMan);
|
2012-03-30 05:41:11 +00:00
|
|
|
RegisterModule(m_sysmem);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
m_modload = new Iop::CModload(*this, m_ram);
|
|
|
|
RegisterModule(m_modload);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CSysclib(m_ram, *m_stdio));
|
|
|
|
}
|
|
|
|
{
|
2012-10-24 06:39:29 +00:00
|
|
|
m_loadcore = new Iop::CLoadcore(*this, m_ram, *m_sifMan);
|
|
|
|
RegisterModule(m_loadcore);
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
2015-02-07 23:06:08 -05:00
|
|
|
{
|
|
|
|
m_libsd = std::make_shared<Iop::CLibSd>();
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CThbase(*this, m_ram));
|
|
|
|
}
|
2012-06-30 02:31:22 +00:00
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CThmsgbx(*this, m_ram));
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CThsema(*this, m_ram));
|
|
|
|
}
|
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CThevent(*this, m_ram));
|
|
|
|
}
|
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CTimrman(*this));
|
|
|
|
}
|
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CIntrman(*this, m_ram));
|
|
|
|
}
|
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CVblank(*this));
|
|
|
|
}
|
|
|
|
{
|
2015-03-14 04:25:15 -04:00
|
|
|
m_cdvdman = new Iop::CCdvdman(*this, m_ram);
|
2012-03-30 05:41:11 +00:00
|
|
|
RegisterModule(m_cdvdman);
|
|
|
|
}
|
2008-11-14 17:10:21 +00:00
|
|
|
{
|
|
|
|
RegisterModule(m_sifMan);
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
|
|
|
m_sifCmd = new Iop::CSifCmd(*this, *m_sifMan, *m_sysmem, m_ram);
|
|
|
|
RegisterModule(m_sifCmd);
|
|
|
|
}
|
2008-10-20 04:14:13 +00:00
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
2015-01-26 03:39:44 -05:00
|
|
|
m_fileIo = new Iop::CFileIo(*m_sifMan, *m_ioman);
|
|
|
|
RegisterModule(m_fileIo);
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
{
|
|
|
|
m_cdvdfsv = new Iop::CCdvdfsv(*m_sifMan, m_ram);
|
|
|
|
RegisterModule(m_cdvdfsv);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
RegisterModule(new Iop::CMcServ(*m_sifMan));
|
|
|
|
}
|
|
|
|
{
|
|
|
|
m_padman = new Iop::CPadMan(*m_sifMan);
|
|
|
|
RegisterModule(m_padman);
|
|
|
|
}
|
2008-10-20 04:14:13 +00:00
|
|
|
#endif
|
2008-05-26 00:04:29 +00:00
|
|
|
|
2014-12-12 20:20:51 +00:00
|
|
|
{
|
|
|
|
const int sifDmaBufferSize = 0x1000;
|
|
|
|
uint32 sifDmaBufferPtr = m_sysmem->AllocateMemory(sifDmaBufferSize, 0, 0);
|
|
|
|
m_sifMan->SetDmaBuffer(sifDmaBufferPtr, sifDmaBufferSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const int sifCmdBufferSize = 0x100;
|
|
|
|
uint32 sifCmdBufferPtr = m_sysmem->AllocateMemory(sifCmdBufferSize, 0, 0);
|
|
|
|
m_sifMan->SetCmdBuffer(sifCmdBufferPtr, sifCmdBufferSize);
|
|
|
|
}
|
|
|
|
|
2012-09-18 06:33:33 +00:00
|
|
|
m_sifMan->GenerateHandlers(m_ram, *m_sysmem);
|
2008-11-25 02:00:42 +00:00
|
|
|
|
2012-09-18 03:21:43 +00:00
|
|
|
InitializeModuleLoader();
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
Reschedule();
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2009-01-28 23:06:08 +00:00
|
|
|
uint32& CIopBios::ThreadLinkHead() const
|
2009-01-26 02:53:10 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return *reinterpret_cast<uint32*>(m_ram + BIOS_THREAD_LINK_HEAD_BASE);
|
2009-01-26 02:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32& CIopBios::CurrentThreadId() const
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return *reinterpret_cast<uint32*>(m_ram + BIOS_CURRENT_THREAD_ID_BASE);
|
2009-01-26 02:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64& CIopBios::CurrentTime() const
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return *reinterpret_cast<uint64*>(m_ram + BIOS_CURRENT_TIME_BASE);
|
2009-01-26 02:53:10 +00:00
|
|
|
}
|
|
|
|
|
2012-09-18 03:21:43 +00:00
|
|
|
uint32& CIopBios::ModuleLoadRequestHead() const
|
|
|
|
{
|
|
|
|
return *reinterpret_cast<uint32*>(m_ram + BIOS_MODULELOADREQUEST_HEAD_BASE);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32& CIopBios::ModuleLoadRequestFree() const
|
|
|
|
{
|
|
|
|
return *reinterpret_cast<uint32*>(m_ram + BIOS_MODULELOADREQUEST_FREE_BASE);
|
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
void CIopBios::SaveState(Framework::CZipArchiveWriter& archive)
|
2009-01-26 02:53:10 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
CStructCollectionStateFile* modulesFile = new CStructCollectionStateFile(STATE_MODULES);
|
|
|
|
{
|
|
|
|
for(DynamicIopModuleListType::iterator moduleIterator(m_dynamicModules.begin());
|
|
|
|
moduleIterator != m_dynamicModules.end(); moduleIterator++)
|
|
|
|
{
|
|
|
|
Iop::CDynamic* module(*moduleIterator);
|
|
|
|
CStructFile moduleStruct;
|
|
|
|
{
|
|
|
|
uint32 importTableAddress = reinterpret_cast<uint8*>(module->GetExportTable()) - m_ram;
|
|
|
|
moduleStruct.SetRegister32(STATE_MODULE_IMPORT_TABLE_ADDRESS, importTableAddress);
|
|
|
|
}
|
|
|
|
modulesFile->InsertStruct(module->GetId().c_str(), moduleStruct);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
archive.InsertFile(modulesFile);
|
2009-02-06 05:10:51 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_sifCmd->SaveState(archive);
|
2015-03-14 04:26:16 -04:00
|
|
|
m_cdvdman->SaveState(archive);
|
2009-01-26 02:53:10 +00:00
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
void CIopBios::LoadState(Framework::CZipArchiveReader& archive)
|
2009-01-26 02:53:10 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
ClearDynamicModules();
|
2009-02-06 05:10:51 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
CStructCollectionStateFile modulesFile(*archive.BeginReadFile(STATE_MODULES));
|
|
|
|
{
|
|
|
|
for(CStructCollectionStateFile::StructIterator structIterator(modulesFile.GetStructBegin());
|
|
|
|
structIterator != modulesFile.GetStructEnd(); structIterator++)
|
|
|
|
{
|
|
|
|
const CStructFile& structFile(structIterator->second);
|
|
|
|
uint32 importTableAddress = structFile.GetRegister32(STATE_MODULE_IMPORT_TABLE_ADDRESS);
|
|
|
|
Iop::CDynamic* module = new Iop::CDynamic(reinterpret_cast<uint32*>(m_ram + importTableAddress));
|
|
|
|
RegisterDynamicModule(module);
|
|
|
|
}
|
|
|
|
}
|
2009-02-06 05:10:51 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_sifCmd->LoadState(archive);
|
2015-03-14 04:26:16 -04:00
|
|
|
m_cdvdman->LoadState(archive);
|
2013-02-24 08:59:50 +00:00
|
|
|
|
|
|
|
#ifdef DEBUGGER_INCLUDED
|
2014-08-16 21:42:19 -04:00
|
|
|
m_cpu.m_analysis->Clear();
|
2013-02-24 08:59:50 +00:00
|
|
|
for(const auto& moduleTag : m_moduleTags)
|
|
|
|
{
|
2014-08-16 21:42:19 -04:00
|
|
|
m_cpu.m_analysis->Analyse(moduleTag.begin, moduleTag.end);
|
2013-02-24 08:59:50 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2009-01-26 02:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CIopBios::IsIdle()
|
|
|
|
{
|
2010-03-30 04:19:45 +00:00
|
|
|
return (m_cpu.m_State.nPC == m_idleFunctionAddress);
|
2009-01-26 02:53:10 +00:00
|
|
|
}
|
|
|
|
|
2012-09-18 03:21:43 +00:00
|
|
|
void CIopBios::InitializeModuleLoader()
|
|
|
|
{
|
|
|
|
ModuleLoadRequestHead() = 0;
|
|
|
|
ModuleLoadRequestFree() = BIOS_MODULELOADREQUEST_BASE;
|
|
|
|
|
|
|
|
//Initialize Module Load Request Free List
|
|
|
|
for(unsigned int i = 0; i < (MAX_MODULELOADREQUEST - 1); i++)
|
|
|
|
{
|
|
|
|
auto moduleLoadRequest = reinterpret_cast<MODULELOADREQUEST*>(m_ram + BIOS_MODULELOADREQUEST_BASE) + i;
|
|
|
|
moduleLoadRequest->nextPtr = reinterpret_cast<uint8*>(moduleLoadRequest + 1) - m_ram;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_moduleLoaderThreadId = CreateThread(m_moduleLoaderThreadProcAddress, DEFAULT_PRIORITY, DEFAULT_STACKSIZE, 0);
|
|
|
|
StartThread(m_moduleLoaderThreadId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::RequestModuleLoad(uint32 moduleEntryPoint, uint32 gp, const char* path, const char* args, unsigned int argsLength)
|
|
|
|
{
|
|
|
|
uint32 requestPtr = ModuleLoadRequestFree();
|
|
|
|
assert(requestPtr != 0);
|
|
|
|
if(requestPtr == 0)
|
|
|
|
{
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "Too many modules to be loaded.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto moduleLoadRequest = reinterpret_cast<MODULELOADREQUEST*>(m_ram + requestPtr);
|
|
|
|
|
|
|
|
//Unlink from free list and link in active list (at the end)
|
|
|
|
{
|
|
|
|
ModuleLoadRequestFree() = moduleLoadRequest->nextPtr;
|
|
|
|
|
|
|
|
uint32* currentPtr = &ModuleLoadRequestHead();
|
|
|
|
while(*currentPtr != 0)
|
|
|
|
{
|
|
|
|
auto currentModuleLoadRequest = reinterpret_cast<MODULELOADREQUEST*>(m_ram + *currentPtr);
|
|
|
|
currentPtr = ¤tModuleLoadRequest->nextPtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
*currentPtr = requestPtr;
|
|
|
|
|
|
|
|
moduleLoadRequest->nextPtr = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
moduleLoadRequest->entryPoint = moduleEntryPoint;
|
|
|
|
moduleLoadRequest->gp = gp;
|
|
|
|
|
|
|
|
assert((strlen(path) + 1) <= MODULELOADREQUEST::MAX_PATH_SIZE);
|
|
|
|
strncpy(moduleLoadRequest->path, path, MODULELOADREQUEST::MAX_PATH_SIZE);
|
|
|
|
moduleLoadRequest->path[MODULELOADREQUEST::MAX_PATH_SIZE - 1] = 0;
|
|
|
|
|
|
|
|
memcpy(moduleLoadRequest->args, args, argsLength);
|
|
|
|
moduleLoadRequest->argsLength = argsLength;
|
|
|
|
|
|
|
|
WakeupThread(m_moduleLoaderThreadId, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::ProcessModuleLoad()
|
|
|
|
{
|
|
|
|
assert(GetCurrentThreadId() == m_moduleLoaderThreadId);
|
|
|
|
|
|
|
|
uint32 requestPtr = ModuleLoadRequestHead();
|
|
|
|
assert(requestPtr != 0);
|
|
|
|
if(requestPtr == 0)
|
|
|
|
{
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "Asked to load module when none was requested.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto moduleLoadRequest = reinterpret_cast<MODULELOADREQUEST*>(m_ram + requestPtr);
|
|
|
|
|
|
|
|
//Unlink from active list and link in free list
|
|
|
|
{
|
|
|
|
ModuleLoadRequestHead() = moduleLoadRequest->nextPtr;
|
|
|
|
|
|
|
|
moduleLoadRequest->nextPtr = ModuleLoadRequestFree();
|
|
|
|
ModuleLoadRequestFree() = requestPtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(GetCurrentThreadId() == m_moduleLoaderThreadId);
|
|
|
|
|
|
|
|
//Reset stack pointer
|
|
|
|
{
|
|
|
|
auto thread = GetThread(m_moduleLoaderThreadId);
|
|
|
|
assert(thread);
|
2012-09-23 02:44:14 +00:00
|
|
|
m_cpu.m_State.nGPR[CMIPS::SP].nV0 = thread->stackBase + thread->stackSize - STACK_FRAME_RESERVE_SIZE;
|
2012-09-18 03:21:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Patch loader thread context with proper info to invoke module entry proc
|
|
|
|
{
|
|
|
|
const char* path = moduleLoadRequest->path;
|
|
|
|
const char* args = moduleLoadRequest->args;
|
|
|
|
uint32 argsLength = moduleLoadRequest->argsLength;
|
|
|
|
|
|
|
|
typedef std::vector<uint32> ParamListType;
|
|
|
|
ParamListType paramList;
|
|
|
|
|
|
|
|
paramList.push_back(Push(
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::SP].nV0,
|
|
|
|
reinterpret_cast<const uint8*>(path),
|
|
|
|
static_cast<uint32>(strlen(path)) + 1));
|
|
|
|
if(argsLength != 0)
|
|
|
|
{
|
|
|
|
unsigned int argsPos = 0;
|
|
|
|
while(argsPos < argsLength)
|
|
|
|
{
|
|
|
|
const char* arg = args + argsPos;
|
|
|
|
unsigned int argLength = static_cast<unsigned int>(strlen(arg)) + 1;
|
|
|
|
if(argLength == 1)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
argsPos += argLength;
|
|
|
|
uint32 argAddress = Push(
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::SP].nV0,
|
|
|
|
reinterpret_cast<const uint8*>(arg),
|
|
|
|
static_cast<uint32>(argLength));
|
|
|
|
paramList.push_back(argAddress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::A0].nV0 = static_cast<uint32>(paramList.size());
|
|
|
|
for(ParamListType::reverse_iterator param(paramList.rbegin());
|
|
|
|
paramList.rend() != param; param++)
|
|
|
|
{
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::A1].nV0 = Push(
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::SP].nV0,
|
|
|
|
reinterpret_cast<const uint8*>(&(*param)),
|
|
|
|
4);
|
|
|
|
}
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::SP].nV0 -= 4;
|
|
|
|
|
|
|
|
m_cpu.m_State.nGPR[CMIPS::GP].nV0 = moduleLoadRequest->gp;
|
2014-07-10 23:43:29 -04:00
|
|
|
m_cpu.m_State.nGPR[CMIPS::RA].nV0 = m_cpu.m_State.nPC;
|
|
|
|
m_cpu.m_State.nPC = moduleLoadRequest->entryPoint;
|
2012-09-18 03:21:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-10 23:43:29 -04:00
|
|
|
void CIopBios::FinishModuleLoad()
|
|
|
|
{
|
|
|
|
//We need to notify the EE that the load request is over
|
|
|
|
m_sifMan->SendCallReply(Iop::CLoadcore::MODULE_ID, nullptr);
|
|
|
|
}
|
|
|
|
|
2014-08-23 21:42:02 -04:00
|
|
|
bool CIopBios::LoadAndStartModule(const char* path, const char* args, unsigned int argsLength)
|
2008-10-20 04:14:13 +00:00
|
|
|
{
|
2012-09-05 20:40:23 +00:00
|
|
|
uint32 handle = m_ioman->Open(Iop::Ioman::CDevice::OPEN_FLAG_RDONLY, path);
|
2012-03-30 05:41:11 +00:00
|
|
|
if(handle & 0x80000000)
|
|
|
|
{
|
2014-08-23 21:42:02 -04:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "Tried to load '%s' which couldn't be found.\r\n", path);
|
|
|
|
return false;
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
Iop::CIoman::CFile file(handle, *m_ioman);
|
|
|
|
Framework::CStream* stream = m_ioman->GetFileStream(file);
|
|
|
|
CElfFile module(*stream);
|
|
|
|
LoadAndStartModule(module, path, args, argsLength);
|
2014-08-23 21:42:02 -04:00
|
|
|
return true;
|
2008-10-20 04:14:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::LoadAndStartModule(uint32 modulePtr, const char* args, unsigned int argsLength)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
CELF module(m_ram + modulePtr);
|
|
|
|
LoadAndStartModule(module, "", args, argsLength);
|
2008-10-20 04:14:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::LoadAndStartModule(CELF& elf, const char* path, const char* args, unsigned int argsLength)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
ExecutableRange moduleRange;
|
|
|
|
uint32 entryPoint = LoadExecutable(elf, moduleRange);
|
|
|
|
|
|
|
|
//Find .iopmod section
|
|
|
|
const ELFHEADER& header(elf.GetHeader());
|
|
|
|
const IOPMOD* iopMod = NULL;
|
|
|
|
for(unsigned int i = 0; i < header.nSectHeaderCount; i++)
|
|
|
|
{
|
|
|
|
ELFSECTIONHEADER* sectionHeader(elf.GetSection(i));
|
|
|
|
if(sectionHeader->nType != IOPMOD_SECTION_ID) continue;
|
|
|
|
iopMod = reinterpret_cast<const IOPMOD*>(elf.GetSectionData(i));
|
|
|
|
}
|
|
|
|
|
2014-07-04 01:55:09 -04:00
|
|
|
std::string moduleName = iopMod ? iopMod->moduleName : "";
|
|
|
|
if(moduleName.empty())
|
2011-02-26 22:42:59 +00:00
|
|
|
{
|
2014-07-04 01:55:09 -04:00
|
|
|
moduleName = path;
|
2011-02-26 22:42:59 +00:00
|
|
|
}
|
|
|
|
|
2014-07-05 01:12:41 -04:00
|
|
|
InsertLoadedModuleName(moduleName);
|
|
|
|
|
2014-07-04 01:55:09 -04:00
|
|
|
#ifdef DEBUGGER_INCLUDED
|
|
|
|
PrepareModuleDebugInfo(elf, moduleRange, moduleName, path);
|
2008-01-15 20:27:44 +00:00
|
|
|
#endif
|
|
|
|
|
2011-02-26 22:42:59 +00:00
|
|
|
//Patch for Shadow Hearts PSF set --------------------------------
|
|
|
|
if(strstr(path, "RSSD_patchmore.IRX") != NULL)
|
|
|
|
{
|
|
|
|
const uint32 patchAddress = moduleRange.first + 0xCE0;
|
|
|
|
uint32 instruction = m_cpu.m_pMemoryMap->GetWord(patchAddress);
|
|
|
|
if(instruction == 0x1200FFFB)
|
|
|
|
{
|
|
|
|
m_cpu.m_pMemoryMap->SetWord(patchAddress, 0x1000FFFB);
|
|
|
|
}
|
|
|
|
}
|
2012-09-18 03:21:43 +00:00
|
|
|
|
|
|
|
RequestModuleLoad(entryPoint, iopMod ? (iopMod->gp + moduleRange.first) : 0,
|
|
|
|
path, args, argsLength);
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2014-07-05 01:12:41 -04:00
|
|
|
void CIopBios::InsertLoadedModuleName(const std::string& moduleName)
|
|
|
|
{
|
|
|
|
bool loadedModuleNameAdded = false;
|
|
|
|
for(unsigned int i = 0; i < MAX_LOADEDMODULENAME; i++)
|
|
|
|
{
|
|
|
|
auto loadedModule = reinterpret_cast<LOADEDMODULENAME*>(m_ram + BIOS_LOADEDMODULENAME_BASE) + i;
|
|
|
|
if(!strcmp(loadedModule->name, moduleName.c_str()))
|
|
|
|
{
|
|
|
|
//Module name already exists (which is weird, but, ok).
|
|
|
|
loadedModuleNameAdded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(!strlen(loadedModule->name))
|
|
|
|
{
|
|
|
|
strncpy(loadedModule->name, moduleName.c_str(), LOADEDMODULENAME::MAX_NAME_SIZE);
|
|
|
|
loadedModule->name[LOADEDMODULENAME::MAX_NAME_SIZE - 1] = 0;
|
|
|
|
loadedModuleNameAdded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(loadedModuleNameAdded);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CIopBios::IsModuleLoaded(const char* moduleName) const
|
|
|
|
{
|
|
|
|
for(unsigned int i = 0; i < MAX_LOADEDMODULENAME; i++)
|
|
|
|
{
|
|
|
|
auto loadedModule = reinterpret_cast<LOADEDMODULENAME*>(m_ram + BIOS_LOADEDMODULENAME_BASE) + i;
|
|
|
|
if(!strcmp(loadedModule->name, moduleName))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-01-26 03:39:44 -05:00
|
|
|
void CIopBios::ProcessModuleReset(const std::string& imagePath)
|
|
|
|
{
|
|
|
|
unsigned int imageVersion = 1000;
|
|
|
|
auto imageFileName = strstr(imagePath.c_str(), "IOPRP");
|
|
|
|
if(imageFileName != nullptr)
|
|
|
|
{
|
|
|
|
auto cvtCount = sscanf(imageFileName, "IOPRP%d.IMG;1", &imageVersion);
|
|
|
|
if(cvtCount == 1)
|
|
|
|
{
|
|
|
|
if(imageVersion < 100)
|
|
|
|
{
|
|
|
|
imageVersion = imageVersion * 100;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
imageVersion = imageVersion * 10;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
|
|
|
m_fileIo->SetModuleVersion(imageVersion);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2011-04-25 01:33:32 +00:00
|
|
|
CIopBios::THREAD* CIopBios::GetThread(uint32 threadId)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return m_threads[threadId];
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2012-09-12 04:03:41 +00:00
|
|
|
uint32 CIopBios::CreateThread(uint32 threadProc, uint32 priority, uint32 stackSize, uint32 optionData)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: CreateThread(threadProc = 0x%0.8X, priority = %d, stackSize = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), threadProc, priority, stackSize);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
if(stackSize == 0)
|
|
|
|
{
|
|
|
|
stackSize = DEFAULT_STACKSIZE;
|
|
|
|
}
|
2008-12-15 02:57:21 +00:00
|
|
|
|
2009-01-28 23:06:08 +00:00
|
|
|
uint32 threadId = m_threads.Allocate();
|
|
|
|
assert(threadId != -1);
|
2009-01-30 22:10:15 +00:00
|
|
|
if(threadId == -1)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2009-01-28 23:06:08 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = m_threads[threadId];
|
|
|
|
memset(&thread->context, 0, sizeof(thread->context));
|
|
|
|
thread->context.delayJump = 1;
|
2009-01-28 23:06:08 +00:00
|
|
|
thread->stackSize = stackSize;
|
2012-03-30 05:41:11 +00:00
|
|
|
thread->stackBase = m_sysmem->AllocateMemory(thread->stackSize, 0, 0);
|
|
|
|
memset(m_ram + thread->stackBase, 0, thread->stackSize);
|
2009-01-28 23:06:08 +00:00
|
|
|
thread->id = threadId;
|
2012-03-30 05:41:11 +00:00
|
|
|
thread->priority = priority;
|
2012-09-12 04:03:41 +00:00
|
|
|
thread->initPriority = priority;
|
|
|
|
thread->status = THREAD_STATUS_DORMANT;
|
|
|
|
thread->threadProc = threadProc;
|
|
|
|
thread->optionData = optionData;
|
2012-03-30 05:41:11 +00:00
|
|
|
thread->nextActivateTime = 0;
|
|
|
|
thread->context.gpr[CMIPS::GP] = m_cpu.m_State.nGPR[CMIPS::GP].nV0;
|
2012-09-23 02:44:14 +00:00
|
|
|
thread->context.gpr[CMIPS::SP] = thread->stackBase + thread->stackSize - STACK_FRAME_RESERVE_SIZE;
|
2009-01-28 23:06:08 +00:00
|
|
|
LinkThread(thread->id);
|
2012-03-30 05:41:11 +00:00
|
|
|
return thread->id;
|
2009-01-28 23:06:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::DeleteThread(uint32 threadId)
|
|
|
|
{
|
2014-07-26 00:27:48 -04:00
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: DeleteThread(threadId = %d);\r\n",
|
|
|
|
CurrentThreadId(), threadId);
|
|
|
|
#endif
|
2009-01-28 23:06:08 +00:00
|
|
|
THREAD* thread = m_threads[threadId];
|
|
|
|
UnlinkThread(threadId);
|
|
|
|
m_sysmem->FreeMemory(thread->stackBase);
|
|
|
|
m_threads.Free(threadId);
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::StartThread(uint32 threadId, uint32* param)
|
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: StartThread(threadId = %i, param = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), threadId, param);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = GetThread(threadId);
|
2011-04-25 01:33:32 +00:00
|
|
|
assert(thread != NULL);
|
|
|
|
|
|
|
|
if(thread == NULL)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-21 02:37:23 +00:00
|
|
|
if(thread->status == THREAD_STATUS_RUNNING)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-12 04:03:41 +00:00
|
|
|
if(thread->status != THREAD_STATUS_DORMANT)
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
|
|
|
throw std::runtime_error("Invalid thread state.");
|
|
|
|
}
|
|
|
|
thread->status = THREAD_STATUS_RUNNING;
|
|
|
|
if(param != NULL)
|
|
|
|
{
|
|
|
|
thread->context.gpr[CMIPS::A0] = *param;
|
|
|
|
}
|
2012-09-12 04:03:41 +00:00
|
|
|
thread->priority = thread->initPriority;
|
|
|
|
thread->context.epc = thread->threadProc;
|
|
|
|
thread->context.gpr[CMIPS::RA] = m_threadFinishAddress;
|
2012-09-23 02:44:14 +00:00
|
|
|
thread->context.gpr[CMIPS::SP] = thread->stackBase + thread->stackSize - STACK_FRAME_RESERVE_SIZE;
|
2014-11-23 23:52:21 +00:00
|
|
|
|
|
|
|
// If the thread we are starting is the same priority or lower than the current one, do yield.
|
|
|
|
// If may be that the correct action is never to yield - the docs aren't really clear.
|
2014-11-30 18:51:24 -05:00
|
|
|
// INET.IRX (from Champions: Return to Arms) depends on startThread not yielding when starting a
|
|
|
|
// thread of the same priority.
|
|
|
|
auto currentThread = GetThread(CurrentThreadId());
|
|
|
|
if((currentThread == nullptr) || (currentThread->priority < thread->priority))
|
|
|
|
{
|
2014-11-23 23:52:21 +00:00
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
2012-09-12 04:03:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::ExitThread()
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: ExitThread();\r\n", CurrentThreadId());
|
|
|
|
#endif
|
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
thread->status = THREAD_STATUS_DORMANT;
|
2012-03-30 05:41:11 +00:00
|
|
|
m_rescheduleNeeded = true;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2014-07-26 00:27:48 -04:00
|
|
|
uint32 CIopBios::TerminateThread(uint32 threadId)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: TerminateThread(threadId = %d);\r\n",
|
|
|
|
CurrentThreadId(), threadId);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
assert(threadId != CurrentThreadId());
|
|
|
|
auto thread = GetThread(threadId);
|
|
|
|
assert(thread != nullptr);
|
|
|
|
if(thread == nullptr)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if(thread->waitSemaphore != 0)
|
|
|
|
{
|
|
|
|
auto semaphore = m_semaphores[thread->waitSemaphore];
|
|
|
|
if(semaphore != nullptr)
|
|
|
|
{
|
|
|
|
assert(semaphore->waitCount > 0);
|
|
|
|
semaphore->waitCount--;
|
|
|
|
}
|
|
|
|
thread->waitSemaphore = 0;
|
|
|
|
}
|
|
|
|
thread->status = THREAD_STATUS_DORMANT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
void CIopBios::DelayThread(uint32 delay)
|
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: DelayThread(delay = %i);\r\n",
|
|
|
|
CurrentThreadId(), delay);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
thread->nextActivateTime = GetCurrentTime() + MicroSecToClock(delay);
|
|
|
|
m_rescheduleNeeded = true;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2012-10-21 02:37:23 +00:00
|
|
|
void CIopBios::DelayThreadTicks(uint32 delay)
|
|
|
|
{
|
|
|
|
auto thread = GetThread(CurrentThreadId());
|
|
|
|
thread->nextActivateTime = GetCurrentTime() + delay;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::SetAlarm(uint32 timePtr, uint32 alarmFunction, uint32 param)
|
|
|
|
{
|
2014-06-04 03:17:57 -04:00
|
|
|
uint32 alarmThreadId = -1;
|
|
|
|
|
|
|
|
//Find a thread we could recycle for a new alarm
|
|
|
|
for(auto threadIterator = m_threads.Begin();
|
|
|
|
threadIterator != m_threads.End(); threadIterator++)
|
|
|
|
{
|
|
|
|
const auto& thread(m_threads[threadIterator]);
|
|
|
|
if(thread == nullptr) continue;
|
|
|
|
if(thread->threadProc != m_alarmThreadProcAddress) continue;
|
|
|
|
if(thread->status == THREAD_STATUS_DORMANT)
|
|
|
|
{
|
|
|
|
alarmThreadId = thread->id;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//If no threads are available, create a new one
|
|
|
|
if(alarmThreadId == -1)
|
|
|
|
{
|
|
|
|
alarmThreadId = CreateThread(m_alarmThreadProcAddress, 1, DEFAULT_STACKSIZE, 0);
|
|
|
|
}
|
|
|
|
|
2012-10-21 02:37:23 +00:00
|
|
|
StartThread(alarmThreadId);
|
|
|
|
|
|
|
|
auto thread = GetThread(alarmThreadId);
|
|
|
|
thread->context.gpr[CMIPS::SP] -= 0x20;
|
|
|
|
|
|
|
|
uint32* delay = reinterpret_cast<uint32*>(m_ram + timePtr);
|
|
|
|
assert(delay[1] == 0);
|
|
|
|
|
|
|
|
*reinterpret_cast<uint32*>(m_ram + thread->context.gpr[CMIPS::SP] + 0x00) = alarmFunction;
|
|
|
|
*reinterpret_cast<uint32*>(m_ram + thread->context.gpr[CMIPS::SP] + 0x04) = param;
|
|
|
|
*reinterpret_cast<uint32*>(m_ram + thread->context.gpr[CMIPS::SP] + 0x08) = delay[0];
|
|
|
|
|
|
|
|
thread->context.gpr[CMIPS::A0] = thread->context.gpr[CMIPS::SP];
|
|
|
|
|
2014-06-04 03:16:24 -04:00
|
|
|
//Returns negative value on failure
|
|
|
|
return 0;
|
2012-10-21 02:37:23 +00:00
|
|
|
}
|
|
|
|
|
2014-06-04 03:18:31 -04:00
|
|
|
uint32 CIopBios::CancelAlarm(uint32 alarmFunction, uint32 param)
|
|
|
|
{
|
|
|
|
//TODO: This needs to garantee that the alarm handler function won't be called after the cancel
|
2014-11-30 18:54:28 -05:00
|
|
|
|
|
|
|
uint32 alarmThreadId = -1;
|
|
|
|
|
|
|
|
for(auto threadIterator = m_threads.Begin();
|
|
|
|
threadIterator != m_threads.End(); threadIterator++)
|
|
|
|
{
|
|
|
|
const auto& thread(m_threads[threadIterator]);
|
|
|
|
if(thread == nullptr) continue;
|
|
|
|
if(thread->threadProc == m_alarmThreadProcAddress)
|
|
|
|
{
|
|
|
|
alarmThreadId = thread->id;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(alarmThreadId == -1)
|
|
|
|
{
|
|
|
|
// handler not registered
|
|
|
|
return -105;
|
|
|
|
}
|
|
|
|
|
|
|
|
TerminateThread(alarmThreadId);
|
2014-06-04 03:18:31 -04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-10-22 22:36:49 +00:00
|
|
|
void CIopBios::ChangeThreadPriority(uint32 threadId, uint32 newPrio)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
2012-09-12 04:03:41 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: ChangeThreadPriority(threadId = %d, newPrio = %d);\r\n",
|
|
|
|
CurrentThreadId(), threadId, newPrio);
|
2011-10-22 22:36:49 +00:00
|
|
|
#endif
|
2012-06-30 02:31:22 +00:00
|
|
|
|
|
|
|
if(threadId == 0)
|
|
|
|
{
|
|
|
|
threadId = GetCurrentThreadId();
|
|
|
|
}
|
|
|
|
|
2011-10-22 22:36:49 +00:00
|
|
|
THREAD* thread = GetThread(threadId);
|
|
|
|
assert(thread != NULL);
|
|
|
|
|
|
|
|
if(thread == NULL)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
thread->priority = newPrio;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
2012-09-12 04:03:41 +00:00
|
|
|
uint32 CIopBios::ReferThreadStatus(uint32 threadId, uint32 statusPtr)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: ReferThreadStatus(threadId = %d, statusPtr = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), threadId, statusPtr);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(threadId == 0)
|
|
|
|
{
|
|
|
|
threadId = GetCurrentThreadId();
|
|
|
|
}
|
|
|
|
|
|
|
|
THREAD* thread = GetThread(threadId);
|
|
|
|
assert(thread != NULL);
|
|
|
|
|
|
|
|
if(thread == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 threadStatus = 0;
|
|
|
|
if(thread->status == THREAD_STATUS_DORMANT)
|
|
|
|
{
|
|
|
|
threadStatus |= 0x10;
|
|
|
|
}
|
|
|
|
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_ATTRIBUTE] = 0;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_OPTION] = thread->optionData;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_STATUS] = threadStatus;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_THREAD] = thread->threadProc;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_STACK] = thread->stackBase;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_STACKSIZE] = thread->stackSize;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_INITPRIORITY] = thread->initPriority;
|
|
|
|
reinterpret_cast<uint32*>(m_ram + statusPtr)[THREAD_INFO_PRIORITY] = thread->priority;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
void CIopBios::SleepThread()
|
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: SleepThread();\r\n",
|
|
|
|
CurrentThreadId());
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
if(thread->status != THREAD_STATUS_RUNNING)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Thread isn't running.");
|
|
|
|
}
|
|
|
|
if(thread->wakeupCount == 0)
|
|
|
|
{
|
|
|
|
thread->status = THREAD_STATUS_SLEEPING;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
thread->wakeupCount--;
|
|
|
|
}
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2008-11-07 23:50:47 +00:00
|
|
|
uint32 CIopBios::WakeupThread(uint32 threadId, bool inInterrupt)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: WakeupThread(threadId = %d);\r\n",
|
|
|
|
CurrentThreadId(), threadId);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = GetThread(threadId);
|
|
|
|
if(thread->status == THREAD_STATUS_SLEEPING)
|
|
|
|
{
|
|
|
|
thread->status = THREAD_STATUS_RUNNING;
|
2008-11-07 23:50:47 +00:00
|
|
|
if(!inInterrupt)
|
|
|
|
{
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
thread->wakeupCount++;
|
|
|
|
}
|
|
|
|
return thread->wakeupCount;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2011-05-05 04:28:11 +00:00
|
|
|
void CIopBios::SleepThreadTillVBlankStart()
|
|
|
|
{
|
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
thread->status = THREAD_STATUS_WAIT_VBLANK_START;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::SleepThreadTillVBlankEnd()
|
|
|
|
{
|
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
thread->status = THREAD_STATUS_WAIT_VBLANK_END;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
2012-05-21 06:31:50 +00:00
|
|
|
uint32 CIopBios::GetCurrentThreadId() const
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return CurrentThreadId();
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::LoadThreadContext(uint32 threadId)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = GetThread(threadId);
|
|
|
|
for(unsigned int i = 0; i < 32; i++)
|
|
|
|
{
|
2008-01-15 20:27:44 +00:00
|
|
|
if(i == CMIPS::R0) continue;
|
|
|
|
if(i == CMIPS::K0) continue;
|
|
|
|
if(i == CMIPS::K1) continue;
|
2012-03-30 05:41:11 +00:00
|
|
|
m_cpu.m_State.nGPR[i].nD0 = static_cast<int32>(thread->context.gpr[i]);
|
|
|
|
}
|
|
|
|
m_cpu.m_State.nPC = thread->context.epc;
|
|
|
|
m_cpu.m_State.nDelayedJumpAddr = thread->context.delayJump;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::SaveThreadContext(uint32 threadId)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread = GetThread(threadId);
|
|
|
|
for(unsigned int i = 0; i < 32; i++)
|
|
|
|
{
|
2008-01-15 20:27:44 +00:00
|
|
|
if(i == CMIPS::R0) continue;
|
|
|
|
if(i == CMIPS::K0) continue;
|
|
|
|
if(i == CMIPS::K1) continue;
|
2012-03-30 05:41:11 +00:00
|
|
|
thread->context.gpr[i] = m_cpu.m_State.nGPR[i].nV0;
|
|
|
|
}
|
|
|
|
thread->context.epc = m_cpu.m_State.nPC;
|
|
|
|
thread->context.delayJump = m_cpu.m_State.nDelayedJumpAddr;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2009-01-28 23:06:08 +00:00
|
|
|
void CIopBios::LinkThread(uint32 threadId)
|
|
|
|
{
|
|
|
|
THREAD* thread = m_threads[threadId];
|
|
|
|
uint32* nextThreadId = &ThreadLinkHead();
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
if((*nextThreadId) == 0)
|
|
|
|
{
|
|
|
|
(*nextThreadId) = threadId;
|
|
|
|
thread->nextThreadId = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
THREAD* currentThread = m_threads[(*nextThreadId)];
|
2012-05-27 00:54:16 +00:00
|
|
|
if(currentThread->priority > thread->priority)
|
2009-01-28 23:06:08 +00:00
|
|
|
{
|
|
|
|
thread->nextThreadId = (*nextThreadId);
|
|
|
|
(*nextThreadId) = threadId;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nextThreadId = ¤tThread->nextThreadId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::UnlinkThread(uint32 threadId)
|
|
|
|
{
|
|
|
|
THREAD* thread = m_threads[threadId];
|
|
|
|
uint32* nextThreadId = &ThreadLinkHead();
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
if((*nextThreadId) == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
THREAD* currentThread = m_threads[(*nextThreadId)];
|
|
|
|
if((*nextThreadId) == threadId)
|
|
|
|
{
|
|
|
|
(*nextThreadId) = thread->nextThreadId;
|
|
|
|
thread->nextThreadId = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nextThreadId = ¤tThread->nextThreadId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
void CIopBios::Reschedule()
|
|
|
|
{
|
2012-06-24 19:06:02 +00:00
|
|
|
if((m_cpu.m_State.nCOP0[CCOP_SCU::STATUS] & CMIPS::STATUS_EXL) != 0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
if(CurrentThreadId() != -1)
|
|
|
|
{
|
|
|
|
SaveThreadContext(CurrentThreadId());
|
2009-01-28 23:06:08 +00:00
|
|
|
UnlinkThread(CurrentThreadId());
|
|
|
|
LinkThread(CurrentThreadId());
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
2008-01-15 20:27:44 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 nextThreadId = GetNextReadyThread();
|
|
|
|
if(nextThreadId == -1)
|
|
|
|
{
|
2008-10-28 21:32:18 +00:00
|
|
|
#ifdef _DEBUG
|
2008-10-29 01:42:35 +00:00
|
|
|
// printf("Warning, no thread available for running.\r\n");
|
2008-10-28 21:32:18 +00:00
|
|
|
#endif
|
|
|
|
m_cpu.m_State.nPC = m_idleFunctionAddress;
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
2008-10-28 21:32:18 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
LoadThreadContext(nextThreadId);
|
|
|
|
}
|
2008-11-14 17:10:21 +00:00
|
|
|
#ifdef _DEBUG
|
2009-01-26 02:53:10 +00:00
|
|
|
if(nextThreadId != CurrentThreadId())
|
2008-11-14 17:10:21 +00:00
|
|
|
{
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "Switched over to thread %i.\r\n", nextThreadId);
|
|
|
|
}
|
|
|
|
#endif
|
2009-01-26 02:53:10 +00:00
|
|
|
CurrentThreadId() = nextThreadId;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2009-01-28 23:06:08 +00:00
|
|
|
uint32 CIopBios::GetNextReadyThread()
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2009-01-28 23:06:08 +00:00
|
|
|
uint32 nextThreadId = ThreadLinkHead();
|
|
|
|
while(nextThreadId != 0)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* nextThread = m_threads[nextThreadId];
|
2009-01-28 23:06:08 +00:00
|
|
|
nextThreadId = nextThread->nextThreadId;
|
2012-03-30 05:41:11 +00:00
|
|
|
if(GetCurrentTime() <= nextThread->nextActivateTime) continue;
|
|
|
|
if(nextThread->status == THREAD_STATUS_RUNNING)
|
|
|
|
{
|
|
|
|
return nextThread->id;
|
|
|
|
}
|
2009-01-28 23:06:08 +00:00
|
|
|
}
|
|
|
|
return -1;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64 CIopBios::GetCurrentTime()
|
|
|
|
{
|
2009-01-26 02:53:10 +00:00
|
|
|
return CurrentTime();
|
2008-11-07 23:50:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64 CIopBios::MilliSecToClock(uint32 value)
|
|
|
|
{
|
2012-05-29 01:01:02 +00:00
|
|
|
return (static_cast<uint64>(value) * static_cast<uint64>(PS2::IOP_CLOCK_OVER_FREQ)) / 1000;
|
2008-11-07 23:50:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint64 CIopBios::MicroSecToClock(uint32 value)
|
|
|
|
{
|
2012-05-29 01:01:02 +00:00
|
|
|
return (static_cast<uint64>(value) * static_cast<uint64>(PS2::IOP_CLOCK_OVER_FREQ)) / 1000000;
|
2008-11-07 23:50:47 +00:00
|
|
|
}
|
|
|
|
|
2008-11-21 23:15:53 +00:00
|
|
|
uint64 CIopBios::ClockToMicroSec(uint64 clock)
|
|
|
|
{
|
2012-05-29 01:01:02 +00:00
|
|
|
return (clock * 1000000) / static_cast<uint64>(PS2::IOP_CLOCK_OVER_FREQ);
|
2008-11-21 23:15:53 +00:00
|
|
|
}
|
|
|
|
|
2008-11-07 23:50:47 +00:00
|
|
|
void CIopBios::CountTicks(uint32 ticks)
|
|
|
|
{
|
2009-01-26 02:53:10 +00:00
|
|
|
CurrentTime() += ticks;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2011-05-05 04:28:11 +00:00
|
|
|
void CIopBios::NotifyVBlankStart()
|
|
|
|
{
|
|
|
|
uint32 nextThreadId = ThreadLinkHead();
|
|
|
|
while(nextThreadId != 0)
|
|
|
|
{
|
|
|
|
THREAD* nextThread = m_threads[nextThreadId];
|
|
|
|
nextThreadId = nextThread->nextThreadId;
|
|
|
|
if(nextThread->status == THREAD_STATUS_WAIT_VBLANK_START)
|
|
|
|
{
|
|
|
|
nextThread->status = THREAD_STATUS_RUNNING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::NotifyVBlankEnd()
|
|
|
|
{
|
|
|
|
uint32 nextThreadId = ThreadLinkHead();
|
|
|
|
while(nextThreadId != 0)
|
|
|
|
{
|
|
|
|
THREAD* nextThread = m_threads[nextThreadId];
|
|
|
|
nextThreadId = nextThread->nextThreadId;
|
|
|
|
if(nextThread->status == THREAD_STATUS_WAIT_VBLANK_END)
|
|
|
|
{
|
|
|
|
nextThread->status = THREAD_STATUS_RUNNING;
|
|
|
|
}
|
|
|
|
}
|
2014-10-16 03:33:59 -04:00
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
|
|
|
m_cdvdfsv->ProcessCommands(m_sifMan);
|
|
|
|
#endif
|
2011-05-05 04:28:11 +00:00
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
uint32 CIopBios::CreateSemaphore(uint32 initialCount, uint32 maxCount)
|
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: CreateSemaphore(initialCount = %i, maxCount = %i);\r\n",
|
|
|
|
CurrentThreadId(), initialCount, maxCount);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2009-01-30 22:10:15 +00:00
|
|
|
uint32 semaphoreId = m_semaphores.Allocate();
|
|
|
|
assert(semaphoreId != -1);
|
|
|
|
if(semaphoreId == -1)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
SEMAPHORE* semaphore = m_semaphores[semaphoreId];
|
2009-01-30 22:10:15 +00:00
|
|
|
|
|
|
|
semaphore->count = initialCount;
|
2012-03-30 05:41:11 +00:00
|
|
|
semaphore->maxCount = maxCount;
|
|
|
|
semaphore->id = semaphoreId;
|
|
|
|
semaphore->waitCount = 0;
|
2009-01-30 22:10:15 +00:00
|
|
|
|
|
|
|
return semaphore->id;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2009-02-09 05:27:03 +00:00
|
|
|
uint32 CIopBios::DeleteSemaphore(uint32 semaphoreId)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: DeleteSemaphore(semaphoreId = %i);\r\n",
|
|
|
|
CurrentThreadId(), semaphoreId);
|
2009-02-09 05:27:03 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
SEMAPHORE* semaphore = m_semaphores[semaphoreId];
|
|
|
|
if(semaphore == NULL)
|
|
|
|
{
|
2009-02-09 05:27:03 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%i: Warning, trying to access invalid semaphore with id %i.\r\n",
|
|
|
|
CurrentThreadId(), semaphoreId);
|
2012-03-30 05:41:11 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2009-02-09 05:27:03 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
assert(semaphore->waitCount == 0);
|
|
|
|
m_semaphores.Free(semaphoreId);
|
2009-02-09 05:27:03 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
return 0;
|
2009-02-09 05:27:03 +00:00
|
|
|
}
|
|
|
|
|
2008-10-28 21:32:18 +00:00
|
|
|
uint32 CIopBios::SignalSemaphore(uint32 semaphoreId, bool inInterrupt)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-04-12 04:52:26 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: SignalSemaphore(semaphoreId = %d, inInterrupt = %d);\r\n",
|
|
|
|
CurrentThreadId(), semaphoreId, inInterrupt);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
SEMAPHORE* semaphore = m_semaphores[semaphoreId];
|
2009-01-30 22:10:15 +00:00
|
|
|
if(semaphore == NULL)
|
|
|
|
{
|
2012-04-12 04:52:26 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: Warning, trying to access invalid semaphore with id %d.\r\n",
|
2009-01-30 22:10:15 +00:00
|
|
|
CurrentThreadId(), semaphoreId);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
if(semaphore->waitCount != 0)
|
|
|
|
{
|
2009-01-28 23:06:08 +00:00
|
|
|
for(ThreadList::iterator threadIterator(m_threads.Begin());
|
|
|
|
threadIterator != m_threads.End(); threadIterator++)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
THREAD* thread(m_threads[threadIterator]);
|
2009-01-28 23:06:08 +00:00
|
|
|
if(thread == NULL) continue;
|
2012-03-30 05:41:11 +00:00
|
|
|
if(thread->waitSemaphore == semaphoreId)
|
|
|
|
{
|
2012-04-12 04:52:26 +00:00
|
|
|
if(thread->status != THREAD_STATUS_WAITING_SEMAPHORE)
|
2012-03-30 05:41:11 +00:00
|
|
|
{
|
2012-04-12 04:52:26 +00:00
|
|
|
throw std::runtime_error("Thread not waiting for semaphone (inconsistent state).");
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
thread->status = THREAD_STATUS_RUNNING;
|
|
|
|
thread->waitSemaphore = 0;
|
2008-10-28 21:32:18 +00:00
|
|
|
if(!inInterrupt)
|
|
|
|
{
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
semaphore->waitCount--;
|
|
|
|
if(semaphore->waitCount == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
semaphore->count++;
|
|
|
|
}
|
|
|
|
return semaphore->count;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::WaitSemaphore(uint32 semaphoreId)
|
|
|
|
{
|
2008-10-23 02:21:42 +00:00
|
|
|
#ifdef _DEBUG
|
2012-04-12 04:52:26 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: WaitSemaphore(semaphoreId = %d);\r\n",
|
2012-03-30 05:41:11 +00:00
|
|
|
CurrentThreadId(), semaphoreId);
|
2008-10-23 02:21:42 +00:00
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
SEMAPHORE* semaphore = m_semaphores[semaphoreId];
|
2009-01-30 22:10:15 +00:00
|
|
|
if(semaphore == NULL)
|
|
|
|
{
|
2012-04-12 04:52:26 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: Warning, trying to access invalid semaphore with id %d.\r\n",
|
2009-01-30 22:10:15 +00:00
|
|
|
CurrentThreadId(), semaphoreId);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
if(semaphore->count == 0)
|
|
|
|
{
|
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
2012-04-12 04:52:26 +00:00
|
|
|
thread->status = THREAD_STATUS_WAITING_SEMAPHORE;
|
|
|
|
thread->waitSemaphore = semaphoreId;
|
2012-03-30 05:41:11 +00:00
|
|
|
semaphore->waitCount++;
|
2012-04-12 04:52:26 +00:00
|
|
|
m_rescheduleNeeded = true;
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
semaphore->count--;
|
|
|
|
}
|
|
|
|
return semaphore->count;
|
2012-04-12 04:52:26 +00:00
|
|
|
}
|
|
|
|
|
2015-03-08 22:11:36 -04:00
|
|
|
uint32 CIopBios::PollSemaphore(uint32 semaphoreId)
|
|
|
|
{
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: PollSemaphore(semaphoreId = %d);\r\n",
|
|
|
|
CurrentThreadId(), semaphoreId);
|
|
|
|
|
|
|
|
auto semaphore = m_semaphores[semaphoreId];
|
|
|
|
if(semaphore == nullptr)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(semaphore->count == 0)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
semaphore->count--;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::ReferSemaphoreStatus(uint32 semaphoreId, uint32 statusPtr)
|
|
|
|
{
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: ReferSemaphoreStatus(semaphoreId = %d, statusPtr = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), semaphoreId, statusPtr);
|
|
|
|
|
|
|
|
auto semaphore = m_semaphores[semaphoreId];
|
|
|
|
if(semaphore == nullptr)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto status = reinterpret_cast<SEMAPHORE_STATUS*>(m_ram + statusPtr);
|
|
|
|
status->attrib = 0;
|
|
|
|
status->option = 0;
|
|
|
|
status->initCount = 0;
|
|
|
|
status->maxCount = semaphore->maxCount;
|
|
|
|
status->currentCount = semaphore->count;
|
|
|
|
status->numWaitThreads = semaphore->waitCount;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-04-12 04:52:26 +00:00
|
|
|
uint32 CIopBios::CreateEventFlag(uint32 attributes, uint32 options, uint32 initValue)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: CreateEventFlag(attr = 0x%0.8X, opt = 0x%0.8X, initValue = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), attributes, options, initValue);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uint32 eventId = m_eventFlags.Allocate();
|
|
|
|
assert(eventId != -1);
|
|
|
|
if(eventId == -1)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
EVENTFLAG* eventFlag = m_eventFlags[eventId];
|
|
|
|
|
|
|
|
eventFlag->id = eventId;
|
|
|
|
eventFlag->value = initValue;
|
|
|
|
eventFlag->options = options;
|
|
|
|
eventFlag->attributes = attributes;
|
|
|
|
|
|
|
|
return eventFlag->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::SetEventFlag(uint32 eventId, uint32 value, bool inInterrupt)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
2012-04-26 05:50:37 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: SetEventFlag(eventId = %d, value = 0x%0.8X, inInterrupt = %d);\r\n",
|
2012-04-12 04:52:26 +00:00
|
|
|
CurrentThreadId(), eventId, value, inInterrupt);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
EVENTFLAG* eventFlag = m_eventFlags[eventId];
|
|
|
|
if(eventFlag == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
eventFlag->value |= value;
|
|
|
|
|
|
|
|
//Check all threads waiting for this event
|
|
|
|
for(auto threadIterator(m_threads.Begin()); threadIterator != m_threads.End(); threadIterator++)
|
|
|
|
{
|
|
|
|
THREAD* thread(m_threads[threadIterator]);
|
|
|
|
if(thread == NULL) continue;
|
2012-06-30 02:31:22 +00:00
|
|
|
if(thread->status != THREAD_STATUS_WAITING_EVENTFLAG) continue;
|
2012-04-12 04:52:26 +00:00
|
|
|
if(thread->waitEventFlag == eventId)
|
|
|
|
{
|
2012-09-12 04:03:41 +00:00
|
|
|
bool success = ProcessEventFlag(thread->waitEventFlagMode, eventFlag->value, thread->waitEventFlagMask,
|
|
|
|
(thread->waitEventFlagResultPtr != 0) ? reinterpret_cast<uint32*>(m_ram + thread->waitEventFlagResultPtr) : nullptr);
|
2012-04-12 04:52:26 +00:00
|
|
|
if(success)
|
|
|
|
{
|
|
|
|
thread->waitEventFlag = 0;
|
|
|
|
thread->waitEventFlagResultPtr = 0;
|
|
|
|
|
|
|
|
thread->status = THREAD_STATUS_RUNNING;
|
|
|
|
if(!inInterrupt)
|
|
|
|
{
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::ClearEventFlag(uint32 eventId, uint32 value)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: ClearEventFlag(eventId = %d, value = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), eventId, value);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
EVENTFLAG* eventFlag = m_eventFlags[eventId];
|
|
|
|
if(eventFlag == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
eventFlag->value &= value;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::WaitEventFlag(uint32 eventId, uint32 value, uint32 mode, uint32 resultPtr)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: WaitEventFlag(eventId = %d, value = 0x%0.8X, mode = 0x%0.2X, resultPtr = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), eventId, value, mode, resultPtr);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
EVENTFLAG* eventFlag = m_eventFlags[eventId];
|
|
|
|
if(eventFlag == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-09-12 04:03:41 +00:00
|
|
|
bool success = ProcessEventFlag(mode, eventFlag->value, value,
|
|
|
|
(resultPtr != 0) ? reinterpret_cast<uint32*>(m_ram + resultPtr) : nullptr);
|
|
|
|
if(!success)
|
2012-04-12 04:52:26 +00:00
|
|
|
{
|
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
thread->status = THREAD_STATUS_WAITING_EVENTFLAG;
|
|
|
|
thread->waitEventFlag = eventId;
|
2012-09-12 04:03:41 +00:00
|
|
|
thread->waitEventFlagMode = mode;
|
2012-04-12 04:52:26 +00:00
|
|
|
thread->waitEventFlagMask = value;
|
|
|
|
thread->waitEventFlagResultPtr = resultPtr;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
2012-04-26 05:50:37 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::ReferEventFlagStatus(uint32 eventId, uint32 infoPtr)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: ReferEventFlagStatus(eventId = %d, infoPtr = 0x%0.8X);\r\n",
|
|
|
|
CurrentThreadId(), eventId, infoPtr);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
EVENTFLAG* eventFlag = m_eventFlags[eventId];
|
|
|
|
if(eventFlag == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(infoPtr == 0)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
EVENTFLAGINFO* eventFlagInfo(reinterpret_cast<EVENTFLAGINFO*>(m_ram + infoPtr));
|
|
|
|
eventFlagInfo->attributes = eventFlag->attributes;
|
|
|
|
eventFlagInfo->options = eventFlag->options;
|
|
|
|
eventFlagInfo->initBits = 0;
|
|
|
|
eventFlagInfo->currBits = eventFlag->value;
|
|
|
|
eventFlagInfo->numThreads = 0;
|
|
|
|
|
2012-04-12 04:52:26 +00:00
|
|
|
return 0;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2012-09-12 04:03:41 +00:00
|
|
|
bool CIopBios::ProcessEventFlag(uint32 mode, uint32& value, uint32 mask, uint32* resultPtr)
|
|
|
|
{
|
|
|
|
bool success = false;
|
|
|
|
uint32 maskResult = value & mask;
|
|
|
|
|
|
|
|
if(mode & WEF_OR)
|
|
|
|
{
|
|
|
|
success = (maskResult != 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
success = (maskResult == mask);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(success)
|
|
|
|
{
|
|
|
|
if(mode & WEF_CLEAR)
|
|
|
|
{
|
|
|
|
value &= ~mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(resultPtr)
|
|
|
|
{
|
|
|
|
*resultPtr = maskResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2012-06-30 02:31:22 +00:00
|
|
|
uint32 CIopBios::CreateMessageBox()
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: CreateMessageBox();\r\n",
|
|
|
|
CurrentThreadId());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uint32 boxId = m_messageBoxes.Allocate();
|
|
|
|
assert(boxId != -1);
|
|
|
|
if(boxId == -1)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
MESSAGEBOX* box = m_messageBoxes[boxId];
|
|
|
|
box->nextMsgPtr = 0;
|
|
|
|
|
|
|
|
return boxId;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::SendMessageBox(uint32 boxId, uint32 messagePtr)
|
|
|
|
{
|
|
|
|
bool inInterrupt = false;
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: SendMessageBox(boxId = %d, messagePtr = 0x%0.8X, inInterrupt = %d);\r\n",
|
|
|
|
CurrentThreadId(), boxId, messagePtr, inInterrupt);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
MESSAGEBOX* box = m_messageBoxes[boxId];
|
|
|
|
if(box == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check if there's a thread waiting for a message first
|
|
|
|
for(auto threadIterator(m_threads.Begin()); threadIterator != m_threads.End(); threadIterator++)
|
|
|
|
{
|
|
|
|
THREAD* thread(m_threads[threadIterator]);
|
|
|
|
if(thread == NULL) continue;
|
|
|
|
if(thread->status != THREAD_STATUS_WAITING_MESSAGEBOX) continue;
|
|
|
|
if(thread->waitMessageBox == boxId)
|
|
|
|
{
|
|
|
|
if(thread->waitMessageBoxResultPtr != 0)
|
|
|
|
{
|
|
|
|
uint32* result = reinterpret_cast<uint32*>(m_ram + thread->waitMessageBoxResultPtr);
|
|
|
|
*result = messagePtr;
|
|
|
|
}
|
|
|
|
|
|
|
|
thread->waitMessageBox = 0;
|
|
|
|
thread->waitMessageBoxResultPtr = 0;
|
|
|
|
|
|
|
|
thread->status = THREAD_STATUS_RUNNING;
|
|
|
|
if(!inInterrupt)
|
|
|
|
{
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::ReceiveMessageBox(uint32 messagePtr, uint32 boxId)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: ReceiveMessageBox(messagePtr = 0x%0.8X, boxId = %d);\r\n",
|
|
|
|
CurrentThreadId(), messagePtr, boxId);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
MESSAGEBOX* box = m_messageBoxes[boxId];
|
|
|
|
if(box == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(box->nextMsgPtr != 0)
|
|
|
|
{
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
THREAD* thread = GetThread(CurrentThreadId());
|
|
|
|
thread->status = THREAD_STATUS_WAITING_MESSAGEBOX;
|
|
|
|
thread->waitMessageBox = boxId;
|
|
|
|
thread->waitMessageBoxResultPtr = messagePtr;
|
|
|
|
m_rescheduleNeeded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::PollMessageBox(uint32 messagePtr, uint32 boxId)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "%d: PollMessageBox(messagePtr = 0x%0.8X, boxId = %d);\r\n",
|
|
|
|
CurrentThreadId(), messagePtr, boxId);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
MESSAGEBOX* box = m_messageBoxes[boxId];
|
|
|
|
if(box == NULL)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(box->nextMsgPtr == 0)
|
|
|
|
{
|
|
|
|
return 0xFFFFFE58;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
Iop::CIoman* CIopBios::GetIoman()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return m_ioman;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2009-04-28 01:20:03 +00:00
|
|
|
Iop::CCdvdman* CIopBios::GetCdvdman()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return m_cdvdman;
|
2009-04-28 01:20:03 +00:00
|
|
|
}
|
|
|
|
|
2012-10-24 06:39:29 +00:00
|
|
|
Iop::CLoadcore* CIopBios::GetLoadcore()
|
|
|
|
{
|
|
|
|
return m_loadcore;
|
|
|
|
}
|
|
|
|
|
2008-10-20 04:14:13 +00:00
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
|
|
|
|
2008-03-03 00:38:28 +00:00
|
|
|
Iop::CPadMan* CIopBios::GetPadman()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return m_padman;
|
2008-03-03 00:38:28 +00:00
|
|
|
}
|
|
|
|
|
2008-11-10 01:46:02 +00:00
|
|
|
Iop::CCdvdfsv* CIopBios::GetCdvdfsv()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
return m_cdvdfsv;
|
2008-11-10 01:46:02 +00:00
|
|
|
}
|
|
|
|
|
2008-10-20 04:14:13 +00:00
|
|
|
#endif
|
|
|
|
|
2008-10-28 01:48:21 +00:00
|
|
|
bool CIopBios::RegisterIntrHandler(uint32 line, uint32 mode, uint32 handler, uint32 arg)
|
|
|
|
{
|
2009-01-30 22:10:15 +00:00
|
|
|
assert(FindIntrHandler(line) == -1);
|
|
|
|
|
|
|
|
uint32 handlerId = m_intrHandlers.Allocate();
|
|
|
|
assert(handlerId != -1);
|
|
|
|
if(handlerId == -1)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
INTRHANDLER* intrHandler = m_intrHandlers[handlerId];
|
|
|
|
intrHandler->line = line;
|
|
|
|
intrHandler->mode = mode;
|
|
|
|
intrHandler->handler = handler;
|
|
|
|
intrHandler->arg = arg;
|
2009-01-30 22:10:15 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
return true;
|
2008-10-28 01:48:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CIopBios::ReleaseIntrHandler(uint32 line)
|
|
|
|
{
|
2009-01-30 22:10:15 +00:00
|
|
|
uint32 handlerId = FindIntrHandler(line);
|
|
|
|
if(handlerId == -1)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_intrHandlers.Free(handlerId);
|
2012-03-30 05:41:11 +00:00
|
|
|
return true;
|
2008-10-28 01:48:21 +00:00
|
|
|
}
|
|
|
|
|
2009-01-30 22:10:15 +00:00
|
|
|
uint32 CIopBios::FindIntrHandler(uint32 line)
|
|
|
|
{
|
|
|
|
for(IntrHandlerList::iterator handlerIterator(m_intrHandlers.Begin());
|
|
|
|
handlerIterator != m_intrHandlers.End(); handlerIterator++)
|
|
|
|
{
|
|
|
|
INTRHANDLER* handler = m_intrHandlers[handlerIterator];
|
|
|
|
if(handler == NULL) continue;
|
|
|
|
if(handler->line == line) return handlerIterator;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
uint32 CIopBios::AssembleThreadFinish(CMIPSAssembler& assembler)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 address = BIOS_HANDLERS_BASE + assembler.GetProgramSize() * 4;
|
2012-09-18 03:21:43 +00:00
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_EXITTHREAD);
|
2012-03-30 05:41:11 +00:00
|
|
|
assembler.SYSCALL();
|
|
|
|
return address;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2008-10-28 21:32:18 +00:00
|
|
|
uint32 CIopBios::AssembleReturnFromException(CMIPSAssembler& assembler)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 address = BIOS_HANDLERS_BASE + assembler.GetProgramSize() * 4;
|
2015-04-09 01:02:46 -04:00
|
|
|
assembler.ADDIU(CMIPS::SP, CMIPS::SP, STACK_FRAME_RESERVE_SIZE);
|
2012-09-18 03:21:43 +00:00
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_RETURNFROMEXCEPTION);
|
2012-03-30 05:41:11 +00:00
|
|
|
assembler.SYSCALL();
|
|
|
|
return address;
|
2008-10-28 21:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::AssembleIdleFunction(CMIPSAssembler& assembler)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 address = BIOS_HANDLERS_BASE + assembler.GetProgramSize() * 4;
|
2012-09-18 03:21:43 +00:00
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_RESCHEDULE);
|
|
|
|
assembler.SYSCALL();
|
|
|
|
return address;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32 CIopBios::AssembleModuleLoaderThreadProc(CMIPSAssembler& assembler)
|
|
|
|
{
|
|
|
|
uint32 address = BIOS_HANDLERS_BASE + assembler.GetProgramSize() * 4;
|
2014-07-10 23:43:29 -04:00
|
|
|
|
|
|
|
auto startLabel = assembler.CreateLabel();
|
|
|
|
|
|
|
|
assembler.MarkLabel(startLabel);
|
2012-09-18 03:21:43 +00:00
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_SLEEPTHREAD);
|
|
|
|
assembler.SYSCALL();
|
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_PROCESSMODULELOAD);
|
2012-03-30 05:41:11 +00:00
|
|
|
assembler.SYSCALL();
|
2014-07-10 23:43:29 -04:00
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_FINISHMODULELOAD);
|
|
|
|
assembler.SYSCALL();
|
|
|
|
assembler.BEQ(CMIPS::R0, CMIPS::R0, startLabel);
|
|
|
|
assembler.NOP();
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
return address;
|
2008-10-28 21:32:18 +00:00
|
|
|
}
|
|
|
|
|
2012-10-21 02:37:23 +00:00
|
|
|
uint32 CIopBios::AssembleAlarmThreadProc(CMIPSAssembler& assembler)
|
|
|
|
{
|
|
|
|
uint32 address = BIOS_HANDLERS_BASE + assembler.GetProgramSize() * 4;
|
|
|
|
auto delayThreadLabel = assembler.CreateLabel();
|
|
|
|
|
|
|
|
//Prolog
|
|
|
|
assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0xFF80);
|
|
|
|
assembler.SW(CMIPS::RA, 0x10, CMIPS::SP);
|
|
|
|
assembler.SW(CMIPS::S0, 0x14, CMIPS::SP);
|
|
|
|
|
|
|
|
assembler.MOV(CMIPS::S0, CMIPS::A0); //S0 has the info struct ptr
|
|
|
|
|
|
|
|
//Delay thread
|
|
|
|
assembler.MarkLabel(delayThreadLabel);
|
|
|
|
assembler.LW(CMIPS::A0, 0x08, CMIPS::S0);
|
|
|
|
assembler.ADDIU(CMIPS::V0, CMIPS::R0, SYSCALL_DELAYTHREADTICKS);
|
|
|
|
assembler.SYSCALL();
|
|
|
|
|
|
|
|
//Call handler
|
|
|
|
assembler.LW(CMIPS::V0, 0x00, CMIPS::S0);
|
|
|
|
assembler.JALR(CMIPS::V0);
|
|
|
|
assembler.LW(CMIPS::A0, 0x04, CMIPS::S0);
|
|
|
|
|
|
|
|
assembler.BNE(CMIPS::V0, CMIPS::R0, delayThreadLabel);
|
|
|
|
assembler.SW(CMIPS::V0, 0x08, CMIPS::S0);
|
|
|
|
|
|
|
|
//Epilog
|
|
|
|
assembler.LW(CMIPS::S0, 0x14, CMIPS::SP);
|
|
|
|
assembler.LW(CMIPS::RA, 0x10, CMIPS::SP);
|
|
|
|
|
|
|
|
assembler.JR(CMIPS::RA);
|
|
|
|
assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0x0080);
|
|
|
|
|
|
|
|
return address;
|
|
|
|
}
|
|
|
|
|
2008-10-28 01:48:21 +00:00
|
|
|
void CIopBios::HandleException()
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2014-06-04 23:37:40 -04:00
|
|
|
assert(m_cpu.m_State.nHasException == MIPS_EXCEPTION_SYSCALL);
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_rescheduleNeeded = false;
|
2008-12-01 04:00:36 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 searchAddress = m_cpu.m_State.nCOP0[CCOP_SCU::EPC];
|
|
|
|
uint32 callInstruction = m_cpu.m_pMemoryMap->GetWord(searchAddress);
|
|
|
|
if(callInstruction == 0x0000000C)
|
|
|
|
{
|
|
|
|
switch(m_cpu.m_State.nGPR[CMIPS::V0].nV0)
|
|
|
|
{
|
2012-09-18 03:21:43 +00:00
|
|
|
case SYSCALL_EXITTHREAD:
|
2012-09-12 04:03:41 +00:00
|
|
|
ExitThread();
|
2008-10-28 21:32:18 +00:00
|
|
|
break;
|
2012-09-18 03:21:43 +00:00
|
|
|
case SYSCALL_RETURNFROMEXCEPTION:
|
2008-10-28 21:32:18 +00:00
|
|
|
ReturnFromException();
|
|
|
|
break;
|
2012-09-18 03:21:43 +00:00
|
|
|
case SYSCALL_RESCHEDULE:
|
2008-10-28 21:32:18 +00:00
|
|
|
Reschedule();
|
|
|
|
break;
|
2012-09-18 03:21:43 +00:00
|
|
|
case SYSCALL_SLEEPTHREAD:
|
|
|
|
SleepThread();
|
|
|
|
break;
|
|
|
|
case SYSCALL_PROCESSMODULELOAD:
|
|
|
|
ProcessModuleLoad();
|
|
|
|
break;
|
2014-07-10 23:43:29 -04:00
|
|
|
case SYSCALL_FINISHMODULELOAD:
|
|
|
|
FinishModuleLoad();
|
|
|
|
break;
|
2012-10-21 02:37:23 +00:00
|
|
|
case SYSCALL_DELAYTHREADTICKS:
|
|
|
|
DelayThreadTicks(m_cpu.m_State.nGPR[CMIPS::A0].nV0);
|
|
|
|
break;
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Search for the import record
|
|
|
|
uint32 instruction = callInstruction;
|
|
|
|
while(instruction != 0x41E00000)
|
|
|
|
{
|
|
|
|
searchAddress -= 4;
|
|
|
|
instruction = m_cpu.m_pMemoryMap->GetWord(searchAddress);
|
|
|
|
}
|
|
|
|
uint32 functionId = callInstruction & 0xFFFF;
|
|
|
|
uint32 version = m_cpu.m_pMemoryMap->GetWord(searchAddress + 8);
|
2011-01-16 21:47:01 +00:00
|
|
|
std::string moduleName = ReadModuleName(searchAddress + 0x0C);
|
2008-01-15 20:27:44 +00:00
|
|
|
|
2015-03-29 23:57:21 -04:00
|
|
|
#ifdef _DEBUG
|
|
|
|
if(moduleName == "libsd")
|
|
|
|
{
|
|
|
|
Iop::CLibSd::TraceCall(m_cpu, functionId);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
IopModuleMapType::iterator module(m_modules.find(moduleName));
|
|
|
|
if(module != m_modules.end())
|
|
|
|
{
|
|
|
|
module->second->Invoke(m_cpu, functionId);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-01-15 20:27:44 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%0.8X: Trying to call a function from non-existing module (%s, %d).\r\n",
|
|
|
|
m_cpu.m_State.nPC, moduleName.c_str(), functionId);
|
2008-01-15 20:27:44 +00:00
|
|
|
#endif
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
2008-01-15 20:27:44 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
if(m_rescheduleNeeded)
|
|
|
|
{
|
2008-10-28 21:32:18 +00:00
|
|
|
assert((m_cpu.m_State.nCOP0[CCOP_SCU::STATUS] & CMIPS::STATUS_EXL) == 0);
|
2012-03-30 05:41:11 +00:00
|
|
|
m_rescheduleNeeded = false;
|
|
|
|
Reschedule();
|
|
|
|
}
|
2008-01-15 20:27:44 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_cpu.m_State.nHasException = 0;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2008-10-28 01:48:21 +00:00
|
|
|
void CIopBios::HandleInterrupt()
|
|
|
|
{
|
2008-10-28 21:32:18 +00:00
|
|
|
if(m_cpu.GenerateInterrupt(m_cpu.m_State.nPC))
|
2008-10-28 01:48:21 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
//Find first concerned interrupt
|
|
|
|
unsigned int line = -1;
|
|
|
|
UNION64_32 status(
|
2008-10-28 21:32:18 +00:00
|
|
|
m_cpu.m_pMemoryMap->GetWord(Iop::CIntc::STATUS0),
|
|
|
|
m_cpu.m_pMemoryMap->GetWord(Iop::CIntc::STATUS1));
|
|
|
|
UNION64_32 mask(
|
|
|
|
m_cpu.m_pMemoryMap->GetWord(Iop::CIntc::MASK0),
|
|
|
|
m_cpu.m_pMemoryMap->GetWord(Iop::CIntc::MASK1));
|
|
|
|
status.f &= mask.f;
|
2012-03-30 05:41:11 +00:00
|
|
|
for(unsigned int i = 0; i < 0x40; i++)
|
|
|
|
{
|
|
|
|
if(status.f & (1LL << i))
|
2008-10-28 21:32:18 +00:00
|
|
|
{
|
|
|
|
line = i;
|
|
|
|
break;
|
|
|
|
}
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
2008-10-28 21:32:18 +00:00
|
|
|
assert(line != -1);
|
|
|
|
if(line == -1)
|
|
|
|
{
|
|
|
|
ReturnFromException();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
status.f = ~(1LL << line);
|
|
|
|
m_cpu.m_pMemoryMap->SetWord(Iop::CIntc::STATUS0, status.h0);
|
|
|
|
m_cpu.m_pMemoryMap->SetWord(Iop::CIntc::STATUS1, status.h1);
|
|
|
|
//Check if there's an handler to call
|
|
|
|
{
|
2009-01-30 22:10:15 +00:00
|
|
|
uint32 handlerId = FindIntrHandler(line);
|
|
|
|
if(handlerId == -1)
|
2008-10-28 21:32:18 +00:00
|
|
|
{
|
|
|
|
ReturnFromException();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Snap out of current thread
|
2009-01-26 02:53:10 +00:00
|
|
|
if(CurrentThreadId() != -1)
|
2008-10-28 21:32:18 +00:00
|
|
|
{
|
2009-01-26 02:53:10 +00:00
|
|
|
SaveThreadContext(CurrentThreadId());
|
2008-10-28 21:32:18 +00:00
|
|
|
}
|
2009-01-26 02:53:10 +00:00
|
|
|
CurrentThreadId() = -1;
|
2009-01-30 22:10:15 +00:00
|
|
|
INTRHANDLER* handler = m_intrHandlers[handlerId];
|
|
|
|
m_cpu.m_State.nPC = handler->handler;
|
2015-04-09 01:02:46 -04:00
|
|
|
m_cpu.m_State.nGPR[CMIPS::SP].nD0 -= STACK_FRAME_RESERVE_SIZE;
|
2009-01-30 22:10:15 +00:00
|
|
|
m_cpu.m_State.nGPR[CMIPS::A0].nD0 = static_cast<int32>(handler->arg);
|
2008-10-28 21:32:18 +00:00
|
|
|
m_cpu.m_State.nGPR[CMIPS::RA].nD0 = static_cast<int32>(m_returnFromExceptionAddress);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::ReturnFromException()
|
|
|
|
{
|
|
|
|
uint32& status = m_cpu.m_State.nCOP0[CCOP_SCU::STATUS];
|
|
|
|
assert(status & (CMIPS::STATUS_ERL | CMIPS::STATUS_EXL));
|
|
|
|
if(status & CMIPS::STATUS_ERL)
|
|
|
|
{
|
|
|
|
status &= ~CMIPS::STATUS_ERL;
|
|
|
|
}
|
|
|
|
else if(status & CMIPS::STATUS_EXL)
|
|
|
|
{
|
|
|
|
status &= ~CMIPS::STATUS_EXL;
|
2008-10-28 01:48:21 +00:00
|
|
|
}
|
2008-10-28 21:32:18 +00:00
|
|
|
Reschedule();
|
2008-10-28 01:48:21 +00:00
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
void CIopBios::DeleteModules()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
while(m_modules.size() != 0)
|
|
|
|
{
|
|
|
|
delete m_modules.begin()->second;
|
|
|
|
m_modules.erase(m_modules.begin());
|
|
|
|
}
|
|
|
|
m_dynamicModules.clear();
|
|
|
|
|
2015-02-07 23:06:08 -05:00
|
|
|
m_libsd.reset();
|
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_sifMan = NULL;
|
|
|
|
m_stdio = NULL;
|
|
|
|
m_ioman = NULL;
|
|
|
|
m_sysmem = NULL;
|
|
|
|
m_modload = NULL;
|
|
|
|
m_sysmem = NULL;
|
2008-11-25 02:00:42 +00:00
|
|
|
#ifdef _IOP_EMULATE_MODULES
|
2012-03-30 05:41:11 +00:00
|
|
|
m_padman = NULL;
|
|
|
|
m_cdvdfsv = NULL;
|
2008-11-25 02:00:42 +00:00
|
|
|
#endif
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2011-01-16 21:47:01 +00:00
|
|
|
std::string CIopBios::ReadModuleName(uint32 address)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2011-01-16 21:47:01 +00:00
|
|
|
std::string moduleName;
|
2012-01-08 06:04:48 +00:00
|
|
|
const CMemoryMap::MEMORYMAPELEMENT* memoryMapElem = m_cpu.m_pMemoryMap->GetReadMap(address);
|
2011-01-16 21:47:01 +00:00
|
|
|
assert(memoryMapElem != NULL);
|
|
|
|
assert(memoryMapElem->nType == CMemoryMap::MEMORYMAP_TYPE_MEMORY);
|
|
|
|
uint8* memory = reinterpret_cast<uint8*>(memoryMapElem->pPointer) + (address - memoryMapElem->nStart);
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
uint8 character = *(memory++);
|
|
|
|
if(character == 0) break;
|
|
|
|
if(character < 0x10) continue;
|
|
|
|
moduleName += character;
|
|
|
|
}
|
|
|
|
return moduleName;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::RegisterModule(Iop::CModule* module)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
m_modules[module->GetId()] = module;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2009-02-06 05:10:51 +00:00
|
|
|
void CIopBios::ClearDynamicModules()
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
for(DynamicIopModuleListType::iterator dynModuleIterator(m_dynamicModules.begin());
|
|
|
|
dynModuleIterator != m_dynamicModules.end(); dynModuleIterator++)
|
|
|
|
{
|
|
|
|
IopModuleMapType::iterator module(m_modules.find((*dynModuleIterator)->GetId()));
|
|
|
|
assert(module != m_modules.end());
|
|
|
|
if(module != m_modules.end())
|
|
|
|
{
|
|
|
|
delete module->second;
|
|
|
|
m_modules.erase(module);
|
|
|
|
}
|
|
|
|
}
|
2009-02-06 05:10:51 +00:00
|
|
|
|
2012-03-30 05:41:11 +00:00
|
|
|
m_dynamicModules.clear();
|
2009-02-06 05:10:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::RegisterDynamicModule(Iop::CDynamic* dynamicModule)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
m_dynamicModules.push_back(dynamicModule);
|
|
|
|
RegisterModule(dynamicModule);
|
2009-02-06 05:10:51 +00:00
|
|
|
}
|
|
|
|
|
2008-01-15 20:27:44 +00:00
|
|
|
uint32 CIopBios::Push(uint32& address, const uint8* data, uint32 size)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 fixedSize = ((size + 0x3) / 0x4) * 0x4;
|
|
|
|
address -= fixedSize;
|
|
|
|
memcpy(&m_ram[address], data, size);
|
|
|
|
return address;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
2008-10-20 04:14:13 +00:00
|
|
|
uint32 CIopBios::LoadExecutable(CELF& elf, ExecutableRange& executableRange)
|
2008-01-15 20:27:44 +00:00
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
unsigned int programHeaderIndex = GetElfProgramToLoad(elf);
|
|
|
|
if(programHeaderIndex == -1)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("No program to load.");
|
|
|
|
}
|
|
|
|
ELFPROGRAMHEADER* programHeader = elf.GetProgram(programHeaderIndex);
|
|
|
|
uint32 baseAddress = m_sysmem->AllocateMemory(programHeader->nMemorySize, 0, 0);
|
|
|
|
RelocateElf(elf, baseAddress);
|
|
|
|
|
|
|
|
memcpy(
|
|
|
|
m_ram + baseAddress,
|
|
|
|
elf.GetContent() + programHeader->nOffset,
|
|
|
|
programHeader->nFileSize);
|
|
|
|
|
|
|
|
executableRange.first = baseAddress;
|
|
|
|
executableRange.second = baseAddress + programHeader->nMemorySize;
|
|
|
|
|
|
|
|
//Clean BSS sections
|
|
|
|
{
|
|
|
|
const ELFHEADER& header(elf.GetHeader());
|
|
|
|
for(unsigned int i = 0; i < header.nSectHeaderCount; i++)
|
|
|
|
{
|
|
|
|
ELFSECTIONHEADER* sectionHeader = elf.GetSection(i);
|
|
|
|
if(sectionHeader->nType == CELF::SHT_NOBITS && sectionHeader->nStart != 0)
|
|
|
|
{
|
|
|
|
memset(m_ram + baseAddress + sectionHeader->nStart, 0, sectionHeader->nSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return baseAddress + elf.GetHeader().nEntryPoint;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int CIopBios::GetElfProgramToLoad(CELF& elf)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
unsigned int program = -1;
|
|
|
|
const ELFHEADER& header = elf.GetHeader();
|
|
|
|
for(unsigned int i = 0; i < header.nProgHeaderCount; i++)
|
|
|
|
{
|
|
|
|
ELFPROGRAMHEADER* programHeader = elf.GetProgram(i);
|
|
|
|
if(programHeader != NULL && programHeader->nType == 1)
|
|
|
|
{
|
|
|
|
if(program != -1)
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Multiple loadable program headers found.");
|
|
|
|
}
|
|
|
|
program = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return program;
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::RelocateElf(CELF& elf, uint32 baseAddress)
|
|
|
|
{
|
2012-03-30 05:41:11 +00:00
|
|
|
//Process relocation
|
|
|
|
const ELFHEADER& header = elf.GetHeader();
|
|
|
|
for(unsigned int i = 0; i < header.nSectHeaderCount; i++)
|
|
|
|
{
|
|
|
|
ELFSECTIONHEADER* sectionHeader = elf.GetSection(i);
|
|
|
|
if(sectionHeader != NULL && sectionHeader->nType == CELF::SHT_REL)
|
|
|
|
{
|
|
|
|
uint32 lastHi16 = -1;
|
|
|
|
uint32 instructionHi16 = -1;
|
|
|
|
unsigned int linkedSection = sectionHeader->nInfo;
|
|
|
|
unsigned int recordCount = sectionHeader->nSize / 8;
|
|
|
|
ELFSECTIONHEADER* relocatedSection = elf.GetSection(linkedSection);
|
|
|
|
const uint32* relocationRecord = reinterpret_cast<const uint32*>(elf.GetSectionData(i));
|
|
|
|
uint8* relocatedSectionData = reinterpret_cast<uint8*>(const_cast<void*>(elf.GetSectionData(linkedSection)));
|
2014-07-25 01:54:19 -04:00
|
|
|
if(relocatedSection == NULL || relocationRecord == NULL || relocatedSectionData == NULL) continue;
|
2012-03-30 05:41:11 +00:00
|
|
|
uint32 sectionBase = relocatedSection->nStart;
|
|
|
|
for(unsigned int record = 0; record < recordCount; record++)
|
|
|
|
{
|
|
|
|
uint32 relocationAddress = relocationRecord[0] - sectionBase;
|
|
|
|
uint32 relocationType = relocationRecord[1] & 0xFF;
|
|
|
|
if(relocationAddress < relocatedSection->nSize)
|
|
|
|
{
|
|
|
|
uint32& instruction = *reinterpret_cast<uint32*>(&relocatedSectionData[relocationAddress]);
|
|
|
|
switch(relocationType)
|
|
|
|
{
|
|
|
|
case CELF::R_MIPS_32:
|
|
|
|
{
|
|
|
|
instruction += baseAddress;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CELF::R_MIPS_26:
|
|
|
|
{
|
|
|
|
uint32 offset = (instruction & 0x03FFFFFF) + (baseAddress >> 2);
|
|
|
|
instruction &= ~0x03FFFFFF;
|
|
|
|
instruction |= offset;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CELF::R_MIPS_HI16:
|
|
|
|
{
|
|
|
|
lastHi16 = relocationAddress;
|
|
|
|
instructionHi16 = instruction;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CELF::R_MIPS_LO16:
|
|
|
|
{
|
|
|
|
if(lastHi16 != -1)
|
|
|
|
{
|
|
|
|
uint32 offset = static_cast<int16>(instruction) + (instructionHi16 << 16);
|
|
|
|
offset += baseAddress;
|
|
|
|
instruction &= ~0xFFFF;
|
|
|
|
instruction |= offset & 0xFFFF;
|
|
|
|
|
|
|
|
uint32& prevInstruction = *reinterpret_cast<uint32*>(&relocatedSectionData[lastHi16]);
|
|
|
|
prevInstruction &= ~0xFFFF;
|
|
|
|
if(offset & 0x8000) offset += 0x10000;
|
|
|
|
prevInstruction |= offset >> 16;
|
|
|
|
lastHi16 = -1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-01-15 20:27:44 +00:00
|
|
|
#ifdef _DEBUG
|
2012-03-30 05:41:11 +00:00
|
|
|
CLog::GetInstance().Print(LOGNAME, "%s: No HI16 relocation record found for corresponding LO16.\r\n",
|
|
|
|
__FUNCTION__);
|
2008-01-15 20:27:44 +00:00
|
|
|
#endif
|
2012-03-30 05:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2011-01-16 21:47:01 +00:00
|
|
|
throw std::runtime_error("Unknown relocation type.");
|
2012-03-30 05:41:11 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
relocationRecord += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-01-15 20:27:44 +00:00
|
|
|
}
|
2014-07-04 01:55:09 -04:00
|
|
|
|
2014-12-12 20:20:51 +00:00
|
|
|
void CIopBios::TriggerCallback(uint32 address, uint32 arg0, uint32 arg1)
|
|
|
|
{
|
|
|
|
// Call the addres on a callback thread with A0 set to arg0
|
|
|
|
uint32 callbackThreadId = -1;
|
|
|
|
|
|
|
|
//Find a thread we could recycle for a new callback
|
|
|
|
for (auto threadIterator = m_threads.Begin();
|
|
|
|
threadIterator != m_threads.End(); threadIterator++)
|
|
|
|
{
|
|
|
|
const auto& thread(m_threads[threadIterator]);
|
|
|
|
if(thread == nullptr) continue;
|
|
|
|
if(thread->threadProc != address) continue;
|
|
|
|
if(thread->status == THREAD_STATUS_DORMANT)
|
|
|
|
{
|
|
|
|
callbackThreadId = thread->id;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//If no threads are available, create a new one
|
|
|
|
if(callbackThreadId == -1)
|
|
|
|
{
|
|
|
|
callbackThreadId = CreateThread(address, DEFAULT_PRIORITY, DEFAULT_STACKSIZE, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
ChangeThreadPriority(callbackThreadId, 1);
|
|
|
|
StartThread(callbackThreadId);
|
|
|
|
|
|
|
|
auto thread = GetThread(callbackThreadId);
|
|
|
|
thread->context.gpr[CMIPS::A0] = arg0;
|
|
|
|
thread->context.gpr[CMIPS::A1] = arg1;
|
|
|
|
}
|
|
|
|
|
2014-07-04 01:55:09 -04:00
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//Debug Stuff
|
|
|
|
|
|
|
|
#ifdef DEBUGGER_INCLUDED
|
|
|
|
|
|
|
|
#define TAGS_SECTION_IOP_MODULES ("modules")
|
|
|
|
#define TAGS_SECTION_IOP_MODULES_MODULE ("module")
|
|
|
|
#define TAGS_SECTION_IOP_MODULES_MODULE_BEGINADDRESS ("beginAddress")
|
|
|
|
#define TAGS_SECTION_IOP_MODULES_MODULE_ENDADDRESS ("endAddress")
|
|
|
|
#define TAGS_SECTION_IOP_MODULES_MODULE_NAME ("name")
|
|
|
|
|
|
|
|
void CIopBios::LoadDebugTags(Framework::Xml::CNode* root)
|
|
|
|
{
|
|
|
|
auto moduleSection = root->Select(TAGS_SECTION_IOP_MODULES);
|
|
|
|
if(moduleSection == NULL) return;
|
|
|
|
|
|
|
|
for(Framework::Xml::CFilteringNodeIterator nodeIterator(moduleSection, TAGS_SECTION_IOP_MODULES_MODULE);
|
|
|
|
!nodeIterator.IsEnd(); nodeIterator++)
|
|
|
|
{
|
|
|
|
auto moduleNode(*nodeIterator);
|
|
|
|
const char* moduleName = moduleNode->GetAttribute(TAGS_SECTION_IOP_MODULES_MODULE_NAME);
|
|
|
|
const char* beginAddress = moduleNode->GetAttribute(TAGS_SECTION_IOP_MODULES_MODULE_BEGINADDRESS);
|
|
|
|
const char* endAddress = moduleNode->GetAttribute(TAGS_SECTION_IOP_MODULES_MODULE_ENDADDRESS);
|
|
|
|
if(!moduleName || !beginAddress || !endAddress) continue;
|
|
|
|
if(FindModuleDebugInfo(moduleName) != std::end(m_moduleTags)) continue;
|
|
|
|
|
|
|
|
BIOS_DEBUG_MODULE_INFO module;
|
|
|
|
module.name = moduleName;
|
|
|
|
module.begin = lexical_cast_hex<std::string>(beginAddress);
|
|
|
|
module.end = lexical_cast_hex<std::string>(endAddress);
|
|
|
|
module.param = NULL;
|
|
|
|
m_moduleTags.push_back(module);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::SaveDebugTags(Framework::Xml::CNode* root)
|
|
|
|
{
|
|
|
|
Framework::Xml::CNode* moduleSection = new Framework::Xml::CNode(TAGS_SECTION_IOP_MODULES, true);
|
|
|
|
|
|
|
|
for(auto moduleIterator(std::begin(m_moduleTags));
|
|
|
|
std::end(m_moduleTags) != moduleIterator; moduleIterator++)
|
|
|
|
{
|
|
|
|
const auto& module(*moduleIterator);
|
|
|
|
Framework::Xml::CNode* moduleNode = new Framework::Xml::CNode(TAGS_SECTION_IOP_MODULES_MODULE, true);
|
|
|
|
moduleNode->InsertAttribute(TAGS_SECTION_IOP_MODULES_MODULE_BEGINADDRESS, lexical_cast_hex<std::string>(module.begin, 8).c_str());
|
|
|
|
moduleNode->InsertAttribute(TAGS_SECTION_IOP_MODULES_MODULE_ENDADDRESS, lexical_cast_hex<std::string>(module.end, 8).c_str());
|
|
|
|
moduleNode->InsertAttribute(TAGS_SECTION_IOP_MODULES_MODULE_NAME, module.name.c_str());
|
|
|
|
moduleSection->InsertNode(moduleNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
root->InsertNode(moduleSection);
|
|
|
|
}
|
|
|
|
|
2014-07-04 02:18:14 -04:00
|
|
|
BiosDebugModuleInfoArray CIopBios::GetModulesDebugInfo() const
|
2014-07-04 01:55:09 -04:00
|
|
|
{
|
|
|
|
return m_moduleTags;
|
|
|
|
}
|
|
|
|
|
2014-07-04 02:18:14 -04:00
|
|
|
BiosDebugThreadInfoArray CIopBios::GetThreadsDebugInfo() const
|
2014-07-04 01:55:09 -04:00
|
|
|
{
|
|
|
|
BiosDebugThreadInfoArray threadInfos;
|
|
|
|
|
|
|
|
uint32 nextThreadId = ThreadLinkHead();
|
|
|
|
while(nextThreadId != 0)
|
|
|
|
{
|
|
|
|
THREAD* nextThread = m_threads[nextThreadId];
|
|
|
|
|
|
|
|
BIOS_DEBUG_THREAD_INFO threadInfo;
|
|
|
|
threadInfo.id = nextThreadId;
|
|
|
|
threadInfo.priority = nextThread->priority;
|
|
|
|
if(GetCurrentThreadId() == nextThreadId)
|
|
|
|
{
|
|
|
|
threadInfo.pc = m_cpu.m_State.nPC;
|
|
|
|
threadInfo.ra = m_cpu.m_State.nGPR[CMIPS::RA].nV0;
|
|
|
|
threadInfo.sp = m_cpu.m_State.nGPR[CMIPS::SP].nV0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
threadInfo.pc = nextThread->context.epc;
|
|
|
|
threadInfo.ra = nextThread->context.gpr[CMIPS::RA];
|
|
|
|
threadInfo.sp = nextThread->context.gpr[CMIPS::SP];
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(nextThread->status)
|
|
|
|
{
|
|
|
|
case THREAD_STATUS_DORMANT:
|
|
|
|
threadInfo.stateDescription = "Dormant";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_RUNNING:
|
|
|
|
threadInfo.stateDescription = "Running";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_SLEEPING:
|
|
|
|
threadInfo.stateDescription = "Sleeping";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_WAITING_SEMAPHORE:
|
|
|
|
threadInfo.stateDescription = "Waiting (Semaphore: " + boost::lexical_cast<std::string>(nextThread->waitSemaphore) + ")";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_WAITING_EVENTFLAG:
|
|
|
|
threadInfo.stateDescription = "Waiting (Event Flag: " + boost::lexical_cast<std::string>(nextThread->waitEventFlag) + ")";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_WAITING_MESSAGEBOX:
|
|
|
|
threadInfo.stateDescription = "Waiting (Message Box: " + boost::lexical_cast<std::string>(nextThread->waitMessageBox) + ")";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_WAIT_VBLANK_START:
|
|
|
|
threadInfo.stateDescription = "Waiting (Vblank Start)";
|
|
|
|
break;
|
|
|
|
case THREAD_STATUS_WAIT_VBLANK_END:
|
|
|
|
threadInfo.stateDescription = "Waiting (Vblank End)";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
threadInfo.stateDescription = "Unknown";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
threadInfos.push_back(threadInfo);
|
|
|
|
|
|
|
|
nextThreadId = nextThread->nextThreadId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return threadInfos;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CIopBios::PrepareModuleDebugInfo(CELF& elf, const ExecutableRange& moduleRange, const std::string& moduleName, const std::string& modulePath)
|
|
|
|
{
|
|
|
|
//Update module tag
|
|
|
|
{
|
|
|
|
auto moduleIterator(FindModuleDebugInfo(moduleRange.first, moduleRange.second));
|
|
|
|
if(moduleIterator == std::end(m_moduleTags))
|
|
|
|
{
|
|
|
|
moduleIterator = FindModuleDebugInfo(moduleName);
|
|
|
|
if(moduleIterator == std::end(m_moduleTags))
|
|
|
|
{
|
|
|
|
moduleIterator = m_moduleTags.insert(std::end(m_moduleTags), BIOS_DEBUG_MODULE_INFO());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& module(*moduleIterator);
|
|
|
|
module.name = moduleName;
|
|
|
|
module.begin = moduleRange.first;
|
|
|
|
module.end = moduleRange.second;
|
|
|
|
module.param = NULL;
|
|
|
|
}
|
|
|
|
|
2014-08-16 21:42:19 -04:00
|
|
|
m_cpu.m_analysis->Analyse(moduleRange.first, moduleRange.second);
|
2014-07-04 01:55:09 -04:00
|
|
|
|
|
|
|
bool functionAdded = false;
|
|
|
|
//Look for import tables
|
|
|
|
for(uint32 address = moduleRange.first; address < moduleRange.second; address += 4)
|
|
|
|
{
|
|
|
|
if(m_cpu.m_pMemoryMap->GetWord(address) == 0x41E00000)
|
|
|
|
{
|
|
|
|
if(m_cpu.m_pMemoryMap->GetWord(address + 4) != 0) continue;
|
|
|
|
|
|
|
|
uint32 version = m_cpu.m_pMemoryMap->GetWord(address + 8);
|
|
|
|
std::string moduleName = ReadModuleName(address + 0xC);
|
|
|
|
IopModuleMapType::iterator module(m_modules.find(moduleName));
|
|
|
|
|
|
|
|
size_t moduleNameLength = moduleName.length();
|
|
|
|
uint32 entryAddress = address + 0x0C + ((moduleNameLength + 3) & ~0x03);
|
|
|
|
while(m_cpu.m_pMemoryMap->GetWord(entryAddress) == 0x03E00008)
|
|
|
|
{
|
|
|
|
uint32 target = m_cpu.m_pMemoryMap->GetWord(entryAddress + 4);
|
|
|
|
uint32 functionId = target & 0xFFFF;
|
|
|
|
std::string functionName;
|
2015-02-07 23:06:08 -05:00
|
|
|
if(moduleName == m_libsd->GetId())
|
|
|
|
{
|
|
|
|
functionName = m_libsd->GetFunctionName(functionId);
|
|
|
|
}
|
|
|
|
else if(module != m_modules.end())
|
2014-07-04 01:55:09 -04:00
|
|
|
{
|
|
|
|
functionName = (module->second)->GetFunctionName(functionId);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char functionNameTemp[256];
|
|
|
|
sprintf(functionNameTemp, "unknown_%0.4X", functionId);
|
|
|
|
functionName = functionNameTemp;
|
|
|
|
}
|
|
|
|
if(m_cpu.m_Functions.Find(address) == NULL)
|
|
|
|
{
|
|
|
|
m_cpu.m_Functions.InsertTag(entryAddress, (std::string(moduleName) + "_" + functionName).c_str());
|
|
|
|
functionAdded = true;
|
|
|
|
}
|
|
|
|
entryAddress += 8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Look also for symbol tables
|
|
|
|
{
|
|
|
|
ELFSECTIONHEADER* pSymTab = elf.FindSection(".symtab");
|
|
|
|
if(pSymTab != NULL)
|
|
|
|
{
|
|
|
|
const char* pStrTab = reinterpret_cast<const char*>(elf.GetSectionData(pSymTab->nIndex));
|
|
|
|
if(pStrTab != NULL)
|
|
|
|
{
|
|
|
|
const ELFSYMBOL* pSym = reinterpret_cast<const ELFSYMBOL*>(elf.FindSectionData(".symtab"));
|
|
|
|
unsigned int nCount = pSymTab->nSize / sizeof(ELFSYMBOL);
|
|
|
|
|
|
|
|
for(unsigned int i = 0; i < nCount; i++)
|
|
|
|
{
|
|
|
|
if((pSym[i].nInfo & 0x0F) != 0x02) continue;
|
|
|
|
ELFSECTIONHEADER* symbolSection = elf.GetSection(pSym[i].nSectionIndex);
|
|
|
|
if(symbolSection == NULL) continue;
|
|
|
|
m_cpu.m_Functions.InsertTag(moduleRange.first + symbolSection->nStart + pSym[i].nValue, (char*)pStrTab + pSym[i].nName);
|
|
|
|
functionAdded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(functionAdded)
|
|
|
|
{
|
|
|
|
m_cpu.m_Functions.OnTagListChange();
|
|
|
|
}
|
|
|
|
|
|
|
|
CLog::GetInstance().Print(LOGNAME, "Loaded IOP module '%s' @ 0x%0.8X.\r\n",
|
|
|
|
modulePath.c_str(), moduleRange.first);
|
|
|
|
}
|
|
|
|
|
|
|
|
BiosDebugModuleInfoIterator CIopBios::FindModuleDebugInfo(const std::string& name)
|
|
|
|
{
|
|
|
|
return std::find_if(std::begin(m_moduleTags), std::end(m_moduleTags),
|
|
|
|
[=] (const BIOS_DEBUG_MODULE_INFO& module)
|
|
|
|
{
|
|
|
|
return name == module.name;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
BiosDebugModuleInfoIterator CIopBios::FindModuleDebugInfo(uint32 beginAddress, uint32 endAddress)
|
|
|
|
{
|
|
|
|
return std::find_if(std::begin(m_moduleTags), std::end(m_moduleTags),
|
|
|
|
[=] (const BIOS_DEBUG_MODULE_INFO& module)
|
|
|
|
{
|
|
|
|
return (beginAddress == module.begin) && (endAddress == module.end);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|