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_startapp_magic_end
- I then created a function placed in the
.init3section 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 thestall_and_scream()function in the.app_earlysection
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?
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_earlysection, and remove thenakedattribute - Create a single naked function called
call_app_valid_check()that is included in the.init3section, 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.
-
Its hard to grasp what exactly you are trying to achieve. Are you sure, you aren't over-engineering? Why not just write update-state as invalid, as soon as the update starts and verify flash with a CRC when everything is done and go to state OK. Bootloader just needs to check the state and indicates error over LED/UART. But maybe I am completely missing you point.Rev– Rev2024年11月29日 09:37:14 +00:00Commented Nov 29, 2024 at 9:37
-
1The bootloader cannot be changed as it is already deployed on thousands of units, so bootloader features will be of no help. What I am trying to achieve is the ability for the user to see that a firmware update was prematurely unplugged, and provide a UART signature that the host can detect in order to know that there is a device present on a given communication port that just got plugged in with incomplete firmware (since it is "screaming" 0xAAAAAAAA at the host). Note that my code succeeds in doing this, but I still have the "naked function is not supported" compiler warning that irks me.Tom MacDonald– Tom MacDonald2024年11月29日 15:05:36 +00:00Commented Nov 29, 2024 at 15:05
-
AH OK, I missed the part, that this is a afterthought and you can't touch the boot-loader.Rev– Rev2024年12月04日 07:35:28 +00:00Commented Dec 4, 2024 at 7:35
1 Answer 1
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.
3 Comments
Explore related questions
See similar questions with these tags.