6

I am writing the boot-up code for an ARM CPU. There is no internal RAM, but there is 1GB of DDRAM connected to the CPU, which is not directly accessible before initialisation. The code is stored in flash, initialises RAM, then copies itself and the data segment to RAM and continue execution there. My program is:

#define REG_BASE_BOOTUP 0xD0000000
#define INTER_REGS_BASE REG_BASE_BOOTUP

#define SDRAM_FTDLL_REG_DEFAULT_LEFT            0x887000

#define DRAM_BASE               0x0
#define SDRAM_FTDLL_CONFIG_LEFT_REG (DRAM_BASE+ 0x1484)
... //a lot of registers

void sdram_init() __attribute__((section(".text_sdram_init")));

void ram_init()
{
  static volatile unsigned int* const sdram_ftdll_config_left_reg = (unsigned int*)(INTER_REGS_BASE + SDRAM_FTDLL_CONFIG_LEFT_REG);
  ... //a lot of registers assignments

  *sdram_ftdll_config_left_reg = SDRAM_FTDLL_REG_DEFAULT_LEFT;
}

At the moment my program is not working correctly because the register values end up being linked to RAM, and at the moment the program tries to access them only the flash is usable.

How could I change my linker script or my program so that those values have their address in flash? Is there a way I can have those values in the text segment?

And actually are those defined values global or static data when they are declared at file scope?

Edit:

The object file is linked with the following linker script:

MEMORY 
{                                                                                                             
RAM (rw)    : ORIGIN = 0x00001000, LENGTH = 12M-4K                                
ROM (rx)    : ORIGIN = 0x007f1000, LENGTH = 60K
VECTOR (rx) : ORIGIN = 0x007f0000, LENGTH = 4K   
}
SECTIONS
{
    .startup :
    {
    KEEP((.text.vectors))
    sdram_init.o(.sdram_init)
    } > VECTOR
...
}

