The OMAP5912 boot process

OMAP series SOCs from Texas Instruments
Post Reply
Posts: 584
Joined: 08 Feb 2019, 12:25

The OMAP5912 boot process

Post by Go4IT » 04 Mar 2019, 06:09

Here i want to describe my results jn research on how the bootstrap works for the OMAP5912.

The OMAP has several boot modes, which can be choosen by the status of some IO pins on poweron. It also provides an internal boot ROM comes with it. As you might expect there are many ways to boot from external ressources, like Flash, UART, USB, SD-Card, ...

Posts: 584
Joined: 08 Feb 2019, 12:25

Re: The OMAP5912 boot process

Post by Go4IT » 06 Mar 2019, 16:39

I'd like to describe my findings on the NX board in correlation to the TI datasheets.

The board uses two external crystal oscillators for driving the OMAP, one 32,768 kHz at OSC32K_IN and another one with 12 MHz going to OSC1_IN (Just as a side note: this is why the maximum speed of the JTAG interface is 12 MHz also). Because of this external setup i guess the "reset mode" is defined to be '0'. From the docs "Reset mode is based on the value of the RESET_MODE pin (sampled on the rising edge of PWRON_RESET )." This pin could be ball P22 of the chip, which is tied to GND via a 10k resistor.

The processor has up to three external reset pins (inputs):
1.) /PWRON_RESET is the cold reset for the entire chip
2.) /MPU_RST is the MPU subsystem warm reset
3.) RTC_ON_NOFF is a software controlled, power-on reset

And an reset signal output called /RST_OUT. This pin seems to control some external ressources of the OMAP on the board like the Flash! It is also available at this measure test point (MP SYS_RESET):
:!: I found this signal going low in the moment the Flash write aborts (20s). Because it is generated by the OMAP itself (output and source of signal) and the reset input signals POR_RESET, MPU_RESET keeps stable, i'm pretty shure the OMAP is doing an internal reset (warm restart), generated by a watchdog.

A warm reset can also be executed by the 32-kHz watchdog time-out, which is by default 32s or by the MPU watchdog, DSP watchdog as well, as per software (RST instruction).
You do not have the required permissions to view the files attached to this post.

Posts: 584
Joined: 08 Feb 2019, 12:25

Re: The OMAP5912 boot process

Post by Go4IT » 07 Mar 2019, 17:29

Now, regarding to the docs the OMAP uses the addresses from 0xFFFE 0000 onwards to gain read and/or write access to various registers of the chips components (ARM, DSP, ...).:As of the architecture of the system, each register is 32 Bit in size.

There we find a register called CONF_STATUS at address 0xFFFE 1130. This is read-only and the bits (indexed by Bit 31 down to Bit 0) give us these informations:

Bits 31:6 are UNUSED

Bits 5:4 are CONF_DEVICE_TYPE_R and contains the status of the eFuses that determine the type of device:
00 = Production (normal) device
01 = Bad device
10 = Emulator device
11 = Test device

Bit 3 is the CONF_STATUS and contains the status of GPIO1 input pin. Value is latched on the rising edge of /PWRON_RESET. For internal boot, software configures the external chip-select after reading this bit. For external boot, the external chip-select configuration is forced after reading this bit.
0 means "Nonaddress/data multiplexed"
1 means "Address/data multiplexed"

Bit 2, the CONF_EMIFS_MUX_STAT_R, contains information about EMIFS protocol at boot.
0: Nonaddress/data multiplexed
1: Address/data multiplexed
Reset value for this signal depends on GPIO1, MPU_BOOT, and EFUSE_DEVICE_TYPE signals.

Bit 1 is CONF_ARM_BOOT_STAT_R. This register contains boot mode active chip-select (CONF_ARM BOOT MODE),latched at the rising edge of /PWRON_RESET. (Emulator type devices only)
0: MPU boots from internal ROM
1: MPU boots from external memory :!: hope this is what we get...
Reset value for this signal depends on MPU_BOOT and EFUSE_DEVICE_TYPE signals.

This register contains the status of the RESET_MODE pin latched at the rising edge of the reset pin /PWRON_RESET.
0: Reset mode 0
1: Reset mode 1

