2

I have seen in the past the following trick:

struct MyStruct
{
   field 1;
   field 2;
   field 3;
};

void f()
{
   MyStruct *example = (memory mapped peripheral address);
}

This basically makes it so that the variable example now contains the address (value) of a memory mapped peripheral, and then field 1 is at the same offset, field 2 is at (base address + sizeof(field 1)) and so on.

This is useful when there is multiple offsets associated with the peripheral that are in contiguous memory as it provides a layer of abstraction.

I am wondering if you can do the same with a C++ object, and how to make this object a singleton and how would inheritance affect this kind of mapping.

Makogan
  • 8,208
  • 7
  • 44
  • 112
  • In principle there is no difference between `struct` and `class`. – πάντα ῥεῖ May 29 '17 at 20:12
  • That is not completely true, as class has inheritance, and it's also possible for the compiler to shuffle values around. – Makogan May 29 '17 at 20:21
  • You can inhertit from and to `struct`s equally well. Read [here](https://stackoverflow.com/questions/2750270/c-c-struct-vs-class) what the difference is. – πάντα ῥεῖ May 29 '17 at 20:22
  • 1
    @Makogan the single and only difference between `struct` and `class` is the default access specifier in definitions. – Quentin May 29 '17 at 20:22
  • Yes, but g++ compilers do name mangling and other similar tricks when compiling. in fact I know there exists a possibility of fields not being compiled in the order that they were defined in the source code. I want to know whether there is a risk of that affecting this particular case. – Makogan May 29 '17 at 20:55
  • @Makogan What does _name mangling_ have to do with that?? Any symbols are stripped off after linking anyways. Also you can use attributes to steer how your members are ordered. You can look at [this question of mine](https://stackoverflow.com/questions/18251815/creating-an-array-initializer-from-a-tuple-or-variadic-template-parameters) where I was implementing a small database supposed to access a flash devices memory. I'm pretty sure you can also use a _memory mapped_ device in the same way without a driver. – πάντα ῥεῖ May 29 '17 at 21:03

2 Answers2

4

I am wondering if you can do the same with a C++ object,

Yes, the same can be done. I have used C++ for memory mapped io in telecomm system development for more than a decade. I seldom dealt with structs in embedded software, a struct has no advantages over a class. Also, I prefer to mask and shift to handle fields, and I have developed a bit of distaste for bit fields.

For struct and class instances, the data layout and packing are possible to determine and confirm, and thus an assert() can inform at runtime when the data has been built incorrectly.

a) neither C nor C++ provide memory layout semantics, and

b) compiler options can change the results (packing, alignment, field rearrangement)

These two items required a runtime check of some sort. We typically asserted on data attribute total length, and one or more distances between starts of field offsets, all in a named-ctor.

Many compilers provide pragmas to support memory-mapped-io packing and alignment issues. Unfortunately, often the pragma's of different compilers have different names. This is usually not important, as hardware specific software is almost never portable anyway. All pragmas I encountered seem to work. Ultimately we chose to not use them.

Note that endianness also has impacts, but the hardware you work with will be (or perhaps must be) designed with the correct endianness.

I have most of my embedded system experience with the tool vxWorks, and some with OSE. Both using GCC prior to 2011.

and how to make this object a singleton and

I find no value in singletons. But I believe a singleton can be built in the conventional way as the code of an instance is not stored in a data field. They are separate. And when you invoke your singletonGet() method, it will need to do the mapping of the data 'out' to the hw address.

The most typical mechanism of 'how to address a hw at address 0xAAAA0001' is to cast a uint64_t enumerated address to a pointer

 class FooHW; // hardware class

 FooHW* foo = reinterpret_cast<FooHW*>(AAAA0001_uint64_t); // enum addr

This skips all the ctor / dtor stuff. And this is desirable. How you access a memory mapped equipment generally must not disturb the operating hw during a sofware restart. A warm start (where hw is already running, but software has been restarted) is far more common during both development and operation than a cold start (aka power bounce).

It is possible to use placement 'new' (and 'delete') to create an instance at the correct memory address. But this has no advantage. You should research cold-start, warm-start, protection-switch, etc. The Foo* pointer residing with the rest of the code is typical.

how would inheritance affect this kind of mapping.

I suppose all these questions are tool specific. What I have seen is that the base class data attributes is pre-pended to the derived class data attributes. It is possible some tools do it the other way around.

In my work, I can not remember a memory mapped instance to be derived. However, I think it can work, just more effort which I would choose to avoid.

2785528
  • 5,438
  • 2
  • 18
  • 20
1

You don't need to make it a singleton at all. By the technique you're using you're always going to be pointing at the same address. Doesn't matter which pointer variable you use to get there, as long as it contains the right address.

The syntax looks like this:

#define DEV_REG_SET (*(volatile MyStruct *)(0x4CF3217))

where 0x4CF3217 is your special device address and you would use it like

long f1 = DEV_REG_SET->field1;  // to read from device

You absolutely do need to declare your pointer as a pointer to a volatile thing. That will ensure the compiler always reads and writes the thing pointed to and doesn't optimize reads and writes away.

For a great discussion of volatile and how to properly use it see this blog post "Nine ways to break your systems code using volatile".

Note that I've seen blog posts that claim that some compilers may or may not properly respect the volatile keyword when it is applied to a field of a struct. But I haven't yet seen anything on the web that would say any compiler doesn't respect the volatile on a pointer to struct. But you never know, so look at the generated machine language before committing to this (and remember to check it if you're getting funky results).

Now, with respect to inheritance - as long as you stay away from anything where the compiler adds hidden data to a struct you'll be ok. But you know that anyway. After all, you already know you have to make sure you understand all the padding that the compiler might do. So you have to stay away from having any virtual methods or virtual inheritance, and you need to eschew access specifiers (which can affect field order). In other words, stick to PODs - plain old (C) data structures.

davidbak
  • 5,775
  • 3
  • 34
  • 50