Services and Modeling for Embedded Software Development
Embecosm divider strip
Prev  Next

5.2.2.  The _start Function and Stack Initialization

The OpenRISC 1000  ABI uses a falling stack. The linker will place code and static data at the bottom of memory (starting with the exception vectors). The heap then starts immediately after this, while the stack grows down from the end of memory.

The linker will supply the address for the start of heap (it is in the global variable end). However we must find the stack location by trying to write to memory above the heap to determine the end of memory. Rather than write to every location, the code assumes memory is a multiple of 64KB, and tries writing to the last word of each 64KB block above end until the value read back fails.

This failure will trigger a bus error exception, which must be handled (see Section 5.2.1). The address used for the start of the stack (which is also the last word of memory) is stored in a global location, _stack (which C will recognize as stack).

        .section .data
        .global _stack
_stack: .space  4,0

_start is declared so it looks like a C function. GDB knows that _start is special, and this will ensure that backtraces do not wind back further than main. It is located in ordinary text space, so will be placed with other code by the linker/loader.

        .section .text
        .global _start
        .type   _start,@function

The first memory location to test is found by rounding the end location down to a multiple of 64KB, then taking the last word of the 64KB above that. 0xaaaaaaaa is used as the test word to write to memory and read back.

        l.movhi r30,hi(end)
        l.ori   r30,r30,lo(end)
        l.srli  r30,r30,16              /* Round down to 64KB boundary */
        l.slli  r30,r30,16

        l.addi  r28,r0,1                /* Constant 64KB in register */
        l.slli  r28,r28,16

        l.add   r30,r30,r28
        l.addi  r30,r30,-4              /* SP one word inside next 64KB? */

        l.movhi r26,0xaaaa              /* Test pattern to store in memory */
        l.ori   r26,r26,0xaaaa

Each 64KB block is tested by writing the test value and reading back to see if it matches.

        l.sw    0(r30),r26
        l.lwz   r24,0(r30)
        l.sfeq  r24,r26
        l.bnf   .L4
        l.j     .L3
        l.add   r30,r30,r28             /* Try 64KB higher */


The previous value is then the location to use for end of stack, and should be stored in the _stack location.

        l.sub   r30,r30,r28             /* Previous value was wanted */
        l.movhi r26,hi(_stack)
        l.ori   r26,r26,lo(_stack)
        l.sw    0(r26),r30

The stack pointer (r1) and frame pointer (r2) can be initialized with this value.

        l.add   r1,r30,r0
        l.add   r2,r30,r0

Having determined the end of memory, there is no need to handle bus errors silently. The words of code between _buserr and _buserr_std can be replaced by l.nop.

        l.movhi r30,hi(_buserr)
        l.ori   r30,r30,lo(_buserr)
        l.movhi r28,hi(_buserr_std)
        l.ori   r28,r28,lo(_buserr_std)
        l.movhi r26,0x1500              /* l.nop 0 */
        l.ori   r26,r26,0x0000

        l.sfeq  r28,r30    .L6

        l.sw    0(r30),r26              /* Patch the instruction */
        l.j     .L5
        l.addi  r30,r30,4               /* Next instruction */


It is essential that this code is before any data or instruction cache is initialized. Otherwise more complex steps would be required to enforce data write back and invalidate any instruction cache entry.

Embecosm divider strip