The actual reset value for all bits in this register depends on the deviceconfiguration at power-up reset. The values from the Reset column are notapplicable.The boot ROM code uses CONF_STATUS register bits to determine the execution path during boot.

Now, let's play! :)

I use Segger J-Link to connect to the OMAP. It presents a commandline interface after start. Use 'connect' command to establish a JTAG session with the chip:
As you see, it will show us a freeze-frame of the last CPU register values, which is quite nice. Browse through it and you will also find the PC pointing to the program address after the one executed at the moment of the register snapshot. Beware the CPU is not halted now, so the values have changed since you look at them ;)

Now, i want to know the value of the above explained status register at 0xFFFE 1130. The command to read a word (which is in case of the ARM CPU 32 bit long, meaning 4 Bytes) is 'mem32 <addr>, <count>'. So i issue 'mem32 fffe1139, q' and get:
You do not have the required permissions to view the files attached to this post.

Posts: 584
Joined: 08 Feb 2019, 12:25

Re: The OMAP5912 boot process

Post by Go4IT » 19 Aug 2019, 19:23

Now, that we managed to get the ROM code of the OMAP5948 we can start disassembling it and try to understand what is does. Maybe this lead us to some backdoors we can use (e.g. boot from external devices, USB, HDD, SD-Card, CF-Card, or whatever ;-)

I've attached the image and an raw IDA disasm of it. If you are using IDA Pro, load the image, set the CPU-Type to "ARM" and in the Processor-Options choose "ARMv5TEJ" as architecture (little endian) and here you go!

Like every other ARM-CPU the one in OMAP will start execution at address 0x00000000 after reset. At this address we usually find the "reset vector table", containing only jump instructions, used on certain conditions of the CPU. On is "RESET" and it will set PC to 0x00000000. Another one is "Undefined Instruction" which will put PC to 0x00000004, and so on. There is a table you can find on the internet.

Well, after RESET the code at 0x00000000 jumps to 0x00001638, containing the first lines of code:

Code: Select all

ROM:00001638 loc_1638                                ; CODE XREF: ROM:00000000↑j
ROM:00001638                 LDR     R0, =0xFFFECE14
ROM:0000163C                 LDR     R1, =1
ROM:00001640                 LDRH    R2, [R0]
ROM:00001644                 ORR     R1, R1, R2
ROM:00001648                 STRH    R1, [R0]
ROM:0000164C                 LDR     R0, =0xFFFECE24
ROM:00001650                 LDR     R1, =0x15
ROM:00001654                 LDRH    R2, [R0]
ROM:00001658                 ORR     R1, R1, R2
ROM:0000165C                 STRH    R1, [R0]
ROM:00001660                 LDR     R0, =0xFFFEC808
ROM:00001664                 LDR     R1, =0xF500F5
ROM:00001668                 STRH    R1, [R0]
ROM:0000166C                 LDR     R1, =0xA000A0
ROM:00001670                 STRH    R1, [R0]
ROM:00001674                 ADR     R0, (loc_167C+1)
ROM:00001678                 BX      R0              ; loc_167C
I try to go deep into this now, and also to explain how IDA (or other Disassemblers) work, so you can read and understand whats going on. The first instruction (called "Mnemonic") here is "LDR", one of the most common found in all ARM architectures. It is used to load a 4 Byte (1 Word) value from memory into a register. The target register is given first, here "R0" (R0 is one of 16 general purpose 32 Bit registers), the source memory address next. But wait! normally this should be written as "LDR R0, [Rx]" (where Rx is another register, like R1 or so). The brackets denotes that the current value of Rx is used to indicate the memory location where the word is to be loaded.

But why the hell IDA writes "=0xFFFECE14" here instead? Well, the answer is easy, this is not a valid ARM instruction, it is a "pseudo instruction" comming from the Disassembler. It is normally used to put a "label" here, like "=myvar" when writing Assembler. The compiler will translate this to "LDR R0, [R15, #CC]" which means "Load the word found at address calculated by current content of R15 (PC) with offset 0xCC added and put that into R0".

Here is what's working behind the scenes:
When we enable IDA to show the opcodes of each disassembled instruction (Options -> General -> Disassembly -> Number of opcode-bytes = 4) the instruction looks like this:

