Skip to content

Instantly share code, notes, and snippets.

@xerpi
Last active July 31, 2021 14:36
Show Gist options
  • Save xerpi/0e682d594c5def602750c523ee491098 to your computer and use it in GitHub Desktop.
Save xerpi/0e682d594c5def602750c523ee491098 to your computer and use it in GitHub Desktop.
PSVita Reverse Engineering
/*
* SceKermit (MIPS side):
* - https://gist.github.com/TheOfficialFloW/4fdec09e53a7c93a0d07d9e3e982dff4#file-kermit-c-L145
* - https://github.com/Total-Noob/kermit_reverse/blob/master/main.c
*/
/*
* Initial state:
* MIPS ARM
* Consumer ready = true Producer ready = true
* Response ready = false Command ready = false
*
* Sequence diagram:
*
* MIPS ARM
* | | compat_wait_and_get_request()
* | |<-----------------------------------------
* | | - Wait command ready
* | | (blocked)
* Send command | |
* --------------------------------->| |
* - Wait consumer ready | |
* - Write command to SRAM | |
* - Send interrupt [68-70] |-------Interrupt-------->|************************
* - Wait response ready (blocked) | | Push command to CQ *
* *************************|<------Interrupt---------| Send interrupt [4-6] *
* * Signal consumer ready | | Signal command ready *
* *************************| |************************
* | | - Wake from command ready
* | | - Pop command from CQ and return
* | | ---------------------------------------->
* | |
* | | compat_wait_intr()
* | |<-----------------------------------------
* | | - Wait producer ready
* | |----------------------------------------->
* | |
* | | compat_return_value_ex()
* | |<-----------------------------------------
* | | - Write response to SRAM
* **************************|<------Interrupt---------| - Send interrupt [7-9]
* * Read response value | |----------------------------------------->
* * Signal response ready | |
* * Send interrupt [71-73] |-------Interrupt-------->|************************
* **************************| | Signal producer ready *
* - Wake from response ready | |************************
* - Return response | |
* <---------------------------------| |
* | |
* | |
*/
typedef enum SceKermitMode {
SCE_KERMIT_MODE_NONE,
SCE_KERMIT_MODE_UNK_1,
SCE_KERMIT_MODE_UNK_2,
SCE_KERMIT_MODE_MSFS,
SCE_KERMIT_MODE_FLASHFS,
SCE_KERMIT_MODE_AUDIOOUT,
SCE_KERMIT_MODE_ME,
SCE_KERMIT_MODE_LOWIO,
SCE_KERMIT_MODE_POCS_USBPSPCM,
SCE_KERMIT_MODE_PERIPHERAL,
SCE_KERMIT_MODE_WLAN,
SCE_KERMIT_MODE_AUDIOIN,
SCE_KERMIT_MODE_USB,
SCE_KERMIT_MODE_UTILITY,
SCE_KERMIT_MODE_EXTRA_1,
SCE_KERMIT_MODE_EXTRA_2
} SceKermitMode;
typedef enum SceKermitVirtualInterrupt {
SCE_KERMIT_VIRTUAL_INTR_NONE,
SCE_KERMIT_VIRTUAL_INTR_AUDIO_CH1,
SCE_KERMIT_VIRTUAL_INTR_AUDIO_CH2,
SCE_KERMIT_VIRTUAL_INTR_AUDIO_CH3,
SCE_KERMIT_VIRTUAL_INTR_ME_DMA_CH1,
SCE_KERMIT_VIRTUAL_INTR_ME_DMA_CH2,
SCE_KERMIT_VIRTUAL_INTR_ME_DMA_CH3,
SCE_KERMIT_VIRTUAL_INTR_WLAN_CH1,
SCE_KERMIT_VIRTUAL_INTR_WLAN_CH2,
SCE_KERMIT_VIRTUAL_INTR_IMPOSE_CH1,
SCE_KERMIT_VIRTUAL_INTR_POWER_CH1,
SCE_KERMIT_VIRTUAL_INTR_UNKNOWN_CH1, // <- used after settings
SCE_KERMIT_VIRTUAL_INTR_USBGPS_CH1,
SCE_KERMIT_VIRTUAL_INTR_USBPSPCM_CH1
} KermitVirtualInterrupt;
typedef enum SceKermitArgumentMode {
SCE_KERMIT_INPUT_MODE = 0x1,
SCE_KERMIT_OUTPUT_MODE = 0x2
} SceKermitArgumentMode;
typedef enum SceCompatCacheMode {
SCE_COMPAT_CACHE_NONE = 0,
SCE_COMPAT_CACHE_INVALIDATE = 1,
SCE_COMPAT_CACHE_WRITEBACK = 2
} SceCompatCacheMode;
typedef enum SceCompatPeripheralMode {
SCE_COMPAT_PERIPHERAL_POWER_IS_SUSPEND_REQUIRED = 1,
SCE_COMPAT_PERIPHERAL_POWER_GET_BATTERY_LIFETIME = 2,
SCE_COMPAT_PERIPHERAL_POWER_GET_BATTERY_PERCENT = 3,
SCE_COMPAT_PERIPHERAL_HPREMOTE_IS_HEADPHONE_EXIST = 4
} SceCompatPeripheralMode;
/* API structures */
typedef struct SceCompatCdram {
void *cached_cdram;
void *uncached_cdram;
} SceCompatCdram;
typedef struct SceCtrlDataPsp {
unsigned int timestamp;
unsigned int buttons;
unsigned char lx;
unsigned char ly;
unsigned char rx;
unsigned char ry;
unsigned char reserved[4];
} SceCtrlDataPsp; // 0x10
typedef struct SceKermitRequest {
uint32_t cmd; //0x0
SceUID sema_id; //0x4
uint64_t *response; //0x8
uint32_t padding; //0xC
uint64_t args[14]; // 0x10
} SceKermitRequest; //0x80
typedef struct SceKermitReturnParam {
SceUID sema_id; //0x0
uint64_t *response; //0x4
} SceKermitReturnParam; //0x8
/* Internal structures */
typedef struct SceCompatCircularQueue {
SceKermitRequest *request[16];
uint32_t head;
uint32_t tail;
uint32_t size;
uint32_t full;
} SceCompatCircularQueue; /* size = 0x50 */
/* Shared structures */
// MIPS -> ARM. 0xBFC00800
typedef struct SceKermitCommand {
uint32_t cmd; //0x00
SceKermitRequest *request; //0x04
} SceKermitCommand; //0x8
// ARM -> MIPS. 0xBFC00840
typedef struct SceKermitResponse {
uint64_t result; //0x0
SceUID sema_id; //0x8
int32_t unk_C; //0xC
uint64_t *response; //0x10
uint32_t padding; //0x14
uint64_t unk_18; //0x18
} SceKermitResponse; //0x20
// ARM -> MIPS. 0xBFC008C0
typedef struct SceKermitInterrupt {
int32_t flag; //0x0
int32_t unused; //0x4
} SceKermitInterrupt; //0x8
// 0xE5070000
typedef struct SceCompatMailbox {
uint32_t unk00[32]; // 0x000, for Intr IDs 64 - 74
uint32_t unk80[32]; // 0x080, for Intr IDs 64 - 74
} SceCompatMailbox; /* size = 0x1000 */
// 0xE8100000 on ARM, 0xBFC00000 on MIPS
typedef struct SceCompatSharedSram {
uint8_t unk000[0x100]; // 0x000
uint32_t shared32_0_80; // 0x100
uint32_t shared32_81; // 0x104
uint32_t shared32_82; // 0x108
uint32_t shared32_4; // 0x10c
uint32_t shared32_5; // 0x110
uint32_t shared32_6; // 0x114
uint32_t unk118; // 0x118
uint32_t unk11c; // 0x11c
SceCtrlDataPsp pad_data; // 0x120
uint8_t unk130[0x10]; // 0x130
uint32_t pad_data_timestamp; // 0x140
uint8_t unk144[0x2c]; // 0x144
uint32_t shared32_1_83; // 0x170
uint32_t shared32_84; // 0x174
uint32_t shared32_85; // 0x178
uint32_t shared32_2_86; // 0x17c
uint32_t shared32_87; // 0x180
uint32_t shared32_88; // 0x184
uint32_t shared32_7; // 0x188
uint32_t unk18c; // 0x18c
uint8_t unk190[0x650]; // 0x190
SceKermitCommand kermit_command[10]; // 0x7E0, Each for Intr IDs 64 - 74
uint32_t unk830; // 0x830
uint32_t unk834; // 0x834
uint32_t unk838; // 0x838
uint32_t unk83c; // 0x83c
SceKermitResponse kermit_response[3]; // 0x840
uint8_t unk8a0[0x20]; // 0x8a0
SceKermitInterrupt kermit_intr[16]; // 0x8c0
uint8_t unk940[0x6B8]; // 0x940
uint32_t unkff8; // 0xff8
uint32_t unkffc; // 0xffc
} SceCompatSharedSram; /* size = 0x1000 */
static SceUID g_compat_mutex; // Default value = 0
// Command ready semaphore
static SceUID g_compat_virtual_handler_sema[16]; // Default value: [0] = 1, rest = 0
// Producer ready semaphore
static SceUID g_compat_r_sema[3]; // Default value = 1
static SceCompatCircularQueue g_circular_queue[16];
static SceCompatSharedSram *g_shared_sram_addr;
static SceCompatMailbox *g_mailbox_addr;
#if COMPILE
// Triggered when MIPS is ready to receive a Response (wakes compat_wait_intr())
int SceCompatNr_intr_handler(int intr_id, SceUID sema)
{
// Same as g_compat_r_sema[intr_id - 71 (SceCompat0r)]
ksceKernelSignalSema(sema, 1);
return -1;
}
// Triggered when MIPS sends a Command (wakes compat_wait_and_get_request())
int SceCompatNc_intr_handler(int intr_id)
{
SceKermitCommand *cmd;
uint32_t tail;
uint32_t mode;
uint32_t cur_size;
command = &g_shared_sram_addr.kermit_command[intr_id - 64];
mode = command->cmd >> 16;
if (mode < 16) {
tail = g_circular_queue[mode].tail;
cur_size = g_circular_queue[mode].size;
if (cur_size < 16) {
g_circular_queue[mode].request[tail] = command->request;
g_circular_queue[mode].tail = (tail + 1) % 16;
g_circular_queue[mode].size = cur_size + 1;
g_mailbox_addr->unk80[intr_id - 64] = 0xffffffff; /* MIPS Intr IDs 4-6 */
dsb(0xf);
g_mailbox_addr->unk00[intr_id - 64] = 0xffffffff;
dsb(0xf);
} else {
g_circular_queue[mode].full = 1;
}
ksceKernelSignalSema(g_compat_virtual_handler_sema[mode], 1);
}
return -1;
}
// Waits for MIPS to send an Interrupt
int compat_wait_intr(uint32_t intr_index)
{
if (intr_index >= 3)
return 0x80020005;
return ksceKernelWaitSema(g_compat_r_sema[intr_index], 1, NULL);
}
// Waits for MIPS to send a Command
int compat_wait_and_get_request(SceKermitMode mode, uint32_t intr_index)
{
SceCompatMailbox *mailbox;
int ret;
int flags;
uint32_t head;
uint32_t cur_size;
uint32_t tail;
SceKermitRequest *request;
uint32_t timeout;
if ((mode >= 16) || (intr_index >= 3))
return 0x80020005;
ret = ksceKernelWaitSema(g_compat_virtual_handler_sema[mode], 1, NULL);
if (ret < 0)
return ret;
timeout = 5000000;
ret = ksceKernelLockMutex(g_compat_mutex, 1, &timeout);
if (ret < 0)
return 0x80028005;
flags = ksceKernelCpuDisableInterrupts();
head = g_circular_queue[mode].head;
cur_size = g_circular_queue[mode].size;
request = g_circular_queue[mode].request[head];
g_circular_queue[mode].head = (head + 1) % 16;
if (cur_size == 0) {
ksceKernelCpuEnableInterrupts(flags);
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x80010016;
}
g_circular_queue[mode].size = cur_size - 1;
if (g_circular_queue[mode].full) {
tail = g_circular_queue[mode].tail;
g_circular_queue[mode].request[tail] =
g_shared_sram_addr->kermit_command[intr_index + 4].request;
g_circular_queue[mode].tail = (tail + 1) % 16;
g_mailbox_addr->unk80[intr_index + 4] = 0xffffffff; /* MIPS Intr IDs 4-6 */
dsb(0xf);
g_mailbox_addr->unk00[intr_index + 4] = 0xffffffff;
dsb(0xf);
g_circular_queue[mode].full = 0;
}
ksceKernelCpuEnableInterrupts(flags);
ksceKernelUnlockMutex(g_compat_mutex, 1);
ksceKernelCpuDcacheInvalidateRange
((void *)(((uint32_t) request & 0x3ffffff) + SceCompatCdram_memblock_addr),
0x80);
dsb(0xf);
return (int)((uint32_t)request >> 1);
}
// Returns the Response to a Command that was initiated by MIPS
int compat_return_value_ex(uint32_t intr_index, const SceKermitReturnParam *param, uint64_t result)
{
int ret;
uint32_t *kermit_response;
uint32_t lr;
uint32_t timeout;
if (intr_index >= 3)
return 0x80020005;
timeout = 5000000;
ret = ksceKernelLockMutex(g_compat_mutex, 1, &timeout);
if (ret < 0) {
return 0x80028005;
}
if (DAT_8100606c == 0) {
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x80027205;
}
kermit_response = &g_shared_sram_addr->kermit_response[intr_index];
if ((kermit_response < g_shared_sram_addr) || (&g_shared_sram_addr->unkff8 < kermit_response)) {
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x8002000b;
}
g_shared_sram_addr->kermit_response[intr_index].result = result;
g_shared_sram_addr->kermit_response[intr_index].sema_id = param->sema_id;
g_shared_sram_addr->kermit_response[intr_index].unk_C = 0;
g_shared_sram_addr->kermit_response[intr_index].response = param->response;
g_shared_sram_addr->kermit_response[intr_index].padding = 0;
dsb(0xf);
dsb(0xf);
g_mailbox_addr->unk80[intr_index + 7] = 0xffffffff; /* MIPS Intr IDs 7-9 */
dsb(0xf);
g_mailbox_addr->unk00[intr_index + 7] = 0xffffffff;
dsb(0xf);
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0;
}
// Sends a Virtual Interrupt to MIPS
int compat_interrupt(SceKermitVirtualInterrupt intr_code)
{
int ret;
uint64_t start_time;
uint32_t timeout;
if (intr_code >= 16)
return 0x80010016;
timeout = 5000000;
ret = ksceKernelLockMutex(g_compat_mutex, 1, &timeout);
if (ret < 0)
return 0x80028005;
if (DAT_8100606c == 0) {
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x80010086;
}
start_time = ksceKernelGetProcessTimeWideCore();
do {
if (g_shared_sram_addr->kermit_intr[intr_code].flag == 0)
break;
} while (ksceKernelGetProcessTimeWideCore() < (start_time + 1000000));
g_shared_sram_addr->kermit_intr[intr_code].flag = 1;
dsb(0xf);
g_mailbox_addr->unk80[intr_code + 16] = 0xffffffff; /* MIPS Intr IDs 16-31 */
dsb(0xf);
g_mailbox_addr->unk00[intr_code + 16] = 0xffffffff;
dsb(0xf);
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0;
}
int compat_write_shared_ctrl(SceCtrlDataPsp *pad_data)
{
int ret;
uint32_t timestamp;
timeout = 5000000;
ret = ksceKernelLockMutex(g_compat_mutex, 1, &timeout);
if (ret < 0)
return 0x80028005;
if (DAT_8100606c == 0) {
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x80027205;
}
timestamp = pad_data->timestamp;
g_shared_sram_addr->pad_data.timestamp = timestamp;
dsb(0xf);
g_shared_sram_addr->pad_data.buttons = pad_data->buttons;
*(uint32_t *)&g_shared_sram_addr.pad_data.lx = *(uint32_t *)&pad_data->lx;
dsb(0xf);
g_shared_sram_addr->pad_data_timestamp = timestamp;
dsb(0xf);
dsb(0xf);
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0;
}
int compat_read_shared32(uint32_t location, uint32_t *value)
{
int ret;
uint32_t timeout;
timeout = 5000000;
ret = ksceKernelLockMutex(g_compat_mutex, 1, &timeout);
if (ret < 0)
return 0x80028005;
if (DAT_8100606c != 0) {
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x80027205;
}
ret = 0;
switch (location) {
case 0:
*value = g_shared_sram_addr->shared32_0;
break;
case 1:
*value = g_shared_sram_addr->shared32_1;
break;
case 2:
*value = g_shared_sram_addr->shared32_2;
break;
default:
ret = 0x80020005;
break;
case 4:
*value = g_shared_sram_addr->shared32_4;
break;
case 5:
*value = g_shared_sram_addr->shared32_5;
break;
case 6:
*value = g_shared_sram_addr->shared32_6;
break;
case 7:
*value = g_shared_sram_addr->shared32_7;
}
ksceKernelUnlockMutex(g_compat_mutex, 1);
return ret;
}
int compat_write_shared32(uint32_t location, uint32_t value)
{
int ret;
uint32_t timeout;
timeout = 5000000;
ret = ksceKernelLockMutex(g_compat_mutex,1 , &timeout);
if (ret < 0)
return 0x80028005;
if (DAT_8100606c != 0) {
ksceKernelUnlockMutex(g_compat_mutex, 1);
return 0x80027205;
}
ret = 0;
switch (location) {
case 0x80:
g_shared_sram_addr->shared32_0_80 = value;
dsb(0xf);
break;
case 0x81:
g_shared_sram_addr->shared32_81 = value;
dsb(0xf);
break;
case 0x82:
g_shared_sram_addr->shared32_82 = value;
dsb(0xf);
break;
case 0x83:
g_shared_sram_addr->shared32_1_83 = value;
dsb(0xf);
break;
case 0x84:
g_shared_sram_addr->shared32_84 = value;
dsb(0xf);
break;
case 0x85:
g_shared_sram_addr->shared32_85 = value;
dsb(0xf);
break;
case 0x86:
g_shared_sram_addr->shared32_2_86 = value;
dsb(0xf);
break;
case 0x87:
g_shared_sram_addr->shared32_87 = value;
dsb(0xf);
break;
case 0x88:
g_shared_sram_addr->shared32_88 = value;
dsb(0xf);
break;
default:
ret = 0x80020005;
}
ksceKernelUnlockMutex(g_compat_mutex, 1);
return ret;
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment