This document will serve as a repository of undocumented knowledge on the PSoC4 family of chips. I will update it as I learn more things. You really are unlikely to get much use of this without first acquainting yourself with my previous PSoC4 article. Revision history follows:
With this info you can use the chip to its full power, write flash in new ways, unlock extra storage, and have a fun time reverse engineering more things. Enjoy :)
# | Name | Permissions | Availability | Parameters | Notes | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CPU | Debugger | 4000 | 4100 / 4200 | 4100M / 4200M | 4000S | CYBL 10x6x | Param in reg | Struct | |||||||||
Virgin | Kill | Prot | Open | Virgin | Kill | Prot | Open | ||||||||||
0x00 | Get_Silicon_ID | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | documented | |
0x01 | Write_NVL_byte | ✔ | ✔ | [1] | ✔ | ✔ | [1] | ✔ | u16 key; u8 idx; u8 val; |
Write "val" into latch whose index is "idx". Survives reboots but not power cycles. Not sure why | |||||||
0x02 | Load_NV_latches_from_HW | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | [1] | ✔ | ✔ | [1] | ✔ | u16 key; | Loads NV latches from wherever they are stored in the chip. Overwrites anything written using Write_NVL_byte() | |||
0x03 | Read_NVL_byte | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | [1] | ✔ | ✔ | [1] | ✔ | u16 key; u8 idx; u8 val; |
Read latch whose index is "idx" into "val" | |||
0x04 | Load_Write_Latches | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u8 offset; u8 macro; u8 loadSzMinus1; u8 reserved[3]; u8 data[loadSzMinus1+1]; |
documented | |||
0x05 | Write_Row | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u16 rowID; |
documented write is only performed if write latches have any nonzero bytes |
|||
0x06 | Program_Row | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u16 rowID; |
documented write is only performed if write latches have any nonzero bytes flash is not checked for being blank |
|||
0x07 | Nonblocking_Write_Row | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u16 rowID; |
documented write is only performed if write latches have any nonzero bytes |
|||||
0x08 | Nonblocking_Program_Row | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u16 rowID; |
documented write is only performed if write latches have any nonzero bytes flash is not checked for being blank |
|||||
0x09 | Nonblocking_Resume | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | documented write is only performed if write latches have any nonzero bytes flash is not checked for being blank |
|||||
0x0a | Erase_All | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | documented | ||||||
0x0b | Checksum | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u16 row; |
any "row" >= 0x8000 checksums all code flash else any "row" >= 0x4000 checksums that row of sflash else given code flash row is checksummed |
|
0x0c | Flash_Analog_Read | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u8 level1; u8 flags; u32 addr; u8 levels[]; u8 data[]; |
If chip has only one flash macro, "level1" is used for level "levels" is 0-sized, else levels[] is a multiple of 4 and data is written after it. Flags: 0x20 - read single byte (else row), 0x10 - read sflash (else code flash), 0x01 - set "0" threshold, else "1". Internally calls Flash_Analog_Read_Mode_Enter() and Flash_Analog_Read_Mode_Exit() as needed. |
||||||
0x0d | Write_Protection | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u8 prot; u8 macro; |
undocumented option: write prot to 0xDE and macro to 0xFA to returnchip to virgin state | ||||
0x0e | Memset_Word | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u32 start; u32 end; u32 word |
a memset, but word-sized | |||
0x0f | Copy_Some_Code_to_Ram | ✔ | ✔ | ✔ 0x80 |
✔ 0xF0 |
✔ 0x110 |
✔ 0x80 |
✔ 0x110 |
u16 key; u32 word; |
If SPCIF_GEOMETRY.DE_CPD_LP is not set, set it and copy the following bytes [3] to ram + ofst. Besides that, write "word" to ram + offset + 0x24, and 0xAE to ram + offset + 0x20 | |||||||
0x10 | Flash_Rubber_To_Road | ✔ | ✔ | ✔ 0x108 |
✔ 0x148 |
✔ 0x168 |
✔ 0x108 |
✔ 0x168 |
/*no key*/ | Calls flash_rubber_to_road(*(u8)(ram + ofst + 0), *(u8)(ram + ofst + 1)) | |||||||
0x11 | Flash_Analog_Read_Mode_Enter | ✔ | ✔ | ✔ 0x10C |
✔ 0x14C |
✔ 0x16C |
✔ 0x10C |
✔ 0x16C |
/*no key*/ | Address you intend to read should be written to ram + ofst | |||||||
0x12 | Flash_Analog_Read_Mode_Exit | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | /*no key*/ | ||||||||
0x13 | Init_Chip | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | Verifies SFLASH checksum and sets some sane defaults in many registers | |||||
0x14 | Unknown | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | On all chips i've seen this simply returns error 0xf0000010 | ||||
0x15 | Config_Flash_Clock | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | [2] | ✔ | ✔ | ✔ | u16 key; | documented | ||
0x16 | Read_Clock_Regs | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u32 *dst; |
CLK_IMO_CONFIG -> dst[0]; CLK_SELECT -> dst[1], CLK_IMO_TRIM2 -> dst[2], CPUSS_FLASH_CTL -> dst[3] | ||||
0x17 | Clocks_Manual_Setup | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u32* cfg; |
cfg: {u32 v_CLK_IMO_CONFIG; u32 v_CLK_SELECT; u32 v_CLK_IMO_TRIM2; u32 v_CLK_IMO_CONFIG;}; Writes those regs, configures clock trims to use them as requested. Config_Flash_Clock() is just a special case of a call to this. | ||||
0x18 | Write_Sflash_Row | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; u32 rowNum; |
4 rows are available. They can be read at 0x0FFFF200. Always blocks and always performs an erase first. There is no nonblocking/nonerasing version. | |||||
0x19 | Unknown | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | still working on this | |||||||
0x1a | Unknown_And_Boring | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | Write 0x00000001 to 0x20000000 and to 0x20000004, set SYSARG to 0xA0000000 | *(u32*)0x200000c0 | ||||||||
0x1b | Chip_Reset | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | u16 key; | Literally just resets the chip |
PSoC4000/PSoC4100/PSoC4200: | PSoC4000S/PSoC4100M/PSoC4200M/CYBL10x6x: | |
---|---|---|
0xB570 - PUSH {R4-R6,LR}
0x4D06 - LDR R5, [PC, #0x18] //label_3
0x2001 - MOV R0, #0x01
0x686E - LDR R6, [R5, #0x04]
0x4306 - ORR R6, R0
0x2400 - MOV R4, #0x00
label_1:
0x6828 - LDR R0, [R5]
0x28AE - CMP R0, #0xAE
0xD101 - BNE label_2
0x47B0 - BLX R6
0x602C - STR R4, [R5]
label_2:
0xBF30 - WFI
0xE7F8 - B label_1
0x0000
label_3:
DCD ram + offset + 0x10
|
0x4D05 - LDR R5, [PC, #0x14] //label_3
0x2001 - MOV R0, #0x01
0x686C - LDR R4, [R5,#0x04]
0x4304 - ORR R4, R0
0x2600 - MOV R6, #0x00
label_1:
0x6828 - LDR R0, [R5]
0x28AE - CMP R0, #0xAE
0xD101 - BNE label_2
0x47A0 - BLX R4
0x602E - STR R6, [R5]
label_2:
0xBF30 - WFI
0xE7F8 - B label_1
label_3:
DCD ram + offset + 0x1C
DCD ram + offset + 0x20
|
Number | Documented | Meaning |
---|---|---|
0xf0000001 | ✔ | syscall not avail in current chip protection mode |
0xf0000002 | nvlatch index out of bounds | |
0xf0000003 | ✔ | flash latch offset or length out of bounds |
0xf0000004 | ✔ | rowID out of bounds |
0xf0000005 | ✔ | rowID refers to a protected row |
0xf0000006 | ??? I have not seen this one ??? | |
0xf0000007 | ✔ | no more resume (all background tasks are done) |
0xf0000008 | ✔ | resume pending (no api can be called until you call resume) |
0xf0000009 | ✔ | resume called too soon (background task ongoing) |
0xf000000a | ✔ | memory nonzero (likely flash erase failed) |
0xf000000b | ✔ | no such syscall |
0xf000000c | ✔ | syscall key mismatch |
0xf000000d | ??? I have not seen this one ??? | |
0xf000000e | ✔ | invalid address range (start > end) |
0xf000000f | nvlatches not supported on this device | |
0xf0000010 | ??? Not sure. Syscall number 0x14 on 4000S/4200M/BLE/4000 always returns this error | |
0xf0000011 | ??? I have not seen this one ??? | |
0xf0000012 | ✔ | flash clocking error. Forgot to call Config_Flash_Clock()? |
0xf0000013 | clock already up - returned by chips which do not need a call to Configure_Clock() | |
0xf0000014 | ??? In weird mode (returned from some api (0x0a, 0x0d, 0x15, 0x17, 0x18) when CPUSS_PROTECTION & 0x40000000) |
In SFLASH there are a few values that are of interest. A 24-bit Lot ID for the silicon wafer that this chip came from at 0x0FFFF178, The 8-bit wafer number in the lot that this chip came from at 0x0FFFF17B, and the 8-bit X and 8-bit Y coordinates on the wafer that this chip came from (row and colum numbers) at 0x0FFFF17C and 0x0FFFF17D respectively. Clearly in any given family this will be unique as no two chips could occupy the same physical location on the same wafer in the same lot. I am not sure if this is unique inter-family, so you should also, together which these values, use the Silicon ID (16-bit), Revision ID (8-bit), and the family ID (12-bit) values from the SiliconID syscall. Together these form a 24+8+8+8+16+8+12 = 84-bit unique chip ID. Keep in mind that while this will be unique, it is also highly predictable. For example, the 5 PSoC4000 chips I ordered from Mouser only differ in wafer X coordinate, and by exactly one. So you should not use this directly in a place where guessing it correctly would compromise safety. If in doubt, securely hash with salt to obtain a harder-to-guess uniqie ID. A quick-and-dirty function to get the raw unique ID might look like this:
If you read SPCIF_GEOMETRY in some members of the PSoC4 family, you will see it claiming 16K of flash, while the chip only has 8K (as per datasheet, cypress tools, and syscalls). In some members of the PSoC4200M family, you'll see the register claim twice the RAM amount the datasheet says you have. This is not a bug. Well, it kind of is and kind of is not. It is market segmentation at work. The values you see in SPCIF_GEOMETRY are the actual amounts of flash, SFLASH, RAM, and ROM that your chip has. If your datasheet claims less, the remainder is fully functional, but disabled for market segmentation resons. This is done using WOUNDING. With some effort, you can undo this and gain use of extra flash and RAM permanently.
Wounding is Cypress's method of market segmentation. It turns out that for small sizes of flash and RAM, developing a die with differing amounts and making them costs more than just giving each die the same amount (the larger). But this means you either lower margins or do not sell a low-end product. To avoid both, you can just hide some of the RAM and flash and claim the chip has less. Since using the same die for both is actually cheaper, this allows the company to capture the extra money from people who do want more than the lower amount of flash or RAM.
In PSoC4 chips, this is implemented using WOUNDING registers. These registers are write-once in a stange way. You can always increment them, but you cannot decrement them. Reset clears them. Thus, the higher the value in the WOUNDING register, the more of flash and RAM is hidden and inaccessible. There are, in fact, a few WOUNDING registers. The one for flash and RAM is CPUSS_WOUNDING, which exists at [CPUSS + 0x1C]. In here you'll find a 3-bit bitfield at bits 20..22 for flash limiting, and a 3-bit bitfield at bits 16..18 for RAM limiting. The value (N) you place in those bitfields determines how much ram and flash is left accessible. The formula is easy. Accesible = Total / (2 ^ N). RAM and flash limits may differ. If you try to read this register, you'll find that it is not readable to you. It is a privileged-only register. However, unless you changed it (unlikely as you cannot even read it), the boot process probably set the values for you. How does it know what to? SFLASH! At address 0x0FFFF140 you'll find a word whose contents will be copied to CPUSS_WOUNDING. How is the actual "inaccesibility" implemented? Same way that privileged-only registers are.
It is not only flash and RAM that can get artifically limited. The SAR ADC, in chips that have it, can also be artificially limited. The register for that is SAR_WOUNDING, and it is at [SAR + 0xF04]. privileged-only, of course. I have not seen any SROM code that writes this, but, then again, I have not read SROMs of all the members of the PSoC4 family.
Like most chips, PSoC4 has many undocumented secret registers. Some fascinating, many mundane. However, unlike most chips, PSoC4's secret registers are not easy to access. Cypress gated access to most using the HPROT[1] flag. The practical upshot of this is that to normal code running on the chip, it looks like these registers do not exist. This is accomplished by repling to the AHB transaction with HRESP[0] = 1 (aka: ERROR). The core understands this as accessing a nonexistent register and triggers a HardFault (or a LOCKUP if at NMI or HardFault priority). I refer to these as privileged-only registers, and the chip is full of them. Every peripheral on the chip has some. Older Cypress docs documented a few of them, like CPUSS_WOUNDING. Newer docs do not mention them at all. Many were never documented at all, for example the insides of the SPCIF module. I will document registers here as I reverse engineer them. Please, however, do keep in mind that these are only my guesses, substantiated only by disassembled SROM code. Any or all of this could be subtly incorrect, incomplete, inaccurate, and/or simply wildly wrong. That being said, I have successfully used most of these as I documented them. The section on privileged execution is a good start on experimenting with these.
Depending on the chip, few activities hapen. I will outline the generic stuff here, and provide specifics as I learn them.
AV pairs (address-value pairs) are values for various hardware registers that are initialized on boot from SFLASH. Since SFLASH contains values that are usually unique per chip and SROM contains others that are always the same, we can assume that all registers written to by AV pair data are different between different chips of the same family.
32-bit AV pairs (0x0FFFF100..0x0FFFF13F) are simple: they literally contain 32-bit address and then 32bit data. Address of 0xffffffff acts as terminator of this list
8-bit AV pairs (0x0FFFF080..0x0FFFF0FF) are complicated and encoded differently on each chip in the family. The only thing they have in common is that they always write 8-bit values to initialize 32-bit registers at 0x4000FF00 or later. Most of them write towards the end of each peripheral's address space (where various calibration registers are). What follows are rough decoders of the 8-bit AV pairs for the old format (4000/4100/4200) and new format 4000S/4100M/4200M. You can tell them apart by looking at the last byte before the zeroes begin. Here 0x37 means old format, 0xff means new. The code is written in this style because it is written to closely match assembly listing and not to be pretty.
Old format decoder | New format decoder |
---|---|
Params are "vals" (0x0ffff080) and "dataLen" (128) | |
uint32_t val, len, i, tmp = 0, strVal, strAddr;
uint8_t *p = vals, *nextAddr;
while (1) {
val = *p;
len = val & 7;
nextAddr = p + len;
if (nextAddr >= vals + dataLen)
break;
if (val == 0x37)
break;
for (i = 0; i < len; i++) {
strAddr = 0x4000FF00;
strVal = p[i + 1];
if (((tmp >> 8) & 0xF8) == (val & 0xF8)) {
tmp++;
strAddr += (tmp & 0xff) << 2;
}
else {
tmp = val << 8;
}
if (val & 0x08)
strAddr += 0x00100000;
strAddr += (val & 0xf0) << 12;
printf("0x%02X -> [0x%08X]\n", strVal, strAddr);
}
p = nextAddr + 2;
}
|
uint32_t val, len;
uint8_t *p = vals, *nextAddr;
while (1) {
uint32_t i, strVal, strAddr = 0x4000FF00;
val = p[0];
len = p[1];
nextAddr = p + len;
if (nextAddr >= vals + dataLen)
break;
if (val == 0xff)
break;
strAddr += (val & 0xE0) << 15;
strAddr += (val & 0x1F) << 16;
for (i = 0; i < len; i++) {
strVal = p[i + 2];
printf("0x%02X -> [0x%08X]\n", strVal, strAddr + i * 4);
}
p = nextAddr + 2;
}
|
I looked into this. It seems like it should. It sort of works. If you clock it, it takes a byte into the shift register even (as per TX_FIFO_STATUS). But that is as far as it goes. I checked the SROM in case there is a register that cripples (WOUNDS?) the SCB, but I found nothing that looks like it. Sorry.
Protected mode is meant to protect the code in the chip form readout. As you saw in the syscall table, most API are not available in protected mode. Also, most memory is off-limits to the debugger. What is of interest is exactly what memory is and is not accessible. This is not documented and I see no ways to tweak this. I did a full scan (over about a week) of the address space on the PSoC4000S. The accessible areas are CPUSS, TST, and the top level rom table. Here are the values:
I've automated this to the exteme in CortexProg (look for it out soon). At this point it is a two-step process. First step automatically finds an exploitable load in the first part of the SROM and uses it to dump said first half. Second step needs an offset of an exploitable gadget in the first half to use to dump the rest. Human intervention is needed once per chip family to find a gadget. This offset is fed to CortexProg to use to dump the rest and enable the "Read As Provileged" function on PSoC to allow access to privileged-only registers for further investigations. What does it look like? Step 1 looks like this.
Step 2 is a bit more work and looks like this
Keep in mind that normally execution in privileged mode is rather limited in a number of ways. To start with, I have not yet found a way to execute in Thread mode as privileged generically on all devices. Currently there needs to be a per-device exploit. The CPU does boot in that way, but I have yet to find a way to do this on demand easily. I did find specific per-family exploits for some devices. See below. For devices without these fancy nice exploits, there is a way to run in NMI-mode privileged mode. What that means is that you're running at NMI priority. What does this cause? Effectively interrupts are disabled while you're running privileged code, since you're executing at a higher priority. But that's not all. You're also at higher priority than HardFault, so if you mess up, the CPU enters LOCKUP state. Your hard fault handler does not run either. And if that is not enough, debugging is also inhibited while in privileged mode, so do not expect your debugger's help. It seems to me that while in privileged mode, the debugger limitations are similar to those that are applied when chip is in protected mode. That means that you can only access very limited things, none of them useful. All of this combined makes execution in privileged mode a rather large pain. But sometimes, it is worth it :).
But there are specific exploits you can use! So, here are the gems you came for. This code will allow you to enter privileged mode on some of the PSoC4 family chips. Not only that, you will not be in NMI mode, so interupts will work and HardFault handlers will work if you mess up! Keep in mind that you still get no debugging! Your debugger (whatever it is) will either freak out or give up as soon as you enter privileged mode. This is by design and cannot be avoided as far as I can tell. This exploit was quite a difficult thing to achieve, but here it is! To enter privileged mode, just call this function. Upon its return you'll be privileged. To exit privileged mode, just write 0 to CPUSS->SYSREQ. This will not work on any other PSoC4 chip family (do not bother trying), and might not even work on other SROM revisions (YMMV). I do not currently have such a simple exploit for other chip families - there I am still stuck doing privileged experimentation in NMI mode. I will try to generalize this exploit as much as I can: to other SROM revisions (if they exist) and other chip families. Stay tuned. Since NMI-mode privileged world is a much harsher world to live in, I will not publish the NMI exploits for those chips until I find one as convenient as this one for those chips. As with all other code here, this is licensed as: free to use for non-commercial use only. No commercial use without my prior approval. Allrighty then, here are the magic bytes:
4000 | 4100/4200 | 4000S | 4100M/4200M | CYBL10x6x |
---|---|---|---|---|
.globl privilegedEnter
privilegedEnter:
push {r4, lr}
mov r4, sp
lsr r0, r4, #3
bcc 1f
push {r0}
1:
mov r0, #0xE1
lsl r0, #8
add r0, #0xB6
mov r1, sp
sub r1, #0x44
mov r2, r1
mov r3, #0x10
lsl r3, #16
add r3, #0x0D
lsl r3, #8
add r3, #0x2F
push {r0-r3}
mov r1, sp
mov r2, #0x40
lsl r2, #8
add r2, #0x10
lsl r2, #16
mov r0, #0x80
lsl r0, #24
add r0, #0x0e
str r1, [r2, #8]
dsb
str r0, [r2, #4]
dsb
mov sp, r4
pop {r4, pc}
|
.globl privilegedEnter
privilegedEnter:
push {r4, lr}
mov r4, sp
lsr r0, r4, #3
bcc 1f
push {r0}
1:
mov r0, #0xE1
lsl r0, #8
add r0, #0xB6
mov r1, sp
sub r1, #0x44
mov r2, r1
mov r3, #0x10
lsl r3, #16
add r3, #0x0D
lsl r3, #8
add r3, #0x47
push {r0-r3}
mov r1, sp
mov r2, #0x40
lsl r2, #24
mov r0, #0x80
lsl r0, #24
add r0, #0x0e
str r1, [r2, #8]
dsb
str r0, [r2, #4]
dsb
mov sp, r4
pop {r4, pc}
|
.globl privilegedEnter
privilegedEnter:
push {r4, lr}
mov r4, sp
lsr r0, r4, #3
bcc 1f
push {r0}
1:
mov r0, #0xE1
lsl r0, #8
add r0, #0xB6
mov r1, sp
sub r1, #0x44
mov r2, r1
mov r3, #0x10
lsl r3, #16
add r3, #0x13
lsl r3, #8
add r3, #0xCD
push {r0-r3}
mov r1, sp
mov r2, #0x40
lsl r2, #8
add r2, #0x10
lsl r2, #16
mov r0, #0x80
lsl r0, #24
add r0, #0x0e
str r1, [r2, #8]
dsb
str r0, [r2, #4]
dsb
mov sp, r4
pop {r4, pc}
|
.globl privilegedEnter
privilegedEnter:
push {r4, lr}
mov r4, sp
lsr r0, r4, #3
bcc 1f
push {r0}
1:
mov r0, #0xE1
lsl r0, #8
add r0, #0xB6
mov r1, sp
sub r1, #0x64
mov r2, r1
mov r3, #0x10
lsl r3, #16
add r3, #0x0E
lsl r3, #8
add r3, #0x3D
push {r0-r3}
mov r1, sp
mov r2, #0x40
lsl r2, #8
add r2, #0x10
lsl r2, #16
mov r0, #0x80
lsl r0, #24
add r0, #0x0e
str r1, [r2, #8]
push {r2-r7}
dsb
str r0, [r2, #4]
dsb
pop {r2-r7}
mov sp, r4
pop {r4, pc}
|
.globl privilegedEnter
privilegedEnter:
push {r4, lr}
mov r4, sp
lsr r0, r4, #3
bcc 1f
push {r0}
1:
mov r0, #0xE1
lsl r0, #8
add r0, #0xB6
mov r1, sp
sub r1, #0x44
mov r2, r1
mov r3, #0x10
lsl r3, #16
add r3, #0x10
lsl r3, #8
add r3, #0x51
push {r0-r3}
mov r1, sp
mov r2, #0x40
lsl r2, #8
add r2, #0x10
lsl r2, #16
mov r0, #0x80
lsl r0, #24
add r0, #0x0e
str r1, [r2, #8]
dsb
str r0, [r2, #4]
dsb
mov sp, r4
pop {r4, pc}
|