Write-Only pointer type

C++CPointersReadonlyConst Correctness

C++ Problem Overview


I'm writing software for an embedded system.

We are using pointers to access registers of an FPGA device.
Some of the registers are read-only, while others are write-only.

The write-only registers will produce undefined values when read.

I want to define a pointer type that will allow the compiler to detect when reading values from a write-only registers (a.k.a. dereferencing).

Can a write-only pointer be created using only C language syntax?
(We are developing first prototype using C, but moving to C++ on 2nd generation.)

How can an efficient write-only pointer be created in C++? (Remember, this is not tracking items in dynamic memory, but accessing hardware addresses.)

This code is used on an embedded system where safety and quality are highest concerns.

C++ Solutions


Solution 1 - C++

I'd probably write a tiny wrapper class for each:

template <class T>
class read_only {
    T volatile *addr;
public:
    read_only(int address) : addr((T *)address) {}
    operator T() volatile const { return *addr; }
};

template <class T>
class write_only { 
    T volatile *addr;
public:
    write_only(int address) : addr ((T *)address) {}

    // chaining not allowed since it's write only.
    void operator=(T const &t) volatile { *addr = t; } 
};

At least assuming your system has a reasonable compiler, I'd expect both of these to be optimized so the generated code was indistinguishable from using a raw pointer. Usage:

read_only<unsigned char> x(0x1234);
write_only<unsigned char> y(0x1235);

y = x + 1;         // No problem

x = y;             // won't compile

Solution 2 - C++

I would use a combination of structs to rappresent the register and a pair of functions to handle them.

In a fpga_register.h you would have something like

#define FPGA_READ = 1; 
#define FPGA_WRITE = 2;
typedef struct register_t {
    char permissions;
} FPGARegister;

FPGARegister* fpga_init(void* address, char permissions);

int fpga_write(FPGARegister* register, void* value);

int fpga_read(FPGARegister* register, void* value);

with READ and WRITE in xor to express permissions.

Than in the fpga_register.c you would define a new struct

typedef struct register_t2 {
    char permissions;
    void * address;
} FPGARegisterReal;

so that you returns a pointer to it instead of a pointer to FPGARegister on fpga_init.

Then, on fpga_read and fpga_write you check the permissions and

  • if the operetion is allowed, cast back the FPGARegister from the argument to a FPGARegisterReal, execute the desired action (set or read the value) and return a success code
  • if the operation is not allowed, just return an error code

This way, no one including the header file will be able to access the FPGARegisterReal structure, and thus it will not have direct access to the register address. Obviously, one could hack it, but I'm quite sure that such intentional hacks are not your actual concerns.

Solution 3 - C++

I've worked with a lot of hardware, and some of which has "read only" or "write only" registers (or different functions depending on whether you read or write to the register, which makes for fun when someone decides to do "reg |= 4;" instead of remembering the value it should have, set bit 2 and write the new value, like you should. Nothing like trying to debug hardware that has random bits appearing and disappearing from registers you can't read! ;) I have so far not seen any attempts of actually blocking reads from a write-only register, or writes to read-only registers.

By the way, did I say that having registers that are "write only" is a REALLY bad idea, because you can't read back to check if the software has set the register correctly, which makes debugging really hard - and people writing drivers don't like debugging hard problems that could be made really easy by two lines of VHDL or Verilog code.

If you have some control over the register layout, I would suggest that you put "readonly" registers at a 4KB-aligned address, and "writeonly" registers in another 4KB-aligned address [more than 4KB is fine]. Then you can program the memory controller of the hardware to prevent the access.

Or, let the hardware produce an interrupt if registers that aren't supposed to be read are being read, or registers that aren't supposed to be written are written. I presume the hardware does produce interrupts for other purposes?

The other suggestions made using various C++ solutions are fine, but it doesn't really stop someone who is intent on using the registers directly, so if it's really a safety concern (rather than "let's make it awkward"), then you should have hardware to protect against the misuse of the hardware.

Solution 4 - C++

In C, you can use pointers to incomplete types to prevent all dereferencing:


/* writeonly.h */
typedef struct writeonly *wo_ptr_t;

/* writeonly.c */
#include "writeonly.h"

struct writeonly {
  int value 
};

/*...*/

   FOO_REGISTER->value = 42;

/* someother.c */
#include "writeonly.h"

/*...*/

   int x = FOO_REGISTER->value; /* error: deref'ing pointer to incomplete type */

Only writeonly.c, or in general any code that has a definition struct writeonly, can dereference the pointer. That code, of course, can accidentally read the value also, but at least all other code is prevented from dereferencing the pointers all together, while being able to pass those pointers around and store them in variables, arrays and structures.

writeonly.[ch] could provide a function for writing a value.

Solution 5 - C++

I see no elegant way of doing it in C. I do however see a way of doing it:

#define DEREF_PTR(type, ptr) type ptr; \
typedef char ptr ## _DEREF_PTR;

#define NO_DEREF_PTR(type, ptr) type ptr; \

#define DEREFERENCE(ptr) \
*ptr; \
{ptr ## _DEREF_PTR \
attempt_to_dereference_pointer_ ## ptr;}

int main(int argc, char *argv[]) {
    DEREF_PTR(int*, x)
    NO_DEREF_PTR(int*, y);

    DEREFERENCE(x);
    DEREFERENCE(y); // will throw an error
}

This has the benefit of giving you static error checking. Of course, using this method, you'll have to go out and modify all of your pointer declarations to use macros, which is probably not a whole lot of fun.

Edit: As described in the comments.

#define READABLE_PTR(type, ptr) type ptr; \
typedef char ptr ## _READABLE_PTR;

#define NON_READABLE_PTR(type, ptr) type ptr; \

#define GET(ptr) \
*ptr; \
{ptr ## _READABLE_PTR \
attempt_to_dereference_non_readable_pointer_ ## ptr;}

#define SET(ptr, value) \
*ptr = value;


int main(int argc, char *argv[]) {
	READABLE_PTR(int*, x)
	NON_READABLE_PTR(int*, y);

	SET(x, 1);
	SET(y, 1);

	int foo = GET(x);
	int bar = GET(y); // error
}

Solution 6 - C++

Dan Saks has a Youtube presentation, that I couldn't, find where he introduces write-only registers for embedded devices.

Fortunately he wrote an article, that was easier to search, here: https://www.embedded.com/how-to-enforce-write-only-access/

This is the code from the article, updated for C++11

class write_only_T{
public:
    write_only_T(){}
    write_only_T(T const& v) : m(v){}
    write_only_T(T&& v) : m(std::move(v)){}
    write_only_T& operator=(T const& v){
        m = v;
        return *this;
    }
    write_only_T& operator=(T&& v){
        m = std::move(v);
        return *this;
    }
    write_only_T(write_only_T const&) = delete;
    write_only_T(write_only_T&&) = delete;
    write_only_T& operator=(write_only_T const&) = delete;
    write_only_T& operator=(write_only_T&&) = delete;
private:
    T m;
};

I don't think you need a special pointer type if you used this because write-only is a property of the value, but I can imagine a synthetic pointer-type that skips the value type. Likely you will need to introduce a write-only reference-type and so on.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThomas MatthewsView Question on Stackoverflow
Solution 1 - C++Jerry CoffinView Answer on Stackoverflow
Solution 2 - C++Giacomo TesioView Answer on Stackoverflow
Solution 3 - C++Mats PeterssonView Answer on Stackoverflow
Solution 4 - C++KazView Answer on Stackoverflow
Solution 5 - C++Martin SvanbergView Answer on Stackoverflow
Solution 6 - C++alfCView Answer on Stackoverflow