Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading a struct from a read only memory

I'm working on a embedded system, where some calibration data is stored in the flash memory. The calibration data is stored in a struct which is placed in a special section that the linker knows to place in the flash:

struct data_block {
    calibration_data mData;
    uint16_t mCheckSum;
};

//Define to compile the fixed flash location for image data
const data_block __attribute__((section (".caldata"))) gCalibrationData{};

where calibration_data is another POD struct which contains the actual values.

The problem is that if I now simply write the following:

const data_block data{gCalibrationData};

if (CheckSum(&(data.mData)) == data.mCheckSum) {
    //do stuff
} else {
    //Error
}

this always goes to the error branch, even though the actual checksum in flash is absolutely correct (writing this a bit differently makes it work, see below).

This is of course understandable: the compiler sees a const global object, which is default-initialized, so it knows all the values, so I guess it actually optimizes out the whole if (if I debug-printf data via a uint16_t *, I actually get the correct values).

The way I think would be correct is to define

const volatile data_block __attribute__((section (".caldata"))) gCalibrationData{};

However, now I have the problem that I can't assign a volatile struct to non-volatile, i.e. const data{gCalibrationData}; does not compile. The same problem also appears if I try to access through a const volatile data_block *.

There's at least two or three ways I can make this work, and I don't like any of them:

  1. remove the const (and volatile) qualifier from gCalibrationData. However, this is a bit of a hack based on the compiler not being clever enough to guarantee that gCalibrationData is never touched in my program, and on the other hand, I'd like to keep to const qualifier, since trying to write to gCalibrationData by assigning is a hard fault.
  2. access gCalibrationData via const gCalibrationData * volatile pData (yes, the volatile is exactly where I mean it). Accessing through a pointer which is volatile forces the compiler to actually load the data. Again, this seems like a hack, since the pointer itself certainly isn't volatile.
  3. give data_block and calibration_data an assignment operator taking const volatile &, and assign field by field in them. This seems to be correct from the language point of view, but then whenever calibration_data changes I need to edit the assignment operator by hand. Failing to do so will produce hard-to-detect bugs.

My question: what would be the correct way to read the calibration data? My ideal criteria would be:

  • the global object itself is const, to catch unintended writes.
  • no undefined behaviour
  • access by assigning the struct directly to another struct
  • or at least so that I'm not required to remember to assign each variable of primitive type in calibration_data, see option 3. above
  • bonus points for thread-safety, although in my specific case only a single thread ever reads or writes the flash (all other "threads" are interrupts).
like image 567
Timo Avatar asked Sep 10 '18 12:09

Timo


People also ask

Is struct contiguous in memory?

Because the contents of a struct are stored in contiguous memory, the sizeof operator must be used to get the number of bytes needed to store a particular type of struct, just as it can be used for primitives.

Are structs stored in memory?

Structs and classes are data types, so they don't occupy memory at runtime. Structs and classes are really compile-time things, not runtime things. Objects (variables) of those data types do occupy memory.

How much memory is a struct?

In 32 bit processor, it can access 4 bytes at a time which means word size is 4 bytes. Similarly in a 64 bit processor, it can access 8 bytes at a time which means word size is 8 bytes. Structure padding is used to save number of CPU cycles.

What is read only data in C?

In practice, this means that a C or C++ program that wants to be portable has to avoid modifying constant strings. In general, the compiler will not allow you to modify the contents of of "const" variables, so you can consider "const" to mean "read only" in most cases.


1 Answers

One solution could be to declare a buffer in a separate source file, to inform the linker of size of data_block and then define gCalibrationData to be a symbol whose value is the begining of this buffer:

data_block.cpp:

//no initialization performed here, just used to
//transmit to the linker the information of the size
//and alignment of data_block
extern "C"{//simpler name mangling
[[gnu::section(".caldata")]] volatile
aligned_storage<sizeof(data_block),alignof(data_block)> datablock_buffer;
}

//then we specify that gCalibrationData refers to this buffer
extern const volatile data_block
gCalibrationData [[gnu::alias("datablock_buffer")]];

Alternatively the definition of gCalibrationData symbol can be done via a linker script:

SECTIONS{
  .caldata : {
    gCalibrationData = . ;
    data_block.o(.caldata)
    }
  }

gCalibrationData is an alias to an data_block_buffer. This will not cause undefined behavior because such aliasing is permitted by the language: data_block_buffer provides storage for gCalibrationData.

Semanticaly, the extern specifier is used to say that this declaration is not a definition of the value of gCalibrationData. Nevertheless the alias attribute is a definition of the symbol for the linker.

data_block.hpp

extern const volatile data_block gCalibrationData;

//and copy must be enabled for volatile:
struct data_block{
  /*...*/
  data_block(const data_block&) =default; 

  data_block& operator=(const data_block&) =default;

  data_block(const volatile data_block& other){
    //the const cast means: you are responsible not to 
    //perform this operation while performing a rom update.
    memcpy(this,const_cast<const data_block*>(&other);
    }

  data_block& operator=(const volatile data_block& other){
    memmove(this,const_cast<const data_block*>(&other);
    //or memcpy if you are sure a self assignment will never happen.
    return *this;
    }
  };
like image 91
Oliv Avatar answered Oct 08 '22 02:10

Oliv