Sharing an ISR (and globals) with a bootloader

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

Thanks to various helpful threads, I've managed to share code between my app and bootloader with a jump table at a fixed location in the bootloader.

Now I'd like to share an ISR located in the bootloader with the app. Two questions:

1. I'm thinking the app can just read the ISR address from the bootloader's interrupt table and write that to its own. Any issues with that? I could also create small ISRs in each the bootloader and app that called a shared function, but that would force both ISRs to save and restore most registers.

2. The ISR in question is for handling certain USB events, and it uses a global variable to communicate with non-interrupt code. What is a good way to get a pointer into an ISR? I thought about stashing a pointer in GPIOR2 & GPIOR1, but I'll have to make sure those aren't used for anything else. I could also write the address to EEPROM as well (only when the pointer is different than what was previously stored). Any other ideas?

I did consider using a fixed section like I did for the jump table, but it would be more difficult to find a safe spot to stash it between the changing variable size (upon code changes) and somewhat variable stack size.

-Brad

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

If you can stand the overhead I'd just have the vectors jump via your already present jump table - just have the ISR call through a function pointer

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

clawson wrote:
If you can stand the overhead I'd just have the vectors jump via your already present jump table - just have the ISR call through a function pointer

Yes thanks, I may do that to share code since it is simpler. But there is still the problem of global variables.

Unless I am thinking about this all wrong, it isn't possible for code shared between the bl and app to access global variables since, without a fixed SRAM section, their addresses depend on which side is running.

So given that I can't pass a pointer on the stack to an ISR, what is a good way to provide that shared code a pointer to global data?

-Brad

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

Quote:

Unless I am thinking about this all wrong, it isn't possible for code shared between the bl and app to access global variables since, without a fixed SRAM section, their addresses depend on which side is running.

Not entirely true:
shared.h

typedef struct {
 long big_var;
 char text[20];
 int n,m;
 short s;
} my_vars_type;

one_side.c

my_vars_type my_vars;

my_var_type get_address_of_my_vars(void) {
 return &my_vars;
}

int main(void) {
 my_vars.big_var = 0x12345678;
 strcpy(my_vars.text, "hello");
 etc.
}

other_side.c

int main(void) {
 my_vars_type *p_my_vars;

 p_my_vars = get_address_of_my_vars(); //but called by shared jump table

 if (p_my_vars->big_var == 0x12345678) {
   display_string(p_my_vars->text);
   p_my_vars->n = 13;
   p_my_vars->m = 73;
   etc.
 }
}

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

I'm not seeing how your example would work, because the SRAM layout will change depending upon which side is running.

Let's say that when one_side is built, my_vars gets located at 0xA in SRAM. That will be the address returned by get_address_of_my_vars even when called through the jump table, correct?

Now when other_side is built and running, what is to stop one of its own globals from being positioned at 0xA? In the context of other_side wouldn't the 0xA value returned by get_address_of_my_vars be just a "random" location in its own SRAM layout?

-Brad

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

If you are so extremely tight on Flash, I would suggest, use a bigger AVR.

I think, no AVR compiler has support for such an approach.
So it would be a real nightmare until such a system runs error free.
Also the smallest change (source code, compiler version, optimization switches) may cause malfunction every time.

Peter

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

danni wrote:
If you are so extremely tight on Flash, I would suggest, use a bigger AVR.

This isn't a hobby project, it is a commercial product where small changes in manufacturing costs and size will have a large impact. I spend more time thinking about how to go to an even smaller AVR.

danni wrote:

I think, no AVR compiler has support for such an approach.
So it would be a real nightmare until such a system runs error free.
Also the smallest change (source code, compiler version, optimization switches) may cause malfunction every time.

I appreciate the warning, but I guess I don't totally agree. Plenty of people have shared code between app and bootloader. If it is done correctly, code/data position changes should have zero effect. Only the jumpt table, which is written in ASM, needs to be fixed.

But I did just think of a problem with using EEPROM to stash the location of global data. The value would need to be changed every time there was an app / bootloader switch. That could quickly reach the durability limit. So maybe storing it in GPIO registers is better. Unless Cliff is onto something that I just haven't wrapped my head around yet.

-Brad

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

Quote:

I'm not seeing how your example would work, because the SRAM layout will change depending upon which side is running.

Let's say that when one_side is built, my_vars gets located at 0xA in SRAM. That will be the address returned by get_address_of_my_vars even when called through the jump table, correct?


Only one C runtime can be active at any time - if the app (say) has gained control it's C pre-amble will have laid out .data and .bss. It may well call to routines in the bootloader section but that doesn't suddenly mean that the bootloaders pre-amble gets a chance to run and make a new layout in SRAM. So the BL routines are run in the context of the app code. That is why one side has to provide a function visible to the other to get the address of the base of the struct that actually exists in the .data/.bss context of the half that has initialisation control. By using a struct to group everything together it means that it's not necessary to pass around the addresses of lots of different "shared globals" but simply the base address of one large, composite block. At build time both halves know the layout of the elements in the struct so that maybe 'n' is at offest 14 and 's' is at offset 16 or whatever.

