Jump to Function (instead of call)

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

I'm writing a task switcher and want to be able to call my OS's taskSwitch() function using an [R]JMP so the calling location does not get pushed onto stack. I can't use IJMP because I can't clobber any registers.

taskSwitch() is only called from ISRs (for preemtive kernel) or wait(), which tasks call when they have nothing to do, instead of waiting for ISR tick. Therefore when taskSwitch() returns, it jumps straight back into user code and skips the epilog of the ISR or wait() that called it.

The current solution is to take the mangled name of the taskSwitch() function copied out of an assembly call line and use that as rjmp's argument in inline assembly.

Real example:
And yes, I'm actually using a C++ static function because I want the syntactic sugar (and, if written correctly, there is no overhead to C++)

original C++:

OS::taskSwitch();

Becomes this assembly function call:

call _ZN2OS10taskSwitchEv

But I want this:

rjmp _ZN2OS10taskSwitchEv

Which works if I enter that explicitly, but I want the compiler to mangle the names for me. (I guess there is a pattern to the mangled names so they shouldn't change?)

So, does anyone know if this is possible?

If this isn't possible, I have another solution where I can push a register onto stack first (push rX), then call taskSwitch(), then pop the return location to the saved register (pop rX, pop rX), then continue with the context save and rearrange context restore accordingly. This method however has a 2 clock penalty...

Pushing AVRs to their limits

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

What about Goto?

No RSTDISBL, no fun!

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

goto only works when jumping to a place within the same function that the goto is in.

Regards,
Steve A.

The Board helps those that help themselves.

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

cinderblock wrote:
I'm writing a task switcher and want to be able to call my OS's taskSwitch() function using an [R]JMP so the calling location does not get pushed onto stack.
It's pretty simple to write a macro that utilizes inline assembly code to perform a jmp to a symbolic entry. See this thread for some example code.

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

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

Brutte wrote:
What about Goto?

Doesn't seem to work:

void OS::wait() {
 goto OS_TASK_SWITCH;
}

void OS::taskSwitch() {
 OS_TASK_SWITCH:
  // ...
}

gives:

OS.cpp: In static member function 'static void OS::wait()':
OS.cpp:60: error: label 'OS_TASK_SWITCH' used but not defined
OS.cpp: In static member function 'static void OS::taskSwitch()':
OS.cpp:66: warning: label 'OS_TASK_SWITCH' defined but not used

I guess you can't across functions... I'm also trying to do this across object files...

Pushing AVRs to their limits

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

Quote:
when taskSwitch() returns, it jumps straight back into user code

I guess you know:
http://en.wikipedia.org/wiki/Pro...

No RSTDISBL, no fun!

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

Brutte wrote:
I guess you know:
http://en.wikipedia.org/wiki/Pro...

Nope! But nice read. Thanks.

I'm looking for preemption though.

I plan to eventually publish my code, but... yeah... after I get a first complete version... big plans

Pushing AVRs to their limits

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

You need a 'long jump' instruction instead of goto. But I don't know what it is :)

--greg
Still learning, don't shout at me, educate me.
Starting the fire is easy; the hardest part is learning how to keep the flame!

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

A task switch function will return to it's caller eventually, after it was called in another thread. Therefore calling it using rjmp is inappropriate, except after the function epilogue instead of a ret instruction, which can't be done using inline assembly.

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

Have you looked at setjump/longjump? I've never used them but they may fit the bill here.

In every RTOS I've seen (so far) the pre-emptive task switch occurs as a stack/register switch. I'm not sure how you would perform that using solely C++. You might want to look at FreeRTOS both as an example and because their documentation is a good intro to an RTOS. For ATmegaxxx processors there has been lots of discussion and examples of FreeRTOS running on AVR.

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

stu_san wrote:
Have you looked at setjump/longjump? I've never used them but they may fit the bill here.

In every RTOS I've seen (so far) the pre-emptive task switch occurs as a stack/register switch. I'm not sure how you would perform that using solely C++. You might want to look at FreeRTOS both as an example and because their documentation is a good intro to an RTOS. For ATmegaxxx processors there has been lots of discussion and examples of FreeRTOS running on AVR.


setjmp looks like it introduces an overhead, and messes with context, which I'm trying to do myself so I don't have any free registers and can't change SP at all. (though there are reasons to use gcc to do everything, if it does it right, which seems crazy to implement)

I'm using inline assembly to save and restore the context and I used the ideas in FreeRTOS to get started, but I've got some other features in mind so I need to do some things a certain way.

Pushing AVRs to their limits

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

I think that something like

asm (" rjmp %0" : : ???(OS::taskSwitch));

will do what you asked for.
As others have noted,
what you asked for might not be all that useful.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

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

skeeve wrote:
I think that something like
asm (" rjmp %0" : : ???(OS::taskSwitch));

will do what you asked for.
As others have noted,
what you asked for might not be all that useful.


That was the first thing I looked to but the inline assembly documentation doesn't show how to use call or [r]jmp nor does it have any suitable operand constraints for a function pointer input.

Looking into the name mangling method, Wikipedia suggests gcc is consistent with how names are clobbered, so my current solution should be stable enough/predictable enough and maybe suitable for macroization. A quick example of how the conversion works:

OS::taskSwitch(void) becomes:
_Z N 2OS 10taskSwitch E v

_Z: mangled name prefix
N: nested name (class/namespace)
#foo: # of characters in this class/namespace's name
E: end
v: void (the argument, return value is not encoded in mangled name)

Pushing AVRs to their limits

Last Edited: Tue. Mar 15, 2011 - 09:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cinderblock wrote:
skeeve wrote:
I think that something like
asm (" rjmp #0" : : ???(OS::taskSwitch));

will do what you asked for.
As others have noted,
what you asked for might not be all that useful.


That was the first thing I looked to but the inline assembly documentation doesn't show how to use call or [r]jmp nor does it have any suitable operand constraints for a function pointer input.
I'm not sure, but I think
asm (" JMP %0\n" : : "i"(OS::taskSwitch));

does the job.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

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

skeeve wrote:
I'm not sure, but I think
asm (" JMP %0\n" : : "i"(OS::taskSwitch));

does the job.


Wow! where did you find the "i" constraint?
I tried that and .lst file shows it's quite close:
[r]jmp gs(_ZN2OS10taskSwitchEv)

but also makes the assembler barf "Error: garbage at end of line" for the line in question. Anyone know what the gs() in assembly is? Or where the documentation for "i" is?

Pushing AVRs to their limits

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

Quote:
Anyone know what the gs() in assembly is? Or where the documentation for "i" is?
http://gcc.gnu.org/onlinedocs/gc...

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

atomicdog wrote:
Quote:
Anyone know what the gs() in assembly is? Or where the documentation for "i" is?
http://gcc.gnu.org/onlinedocs/gc...

Lol, I was just looking at that page and must have missed it :(

That still doesn't explain the error or the gs()

Pushing AVRs to their limits

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

I have a vague recollection of gs() being described in the avr-libc manual but I can't find it now.

Last Edited: Tue. Mar 15, 2011 - 11:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cinderblock wrote:
atomicdog wrote:
Quote:
Anyone know what the gs() in assembly is? Or where the documentation for "i" is?
http://gcc.gnu.org/onlinedocs/gc...

Lol, I was just looking at that page and must have missed it :(

That still doesn't explain the error or the gs()

Apparently the compiler is doing a right thing,
but there is a bug in the assembler: http://www.mail-archive.com/avr-...
It's been known since last May.
That said, I'm not clear on why the gs() would be needed.
From cinderblock's experience, it seems to be not be necessary,
at least when the symbol is used directly.

Edit: I get the gs() when compiling for an atmega168.
Edit: This bug from last October is spot on: http://www.mail-archive.com/bug-...

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

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

So I guess that means the current solution is to just use the manually mangled name with rjmp in the assembly.

Another quick question, is there any reason to jmp? Maybe just because the rjmp offset can only reach so far? Would the linker then take care of keeping the code together? jmp also takes an extra cycle, and is only available on certain devices.

Pushing AVRs to their limits

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

Quote:

Another quick question, is there any reason to jmp? Maybe just because the rjmp offset can only reach so far? Would the linker then take care of keeping the code together? jmp also takes an extra cycle, and is only available on certain devices.

RJMP can only reach +/-4KB. The linker will not group code to keep things in range but will (hopefully!) produce an error when a hard coded RJMP is asked to reach something out of range. Almost certainly the better idea is to always hard code JMP/CALL rather than RJMP/RCALL then pass -Wl,--relax to the linker which instructs it to drop these back to RJMP/RCALL if the targets are in range.

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

Another possibility is to sidestep name-mangling and specify the name yourself:

extern func () asm ("FUNC");

I'm not sure of the syntax for C++ static methods.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles