Friday, September 21, 2012

Starting up an ARM processor with 90% C code

Now why would you want to do a darn fool thing like that? Startup.S works fine, why mess with it?

One word: Understanding.

Let's face it: Assembly is hard to read. Especially ARM assembly, with its special registers, shift-operands, and treating memory (load/store) fundamentally different from registers (mov). You can't get more than a few lines through an assembly listing without having to crack the ARM ARM.

Besides, I have a philosophy to use a consistent language throughout a program to the extent possible. So, for the embedded stuff, it's C++ when you can, C when you have to, and asm only when you really have to.

So what stands in the way? Mostly it's the fact that the toolchain makes it difficult to put things exactly where you want:

  1. The interrupt vector table. This is what keeps the code from being 99% C instead of 90%. On an x86 processor, the interrupt vector table is purely a list of addresses. Upon interruption, the processor looks up the correct vector, and loads it straight into the instruction pointer, causing the processor to branch to the handler. In many other processors, the table is actually code. When an interrupt happens, the processor jumps to the correct slot in the table and executes it. Usually this is a jump to where the handler really is. In ARM, there isn't enough space for a long jump in a single instruction, so each vector says to load the program counter with another value from memory, usually in a table located right after the true interrupt table. In any case, C just isn't a good language for building the table. What we do then is use inline asm. We make a function called vectorg which is purely inline asm. The first half is instructions, specifically the long jump instructions with embedded pointers to the second half, which is the address table. This is populated with symbols, so the linker can patch it up.
  2. Putting things where we want. The old Turbo Pascal had a mechanism for assigning a variable to a particular point in memory. In asm, this is easy: just define a symbol with a hard-coded address. This just isn't possible in C. So, we need cooperation from the linker. We need to specify the linker script. In particular, we need to say that a particular named ELF section is to be linked right at the beginning of flash, and then make sure that the table is in fact in that section, at the beginning of it. The easiest way to do that is to turn on -ffunction-sections when compiling, then link .text.vectorg at the beginning. The source code and the linker script have to agree on this.
  3. Registers - This is the other 1%. To set up the program, the reset handler has to set up the stacks, move the initialized data from flash to RAM, zero out the uninitialized data, and call all the constructors for global objects. But, in order to set up the stacks, the code has to be able to set the CPSR register, so it can flip through modes and set each mode's stack register. It also has to be able to write directly to the stack register itself.

No comments:

Post a Comment