Disassembly from the register assignment:

  *sdram_ftdll_config_left_reg = SDRAM_FTDLL_REG_DEFAULT_LEFT;
  7f0068:       e59f3204        ldr     r3, [pc, #516]  ; 7f0274 <sdram_init+0x254>
  7f006c:       e5932000        ldr     r2, [r3]
  7f0070:       e59f3200        ldr     r3, [pc, #512]  ; 7f0278 <sdram_init+0x258>
  7f0074:       e5823000        str     r3, [r2]
  ...
  7f0274:       007f2304        .word   0x007f2304
  7f0278:       00887000        .word   0x00887000
Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
Étienne
  • 4,131
  • 2
  • 27
  • 49
  • 1
    `__attribute__((progmem))` or something? –  Apr 07 '13 at 22:24
  • 4
    Are you sure that's your actual code? I doubt that will compile... – Oliver Charlesworth Apr 07 '13 at 22:25
  • @Étienne That's not about the implicit return value, that's rather about how you try to assign one numeric literal to another. Your current code expands to `0xd0010000 = 0x800f800f;` which doesn't make sense. Didn't you mean to define those addresses to hard-coded pointers instead? `#define register1 *(uint32_t *)0xd0010000` –  Apr 07 '13 at 22:30
  • 1
    @Étienne (***there's no such thing as an implicit cast!*** There is cast which is explicit type conversion, and there is promotion which is implicit type conversion.) Still not good. You can't dereference an `int`, need to cast it to a non-`void` data pointer type. But to the point, have you done what I suggested? Most probably there's something about an attribute in GCC's manual. –  Apr 07 '13 at 22:36
  • I'll search further but progmem seems to be specific to the AVR architecture http://gcc.gnu.org/onlinedocs/gcc-4.8.0/gcc/Variable-Attributes.html#Variable-Attributes – Étienne Apr 07 '13 at 22:45
  • 1
    @Étienne I have updated my answer now that we have the real code. I believe it has some useful advice, even though you have accepted *duskwuff's* answer. – artless noise Apr 08 '13 at 14:04
  • Yeah thank you for your answer, I upvoted it from the beginning as it was very useful to me. I accepted duskwuff answer since he was right about the fact that the problem was not coming from what I described, and that the values ended up in the text segment. With that information I found out that I had written "text_sdram_init" in the C file and "sdram_init" in the linker script for the section name, which is why it wasn't linked correctly :) – Étienne Apr 08 '13 at 17:55

2 Answers2

3

To answer your question directly -- #defined values are not stored in the program anywhere (besides possibly in debug sections). Macros are expanded at compile time as if you'd typed them out in the function, something like:

*((unsigned int *) 0xd0010000) = 0x800f800f;

The values do end up in the text segment, as part of your compiled code.

What's much more likely here is that there's something else you're doing wrong. Off the top of my head, my first guess would be that your stack isn't initialized properly, or is located in a memory region that isn't available yet.

duskwuff -inactive-
  • 171,163
  • 27
  • 219
  • 269
  • I'm not using the stack at all during initialisation, since no ram is available at that time. – Étienne Apr 07 '13 at 22:46
  • 1
    You almost certainly are using the stack. If you're calling a function, the caller will save some registers to the stack, and the compiler may be spilling various other locals to the stack. You can't run compiled C code reliably without a stack — you will either need to use on-chip memory for the stack during startup, or use assembly only. – duskwuff -inactive- Apr 07 '13 at 22:49
  • I'm calling the function by branching to a label defined on it from assembly, and I'm setting the stack pointer manually after this RAM initialisation. – Étienne Apr 07 '13 at 22:51
  • 2
    Doesn't matter — the compiler is still going to assume that the stack is initialized, and may end up trying to use it. Try disassembling the compiled file if you don't believe me. – duskwuff -inactive- Apr 07 '13 at 23:02
  • Well, in the assembly I can see there is a push on the stack at the beginning of the function, and a pop at the end, even though the stack pointer is not initialized. Doesn't look good :/ – Étienne Apr 08 '13 at 10:07
  • Actually it is still working since no variables are created on the stack in the function, even though it is a potential bug. – Étienne Apr 08 '13 at 10:18
  • 2
    @Étienne Your bug is actually as I describe, in the last comment in my message (in reply to *duskwuff*); you have assigned a static variable pointer with the define. I also thought that you would not have a stack issue, because of the way `gcc` processes ARM leaf functions, but it is a good point anyways, because it is a time bomb. – artless noise Apr 08 '13 at 13:41
1

There are a few options to solve this problem.

  1. Use PC relative data access.
  2. Use a custom linker script.
  3. Use assembler.

Use PC relative data access

The trouble you have with this method is you must know details of how the compiler will generate code. #define register1 (volatile unsigned int *)0xd0010000UL is that this is being stored as a static variable which is loaded from the linked SDRAM address.

  7f0068:      ldr     r3, [pc, #516]  ; 7f0274 <sdram_init+0x254>
  7f006c:      ldr     r2, [r3]  ; !! This is a problem !!
  7f0070:      ldr     r3, [pc, #512]  ; 7f0278 <sdram_init+0x258>
  7f0074:      str     r3, [r2]
  ...
  7f0274:     .word   0x007f2304  ; !! This memory doesn't exist.
  7f0278:     .word   0x00887000

You must do this,

 void ram_init()
 {
     /* NO 'static', you can not do that. */
     /* static */ volatile unsigned int* const sdram_reg = 
         (unsigned int*)(INTER_REGS_BASE + SDRAM_FTDLL_CONFIG_LEFT_REG);
     *sdram_ftdll_config_left_reg = SDRAM_FTDLL_REG_DEFAULT_LEFT;
 }

Or you may prefer to implement this in assembler as it is probably pretty obtuse as to what you can and can't do here. The main effect of the above C code is that every thing is calculated or PC relative. If you opt not to use a linker script, this must be the case. As Duskwuff points out, you also can have stack issues. If you have no ETB memory, etc, that you can use as a temporary stack then it probably best to code this in assembler.

Linker script

See gnu linker map... and many other question on using a linker script in this case. If you want specifics, you need to give actual addresses use by the processor. With this option you can annotate your function to specify which section it will live in. For instance,

void ram_init() __attribute__((section("FLASH")));

In this case, you would use the Gnu Linkers MEMORY statement and AT statements to put this code at the flash address where you desire it to run from.

Use assembler

Assembler gives you full control over memory use. You can garentee that no stack is used, that no non-PC relative code is generated and it will probably be faster to boot. Here is some table driven ARM assembler I have used for the case you describe, initializing an SDRAM controller.

 /* Macro for table of register writes. */
 .macro DCDGEN,type,addr,data
 .long \type
 .long \addr
 .long \data
 .endm
 .set FTDLL_CONFIG_LEFT, 0xD0001484

 sdram_init:
 DCDGEN 4, FTDLL_CONFIG_LEFT, 0x887000
 1:

 init_sdram_bank:
         adr     r0,sdram_init
         adr     r1,1b
 1:
         /* Delay. */
         mov     r5,#0x100
 2:      subs    r5,r5,#1
         bne     2b

         ldmia   r0!, {r2,r3,r4} /* Load DCD entry. */
         cmp     r2,#1           /* byte? */
         streqb  r4,[r3]         /* Store byte... */
         strne   r4,[r3]         /* Store word. */

         cmp     r0,r1           /* table done? */
         blo     1b

         bx lr

         /* Dump literal pool. */
        .ltorg

Assembler has many benefits. You can also clear the bss section and setup the stack with simple routines. There are many on the Internet and I think you can probably code one yourself. The gnu ld script is also beneficial with assembler as you can ensure that sections like bss are aligned and a multiple of 4,8,etc. so that the clearing routine doesn't need special cases. Also, you will have to copy the code from flash to SDRAM after it is initialized. This is a fairly expensive/long running task and you can speed it up with some short assembler.

Community
  • 1
  • 1
artless noise
  • 18,969
  • 5
  • 57
  • 95
  • `#define`s do not exist in memory anywhere. – duskwuff -inactive- Apr 07 '13 at 23:00
  • @duskwuff That is true. But with the ARM, constant of 8bits and a shift can be encoded as immediate. A `#define` such as Etienne has will be encoded as a `.long` load from memory at a fixed address; at least that is the explanation of my first section, which maybe incorrect. So the use of `#define` exists in memory. – artless noise Apr 07 '13 at 23:04
  • No, that's not how any compiler I've ever seen would work. At most, the constant might end up put at the end of the function and loaded using a PC-relative reference — if the function is already in flash, though, so will be the constant. – duskwuff -inactive- Apr 07 '13 at 23:07
  • @duskwuff I think we both need to see the real code to know that. For example, I have seen people assign the `#define` to a pointer variable and then use the variable. I also agree that the code needs to be PC-relative as I said in that section of the answer. – artless noise Apr 07 '13 at 23:24