Code: Select all

ROM:00001638  CC 00 9F E5                 LDR     R0, =0xFFFECE14
So "LDR R0, =0xFFFECE14" is made up of the four bytes in opcode "CC 00 9F E5". As we use Little-Endian, this is the binary representation of the opcode (swapped to E59F00CC):

Code: Select all

1110 0101 1 0 0 1 1111 0000 000011001100
The leftmost 4 bits are the "condition" Bits. They tell under which condition the command is to be executed. Here "1110" means "always execute" (condition = "AL").
The next 4 Bits "0101" tells that this is an "Load or Store unsigned word with immediate offset" ARM instruction.
The following Bit "1" tells that the offset is added to the base register.
The next Bit "0" then tells it is a "word" operation.
The next Bit "0" is fixed.
The following Bit "1" tells it is a LDR operation.
Then 4 bits with value "1111" giving the base register to calculate the memory location. "1111" equals to R15 (used as PC). NOTE: PC always points 8 bytes ahead of the start of the current instruction. So the base is current instruction memory location + 8
After that another 4 Bits follows "0000" telling the destination register of the value loaded from address. Here it is R0 then.
The last 12 bits are the offset, added to the base address. Here the offset is 0xCC.

Now a little math: Current instruction (PC) ist at 0x1638, plus 8 = 0x1640, plus 0xCC = 0x170C. When we go to that memory location we find these bytes "14 CE FE FF". Note the Little-Endianes, so it's "FF FE CE 14"... Tadaa, this is what we seen in the LDR-instruction above. In IDA at this location it looks like:

Code: Select all

ROM:0000170C 14 CE FE FF off_170C        DCD 0xFFFECE14          ; DATA XREF: ROM:loc_1638↑r
So the register is not loaded with a constant value of 0xFFFECE14 directly, but loads it from an memory location. This is because ARM can only use memory locations to store and read values and each ARM instruction is exactly 4 bytes (1 word) long. But IDA knows how ARM is doing this, reads the value at the given memory location an name the label like the value to aid us.
In the comment on the location 0x170C you see "DATA XREF: ROM:loc_1638↑r". IDA points us to the instruction using this value, here "loc_1638". This is called "DATA_XREF" in IDA. The arrow shows that the code which is using this memory location is before in address space (lower) and the small "r" denotes that this location is "referenced to".

Cool, isn't it?
But what does the OMAP do with the address? Let's view it with the next lines of code (i've added line numbers to refer easily):

Code: Select all

1. ROM:00001638                 LDR     R0, =0xFFFECE14
2. ROM:0000163C                 LDR     R1, =1
3. ROM:00001640                 LDRH    R2, [R0]
4. ROM:00001644                 ORR     R1, R1, R2
5. ROM:00001648                 STRH    R1, [R0]
In the 1. line it loads register R0 with the value 0xFFFECE14
In the 2. line, register R1 is loaded with 0x00000001
The 3. line loads the content at memory location R0 (0xFFFECE14) into R2. Here "LDRH" is only handling 16 Bit ("H" stands for "Half Word"), not 32 Bit as usual. So Only the Byte from 0xFFFE CE14 and 0xFFFE CE15 are loaded and put in the lower 16 Bits of register R2.
The 4. instruction do an bitwise OR of the contents of R1 and R2 and put the result back into R1. It's purpose is to ensure that Bit 0 of the 16 Bit value read from memory is set to "1". We will se later what this means for the periphal, addressed by this location.
In the 5. and last instruction the lower half-word of the manipulated value in R1 is stored back at memory location R0.

What is the meaning of this all?
When we look into the datasheet of the OMAP5912 (which is nearly the same as the OMAP5948), we found that this address is used by the "Peripherals Reset Register (ARM_RSTCT2)". After RESET this register has a value of 0x0000 0000 and it is a 32 Bit register. But the datasheet tell us that "Bits 1-31 are RESERVED - Reading these bits gives undefined values. Writing to them has no effect", so we don't care. But Bit 0 is "PER_EN - MPU Peripheral reset. Resets and/or enables the external peripherals connected to MPU TIPB (controls 3.2 ARMPER_nRST). Value of 0 Resets MPU peripherals, Value of 1 Enables MPU peripherals"
Remember what the code above did? It loads the current value of this register and set Bit 0 to "1" and wrote it back. So in fact it enables the MPU periphals.

