jump to function using specific registers

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

Hi all,
I'm trying to write a function that jumps to another using WELL SPECIFIED registers EIND, :

static __always_inline void jump(void){
   asm volatile {
     ldi     r30, 0x4A       ; 74
     ldi     r31, 0x01       ; 1
     eijmp
   }
}

Now this would be fine, as long as I knew in advance that the function I want to jump into is at address 0x174 (in words).
What if (at compile time at least), I wanted to specify the function in there through a macro, a function or whatever and let the compiler find the address and replace with correct values?

Best would be to have a jump(uint32_t myaddress) that somehow loaded myaddress in the right registers.. but I can understand that this is faar too difficult.. but at least having the function name replaced with the hardcoded at compile time would be the least.

In short.. I would be soo happy if I could write the program counter of the avr (xmega256).. and switch function in a seamless way (minimum and controlled amount of registers altered).

Thanks every one.
R

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

Sorry but what's wrong with:

typedef void (*f_ptr_t)(void);

f_ptr_t target = (f_ptr_t) 0x1234;

target();

You can then do things like:

void jump(f_ptr_t destination) {
  destination();
}

and pass this function a destination address at run time. Obviously passing it "magic numbers" like 0x1234 is not wise but you could:

void a_func(void) {
  // code
}

...

jump(a_func);

which would be a very round about way of calling a_func().

When you disassemble jump() you'll find it's just moving the passed parameter (R25:R24) into Z then doing an EICALL.

BTW if this is all about context switching in an TOS the tutorial forum has a thread that lists about 27 or more RTOS for AVR. You could study any one of them to see how the context switch is done. FreeRTOS even has a whole set of web pages explaining exactly how the AVR version works.

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

clawson wrote:
Sorry but what's wrong with:
typedef void (*f_ptr_t)(void);

f_ptr_t target = (f_ptr_t) 0x1234;

target();

It's not supported by GCC for devices with EIND.

See the GCC documentation.

@riccardo:

I don't understand what you are trying to do accomplish. If you want to call a function, then call it. If you want to call a function indirectly, then call it indirectly. If you want to get a functions address, then get its address.

It's all plain vanilla C except that you must not cast integers to function pointers as mentioned above:

extern void f (void);

void (*get_callee (void))(void)
{
    return f;
}

void caller (void (*callee)(void))
{
    callee();
}

void call_f (void)
{
    caller (get_callee());
}

avrfreaks does not support Opera. Profile inactive.

Last Edited: Tue. Apr 24, 2012 - 05:32 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

It's not supported by GCC for devices with EIND.

Do you simply mean you have to set EIND manually before the invocation or that the compiler does not generate EICALL.

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

You must not set EIND. You break the ABI by changing EIND after .init3.

The compiler will use EIND/EIJMP/RET to reach all of program memory.

See the documentation:

EIND and Devices with more than 128 Ki Bytes of Flash

avrfreaks does not support Opera. Profile inactive.

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

clawson wrote:

jump(a_func);

which would be a very round about way of calling a_func().

When you disassemble jump() you'll find it's just moving the passed parameter (R25:R24) into Z then doing an EICALL.


The point is, I want this to work regardless the optimization level. I do not want the compiler to do the job for me (moving to Z and EICALL) I want to write such routine myself and have that working in a
flexible way (taking any function as argument at compile-time or -even better- run-time).
Quote:
BTW if this is all about context switching

yes
Quote:
FreeRTOS even has a whole set of web pages explaining exactly how the AVR version works.

Ok I'll try to see how they do things in there..
Maybe I should add that I'm porting my own OS which works on different target onto AVR. That is why I need to have this functinality; I have specific interfaces to implement somehow and I can't do things just like FreeRTOS or any Y-A-RTOS do. At most I can try to write something similar and wrap it into my primitives.

R

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

Well FreeRTOS actually "calls" the task by pushing its PC onto the stack and then RETing into it.

As for programmed call or jumps in AVR. You are going to end up using IJMP or ICALL and they both use Z so I don't see that you have an alternative if you use those. Unless you do what FreeRTOS does and PUSH;PUSH;RET

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

Quote:
FreeRTOS even has a whole set of web pages explaining exactly how the AVR version works.

I have looked at this document:
https://www.avrfreaks.net/index.php?module=Freaks%20Files&func=viewFile&id=825&showinfo=1, where interrupts are declared with the __attribute__((signal)) attribute to force Call-Used registers saving/restoring; if I use this attribute i get the following:

