This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Realview problem with "lr"

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.