SPURS: Improve the readability of the SPURS2 kernel at the cost of some performance

This commit is contained in:
S Gopal Rajagopal 2014-12-21 01:37:29 +05:30
parent 5a60160035
commit 698f4fd450
4 changed files with 242 additions and 185 deletions

View file

@ -608,7 +608,7 @@ public:
for (auto &arg : values) for (auto &arg : values)
{ {
u32 arg_size = align(u32(arg.size() + 1), stack_align); u32 arg_size = align(u32(arg.size() + 1), stack_align);
u32 arg_addr = Memory.MainMem.AllocAlign(arg_size, stack_align); u32 arg_addr = (u32)Memory.MainMem.AllocAlign(arg_size, stack_align);
std::strcpy(vm::get_ptr<char>(arg_addr), arg.c_str()); std::strcpy(vm::get_ptr<char>(arg_addr), arg.c_str());

View file

@ -112,7 +112,7 @@ namespace vm
__forceinline T* const operator -> () const __forceinline T* const operator -> () const
{ {
return vm::get_ptr<T>(m_addr); return vm::get_ptr<T>((u32)m_addr);
} }
_ptr_base operator++ (int) _ptr_base operator++ (int)

View file

@ -37,7 +37,7 @@ s64 spursCreateLv2EventQueue(vm::ptr<CellSpurs> spurs, u32& queue_id, vm::ptr<u8
return CELL_EAGAIN; // rough return CELL_EAGAIN; // rough
} }
if (s32 res = spursAttachLv2EventQueue(spurs, queue_id, port, 1, true)) if (s32 res = (s32)spursAttachLv2EventQueue(spurs, queue_id, port, 1, true))
{ {
assert(!"spursAttachLv2EventQueue() failed"); assert(!"spursAttachLv2EventQueue() failed");
} }
@ -112,10 +112,10 @@ s64 spursInit(
spurs->m.wklSysG.pm.set(be_t<u64>::make(vm::read32(libsre_rtoc - 0x7EA4))); spurs->m.wklSysG.pm.set(be_t<u64>::make(vm::read32(libsre_rtoc - 0x7EA4)));
spurs->m.wklSysG.size = 0x2200; spurs->m.wklSysG.size = 0x2200;
#else #else
spurs->m.wklSysG.pm.set(be_t<u64>::make(0x100)); // wrong 64-bit address spurs->m.wklInfoSysSrv.pm.set(be_t<u64>::make(0x100)); // wrong 64-bit address
#endif #endif
spurs->m.wklSysG.data = 0; spurs->m.wklInfoSysSrv.data = 0;
spurs->m.wklSysG.copy.write_relaxed(0xff); spurs->m.wklInfoSysSrv.copy.write_relaxed(0xff);
u32 sem; u32 sem;
for (u32 i = 0; i < 0x10; i++) for (u32 i = 0; i < 0x10; i++)
{ {
@ -146,7 +146,7 @@ s64 spursInit(
assert(!"spu_image_import() failed"); assert(!"spu_image_import() failed");
} }
#else #else
spurs->m.spuImg.addr = Memory.Alloc(0x40000, 4096); spurs->m.spuImg.addr = (u32)Memory.Alloc(0x40000, 4096);
#endif #endif
s32 tgt = SYS_SPU_THREAD_GROUP_TYPE_NORMAL; s32 tgt = SYS_SPU_THREAD_GROUP_TYPE_NORMAL;
@ -208,12 +208,12 @@ s64 spursInit(
u64 vRES = 0x20ull << 32; u64 vRES = 0x20ull << 32;
u128 vSET = {}; u128 vSET = {};
if (spurs->m.x72.read_relaxed() & (1 << num)) if (spurs->m.sysSrvMessage.read_relaxed() & (1 << num))
{ {
SPU.WriteLS8(0x1eb, 0); // var4 SPU.WriteLS8(0x1eb, 0); // var4
if (arg1 == 0 || var1 == 0x20) if (arg1 == 0 || var1 == 0x20)
{ {
spurs->m.x72._and_not(1 << num); spurs->m.sysSrvMessage._and_not(1 << num);
} }
} }
else else
@ -224,9 +224,9 @@ s64 spursInit(
u128 savedD = SPU.ReadLS128(0x1B0); u128 savedD = SPU.ReadLS128(0x1B0);
u128 vRC = u128::add8(u128::minu8(wklReadyCount0, u128::from8p(8)), u128::minu8(wklReadyCount1, u128::from8p(8))); u128 vRC = u128::add8(u128::minu8(wklReadyCount0, u128::from8p(8)), u128::minu8(wklReadyCount1, u128::from8p(8)));
u32 wklFlag = spurs->m.wklFlag.flag.read_relaxed(); u32 wklFlag = spurs->m.wklFlag.flag.read_relaxed();
u32 flagRecv = spurs->m.flagRecv.read_relaxed(); u32 flagRecv = spurs->m.wklFlagReceiver.read_relaxed();
u128 vFM = u128::fromV(g_imm_table.fsmb_table[(wklFlag == 0) && (flagRecv < 16) ? 0x8000 >> flagRecv : 0]); u128 vFM = u128::fromV(g_imm_table.fsmb_table[(wklFlag == 0) && (flagRecv < 16) ? 0x8000 >> flagRecv : 0]);
u128 wklSet1 = u128::fromV(g_imm_table.fsmb_table[spurs->m.wklSet1.read_relaxed()]); u128 wklSet1 = u128::fromV(g_imm_table.fsmb_table[spurs->m.wklSignal1.read_relaxed()]);
u128 vFMS1 = vFM | wklSet1; u128 vFMS1 = vFM | wklSet1;
u128 vFMV1 = u128::fromV(g_imm_table.fsmb_table[(var1 < 16) ? 0x8000 >> var1 : 0]); u128 vFMV1 = u128::fromV(g_imm_table.fsmb_table[(var1 < 16) ? 0x8000 >> var1 : 0]);
u32 var5 = SPU.ReadLS32(0x1ec); u32 var5 = SPU.ReadLS32(0x1ec);
@ -270,7 +270,7 @@ s64 spursInit(
if (!arg1 || var1 == vNUM) if (!arg1 || var1 == vNUM)
{ {
spurs->m.wklSet1._and_not(be_t<u16>::make((u16)(vNUM < 16 ? 0x8000 >> vNUM : 0))); spurs->m.wklSignal1._and_not(be_t<u16>::make((u16)(vNUM < 16 ? 0x8000 >> vNUM : 0)));
if (vNUM == flagRecv && wklFlag == 0) if (vNUM == flagRecv && wklFlag == 0)
{ {
spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::make(-1)); spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::make(-1));
@ -305,126 +305,145 @@ s64 spursInit(
{ {
LV2_LOCK(0); // TODO: lock-free implementation if possible LV2_LOCK(0); // TODO: lock-free implementation if possible
const u32 arg1 = SPU.GPR[3]._u32[3]; auto mgmt = vm::get_ptr<SpursKernelMgmtData>(SPU.ls_offset);
u32 var0 = SPU.ReadLS32(0x1d8);
u32 var1 = SPU.ReadLS32(0x1dc);
u128 wklA = vm::read128(spurs.addr() + 0x20);
u128 wklB = vm::read128(spurs.addr() + 0x30);
u128 savedA = SPU.ReadLS128(0x180);
u128 savedB = SPU.ReadLS128(0x190);
u128 vAA = u128::sub8(wklA, savedA);
u128 vBB = u128::sub8(wklB, savedB);
u128 vM1 = {}; if (var1 <= 31) vM1.u8r[var1 & 0xf] = (var1 <= 15) ? 0xf : 0xf0;
u128 vAABB = (arg1 == 0) ? vAA : u128::add8(vAA, u128::andnot(vM1, vBB));
u32 vNUM = 0x20; // The first and only argument to this function is a boolean that is set to false if the function
u64 vRES = 0x20ull << 32; // is called by the SPURS kernel and set to true if called by cellSpursModulePollStatus.
u128 vSET = {}; // If the first argument is true then the shared data is not updated with the result.
const auto isPoll = SPU.GPR[3]._u32[3];
if (spurs->m.x72.read_relaxed() & (1 << num)) // 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++)
{ {
SPU.WriteLS8(0x1eb, 0); // var4 contention[i] = mgmt->spurs->m.wklCurrentContention[i & 0x0F] - mgmt->wklLocContention[i & 0x0F];
if (arg1 == 0 || var1 == 0x20) 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)
{ {
spurs->m.x72._and_not(1 << num); pendingContention[i] = mgmt->spurs->m.wklPendingContention[i] - mgmt->wklLocPendingContention[i];
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->x1EB = 0;
if (!isPoll || mgmt->wklCurrentId == 0x20)
{
// Clear the message bit
mgmt->spurs->m.sysSrvMessage.write_relaxed(mgmt->spurs->m.sysSrvMessage.read_relaxed() & ~(1 << mgmt->spuNum));
} }
} }
else else
{ {
u128 wklReadyCount0 = vm::read128(spurs.addr() + 0x0); // Caclulate the scheduling weight for each worjload
u128 wklReadyCount1 = vm::read128(spurs.addr() + 0x10); u8 maxWeight = 0;
u128 savedC = SPU.ReadLS128(0x1A0); for (auto i = 0; i < CELL_SPURS_MAX_WORKLOAD2; i++)
u128 wklMaxCnt = vm::read128(spurs.addr() + 0x50); {
u32 wklFlag = spurs->m.wklFlag.flag.read_relaxed(); auto j = i & 0x0F;
u32 flagRecv = spurs->m.flagRecv.read_relaxed(); u8 x1ECx1EE = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->x1EC & (0x8000 >> j) : mgmt->x1EE & (0x8000 >> j);
u128 wklSet1 = u128::fromV(g_imm_table.fsmb_table[spurs->m.wklSet1.read_relaxed()]); u8 priority = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->priority[j] & 0x0F : mgmt->priority[j] >> 4;
u128 wklSet2 = u128::fromV(g_imm_table.fsmb_table[spurs->m.wklSet2.read_relaxed()]); u8 maxContention = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklMaxContention[j].read_relaxed() & 0x0F : mgmt->spurs->m.wklMaxContention[j].read_relaxed() >> 4;
u128 vABL = vAABB & u128::from8p(0x0f); u8 wklSignal = i < CELL_SPURS_MAX_WORKLOAD ? mgmt->spurs->m.wklSignal1.read_relaxed() & (0x8000 >> j) : mgmt->spurs->m.wklSignal2.read_relaxed() & (0x8000 >> j);
u128 vABH = u128::fromV(_mm_srli_epi32((vAABB & u128::from8p(0xf0)).vi, 4)); u8 wklFlag = mgmt->spurs->m.wklFlag.flag.read_relaxed() == 0 ? mgmt->spurs->m.wklFlagReceiver.read_relaxed() == i ? 1 : 0 : 0;
u32 var5 = SPU.ReadLS32(0x1ec);
u128 v5L = u128::fromV(g_imm_table.fsmb_table[var5 >> 16]);
u128 v5H = u128::fromV(g_imm_table.fsmb_table[(u16)var5]);
u128 vFML = u128::fromV(g_imm_table.fsmb_table[(wklFlag == 0) && (flagRecv < 16) ? 0x8000 >> flagRecv : 0]);
u128 vFMH = u128::fromV(g_imm_table.fsmb_table[(u16)((wklFlag == 0) && (flagRecv < 32) ? 0x80000000 >> flagRecv : 0)]);
u128 vCL = u128::fromV(_mm_slli_epi32((savedC & u128::from8p(0x0f)).vi, 4));
u128 vCH = savedC & u128::from8p(0xf0);
u128 vABRL = u128::gtu8(wklReadyCount0, vABL);
u128 vABRH = u128::gtu8(wklReadyCount1, vABH);
u128 vCCL = v5L & u128::gtu8(vCL, {}) & u128::gtu8(wklMaxCnt & u128::from8p(0x0f), vABL) & (wklSet1 | vFML | vABRL);
u128 vCCH = v5H & u128::gtu8(vCH, {}) & u128::gtu8(u128::fromV(_mm_srli_epi32((wklMaxCnt & u128::from8p(0xf0)).vi, 4)), vABH) & (wklSet2 | vFMH | vABRH);
u128 v1H = {}; if (var1 <= 31 && var1 > 15) v1H.u8r[var1 & 0xf] = 4;
u128 v1L = {}; if (var1 <= 15) v1L.u8r[var1] = 4;
u128 vCH1 = (v1H | vCH & u128::from8p(0xFB)) & vCCH;
u128 vCL1 = (v1L | vCL & u128::from8p(0xFB)) & vCCL;
u128 vSTATL = vABRL & u128::from8p(1) | wklSet1 & u128::from8p(2) | vFML & u128::from8p(4);
u128 vSTATH = vABRH & u128::from8p(1) | wklSet2 & u128::from8p(2) | vFMH & u128::from8p(4);
s32 max = -1; // For a worload to be considered for scheduling:
for (u32 i = 0; i < 0x10; i++) // 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
const s32 value = vCL1.u8r[i]; // 3. The bit in 0x1EC/0x1EE for the wokload must be set
if (value > max && (vCCL.u8r[i] & 1)) // 4. The number of SPUs allocated to it must be less than the number of SPUs requested (i.e. readyCount)
// Condition #4 may be overriden using a workload signal or using the workload flag
if (x1ECx1EE && priority > 0 && maxContention > contention[i])
{ {
vNUM = i; if (wklFlag || wklSignal || mgmt->spurs->m.wklReadyCount[i].read_relaxed() > contention[i])
max = value; {
} // 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.
for (u32 i = 16; i < 0x20; i++) u8 weight = priority << 4;
{ if (mgmt->wklCurrentId == i)
const s32 value = vCH1.u8r[i]; {
if (value > max && (vCCH.u8r[i] & 1)) weight |= 0x04;
{ }
vNUM = i;
max = value; // In case of a tie the lower numbered workload is chosen
if (weight > maxWeight)
{
wklSelectedId = i;
maxWeight = weight;
pollStatus = mgmt->spurs->m.wklReadyCount[i].read_relaxed() > 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;
}
}
} }
} }
if (vNUM < 0x10) // Not sure what this does. Possibly mark the SPU as idle/in use.
{ mgmt->x1EB = wklSelectedId == CELL_SPURS_SYS_SERVICE_WORKLOAD_ID ? 1 : 0;
vRES = ((u64)vNUM << 32) | vSTATL.u8r[vNUM];
vSET.u8r[vNUM] = 0x01;
}
else if (vNUM < 0x20)
{
vRES = ((u64)vNUM << 32) | vSTATH.u8r[vNUM & 0xf];
vSET.u8r[vNUM] = 0x10;
}
SPU.WriteLS8(0x1eb, vNUM == 0x20); if (!isPoll || wklSelectedId == mgmt->wklCurrentId)
if (!arg1 || var1 == vNUM)
{ {
spurs->m.wklSet1._and_not(be_t<u16>::make((u16)(vNUM < 16 ? 0x8000 >> vNUM : 0))); // Clear workload signal for the selected workload
spurs->m.wklSet2._and_not(be_t<u16>::make((u16)(0x80000000 >> vNUM))); mgmt->spurs->m.wklSignal1.write_relaxed(be_t<u16>::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId)));
if (vNUM == flagRecv && wklFlag == 0) mgmt->spurs->m.wklSignal2.write_relaxed(be_t<u16>::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())
{ {
spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::make(-1)); mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::make(0xFFFFFFFF));
} }
} }
} }
if (arg1 == 0) if (!isPoll)
{ {
vm::write128(spurs.addr() + 0x20, u128::add8(vAA, vSET)); // update wklA // Called by kernel
// Increment the contention for the selected workload
if (wklSelectedId != CELL_SPURS_SYS_SERVICE_WORKLOAD_ID)
{
contention[wklSelectedId]++;
}
SPU.WriteLS128(0x180, vSET); // update savedA for (auto i = 0; i < (CELL_SPURS_MAX_WORKLOAD2 >> 1); i++)
SPU.WriteLS32(0x1dc, vNUM); // update var1 {
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
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;
} }
if (arg1 == 1 && vNUM != var1) u64 result = (u64)wklSelectedId << 32;
{ result |= pollStatus;
vm::write128(spurs.addr() + 0x30, u128::add8(vBB, vSET)); // update wklB return result;
SPU.WriteLS128(0x190, vSET); // update savedB
}
else
{
vm::write128(spurs.addr() + 0x30, vBB); // update wklB
SPU.WriteLS128(0x190, {}); // update savedB
}
return vRES;
}; };
//SPU.m_code3_func = [spurs, num](SPUThread& SPU) -> u64 // test //SPU.m_code3_func = [spurs, num](SPUThread& SPU) -> u64 // test
//{ //{
@ -434,10 +453,13 @@ s64 spursInit(
// return vRES; // return vRES;
//}; //};
SPU.WriteLS128(0x1c0, u128::from32r(0, spurs.addr(), num, 0x1f)); SpursKernelMgmtData * mgmt = vm::get_ptr<SpursKernelMgmtData>(SPU.ls_offset);
mgmt->spurs = spurs;
mgmt->spuNum = num;
mgmt->dmaTagId = 0x1F;
u32 wid = 0x20; u32 wid = CELL_SPURS_SYS_SERVICE_WORKLOAD_ID;
u32 stat = 0; u32 pollStatus = 0;
while (true) while (true)
{ {
if (Emu.IsStopped()) if (Emu.IsStopped())
@ -447,14 +469,14 @@ s64 spursInit(
} }
// get current workload info: // get current workload info:
auto& wkl = wid <= 15 ? spurs->m.wklG1[wid] : (wid <= 31 && isSecond ? spurs->m.wklG2[wid & 0xf] : spurs->m.wklSysG); 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 (SPU.ReadLS64(0x1d0) != wkl.pm.addr()) if (mgmt->wklCurrentAddr != wkl.pm)
{ {
// load executable code: // load executable code:
memcpy(vm::get_ptr<void>(SPU.ls_offset + 0xa00), wkl.pm.get_ptr(), wkl.size); memcpy(vm::get_ptr<void>(SPU.ls_offset + 0xa00), wkl.pm.get_ptr(), wkl.size);
SPU.WriteLS64(0x1d0, wkl.pm.addr()); mgmt->wklCurrentAddr = wkl.pm;
SPU.WriteLS32(0x1d8, wkl.copy.read_relaxed()); mgmt->x1D8 = wkl.copy.read_relaxed();
} }
if (!isSecond) SPU.WriteLS16(0x1e8, 0); if (!isSecond) SPU.WriteLS16(0x1e8, 0);
@ -463,7 +485,7 @@ s64 spursInit(
SPU.GPR[1]._u32[3] = 0x3FFB0; SPU.GPR[1]._u32[3] = 0x3FFB0;
SPU.GPR[3]._u32[3] = 0x100; SPU.GPR[3]._u32[3] = 0x100;
SPU.GPR[4]._u64[1] = wkl.data; SPU.GPR[4]._u64[1] = wkl.data;
SPU.GPR[5]._u32[3] = stat; SPU.GPR[5]._u32[3] = pollStatus;
SPU.FastCall(0xa00); SPU.FastCall(0xa00);
// check status: // check status:
@ -481,7 +503,7 @@ s64 spursInit(
SPU.GPR[3].clear(); SPU.GPR[3].clear();
assert(SPU.m_code3_func); assert(SPU.m_code3_func);
u64 res = SPU.m_code3_func(SPU); u64 res = SPU.m_code3_func(SPU);
stat = (u32)(res); pollStatus = (u32)(res);
wid = (u32)(res >> 32); wid = (u32)(res >> 32);
} }
@ -507,8 +529,8 @@ s64 spursInit(
assert(!"lwcond_create() failed"); assert(!"lwcond_create() failed");
} }
spurs->m.flags1 = (flags & SAF_EXIT_IF_NO_WORK ? SF1_EXIT_IF_NO_WORK : 0) | (isSecond ? SF1_IS_SECOND : 0); spurs->m.flags1 = (flags & SAF_EXIT_IF_NO_WORK ? SF1_EXIT_IF_NO_WORK : 0) | (isSecond ? SF1_32_WORKLOADS : 0);
spurs->m.flagRecv.write_relaxed(0xff); spurs->m.wklFlagReceiver.write_relaxed(0xff);
spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::make(-1)); spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::make(-1));
spurs->_u8[0xD64] = 0; spurs->_u8[0xD64] = 0;
spurs->_u8[0xD65] = 0; spurs->_u8[0xD65] = 0;
@ -516,7 +538,7 @@ s64 spursInit(
spurs->m.ppuPriority = ppuPriority; spurs->m.ppuPriority = ppuPriority;
u32 queue; u32 queue;
if (s32 res = spursCreateLv2EventQueue(spurs, queue, vm::ptr<u8>::make(spurs.addr() + 0xc9), 0x2a, *(u64*)"_spuPrv")) if (s32 res = (s32)spursCreateLv2EventQueue(spurs, queue, vm::ptr<u8>::make(spurs.addr() + 0xc9), 0x2a, *(u64*)"_spuPrv"))
{ {
assert(!"spursCreateLv2EventQueue() failed"); assert(!"spursCreateLv2EventQueue() failed");
} }
@ -575,14 +597,14 @@ s64 spursInit(
for (u32 i = 0; i < 16; i++) for (u32 i = 0; i < 16; i++)
{ {
if (spurs->m.wklStat1[i].read_relaxed() == 2 && if (spurs->m.wklStat1[i].read_relaxed() == 2 &&
spurs->m.wklG1[i].priority.ToBE() != 0 && spurs->m.wklInfo1[i].priority.ToBE() != 0 &&
spurs->m.wklMaxCnt[i].read_relaxed() & 0xf spurs->m.wklMaxContention[i].read_relaxed() & 0xf
) )
{ {
if (spurs->m.wklReadyCount[i].read_relaxed() || if (spurs->m.wklReadyCount[i].read_relaxed() ||
spurs->m.wklSet1.read_relaxed() & (0x8000u >> i) || spurs->m.wklSignal1.read_relaxed() & (0x8000u >> i) ||
(spurs->m.wklFlag.flag.read_relaxed() == 0 && (spurs->m.wklFlag.flag.read_relaxed() == 0 &&
spurs->m.flagRecv.read_relaxed() == (u8)i spurs->m.wklFlagReceiver.read_relaxed() == (u8)i
)) ))
{ {
do_break = true; do_break = true;
@ -590,17 +612,17 @@ s64 spursInit(
} }
} }
} }
if (spurs->m.flags1 & SF1_IS_SECOND) for (u32 i = 0; i < 16; i++) if (spurs->m.flags1 & SF1_32_WORKLOADS) for (u32 i = 0; i < 16; i++)
{ {
if (spurs->m.wklStat2[i].read_relaxed() == 2 && if (spurs->m.wklStat2[i].read_relaxed() == 2 &&
spurs->m.wklG2[i].priority.ToBE() != 0 && spurs->m.wklInfo2[i].priority.ToBE() != 0 &&
spurs->m.wklMaxCnt[i].read_relaxed() & 0xf0 spurs->m.wklMaxContention[i].read_relaxed() & 0xf0
) )
{ {
if (spurs->m.wklReadyCount[i + 0x10].read_relaxed() || if (spurs->m.wklReadyCount[i + 0x10].read_relaxed() ||
spurs->m.wklSet2.read_relaxed() & (0x8000u >> i) || spurs->m.wklSignal2.read_relaxed() & (0x8000u >> i) ||
(spurs->m.wklFlag.flag.read_relaxed() == 0 && (spurs->m.wklFlag.flag.read_relaxed() == 0 &&
spurs->m.flagRecv.read_relaxed() == (u8)i + 0x10 spurs->m.wklFlagReceiver.read_relaxed() == (u8)i + 0x10
)) ))
{ {
do_break = true; do_break = true;
@ -1339,7 +1361,7 @@ s32 spursAddWorkload(
} }
u32 wnum; u32 wnum;
const u32 wmax = spurs->m.flags1 & SF1_IS_SECOND ? 0x20u : 0x10u; // TODO: check if can be changed const u32 wmax = spurs->m.flags1 & SF1_32_WORKLOADS ? 0x20u : 0x10u; // TODO: check if can be changed
spurs->m.wklMskA.atomic_op([spurs, wmax, &wnum](be_t<u32>& value) spurs->m.wklMskA.atomic_op([spurs, wmax, &wnum](be_t<u32>& value)
{ {
wnum = cntlz32(~(u32)value); // found empty position wnum = cntlz32(~(u32)value); // found empty position
@ -1358,15 +1380,15 @@ s32 spursAddWorkload(
u32 index = wnum & 0xf; u32 index = wnum & 0xf;
if (wnum <= 15) if (wnum <= 15)
{ {
assert((spurs->m.wklA[wnum] & 0xf) == 0); assert((spurs->m.wklCurrentContention[wnum] & 0xf) == 0);
assert((spurs->m.wklB[wnum] & 0xf) == 0); assert((spurs->m.wklPendingContention[wnum] & 0xf) == 0);
spurs->m.wklStat1[wnum].write_relaxed(1); spurs->m.wklStat1[wnum].write_relaxed(1);
spurs->m.wklD1[wnum] = 0; spurs->m.wklD1[wnum] = 0;
spurs->m.wklE1[wnum] = 0; spurs->m.wklE1[wnum] = 0;
spurs->m.wklG1[wnum].pm = pm; spurs->m.wklInfo1[wnum].pm = pm;
spurs->m.wklG1[wnum].data = data; spurs->m.wklInfo1[wnum].data = data;
spurs->m.wklG1[wnum].size = size; spurs->m.wklInfo1[wnum].size = size;
spurs->m.wklG1[wnum].priority = *(be_t<u64>*)priorityTable; spurs->m.wklInfo1[wnum].priority = *(be_t<u64>*)priorityTable;
spurs->m.wklH1[wnum].nameClass = nameClass; spurs->m.wklH1[wnum].nameClass = nameClass;
spurs->m.wklH1[wnum].nameInstance = nameInstance; spurs->m.wklH1[wnum].nameInstance = nameInstance;
memset(spurs->m.wklF1[wnum].unk0, 0, 0x20); // clear struct preserving semaphore id memset(spurs->m.wklF1[wnum].unk0, 0, 0x20); // clear struct preserving semaphore id
@ -1377,23 +1399,23 @@ s32 spursAddWorkload(
spurs->m.wklF1[wnum].hookArg = hookArg; spurs->m.wklF1[wnum].hookArg = hookArg;
spurs->m.wklE1[wnum] |= 2; spurs->m.wklE1[wnum] |= 2;
} }
if ((spurs->m.flags1 & SF1_IS_SECOND) == 0) if ((spurs->m.flags1 & SF1_32_WORKLOADS) == 0)
{ {
spurs->m.wklReadyCount[wnum + 16].write_relaxed(0); spurs->m.wklReadyCount[wnum + 16].write_relaxed(0);
spurs->m.wklMinCnt[wnum] = minContention > 8 ? 8 : minContention; spurs->m.wklMinContention[wnum] = minContention > 8 ? 8 : minContention;
} }
} }
else else
{ {
assert((spurs->m.wklA[index] & 0xf0) == 0); assert((spurs->m.wklCurrentContention[index] & 0xf0) == 0);
assert((spurs->m.wklB[index] & 0xf0) == 0); assert((spurs->m.wklPendingContention[index] & 0xf0) == 0);
spurs->m.wklStat2[index].write_relaxed(1); spurs->m.wklStat2[index].write_relaxed(1);
spurs->m.wklD2[index] = 0; spurs->m.wklD2[index] = 0;
spurs->m.wklE2[index] = 0; spurs->m.wklE2[index] = 0;
spurs->m.wklG2[index].pm = pm; spurs->m.wklInfo2[index].pm = pm;
spurs->m.wklG2[index].data = data; spurs->m.wklInfo2[index].data = data;
spurs->m.wklG2[index].size = size; spurs->m.wklInfo2[index].size = size;
spurs->m.wklG2[index].priority = *(be_t<u64>*)priorityTable; spurs->m.wklInfo2[index].priority = *(be_t<u64>*)priorityTable;
spurs->m.wklH2[index].nameClass = nameClass; spurs->m.wklH2[index].nameClass = nameClass;
spurs->m.wklH2[index].nameInstance = nameInstance; spurs->m.wklH2[index].nameInstance = nameInstance;
memset(spurs->m.wklF2[index].unk0, 0, 0x20); // clear struct preserving semaphore id memset(spurs->m.wklF2[index].unk0, 0, 0x20); // clear struct preserving semaphore id
@ -1409,27 +1431,27 @@ s32 spursAddWorkload(
if (wnum <= 15) if (wnum <= 15)
{ {
spurs->m.wklMaxCnt[wnum].atomic_op([maxContention](u8& v) spurs->m.wklMaxContention[wnum].atomic_op([maxContention](u8& v)
{ {
v &= ~0xf; v &= ~0xf;
v |= (maxContention > 8 ? 8 : maxContention); v |= (maxContention > 8 ? 8 : maxContention);
}); });
spurs->m.wklSet1._and_not({ be_t<u16>::make(0x8000 >> index) }); // clear bit in wklFlag1 spurs->m.wklSignal1._and_not({ be_t<u16>::make(0x8000 >> index) }); // clear bit in wklFlag1
} }
else else
{ {
spurs->m.wklMaxCnt[index].atomic_op([maxContention](u8& v) spurs->m.wklMaxContention[index].atomic_op([maxContention](u8& v)
{ {
v &= ~0xf0; v &= ~0xf0;
v |= (maxContention > 8 ? 8 : maxContention) << 4; v |= (maxContention > 8 ? 8 : maxContention) << 4;
}); });
spurs->m.wklSet2._and_not({ be_t<u16>::make(0x8000 >> index) }); // clear bit in wklFlag2 spurs->m.wklSignal2._and_not({ be_t<u16>::make(0x8000 >> index) }); // clear bit in wklFlag2
} }
spurs->m.flagRecv.compare_and_swap(wnum, 0xff); spurs->m.wklFlagReceiver.compare_and_swap(wnum, 0xff);
u32 res_wkl; u32 res_wkl;
CellSpurs::_sub_str3& wkl = wnum <= 15 ? spurs->m.wklG1[wnum] : spurs->m.wklG2[wnum & 0xf]; CellSpurs::WorkloadInfo& wkl = wnum <= 15 ? spurs->m.wklInfo1[wnum] : spurs->m.wklInfo2[wnum & 0xf];
spurs->m.wklMskB.atomic_op_sync([spurs, &wkl, wnum, &res_wkl](be_t<u32>& v) spurs->m.wklMskB.atomic_op_sync([spurs, &wkl, wnum, &res_wkl](be_t<u32>& v)
{ {
const u32 mask = v.ToLE() & ~(0x80000000u >> wnum); const u32 mask = v.ToLE() & ~(0x80000000u >> wnum);
@ -1439,7 +1461,7 @@ s32 spursAddWorkload(
{ {
if (mask & m) if (mask & m)
{ {
CellSpurs::_sub_str3& current = i <= 15 ? spurs->m.wklG1[i] : spurs->m.wklG2[i & 0xf]; CellSpurs::WorkloadInfo& current = i <= 15 ? spurs->m.wklInfo1[i] : spurs->m.wklInfo2[i & 0xf];
if (current.pm.addr() == wkl.pm.addr()) if (current.pm.addr() == wkl.pm.addr())
{ {
// if a workload with identical policy module found // if a workload with identical policy module found
@ -1461,7 +1483,7 @@ s32 spursAddWorkload(
spurs->wklStat(wnum).exchange(2); spurs->wklStat(wnum).exchange(2);
spurs->m.xBD.exchange(0xff); spurs->m.xBD.exchange(0xff);
spurs->m.x72.exchange(0xff); spurs->m.sysSrvMessage.exchange(0xff);
return CELL_OK; return CELL_OK;
} }
@ -1671,7 +1693,7 @@ s64 _cellSpursWorkloadFlagReceiver(vm::ptr<CellSpurs> spurs, u32 wid, u32 is_set
{ {
return CELL_SPURS_POLICY_MODULE_ERROR_ALIGN; return CELL_SPURS_POLICY_MODULE_ERROR_ALIGN;
} }
if (wid >= (spurs->m.flags1 & SF1_IS_SECOND ? 0x20u : 0x10u)) if (wid >= (spurs->m.flags1 & SF1_32_WORKLOADS ? 0x20u : 0x10u))
{ {
return CELL_SPURS_POLICY_MODULE_ERROR_INVAL; return CELL_SPURS_POLICY_MODULE_ERROR_INVAL;
} }
@ -1687,14 +1709,14 @@ s64 _cellSpursWorkloadFlagReceiver(vm::ptr<CellSpurs> spurs, u32 wid, u32 is_set
{ {
if (is_set) if (is_set)
{ {
if (spurs->m.flagRecv.read_relaxed() != 0xff) if (spurs->m.wklFlagReceiver.read_relaxed() != 0xff)
{ {
return CELL_SPURS_POLICY_MODULE_ERROR_BUSY; return CELL_SPURS_POLICY_MODULE_ERROR_BUSY;
} }
} }
else else
{ {
if (spurs->m.flagRecv.read_relaxed() != wid) if (spurs->m.wklFlagReceiver.read_relaxed() != wid)
{ {
return CELL_SPURS_POLICY_MODULE_ERROR_PERM; return CELL_SPURS_POLICY_MODULE_ERROR_PERM;
} }
@ -1706,7 +1728,7 @@ s64 _cellSpursWorkloadFlagReceiver(vm::ptr<CellSpurs> spurs, u32 wid, u32 is_set
return res; return res;
} }
spurs->m.flagRecv.atomic_op([wid, is_set](u8& FR) spurs->m.wklFlagReceiver.atomic_op([wid, is_set](u8& FR)
{ {
if (is_set) if (is_set)
{ {
@ -1783,7 +1805,7 @@ s64 cellSpursReadyCountStore(vm::ptr<CellSpurs> spurs, u32 wid, u32 value)
{ {
return CELL_SPURS_POLICY_MODULE_ERROR_ALIGN; return CELL_SPURS_POLICY_MODULE_ERROR_ALIGN;
} }
if (wid >= (spurs->m.flags1 & SF1_IS_SECOND ? 0x20u : 0x10u) || value > 0xff) if (wid >= (spurs->m.flags1 & SF1_32_WORKLOADS ? 0x20u : 0x10u) || value > 0xff)
{ {
return CELL_SPURS_POLICY_MODULE_ERROR_INVAL; return CELL_SPURS_POLICY_MODULE_ERROR_INVAL;
} }

View file

@ -94,6 +94,7 @@ enum SPURSKernelInterfaces
CELL_SPURS_MAX_SPU = 8, CELL_SPURS_MAX_SPU = 8,
CELL_SPURS_MAX_WORKLOAD = 16, CELL_SPURS_MAX_WORKLOAD = 16,
CELL_SPURS_MAX_WORKLOAD2 = 32, CELL_SPURS_MAX_WORKLOAD2 = 32,
CELL_SPURS_SYS_SERVICE_WORKLOAD_ID = 32,
CELL_SPURS_MAX_PRIORITY = 16, CELL_SPURS_MAX_PRIORITY = 16,
CELL_SPURS_NAME_MAX_LENGTH = 15, CELL_SPURS_NAME_MAX_LENGTH = 15,
CELL_SPURS_SIZE = 4096, CELL_SPURS_SIZE = 4096,
@ -162,8 +163,8 @@ enum SpursAttrFlags : u32
enum SpursFlags1 : u8 enum SpursFlags1 : u8
{ {
SF1_NONE = 0x0, SF1_NONE = 0x0,
SF1_IS_SECOND = 0x40, SF1_32_WORKLOADS = 0x40,
SF1_EXIT_IF_NO_WORK = 0x80, SF1_EXIT_IF_NO_WORK = 0x80,
}; };
@ -214,6 +215,12 @@ struct CellSpursWorkloadFlag
typedef void(*CellSpursShutdownCompletionEventHook)(vm::ptr<CellSpurs>, u32 wid, vm::ptr<void> arg); typedef void(*CellSpursShutdownCompletionEventHook)(vm::ptr<CellSpurs>, u32 wid, vm::ptr<void> arg);
enum CellSpursModulePollStatus {
CELL_SPURS_MODULE_POLL_STATUS_READYCOUNT = 1,
CELL_SPURS_MODULE_POLL_STATUS_SIGNAL = 2,
CELL_SPURS_MODULE_POLL_STATUS_FLAG = 4
};
// Core CellSpurs structures // Core CellSpurs structures
struct CellSpurs struct CellSpurs
{ {
@ -246,7 +253,7 @@ struct CellSpurs
static_assert(sizeof(_sub_str2) == 0x80, "Wrong _sub_str2 size"); static_assert(sizeof(_sub_str2) == 0x80, "Wrong _sub_str2 size");
struct _sub_str3 struct WorkloadInfo
{ {
vm::bptr<const void, 1, u64> pm; // policy module vm::bptr<const void, 1, u64> pm; // policy module
be_t<u64> data; // spu argument be_t<u64> data; // spu argument
@ -255,7 +262,7 @@ struct CellSpurs
be_t<u64> priority; be_t<u64> priority;
}; };
static_assert(sizeof(_sub_str3) == 0x20, "Wrong _sub_str3 size"); static_assert(sizeof(WorkloadInfo) == 0x20, "Wrong WorkloadInfo size");
struct _sub_str4 struct _sub_str4
{ {
@ -274,29 +281,33 @@ struct CellSpurs
// real data // real data
struct struct
{ {
atomic_t<u8> wklReadyCount[0x20]; // 0x0 (index = wid) // The first 0x80 bytes of the CellSpurs structure is shared by all instances of the SPURS kernel in a SPURS instance and the PPU.
u8 wklA[0x10]; // 0x20 (packed 4-bit data, index = wid % 16, internal index = wid / 16) - Current contention // The SPURS kernel copies this from main memory to the LS (address 0x100) then runs its scheduling algorithm using this as one
u8 wklB[0x10]; // 0x30 (packed 4-bit data, index = wid % 16, internal index = wid / 16) - Pending // of the inputs. After selecting a new workload, the SPURS kernel updates this and writes it back to main memory.
u8 wklMinCnt[0x10]; // 0x40 (seems only for first 0..15 wids) - Min contention // The read-modify-write is performed atomically by the SPURS kernel.
atomic_t<u8> wklMaxCnt[0x10]; // 0x50 (packed 4-bit data, index = wid % 16, internal index = wid / 16) - Max contention atomic_t<u8> wklReadyCount[0x10]; // 0x00 (index = wid) - Number of SPUs requested by each workload
CellSpursWorkloadFlag wklFlag; // 0x60 u8 wklCurrentContention[0x10]; // 0x20 (packed 4-bit data, index = wid % 16, internal index = wid / 16) - Number of SPUs used by each workload
atomic_t<u16> wklSet1; // 0x70 (bitset for 0..15 wids) - Workload signal u8 wklPendingContention[0x10]; // 0x30 (packed 4-bit data, index = wid % 16, internal index = wid / 16) - Number of SPUs that are pending to context switch to the workload
atomic_t<u8> x72; // 0x72 - message u8 wklMinContention[0x10]; // 0x40 (seems only for first 0..15 wids) - Min SPUs required for each workload
u8 x73; // 0x73 - idling atomic_t<u8> wklMaxContention[0x10]; // 0x50 (packed 4-bit data, index = wid % 16, internal index = wid / 16) - Max SPUs that may be allocated to each workload
u8 flags1; // 0x74 - Bit0(MSB)=exit_if_no_work, Bit1=32_workloads CellSpursWorkloadFlag wklFlag; // 0x60
u8 x75; // 0x75 - trace control atomic_t<u16> wklSignal1; // 0x70 (bitset for 0..15 wids)
u8 nSpus; // 0x76 - Number of SPUs atomic_t<u8> sysSrvMessage; // 0x72
atomic_t<u8> flagRecv; // 0x77 u8 sysSrvIdling; // 0x73
atomic_t<u16> wklSet2; // 0x78 (bitset for 16..32 wids) - Workload signal u8 flags1; // 0x74 Type is SpursFlags1
u8 x7A[6]; // 0x7A u8 sysSrvTraceControl; // 0x75
u8 nSpus; // 0x76
atomic_t<u8> wklFlagReceiver; // 0x77
atomic_t<u16> wklSignal2; // 0x78 (bitset for 16..32 wids)
u8 x7A[6]; // 0x7A
atomic_t<u8> wklStat1[0x10]; // 0x80 - Workload state (16*u8) - State enum {non_exitent, preparing, runnable, shutting_down, removable, invalid} atomic_t<u8> wklStat1[0x10]; // 0x80 - Workload state (16*u8) - State enum {non_exitent, preparing, runnable, shutting_down, removable, invalid}
u8 wklD1[0x10]; // 0x90 - Workload status (16*u8) u8 wklD1[0x10]; // 0x90 - Workload status (16*u8)
u8 wklE1[0x10]; // 0xA0 - Workload event (16*u8) u8 wklE1[0x10]; // 0xA0 - Workload event (16*u8)
atomic_t<u32> wklMskA; // 0xB0 - Available workloads (32*u1) atomic_t<u32> wklMskA; // 0xB0 - System service - Available workloads (32*u1)
atomic_t<u32> wklMskB; // 0xB4 - Available module id atomic_t<u32> wklMskB; // 0xB4 - System service - Available module id
u8 xB8[5]; // 0xB8 - 0xBC - exit barrier u8 xB8[5]; // 0xB8 - 0xBC - Syetem service exit barrier
atomic_t<u8> xBD; // 0xBD - update workload atomic_t<u8> xBD; // 0xBD - System service message - update workload
u8 xBE[2]; // 0xBE - 0xBF - message - terminate u8 xBE[2]; // 0xBE - 0xBF - System service message - terminate
u8 xC0[8]; // 0xC0 - System workload u8 xC0[8]; // 0xC0 - System workload
u8 xC8; // 0xC8 - System service - on spu u8 xC8; // 0xC8 - System service - on spu
u8 spuPort; // 0xC9 - SPU port for system service u8 spuPort; // 0xC9 - SPU port for system service
@ -304,7 +315,7 @@ struct CellSpurs
u8 xCB; // 0xCB u8 xCB; // 0xCB
u8 xCC; // 0xCC u8 xCC; // 0xCC
u8 xCD; // 0xCD u8 xCD; // 0xCD
u8 xCE; // 0xCE - message - update trace u8 xCE; // 0xCE - System service message - update trace
u8 xCF; // 0xCF u8 xCF; // 0xCF
atomic_t<u8> wklStat2[0x10]; // 0xD0 - Workload state (16*u8) atomic_t<u8> wklStat2[0x10]; // 0xD0 - Workload state (16*u8)
u8 wklD2[0x10]; // 0xE0 - Workload status (16*u8) u8 wklD2[0x10]; // 0xE0 - Workload status (16*u8)
@ -317,8 +328,8 @@ struct CellSpurs
be_t<u32> unk12; // 0x98C be_t<u32> unk12; // 0x98C
be_t<u64> unk13; // 0x990 be_t<u64> unk13; // 0x990
u8 unknown4[0xB00 - 0x998]; u8 unknown4[0xB00 - 0x998];
_sub_str3 wklG1[0x10]; // 0xB00 WorkloadInfo wklInfo1[0x10]; // 0xB00
_sub_str3 wklSysG; // 0xD00 WorkloadInfo wklInfoSysSrv; // 0xD00
be_t<u64> ppu0; // 0xD20 be_t<u64> ppu0; // 0xD20
be_t<u64> ppu1; // 0xD28 be_t<u64> ppu1; // 0xD28
be_t<u32> spuTG; // 0xD30 - SPU thread group be_t<u32> spuTG; // 0xD30 - SPU thread group
@ -347,7 +358,7 @@ struct CellSpurs
_sub_str4 wklH1[0x10]; // 0xE00 _sub_str4 wklH1[0x10]; // 0xE00
_sub_str2 sub3; // 0xF00 _sub_str2 sub3; // 0xF00
u8 unknown6[0x1000 - 0xF80]; // 0xF80 - Gloabl SPU exception handler 0xF88 - Gloabl SPU exception handlers args u8 unknown6[0x1000 - 0xF80]; // 0xF80 - Gloabl SPU exception handler 0xF88 - Gloabl SPU exception handlers args
_sub_str3 wklG2[0x10]; // 0x1000 WorkloadInfo wklInfo2[0x10]; // 0x1000
_sub_str1 wklF2[0x10]; // 0x1200 _sub_str1 wklF2[0x10]; // 0x1200
_sub_str4 wklH2[0x10]; // 0x1A00 _sub_str4 wklH2[0x10]; // 0x1A00
} m; } m;
@ -741,5 +752,29 @@ struct CellSpursTaskBinInfo
CellSpursTaskLsPattern lsPattern; CellSpursTaskLsPattern lsPattern;
}; };
// 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
u8 priority[0x10]; // 0x1A0
u8 x1B0[0x10]; // 0x1B0
vm::bptr<CellSpurs, 1, u64> spurs; // 0x1C0
be_t<u32> spuNum; // 0x1C8
be_t<u32> dmaTagId; // 0x1CC
vm::bptr<const void, 1, u64> wklCurrentAddr; // 0x1D0
be_t<u32> x1D8; // 0x1D8
u32 wklCurrentId; // 0x1DC
be_t<u32> yieldToKernelAddr; // 0x1E0
be_t<u32> selectWorkloadAddr; // 0x1E4
u8 x1E8; // 0x1E8
u8 x1E9; // 0x1E9
u8 x1EA; // 0x1EA
u8 x1EB; // 0x1EB - This might be spuIdling
be_t<u16> x1EC; // 0x1EC - This might be wklEnable1
be_t<u16> x1EE; // 0x1EE - This might be wklEnable2
};
s64 spursAttachLv2EventQueue(vm::ptr<CellSpurs> spurs, u32 queue, vm::ptr<u8> port, s32 isDynamic, bool wasCreated); s64 spursAttachLv2EventQueue(vm::ptr<CellSpurs> spurs, u32 queue, vm::ptr<u8> port, s32 isDynamic, bool wasCreated);
s64 spursWakeUp(vm::ptr<CellSpurs> spurs); s64 spursWakeUp(vm::ptr<CellSpurs> spurs);