Reputation: 1
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
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
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