Search code examples
c++objectmemory-mapped-io

Memory-mapped C++ objects non hardware members


I am developing a driver for a piece of memory mapped hardware using C++ and I defined a class that represents this device. It looks something like this:

class device
{
method1();
method2();

private:
device_register reg1;
device_register reg2;

}

The class has two files: device.cpp and device.h. The member variables represent actual registers on the device itself. Now I want to define more members that are not actual registers on the device itself, but I cannot define them inside the class because if I do, they will be defined at the memory mapped location of the device, which could contain other devices/registers. if I define them as public, that breaks the standard layout and the class won't work anymore.

So what I did is that I defined them as global variables outside the class definition. The problem is if I define more than one object, they will all share these global variables, but I want each object to have its own, how can I do this?


Solution

  • You should create a separate POD (Plain Old Data) struct for the registers, because a non-POD class/struct could ruin your memory mapping even without adding extra data members. In POD types, the offset of the first data member is guaranteed to be 0, which is what you need. In non-POD types this contract is not always followed, e.g. it may be disrupted by vtables or RTTI. True, you could dutifully avoid only the changes that would change the offset of the first data member, but a more robust solution is to stick to all the POD requirements so that the standard requires your compiler to do what you want.

    The requirements for a POD type can be found at cppreference here, but an easy mnemonic is that a struct with nothing but native type members will always be POD.

    You should also make sure the registers are marked "volatile," though I have a hunch that if you already have a special "device_register" typedef, it includes a volatile specifier. In which case, you don't need an extra one in your own struct.

    Example:

    struct MyPeripheralRegisters
    {
    volatile device_register reg1;
    volatile device_register reg2;
    };
    
    class MyPeripheralDriver
    {
    private:
      MyPeripheralRegisters* hardware = MY_MEMORY_MAPPED_ADDR;
      int my_private_state;
    public:
      void method1();
      void method2();
    };
    

    This pattern for memory mapped components is often used in embedded driver libraries, so it has the principle of least astonishment on its side.