Now that we know what this small code block is doing, we find some more in the first part of the init routine of the OMAPs ROM. The next in line is (i will comment in code):

Code: Select all

ROM:0000164C                 LDR     R0, =0xFFFECE24  ; Load R0 with location address of "MPU Idle Enable Control Register 3 (ARM_IDLECT3)"
ROM:00001650                 LDR     R1, =0x15             ; Load R1 with value 0b0000 0000 0001 0101
ROM:00001654                 LDRH    R2, [R0]              ; Load current value of register ARM_IDLECT3 into R2 (after reset it is: 0b0000 0000 0001 0101)
ROM:00001658                 ORR     R1, R1, R2           ; Add "1" Bits to current value (which makes no difference to the default reset value here)
ROM:0000165C                 STRH    R1, [R0]             ; Set new value into ARM_IDLECT3
What it means at all is:
  • Bit 0 = "1" = The L3 OCPI clock is active.
  • Bit 1 = "0" = The L3 OCP-I clock remains active when the MPU enters the idle mode
  • Bit 2 = "1" = The TC1_CK clock is active
  • Bit 3 = "0" = The TC1 clock remains active when the MPU enters the idle mode (ARM_CK stopped)
  • Bit 4 = "1" = The TC2_CK clock is active
  • Bit 5 = "0" = The TC2 clock remains active when the MPU enters the idle mode (ARM_CK stopped)
  • The Bits 6..31 have not effect
Currently i have no clue what those clocks are used for :-)

Let's take a look at the next block:

Code: Select all

ROM:00001660                 LDR     R0, =0xFFFEC808  ; Load R0 with address of "OMAP Watchdog TIMER_MODE Register"
ROM:00001664                 LDR     R1, =0xF500F5     ; Load R1 with value 0x00F5 00F5
ROM:00001668                 STRH    R1, [R0]             ; Set OMAP register with value
ROM:0000166C                 LDR     R1, =0xA000A0    ; Load R1 with other value of 0x00A0 00A0
ROM:00001670                 STRH    R1, [R0]             ; Again set new value
Oh, this looks very familar to me! ;-) It is the reprogram method to disable the watchdog timers of the OMAP. The datasheet tells us: "Disable the MPU watchdog timer by writing the sequence F5, A0 in the TIMER_MODE register:
  • Command WR32 0xFFFE C808 0x0000 00F5
  • Command WR32 0xFFFE C808 0x0000 00A0
Remeber that? I also use this to disable the OMAP's watchdog for JTAG programming the Satnav device :-)
Loading R1 with 0x00F500F5 and then use STRH, which only stores the lower half-word of the value seems senseless to me. Loading a value of 0x00F5 would be sufficient, but so what...

Now head to the last part of this routine:

Code: Select all

ROM:00001674 01 00 8F E2                 ADR     R0, (loc_167C+1)
ROM:00001678 10 FF 2F E1                 BX      R0              ; loc_167C
ROM:0000167C             ; ---------------------------------------------------------------------------
ROM:0000167C                             CODE16
ROM:0000167C             loc_167C                                ; CODE XREF: ROM:00001678↑j
ROM:0000167C                                                     ; DATA XREF: ROM:00001674↑o
ROM:0000167C 1D 48                       LDR     R0, =0x20000030
ROM:0000167E 85 46                       MOV     SP, R0
But what is this? The ADR instruction is used to set register R0 with an memory location the subsequent branch instruction "BX R0" uses to jump to. Why not using "LDR" like above? Well, near jumps whith addresses in a range of 1000 bytes can be done as relative jumps inside a single ARM instruction, without loading the address from an variable in memory. This is why ADR is more efficient here thant LDR. As you see the destination location is just a byte behind the jump instruction. But, does this make sense anyhow? The answer is YES, but you need to now something about ARM architectures to understand it.

