We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
Hi,
This is the code I need to make compatible with RV.
void vPortYieldProcessor( void ) { __asm{ ADD lr, lr, #4 }; /* Perform the context switch. */ portSAVE_CONTEXT(); vTaskSwitchContext(); portRESTORE_CONTEXT(); }
With this code. I received error "lr is undefined"
After reading the manual, and probably understood incorrectly, I put declaration
#define lr __return_address()
before the above function, and received this error instead
..\..\Source\portable\Keil\ARM7\portISR.c(98): error: #1093: Must be a modifiable lvalue
Anyone can help? Thanks
You can resolve this problem by using embedded assembler.
This is hardly a "problem" with lr.
The intrinsic __return_address() is prototyped as a access function to the current value of lr, not the actual register. You cannot assign to it, just read from it.
I have a few comments on your code snippet:
1 - You are using the inline assembler. It is actually a high-level assembly-like language, not an 'assembler' as you might expect. Your __asm{} code is subject to code optimizations, code reordering and register remap. Also, There are severe restrictions imposed on registers access, and you can't control how the compiler will use temporary values on cpu registers.
2 - More importantly, the inline code block should not rely on values of any registers on entry or on exit, since any register context used will be discarded. Especially, you are not allowed to modify sp, lr or pc.
3 - There is another assembly coder supplied by the C compiler, the embedded assembler. You can write unrestricted asm functions wrapped in a C/C++ function that is compliant with the AAPCS (arguments passed in r0-r3, etc). With this you can certainly access lr as you please, BUT:
4 - Your code seems to be the entry gate for a task-switch kernel. There are problems in many levels of doing this as you are.
4a - The task switch code must all be run in a priviledged cpu state, like supervisor mode. I can't see where your function is being declared as a priviledged mode;
4b - If you access the link register from C via an embedded call to get access to the link register, you will get the wrong lr value, because it will be the lr for the vPortYieldProcessor() function, instead of the user function. Moreover, in a priviledged state, the user lr is saved in a banked register specific to the supervised state, like lr_svc, or lr_irq.
4c - Your function seems to be adjusting the return address for an exception or a __irq state call. Are you declaring this function as a IRQ handler? Do you know the difference of a _svc and a _irq handler?
4d - You must make sure your function calls do not mess up the stack or the user registers before you save the context.
The ARM core is beautifully tailored for task switching, and the RVCT makes the integration of supervisor modes very easy, but you still must know exactly what you are doing.
As an example of a very fast task switcher in ARM7 asm, look at this code snippet:
EXPORT SWI_GATE SWI_GATE PROC ROUT ARM ; context save. [sp] points to thread control block. (17 cycles) STMDB sp, {r4 - r14}^ ; (12 cyc) store user registers in TCB (aapcs context) MRS r4, spsr ; (1 cyc) get user thread CPSR STR r4, [sp] ; (2 cyc) store user CPSR in TCB STR lr, [sp, #0x00000004] ; (2 cyc) store resume address in TCB ; get SWI service request number (6 cycles). ; (If you need a switch 83ns faster, pass the service # in r12 instead.) TST r4, #0x20 ; probe T bit of current thread to check for THUMB mode LDREQ r4, [lr, #-4] ; thread in ARM state: load 32bit RISC word LDRNEH r4, [lr, #-2] ; thread in THUMB state: load 16bit RISC halfword BIC r4, #0xffffff00 ; clear all but lower 8 bits of service number ; jumptable for system service request in r4 (6 cycles) CMP r4, #MAX_SVC LDRLS pc, [pc, r4, lsl #2] ; dispatch to the selected service B ScheduleNextThread ; invalid service number. ignore and schedule the next thread. SWI_JUMPTABLE DCD svc_yield DCD svc_suspend DCD svc_create_thread DCD svc_destroy_thread ; ... ;-------------------------------------------------------------- svc_yield B ScheduleNextThread svc_suspend ; ... ScheduleNextThread ; ; [...] run scheduler and select next thread ; ; on exit, [sp] points to the new thread TCB ; 21 cycles aapcs context restore: for preemptive switch, see the _irq handler LDMIA sp, {r3, lr} ; get user saved cpsr and thread resume address LDMDB sp, {r4-r14}^ ; load the full user thread context MSR spsr_fsxc, r3 ; store user saved cpsr in supervisor spsr MOVS pc, lr ; resume user thread with cpsr ENDP
The above ASM handler is the SVC (or SWI) handler, and is used as the supervisor call for the kernel, like this:
// --- SWI calls prototypes --------------------------------------------------- void __svc(0x00) yield(void); void __svc(0x01) suspend(void); handle __svc(0x02) create_thread(thread_t*); // ...
It will take me sometime to digest all that, but thank you very much.
Your post will surely help me solve the problem.
Actually, that's an early version of the entry/leave code for the SVC gate of a multithreading nanokernel I'm writing for the ARM7.
I have stripped-off detailed comments to keep the code focused on the entry/exit sequences. The point I want to make is that handling the context saving/restore must be done in a way that preserves the calling context. Although it can be done with the RVCT C compiler (using the embedded assembler), it is much easier, clearer and probably faster to do that directly in assembly. But, in any case, you would probably not be able to do that in pure C code.