The half that doesn't "own" the variables then doesn't access them directly but by indirection through the pointer (that's what '->' is all about).

I know this system works well because we had an architecture that had not just two (app and boot) modules built independently - but 105 different modules and their only visibility of each other's data was by the passing of base pointers to a struct{} that each owned and held all its own variables within.

Cliff

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

clawson wrote:
Only one C runtime can be active at any time - if the app (say) has gained control it's C pre-amble will have laid out .data and .bss.

Right and this is exactly my concern. When the app is running it's init code will have laid out .data and .bss. Your example works for the running app calling into bootloader code because the get_address callout that provides the address is compiled into the app.

But when the bootloader is running, it has setup its own memory layout (which will be different than the app's). When the bootloader calls the same shared bootloader function that call the same get_address function in the app, isn't the returned address going to be the same app specific address? The get_address function was compiled into the app, so it is going to have an app specific address baked into it. And that address will not be valid in the context of the running bootloader.

In other words, haven't you just reversed the problem? This seems like the exact same problem as accessing globals defined inside the bootloader directly from bootloader functions called by the running app (just in the opposite direction).

Quote:
It may well call to routines in the bootloader section but that doesn't suddenly mean that the bootloaders pre-amble gets a chance to run and make a new layout in SRAM.

Right, and when the bootloader is running the memory layout will be that of the bootloader. And if that get_address was compiled into the application it is going to return an address that has no meaning in the bootloader's SRAM layout.

Quote:
I know this system works well because we had an architecture that had not just two (app and boot) modules built independently - but 105 different modules...

Yes, I can see that this would work if those modules are just code blocks that are complied separately but don't run on their own. Or if they are run independently and do setup their own SRAM layouts, then perhaps they pass a pointer to global data on the stack?

My problem is that both sides will run independently (with their own memory layouts), and each needs to provide their own pointer to global data in their own SRAM layout. And I can't pass anything on the stack to an ISR. Thus my search for another place to stash it. I really hope I'm not just having a mental block here...

-Brad

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

schickb wrote:
This isn't a hobby project, it is a commercial product where small changes in manufacturing costs and size will have a large impact

Yes I know, unfortunately there exist different thinking about "commercial".

Some people means "cheap", other means "reliable".

I work on gadgets, which schould be so reliable as possible.
So plaing with every stuff, which makes programming, testing and debugging even harder was absolutely no option for me.
And typically the cost of the mc was far below 1% of the whole gadget and so it was no point to achieve noticeable cost reducing.

Also more reliable can be cheaper finally, if you have less cost for returning and repairing your products.

Peter

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

I probably don't fully understand, but-

#include 

//app only
void USB_GEN_vect(void) __attribute__((naked));
void USB_GEN_vect(void){
asm("jmp 0x1F028"); //address of boot vector table
}


//these goes in both app and bootloader
struct global_struct{
	//common globals here
	uint8_t a;
	uint16_t b;
	void* c;
	uint32_t d;
	uint8_t* e;
};
#define MY_GLOBALS (*(struct global_struct*)0x100)


//both app and bootloader-
//start data section higher than size of globals
//-Wl,-section-start=.data=0x800110
//0x10 bytes higher in this example


//globals are not initialized

int main(){

	//I need one global initialized
	MY_GLOBALS.b=5;

}

with the data section 'moved up', and both bootloader and app using the sram below that via a struct, it seems both app and bootloader will be accessing the same 'globals' (via isr I guess). To save a cycle or two, that isr jmp could be changed to an rjmp (and the actual vector jump could be changed if wanted, it just requires more work).

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

curtvm wrote:
with the data section 'moved up', and both bootloader and app using the sram below that via a struct, it seems both app and bootloader will be accessing the same 'globals' (via isr I guess).

Great idea. I can't think of a reason that wouldn't work. It didn't occur to me to reposition the .data section to reserve some space. I'll give it a try.

-Brad

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

danni wrote:
I work on gadgets, which schould be so reliable as possible.
So plaing with every stuff, which makes programming, testing and debugging even harder was absolutely no option for me.
And typically the cost of the mc was far below 1% of the whole gadget and so it was no point to achieve noticeable cost reducing.

Yeah, ever project is different. For our project, cost and size are really of top importance. We want these devices to be so affordable that they feel almost disposable. Of course they have to work as well, but we'll have plenty of testing and while these ideas are certainly harder to implement they shouldn't be unreliable once completed.

-Brad

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

curtvm wrote:
with the data section 'moved up', and both bootloader and app using the sram below that via a struct, it seems both app and bootloader will be accessing the same 'globals' (via isr I guess).

This worked well, thanks again for the idea. One caveat: The reserved memory below .data will not be initialized, so if code depends on it being initialized to anything (even zero) make sure you do that early somewhere. Like this:

void __low_level_init(void) __attribute__ ((section (".init3"),naked));
void __low_level_init()
{
    memset(((UsbGlobals*)0x100), 0, sizeof(UsbGlobals));
}

I haven't tried the direct ISR jump you suggested, but it seems like that would work also. Although I do prefer having just one jumping mechanism to keep track of, so I may just stick with a shared function and a few extra register push/pops.

-Brad

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

curtvm wrote:

//app only
void USB_GEN_vect(void) __attribute__((naked));
void USB_GEN_vect(void){
asm("jmp 0x1F028"); //address of boot vector table
}

I'm now also using this to share an ISR that lives in the bootloader. Working well.

-Brad