brag
brag

Reputation: 1

Access C++ non-POD class data from naked asm function

Is it possible to make this mixed c++/asm function to be standard-compliant? Function ePendSV() must have this layout:

ePendSV: //function entry point
    mrs r0,PSP
    stmdb r0!,{r4-r11,lr}
// compiler can generate any code here doing these things:
    readyTcbQueue.pTcb.runTcb->psp = r0;
    readyTcbQueue.pTcb.runTcb=readyTcbQueue.pTcb.readyTcb;
    r0 = readyTcbQueue.pTcb.readyTcb->psp;
// work with r0 in assembly
    ldmia r0!,{r4-r11,lr}
    msr PSP,r0
    bx lr

readyTcbQueue.pTcb is a simple struct object with just two pointers to BragOsTcb objects;

struct{
    BragOsTcb *runTcb;
    BragOsTcb *readyTcb;
};

BragOsTcb is non-POD class but without virtual functions and virtual inheritance and looks like this:

class BragOsTcb : public TcbCdllq, public TimerTcbCdllq, public BragOsObject{
public:
    BragOsTcb();
....
private:
.....
public:
    unsigned long psp;
....
};

TcbCdllq, TimerTcbCdllq, BragOsObject are also simple classes with similar layout and without virtuals. But they are also non-POD.

I've made this code it works on gcc and clang but it's a nonstandard hack and might not work.

__attribute__((naked)) void ePendSV(){
  asm volatile("\
    mrs r0,PSP \n\
    stmdb r0!,{r4-r11,lr} \n\
\n\
    ldr r1,=%0 \n\
    ldmia r1,{r2,r3}  // r2=runTcb, r3=readyTcb \n\
    str r0,[r2,%1] // save psp \n\
    str r3,[r1,#0] // runTcb=readyTcb \n\
    ldr r0,[r3,%1] // readyTcb->psp \n\
\n\
   // restore LR(EXC_RETURN),R11-R4 from new PSP, set new PSP, return \n\
    ldmia r0!,{r4-r11,lr} \n\
    msr PSP,r0 \n\
    bx lr \n\
    "   :
        : "i"(&readyTcbQueue.pTcb),"i"(&(((BragOsTcb*)0)->psp))
        : );
    // it'is an offsetof hack which might not work
}

Thanks!

Upvotes: 0

Views: 237

Answers (2)

brag
brag

Reputation: 1

Since extended asm in naked function is not supported by gcc/clang

naked This attribute allows the compiler to construct the requisite function declaration, while allowing the body of the function to be assembly code. The specified function will not have prologue/epilogue sequences generated by the compiler. Only basic asm statements can safely be included in naked functions (see Basic Asm). While using extended asm or a mixture of basic asm and C code may appear to work, they cannot be depended upon to work reliably and are not supported. https://gcc.gnu.org/onlinedocs/gcc/ARM-Function-Attributes.html#ARM-Function-Attributes

We can use normal C++ function and jump/call it from naked

typedef unsigned long U32;

__attribute__((naked)) void ePendSV(){
    asm volatile("\
        mrs r0, PSP \n\
        stmdb r0!, {r4-r11,lr} \n\
        b realSwitchContext"); // this solution costs single jump instruction named **b**. It's valid for ARM-assembly in this case.
                              // Stack pointer has been saved in r0 register. we dont care about stack for now.
}

extern "C" void realSwitchContext(U32 psp){
    readyTcbQueue.pTcb.runTcb->psp = psp;
    BragOsTcb *tcb = readyTcbQueue.pTcb.readyTcb;
    readyTcbQueue.pTcb.runTcb = tcb;
    psp = tcb->psp;
    asm volatile("\
        ldmia %0!, {r4-r11,lr} \n\
        msr PSP, %0 \n\
        bx lr" : : "r"(psp)); // we just changed stack pointer then can return here and don't care about C++ stacked data
}

There is some bug in implementation above because of two stack pointers - MSP and PSP. MSP will be corrupted by last bx lr instruction if C++ will use the stack. Solution below is correct but costs 2 'expensive' instructions - bl (call) and bx lr (return)

__attribute__((naked)) void ePendSV(){
    asm volatile("\
        mrs r0, PSP \n\
        stmdb r0!, {r4-r11,lr} \n\
        bl realSwitchContext \n\
        ldmia r0!, {r4-r11,lr} \n\
        msr PSP, r0 \n\
        bx lr");
}

extern "C" U32 realSwitchContext(U32 psp){
    readyTcbQueue.pTcb.runTcb->psp = psp;
    BragOsTcb *tcb = readyTcbQueue.pTcb.readyTcb;
    readyTcbQueue.pTcb.runTcb = tcb;
    return tcb->psp;
}

Upvotes: 0

Sebastian Cabot
Sebastian Cabot

Reputation: 1822

So what you want is to access the psp member of BragOsTcb from a naked function that can have no arguments and further more it is called from as a hardware interrupt handler so you can't add any other code to load the address for you. BragOsTcb is non POD so you are afraid that the psp member offset from the beginning of the class will be different for different compilers.

I would suggest the following scheme:


struct BragOsTcbWrapper
{
    BragOsTcb* this_;
    unsigned long psp;
};

struct{
    BragOsTcbWrapper *runTcb;
    BragOsTcbWrapper *readyTcb;
};

class BragOsTcb : public TcbCdllq, public TimerTcbCdllq, public BragOsObject{
public:
    BragOsTcb()
    : pspHolder({this,0})
    {

    }
....
private:
.....
public:
    BragOsTcbWrapper pspHolder;
....
};

Now for the asemblly you will do:


__attribute__((naked)) void ePendSV(){
  asm volatile("\
    mrs r0,PSP \n\
    stmdb r0!,{r4-r11,lr} \n\
\n\
    ldr r1,=%0 \n\
    ldmia r1,{r2,r3}  // r2=runTcb, r3=readyTcb \n\
    str r0,[r2,%1] // save psp \n\
    str r3,[r1,#0] // runTcb=readyTcb \n\
    ldr r0,[r3,%1] // readyTcb->psp \n\
\n\
   // restore LR(EXC_RETURN),R11-R4 from new PSP, set new PSP, return \n\
    ldmia r0!,{r4-r11,lr} \n\
    msr PSP,r0 \n\
    bx lr \n\
    "   :
        : "i"(&readyTcbQueue.pTcb),"i"(&(((BragOsTcbWrapper*)0)->psp))
        : );
}

I think this will work for you

Upvotes: 0

Related Questions