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
7
  • 1
    __attribute__((progmem)) or something? Commented Apr 7, 2013 at 22:24
  • 4
    Are you sure that's your actual code? I doubt that will compile... Commented Apr 7, 2013 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 Commented Apr 7, 2013 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. Commented Apr 7, 2013 at 22:36
  • 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. Commented Apr 8, 2013 at 14:04

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

7 Comments

I'm not using the stack at all during initialisation, since no ram is available at that time.
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.
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.
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.
@É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.
|
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.

4 Comments

#defines do not exist in memory anywhere.
@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.
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 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.