How to assign to a C++ volatile struct?

220 Views Asked by At

For a hardware driver, I need a method to write a C++ struct of 8 x uint32_t to a block of 8 successive 32-bit hardware registers. Here is my code:

#include "stdint.h"

typedef struct
{
    uint32_t a[8];
} TCmd;

class MyClass {
    public:
        void writeCommandBlock( TCmd* ap_src)
        
        {
             TCmd * p_dest = reinterpret_cast< TCmd*>(0x40080000); // base register address
             *p_dest = *ap_src;
        }
};

int main()
{
    MyClass m;

    TCmd cmd;
    cmd.a[0] = 12;

    m.writeCommandBlock(&cmd);
}

(The actual struct will have bit-fields and the register address will be defined by a constant).

I want to use a simple assignment operator (as shown above):

*p_dest = *ap_src;

because I am working with an ARM Cortex M4 processor and want to use the ldm/stm instructions, yielding something like:

        ldr     r1, [sp, #4]
        ldr     r0, [sp]
        ldm     r1!, {r2, r3, r12, lr}
        stm     r0!, {r2, r3, r12, lr}
        ldm     r1, {r2, r3, r12, lr}
        stm     r0, {r2, r3, r12, lr}

which should be faster than memcpy etc.

The problem comes when I enable optimisation - the assignment is optimised away. So I declare p_dest as volatile:

volatile TCmd * p_dest = reinterpret_cast< TCmd*>(0x40080000);

but the assignment line then gives error:

<source>:16:21: error: no viable overloaded '='
            *p_dest = *ap_src;
            ~~~~~~~ ^ ~~~~~~~
<source>:3:9: note: candidate function (the implicit copy assignment operator) not viable: 'this' argument has type 'volatile TCmd', but method is not marked volatile
typedef struct
        ^
<source>:3:9: note: candidate function (the implicit move assignment operator) not viable: 'this' argument has type 'volatile TCmd', but method is not marked volatile
1 error generated.
Compiler returned: 1

How can I fix this please?

1

There are 1 best solutions below

4
On

I'm assuming you don't want TCmd to be volatile so constructing it is fast and only the final commit to the hardware registers should be volatile.

How about this:

#include <cstdint>
#include <array>
#include <algorithm>

using TCmd = std::array<uint32_t, 8>;

class MyClass {
    public:
        void writeCommandBlock(const TCmd & ap_src) 
        {
             volatile uint32_t * p_dest = reinterpret_cast<volatile uint32_t *>(0x40080000); // base register address
             std::copy(&ap_src[0], &ap_src[ap_src.size()], p_dest);
        }
};

int main()
{
    MyClass m;

    TCmd cmd;
    cmd[0] = 12;

    m.writeCommandBlock(cmd);
}

Note: With gcc and -O2 this turns into a loop, with -O3 is unrolls into 8 loads and stores. But the loops seems to be the preferable code.

While a ldm opcode could be preferable I believe stm on MMIO registers is undefined. At least on something like the RaspberryPI the device interface is horribly fragile and likely to do the wrong thing.

You should probably also add a memory barrier after the write to ensure it actually happens and completes.