Bios, Bootloader and Jump table with AVR GCC

Go To Last Post
28 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am trying to do an auto updatable app/bootloader through a wireless link (gsm link)

 

My micro-controller is a ATmega4809.

In this µC, the flash is divided in 3 sections :

- the Boot section

- the AppCode section

- the AppData section

 

SPM in Boot section can write in AppCode and AppData (not in Boot section itself)

SPM in AppCode section can write in AppData only

SPM in AppData section can write anywhere nowhere

 

So basically, what I want to do is to have a "Bios" with only tools to manage things in flash (eraseBlock, buffer2flash, flash2flash, spi2flash, ... ) (I have an external SPI flash)

These bios functions will not be updatable as in Boot section.

To use this, the aim is that the app or the bootloader store the new application code coming from the wireless link in a free flash area in AppData section, then call the bios function to move to the good location and then restart to execute the new program.

 

(First approach is to have Boot section 1ko, AppCode 3ko, and AppData 44ko)

 

To do this, I want to have a jump table at 0x0000 :

#define AppcodeSectionAddr		0x400

typedef void (*fn_ptr_t)(void);

/// not really C ;-)
0x0000      JMP main
0x0004      JMP eraseBlock
0x0008      JMP buffer2flash
0x000c      JMP flash2flash
0x0010      JMP spi2flash

__attribute__((naked)) __attribute__((section(".ctors"))) void main(void) {
	/* Initialize system for AVR GCC support, expects r1 = 0 */
	asm volatile("clr r1");
	SREG=0;
	SP = RAMEND;

	/* Go to application, located immediately after boot section */
	fn_ptr_t app = (fn_ptr_t)(AppcodeSectionAddr / sizeof(fn_ptr_t));
	app();
}

uint8_t eraseBlock(uint8_t block) {
    // stuff
}

uint8_t flash2flash(uint8_t * src, uint8_t * dest, uint16_t length) {
    // stuff
}

....

 

My question is what code should I write to achieve this jump table in C ?

 

Thanks.

 

AVR inside

Last Edited: Tue. Jul 21, 2020 - 02:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SPM in AppData section can write anywhere

That's incorrect.  SPM in AppData can write NOWHERE.

AFAIK, there is no way for an AT4809 to update it's own bootloader section, programatically.