00000470 :
     470:       1f 92           push    r1
     472:       0f 92           push    r0
     474:       0f b6           in      r0, 0x3f        ; 63
     476:       0f 92           push    r0
     478:       00 90 3b 00     lds     r0, 0x003B
     47c:       0f 92           push    r0
     47e:       11 24           eor     r1, r1
     480:       2f 93           push    r18
     482:       3f 93           push    r19
     484:       4f 93           push    r20
     486:       5f 93           push    r21
     488:       6f 93           push    r22
     48a:       7f 93           push    r23
     48c:       8f 93           push    r24
     48e:       9f 93           push    r25
     490:       af 93           push    r26
     492:       bf 93           push    r27
     494:       ef 93           push    r30
     496:       ff 93           push    r31
     498:       df 93           push    r29
     49a:       cf 93           push    r28
     49c:       cd b7           in      r28, 0x3d       ; 61
     49e:       de b7           in      r29, 0x3e       ; 62
     4a0:       0e 94 31 02     call    0x462   ; 0x462 
     4a4:       cf 91           pop     r28
     4a6:       df 91           pop     r29
     4a8:       ff 91           pop     r31
     4aa:       ef 91           pop     r30
     4ac:       bf 91           pop     r27
     4ae:       af 91           pop     r26
     4b0:       9f 91           pop     r25
     4b2:       8f 91           pop     r24
     4b4:       7f 91           pop     r23
     4b6:       6f 91           pop     r22
     4b8:       5f 91           pop     r21
     4ba:       4f 91           pop     r20
     4bc:       3f 91           pop     r19
     4be:       2f 91           pop     r18
     4c0:       0f 90           pop     r0
     4c2:       00 92 3b 00     sts     0x003B, r0
     4c6:       0f 90           pop     r0
     4c8:       0f be           out     0x3f, r0        ; 63
     4ca:       0f 90           pop     r0
     4cc:       1f 90           pop     r1
     4ce:       18 95           reti

1) Why SREG and RAMPZ only are saved (what about RAMPD,X,Y and EIND)?
2) What is the code doing with R28:29? It seems like it is kind of preparing the Frame Pointer prior calling the foo() function
The problem is that CALL pushes the PC+2 in the Stack (3bytes in total) which therefore gets altered (along with the SP). I can go with this solution only if foo() POPs 3 bytes out of the Stack as soon as it is entered.

R

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

I'd begin to wonder how relevant a document relating to a compiler may be 8 years later? That document was written in 2004. avr-gcc is quite a different beast these days (even if you are still using the Jan 2010 version in WinAVR20100110)

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

clawson wrote:
PUSH;PUSH;RET

That is exactly what I needed... or better, I need to perform

jmp(foo(void(*retptr)(void)) ;[foo gets stored in R25,R24]

;jmp asm
MOVW R30, R24 ;load Z with foo() address
POP R23,
OUT EIND, R23
POP R25
POP R24       ;align SP and pass return address as foo arg
EIJMP

foo can then do its stuff and finally terminate with

MOVW R30, R24
EIJMP

This aligns the SP in such a way that the call to foo() is totally transparent even though I do a CS and come back.

R

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

Remember that modifying EIND like that is not supported, because it will break the compiler's reliance on EIND always maintaining its predetermined value (established during init) to suit the compiler's own internal purposes.

A preferred method would be something like:

;jmp asm
; argument: target function address in uint32_t
; r25 = scratch
; r24 = high byte of address
; r23 = mid byte of address
; r22 = low byte of address
push r24     ; SP advances by 1 as we write the hi byte of the target address on the stack.
push r23     ; SP advances by 1 as we write the mid byte of the target address on the stack.
push r22     ; SP advances by 1 as we write the low byte of the target address on the stack.
ret          ; This "calls" the target function, and simultaneously retracts SP by 3.

Your target function would then seamlessly return back to the original caller using a straightforward "ret" instruction.

Note that I may have the endianness backward in the order in which the hi mid and low bytes were written to the stack.

If you don't want to use another "return" to switch back to the original function(for example if you just want to continuously flip between them in a round robin fashion), then you'd also need pop the initial return address (of the task that initially called the "call" function) off the stack (and perhaps save it somewhere) and then continue going about the process of pushing in the new desired target address. Then the second function could switch back to the first function by a successive invocation of the same "call" routine.

Maintaining additional context (such as saving/restoring independent stacks and register sets for each task) would require some additional manual manipulation of the stack frame.

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

lfmorrison wrote:
Remember that modifying EIND like that is not supported, because it will break the compiler's reliance on EIND always maintaining its predetermined value (established during init) to suit the compiler's own internal purposes.

A preferred method would be something like:

;jmp asm
; argument: target function address in uint32_t
; r25 = scratch
; r24 = high byte of address
; r23 = mid byte of address
; r22 = low byte of address
push r24     ; SP advances by 1 as we write the hi byte of the target address on the stack.
push r23     ; SP advances by 1 as we write the mid byte of the target address on the stack.
push r22     ; SP advances by 1 as we write the low byte of the target address on the stack.
ret          ; This "calls" the target function, and simultaneously retracts SP by 3.

Your target function would then seamlessly return back to the original caller using a straightforward "ret" instruction.


This is good too for me I'll go for this solution! Thanks

Quote:
Maintaining additional context (such as saving/restoring independent stacks and register sets for each task) would require some additional manual manipulation of the stack frame.

I see you're getting the nature of my questions. This prolly means I'm not out of scope with them.. very appreciated.