Search code examples
initializationgcc-warningavr-gcclinker-scripts

Calling C function from the .init3 section of a AVR-GCC compiled program


I am working on a project that contains a firmware update failure feature that :

notifies user/host that firmware update failed whenever the device gets unplugged in the middle of a firmware update does not attempt to run jump to main() in application code.

To achieve this, I created some code that is designed to trap program execution when a DFU update is not completed. This code is located at the very beginning of the .text section so that it makes it pretty likely that this code gets successfully flashed before the user prematurely unplugs the device (this section of flash is done being programmed after only about 500 ms of the DFU, which takes about 13 seconds overall).

The DFU failure code

checks magic number at start of .text and end of .text section matches, and if not blinks LED at the user emits 0xAA byte over and over again on the UART so that the host can detect the failure signature I have this working using the following technique:

  • Modified my linker script to so that the DFU fail code can have its own low-address section, and magic number information, at the beginning of the text section
  • As a pre-build step before I compile, a magic number is generated. When I compile, the magic number gets inserted at the beginning and end of the .text section:
    • app_magic_start
    • app_magic_end
  • I then created a function placed in the .init3 section which evaluates whether or not the magic number at start of text section matches the one at the end of text section, and if it doesn't then it starts calling the stall_and_scream() function in the .app_early section

Modified Linker Script's text section

.text 0xC0 :
  {
    KEEP(*(.app_magic_start))
    KEEP(*(.app_fw_version))
    *(.app_early)
    /*  edited location of INIT sections!!! */
    /* From this point on, we don't bother about wether the insns are
       below or above the 16 bits boundary.  */
    *(.init0)  /* Start here after reset.  */
    KEEP (*(.init0))
    *(.init1)
    KEEP (*(.init1))
    *(.init2)  /* Clear __zero_reg__, set up stack pointer.  */
    KEEP (*(.init2))
    *(.init3)
    KEEP (*(.init3))
    *(.init4)  /* Initialize data and BSS.  */
    KEEP (*(.init4))
    *(.init5)
    KEEP (*(.init5))
    *(.init6)  /* C++ constructors.  */
    KEEP (*(.init6))
    *(.init7)
    KEEP (*(.init7))
    *(.init8)
    KEEP (*(.init8))
    *(.init9)  /* Call main().  */
    KEEP (*(.init9))
    /* For data that needs to reside in the lower 64k of progmem.  */
     *(.progmem.gcc*)
    /* PR 13812: Placing the trampolines here gives a better chance
       that they will be in range of the code that uses them.  */
    . = ALIGN(2);
     __trampolines_start = . ;
    /* The jump trampolines for the 16-bit limited relocs will reside here.  */
    *(.trampolines)
     *(.trampolines*)
     __trampolines_end = . ;
    /* avr-libc expects these data to reside in lower 64K. */
     *libprintf_flt.a:*(.progmem.data)
     *libc.a:*(.progmem.data)
     *(.progmem*)
 
 
    . = ALIGN(2);
    /* For future tablejump instruction arrays for 3 byte pc devices.
       We don't relax jump/call instructions within these sections.  */
    *(.jumptables)
     *(.jumptables*)
    /* For code that needs to reside in the lower 128k progmem.  */
    *(.lowtext)
     *(.lowtext*)
     __ctors_start = . ;
     *(.ctors)
     __ctors_end = . ;
     __dtors_start = . ;
     *(.dtors)
     __dtors_end = . ;
    KEEP(SORT(*)(.ctors))
    KEEP(SORT(*)(.dtors))
    /* original location of INIT sections */
    *(.text)
    . = ALIGN(2);
     *(.text.*)
    . = ALIGN(2);
    *(.fini9)  /* _exit() starts here.  */
    KEEP (*(.fini9))
    *(.fini8)
    KEEP (*(.fini8))
    *(.fini7)
    KEEP (*(.fini7))
    *(.fini6)  /* C++ destructors.  */
    KEEP (*(.fini6))
    *(.fini5)
    KEEP (*(.fini5))
    *(.fini4)
    KEEP (*(.fini4))
    *(.fini3)
    KEEP (*(.fini3))
    *(.fini2)
    KEEP (*(.fini2))
    *(.fini1)
    KEEP (*(.fini1))
    *(.fini0)  /* Infinite loop after program termination.  */
    KEEP (*(.fini0))
    *(.app_magic_end)
     _etext = . ;
  }  > text

DFU Failure Code