(I'm not sure if that's what you want to do, or whether it's an ambiguity introduced by a typo.)

 

Someone modified optiboot (on atmega328/etc) to copy application-loaded flash to program space...

https://github.com/Optiboot/opti...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

doomstar wrote:
My question is what code should I write to achieve this jump table in C ?
an array of function pointers returning uint8_t

Examples of pointer constructs | C Programming/Pointers and arrays - Wikibooks, open books for an open world

 

edit : computed goto in GCC

Labels as Values (Using the GNU Compiler Collection (GCC))

...

One way of using these constants is in initializing a static array that serves as a jump table:

...

due to

Tools and Tips | The Embedded Muse 402

...

Bob Paddock wrote:

...

 

"Dare to be naïve." - Buckminster Fuller

Last Edited: Tue. Jul 21, 2020 - 02:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:

SPM in AppData section can write anywhere

That's incorrect.  SPM in AppData can write NOWHERE.

Obviouly, this is a typo error (I am French)

 

westfw wrote:

AFAIK, there is no way for an AT4809 to update it's own bootloader section, programatically.


Sure, it is not possible to update "programatically" the whole bootloader, but I think that we can update quite everything except the part that doing the flash copy (what I called bios)

In my case the first 1ko This should not moved a lot when it was written and a little bit tested.

For this we have to put the real bootloader in the appcode section (at least SPM instructions).

 

Thanks for the link

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@gchapman, I succeeded in placing at 0x0000 an array with address of needed function, but unfortunately, this is not JMP.

 

I also tried goto, but it seems not to be converted to JMP (nor RJMP)

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

doomstar wrote:
but unfortunately, this is not JMP
Why does it need to be JMP ? The only important thing for code on both sides of the fence is the address of the function and also the API. For example suppose my bootloader contains:

int add(int a, int b) {
    return a + b;
}

and I want to be able to call this from my app code. Then:

typedef int (*fptr_t)(int, int);

then somewhere I just need the address of the function held at a location that both sides "know"...

fptr_t * pAdd = add;

Now the thing that wants to call my exposed add() function uses:

fptr_t bootAdd;

bootAdd = pAdd; // or the contents of whatever fixed address the function address is held in

result = bootAdd(123, 456);

of course when you build an array of function pointers it's maybe easier to treat them all as void function:

void (*voidFn_t)(void);

and build an array of them:

voidFn_t funcs[] = {
    (voidFn_t)add,
    (voidFn_t)sub,
    (voidFn_t)multiply,
    (voidFn_t)divide
};

so now it doesn't really matter what the API is. It's a bit like passing round void * pointers (which is another possibility!) and all that matters is that at the moment of deference/invocation they are cast to the right type for the parameters to be passed in/out.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think it is easier with JMP or RJMP. I am maybe wrong.

 

I have found some clue in optiboot code and this seems to work (to put RJMP) :

int main(void);
void biosEraseFlash(void);
void biosCopyFlash(void);

__attribute__((naked)) __attribute__((section(".ctors"))) void jumpTable(void) {
  asm volatile (
    "	rjmp    main\n"
    "	rjmp    biosEraseFlash\n"
    "	rjmp    biosCopyFlash\n"
  );
}

__attribute__((naked)) __attribute__((section(".ctors"))) int main(void) {
	/* Initialize system for AVR GCC support, expects r1 = 0 */
	asm volatile("clr r1");
	SREG=0;
	SP = RAMEND;

	/* Go to application, located immediately after boot section */
	fn_ptr_t app = (fn_ptr_t)(AppcodeSectionAddr / sizeof(fn_ptr_t));
	app();
	return(0);
}

 

Disasm is :

Disassembly of section .text:

00000000 <jumpTable>:
int main(void);
void biosEraseFlash(void);
void biosCopyFlash(void);

__attribute__((naked)) __attribute__((section(".ctors"))) void jumpTable(void) {
  asm volatile (
   0:	02 c0       	rjmp	.+4      	; 0x6 <main>
   2:	0c c0       	rjmp	.+24     	; 0x1c <biosEraseFlash>
   4:	15 c0       	rjmp	.+42     	; 0x30 <biosCopyFlash>

00000006 <main>:
  );
}

__attribute__((naked)) __attribute__((section(".ctors"))) int main(void) {
	/* Initialize system for AVR GCC support, expects r1 = 0 */
	asm volatile("clr r1");
   6:	11 24       	eor	r1, r1
	SREG=0;
   8:	1f be       	out	0x3f, r1	; 63
	SP = RAMEND;
   a:	8f ef       	ldi	r24, 0xFF	; 255
   c:	9f e3       	ldi	r25, 0x3F	; 63
   e:	8d bf       	out	0x3d, r24	; 61
  10:	9e bf       	out	0x3e, r25	; 62

	/* Go to application, located immediately after boot section */
	fn_ptr_t app = (fn_ptr_t)(AppcodeSectionAddr / sizeof(fn_ptr_t));
	app();
  12:	e0 e0       	ldi	r30, 0x00	; 0
  14:	f2 e0       	ldi	r31, 0x02	; 2
  16:	09 95       	icall
	return(0);
}
  18:	80 e0       	ldi	r24, 0x00	; 0
  1a:	90 e0       	ldi	r25, 0x00	; 0

0000001c <biosEraseFlash>:
void biosEraseFlash(void) {
}

...

 

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That adds a level of indirection over what I suggested. That has ICALL then RJMP. What I proposed would be only ICALL.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

That adds a level of indirection over what I suggested. That has ICALL then RJMP. What I proposed would be only ICALL.

Sure but getting the address from the table is also something like an indirection

So 2 vs quite 2 !

 

But many thanks for your lines of code, they will help me. And maybe I will finally implement this solution.
For now, I am just at the beginning of this dev.

 

D.

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have done that :

typedef uint16_t (*fptr_u16u16u16_t) (uint16_t, uint16_t);
fptr_u16u16u16_t addition = (fptr_u16u16u16_t) 0x0006;          // (0x0006/ sizeof(fptr_u16u16u16_t));
fptr_u16u16u16_t substraction = (fptr_u16u16u16_t) 0x0008;      //(0x0008/ sizeof(fptr_u16u16u16_t));
uint16_t resultAddition = addition(12,23);
int16_t resultSubstraction = substraction(42,23);	

And disassembly is :

		uint16_t resultAddition = addition(12,23);
    1460:	e0 91 43 28 	lds	r30, 0x2843	; 0x802843 <addition>
    1464:	f0 91 44 28 	lds	r31, 0x2844	; 0x802844 <addition+0x1>
    1468:	67 e1       	ldi	r22, 0x17	; 23
    146a:	70 e0       	ldi	r23, 0x00	; 0
    146c:	8c e0       	ldi	r24, 0x0C	; 12
    146e:	90 e0       	ldi	r25, 0x00	; 0
    1470:	09 95       	icall
    1472:	88 2e       	mov	r8, r24
    1474:	79 2e       	mov	r7, r25
		uint16_t resultSubstraction = substraction(42,23);
    1476:	e0 91 41 28 	lds	r30, 0x2841	; 0x802841 <substraction>
    147a:	f0 91 42 28 	lds	r31, 0x2842	; 0x802842 <substraction+0x1>
    147e:	67 e1       	ldi	r22, 0x17	; 23
    1480:	70 e0       	ldi	r23, 0x00	; 0
    1482:	8a e2       	ldi	r24, 0x2A	; 42
    1484:	90 e0       	ldi	r25, 0x00	; 0
    1486:	09 95       	icall
    1488:	b8 2e       	mov	r11, r24
    148a:	a9 2e       	mov	r10, r25

 

Do not understand why avrgcc do an 'ICALL' and not a 'CALL 0x0006'.
What should I write in C to be translated with a 'CALL' statement ?

 

And also, it does not work.
It is better only if I divide by sizeof(fptr_u16u16u16_t).  But result is operand 'a' and not a+b or a-b.

D.

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

doomstar wrote:
My question is what code should I write to achieve this jump table in C ?
Methinks this is the problem right here.

When one is fussy about precisely what assembly instructions are issued,

assembly is usually the way to go.

What doomstar says he wants is not that hard in assembly.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

>First approach is to have Boot section 1ko, AppCode 3ko, and AppData 44ko

 

Why not create the BOOT 2k, APP 23k, APPDATA 23k

APP can write to APPDATA, so BOOT functions not needed

 

APP code writes to APPDATA- get a new app from wherever you want, store in APPDATA

time to upgrade, signal BOOT in way that an upgrade needs to happen (eeprom value, a value in a special location in APPDATA, etc.)

app does a software reboot

 

BOOT code checks if need to upgrade via a flag, if so copy APPDATA to APP, verify, 'clear' flag, reboot

if first word in APP is erased (or some other signal to stay in bootloder, like a pin state), stay in bootloader to do normal bootloader things, else jump to app

 

I'm not sure what value there is to calling functions in the BOOT section when the APP code will never be calling anything that will touch the APP section (as you then do not want a return). The nvm code is so trivial that writing to the APPDATA section can easily be done in the app code.

 

You end up with a bootloader that can copy APPDATA to APP, and also do normal bootloader things when all else fails. The app code then has no need to deal with calling BOOT functions.

 

 

 

>What should I write in C to be translated with a 'CALL' statement ?

 

I don't think you can unless you get into asm, but you can make the function pointers constant so you do not have to store/access their address in ram. You still get the icall, but compiler will have no need to store the address. I wouldn't worry about CALL vs ICALL, its not important.

 

edit

and note that the icall addresses are word addresses.

 

 

Last Edited: Fri. Jul 24, 2020 - 05:07 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If functions are declared with const, it still uses icall, but value are loaded directly without using RAM

typedef uint16_t (*fptr_u16u16u16_t) (uint16_t, uint16_t);
static const fptr_u16u16u16_t addition = (fptr_u16u16u16_t) (0x0006/2);
static const fptr_u16u16u16_t substraction = (fptr_u16u16u16_t) (0x0008/2);

Disassembly :

		uint16_t resultAddition = addition(12,23);
    1460:	67 e1       	ldi	r22, 0x17	; 23
    1462:	70 e0       	ldi	r23, 0x00	; 0
    1464:	8c e0       	ldi	r24, 0x0C	; 12
    1466:	90 e0       	ldi	r25, 0x00	; 0
    1468:	e3 e0       	ldi	r30, 0x03	; 3
    146a:	f0 e0       	ldi	r31, 0x00	; 0
    146c:	09 95       	icall
    146e:	b8 2e       	mov	r11, r24
    1470:	a9 2e       	mov	r10, r25
		uint16_t resultSubstraction = substraction(42,23);
    1472:	67 e1       	ldi	r22, 0x17	; 23
    1474:	70 e0       	ldi	r23, 0x00	; 0
    1476:	8a e2       	ldi	r24, 0x2A	; 42
    1478:	90 e0       	ldi	r25, 0x00	; 0
    147a:	e4 e0       	ldi	r30, 0x04	; 4
    147c:	f0 e0       	ldi	r31, 0x00	; 0
    147e:	09 95       	icall
    1480:	d8 2e       	mov	r13, r24
    1482:	c9 2e       	mov	r12, r25

 

But I do not understand avr gcc optimisation as :

LDI+LDI+ICALL = 4/5 clocks and 48 bits of code

and CALL = 3/4 clocks and 32 bits of code

 

So CALL seems more efficient in all terms than ICALL !

 

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

Why not create the BOOT 2k, APP 23k, APPDATA 23k

APP can write to APPDATA, so BOOT functions not needed

This limits the code to 23kB.

Because it is remote via gsm network so bootloader will do more than just receiving bytes from UART.

 

With my solution, I will have 1k for bios, 3k for bootloader (or more if needed), and up to 44k for application code

 

But this is just what I think now. Maybe I will change.

AVR inside

Last Edited: Fri. Jul 24, 2020 - 08:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Its a pointer, the content may vary at run time so it has to be ICALL as it could not encode a CALL at compile time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I need also to prevent my bios to use the RAM.
Is there a way to do that ?

And also I need my function to save used register(on stack for example).
Is there also a way for this to be handled by avr gcc ?

 

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

doomstar wrote:

I need also to prevent my bios to use the RAM.
...

And also I need my function to save used register(on stack for example).

 

Something of a contradiction in terms there - the stack is in ram.

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

barnacle wrote:

Something of a contradiction in terms there - the stack is in ram.

 

Yes I know stack is in RAM.
But if I push something on stack at the beginning of my subroutine and pop it at the end, then it is not a problem. Stack is unchanged.

But if I overwrite data of my app in RAM, then it will be unrecoverable.

 

Same if I overwrite registers (not all, but don't remember which one).

AVR inside

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ah, I wondered if that was what you meant. In which case, it's normal programming; provided there is enough depth in the stack (you'll have to work out the worst case and then add an interrupt!) it will look after itself.

 

As for the RAM in general, that's a question of negotiation; you need to define an area of ram for the IOS to use and never allow the application to use that memory.

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Seems that my subroutines are quite easy and do not use RAM variables, only register vars.

 

And seems that avr gcc do what is needed to save registers that must be saved : https://www.nongnu.org/avr-libc/...

 

So seems I have nothing to do about that.

 

Unfortunately, I did not found any directive or so to dissallow RAM  usage (except stack) for my bios subroutines.

Maybe the best I can do is to define all vars with 'register' modifier.

AVR inside

Last Edited: Sun. Jul 26, 2020 - 08:40 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

'register' is only a hint to the compiler - the use of it guarantees very little. Local variables are likely to be allocated onto the stack.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

doomstar wrote:

Unfortunately, I did not found any directive or so to dissallow RAM  usage (except stack) for my bios subroutines.

Maybe the best I can do is to define all vars with 'register' modifier.

What is the purpose behind all this? What is to be gained (apart from impacted performance) in forcing the compiler to use registers alone?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So long as you have no variables with static storage class, no SRAM will be used (apart from that used by the stack).  So, don't use any global variables (all of which necessarily have static storage class), and don't use the static modifier to declare local variables.  The default for local variables declared without a modifier is 'automatic'.  Such variables are held in registers or on the stack.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

joeymorin wrote:
Such variables are held in registers or on the stack.
But the stack is in RAM and if I've understood this right, the goal here is to avoid any RAm being used (though we don't yet know why this must be).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

But the stack is in RAM and if I've understood this right, the goal here is to avoid any RAm being used (though we don't yet know why this must be).

It doesn't seem so:

doomstar wrote:
Yes I know stack is in RAM.
But if I push something on stack at the beginning of my subroutine and pop it at the end, then it is not a problem. Stack is unchanged.

doomstar wrote:
Unfortunately, I did not found any directive or so to dissallow RAM  usage (except stack) for my bios subroutines.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wonder if this is about RAD hardening and not leaving things in RAM for too long lest the cosmic rays eat it? Of course the other approach in that case are plenty of validity checks and redundancy etc.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the case of avr-gcc *all* auto variables are either in registers or on *the* stack.

Said stack space is given up on return.

The only way for an ordinary function call to use up RAM is if it uses malloc or similar.

Things like direct stack manipulation are also possible.

 

Most other AVR-C compilers use a software stack in addition to the hardware stack.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Wonder if this is about RAD hardening and not leaving things in RAM for too long lest the cosmic rays eat it? Of course the other approach in that case are plenty of validity checks and redundancy etc.
Are the registers less susceptible to cosmic rays than the RAM?

Iluvatar is the better part of Valar.