├── Makefile ├── README.md ├── linux_bootstrap.ld ├── linux_bootstrap.s ├── linuxloader.yml ├── main.c └── resume.s /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = linuxloader 2 | TARGET_OBJS = main.o resume.o 3 | BOOTSTRAP_OBJS = linux_bootstrap.o 4 | 5 | LIBS = -ltaihenForKernel_stub -lSceSysclibForDriver_stub -lSceSysmemForDriver_stub \ 6 | -lSceSysmemForKernel_stub -lSceThreadmgrForDriver_stub -lSceCpuForKernel_stub \ 7 | -lSceCpuForDriver_stub -lSceUartForKernel_stub -lScePervasiveForDriver_stub \ 8 | -lSceSysconForDriver_stub -lScePowerForDriver_stub -lSceIofilemgrForDriver_stub \ 9 | -lSceSblAIMgrForDriver_stub 10 | 11 | PREFIX = arm-vita-eabi 12 | CC = $(PREFIX)-gcc 13 | AS = $(PREFIX)-as 14 | OBJCOPY = $(PREFIX)-objcopy 15 | CFLAGS = -Wl,-q -Wall -O0 -nostartfiles -mcpu=cortex-a9 -mthumb-interwork 16 | ASFLAGS = 17 | 18 | all: $(TARGET).skprx 19 | 20 | %.skprx: %.velf 21 | vita-make-fself -c $< $@ 22 | 23 | %.velf: %.elf 24 | vita-elf-create -e $(TARGET).yml $< $@ 25 | 26 | linux_bootstrap.elf: $(BOOTSTRAP_OBJS) 27 | $(CC) -T linux_bootstrap.ld -nostartfiles -nostdlib $^ -o $@ -lgcc 28 | 29 | linux_bootstrap.bin: linux_bootstrap.elf 30 | $(OBJCOPY) -S -O binary $^ $@ 31 | 32 | linux_bootstrap_bin.o: linux_bootstrap.bin 33 | $(OBJCOPY) -I binary -O elf32-littlearm --binary-architecture arm $^ $@ 34 | 35 | $(TARGET).elf: $(TARGET_OBJS) linux_bootstrap_bin.o 36 | $(CC) $(CFLAGS) $^ $(LIBS) -o $@ 37 | 38 | .PHONY: all clean send 39 | 40 | clean: 41 | @rm -rf $(TARGET).skprx $(TARGET).velf $(TARGET).elf $(TARGET_OBJS) $(BOOTSTRAP_OBJS) \ 42 | linux_bootstrap.elf linux_bootstrap.bin linux_bootstrap_bin.o 43 | 44 | send: $(TARGET).skprx 45 | curl --ftp-method nocwd -T $(TARGET).skprx ftp://$(PSVITAIP):1337/ux0:/data/tai/kplugin.skprx 46 | @echo "Sent." 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlayStation Vita Linux Loader 2 | 3 | ## What's this? 4 | 5 | This is a kernel plugin that lets you run Linux in ARMv7 non-secure System mode. 6 | 7 | ## How does it work? 8 | 9 | At first, the plugin allocates a couple of physically contiguous buffers where it loads the Linux kernel image and the [Device Tree Blob](https://en.wikipedia.org/wiki/Device_tree). 10 | 11 | Then it triggers a power standby request and when PSVita OS is about to send the [Syscon](https://wiki.henkaku.xyz/vita/Syscon) 12 | command to actually perform the standby, it changes the request type into a soft-reset and the resume routine address to a custom one (`resume.s`). 13 | 14 | Once the PSVita wakes from the soft-reset, the custom resume routine executes and identity maps the scratchpad (address [0x1F000000](https://wiki.henkaku.xyz/vita/Physical_Memory)) using a 1MiB section. Afterwards, the Linux bootstrap code (`linux_bootstrap.s`) is copied to the scratchpad where it proceeds and jumps to (passing some parameters such as the Linux and DTB physical addresses). 15 | 16 | Since the Linux bootstrap code is now in an identity-mapped location, it can proceed to disable the MMU (and the caches) and finally jump to the Linux kernel. 17 | 18 | ## Instructions 19 | 20 | You will need a compiled Linux kernel image (build instructions [here](https://gist.github.com/xerpi/5c60ce951caf263fcafffb48562fe50f)), which has to be placed at `ux0:/linux/zImage`, and the corresponding DTB file, which has to be placed at `ux0:/linux/vita.dtb`. 21 | 22 | ## Debugging 23 | 24 | This Linux loader will print debug info over UART0. Check [UART Console](https://wiki.henkaku.xyz/vita/UART_Console) for the location of the pins. 25 | 26 | ## Credits 27 | Thanks to everybody who has helped me, specially the [Team Molecule](https://twitter.com/teammolecule) (formed by [Davee](https://twitter.com/DaveeFTW), Proxima, [xyz](https://twitter.com/pomfpomfpomf3), and [YifanLu](https://twitter.com/yifanlu)), [TheFloW](https://twitter.com/theflow0), [motoharu](https://github.com/motoharu-gosuto), and everybody at the [HENkaku](https://discord.gg/m7MwpKA) Discord channel. 28 | -------------------------------------------------------------------------------- /linux_bootstrap.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 2 | OUTPUT_ARCH(arm) 3 | 4 | ENTRY(_start) 5 | 6 | SECTIONS 7 | { 8 | . = 0x1F000000; 9 | .text : { 10 | *(.text) 11 | *(.text*) 12 | } 13 | .data : { 14 | *(.data*) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /linux_bootstrap.s: -------------------------------------------------------------------------------- 1 | .set CPU123_WAIT_BASE, 0x1F007F00 2 | 3 | .align 4 4 | .text 5 | .cpu cortex-a9 6 | 7 | .macro get_cpu_id, rd 8 | mrc p15, 0, \rd, c0, c0, 5 9 | and \rd, #0xF 10 | .endm 11 | 12 | .globl _start 13 | @ r0 = Linux entry paddr, r1 = DTB paddr 14 | _start: 15 | dsb 16 | mov r9, r0 17 | mov r10, r1 18 | 19 | @ Clean jump target address (base + CPU_ID * 4) 20 | ldr r0, =CPU123_WAIT_BASE 21 | get_cpu_id r1 22 | mov r2, #0 23 | str r2, [r0, r1, lsl #2] 24 | 25 | @ Barrier 26 | ldr r0, =sync_point_1 27 | bl cpus_sync 28 | 29 | @ Clean and invalidate the entire Dcache 30 | bl dcache_clean_inv_all 31 | 32 | @ Identity-mapped region, disable MMU, D/Icache 33 | mrc p15, 0, r0, c1, c0, 0 34 | bic r0, #1 << 0 @ MMU 35 | bic r0, #1 << 2 @ Dcache 36 | bic r0, #1 << 12 @ Icache 37 | mcr p15, 0, r0, c1, c0, 0 38 | 39 | @ Invalidate the entire Dcache 40 | bl dcache_inv_all 41 | 42 | mov r0, #0 43 | mcr p15, 0, r0, c7, c5, 6 @ BPIALL (Branch Predictor Invalidate All) 44 | isb 45 | mcr p15, 0, r0, c7, c5, 0 @ ICIALLU (Icache Invalidate All to PoU) 46 | dsb 47 | mcr p15, 0, r0, c8, c7, 0 @ TLBIALL (Unified TLB Invalidate All) 48 | isb 49 | 50 | @ Check CPU ID 51 | get_cpu_id r0 52 | cmp r0, #0 53 | beq cpu0_cont 54 | 55 | @ CPUs 1,2,3 wait for an address to jump to 56 | ldr r1, =CPU123_WAIT_BASE 57 | cpu123_wait: 58 | wfe 59 | ldr r2, [r1, r0, lsl #2] 60 | cmp r2, #0 61 | beq cpu123_wait 62 | bx r2 63 | 64 | cpu0_cont: 65 | @ Enable the UART 66 | bl uart_enable 67 | 68 | @ L2 cache clean and invalidate all and disable 69 | @ XXX: Better to not touch the L2 for now 70 | @ldr r12, =0x16A 71 | @mov r0, #0 72 | @smc #0 73 | @isb 74 | @dsb 75 | 76 | @ Jump to Linux! 77 | mov r0, #0 78 | mvn r1, #0 79 | mov r2, r10 80 | mov lr, r9 81 | bx lr 82 | 83 | @ Uses r0, r1 and r2 84 | uart_enable: 85 | @ Enable the UART clock (pervasive) 86 | ldr r0, =0xE3102000 @ ScePervasiveGate 87 | ldr r1, =0xE3101000 @ ScePervasiveResetReg 88 | 89 | ldr r2, [r0, #0x120] @ Clock enable 90 | orr r2, #1 91 | str r2, [r0, #0x120] 92 | 93 | ldr r2, [r1, #0x120] @ Reset disable 94 | bic r2, #1 95 | str r2, [r1, #0x120] 96 | 97 | @ Setup the UART 98 | ldr r0, =0xE2030000 @ SceUartReg 99 | ldr r1, =0xE3105000 @ SceUartClkgenReg 100 | 101 | mov r2, #0 @ Disable the device 102 | str r2, [r0, #4] 103 | dsb 104 | 105 | ldr r2, =0x1001A @ Baudrate = 115200 106 | str r2, [r1] 107 | 108 | mov r2, #1 @ UART config 109 | str r2, [r0, #0x10] 110 | mov r2, #3 111 | str r2, [r0, #0x20] 112 | mov r2, #0 113 | str r2, [r0, #0x30] 114 | str r2, [r0, #0x40] 115 | str r2, [r0, #0x50] 116 | mov r2, #0x303 117 | str r2, [r0, #0x60] 118 | ldr r2, =0x10001 119 | str r2, [r0, #0x64] 120 | dsb 121 | 122 | mov r2, #1 @ Enable the device 123 | str r2, [r0, #4] 124 | dsb 125 | 126 | bx lr 127 | 128 | @ Uses: r0, r1 129 | dcache_clean_inv_all: 130 | mov r0, #0 131 | 1: 132 | mcr p15, 0, r0, c7, c14, 2 @ DCCISW (Data cache clean and invalidate by set/way) 133 | adds r0, r0, #0x40000000 134 | bcc 1b 135 | adds r0, #0x20 136 | lsrs r1, r0, #0xD 137 | beq 1b 138 | dsb 139 | bx lr 140 | 141 | @ Uses: r0, r1 142 | dcache_inv_all: 143 | mov r0, #0 144 | 1: 145 | mcr p15, 0, r0, c7, c6, 2 @ DCISW (Data cache invalidate by set/way) 146 | adds r0, r0, #0x40000000 147 | bcc 1b 148 | adds r0, #0x20 149 | lsrs r1, r0, #0xD 150 | beq 1b 151 | dsb 152 | bx lr 153 | 154 | 155 | @ r0 = sync point address 156 | @ Uses: r0, r1, r2 157 | cpus_sync: 158 | get_cpu_id r1 159 | cmp r1, #0 160 | streq r1, [r0] 161 | 1: 162 | ldrb r2, [r0] 163 | cmp r1, r2 164 | wfene 165 | bne 1b 166 | ldrh r2, [r0] 167 | adds r2, #1 168 | adds r2, r2, #0x100 169 | strh r2, [r0] 170 | dsb 171 | sev 172 | 1: 173 | ldrb r2, [r0, #1] 174 | cmp r2, #4 175 | wfene 176 | bne 1b 177 | bx lr 178 | 179 | .data 180 | sync_point_1: .word 0 181 | -------------------------------------------------------------------------------- /linuxloader.yml: -------------------------------------------------------------------------------- 1 | linuxloader: 2 | attributes: 0 3 | version: 4 | major: 1 5 | minor: 1 6 | main: 7 | start: module_start 8 | stop: module_stop 9 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define LOG(s, ...) \ 17 | do { \ 18 | char _buffer[256]; \ 19 | snprintf(_buffer, sizeof(_buffer), s, ##__VA_ARGS__); \ 20 | uart0_print(_buffer); \ 21 | } while (0) 22 | 23 | void _start() __attribute__ ((weak, alias ("module_start"))); 24 | 25 | #define ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) 26 | 27 | #define LINUX_DIR "ux0:/linux/" 28 | 29 | #define ZIMAGE LINUX_DIR "zImage" 30 | #define VITA1000_DTB LINUX_DIR "vita1000.dtb" 31 | #define VITA2000_DTB LINUX_DIR "vita2000.dtb" 32 | #define PSTV_DTB LINUX_DIR "pstv.dtb" 33 | 34 | typedef struct SceSysconResumeContext { 35 | unsigned int size; 36 | unsigned int unk; 37 | unsigned int buff_vaddr; 38 | unsigned int resume_func_vaddr; 39 | unsigned int SCTLR; 40 | unsigned int ACTLR; 41 | unsigned int CPACR; 42 | unsigned int TTBR0; 43 | unsigned int TTBR1; 44 | unsigned int TTBCR; 45 | unsigned int DACR; 46 | unsigned int PRRR; 47 | unsigned int NMRR; 48 | unsigned int VBAR; 49 | unsigned int CONTEXTIDR; 50 | unsigned int TPIDRURW; 51 | unsigned int TPIDRURO; 52 | unsigned int TPIDRPRW; 53 | unsigned int unk2[6]; 54 | unsigned long long time; 55 | } SceSysconResumeContext; 56 | 57 | extern void resume_function(void); 58 | 59 | static unsigned int *get_lvl1_page_table_va(void); 60 | static int find_paddr(uint32_t paddr, const void *vaddr_start, unsigned int range, void **found_vaddr); 61 | static int alloc_phycont(unsigned int size, unsigned int alignment, SceUID *uid, void **addr); 62 | static int load_file_phycont(const char *path, SceUID *uid, void **addr, unsigned int *size); 63 | static void uart0_print(const char *str); 64 | 65 | static tai_hook_ref_t SceSyscon_ksceSysconResetDevice_ref; 66 | static SceUID SceSyscon_ksceSysconResetDevice_hook_uid = -1; 67 | static tai_hook_ref_t SceSyscon_ksceSysconSendCommand_ref; 68 | static SceUID SceSyscon_ksceSysconSendCommand_hook_uid = -1; 69 | 70 | static tai_hook_ref_t SceLowio_kscePervasiveUartResetEnable_ref; 71 | static SceUID SceLowio_kscePervasiveUartResetEnable_hook_uid = -1; 72 | static tai_hook_ref_t SceLowio_ScePervasiveForDriver_81A155F1_ref; 73 | static SceUID SceLowio_ScePervasiveForDriver_81A155F1_hook_uid = -1; 74 | 75 | static SceSysconResumeContext resume_ctx; 76 | static uintptr_t resume_ctx_paddr; 77 | static unsigned int resume_ctx_buff[32]; 78 | /* 79 | * Global variables used by the resume function. 80 | */ 81 | uintptr_t linux_paddr; 82 | uintptr_t dtb_paddr; 83 | void *lvl1_pt_va; 84 | 85 | static void setup_payload(void) 86 | { 87 | memset(&resume_ctx, 0, sizeof(resume_ctx)); 88 | resume_ctx.size = sizeof(resume_ctx); 89 | resume_ctx.buff_vaddr = (unsigned int )resume_ctx_buff; 90 | resume_ctx.resume_func_vaddr = (unsigned int)&resume_function; 91 | asm volatile("mrc p15, 0, %0, c1, c0, 0\n\t" : "=r"(resume_ctx.SCTLR)); 92 | asm volatile("mrc p15, 0, %0, c1, c0, 1\n\t" : "=r"(resume_ctx.ACTLR)); 93 | asm volatile("mrc p15, 0, %0, c1, c0, 2\n\t" : "=r"(resume_ctx.CPACR)); 94 | asm volatile("mrc p15, 0, %0, c2, c0, 0\n\t" : "=r"(resume_ctx.TTBR0)); 95 | asm volatile("mrc p15, 0, %0, c2, c0, 1\n\t" : "=r"(resume_ctx.TTBR1)); 96 | asm volatile("mrc p15, 0, %0, c2, c0, 2\n\t" : "=r"(resume_ctx.TTBCR)); 97 | asm volatile("mrc p15, 0, %0, c3, c0, 0\n\t" : "=r"(resume_ctx.DACR)); 98 | asm volatile("mrc p15, 0, %0, c10, c2, 0\n\t" : "=r"(resume_ctx.PRRR)); 99 | asm volatile("mrc p15, 0, %0, c10, c2, 1\n\t" : "=r"(resume_ctx.NMRR)); 100 | asm volatile("mrc p15, 0, %0, c12, c0, 0\n\t" : "=r"(resume_ctx.VBAR)); 101 | asm volatile("mrc p15, 0, %0, c13, c0, 1\n\t" : "=r"(resume_ctx.CONTEXTIDR)); 102 | asm volatile("mrc p15, 0, %0, c13, c0, 2\n\t" : "=r"(resume_ctx.TPIDRURW)); 103 | asm volatile("mrc p15, 0, %0, c13, c0, 3\n\t" : "=r"(resume_ctx.TPIDRURO)); 104 | asm volatile("mrc p15, 0, %0, c13, c0, 4\n\t" : "=r"(resume_ctx.TPIDRPRW)); 105 | resume_ctx.time = ksceKernelGetSystemTimeWide(); 106 | 107 | ksceKernelCpuDcacheAndL2WritebackRange(&resume_ctx, sizeof(resume_ctx)); 108 | 109 | lvl1_pt_va = get_lvl1_page_table_va(); 110 | 111 | LOG("Level 1 page table virtual address: %p\n", lvl1_pt_va); 112 | } 113 | 114 | static int ksceSysconResetDevice_hook_func(int type, int mode) 115 | { 116 | LOG("ksceSysconResetDevice(0x%08X, 0x%08X)\n", type, mode); 117 | 118 | /* 119 | * The Vita OS thinks it's about to poweroff, but we will instead 120 | * setup the payload and trigger a soft reset. 121 | */ 122 | if (type == SCE_SYSCON_RESET_TYPE_POWEROFF) { 123 | setup_payload(); 124 | type = SCE_SYSCON_RESET_TYPE_SOFT_RESET; 125 | } 126 | 127 | LOG("Resetting the device!\n"); 128 | 129 | ksceKernelCpuDcacheWritebackInvalidateAll(); 130 | ksceKernelCpuIcacheInvalidateAll(); 131 | 132 | return TAI_CONTINUE(int, SceSyscon_ksceSysconResetDevice_ref, type, mode); 133 | } 134 | 135 | static int ksceSysconSendCommand_hook_func(int cmd, void *buffer, unsigned int size) 136 | { 137 | LOG("ksceSysconSendCommand(0x%08X, %p, 0x%08X)\n", cmd, buffer, size); 138 | 139 | /* 140 | * Change the resume context to ours. 141 | */ 142 | if (cmd == SCE_SYSCON_CMD_RESET_DEVICE && size == 4) 143 | buffer = &resume_ctx_paddr; 144 | 145 | return TAI_CONTINUE(int, SceSyscon_ksceSysconSendCommand_ref, cmd, buffer, size); 146 | } 147 | 148 | static int kscePervasiveUartResetEnable_hook_func(int uart_bus) 149 | { 150 | /* 151 | * We want to keep the UART enabled... 152 | */ 153 | return 0; 154 | } 155 | 156 | /* 157 | * Returns ScePervasiveMisc vaddr, ScePower uses it to disable the UART 158 | * by writing 0x80000000 to the word 0x20 bytes past the return value. 159 | */ 160 | static void *ScePervasiveForDriver_81A155F1_hook_func(void) 161 | { 162 | static unsigned int tmp[0x24 / 4]; 163 | LOG("ScePervasiveForDriver_81A155F1()\n"); 164 | return tmp; 165 | } 166 | 167 | int module_start(SceSize argc, const void *args) 168 | { 169 | int ret; 170 | char *dtb; 171 | SceUID linux_uid; 172 | SceUID dtb_uid; 173 | void *linux_vaddr; 174 | void *dtb_vaddr; 175 | unsigned int linux_size; 176 | unsigned int dtb_size; 177 | 178 | kscePervasiveUartClockEnable(0); 179 | kscePervasiveUartResetDisable(0); 180 | 181 | ksceUartInit(0); 182 | 183 | LOG("Linux loader by xerpi\n"); 184 | 185 | /* 186 | * Load the Linux files (kernel image and device tree blob). 187 | */ 188 | ret = load_file_phycont(ZIMAGE, &linux_uid, &linux_vaddr, &linux_size); 189 | if (ret < 0) { 190 | LOG("Error loading " ZIMAGE ": 0x%08X\n", ret); 191 | goto error_load_linux_image; 192 | } 193 | 194 | ksceKernelGetPaddr(linux_vaddr, &linux_paddr); 195 | 196 | LOG("Linux image: '%s'\n", ZIMAGE); 197 | LOG("Linux memory UID: 0x%08X\n", linux_uid); 198 | LOG("Linux load vaddr: 0x%08X\n", (unsigned int)linux_vaddr); 199 | LOG("Linux load paddr: 0x%08X\n", linux_paddr); 200 | LOG("Linux size: 0x%08X\n", linux_size); 201 | LOG("\n"); 202 | 203 | if (ksceSblAimgrIsVITA()) { 204 | dtb = VITA1000_DTB; 205 | } else if (0) { 206 | dtb = VITA2000_DTB; 207 | } else if (ksceSblAimgrIsDolce()) { 208 | dtb = PSTV_DTB; 209 | } else { 210 | LOG("Unsupported Vita model.\n"); 211 | goto error_load_dtb; 212 | } 213 | 214 | ret = load_file_phycont(dtb, &dtb_uid, &dtb_vaddr, &dtb_size); 215 | if (ret < 0) { 216 | LOG("Error loading %s: 0x%08X\n", dtb, ret); 217 | goto error_load_dtb; 218 | } 219 | 220 | ksceKernelGetPaddr(dtb_vaddr, &dtb_paddr); 221 | 222 | LOG("DTB: '%s'\n", dtb); 223 | LOG("DTB memory UID: 0x%08X\n", dtb_uid); 224 | LOG("DTB load vaddr: 0x%08X\n", (unsigned int)dtb_vaddr); 225 | LOG("DTB load paddr: 0x%08X\n", dtb_paddr); 226 | LOG("DTB size: 0x%08X\n", dtb_size); 227 | LOG("\n"); 228 | 229 | SceSyscon_ksceSysconResetDevice_hook_uid = taiHookFunctionExportForKernel(KERNEL_PID, 230 | &SceSyscon_ksceSysconResetDevice_ref, "SceSyscon", 0x60A35F64, 231 | 0x8A95D35C, ksceSysconResetDevice_hook_func); 232 | 233 | SceSyscon_ksceSysconSendCommand_hook_uid = taiHookFunctionExportForKernel(KERNEL_PID, 234 | &SceSyscon_ksceSysconSendCommand_ref, "SceSyscon", 0x60A35F64, 235 | 0xE26488B9, ksceSysconSendCommand_hook_func); 236 | 237 | SceLowio_kscePervasiveUartResetEnable_hook_uid = taiHookFunctionExportForKernel(KERNEL_PID, 238 | &SceLowio_kscePervasiveUartResetEnable_ref, "SceLowio", 0xE692C727, 239 | 0x788B6C61, kscePervasiveUartResetEnable_hook_func); 240 | 241 | SceLowio_ScePervasiveForDriver_81A155F1_hook_uid = taiHookFunctionExportForKernel(KERNEL_PID, 242 | &SceLowio_ScePervasiveForDriver_81A155F1_ref, "SceLowio", 0xE692C727, 243 | 0x81A155F1, ScePervasiveForDriver_81A155F1_hook_func); 244 | 245 | LOG("Hooks installed.\n"); 246 | 247 | ksceKernelGetPaddr(&resume_ctx, &resume_ctx_paddr); 248 | LOG("Resume context pa: 0x%08X\n", resume_ctx_paddr); 249 | 250 | LOG("Requesting standby...\n"); 251 | 252 | kscePowerRequestStandby(); 253 | 254 | return SCE_KERNEL_START_SUCCESS; 255 | 256 | error_load_dtb: 257 | ksceKernelFreeMemBlock(linux_uid); 258 | error_load_linux_image: 259 | return SCE_KERNEL_START_FAILED; 260 | } 261 | 262 | int module_stop(SceSize argc, const void *args) 263 | { 264 | /* 265 | * This is very important: it avoids the freeing 266 | * of the resources allocated by the module. 267 | */ 268 | return SCE_KERNEL_STOP_CANCEL; 269 | } 270 | 271 | unsigned int *get_lvl1_page_table_va(void) 272 | { 273 | uint32_t ttbcr; 274 | uint32_t ttbr0; 275 | uint32_t ttbcr_n; 276 | uint32_t lvl1_pt_pa; 277 | void *lvl1_pt_va; 278 | 279 | asm volatile( 280 | "mrc p15, 0, %0, c2, c0, 2\n\t" 281 | "mrc p15, 0, %1, c2, c0, 0\n\t" 282 | : "=r"(ttbcr), "=r"(ttbr0)); 283 | 284 | ttbcr_n = ttbcr & 7; 285 | lvl1_pt_pa = ttbr0 & ~((1 << (14 - ttbcr_n)) - 1); 286 | 287 | if (!find_paddr(lvl1_pt_pa, (void *)0, 0xFFFFFFFF, &lvl1_pt_va)) 288 | return NULL; 289 | 290 | return lvl1_pt_va; 291 | } 292 | 293 | int find_paddr(uint32_t paddr, const void *vaddr_start, unsigned int range, void **found_vaddr) 294 | { 295 | const unsigned int step = 0x1000; 296 | void *vaddr = (void *)vaddr_start; 297 | const void *vaddr_end = vaddr_start + range; 298 | 299 | for (; vaddr < vaddr_end; vaddr += step) { 300 | uintptr_t cur_paddr; 301 | 302 | if (ksceKernelGetPaddr(vaddr, &cur_paddr) < 0) 303 | continue; 304 | 305 | if ((cur_paddr & ~(step - 1)) == (paddr & ~(step - 1))) { 306 | if (found_vaddr) 307 | *found_vaddr = vaddr; 308 | return 1; 309 | } 310 | } 311 | 312 | return 0; 313 | } 314 | 315 | int alloc_phycont(unsigned int size, unsigned int alignment, SceUID *uid, void **addr) 316 | { 317 | int ret; 318 | SceUID mem_uid; 319 | void *mem_addr; 320 | 321 | SceKernelAllocMemBlockKernelOpt opt; 322 | memset(&opt, 0, sizeof(opt)); 323 | opt.size = sizeof(opt); 324 | opt.attr = SCE_KERNEL_ALLOC_MEMBLOCK_ATTR_PHYCONT | SCE_KERNEL_ALLOC_MEMBLOCK_ATTR_HAS_ALIGNMENT; 325 | opt.alignment = ALIGN(alignment, 0x1000); 326 | mem_uid = ksceKernelAllocMemBlock("phycont", 0x30808006, ALIGN(size, 0x1000), &opt); 327 | if (mem_uid < 0) 328 | return mem_uid; 329 | 330 | ret = ksceKernelGetMemBlockBase(mem_uid, &mem_addr); 331 | if (ret < 0) { 332 | ksceKernelFreeMemBlock(mem_uid); 333 | return ret; 334 | } 335 | 336 | if (uid) 337 | *uid = mem_uid; 338 | if (addr) 339 | *addr = mem_addr; 340 | 341 | return 0; 342 | } 343 | 344 | int load_file_phycont(const char *path, SceUID *uid, void **addr, unsigned int *size) 345 | { 346 | int ret; 347 | SceUID fd; 348 | SceUID mem_uid; 349 | void *mem_addr; 350 | unsigned int file_size; 351 | unsigned int aligned_size; 352 | 353 | fd = ksceIoOpen(path, SCE_O_RDONLY, 0); 354 | if (fd < 0) 355 | return fd; 356 | 357 | file_size = ksceIoLseek(fd, 0, SCE_SEEK_END); 358 | aligned_size = ALIGN(file_size, 4096); 359 | 360 | ret = alloc_phycont(aligned_size, 4096, &mem_uid, &mem_addr); 361 | if (ret < 0) { 362 | ksceIoClose(fd); 363 | return ret; 364 | } 365 | 366 | ksceIoLseek(fd, 0, SCE_SEEK_SET); 367 | ksceIoRead(fd, mem_addr, file_size); 368 | 369 | ksceKernelCpuDcacheAndL2WritebackRange(mem_addr, aligned_size); 370 | ksceKernelCpuIcacheInvalidateRange(mem_addr, aligned_size); 371 | 372 | ksceIoClose(fd); 373 | 374 | if (uid) 375 | *uid = mem_uid; 376 | if (addr) 377 | *addr = mem_addr; 378 | if (size) 379 | *size = file_size; 380 | 381 | return 0; 382 | } 383 | 384 | static void uart0_print(const char *str) 385 | { 386 | while (*str) { 387 | ksceUartWrite(0, *str); 388 | if (*str == '\n') 389 | ksceUartWrite(0, '\r'); 390 | str++; 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /resume.s: -------------------------------------------------------------------------------- 1 | .set BOOTSTRAP_PADDR, 0x1F000000 2 | 3 | .align 4 4 | .text 5 | .cpu cortex-a9 6 | 7 | .global resume_function 8 | .type resume_function, %function 9 | resume_function: 10 | dsb 11 | 12 | @ Disable FIQs, IRQs, imprecise aborts and enter SVC mode 13 | cpsid aif, #0x13 14 | 15 | @ Set CONTEXTIDR (Context ID Register) to zero. 16 | mov r3, #0 17 | mcr p15, 0, r3, c13, c0, 1 18 | isb 19 | 20 | ldr r0, =sync_point_1 21 | bl cpus_sync 22 | 23 | @ Get CPU ID 24 | mrc p15, 0, r0, c0, c0, 5 25 | and r0, #0xF 26 | cmp r0, #0 27 | bne cpu1_3_cont 28 | 29 | @ CPU0: Identity map the scratchpad using a 1MiB section 30 | ldr r2, =lvl1_pt_va 31 | ldr r2, [r2] 32 | add r2, #(BOOTSTRAP_PADDR >> 20) << 2 33 | ldr r3, =((BOOTSTRAP_PADDR >> 20) << 20) | 0x91402 34 | str r3, [r2] 35 | mcr p15, 0, r2, c7, c14, 1 @ DCCIMVAC (Data Cache line Clean and Invalidate by VA to PoC) 36 | dsb 37 | mcr p15, 0, r0, c8, c7, 0 @ TLBIALL (Unified TLB Invalidate All) 38 | dsb 39 | isb 40 | 41 | @ Copy the Linux bootstrap payload to the scratchpad 42 | ldr r0, =BOOTSTRAP_PADDR 43 | ldr r1, =_binary_linux_bootstrap_bin_start 44 | ldr r2, =_binary_linux_bootstrap_bin_size 45 | bl resume_memcpy 46 | 47 | ldr r0, =BOOTSTRAP_PADDR 48 | ldr r1, =_binary_linux_bootstrap_bin_size 49 | bl dcache_clean_range 50 | 51 | cpu1_3_cont: 52 | ldr r0, =sync_point_2 53 | bl cpus_sync 54 | 55 | @ TLBIALL (Unified TLB Invalidate All) 56 | mcr p15, 0, r0, c8, c7, 0 57 | dsb 58 | isb 59 | 60 | @ ICIALLU (Icache Invalidate All to PoU) 61 | mov r0, #0 62 | mcr p15, 0, r0, c7, c5, 0 63 | dsb 64 | 65 | @ Get Linux parameters 66 | ldr r0, =linux_paddr 67 | ldr r0, [r0] 68 | ldr r1, =dtb_paddr 69 | ldr r1, [r1] 70 | 71 | @ Jump to the payload! 72 | ldr lr, =BOOTSTRAP_PADDR 73 | bx lr 74 | 75 | @ r0 = sync point address 76 | @ Uses: r0, r1, r2 77 | cpus_sync: 78 | mrc p15, 0, r1, c0, c0, 5 79 | and r1, #0xF 80 | cmp r1, #0 81 | streq r1, [r0] 82 | 1: 83 | ldrb r2, [r0] 84 | cmp r1, r2 85 | wfene 86 | bne 1b 87 | ldrh r2, [r0] 88 | adds r2, #1 89 | adds r2, r2, #0x100 90 | strh r2, [r0] 91 | dsb 92 | sev 93 | 1: 94 | ldrb r2, [r0, #1] 95 | cmp r2, #4 96 | wfene 97 | bne 1b 98 | bx lr 99 | 100 | @ r0 = addr, r1 = size 101 | @ Uses: r0, r1 102 | dcache_clean_range: 103 | add r1, r0 104 | bic r0, #(32 - 1) 105 | dsb 106 | 1: 107 | mcr p15, 0, r0, c7, c10, 1 @ DCCMVAC (Data Cache line Clean by VA to PoC) 108 | add r0, #32 109 | cmp r0, r1 110 | blo 1b 111 | dsb 112 | bx lr 113 | 114 | @ r0 = dst, r1 = src, r2 = size 115 | @ Uses: r0, r1, r2, r3 116 | resume_memcpy: 117 | ldmia r1!, {r3} 118 | stmia r0!, {r3} 119 | subs r2, #4 120 | bne resume_memcpy 121 | bx lr 122 | 123 | .data 124 | sync_point_1: .word 0 125 | sync_point_2: .word 0 126 | --------------------------------------------------------------------------------