diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 719cdc8cab..bc6d1d681f 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -1029,7 +1029,7 @@ void SPUThread::StopAndSignal(u32 code) case 0x003: { - GPR[3]._u64[1] = m_code3_func(*this); + m_code3_func(*this); break; } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index e7c28ed8ff..4bebaf1baf 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -511,7 +511,7 @@ public: void WriteLS128(const u32 lsa, const u128& data) const { vm::write128(lsa + m_offset, data); } std::function m_custom_task; - std::function m_code3_func; + std::function m_code3_func; public: SPUThread(CPUThreadType type = CPU_THREAD_SPU); diff --git a/rpcs3/Emu/SysCalls/Modules/cellSpurs.cpp b/rpcs3/Emu/SysCalls/Modules/cellSpurs.cpp index 4cac04ffad..343ca9ecbb 100644 --- a/rpcs3/Emu/SysCalls/Modules/cellSpurs.cpp +++ b/rpcs3/Emu/SysCalls/Modules/cellSpurs.cpp @@ -21,6 +21,8 @@ extern u32 libsre; extern u32 libsre_rtoc; #endif +void spursKernelMain(SPUThread & spu); + s64 spursCreateLv2EventQueue(vm::ptr spurs, u32& queue_id, vm::ptr port, s32 size, u64 name_u64) { #ifdef PRX_DEBUG_XXX @@ -112,7 +114,7 @@ s64 spursInit( spurs->m.wklInfoSysSrv.addr.set(be_t::make(vm::read32(libsre_rtoc - 0x7EA4))); spurs->m.wklInfoSysSrv.size = 0x2200; #else - spurs->m.wklInfoSysSrv.addr.set(be_t::make(0x100)); // wrong 64-bit address + spurs->m.wklInfoSysSrv.addr.set(be_t::make(SPURS_IMG_ADDR_SYS_SRV_WORKLOAD)); #endif spurs->m.wklInfoSysSrv.arg = 0; spurs->m.wklInfoSysSrv.uniqueId.write_relaxed(0xff); @@ -170,399 +172,16 @@ s64 spursInit( name += "CellSpursKernel0"; for (s32 num = 0; num < nSpus; num++, name[name.size() - 1]++) { - spurs->m.spus[num] = spu_thread_initialize(tg, num, spurs->m.spuImg, name, SYS_SPU_THREAD_OPTION_DEC_SYNC_TB_ENABLE, 0, 0, 0, 0, [spurs, num, isSecond](SPUThread& SPU) + spurs->m.spus[num] = spu_thread_initialize(tg, num, spurs->m.spuImg, name, SYS_SPU_THREAD_OPTION_DEC_SYNC_TB_ENABLE, 0, 0, 0, 0, [spurs, num](SPUThread& SPU) { -#ifdef PRX_DEBUG_XXX SPU.GPR[3]._u32[3] = num; SPU.GPR[4]._u64[1] = spurs.addr(); + +#ifdef PRX_DEBUG_XXX return SPU.FastCall(SPU.PC); #endif - // code replacement: - { - const u32 addr = /*SPU.ReadLS32(0x1e0) +*/ 8; //SPU.ReadLS32(0x1e4); - SPU.WriteLS32(addr + 0, 3); // hack for cellSpursModulePollStatus - SPU.WriteLS32(addr + 4, 0x35000000); // bi $0 - SPU.WriteLS32(0x1e4, addr); - - SPU.WriteLS32(SPU.ReadLS32(0x1e0), 2); // hack for cellSpursModuleExit - } - - if (!isSecond) SPU.m_code3_func = [spurs, num](SPUThread& SPU) -> u64 // first kernel - { - LV2_LOCK(0); // TODO: lock-free implementation if possible - - auto mgmt = vm::get_ptr(SPU.ls_offset); - - // The first and only argument to this function is a boolean that is set to false if the function - // is called by the SPURS kernel and set to true if called by cellSpursModulePollStatus. - // If the first argument is true then the shared data is not updated with the result. - const auto isPoll = SPU.GPR[3]._u32[3]; - - // Calculate the contention (number of SPUs used) for each workload - u8 contention[CELL_SPURS_MAX_WORKLOAD]; - u8 pendingContention[CELL_SPURS_MAX_WORKLOAD]; - for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) - { - contention[i] = mgmt->spurs->m.wklCurrentContention[i] - mgmt->wklLocContention[i]; - - // If this is a poll request then the number of SPUs pending to context switch is also added to the contention presumably - // to prevent unnecessary jumps to the kernel - if (isPoll) - { - pendingContention[i] = mgmt->spurs->m.wklPendingContention[i] - mgmt->wklLocPendingContention[i]; - if (i != mgmt->wklCurrentId) - { - contention[i] += pendingContention[i]; - } - } - } - - u32 wklSelectedId = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; - u32 pollStatus = 0; - - // The system service workload has the highest priority. Select the system service workload if - // the system service message bit for this SPU is set. - if (mgmt->spurs->m.sysSrvMessage.read_relaxed() & (1 << mgmt->spuNum)) - { - mgmt->spuIdling = 0; - if (!isPoll || mgmt->wklCurrentId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - // Clear the message bit - mgmt->spurs->m.sysSrvMessage.write_relaxed(mgmt->spurs->m.sysSrvMessage.read_relaxed() & ~(1 << mgmt->spuNum)); - } - } - else - { - // Caclulate the scheduling weight for each workload - u16 maxWeight = 0; - for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) - { - u8 runnable = mgmt->wklRunnable1 & (0x8000 >> i); - u8 wklSignal = mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> i); - u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0; - u8 readyCount = mgmt->spurs->m.wklReadyCount1[i].read_relaxed() > CELL_SPURS_MAX_SPU ? CELL_SPURS_MAX_SPU : mgmt->spurs->m.wklReadyCount1[i].read_relaxed(); - u8 idleSpuCount = mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[i].read_relaxed() > CELL_SPURS_MAX_SPU ? CELL_SPURS_MAX_SPU : mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[i].read_relaxed(); - u8 requestCount = readyCount + idleSpuCount; - - // For a workload to be considered for scheduling: - // 1. Its priority must not be 0 - // 2. The number of SPUs used by it must be less than the max contention for that workload - // 3. The workload should be in runnable state - // 4. The number of SPUs allocated to it must be less than the number of SPUs requested (i.e. readyCount) - // OR the workload must be signalled - // OR the workload flag is 0 and the workload is configured as the wokload flag receiver - if (runnable && mgmt->priority[i] != 0 && mgmt->spurs->m.wklMaxContention[i].read_relaxed() > contention[i]) - { - if (wklFlag || wklSignal || (readyCount != 0 && requestCount > contention[i])) - { - // The scheduling weight of the workload is formed from the following parameters in decreasing order of priority: - // 1. Wokload signal set or workload flag or ready count > contention - // 2. Priority of the workload on the SPU - // 3. Is the workload the last selected workload - // 4. Minimum contention of the workload - // 5. Number of SPUs that are being used by the workload (lesser the number, more the weight) - // 6. Is the workload executable same as the currently loaded executable - // 7. The workload id (lesser the number, more the weight) - u16 weight = (wklFlag || wklSignal || (readyCount > contention[i])) ? 0x8000 : 0; - weight |= (u16)(mgmt->priority[i] & 0x7F) << 16; - weight |= i == mgmt->wklCurrentId ? 0x80 : 0x00; - weight |= (contention[i] > 0 && mgmt->spurs->m.wklMinContention[i] > contention[i]) ? 0x40 : 0x00; - weight |= ((CELL_SPURS_MAX_SPU - contention[i]) & 0x0F) << 2; - weight |= mgmt->wklUniqueId[i] == mgmt->wklCurrentId ? 0x02 : 0x00; - weight |= 0x01; - - // In case of a tie the lower numbered workload is chosen - if (weight > maxWeight) - { - wklSelectedId = i; - maxWeight = weight; - pollStatus = readyCount > contention[i] ? CELL_SPURS_MODULE_POLL_STATUS_READYCOUNT : 0; - pollStatus |= wklSignal ? CELL_SPURS_MODULE_POLL_STATUS_SIGNAL : 0; - pollStatus |= wklFlag ? CELL_SPURS_MODULE_POLL_STATUS_FLAG : 0; - } - } - } - } - - // Not sure what this does. Possibly mark the SPU as idle/in use. - mgmt->spuIdling = wklSelectedId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID ? 1 : 0; - - if (!isPoll || wklSelectedId == mgmt->wklCurrentId) - { - // Clear workload signal for the selected workload - mgmt->spurs->m.wklSignal1.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId))); - mgmt->spurs->m.wklSignal2.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x80000000u >> wklSelectedId))); - - // If the selected workload is the wklFlag workload then pull the wklFlag to all 1s - if (wklSelectedId == mgmt->spurs->m.wklFlagReceiver.read_relaxed()) - { - mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t::make(0xFFFFFFFF)); - } - } - } - - if (!isPoll) - { - // Called by kernel - // Increment the contention for the selected workload - if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - contention[wklSelectedId]++; - } - - for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) - { - mgmt->spurs->m.wklCurrentContention[i] = contention[i]; - mgmt->wklLocContention[i] = 0; - mgmt->wklLocPendingContention[i] = 0; - } - - if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - mgmt->wklLocContention[wklSelectedId] = 1; - } - - mgmt->wklCurrentId = wklSelectedId; - } - else if (wklSelectedId != mgmt->wklCurrentId) - { - // Not called by kernel but a context switch is required - // Increment the pending contention for the selected workload - if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - pendingContention[wklSelectedId]++; - } - - for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) - { - mgmt->spurs->m.wklPendingContention[i] = pendingContention[i]; - mgmt->wklLocPendingContention[i] = 0; - } - - if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - mgmt->wklLocPendingContention[wklSelectedId] = 1; - } - } - - u64 result = (u64)wklSelectedId << 32; - result |= pollStatus; - return result; - }; - else SPU.m_code3_func = [spurs, num](SPUThread& SPU) -> u64 // second kernel - { - LV2_LOCK(0); // TODO: lock-free implementation if possible - - auto mgmt = vm::get_ptr(SPU.ls_offset); - - // The first and only argument to this function is a boolean that is set to false if the function - // is called by the SPURS kernel and set to true if called by cellSpursModulePollStatus. - // If the first argument is true then the shared data is not updated with the result. - const auto isPoll = SPU.GPR[3]._u32[3]; - - // Calculate the contention (number of SPUs used) for each workload - u8 contention[CELL_SPURS_MAX_WORKLOAD2]; - u8 pendingContention[CELL_SPURS_MAX_WORKLOAD2]; - for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD2; i++) - { - contention[i] = mgmt->spurs->m.wklCurrentContention[i & 0x0F] - mgmt->wklLocContention[i & 0x0F]; - contention[i] = i < CELL_SPURS_MAX_WORKLOAD ? contention[i] & 0x0F : contention[i] >> 4; - - // If this is a poll request then the number of SPUs pending to context switch is also added to the contention presumably - // to prevent unnecessary jumps to the kernel - if (isPoll) - { - pendingContention[i] = mgmt->spurs->m.wklPendingContention[i & 0x0F] - mgmt->wklLocPendingContention[i & 0x0F]; - pendingContention[i] = i < CELL_SPURS_MAX_WORKLOAD ? pendingContention[i] & 0x0F : pendingContention[i] >> 4; - if (i != mgmt->wklCurrentId) - { - contention[i] += pendingContention[i]; - } - } - } - - u32 wklSelectedId = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; - u32 pollStatus = 0; - - // The system service workload has the highest priority. Select the system service workload if - // the system service message bit for this SPU is set. - if (mgmt->spurs->m.sysSrvMessage.read_relaxed() & (1 << mgmt->spuNum)) - { - // Not sure what this does. Possibly Mark the SPU as in use. - mgmt->spuIdling = 0; - if (!isPoll || mgmt->wklCurrentId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - // Clear the message bit - mgmt->spurs->m.sysSrvMessage.write_relaxed(mgmt->spurs->m.sysSrvMessage.read_relaxed() & ~(1 << mgmt->spuNum)); - } - } - else - { - // Caclulate the scheduling weight for each workload - u8 maxWeight = 0; - for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD2; i++) - { - auto j = i & 0x0F; - u8 runnable = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->wklRunnable1 & (0x8000 >> j) : mgmt->wklRunnable2 & (0x8000 >> j); - u8 priority = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->priority[j] & 0x0F : mgmt->priority[j] >> 4; - u8 maxContention = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklMaxContention[j].read_relaxed() & 0x0F : mgmt->spurs->m.wklMaxContention[j].read_relaxed() >> 4; - u8 wklSignal = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> j) : mgmt->spurs->m.wklSignal2.read_relaxed() & (0x8000 >> j); - u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0; - u8 readyCount = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklReadyCount1[j].read_relaxed() : mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[j].read_relaxed(); - - // For a workload to be considered for scheduling: - // 1. Its priority must be greater than 0 - // 2. The number of SPUs used by it must be less than the max contention for that workload - // 3. The workload should be in runnable state - // 4. The number of SPUs allocated to it must be less than the number of SPUs requested (i.e. readyCount) - // OR the workload must be signalled - // OR the workload flag is 0 and the workload is configured as the wokload receiver - if (runnable && priority > 0 && maxContention > contention[i]) - { - if (wklFlag || wklSignal || readyCount > contention[i]) - { - // The scheduling weight of the workload is equal to the priority of the workload for the SPU. - // The current workload is given a sligtly higher weight presumably to reduce the number of context switches. - // In case of a tie the lower numbered workload is chosen. - u8 weight = priority << 4; - if (mgmt->wklCurrentId == i) - { - weight |= 0x04; - } - - if (weight > maxWeight) - { - wklSelectedId = i; - maxWeight = weight; - pollStatus = readyCount > contention[i] ? CELL_SPURS_MODULE_POLL_STATUS_READYCOUNT : 0; - pollStatus |= wklSignal ? CELL_SPURS_MODULE_POLL_STATUS_SIGNAL : 0; - pollStatus |= wklFlag ? CELL_SPURS_MODULE_POLL_STATUS_FLAG : 0; - } - } - } - } - - // Not sure what this does. Possibly mark the SPU as idle/in use. - mgmt->spuIdling = wklSelectedId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID ? 1 : 0; - - if (!isPoll || wklSelectedId == mgmt->wklCurrentId) - { - // Clear workload signal for the selected workload - mgmt->spurs->m.wklSignal1.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId))); - mgmt->spurs->m.wklSignal2.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x80000000u >> wklSelectedId))); - - // If the selected workload is the wklFlag workload then pull the wklFlag to all 1s - if (wklSelectedId == mgmt->spurs->m.wklFlagReceiver.read_relaxed()) - { - mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t::make(0xFFFFFFFF)); - } - } - } - - if (!isPoll) - { - // Called by kernel - // Increment the contention for the selected workload - if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - contention[wklSelectedId]++; - } - - for (auto i = 0; i < (CELL_SPURS_MAX_WORKLOAD2 >> 1); i++) - { - mgmt->spurs->m.wklCurrentContention[i] = contention[i] | (contention[i + 0x10] << 4); - mgmt->wklLocContention[i] = 0; - mgmt->wklLocPendingContention[i] = 0; - } - - mgmt->wklLocContention[wklSelectedId & 0x0F] = wklSelectedId < CELL_SPURS_MAX_WORKLOAD ? 0x01 : wklSelectedId < CELL_SPURS_MAX_WORKLOAD2 ? 0x10 : 0; - mgmt->wklCurrentId = wklSelectedId; - } - else if (wklSelectedId != mgmt->wklCurrentId) - { - // Not called by kernel but a context switch is required - // Increment the pending contention for the selected workload - if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) - { - pendingContention[wklSelectedId]++; - } - - for (auto i = 0; i < (CELL_SPURS_MAX_WORKLOAD2 >> 1); i++) - { - mgmt->spurs->m.wklPendingContention[i] = pendingContention[i] | (pendingContention[i + 0x10] << 4); - mgmt->wklLocPendingContention[i] = 0; - } - - mgmt->wklLocPendingContention[wklSelectedId & 0x0F] = wklSelectedId < CELL_SPURS_MAX_WORKLOAD ? 0x01 : wklSelectedId < CELL_SPURS_MAX_WORKLOAD2 ? 0x10 : 0; - } - - u64 result = (u64)wklSelectedId << 32; - result |= pollStatus; - return result; - }; - //SPU.m_code3_func = [spurs, num](SPUThread& SPU) -> u64 // test - //{ - // LV2_LOCK(0); - // SPU.FastCall(0x290); - // u64 vRES = SPU.GPR[3]._u64[1]; - // return vRES; - //}; - - SpursKernelMgmtData * mgmt = vm::get_ptr(SPU.ls_offset); - mgmt->spurs = spurs; - mgmt->spuNum = num; - mgmt->dmaTagId = 0x1F; - - u32 wid = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; - u32 pollStatus = 0; - while (true) - { - if (Emu.IsStopped()) - { - cellSpurs->Warning("Spurs Kernel aborted"); - return; - } - - // get current workload info: - auto& wkl = wid < CELL_SPURS_MAX_WORKLOAD ? spurs->m.wklInfo1[wid] : (wid < CELL_SPURS_MAX_WORKLOAD2 && isSecond ? spurs->m.wklInfo2[wid & 0xf] : spurs->m.wklInfoSysSrv); - - if (mgmt->wklCurrentAddr != wkl.addr) - { - // load executable code: - memcpy(vm::get_ptr(SPU.ls_offset + 0xa00), wkl.addr.get_ptr(), wkl.size); - mgmt->wklCurrentAddr = wkl.addr; - mgmt->wklCurrentUniqueId = wkl.uniqueId.read_relaxed(); - } - - if (!isSecond) SPU.WriteLS16(0x1e8, 0); - - // run workload: - SPU.GPR[1]._u32[3] = 0x3FFB0; - SPU.GPR[3]._u32[3] = 0x100; - SPU.GPR[4]._u64[1] = wkl.arg; - SPU.GPR[5]._u32[3] = pollStatus; - SPU.FastCall(0xa00); - - // check status: - auto status = SPU.SPU.Status.GetValue(); - if (status == SPU_STATUS_STOPPED_BY_STOP) - { - return; - } - else - { - assert(status == SPU_STATUS_RUNNING); - } - - // get workload id: - SPU.GPR[3].clear(); - assert(SPU.m_code3_func); - u64 res = SPU.m_code3_func(SPU); - pollStatus = (u32)(res); - wid = (u32)(res >> 32); - } - + spursKernelMain(SPU); })->GetId(); } @@ -653,7 +272,7 @@ s64 spursInit( for (u32 i = 0; i < 16; i++) { if (spurs->m.wklState1[i].read_relaxed() == 2 && - spurs->m.wklInfo1[i].priority.ToBE() != 0 && + *((u64 *)spurs->m.wklInfo1[i].priority) != 0 && spurs->m.wklMaxContention[i].read_relaxed() & 0xf ) { @@ -671,7 +290,7 @@ s64 spursInit( if (spurs->m.flags1 & SF1_32_WORKLOADS) for (u32 i = 0; i < 16; i++) { if (spurs->m.wklState2[i].read_relaxed() == 2 && - spurs->m.wklInfo2[i].priority.ToBE() != 0 && + *((u64 *)spurs->m.wklInfo2[i].priority) != 0 && spurs->m.wklMaxContention[i].read_relaxed() & 0xf0 ) { @@ -3383,7 +3002,7 @@ void spursTraceStatusUpdate(vm::ptr spurs) spurs->m.xCD = 1; spurs->m.sysSrvMsgUpdateTrace = (1 << spurs->m.nSpus) - 1; spurs->m.sysSrvMessage.write_relaxed(0xFF); - sys_semaphore_wait(spurs->m.semPrv, 0); + sys_semaphore_wait((u32)spurs->m.semPrv, 0); } } @@ -3421,7 +3040,7 @@ s64 spursTraceInitialize(vm::ptr spurs, vm::ptr b spurs->m.traceBuffer.set(buffer.addr() | (mode & CELL_SPURS_TRACE_MODE_FLAG_WRAP_BUFFER ? 1 : 0)); spurs->m.traceMode = mode; - u32 spuTraceDataCount = (spurs->m.traceDataSize / CellSpursTracePacket::size) / spurs->m.nSpus; + u32 spuTraceDataCount = (u32)((spurs->m.traceDataSize / CellSpursTracePacket::size) / spurs->m.nSpus); for (u32 i = 0, j = 8; i < 6; i++) { spurs->m.traceStartIndex[i] = j; diff --git a/rpcs3/Emu/SysCalls/Modules/cellSpurs.h b/rpcs3/Emu/SysCalls/Modules/cellSpurs.h index 3d817e4a4d..771406b3af 100644 --- a/rpcs3/Emu/SysCalls/Modules/cellSpurs.h +++ b/rpcs3/Emu/SysCalls/Modules/cellSpurs.h @@ -140,7 +140,7 @@ enum SpursFlags1 : u8 SF1_EXIT_IF_NO_WORK = 0x80, }; -enum SpursWorkloadConstants +enum SpursWorkloadConstants : u64 { // Workload states SPURS_WKL_STATE_NON_EXISTENT = 0, @@ -149,6 +149,12 @@ enum SpursWorkloadConstants SPURS_WKL_STATE_SHUTTING_DOWN = 3, SPURS_WKL_STATE_REMOVABLE = 4, SPURS_WKL_STATE_INVALID = 5, + + // GUID + SPURS_GUID_SYS_WKL = 0x1BB841BF38F89D33ull, + + // Image addresses + SPURS_IMG_ADDR_SYS_SRV_WORKLOAD = 0x100, }; enum CellSpursModulePollStatus @@ -199,7 +205,6 @@ enum SpursTaskConstants class SPURSManager; class SPURSManagerEventFlag; class SPURSManagerTaskset; - struct CellSpurs; struct CellSpursAttribute @@ -249,7 +254,65 @@ struct CellSpursWorkloadFlag typedef void(*CellSpursShutdownCompletionEventHook)(vm::ptr, u32 wid, vm::ptr arg); -struct CellSpursTraceInfo; +struct CellSpursTraceInfo +{ + static const u32 size = 0x80; + static const u32 align = 16; + + be_t spu_thread[8]; // 0x00 + be_t count[8]; // 0x20 + be_t spu_thread_grp; // 0x40 + be_t nspu; // 0x44 + //u8 padding[]; +}; + +struct CellSpursTracePacket +{ + static const u32 size = 16; + + struct + { + u8 tag; + u8 length; + u8 spu; + u8 workload; + be_t time; + } header; + + union + { + struct + { + be_t incident; + be_t reserved; + } service; + + struct + { + be_t ea; + be_t ls; + be_t size; + } load; + + struct + { + be_t offset; + be_t ls; + be_t size; + } map; + + struct + { + s8 module[4]; + be_t level; + be_t ls; + } start; + + be_t user; + be_t guid; + be_t stop; + } data; +}; // Core CellSpurs structures struct CellSpurs @@ -289,7 +352,7 @@ struct CellSpurs be_t arg; // spu argument be_t size; atomic_t uniqueId; // The unique id is the same for all workloads with the same addr - be_t priority[8]; + u8 priority[8]; }; static_assert(sizeof(WorkloadInfo) == 0x20, "Wrong WorkloadInfo size"); @@ -311,10 +374,6 @@ struct CellSpurs // real data struct { - // The first 0x80 bytes of the CellSpurs structure is shared by all instances of the SPURS kernel in a SPURS instance and the PPU. - // The SPURS kernel copies this from main memory to the LS (address 0x100) then runs its scheduling algorithm using this as one - // of the inputs. After selecting a new workload, the SPURS kernel updates this and writes it back to main memory. - // The read-modify-write is performed atomically by the SPURS kernel. atomic_t wklReadyCount1[0x10]; // 0x00 Number of SPUs requested by each workload (0..15 wids). atomic_t wklIdleSpuCountOrReadyCount2[0x10]; // 0x10 SPURS1: Number of idle SPUs requested by each workload (0..15 wids). SPURS2: Number of SPUs requested by each workload (16..31 wids). u8 wklCurrentContention[0x10]; // 0x20 Number of SPUs used by each workload. SPURS1: index = wid. SPURS2: packed 4-bit data, index = wid % 16, internal index = wid / 16. @@ -332,33 +391,34 @@ struct CellSpurs atomic_t wklSignal2; // 0x78 (bitset for 16..32 wids) u8 x7A[6]; // 0x7A atomic_t wklState1[0x10]; // 0x80 SPURS_WKL_STATE_* - u8 wklStatus1[0x10]; // 0x90 - u8 wklEvent1[0x10]; // 0xA0 - atomic_t wklMskA; // 0xB0 - System service - Available workloads (32*u1) - atomic_t wklMskB; // 0xB4 - System service - Available module id - u8 xB8[5]; // 0xB8 - 0xBC - Syetem service exit barrier - atomic_t sysSrvMsgUpdateWorkload; // 0xBD - u8 xBE; // 0xBE - u8 sysSrvMsgTerminate; // 0xBF - u8 sysSrvWorkload[8]; // 0xC0 - u8 sysSrvOnSpu; // 0xC8 - u8 spuPort; // 0xC9 - SPU port for system service - u8 xCA; // 0xCA - u8 xCB; // 0xCB - u8 xCC; // 0xCC - u8 xCD; // 0xCD - u8 sysSrvMsgUpdateTrace; // 0xCE - u8 xCF; // 0xCF - atomic_t wklState2[0x10]; // 0xD0 SPURS_WKL_STATE_* - u8 wklStatus2[0x10]; // 0xE0 - u8 wklEvent2[0x10]; // 0xF0 - _sub_str1 wklF1[0x10]; // 0x100 - vm::bptr traceBuffer; // 0x900 - be_t traceStartIndex[6]; // 0x908 - u8 unknown7[0x948 - 0x920]; // 0x920 - be_t traceDataSize; // 0x948 - be_t traceMode; // 0x950 - u8 unknown8[0x980 - 0x954]; // 0x954 + u8 wklStatus1[0x10]; // 0x90 + u8 wklEvent1[0x10]; // 0xA0 + atomic_t wklMskA; // 0xB0 - System service - Available workloads (32*u1) + atomic_t wklMskB; // 0xB4 - System service - Available module id + u32 xB8; // 0xB8 + u8 sysSrvExitBarrier; // 0xBC + atomic_t sysSrvMsgUpdateWorkload; // 0xBD + u8 xBE; // 0xBE + u8 sysSrvMsgTerminate; // 0xBF + u8 sysSrvWorkload[8]; // 0xC0 + u8 sysSrvOnSpu; // 0xC8 + u8 spuPort; // 0xC9 + u8 xCA; // 0xCA + u8 xCB; // 0xCB + u8 xCC; // 0xCC + u8 xCD; // 0xCD + u8 sysSrvMsgUpdateTrace; // 0xCE + u8 xCF; // 0xCF + atomic_t wklState2[0x10]; // 0xD0 SPURS_WKL_STATE_* + u8 wklStatus2[0x10]; // 0xE0 + u8 wklEvent2[0x10]; // 0xF0 + _sub_str1 wklF1[0x10]; // 0x100 + vm::bptr traceBuffer; // 0x900 + be_t traceStartIndex[6]; // 0x908 + u8 unknown7[0x948 - 0x920]; // 0x920 + be_t traceDataSize; // 0x948 + be_t traceMode; // 0x950 + u8 unknown8[0x980 - 0x954]; // 0x954 be_t semPrv; // 0x980 be_t unk11; // 0x988 be_t unk12; // 0x98C @@ -559,66 +619,6 @@ struct CellSpursExceptionInfo be_t option; }; -struct CellSpursTraceInfo -{ - static const u32 size = 0x80; - static const u32 align = 16; - - be_t spu_thread[8]; // 0x00 - be_t count[8]; // 0x20 - be_t spu_thread_grp; // 0x40 - be_t nspu; // 0x44 - //u8 padding[]; -}; - -struct CellSpursTracePacket -{ - static const u32 size = 16; - - struct - { - u8 tag; - u8 length; - u8 spu; - u8 workload; - be_t time; - } header; - - union - { - struct - { - be_t incident; - be_t reserved; - } service; - - struct - { - be_t ea; - be_t ls; - be_t size; - } load; - - struct - { - be_t offset; - be_t ls; - be_t size; - } map; - - struct - { - s8 module[4]; - be_t level; - be_t ls; - } start; - - be_t user; - be_t guid; - be_t stop; - } data; -}; - // Exception handlers. //typedef void (*CellSpursGlobalExceptionEventHandler)(vm::ptr spurs, vm::ptr info, // u32 id, vm::ptr arg); @@ -793,7 +793,6 @@ struct CellSpursTaskBinInfo // The SPURS kernel data store. This resides at 0x00 of the LS. struct SpursKernelMgmtData { - u8 unk0[0x100]; // 0x00 u8 tempArea[0x80]; // 0x100 u8 wklLocContention[0x10]; // 0x180 u8 wklLocPendingContention[0x10]; // 0x190 @@ -804,7 +803,7 @@ struct SpursKernelMgmtData { be_t dmaTagId; // 0x1CC vm::bptr wklCurrentAddr; // 0x1D0 be_t wklCurrentUniqueId; // 0x1D8 - u32 wklCurrentId; // 0x1DC + be_t wklCurrentId; // 0x1DC be_t yieldToKernelAddr; // 0x1E0 be_t selectWorkloadAddr; // 0x1E4 u8 x1E8; // 0x1E8 diff --git a/rpcs3/Emu/SysCalls/Modules/cellSpursSpu.cpp b/rpcs3/Emu/SysCalls/Modules/cellSpursSpu.cpp new file mode 100644 index 0000000000..643aed981c --- /dev/null +++ b/rpcs3/Emu/SysCalls/Modules/cellSpursSpu.cpp @@ -0,0 +1,786 @@ +#include "stdafx.h" +#include "Emu/Memory/Memory.h" +#include "Emu/System.h" +#include "Emu/Cell/SPUThread.h" +#include "Emu/SysCalls/Modules.h" +#include "Emu/SysCalls/Modules/cellSpurs.h" + +// +// SPURS utility functions +// +void cellSpursModulePutTrace(CellSpursTracePacket * packet, unsigned tag); +u32 cellSpursModulePollStatus(SPUThread & spu, u32 * status); + +// +// SPURS Kernel functions +// +void spursKernelSelectWorkload(SPUThread & spu); +void spursKernelSelectWorkload2(SPUThread & spu); + +// +// SPURS system service workload functions +// +void spursSysServiceCleanupAfterPreemption(SPUThread & spu, SpursKernelMgmtData * mgmt); +void spursSysServiceUpdateTraceCount(SPUThread & spu, SpursKernelMgmtData * mgmt); +void spursSysServiceUpdateTrace(SPUThread & spu, SpursKernelMgmtData * mgmt, u32 arg2, u32 arg3, u32 arg4); +void spursSysServiceUpdateEvent(SPUThread & spu, SpursKernelMgmtData * mgmt, u32 wklShutdownBitSet); +void spursSysServiceUpdateWorkload(SPUThread & spu, SpursKernelMgmtData * mgmt); +void spursSysServiceProcessMessages(SPUThread & spu, SpursKernelMgmtData * mgmt); +void spursSysServiceWaitOrExit(SPUThread & spu, SpursKernelMgmtData * mgmt); +void spursSysServiceWorkloadMain(SPUThread & spu, u32 pollStatus); +void spursSysServiceWorkloadEntry(SPUThread & spu); + +extern Module *cellSpurs; + +////////////////////////////////////////////////////////////////////////////// +// SPURS utility functions +////////////////////////////////////////////////////////////////////////////// + +/// Output trace information +void cellSpursModulePutTrace(CellSpursTracePacket * packet, unsigned tag) { + // TODO: Implement this +} + +/// Check for execution right requests +u32 cellSpursModulePollStatus(SPUThread & spu, u32 * status) { + auto mgmt = vm::get_ptr(spu.ls_offset + 0x100); + + spu.GPR[3]._u32[3] = 1; + if (mgmt->spurs->m.flags1 & SF1_32_WORKLOADS) { + spursKernelSelectWorkload2(spu); + } else { + spursKernelSelectWorkload(spu); + } + + auto result = spu.GPR[3]._u64[1]; + if (status) { + *status = (u32)result; + } + + u32 wklId = result >> 32; + return wklId == mgmt->wklCurrentId ? 0 : 1; +} + +////////////////////////////////////////////////////////////////////////////// +// SPURS kernel functions +////////////////////////////////////////////////////////////////////////////// + +/// Select a workload to run +void spursKernelSelectWorkload(SPUThread & spu) { + LV2_LOCK(0); // TODO: lock-free implementation if possible + + auto mgmt = vm::get_ptr(spu.ls_offset + 0x100); + + // The first and only argument to this function is a boolean that is set to false if the function + // is called by the SPURS kernel and set to true if called by cellSpursModulePollStatus. + // If the first argument is true then the shared data is not updated with the result. + const auto isPoll = spu.GPR[3]._u32[3]; + + // Calculate the contention (number of SPUs used) for each workload + u8 contention[CELL_SPURS_MAX_WORKLOAD]; + u8 pendingContention[CELL_SPURS_MAX_WORKLOAD]; + for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + contention[i] = mgmt->spurs->m.wklCurrentContention[i] - mgmt->wklLocContention[i]; + + // If this is a poll request then the number of SPUs pending to context switch is also added to the contention presumably + // to prevent unnecessary jumps to the kernel + if (isPoll) { + pendingContention[i] = mgmt->spurs->m.wklPendingContention[i] - mgmt->wklLocPendingContention[i]; + if (i != mgmt->wklCurrentId) { + contention[i] += pendingContention[i]; + } + } + } + + u32 wklSelectedId = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; + u32 pollStatus = 0; + + // The system service workload has the highest priority. Select the system service workload if + // the system service message bit for this SPU is set. + if (mgmt->spurs->m.sysSrvMessage.read_relaxed() & (1 << mgmt->spuNum)) { + mgmt->spuIdling = 0; + if (!isPoll || mgmt->wklCurrentId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + // Clear the message bit + mgmt->spurs->m.sysSrvMessage.write_relaxed(mgmt->spurs->m.sysSrvMessage.read_relaxed() & ~(1 << mgmt->spuNum)); + } + } else { + // Caclulate the scheduling weight for each workload + u16 maxWeight = 0; + for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + u16 runnable = mgmt->wklRunnable1 & (0x8000 >> i); + u16 wklSignal = mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> i); + u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0; + u8 readyCount = mgmt->spurs->m.wklReadyCount1[i].read_relaxed() > CELL_SPURS_MAX_SPU ? CELL_SPURS_MAX_SPU : mgmt->spurs->m.wklReadyCount1[i].read_relaxed(); + u8 idleSpuCount = mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[i].read_relaxed() > CELL_SPURS_MAX_SPU ? CELL_SPURS_MAX_SPU : mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[i].read_relaxed(); + u8 requestCount = readyCount + idleSpuCount; + + // For a workload to be considered for scheduling: + // 1. Its priority must not be 0 + // 2. The number of SPUs used by it must be less than the max contention for that workload + // 3. The workload should be in runnable state + // 4. The number of SPUs allocated to it must be less than the number of SPUs requested (i.e. readyCount) + // OR the workload must be signalled + // OR the workload flag is 0 and the workload is configured as the wokload flag receiver + if (runnable && mgmt->priority[i] != 0 && mgmt->spurs->m.wklMaxContention[i].read_relaxed() > contention[i]) { + if (wklFlag || wklSignal || (readyCount != 0 && requestCount > contention[i])) { + // The scheduling weight of the workload is formed from the following parameters in decreasing order of priority: + // 1. Wokload signal set or workload flag or ready count > contention + // 2. Priority of the workload on the SPU + // 3. Is the workload the last selected workload + // 4. Minimum contention of the workload + // 5. Number of SPUs that are being used by the workload (lesser the number, more the weight) + // 6. Is the workload executable same as the currently loaded executable + // 7. The workload id (lesser the number, more the weight) + u16 weight = (wklFlag || wklSignal || (readyCount > contention[i])) ? 0x8000 : 0; + weight |= (u16)(mgmt->priority[i] & 0x7F) << 16; + weight |= i == mgmt->wklCurrentId ? 0x80 : 0x00; + weight |= (contention[i] > 0 && mgmt->spurs->m.wklMinContention[i] > contention[i]) ? 0x40 : 0x00; + weight |= ((CELL_SPURS_MAX_SPU - contention[i]) & 0x0F) << 2; + weight |= mgmt->wklUniqueId[i] == mgmt->wklCurrentId ? 0x02 : 0x00; + weight |= 0x01; + + // In case of a tie the lower numbered workload is chosen + if (weight > maxWeight) { + wklSelectedId = i; + maxWeight = weight; + pollStatus = readyCount > contention[i] ? CELL_SPURS_MODULE_POLL_STATUS_READYCOUNT : 0; + pollStatus |= wklSignal ? CELL_SPURS_MODULE_POLL_STATUS_SIGNAL : 0; + pollStatus |= wklFlag ? CELL_SPURS_MODULE_POLL_STATUS_FLAG : 0; + } + } + } + } + + // Not sure what this does. Possibly mark the SPU as idle/in use. + mgmt->spuIdling = wklSelectedId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID ? 1 : 0; + + if (!isPoll || wklSelectedId == mgmt->wklCurrentId) { + // Clear workload signal for the selected workload + mgmt->spurs->m.wklSignal1.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId))); + mgmt->spurs->m.wklSignal2.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x80000000u >> wklSelectedId))); + + // If the selected workload is the wklFlag workload then pull the wklFlag to all 1s + if (wklSelectedId == mgmt->spurs->m.wklFlagReceiver.read_relaxed()) { + mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t::make(0xFFFFFFFF)); + } + } + } + + if (!isPoll) { + // Called by kernel + // Increment the contention for the selected workload + if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + contention[wklSelectedId]++; + } + + for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + mgmt->spurs->m.wklCurrentContention[i] = contention[i]; + mgmt->wklLocContention[i] = 0; + mgmt->wklLocPendingContention[i] = 0; + } + + if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + mgmt->wklLocContention[wklSelectedId] = 1; + } + + mgmt->wklCurrentId = wklSelectedId; + } else if (wklSelectedId != mgmt->wklCurrentId) { + // Not called by kernel but a context switch is required + // Increment the pending contention for the selected workload + if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + pendingContention[wklSelectedId]++; + } + + for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + mgmt->spurs->m.wklPendingContention[i] = pendingContention[i]; + mgmt->wklLocPendingContention[i] = 0; + } + + if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + mgmt->wklLocPendingContention[wklSelectedId] = 1; + } + } + + u64 result = (u64)wklSelectedId << 32; + result |= pollStatus; + spu.GPR[3]._u64[1] = result; +} + +/// Select a workload to run +void spursKernelSelectWorkload2(SPUThread & spu) { + LV2_LOCK(0); // TODO: lock-free implementation if possible + + auto mgmt = vm::get_ptr(spu.ls_offset + 0x100); + + // The first and only argument to this function is a boolean that is set to false if the function + // is called by the SPURS kernel and set to true if called by cellSpursModulePollStatus. + // If the first argument is true then the shared data is not updated with the result. + const auto isPoll = spu.GPR[3]._u32[3]; + + // Calculate the contention (number of SPUs used) for each workload + u8 contention[CELL_SPURS_MAX_WORKLOAD2]; + u8 pendingContention[CELL_SPURS_MAX_WORKLOAD2]; + for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD2; i++) { + contention[i] = mgmt->spurs->m.wklCurrentContention[i & 0x0F] - mgmt->wklLocContention[i & 0x0F]; + contention[i] = i < CELL_SPURS_MAX_WORKLOAD ? contention[i] & 0x0F : contention[i] >> 4; + + // If this is a poll request then the number of SPUs pending to context switch is also added to the contention presumably + // to prevent unnecessary jumps to the kernel + if (isPoll) { + pendingContention[i] = mgmt->spurs->m.wklPendingContention[i & 0x0F] - mgmt->wklLocPendingContention[i & 0x0F]; + pendingContention[i] = i < CELL_SPURS_MAX_WORKLOAD ? pendingContention[i] & 0x0F : pendingContention[i] >> 4; + if (i != mgmt->wklCurrentId) { + contention[i] += pendingContention[i]; + } + } + } + + u32 wklSelectedId = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; + u32 pollStatus = 0; + + // The system service workload has the highest priority. Select the system service workload if + // the system service message bit for this SPU is set. + if (mgmt->spurs->m.sysSrvMessage.read_relaxed() & (1 << mgmt->spuNum)) { + // Not sure what this does. Possibly Mark the SPU as in use. + mgmt->spuIdling = 0; + if (!isPoll || mgmt->wklCurrentId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + // Clear the message bit + mgmt->spurs->m.sysSrvMessage.write_relaxed(mgmt->spurs->m.sysSrvMessage.read_relaxed() & ~(1 << mgmt->spuNum)); + } + } else { + // Caclulate the scheduling weight for each workload + u8 maxWeight = 0; + for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD2; i++) { + auto j = i & 0x0F; + u16 runnable = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->wklRunnable1 & (0x8000 >> j) : mgmt->wklRunnable2 & (0x8000 >> j); + u8 priority = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->priority[j] & 0x0F : mgmt->priority[j] >> 4; + u8 maxContention = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklMaxContention[j].read_relaxed() & 0x0F : mgmt->spurs->m.wklMaxContention[j].read_relaxed() >> 4; + u16 wklSignal = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> j) : mgmt->spurs->m.wklSignal2.read_relaxed() & (0x8000 >> j); + u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0; + u8 readyCount = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklReadyCount1[j].read_relaxed() : mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[j].read_relaxed(); + + // For a workload to be considered for scheduling: + // 1. Its priority must be greater than 0 + // 2. The number of SPUs used by it must be less than the max contention for that workload + // 3. The workload should be in runnable state + // 4. The number of SPUs allocated to it must be less than the number of SPUs requested (i.e. readyCount) + // OR the workload must be signalled + // OR the workload flag is 0 and the workload is configured as the wokload receiver + if (runnable && priority > 0 && maxContention > contention[i]) { + if (wklFlag || wklSignal || readyCount > contention[i]) { + // The scheduling weight of the workload is equal to the priority of the workload for the SPU. + // The current workload is given a sligtly higher weight presumably to reduce the number of context switches. + // In case of a tie the lower numbered workload is chosen. + u8 weight = priority << 4; + if (mgmt->wklCurrentId == i) { + weight |= 0x04; + } + + if (weight > maxWeight) { + wklSelectedId = i; + maxWeight = weight; + pollStatus = readyCount > contention[i] ? CELL_SPURS_MODULE_POLL_STATUS_READYCOUNT : 0; + pollStatus |= wklSignal ? CELL_SPURS_MODULE_POLL_STATUS_SIGNAL : 0; + pollStatus |= wklFlag ? CELL_SPURS_MODULE_POLL_STATUS_FLAG : 0; + } + } + } + } + + // Not sure what this does. Possibly mark the SPU as idle/in use. + mgmt->spuIdling = wklSelectedId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID ? 1 : 0; + + if (!isPoll || wklSelectedId == mgmt->wklCurrentId) { + // Clear workload signal for the selected workload + mgmt->spurs->m.wklSignal1.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId))); + mgmt->spurs->m.wklSignal2.write_relaxed(be_t::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x80000000u >> wklSelectedId))); + + // If the selected workload is the wklFlag workload then pull the wklFlag to all 1s + if (wklSelectedId == mgmt->spurs->m.wklFlagReceiver.read_relaxed()) { + mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t::make(0xFFFFFFFF)); + } + } + } + + if (!isPoll) { + // Called by kernel + // Increment the contention for the selected workload + if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + contention[wklSelectedId]++; + } + + for (auto i = 0; i < (CELL_SPURS_MAX_WORKLOAD2 >> 1); i++) { + mgmt->spurs->m.wklCurrentContention[i] = contention[i] | (contention[i + 0x10] << 4); + mgmt->wklLocContention[i] = 0; + mgmt->wklLocPendingContention[i] = 0; + } + + mgmt->wklLocContention[wklSelectedId & 0x0F] = wklSelectedId < CELL_SPURS_MAX_WORKLOAD ? 0x01 : wklSelectedId < CELL_SPURS_MAX_WORKLOAD2 ? 0x10 : 0; + mgmt->wklCurrentId = wklSelectedId; + } else if (wklSelectedId != mgmt->wklCurrentId) { + // Not called by kernel but a context switch is required + // Increment the pending contention for the selected workload + if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + pendingContention[wklSelectedId]++; + } + + for (auto i = 0; i < (CELL_SPURS_MAX_WORKLOAD2 >> 1); i++) { + mgmt->spurs->m.wklPendingContention[i] = pendingContention[i] | (pendingContention[i + 0x10] << 4); + mgmt->wklLocPendingContention[i] = 0; + } + + mgmt->wklLocPendingContention[wklSelectedId & 0x0F] = wklSelectedId < CELL_SPURS_MAX_WORKLOAD ? 0x01 : wklSelectedId < CELL_SPURS_MAX_WORKLOAD2 ? 0x10 : 0; + } + + u64 result = (u64)wklSelectedId << 32; + result |= pollStatus; + spu.GPR[3]._u64[1] = result; +} + +/// Entry point of the SPURS kernel +void spursKernelMain(SPUThread & spu) { + SpursKernelMgmtData * mgmt = vm::get_ptr(spu.ls_offset + 0x100); + mgmt->spuNum = spu.GPR[3]._u32[3]; + mgmt->dmaTagId = 0x1F; + mgmt->spurs.set(spu.GPR[4]._u64[1]); + mgmt->wklCurrentId = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; + mgmt->wklCurrentUniqueId = 0x20; + + bool isSecond = mgmt->spurs->m.flags1 & SF1_32_WORKLOADS ? true : false; + mgmt->yieldToKernelAddr = isSecond ? 0x838 : 0x808; + mgmt->selectWorkloadAddr = 0x290; + spu.WriteLS32(mgmt->yieldToKernelAddr, 2); // hack for cellSpursModuleExit + spu.WriteLS32(mgmt->selectWorkloadAddr, 3); // hack for cellSpursModulePollStatus + spu.WriteLS32(mgmt->selectWorkloadAddr + 4, 0x35000000); // bi $0 + spu.m_code3_func = isSecond ? spursKernelSelectWorkload2 : spursKernelSelectWorkload; + + u32 wid = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID; + u32 pollStatus = 0; + while (true) { + if (Emu.IsStopped()) { + cellSpurs->Warning("Spurs Kernel aborted"); + return; + } + + // Get current workload info + auto & wkl = wid < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklInfo1[wid] : (wid < CELL_SPURS_MAX_WORKLOAD2 && isSecond ? mgmt->spurs->m.wklInfo2[wid & 0xf] : mgmt->spurs->m.wklInfoSysSrv); + + if (mgmt->wklCurrentAddr != wkl.addr) { + if (wkl.addr.addr() != SPURS_IMG_ADDR_SYS_SRV_WORKLOAD) { + // Load executable code + memcpy(vm::get_ptr(spu.ls_offset + 0xA00), wkl.addr.get_ptr(), wkl.size); + } + mgmt->wklCurrentAddr = wkl.addr; + mgmt->wklCurrentUniqueId = wkl.uniqueId.read_relaxed(); + } + + if (!isSecond) { + mgmt->x1E8 = 0; + mgmt->x1E9 = 0; + } + + // Run workload + spu.GPR[1]._u32[3] = 0x3FFB0; + spu.GPR[3]._u32[3] = 0x100; + spu.GPR[4]._u64[1] = wkl.arg; + spu.GPR[5]._u32[3] = pollStatus; + switch (mgmt->wklCurrentAddr.addr()) { + case SPURS_IMG_ADDR_SYS_SRV_WORKLOAD: + spursSysServiceWorkloadEntry(spu); + break; + default: + spu.FastCall(0xA00); + break; + } + + // Check status + auto status = spu.SPU.Status.GetValue(); + if (status == SPU_STATUS_STOPPED_BY_STOP) { + return; + } else { + assert(status == SPU_STATUS_RUNNING); + } + + // Select next workload to run + spu.GPR[3].clear(); + if (isSecond) { + spursKernelSelectWorkload2(spu); + } else { + spursKernelSelectWorkload(spu); + } + u64 res = spu.GPR[3]._u64[1]; + pollStatus = (u32)(res); + wid = (u32)(res >> 32); + } +} + +////////////////////////////////////////////////////////////////////////////// +// SPURS system workload functions +////////////////////////////////////////////////////////////////////////////// + +/// Restore scheduling parameters after a workload has been preempted by the system service workload +void spursSysServiceCleanupAfterPreemption(SPUThread & spu, SpursKernelMgmtData * mgmt) { + if (mgmt->spurs->m.sysSrvWorkload[mgmt->spuNum] != 0xFF) { + auto wklId = mgmt->spurs->m.sysSrvWorkload[mgmt->spuNum]; + mgmt->spurs->m.sysSrvWorkload[mgmt->spuNum] = 0xFF; + + spursSysServiceUpdateWorkload(spu, mgmt); + if (wklId >= CELL_SPURS_MAX_WORKLOAD) { + mgmt->spurs->m.wklCurrentContention[wklId & 0x0F] -= 0x10; + mgmt->spurs->m.wklReadyCount1[wklId & 0x0F].write_relaxed(mgmt->spurs->m.wklReadyCount1[wklId & 0x0F].read_relaxed() - 1); + } else { + mgmt->spurs->m.wklCurrentContention[wklId & 0x0F] -= 0x01; + mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[wklId & 0x0F].write_relaxed(mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[wklId & 0x0F].read_relaxed() - 1); + } + + // Set the current workload id to the id of the pre-empted workload since cellSpursModulePutTrace + // uses the current worload id to determine the workload to which the trace belongs + auto wklIdSaved = mgmt->wklCurrentId; + mgmt->wklCurrentId = wklId; + + // Trace - STOP: GUID + CellSpursTracePacket pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.tag = CELL_SPURS_TRACE_TAG_STOP; + pkt.data.stop = SPURS_GUID_SYS_WKL; + cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); + + mgmt->wklCurrentId = wklIdSaved; + } +} + +/// Update the trace count for this SPU in CellSpurs +void spursSysServiceUpdateTraceCount(SPUThread & spu, SpursKernelMgmtData * mgmt) { + if (mgmt->traceBuffer) { + auto traceInfo = vm::ptr::make((u32)(mgmt->traceBuffer - (mgmt->spurs->m.traceStartIndex[mgmt->spuNum] << 4))); + traceInfo->count[mgmt->spuNum] = mgmt->traceMsgCount; + } +} + +/// Update trace control in SPU from CellSpurs +void spursSysServiceUpdateTrace(SPUThread & spu, SpursKernelMgmtData * mgmt, u32 arg2, u32 arg3, u32 arg4) { + auto sysSrvMsgUpdateTrace = mgmt->spurs->m.sysSrvMsgUpdateTrace; + mgmt->spurs->m.sysSrvMsgUpdateTrace &= ~(1 << mgmt->spuNum); + mgmt->spurs->m.xCC &= ~(1 << mgmt->spuNum); + mgmt->spurs->m.xCC |= arg2 << mgmt->spuNum; + + bool notify = false; + if (((sysSrvMsgUpdateTrace & (1 << mgmt->spuNum)) != 0) && (mgmt->spurs->m.sysSrvMsgUpdateTrace == 0) && (mgmt->spurs->m.xCD != 0)) { + mgmt->spurs->m.xCD = 0; + notify = true; + } + + if (arg4 && mgmt->spurs->m.xCD != 0) { + mgmt->spurs->m.xCD = 0; + notify = true; + } + + // Get trace parameters from CellSpurs and store them in the LS + if (((sysSrvMsgUpdateTrace & (1 << mgmt->spuNum)) != 0) || (arg3 != 0)) { + if (mgmt->traceMsgCount != 0xFF || mgmt->spurs->m.traceBuffer.addr() == 0) { + spursSysServiceUpdateTraceCount(spu, mgmt); + } else { + mgmt->traceMsgCount = mgmt->spurs->m.traceBuffer->count[mgmt->spuNum]; + } + + mgmt->traceBuffer = mgmt->spurs->m.traceBuffer.addr() + (mgmt->spurs->m.traceStartIndex[mgmt->spuNum] << 4); + mgmt->traceMaxCount = mgmt->spurs->m.traceStartIndex[1] - mgmt->spurs->m.traceStartIndex[0]; + if (mgmt->traceBuffer == 0) { + mgmt->traceMsgCount = 0; + } + } + + if (notify) { + // TODO: sys_spu_thread_send_event(mgmt->spurs->m.spuPort, 2, 0); + } +} + +/// Update events in CellSpurs +void spursSysServiceUpdateEvent(SPUThread & spu, SpursKernelMgmtData * mgmt, u32 wklShutdownBitSet) { + // Mark the workloads in wklShutdownBitSet as completed and also generate a bit set of the completed + // workloads that have a shutdown completion hook registered + u32 wklNotifyBitSet = 0; + for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + if (wklShutdownBitSet & (0x80000000u >> i)) { + mgmt->spurs->m.wklEvent1[i] |= 0x01; + if (mgmt->spurs->m.wklEvent1[i] & 0x02 || mgmt->spurs->m.wklEvent1[i] & 0x10) { + wklNotifyBitSet |= 0x80000000u >> i; + } + } + + if (wklShutdownBitSet & (0x8000 >> i)) { + mgmt->spurs->m.wklEvent2[i] |= 0x01; + if (mgmt->spurs->m.wklEvent2[i] & 0x02 || mgmt->spurs->m.wklEvent2[i] & 0x10) { + wklNotifyBitSet |= 0x8000 >> i; + } + } + } + + if (wklNotifyBitSet) { + // TODO: sys_spu_thread_send_event(mgmt->spurs->m.spuPort, 0, wklNotifyMask); + } +} + +/// Update workload information in the SPU from CellSpurs +void spursSysServiceUpdateWorkload(SPUThread & spu, SpursKernelMgmtData * mgmt) { + u32 wklShutdownBitSet = 0; + mgmt->wklRunnable1 = 0; + mgmt->wklRunnable2 = 0; + for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + // Copy the priority of the workload for this SPU and its unique id to the LS + mgmt->priority[i] = mgmt->spurs->m.wklInfo1[i].priority[mgmt->spuNum] == 0 ? 0 : 0x10 - mgmt->spurs->m.wklInfo1[i].priority[mgmt->spuNum]; + mgmt->wklUniqueId[i] = mgmt->spurs->m.wklInfo1[i].uniqueId.read_relaxed(); + + // Update workload status and runnable flag based on the workload state + auto wklStatus = mgmt->spurs->m.wklStatus1[i]; + if (mgmt->spurs->m.wklState1[i].read_relaxed() == SPURS_WKL_STATE_RUNNABLE) { + mgmt->spurs->m.wklStatus1[i] |= 1 << mgmt->spuNum; + mgmt->wklRunnable1 |= 0x8000 >> i; + } else { + mgmt->spurs->m.wklStatus1[i] &= ~(1 << mgmt->spuNum); + } + + // If the workload is shutting down and if this is the last SPU from which it is being removed then + // add it to the shutdown bit set + if (mgmt->spurs->m.wklState1[i].read_relaxed() == SPURS_WKL_STATE_SHUTTING_DOWN) { + if (((wklStatus & (1 << mgmt->spuNum)) != 0) && (mgmt->spurs->m.wklStatus1[i] == 0)) { + mgmt->spurs->m.wklState1[i].write_relaxed(SPURS_WKL_STATE_REMOVABLE); + wklShutdownBitSet |= 0x80000000u >> i; + } + } + + if (mgmt->spurs->m.flags1 & SF1_32_WORKLOADS) { + // Copy the priority of the workload for this SPU to the LS + if (mgmt->spurs->m.wklInfo2[i].priority[mgmt->spuNum]) { + mgmt->priority[i] |= (0x10 - mgmt->spurs->m.wklInfo2[i].priority[mgmt->spuNum]) << 4; + } + + // Update workload status and runnable flag based on the workload state + wklStatus = mgmt->spurs->m.wklStatus2[i]; + if (mgmt->spurs->m.wklState2[i].read_relaxed() == SPURS_WKL_STATE_RUNNABLE) { + mgmt->spurs->m.wklStatus2[i] |= 1 << mgmt->spuNum; + mgmt->wklRunnable2 |= 0x8000 >> i; + } else { + mgmt->spurs->m.wklStatus2[i] &= ~(1 << mgmt->spuNum); + } + + // If the workload is shutting down and if this is the last SPU from which it is being removed then + // add it to the shutdown bit set + if (mgmt->spurs->m.wklState2[i].read_relaxed() == SPURS_WKL_STATE_SHUTTING_DOWN) { + if (((wklStatus & (1 << mgmt->spuNum)) != 0) && (mgmt->spurs->m.wklStatus2[i] == 0)) { + mgmt->spurs->m.wklState2[i].write_relaxed(SPURS_WKL_STATE_REMOVABLE); + wklShutdownBitSet |= 0x8000 >> i; + } + } + } + } + + if (wklShutdownBitSet) { + spursSysServiceUpdateEvent(spu, mgmt, wklShutdownBitSet); + } +} + +/// Process any messages +void spursSysServiceProcessMessages(SPUThread & spu, SpursKernelMgmtData * mgmt) { + // Process update workload message + if (mgmt->spurs->m.sysSrvMsgUpdateWorkload.read_relaxed() & (1 << mgmt->spuNum)) { + mgmt->spurs->m.sysSrvMsgUpdateWorkload &= ~(1 << mgmt->spuNum); + spursSysServiceUpdateWorkload(spu, mgmt); + } + + // Process update trace message + if (mgmt->spurs->m.sysSrvMsgUpdateTrace & (1 << mgmt->spuNum)) { + spursSysServiceUpdateTrace(spu, mgmt, 1, 0, 0); + } + + // Process terminate request + if (mgmt->spurs->m.sysSrvMsgTerminate & (1 << mgmt->spuNum)) { + mgmt->spurs->m.sysSrvOnSpu &= ~(1 << mgmt->spuNum); + // TODO: Rest of the terminate processing + } +} + +/// Wait for an external event or exit the SPURS thread group if no workloads can be scheduled +void spursSysServiceWaitOrExit(SPUThread & spu, SpursKernelMgmtData * mgmt) { + while (true) { + // Find the number of SPUs that are idling in this SPURS instance + u32 nIdlingSpus = 0; + for (u32 i = 0; i < 8; i++) { + if (mgmt->spurs->m.spuIdling & (1 << i)) { + nIdlingSpus++; + } + } + + bool allSpusIdle = nIdlingSpus == mgmt->spurs->m.nSpus ? true: false; + bool exitIfNoWork = mgmt->spurs->m.flags1 & SF1_EXIT_IF_NO_WORK ? true : false; + + // Check if any workloads can be scheduled + bool foundReadyWorkload = false; + if (mgmt->spurs->m.sysSrvMessage.read_relaxed() & (1 << mgmt->spuNum)) { + foundReadyWorkload = true; + } else { + if (mgmt->spurs->m.flags1 & SF1_32_WORKLOADS) { + for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD2; i++) { + u32 j = i & 0x0F; + u8 runnable = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->wklRunnable1 & (0x8000 >> j) : mgmt->wklRunnable2 & (0x8000 >> j); + u8 priority = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->priority[j] & 0x0F : mgmt->priority[j] >> 4; + u8 maxContention = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklMaxContention[j].read_relaxed() & 0x0F : mgmt->spurs->m.wklMaxContention[j].read_relaxed() >> 4; + u8 contention = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklCurrentContention[j] & 0x0F : mgmt->spurs->m.wklCurrentContention[j] >> 4; + u8 wklSignal = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> j) : mgmt->spurs->m.wklSignal2.read_relaxed() & (0x8000 >> j); + u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0; + u8 readyCount = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklReadyCount1[j].read_relaxed() : mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[j].read_relaxed(); + + if (runnable && priority > 0 && maxContention > contention) { + if (wklFlag || wklSignal || readyCount > contention) { + foundReadyWorkload = true; + break; + } + } + } + } else { + for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { + u8 runnable = mgmt->wklRunnable1 & (0x8000 >> i); + u8 wklSignal = mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> i); + u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0; + u8 readyCount = mgmt->spurs->m.wklReadyCount1[i].read_relaxed() > CELL_SPURS_MAX_SPU ? CELL_SPURS_MAX_SPU : mgmt->spurs->m.wklReadyCount1[i].read_relaxed(); + u8 idleSpuCount = mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[i].read_relaxed() > CELL_SPURS_MAX_SPU ? CELL_SPURS_MAX_SPU : mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[i].read_relaxed(); + u8 requestCount = readyCount + idleSpuCount; + + if (runnable && mgmt->priority[i] != 0 && mgmt->spurs->m.wklMaxContention[i].read_relaxed() > mgmt->spurs->m.wklCurrentContention[i]) { + if (wklFlag || wklSignal || (readyCount != 0 && requestCount > mgmt->spurs->m.wklCurrentContention[i])) { + foundReadyWorkload = true; + break; + } + } + } + } + } + + // If all SPUs are idling and the exit_if_no_work flag is set then the SPU thread group must exit. Otherwise wait for external events. + if ((mgmt->spurs->m.spuIdling & (1 << mgmt->spuNum)) && (allSpusIdle == false || exitIfNoWork == false) && foundReadyWorkload == false) { + // The system service blocks by making a reservation and waiting on the reservation lost event. This is unfortunately + // not yet completely implemented in rpcs3. So we busy wait here. + //u128 r; + //spu.ReadChannel(r, 0); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + if ((allSpusIdle == true && exitIfNoWork == true) || foundReadyWorkload == false) { + mgmt->spurs->m.spuIdling |= 1 << mgmt->spuNum; + } else { + mgmt->spurs->m.spuIdling &= ~(1 << mgmt->spuNum); + } + + if (allSpusIdle == false || exitIfNoWork == false) { + if (foundReadyWorkload == true) { + return; + } + } else { + // TODO: exit spu thread group + } + } +} + +/// Main function for the system service workload +void spursSysServiceWorkloadMain(SPUThread & spu, u32 pollStatus) { + auto mgmt = vm::get_ptr(spu.ls_offset + 0x100); + + if (mgmt->spurs.addr() % CellSpurs::align) { + assert(0); + } + + // Initialise the system service if this is the first time its being started on this SPU + if (mgmt->sysSrvInitialised == 0) { + mgmt->sysSrvInitialised = 1; + + if (mgmt->spurs->m.sysSrvOnSpu & (1 << mgmt->spuNum)) { + assert(0); + } + + mgmt->spurs->m.sysSrvOnSpu |= 1 << mgmt->spuNum; + mgmt->traceBuffer = 0; + mgmt->traceMsgCount = -1; + + spursSysServiceUpdateTrace(spu, mgmt, 1, 1, 0); + spursSysServiceCleanupAfterPreemption(spu, mgmt); + + // Trace - SERVICE: INIT + CellSpursTracePacket pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.tag = CELL_SPURS_TRACE_TAG_SERVICE; + pkt.data.service.incident = CELL_SPURS_TRACE_SERVICE_INIT; + cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); + } + + // Trace - START: Module='SYS ' + CellSpursTracePacket pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.tag = CELL_SPURS_TRACE_TAG_START; + memcpy(pkt.data.start.module, "SYS ", 4); + pkt.data.start.level = 1; // Policy module + pkt.data.start.ls = 0xA00 >> 2; + cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); + + while (true) { + // Process messages for the system service workload + spursSysServiceProcessMessages(spu, mgmt); + +poll: + if (cellSpursModulePollStatus(spu, nullptr)) { + // Trace - SERVICE: EXIT + CellSpursTracePacket pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.tag = CELL_SPURS_TRACE_TAG_SERVICE; + pkt.data.service.incident = CELL_SPURS_TRACE_SERVICE_EXIT; + cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); + + // Trace - STOP: GUID + memset(&pkt, 0, sizeof(pkt)); + pkt.header.tag = CELL_SPURS_TRACE_TAG_STOP; + pkt.data.stop = SPURS_GUID_SYS_WKL; + cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); + break; + } + + // If we reach here it means that either there are more system service messages to be processed + // or there are no workloads that can be scheduled. + + // If the SPU is not idling then process the remaining system service messages + if (mgmt->spuIdling == 0) { + continue; + } + + // If we reach here it means that the SPU is idling + + // Trace - SERVICE: WAIT + CellSpursTracePacket pkt; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.tag = CELL_SPURS_TRACE_TAG_SERVICE; + pkt.data.service.incident = CELL_SPURS_TRACE_SERVICE_WAIT; + cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); + + spursSysServiceWaitOrExit(spu, mgmt); + goto poll; + } +} + +/// Entry point of the system service workload +void spursSysServiceWorkloadEntry(SPUThread & spu) { + auto mgmt = vm::get_ptr(spu.ls_offset + spu.GPR[3]._u32[3]); + auto arg = spu.GPR[4]._u64[1]; + auto pollStatus = spu.GPR[5]._u32[3]; + + spu.GPR[1]._u32[3] = 0x3FFD0; + *(vm::ptr::make(spu.GPR[1]._u32[3])) = 0x3FFF0; + memset(vm::get_ptr(spu.ls_offset + 0x3FFE0), 0, 32); + + if (mgmt->wklCurrentId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { + spursSysServiceWorkloadMain(spu, pollStatus); + } else { + // TODO: If we reach here it means the current workload was preempted to start the + // system service workload. Need to implement this. + } + + // TODO: Ensure that this function always returns to the SPURS kernel + return; +} diff --git a/rpcs3/cellSpursSpu.cpp b/rpcs3/cellSpursSpu.cpp deleted file mode 100644 index 33c75e8a44..0000000000 --- a/rpcs3/cellSpursSpu.cpp +++ /dev/null @@ -1,352 +0,0 @@ -#include "stdafx.h" -#include "Emu/Memory/Memory.h" -#include "Emu/System.h" -#include "Emu/Cell/SPUThread.h" -#include "Emu/SysCalls/Modules/cellSpurs.h" - -/// Output trace information -void cellSpursModulePutTrace(CellSpursTracePacket * packet, unsigned tag) { - // TODO: Implement this -} - -/// Check for execution right requests -unsigned cellSpursModulePollStatus(CellSpursModulePollStatus * status) { - // TODO: Implement this - return 0; -} - -/// Restore scheduling paraneters to the right values after a workload has been preempted by the system service workload -void spursSysServiceCleanupAfterPreemption(SPUThread & spu, SpursKernelMgmtData * mgmt) { - if (mgmt->spurs->m.sysSrvWorkload[mgmt->spuNum] != -1) { - auto wklId = mgmt->spurs->m.sysSrvWorkload[mgmt->spuNum]; - mgmt->spurs->m.sysSrvWorkload[mgmt->spuNum] = -1; - - spursSysServiceUpdateWorkload(spu, mgmt); - if (wklId >= CELL_SPURS_MAX_WORKLOAD) { - mgmt->spurs->m.wklCurrentContention[wklId & 0x0F] -= 0x10; - mgmt->spurs->m.wklReadyCount1[wklId & 0x0F].write_relaxed(mgmt->spurs->m.wklReadyCount1[wklId & 0x0F].read_relaxed() - 1); - } else { - mgmt->spurs->m.wklCurrentContention[wklId & 0x0F] -= 0x01; - mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[wklId & 0x0F].write_relaxed(mgmt->spurs->m.wklIdleSpuCountOrReadyCount2[wklId & 0x0F].read_relaxed() - 1); - } - - auto wklIdSaved = mgmt->wklCurrentId; - mgmt->wklCurrentId = wklId; - - // Trace - STOP: GUID - CellSpursTracePacket pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.tag = CELL_SPURS_TRACE_TAG_STOP; - pkt.data.stop = 0; // TODO: Put GUID of the sys service workload here - cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); - - mgmt->wklCurrentId = wklIdSaved; - } -} - -/// Updatre the trace count for this SPU in CellSpurs -void spursSysServiceUpdateTraceCount(SPUThread & spu, SpursKernelMgmtData * mgmt) { - if (mgmt->traceBuffer) { - mgmt->spurs->m.traceBuffer->count[mgmt->spuNum] = mgmt->traceMsgCount; - } -} - -/// Update trace control in SPU from CellSpurs -void spursSysServiceUpdateTrace(SPUThread & spu, SpursKernelMgmtData * mgmt, u32 arg2, u32 arg3, u32 arg4) { - auto sysSrvMsgUpdateTrace = mgmt->spurs->m.sysSrvMsgUpdateTrace; - mgmt->spurs->m.sysSrvMsgUpdateTrace &= ~(1 << mgmt->spuNum); - mgmt->spurs->m.xCC &= ~(1 << mgmt->spuNum); - mgmt->spurs->m.xCC |= arg2 << mgmt->spuNum; - - bool notify = false; - if ((sysSrvMsgUpdateTrace & (1 << mgmt->spuNum) != 0) && (mgmt->spurs->m.sysSrvMsgUpdateTrace == 0) && (mgmt->spurs->m.xCD != 0)) { - mgmt->spurs->m.xCD = 0; - notify = true; - } - - if (arg4 && mgmt->spurs->m.xCD != 0) { - mgmt->spurs->m.xCD = 0; - notify = true; - } - - if ((sysSrvMsgUpdateTrace & (1 << mgmt->spuNum) != 0) || (arg3 != 0)) { - if (mgmt->traceMsgCount != 0xFF || mgmt->traceBuffer == 0 || mgmt->spurs->m.traceBuffer.addr() == 0) { - spursSysServiceUpdateTraceCount(spu, mgmt); - } else { - mgmt->traceMsgCount = mgmt->spurs->m.traceBuffer->count[mgmt->spuNum]; - } - - mgmt->traceBuffer = mgmt->spurs->m.traceBuffer.addr() + (mgmt->spurs->m.traceStartIndex[mgmt->spuNum] << 4); - mgmt->traceMaxCount = mgmt->spurs->m.traceStartIndex[1] - mgmt->spurs->m.traceStartIndex[0]; - if (mgmt->traceBuffer == 0) { - mgmt->traceMsgCount = 0; - } - } - - if (notify) { - // TODO: sys_spu_thread_send_event(mgmt->spurs->m.spuPort, 2, 0); - } -} - -/// Update events in CellSpurs -void spursSysServiceUpdateEvent(SPUThread & spu, SpursKernelMgmtData * mgmt, u32 wklShutdownMask) { - u32 wklNotifyMask = 0; - for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { - if (wklShutdownMask & (0x80000000 >> i)) { - mgmt->spurs->m.wklEvent1[i] |= 0x01; - if (mgmt->spurs->m.wklEvent1[i] & 0x02 || mgmt->spurs->m.wklEvent1[i] & 0x10) { - wklNotifyMask |= 0x80000000 >> i; - } - } - } - - for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { - if (wklShutdownMask & (0x8000 >> i)) { - mgmt->spurs->m.wklEvent2[i] |= 0x01; - if (mgmt->spurs->m.wklEvent2[i] & 0x02 || mgmt->spurs->m.wklEvent2[i] & 0x10) { - wklNotifyMask |= 0x8000 >> i; - } - } - } - - if (wklNotifyMask) { - // TODO: sys_spu_thread_send_event(mgmt->spurs->m.spuPort, 0, wklNotifyMask); - } -} - -/// Update workload information in the SPU LS from CellSpurs -void spursSysServiceUpdateWorkload(SPUThread & spu, SpursKernelMgmtData * mgmt) { - u32 wklShutdownMask = 0; - mgmt->wklRunnable1 = 0; - for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { - mgmt->priority[i] = mgmt->spurs->m.wklInfo1[i].priority[mgmt->spuNum] == 0 ? 0 : 0x10 - mgmt->spurs->m.wklInfo1[i].priority[mgmt->spuNum]; - mgmt->wklUniqueId[i] = mgmt->spurs->m.wklInfo1[i].uniqueId.read_relaxed(); - - auto wklStatus = mgmt->spurs->m.wklStatus1[i]; - if (mgmt->spurs->m.wklState1[i].read_relaxed() == SPURS_WKL_STATE_RUNNABLE) { - mgmt->spurs->m.wklStatus1[i] |= 1 << mgmt->spuNum; - mgmt->wklRunnable1 |= 0x8000 >> i; - } else { - mgmt->spurs->m.wklStatus1[i] &= ~(1 << mgmt->spuNum); - } - - if (mgmt->spurs->m.wklState1[i].read_relaxed() == SPURS_WKL_STATE_SHUTTING_DOWN) { - if (((wklStatus & (1 << mgmt->spuNum)) != 0) && (mgmt->spurs->m.wklStatus1[i] == 0)) { - mgmt->spurs->m.wklState1[i].write_relaxed(SPURS_WKL_STATE_REMOVABLE); - wklShutdownMask |= 0x80000000 >> i; - } - } - } - - if (mgmt->spurs->m.flags1 & SF1_32_WORKLOADS) { - mgmt->wklRunnable2 = 0; - for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { - if (mgmt->spurs->m.wklInfo2[i].priority[mgmt->spuNum]) { - mgmt->priority[i] |= (0x10 - mgmt->spurs->m.wklInfo2[i].priority[mgmt->spuNum]) << 4; - } - - auto wklStatus = mgmt->spurs->m.wklStatus2[i]; - if (mgmt->spurs->m.wklState2[i].read_relaxed() == SPURS_WKL_STATE_RUNNABLE) { - mgmt->spurs->m.wklStatus2[i] |= 1 << mgmt->spuNum; - mgmt->wklRunnable2 |= 0x8000 >> i; - } else { - mgmt->spurs->m.wklStatus2[i] &= ~(1 << mgmt->spuNum); - } - - if (mgmt->spurs->m.wklState2[i].read_relaxed() == SPURS_WKL_STATE_SHUTTING_DOWN) { - if (((wklStatus & (1 << mgmt->spuNum)) != 0) && (mgmt->spurs->m.wklStatus2[i] == 0)) { - mgmt->spurs->m.wklState2[i].write_relaxed(SPURS_WKL_STATE_REMOVABLE); - wklShutdownMask |= 0x8000 >> i; - } - } - } - } - - if (wklShutdownMask) { - spursSysServiceUpdateEvent(spu, mgmt, wklShutdownMask); - } -} - -/// Process any messages -void spursSysServiceProcessMessages(SPUThread & spu, SpursKernelMgmtData * mgmt) { - // Process update workload message - if (mgmt->spurs->m.sysSrvMsgUpdateWorkload.read_relaxed() & (1 << mgmt->spuNum)) { - mgmt->spurs->m.sysSrvMsgUpdateWorkload &= ~(1 << mgmt->spuNum); - spursSysServiceUpdateWorkload(spu, mgmt); - } - - // Process update trace message - if (mgmt->spurs->m.sysSrvMsgUpdateTrace & (1 << mgmt->spuNum)) { - spursSysServiceUpdateTrace(spu, mgmt, 1, 0, 0); - } - - // Process terminate request - if (mgmt->spurs->m.sysSrvMsgTerminate & (1 << mgmt->spuNum)) { - mgmt->spurs->m.sysSrvOnSpu &= ~(1 << mgmt->spuNum); - // TODO: Rest of the terminate processing - } -} - -/// Wait for an external event or exit the SPURS thread group if no workloads can be scheduled -void spursSysServiceWaitOrExit(SPUThread & spu, SpursKernelMgmtData * mgmt) { - while (true) { - u32 nIdlingSpus = 0; - for (u32 i = 0; i < 8; i++) { - if (mgmt->spurs->m.spuIdling & (1 << i)) { - nIdlingSpus++; - } - } - - bool shouldExit = nIdlingSpus != mgmt->spurs->m.nSpus ? false : mgmt->spurs->m.flags1 & SF1_EXIT_IF_NO_WORK ? true : false; - bool foundSchedulableWorkload = false; - if (mgmt->spurs->m.sysSrvMessage.read_relaxed() & (1 << mgmt->spuNum)) { - foundSchedulableWorkload = true; - } else { - for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { - if ((mgmt->wklRunnable1 & (0x8000 >> i)) && - (mgmt->priority[i] & 0x0F) != 0 && - (mgmt->spurs->m.wklMaxContention[i].read_relaxed() & 0x0F) > (mgmt->spurs->m.wklCurrentContention[i] & 0x0F)) { - foundSchedulableWorkload = true; - break; - } - } - - if (mgmt->spurs->m.flags1 & SF1_32_WORKLOADS && foundSchedulableWorkload == false) { - for (u32 i = 0; i < CELL_SPURS_MAX_WORKLOAD; i++) { - if ((mgmt->wklRunnable2 & (0x8000 >> i)) && - (mgmt->priority[i] & 0xF0) != 0 && - (mgmt->spurs->m.wklMaxContention[i].read_relaxed() & 0xF0) > (mgmt->spurs->m.wklCurrentContention[i] & 0xF0)) { - foundSchedulableWorkload = true; - break; - } - } - } - } - - if ((mgmt->spurs->m.spuIdling & (1 << mgmt->spuNum)) && shouldExit == false && foundSchedulableWorkload == false) { - // TODO: Wait for events - } - - if (shouldExit || foundSchedulableWorkload == false) { - mgmt->spurs->m.spuIdling |= 1 << mgmt->spuNum; - } else { - mgmt->spurs->m.spuIdling &= ~(1 << mgmt->spuNum); - } - - if (shouldExit == false && foundSchedulableWorkload == false) { - continue; - } - - if (shouldExit == false) { - return; - } - - break; - } - - // TODO: exit spu thread group -} - -/// Main function for the system service workload -void spursSysServiceWorkloadMain(SPUThread & spu, u32 pollStatus) { - auto mgmt = vm::get_ptr(spu.ls_offset); - - if (mgmt->spurs.addr() % CellSpurs::align) { - assert(0); - } - - // Initialise the system service if this is the first time its being started on this SPU - if (mgmt->sysSrvInitialised == 0) { - mgmt->sysSrvInitialised = 1; - - if (mgmt->spurs->m.sysSrvOnSpu & (1 << mgmt->spuNum)) { - assert(0); - } - - mgmt->spurs->m.sysSrvOnSpu |= 1 << mgmt->spuNum; - mgmt->traceBuffer = 0; - mgmt->traceMsgCount = -1; - - spursSysServiceUpdateTrace(spu, mgmt, 1, 1, 0); - spursSysServiceCleanupAfterPreemption(spu, mgmt); - - // Trace - SERVICE: INIT - CellSpursTracePacket pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.tag = CELL_SPURS_TRACE_TAG_SERVICE; - pkt.data.service.incident = CELL_SPURS_TRACE_SERVICE_INIT; - cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); - } - - // Trace - START: Module='SYS ' - CellSpursTracePacket pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.tag = CELL_SPURS_TRACE_TAG_START; - memcpy(pkt.data.start.module, "SYS ", 4); - pkt.data.start.level = 1; // Policy module - pkt.data.start.ls = 0xA00 >> 2; - cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); - - while (true) { - // Process messages for the system service workload - spursSysServiceProcessMessages(spu, mgmt); - -poll: - if (cellSpursModulePollStatus(nullptr)) { - // Trace - SERVICE: EXIT - CellSpursTracePacket pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.tag = CELL_SPURS_TRACE_TAG_SERVICE; - pkt.data.service.incident = CELL_SPURS_TRACE_SERVICE_EXIT; - cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); - - // Trace - STOP: GUID - memset(&pkt, 0, sizeof(pkt)); - pkt.header.tag = CELL_SPURS_TRACE_TAG_STOP; - pkt.data.stop = 0; // TODO: Put GUID of the sys service workload here - cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); - break; - } - - // If we reach here it means that either there are more system service messages to be processed - // or there are no workloads that can be scheduled. - - // If the SPU is not idling then process the remaining system service messages - if (mgmt->spuIdling == 0) { - continue; - } - - // If we reach here it means that the SPU is idling - - // Trace - SERVICE: WAIT - CellSpursTracePacket pkt; - memset(&pkt, 0, sizeof(pkt)); - pkt.header.tag = CELL_SPURS_TRACE_TAG_SERVICE; - pkt.data.service.incident = CELL_SPURS_TRACE_SERVICE_WAIT; - cellSpursModulePutTrace(&pkt, mgmt->dmaTagId); - - spursSysServiceWaitOrExit(spu, mgmt); - goto poll; - } -} - -/// Entry point of the system service workload -void spursSysServiceWorkloadEntry(SPUThread & spu) { - auto mgmt = vm::get_ptr(spu.ls_offset + spu.GPR[3]._u32[3]); - auto arg = spu.GPR[4]._u64[1]; - auto pollStatus = spu.GPR[5]._u32[3]; - - spu.GPR[1]._u32[3] = 0x3FFD0; - *(vm::ptr::make(spu.GPR[1]._u32[3])) = 0x3FFF0; - memset(vm::get_ptr(spu.ls_offset + 0x3FFE0), 0, 32); - - if (mgmt->wklCurrentId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID) { - spursSysServiceWorkloadMain(spu, pollStatus); - } else { - // TODO: If we reach here it means the current workload was preempted to start the - // system service workload. Need to implement this. - } - - // TODO: Ensure that this function always returns to the SPURS kernel - return; -} diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 3599cd2cd3..dbac3af5be 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -54,7 +54,7 @@ - + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index a1e6732cc9..3166265186 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -653,7 +653,7 @@ Emu\CPU\ARMv7\Modules - + Emu\SysCalls\Modules