//================ Copyright (c) Valve Corporation. All Rights Reserved. =========================== // // // //================================================================================================== //-------------------------------------------------------------------------------------------------- // Headers //-------------------------------------------------------------------------------------------------- #include "sys/memory.h" #include "sysutil/sysutil_sysparam.h" #include "cell/sysmodule.h" #include "tier0/platform.h" #include "tier0/dbg.h" #include "tier1/utlbuffer.h" #include #include #include #include #include #include #include #include "ps3_pathinfo.h" #include #include "SpuMgr_ppu.h" #include "memdbgon.h" typedef uint32_t uint32; #define ASSERT Assert //-------------------------------------------------------------------------------------------------- // Defines //-------------------------------------------------------------------------------------------------- // Spu Mailbox Status Register // Described in CBE architecture chapter 8.6.3 SPU Mailbox Status Register (SPU_Mbox_Stat) #define SPU_IN_MBOX_COUNT_SHIFT (8) #define SPU_IN_MBOX_COUNT (0xFF << SPU_IN_MBOX_COUNT_SHIFT) #define SPU_OUT_MBOX_COUNT (0xFF) #define SPU_OUT_INTR_MBOX_COUNT_SHIFT (16) #define SPU_OUT_INTR_MBOX_COUNT (0xFF << SPU_OUT_INTR_MBOX_COUNT_SHIFT) //-------------------------------------------------------------------------------------------------- // Globals //-------------------------------------------------------------------------------------------------- // SPU manager instance SpuMgr gSpuMgr; //-------------------------------------------------------------------------------------------------- // DmaCheckAlignment // Checks restrictions specified in SpuMgr::DmaGet //-------------------------------------------------------------------------------------------------- int DmaCheckAlignment(uint32_t src, uint32_t dest, uint32_t size) { #if !defined( _CERT ) uint32_t align = size; bool error = false; if (size >= 16 && ((size & 0xf) == 0)) { align = 16; } else if (size == 8 || size == 4 || size == 2 || size == 1) { error = ((src & 0xF) != (dest & 0xF)); } else { error = true; // bad size } return (!error && src && dest && SPUMGR_IS_ALIGNED(src, align) && SPUMGR_IS_ALIGNED(dest, align)); #else //!_CERT return 1; #endif //!_CERT } //-------------------------------------------------------------------------------------------------- // Internal functions //-------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------- // handle_syscall // // interrupt handler to handle SPU interrupts // see Handle SPU Interrupts Lv2-Uders_manual_e P34 //-------------------------------------------------------------------------------------------------- void handle_syscall (uint64_t arg) { sys_raw_spu_t id = arg; uint64_t stat; int ret; #ifndef _CERT g_snRawSPULockHandler(); #endif // Create a tag to handle class 2 interrupt, SPU halts fall in // this category ret = sys_raw_spu_get_int_stat(id, 2, &stat); if (ret) { #ifndef _CERT g_snRawSPUUnlockHandler(); #endif sys_interrupt_thread_eoi(); } // // SPU Stop-and-Signal Instruction Trap // This interrupt occurs when the SPU executes a stop-and-signal // instruction. // if (stat & INTR_STOP_MASK) //stop { //We've hit a stop, so what kind of value is it? uint32_t signalVal = GetStopSignal( id ); switch ( signalVal ) { case 0x3: // it was a stop that is in the SPU code to signal to the PPU // do any processing for the user defined stop here // if we do not restart the SPU then we need to call g_snRawSPUNotifySPUStopped(id) // to inform the debugger that SPU has stopped //restart the SPU sys_raw_spu_mmio_write( id, SPU_RunCntl, 0x1 ); break; default: #ifndef _CERT g_snRawSPUNotifySPUStopped(id); #endif break; } } else if (stat & INTR_HALT_MASK) // halt { #ifndef _CERT g_snRawSPUNotifySPUStopped(id); #endif } // Other class 2 interrupts could be handled here // ... // // Must reset interrupt status bit of those not handled. // ret = sys_raw_spu_set_int_stat(id, 2, stat); if (ret) { #ifndef _CERT g_snRawSPUUnlockHandler(); #endif sys_interrupt_thread_eoi(); } // // End of interrupt // #ifndef _CERT g_snRawSPUUnlockHandler(); #endif sys_interrupt_thread_eoi(); } int CreateDefaultInterruptHandler(SpuTaskHandle *pTask) { int res = 0; // // Create a SPU interrupt handler thread, an interrupt tag, // and associate it with the thread // // create thread if (sys_ppu_thread_create(&pTask->m_ppuThread, handle_syscall, 0, INTR_HANDLER_THREAD_PRIORITY, INTR_HANDLER_THREAD_STACK_SIZE, SYS_PPU_THREAD_CREATE_INTERRUPT, "Interrupt PPU Thread")) { res = 1; goto xit; } // create interrupt tag for handling class 2 interrupts from this spu if (sys_raw_spu_create_interrupt_tag(pTask->m_spuId, 2, SYS_HW_THREAD_ANY, &pTask->m_intrTag)) { res = 1; goto xit; } // associate interrupt tag with thread if (sys_interrupt_thread_establish(&pTask->m_interruptThread, pTask->m_intrTag, pTask->m_ppuThread, pTask->m_spuId)) { res = 1; goto xit; } // Set interrupt mask - enable Halt, Stop-and-Signal interrupts if (sys_raw_spu_set_int_mask(pTask->m_spuId, 2, INTR_STOP_MASK | INTR_HALT_MASK)) { res = 1; goto xit; } xit: return res; } //-------------------------------------------------------------------------------------------------- // Class Methods //-------------------------------------------------------------------------------------------------- int SpuMgr::Init(int numRawSpu) { // Need at least 2 SPUs for SPURS instances ASSERT(numRawSpu < 5); // Run SPURS on all SPUs that are not in raw mode // Creating two SPURS instances. One with a thread group of 5 - numRawSpu threads and one // with a thread group of 1 thread. // The instance with a single thread is designed to be singled out as the preemption victim // when the OS needs to use an SPU. We ensure this by giving it a lower priority than the // dedicated SPURS instance. // Init dedicated SPUs SPURS instance // CellSpursAttribute attr; // int32 ret = cellSpursAttributeInitialize(&attr, 5 - numRawSpu, 99, 2, false); // ASSERT(ret == CELL_OK); // ret = cellSpursAttributeEnableSpuPrintfIfAvailable(&attr); // ASSERT(ret == CELL_OK); // ret = cellSpursAttributeSetNamePrefix(&attr, "gameSpusSpurs", std::strlen("gameSpusSpurs")); // ASSERT(ret == CELL_OK); // ret = cellSpursInitializeWithAttribute2(&m_exclusiveSpusSpurs, &attr); // ASSERT(ret == CELL_OK); // Init pre-emption SPU SPURS instance // ret = cellSpursAttributeInitialize(&attr, 1, 100, 2, false); // ASSERT(ret == CELL_OK); // ret = cellSpursAttributeEnableSpuPrintfIfAvailable(&attr); // ASSERT(ret == CELL_OK); // ret = cellSpursAttributeSetNamePrefix(&attr, "sharedSpuSpurs", std::strlen("sharedSpuSpurs")); // ASSERT(ret == CELL_OK); // ret = cellSpursInitializeWithAttribute2(&m_preemptedSpuSpurs, &attr); // ASSERT(ret == CELL_OK); int res = 0; // set up members m_numSpus = 0; // Initialize SPUs if (sys_spu_initialize(6, numRawSpu) != SUCCEEDED) { res = 1; goto xit; } // Create raw spus for (; m_numSpus < (uint32)numRawSpu; m_numSpus++) { if (sys_raw_spu_create(&m_spuIds[m_numSpus], NULL) != SUCCEEDED) { Error("Unable to create saw spu\n"); res = 1; goto xit; } #ifndef _CERT g_snRawSPUNotifyCreation(m_spuIds[m_numSpus]); #endif m_spuInUse[m_numSpus] = 0; } xit: return res; } void SpuMgr::Term() { uint32 spu; // destroy raw spus for (spu = 0; spu < m_numSpus; spu++) { sys_raw_spu_destroy(m_spuIds[spu]); } // destroy the SPURS instances // int ret; // ret = cellSpursfinalize(&m_exclusiveSpusSpurs); // ASSERT(ret == CELL_OK); // // ret = cellSpursfinalize(&m_preemptedSpuSpurs); // ASSERT(ret == CELL_OK); m_numSpus = 0; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- uint32_t spumgr_mmio_read(uint32_t spu, uint32_t regoffset) { uint64_t addr = get_reg_addr(spu,regoffset); addr &= 0xffffffffUL; volatile uint32_t * pAddr = (uint32_t*) addr; return *pAddr; } void spumgr_mmio_write(int spu, int regoffset, uint32_t value) { uint64_t addr = get_reg_addr(spu,regoffset); addr &= 0xffffffffUL; volatile uint32_t * pAddr = (uint32_t*) addr; *pAddr = value; } //-------------------------------------------------------------------------------------------------- // Create Spu task from file based image //-------------------------------------------------------------------------------------------------- static char modPath[MAX_PATH]; int SpuMgr::CreateSpuTask(const char *path, SpuTaskHandle *pTask, CreateSPUTaskCallback *pfnCallback /* = NULL */) { int res = 0; int ret; uint32 spu; register uint32 spuid; uint32 entry; FILE* fp; void* pSpuProg = NULL; sys_spu_image_t img; pTask->m_spuId = -1; pTask->m_ppuThread = NULL; pTask->m_intrTag = NULL; pTask->m_interruptThread = NULL; // find free raw spu for (spu = 0; spu < m_numSpus; spu++) { if (!m_spuInUse[spu]) { break; } } // check we found free spu if (spu == m_numSpus) { res = 1; goto xit; } // Loading an SPU program to the Raw SPU. //if (sys_raw_spu_load(m_spuIds[spu], path, &entry) != SUCCEEDED) sprintf(modPath, "%s/%s", g_pPS3PathInfo->PrxPath(), path); path = modPath; if(strstr(path,".self")) { ret = sys_spu_image_open(&img, path); if(ret != CELL_OK) { // (Running on Main Thread) Error("Failed to open SPU program: %s\n", path); } } else { // Allocate mem for SPU prog CellFsStat stat; cellFsStat(path,&stat); pSpuProg = memalign(4096,((uint32)stat.st_size + 0x7f)&0xffffff80); fp = fopen(path, "rb"); fread(pSpuProg, 1, stat.st_size, fp ); fclose(fp); ret = sys_spu_image_import(&img, pSpuProg, SYS_SPU_IMAGE_PROTECT); if (ret != CELL_OK) { res = 1; goto xit; } } ret = sys_raw_spu_image_load(m_spuIds[spu], &img); spuid = m_spuIds[spu]; if (ret == CELL_OK) { // successfully loaded - mark spu as used and fill in o/p m_spuInUse[spu] = 1; pTask->m_spuId = spuid; } else { res = 1; goto xit; } //Free PPU resources used to load image if(pSpuProg) { free(pSpuProg); } sys_spu_image_close(&img); entry = sys_raw_spu_mmio_read((uint32_t)spuid, (uint32_t)SPU_NPC); #ifndef _CERT g_snRawSPUNotifyElfLoad(spuid, entry, path); #endif // call callback or create default interrupt handler if (!pfnCallback) { res = CreateDefaultInterruptHandler(pTask); } else { res = pfnCallback(pTask); } if (res) { goto xit; } // Run the Raw SPU #ifndef _CERT g_snRawSPUNotifySPUStarted(m_spuIds[spu]); #endif sys_raw_spu_mmio_write(spuid, SPU_NPC, entry); sys_raw_spu_mmio_write(spuid, SPU_RunCntl, 0x1); __asm("eieio"); // Once the SPU has started, write a mailbox with the effective address of the // SPU lock. WriteMailbox( pTask, (uint32) &pTask->m_lock ); WriteMailbox( pTask, (uint32) &pTask->m_memcpyLock ); xit: if(res) { // Error("Error: CreateSpuTask error attempting to load and run %s on SPU\n", path); } return res; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void SpuMgr::DestroySpuTask(SpuTaskHandle *pTask) { if (pTask->m_spuId != -1) { // Stop the Raw spu #ifndef _CERT g_snRawSPUNotifySPUStopped(pTask->m_spuId); #endif sys_raw_spu_mmio_write(pTask->m_spuId, SPU_RunCntl, 0x0); __asm("eieio"); // Cleanup interrupt handling mechanism if (pTask->m_interruptThread) { sys_interrupt_thread_disestablish(pTask->m_interruptThread); // also kills the thread } if (pTask->m_intrTag) { sys_interrupt_tag_destroy(pTask->m_intrTag); } } } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- int SpuMgr::WriteMailbox(SpuTaskHandle *pTask, uint32 val, bool bBlocking /* =true */) { uint32 mboxAvailable; do { // Check the SPU Mailbox Status Register mboxAvailable = sys_raw_spu_mmio_read(pTask->m_spuId, SPU_MBox_Status) & SPU_IN_MBOX_COUNT; } while (bBlocking && !mboxAvailable); if (mboxAvailable) sys_raw_spu_mmio_write(pTask->m_spuId, SPU_In_MBox, (std::uint32_t)val); return !mboxAvailable; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- int SpuMgr::ReadMailbox(SpuTaskHandle *pTask, uint32 *pVal, bool bBlocking /* = true */) { uint32 mailAvailable; do { // Check the SPU Mailbox Status Register mailAvailable = sys_raw_spu_mmio_read(pTask->m_spuId, SPU_MBox_Status) & SPU_OUT_MBOX_COUNT; } while (bBlocking && !mailAvailable); if (mailAvailable) { // Read the SPU Outbound Mailbox Register *pVal = sys_raw_spu_mmio_read(pTask->m_spuId, SPU_Out_MBox); } return !mailAvailable; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- int SpuMgr::ReadIntrMailbox(SpuTaskHandle *pTask, uint32 *pVal, bool bBlocking /* = true */) { uint32 mailAvailable; do { // Check the SPU Mailbox Status Register mailAvailable = sys_raw_spu_mmio_read(pTask->m_spuId, SPU_MBox_Status) & SPU_OUT_INTR_MBOX_COUNT; } while (bBlocking && !mailAvailable); if (mailAvailable) { // Read the SPU Outbound Mailbox Register sys_raw_spu_read_puint_mb(pTask->m_spuId, pVal); } return !mailAvailable; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- bool SpuMgr::Lock( SpuTaskHandle *pTask ) { return cellAtomicCompareAndSwap32( &pTask->m_lock, 0, 1 ) == 0; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void SpuMgr::Unlock( SpuTaskHandle *pTask ) { cellAtomicCompareAndSwap32( &pTask->m_lock, 1, 0 ); } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- bool SpuMgr::MemcpyLock( SpuTaskHandle *pTask ) { return cellAtomicCompareAndSwap32( &pTask->m_memcpyLock, 0, 1 ) == 0; } //-------------------------------------------------------------------------------------------------- // //-------------------------------------------------------------------------------------------------- void SpuMgr::MemcpyUnlock( SpuTaskHandle *pTask ) { cellAtomicCompareAndSwap32( &pTask->m_memcpyLock, 1, 0 ); }