Suspend

From Vita Development Wiki
Revision as of 04:36, 19 June 2022 by CelesteBlue (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

When the system is suspended, it enters a low power state where the main application processor is turned off. The main DRAM state is preserved so upon resume, the kernel does not have to be reloaded.

Suspending

// NOTE: this structure is zeroed before usage. All fields marked "Never written to" are thus 0.
// Also, data coming from KBL Param comes from the "new" KBL Param regenerated after boot
typedef struct SceKernelResumeInfo { // Size is 0x40 on FWs 0.931.010-3.60
    SceSize size;               // Size of this structure
    SceUInt32 wakeupFactor;     // from KBL Param
    SceUInt32 unk0x8;           // Never written to
    SceUInt32 sleepFactor;      // from KBL Param
    SceUInt32 unk0x10;          // Never written to
    SceUInt32 bootControlsInfo; // from KBL Param
    SceUInt32 kblParam_0xC8;    // from KBL Param
    SceUInt32 unk0x1C[9];       // Never written to
} SceKernelResumeInfo;

To suspend the system, PS Vita saves the current context into a buffer and sends it to Syscon. Then it issues a Syscon request to enter the low-power state.

Context Buffer

Offset Size Description
0x0 0x4 Size of this structure (0x68)
0x4 0x4 Mode. Same as in smc_set_power_mode call
0x8 0x4 Virtual address of buffer that receives a copy of SceKernelResumeInfo
0xC 0x4 Virtual address of resume function called with the following arguments:
(u32 TTBR1, u32 CONTEXTIDR, u32 DACR)
0x10 0x4 Saved SCTLR
0x14 0x4 Saved ACTLR
0x18 0x4 Saved CPACR
0x1C 0x4 Saved TTBR0
0x20 0x4 Saved TTBR1
0x24 0x4 Saved TTBCR
0x28 0x4 Saved DACR
0x2C 0x4 Saved PRRR
0x30 0x4 Saved NMRR
0x34 0x4 Saved VBAR
0x38 0x4 Saved CONTEXTIDR
0x3C 0x4 Saved TPIDRURW
0x40 0x4 Saved TPIDRURO
0x44 0x4 Saved TPIDRPRW
0x48 0x18 Unknown/reserved
0x60 0x8 System time

Saving Context

The context buffer defined above is built and the physical address of the context buffer is written to Syscon memory at offset 0xC i.e it calls sceSysconWriteForDriver with sceSysconWriteForDriver(0xC, &paddr, 4);. Then it issues the syscon command to suspend the device.

Resuming

Early boot process

When resume is launched, Syscon starts first_loader, which runs second_loader, like a cold boot.

The physical address of the context buffer is read from Syscon memory at offset 0xC i.e second_loader calls sceSysconRead(0xC, &paddr, 4);. This physical address is written to KBL Param.

SKBL

Upon power up, the Boot Sequence is the same until the point where the SKBL would jump into the non-secure kernel bootloader at 0x51000000. Instead, it checks the suspendinfo address is valid, then copies resume code (from SKBL .text) to the scratchpad (physical address 0x1F000000), the Suspend Info to 0x1F001000 and creates a SceKernelResumeInfo at 0x1F001800, then enters Non-secure state at 0x1F000000.

suspendinfo_adr check

SKBL performs a sanity check on suspendinfo_adr before attempting to resume with it. The address must be properly aligned, and belong in a specific range of physical address space (namely Non-Secure DRAM). If the check fails, SKBL enters an infinite loop and the boot process halts. The following table details the specifics for different firmwares.

FW version Minimal physical address Maximal physical address Required alignment Notes
0.931.010 0x40300000 0x5FFFFFFF 0x10 For ES1 rev0/1 Kermit only
0.931.010 0x40300000 0x7FFFFFFF 0x10 For other Kermit revisions
0.940 0x40400000 0x7FFFFFFF 0x8
3.60 0x40200000 0x5FFFFFFF 0x8

SKBL Resume code

This code starts in Non-secure state, Supervisor mode, with the MMU and all caches disabled.

This code has changed in the following firmwares:

Changelog
Firmware Changes since previous version Notes
0.931.010 N/A First version available
0.940.040 Suspend Info's TTBR1 and CONTEXTIDR are now forwarded to the resume function
0.995.000 Suspend Info's DACR is now forwarded to the resume function instead of being restored Still in use as of FW 3.60.

Entrypoint

The entrypoint is very short. It disables interrupts (cpsid if), clears exclusive access requests (clrex) then jumps to a little thunk at 0x1F000400 which simply jumps to the cleanup function at 0x0000000D (alias of 0x1F00000D).

Cleanup function

This function sets SCTLR to 0, clears the Branch Predictor, TLB and Instruction/Data caches, disables alignement check with SCTLR (clear bit 0x2 - this is redundant as 0 was written to SCTLR earlier), then sets up a MMU context before restoring SCTLR (0x20005805, re-enables MMU) and jumping to the restore function at virtual address 0x80000201 (physical address 0x1F000201). This is important as it means TTBR1 now backs the executed code.

The MMU configuration is the following:

  • TTBCR is set to 2
  • TTBR0 and TTBR1 are set to 0x1F004042
  • DACR is set to 0x55555555 (all domains accessible, permissions checked)
  • CONTEXTIDR is set to 0xF7

The L1 Page Table is located at 0x1F004000 and only contains 3 Section (1MiB) mappings in domain 0:

  • 0x1F000000 is identity mapped
  • 0x80000000 is mapped to physical address 0x1F000000
  • 0xE2000000 is identity mapped as Strongly Ordered memory

Restore function

This function restores (almost) all the registers from the Suspend Info, then copies the SceKernelResumeInfo to the buffer in Suspend Info, sets r3-r12 to 0xDEADBEEF and jumps to the resume function pointed to by the Suspend Info.

NOTE: It is highly plausible that a Suspend Info with TTBCR.N = 0 will crash when TTBCR is restored in this step, as TTBR1 is backing this routine.

In FW 0.931.010, no arguments are provided to the resume function (r0-r2 are 0xDEADBEEF and the resume function is typedef void (*resumeFunc)(void);).
In FWs 0.940-0.990, DACR is not provided to the resume function. r2 is 0xDEADBEEF and the resume function is typedef void(*resumeFunc)(u32 TTBR1, u32 CONTEXTIDR);).

