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 _start:
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.
.L3: l.sw 0(r30),r26 l.lwz r24,0(r30) l.sfeq r24,r26 l.bnf .L4 l.nop l.j .L3 l.add r30,r30,r28 /* Try 64KB higher */ .L4:
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 .L5: l.sfeq r28,r30 l.bf .L6 l.nop l.sw 0(r30),r26 /* Patch the instruction */ l.j .L5 l.addi r30,r30,4 /* Next instruction */ .L6:
Note | |
---|---|
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. |