Question

I'd like to be able to use something like this to make access to my ports clearer:

typedef struct {
    unsigned rfid_en: 1;
    unsigned lcd_en: 1;
    unsigned lcd_rs: 1;
    unsigned lcd_color: 3;
    unsigned unused: 2;
} portc_t;

extern volatile portc_t *portc;

But is it safe? It works for me, but...

1) Is there a chance of race conditions?

2) Does gcc generate read-modify-write cycles for code that modifies a single field?

3) Is there a safe way to update multiple fields?

4) Is the bit packing and order guaranteed? (I don't care about portability in this case, so gcc-specific options to make it Do What I Mean are fine.)

Was it helpful?

Solution

  1. Handling race conditions must be done by operating system level calls (which will indeed use read-modify-writes), GCC won't do that.

  2. Idem., and no GCC does not generate read-modify-write instructions for volatile. However, a CPU will normally do the write atomically (simply because it's one instruction). This holds true if the bit-field stays within an int for example, but this is CPU/implementation dependent; I mean some may guarantee this up to 8-byte value, while other only up to 4-byte values. So under that condition, bits can't be mixed up (i.e. a few written from one thread, and others from another thread won't occur).

  3. The only way to set multiple fields at the same time, is to set these values in an intermediate variable, and then assign this variable to the volatile.

  4. The C standard specifies that bits are packed together (it seems that there might be exceptions when you start mixing types, but I've never seen that; everyone always uses unsigned ...).

Note: Defining something volatile does not cause a compiler to generate read-modify-writes. What volatile does is telling the compiler that an assignment to that pointer/address must always be made, and may not be optimised away.

Here's another post about the same subject matter. I found there to be quite a few other places where you can find more details.

OTHER TIPS

The keyword volatile has nothing to do with race conditions, or what thread is accessing code. The keyword tells the compiler not to cache the value in registers. It tells the compiler to generate code so that every access goes to the location allocated to the variable, because each access may see a different value. This is the case with memory mapped peripherals. This doesn't help if your MPU has it's own cache. There are usually special instructions or un-cached areas of the memory map to ensure the location, and not a cached copy, is read. As for being thread safe, just remember that even a memory access may not be thread safe is it is done in two instructions. E.g. in 8051 assembler, you have to get a 16 bit value one byte at a time. The instruction sequence can be interrupted by an IRQ or another thread and the second byte read or written, potentially corrupted.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top