SPURS: Integrate SPURS kernel and system service workload. Also, fixed some bugs.

This commit is contained in:
S Gopal Rajagopal 2015-01-03 15:59:22 +05:30
parent 4a83d43a8f
commit c1df79b713
8 changed files with 897 additions and 845 deletions

View file

@ -1029,7 +1029,7 @@ void SPUThread::StopAndSignal(u32 code)
case 0x003:
{
GPR[3]._u64[1] = m_code3_func(*this);
m_code3_func(*this);
break;
}

View file

@ -511,7 +511,7 @@ public:
void WriteLS128(const u32 lsa, const u128& data) const { vm::write128(lsa + m_offset, data); }
std::function<void(SPUThread& SPU)> m_custom_task;
std::function<u64(SPUThread& SPU)> m_code3_func;
std::function<void(SPUThread& SPU)> m_code3_func;
public:
SPUThread(CPUThreadType type = CPU_THREAD_SPU);

View file

@ -21,6 +21,8 @@ extern u32 libsre;
extern u32 libsre_rtoc;
#endif
void spursKernelMain(SPUThread & spu);
s64 spursCreateLv2EventQueue(vm::ptr<CellSpurs> spurs, u32& queue_id, vm::ptr<u8> port, s32 size, u64 name_u64)
{
#ifdef PRX_DEBUG_XXX
@ -112,7 +114,7 @@ s64 spursInit(
spurs->m.wklInfoSysSrv.addr.set(be_t<u64>::make(vm::read32(libsre_rtoc - 0x7EA4)));
spurs->m.wklInfoSysSrv.size = 0x2200;
#else
spurs->m.wklInfoSysSrv.addr.set(be_t<u64>::make(0x100)); // wrong 64-bit address
spurs->m.wklInfoSysSrv.addr.set(be_t<u64>::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<SpursKernelMgmtData>(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<u16>::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId)));
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())
{
mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::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<SpursKernelMgmtData>(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<u16>::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId)));
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())
{
mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::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<SpursKernelMgmtData>(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<void>(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<CellSpurs> 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<CellSpurs> spurs, vm::ptr<CellSpursTraceInfo> 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;

View file

@ -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<CellSpurs>, u32 wid, vm::ptr<void> arg);
struct CellSpursTraceInfo;
struct CellSpursTraceInfo
{
static const u32 size = 0x80;
static const u32 align = 16;
be_t<u32> spu_thread[8]; // 0x00
be_t<u32> count[8]; // 0x20
be_t<u32> spu_thread_grp; // 0x40
be_t<u32> nspu; // 0x44
//u8 padding[];
};
struct CellSpursTracePacket
{
static const u32 size = 16;
struct
{
u8 tag;
u8 length;
u8 spu;
u8 workload;
be_t<u32> time;
} header;
union
{
struct
{
be_t<u32> incident;
be_t<u32> reserved;
} service;
struct
{
be_t<u32> ea;
be_t<u16> ls;
be_t<u16> size;
} load;
struct
{
be_t<u32> offset;
be_t<u16> ls;
be_t<u16> size;
} map;
struct
{
s8 module[4];
be_t<u16> level;
be_t<u16> ls;
} start;
be_t<u64> user;
be_t<u64> guid;
be_t<u64> stop;
} data;
};
// Core CellSpurs structures
struct CellSpurs
@ -289,7 +352,7 @@ struct CellSpurs
be_t<u64> arg; // spu argument
be_t<u32> size;
atomic_t<u8> uniqueId; // The unique id is the same for all workloads with the same addr
be_t<u8> 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<u8> wklReadyCount1[0x10]; // 0x00 Number of SPUs requested by each workload (0..15 wids).
atomic_t<u8> 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<u16> wklSignal2; // 0x78 (bitset for 16..32 wids)
u8 x7A[6]; // 0x7A
atomic_t<u8> wklState1[0x10]; // 0x80 SPURS_WKL_STATE_*
u8 wklStatus1[0x10]; // 0x90
u8 wklEvent1[0x10]; // 0xA0
atomic_t<u32> wklMskA; // 0xB0 - System service - Available workloads (32*u1)
atomic_t<u32> wklMskB; // 0xB4 - System service - Available module id
u8 xB8[5]; // 0xB8 - 0xBC - Syetem service exit barrier
atomic_t<u8> 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<u8> wklState2[0x10]; // 0xD0 SPURS_WKL_STATE_*
u8 wklStatus2[0x10]; // 0xE0
u8 wklEvent2[0x10]; // 0xF0
_sub_str1 wklF1[0x10]; // 0x100
vm::bptr<CellSpursTraceInfo, 1, u64> traceBuffer; // 0x900
be_t<u32> traceStartIndex[6]; // 0x908
u8 unknown7[0x948 - 0x920]; // 0x920
be_t<u64> traceDataSize; // 0x948
be_t<u32> traceMode; // 0x950
u8 unknown8[0x980 - 0x954]; // 0x954
u8 wklStatus1[0x10]; // 0x90
u8 wklEvent1[0x10]; // 0xA0
atomic_t<u32> wklMskA; // 0xB0 - System service - Available workloads (32*u1)
atomic_t<u32> wklMskB; // 0xB4 - System service - Available module id
u32 xB8; // 0xB8
u8 sysSrvExitBarrier; // 0xBC
atomic_t<u8> 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<u8> wklState2[0x10]; // 0xD0 SPURS_WKL_STATE_*
u8 wklStatus2[0x10]; // 0xE0
u8 wklEvent2[0x10]; // 0xF0
_sub_str1 wklF1[0x10]; // 0x100
vm::bptr<CellSpursTraceInfo, 1, u64> traceBuffer; // 0x900
be_t<u32> traceStartIndex[6]; // 0x908
u8 unknown7[0x948 - 0x920]; // 0x920
be_t<u64> traceDataSize; // 0x948
be_t<u32> traceMode; // 0x950
u8 unknown8[0x980 - 0x954]; // 0x954
be_t<u64> semPrv; // 0x980
be_t<u32> unk11; // 0x988
be_t<u32> unk12; // 0x98C
@ -559,66 +619,6 @@ struct CellSpursExceptionInfo
be_t<u64> option;
};
struct CellSpursTraceInfo
{
static const u32 size = 0x80;
static const u32 align = 16;
be_t<u32> spu_thread[8]; // 0x00
be_t<u32> count[8]; // 0x20
be_t<u32> spu_thread_grp; // 0x40
be_t<u32> nspu; // 0x44
//u8 padding[];
};
struct CellSpursTracePacket
{
static const u32 size = 16;
struct
{
u8 tag;
u8 length;
u8 spu;
u8 workload;
be_t<u32> time;
} header;
union
{
struct
{
be_t<u32> incident;
be_t<u32> reserved;
} service;
struct
{
be_t<u32> ea;
be_t<u16> ls;
be_t<u16> size;
} load;
struct
{
be_t<u32> offset;
be_t<u16> ls;
be_t<u16> size;
} map;
struct
{
s8 module[4];
be_t<u16> level;
be_t<u16> ls;
} start;
be_t<u64> user;
be_t<u64> guid;
be_t<u64> stop;
} data;
};
// Exception handlers.
//typedef void (*CellSpursGlobalExceptionEventHandler)(vm::ptr<CellSpurs> spurs, vm::ptr<const CellSpursExceptionInfo> info,
// u32 id, vm::ptr<void> 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<u32> dmaTagId; // 0x1CC
vm::bptr<const void, 1, u64> wklCurrentAddr; // 0x1D0
be_t<u32> wklCurrentUniqueId; // 0x1D8
u32 wklCurrentId; // 0x1DC
be_t<u32> wklCurrentId; // 0x1DC
be_t<u32> yieldToKernelAddr; // 0x1E0
be_t<u32> selectWorkloadAddr; // 0x1E4
u8 x1E8; // 0x1E8

View file

@ -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<SpursKernelMgmtData>(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<SpursKernelMgmtData>(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<u16>::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId)));
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()) {
mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::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<SpursKernelMgmtData>(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<u16>::make(mgmt->spurs->m.wklSignal1.read_relaxed() & ~(0x8000 >> wklSelectedId)));
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()) {
mgmt->spurs->m.wklFlag.flag.write_relaxed(be_t<u32>::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<SpursKernelMgmtData>(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<void>(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<CellSpursTraceInfo>::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<SpursKernelMgmtData>(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<SpursKernelMgmtData>(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<u32>::make(spu.GPR[1]._u32[3])) = 0x3FFF0;
memset(vm::get_ptr<void>(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;
}

View file

@ -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<SpursKernelMgmtData>(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<SpursKernelMgmtData>(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<u32>::make(spu.GPR[1]._u32[3])) = 0x3FFF0;
memset(vm::get_ptr<void>(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;
}

View file

@ -54,7 +54,7 @@
<ClCompile Include="..\Utilities\SSemaphore.cpp" />
<ClCompile Include="..\Utilities\StrFmt.cpp" />
<ClCompile Include="..\Utilities\Thread.cpp" />
<ClCompile Include="cellSpursSpu.cpp" />
<ClCompile Include="Emu\SysCalls\Modules\cellSpursSpu.cpp" />
<ClCompile Include="Crypto\aes.cpp" />
<ClCompile Include="Crypto\ec.cpp" />
<ClCompile Include="Crypto\key_vault.cpp" />

View file

@ -653,7 +653,7 @@
<ClCompile Include="Emu\ARMv7\Modules\sceLibm.cpp">
<Filter>Emu\CPU\ARMv7\Modules</Filter>
</ClCompile>
<ClCompile Include="cellSpursSpu.cpp">
<ClCompile Include="Emu\SysCalls\Modules\cellSpursSpu.cpp">
<Filter>Emu\SysCalls\Modules</Filter>
</ClCompile>
</ItemGroup>