//compile app with "magic number" as the first and last 
//32-bit values in the app,
/*
memory map would be like:
vtors
uint32_t magic_start;
....
uint32_t magic_end;
(end of .txt section)
*/
__attribute__((section(".app_magic_start"))) const uint32_t app_magic_start_flash = BUILD_GIT_SHA;
__attribute__((section(".app_magic_end"))) const uint32_t app_magic_end_flash = BUILD_GIT_SHA;
uint32_t __attribute__((section(".app_early"))) read_flash32(uint16_t addr)
{
    uint32_t ret = 0;
    for (int i = 0; i < 4; i++)
    {
        ret = ret | (((uint32_t)(pgm_read_byte(i + addr))) << (i * 8));
    }
    return ret;
}
 
 
void __attribute__((section(".app_early"))) stall_and_scream(uint16_t count)
{
    for(volatile uint16_t c= 1; c< count;){
        c++;
        while((UCSR0A & (1<<UDRE0)) == 0){
        };
        //Delay for a byte or two so that host uart can sycnronize the the 0xAA streams.
        for(volatile uint16_t i = 0; i < 0xFF;){
            i++;
        }
        UDR0 = 0xAA;
        UCSR0A = ((UCSR0A) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0);
        wdt_reset();
    }
}
#define MS_TO_CHARCOUNT(x) (2*x)
//Put this piece of code in the init3 section, which gets called early during gcc
//initialization steps, just after clear zero reg and setup stack pointer (just before initialize data an bss)
//NAKED
//declare the function naked so that it simply runs without trying to return to previous stack pointer location
//--> basically becomes inline assembly code in init sequence right before data and bss initialization
//--> TODO: convert the function to only use inline assembly, as per requirements for the naked attribute
//USED
//declare the function used so that link-time optimization will not discard it before it can be 
//included into the init code
 void __attribute__((section(".init3"), used, naked)) app_valid_check()
{
    wdt_enable(WDTO_8S);
    SWITCH_DDR |= SWITCH_PORT_MSK;  //Configure switch enable pin to output;
    SWTICH_PORT |= SWITCH_PORT_MSK;//Turn the switch on!
    volatile uint32_t magic_start = read_flash32(uint16_t(&app_magic_start_flash));
    volatile uint32_t magic_end = read_flash32(uint16_t(&app_magic_end_flash));
    if(magic_start != magic_end){
        GREEN_LED_1_DDR |= GREEN_LED_1_PORT_MSK;
        GREEN_LED_2_DDR |= GREEN_LED_2_PORT_MSK;
        //configure the uart
        UCSR0A = 0x20 | (1 << U2X0);
        UBRR0 = 0x0019; //Set baud rate
        UCSR0C = 0x06; //Set 8 bit char size
        UCSR0B = (1<<TXEN0); //enable transmitter (no interrupts)
        UCSR0D = 0xA0;
        //magic numbers don't match, app is invalid!  scream over uart and Blink red LED continuously
        while(1){
            //Turn on LEDS
            GREEN_LED_1_PORT |= GREEN_LED_1_PORT_MSK;
            GREEN_LED_2_PORT |= GREEN_LED_2_PORT_MSK;
            stall_and_scream(MS_TO_CHARCOUNT(50));
            //Turn off LEDS
            GREEN_LED_1_PORT &= ~(GREEN_LED_1_PORT_MSK);
            GREEN_LED_2_PORT &= ~(GREEN_LED_2_PORT_MSK);
            stall_and_scream(MS_TO_CHARCOUNT(200));
        }
    }
}

The Problem?

This all seems to work as expected in that:

  • compilation succeeds
  • if I test the feature out (unplug pack during a DFU), it will indeed stall and scream()
  • however I get the following compiler warning:

[N] 2465 : non-ASM statement in naked function is not supported JP2App C:\PARTICLE_LOCAL\JP2\JP2App\JP2App\JP2App.ino 1066

I know that this warning is being generated because I am using the naked AVR-GCC function attribute in app_valid_check()

According to the documentation (6.31.5 AVR Function Attributes) :

This attribute allows the compiler to construct the requisite function declaration, while allowing the body of the function to be assembly code. The specified function will not have prologue/epilogue sequences generated by the compiler. Only basic asm statements can safely be included in naked functions (see Basic Asm). While using extended asm or a mixture of basic asm and C code may appear to work, they cannot be depended upon to work reliably and are not supported.

So, "though it appears to work", it "cannot be depended upon to work reliably".

Things I've tried

Tried removing the naked attribute from app_valid_check(), and instead making it inline, and used attribute always_inline

I was expecting that this would allow the function to simply be plopped directly into the .init3 section (with no jump instruction to get to it) and run properly, but this did not seem to work.

It compiles albeit with a bunch of warnings about app_valid_check declared but never defined.

If I run this program, it seems to execute the code in app_valid_check() , however for some reason it appears that there is assembly code at the end of app_valid_check() with a return statement (see screenshot below). This doesn't make sense though, if the function really is "inline" in the .init3 section... inline functions should have no RET instruction at the end, right?

enter image description here

The Question

You may be wondering what my question is by now. Here it is:

How can I call a C function from the init3 section in AVR-GCC?

I think it may involve:

  • Move the app_valid_function() into the .app_early section, and remove the naked attribute
  • Create a single naked function called call_app_valid_check() that is included in the .init3 section, which contains only basic ASM code (not extended ASM) which:
    • sets up a call to the app_valid_function() (which is a C function, not naked)

But I'm not really experienced in assembly code...

I assume it would involve:

  • Setting up the prologue/epilogue to the call to app_valid_check()
  • Somehow creating a jump instruction to the app_valid_check() without using extended ASM
    • It is my limited understanding that this will involve hard-coding the address of app_valid_check into the ASM code.... is that right?

Your guidance would be greatly appreciated.


Solution

  • What you can do is to call your function via a stub like in:

    // Define or declare my_func()...
     
    __attribute__((__naked__,__used__,__unused__,__section__(".init3")))
    static void call_my_func (void)
    {
       __asm ("%~call %x0" :: "i" (my_func));
    }
    

    Notice that .init3 runs prior to .init4 which initializes static storage (.data, .bss etc), i.e. using static storage in my_func comes with some limitations.

    Also notice that a part of startup code may run in .init3, so you must make sure that the (unspecified) ordering of the code in .init3 does not matter. See AVR-LibC: Memory Sections: The .initN Sections.

    When the time to initialize static storage is no issue, then you can run my_func at .init6 as a static constructor:

    __attribute__((__constructor__))
    static void my_func (void)
    {
       // C/C++ Code
    }
    

    In that case, there's no need for code in .init3.