Some ARM CPUs support an alternative instruction set called "THUMB". As a rule of thumb (haha) you can tell that every architecture having a "T" in its name supports this mode, as in "ARMv5TJ" which is the microarchitecture of the ARM family member ARM926EJS which is the MPU in OMAP. Thumb instructions are only 2 Bytes long but much more limited. It is possible to switch between those instruction sets in a running program. IDA hints us of this change by placing a "CODE16" in front of the first Thumb instruction, or "CODE32" for ARM instruction. So in ARM the PC will be only valid on even addresses divideable by 4. To switch from ARM to Thumb it is common to use the BX instruction. In fact it jumps to a location given in the register provided (here R0) but also change the instruction set, depending on whether or not the Bit 0 of the destination address is set. If it is set, the CPU will use Thumb instructions from that address on, if not it will continue using ARM. Therefore the ADR instruction is loaded with the loc_167C plus 1 (just forgett about the parenthesis, they are used by the assembler only) The current instruction set is reflected in the "T"-Bit of the CPSR (current program status register). You can also see the switch by the number of opcode bytes used per instruction changes from 4 to 2.
So what those first two instructions does is simply changing from ARM to Thumb. Simple, eh? ;-)

What i'm going to do next is to find a way to change the automatically given label "=0xFFFECE14" of IDA to a more meaningfull name like the register name of OMAP "OMAP_ARM_RSTCT2" to find description more easily.
You do not have the required permissions to view the files attached to this post.

Posts: 584
Joined: 08 Feb 2019, 12:25

Re: The OMAP5912 boot process

Post by Go4IT » 20 Aug 2019, 11:18

Now we go on with the next Thumb code block:

Code: Select all

ROM:0000167C             loc_167C                                ; CODE XREF: ROM:00001678↑j
ROM:0000167C                                                     ; DATA XREF: ROM:00001674↑o
ROM:0000167C 1D 48                       LDR     R0, =0x20000030
ROM:0000167E 85 46                       MOV     SP, R0
ROM:00001680 1D 48                       LDR     R0, =0x400
ROM:00001682 85 44                       ADD     SP, R0
ROM:00001684 1D 48                       LDR     R0, =0x1F58
ROM:00001686 01 27                       MOVS    R7, #1
ROM:00001688 F8 42                       CMN     R0, R7
ROM:0000168A 01 D0                       BEQ     loc_1690
ROM:0000168C 00 F0 0F F8                 BL      sub_16AE
ROM:00001690             loc_1690                                ; CODE XREF: ROM:0000168A↑j
ROM:00001690 1B 4D                       LDR     R5, =0xFFFFFFFF
ROM:00001692 FD 42                       CMN     R5, R7
ROM:00001694 05 D0                       BEQ     loc_16A2
ROM:00001696 01 E0                       B       loc_169C
ROM:00001698             ; ---------------------------------------------------------------------------
ROM:00001698             loc_1698                                ; CODE XREF: ROM:000016A0↓j
ROM:00001698 00 F0 3E F9                 BL      sub_1918
ROM:0000169C             loc_169C                                ; CODE XREF: ROM:00001696↑j
ROM:0000169C 10 CD                       LDMIA   R5!, {R4}
ROM:0000169E 00 2C                       CMP     R4, #0
ROM:000016A0 FA D1                       BNE     loc_1698
ROM:000016A2             loc_16A2                                ; CODE XREF: ROM:00001694↑j
ROM:000016A2 FF F7 3F FE                 BL      sub_1324
ROM:000016A6 01 20                       MOVS    R0, #1
ROM:000016A8 00 F0 D4 F8                 BL      sub_1854
ROM:000016AC             ; ---------------------------------------------------------------------------
ROM:000016AC             loc_16AC                                ; CODE XREF: ROM:loc_16AC↓j
ROM:000016AC FE E7                       B       loc_16AC
Whats going on here?

Code: Select all

ROM:0000167C 1D 48                       LDR     R0, =0x20000030
ROM:0000167E 85 46                       MOV     SP, R0
The "MOV" instruction copies (other than the name maybe suggest) the value of register R0 into SP. The SP register (stack pointer) is loaded with the value 0x2000 0030. As we know from our OMAP memory map, the address range from 0x2000 0000 is used by the internal SRAM.

Post Reply