Non-restored registers

The following registers from the Suspend Info are not restored:

Register Reason/Notes
ACTLR Read-only in Non-secure state (SKBL configures it)
TTBR1 Used to map the register restore routine in address space high-part
DACR Restored on firmwares < 0.995.000

Rebooting with Patches

By abusing the way resume works, we can reboot the device into a custom firmware by patching the non-secure kernel bootloader. The general framework to do this is: first, patch the kernel bootloader in RAM to accept unsigned SELFs and load a custom kprx after psp2bootconfig.skprx. It could be the case that by the time your exploit runs the DRAM region containing the kernel loader has been wiped/reused. If so, you must use a lower level exploit to dump the loader. It may even be possible to dump the compressed version, in that case you wouldn't even need to perform cleanup. Your custom krpx will be loaded after the bare essential kernel modules are loaded (memory management, threads, file IO, etc) and it can then patch the full module loader (SceSblAuthMgr) to accept unsigned SELFs as well as any other patches.

Cleaning up non-secure Kernel Bootloader

Ideally, if we can get the original ARZL compressed version of the non-secure kernel bootloader that is extracted from kernel_boot_loader.self, we can extract it (see ARZL), then we can load that to memory and jump to it from our resume function. However, by the time the system is in a state we control, usually that data would be corrupted. What we are left with is the non-secure kernel bootloader in the state after bootup is completed. In order to get it to run successfully again in our resume function, we need to clean up the data it uses.

1.50 cleanup

Physical Address Size Data Notes
0x51002024 0x4 0x0 Interrupt registering returns error if not 0
0x5100203C 0x4 0x0 Interrupt registering returns error if not 0
0x5100205C 0x4 0x0 Interrupt registering returns error if not 0
0x51002074 0x4 0x0 Interrupt registering returns error if not 0
0x51002090 0x4 0x0 Interrupt registering returns error if not 0
0x510020D0 0x4 0x0 Interrupt registering returns error if not 0
0x51002110 0x4 0x0 Interrupt registering returns error if not 0
0x510021A4 0x4 0x0 Interrupt registering returns error if not 0
0x510023FC 0x4 0x0 Interrupt registering returns error if not 0
0x51002604 0x4 0x0 Interrupt registering returns error if not 0
0x51002A40 0x4 0x0 Interrupt registering returns error if not 0
0x5102A014 0x4 0x0 Eventually gets some pointer
0x5102A020 0x4 0x1 Read Block Count (start at 1)
0x5102A024 0x4 0x1 Set to 0 after psp2bootconfig.skprx loads for hacky lock handling
0x5102A02C 0x4 0xFFFFFFFF Memory block id after allocation
0x5102A030 0x4 0x600BF34C Random number set after each boot to ensure integrity
0x5102A034 0x4 0xFF Boot State ID
0x5102A050 0x4 0xFFFFFFFF Memory block id after allocation
0x40300100 0x100 KBL Param KBL Param copied from virtual address 0x46C0

1.69 cleanup

Physical Address Size Data Notes
0x51002A40 0x4 0x0 Interrupt registering returns error if not 0
0x51002024 0x4 0x0 Interrupt registering returns error if not 0
0x5100203C 0x4 0x0 Interrupt registering returns error if not 0
0x5100205C 0x4 0x0 Interrupt registering returns error if not 0
0x51002074 0x4 0x0 Interrupt registering returns error if not 0
0x51002118 0x4 0x0 Interrupt registering returns error if not 0
0x510020D4 0x4 0x0 Interrupt registering returns error if not 0
0x51002090 0x4 0x0 Interrupt registering returns error if not 0
0x51002614 0x4 0x0 Interrupt registering returns error if not 0
0x5100240C 0x4 0x0 Interrupt registering returns error if not 0
0x510021B4 0x4 0x0 Interrupt registering returns error if not 0
0x5102A02C 0x4 0xFFFFFFFF Memory block id after allocation
0x5102A050 0x4 0xFFFFFFFF Memory block id after allocation
0x5102A030 0x4 0x600BF34C Random number set after each boot to ensure integrity
0x5102A024 0x4 0x1 Set to 0 after psp2bootconfig.skprx loads for hacky lock handling
0x40300100 0x100 KBL Param KBL Param copied from virtual address 0x46C0