[Bug 126] Add support for attribute to mark data as volatile.
Timo Sintonen via D.gnu
d.gnu at puremagic.com
Sun Jun 1 08:37:04 PDT 2014
I did not yet read the dip but here are some of my thoughts:
At the old days peripherals were simple. An uart might have a
control register, a status register and a data register, 8 bit
each. It just did not matter how they were accessed. Now a
peripheral like usb or ethernet may have tens of 32 bit registers.
The Basic language did not have pointers or any way to address a
certain location of memory. Several extensions were made to get
access to system locations. One common extension was poke and
peek functions. They had 16 bit address and 8 bit data. Basic did
not have any data types or stuctures.
D has pointers that can be used to access memory. It also has
several data types. A library function does not know if it should
do 8/16/32/64 bit access without templates. That would be too
complicated for such a low level operation like register access.
The registers of a peripheral may be defined as a struct and a
pointer of this struct type is used to access the registers.
There are individual registers but there may also be some sub
register sets inside the register set. A peripheral may have
common registers and then per channel registers. The register
struct may then have substructs or an array of register sets that
may be accessed as structs or arrays.
Yes, there are different kind of registers.
- Normal registers can be read and written. These are used as
normal control and status registers. Some bits may be changed by
hardware at any time. This may be a problem because it is
impossible to have a fully atomic access. The time between read
and write should be as short as possible.
- Read only registers may be used to represent the capabilities
of the peripheral or calibration values. They always return the
same data. Status registers represent the current state of the
hardware and may change any time when the conditions change.
Write to these registers has no effect.
- Write only registers are used to send data. The data packet is
written byte by byte to this same address. These type of
registers are also used to clear status. Reading the register may
return the last data or zero or anything else and the value
should be ignored.
- Bidirectional registers are used as data registers. A read will
return the last received byte and a write will transmit the byte
written.
Usually it does not matter if these registers are accessed wrong
way (write a read only or read a write only) so there is no need
to mark them different. They can all be volatile.
It is also common that one register has mixed read/write, read
only and write only bits. Many registers have also
undefined/reserved bits, which sometimes should be written with
zeros and sometimes left as they are.
One of the most common operations is to wait some status:
while ((regs.status&0x80)==0) { /* check timeout here */ }
The way to clear the status may be one of:
- write directly to the status bit
regs.status &= 0xffffff7f;
- write a 1 to the bit
regs.status |= 0x80;
- sometimes writing 0 to other bits has no effect and there is no
need to read-modify-write
regs.status = 0x80;
- sometimes status is cleared by writing to another bit
regs.status |= 0x200;
- sometimes there is a separate clear register
regs.statusclear = 0x80;
- sometimes accessing the data register clears status
automatically
- sometimes reading the status register clears the status. In
this case all status bits have to be checked at once.
Many of these have the result that reading the register does not
give back the data that was written.
And no, I did not read this on Wikipedia. All these forms of
access exist in the processor I use (STM32F407) It seems that
several teams have made the peripherals on the chip and every
peripheral has its own way to access it.
Another thing is: do I need to mark every member in a struct
volatile or is it enough to mark the struct definition or the
struct pointer. Will it go transitively to every member of an
array of substructs or do I need mark the substructs volatile?
One thing is the struct pointer. The peripherals have a fixed
address. If there is only one peripheral, the address can be a
compile time constant. If there are several similar peripherals,
the address may be known at compile time or it could be immutable
that is initialized at start.
Now I have to make the pointer shared to have the struct members
shared. This means the pointer is also mutable and volatile. The
pointer can not be cached and has to be fetched again every time
the struct variables are accessed. This decreases performance.
D has been marketed as a system language. Accessing registers is
an essential part of system programming. Whatever the method is,
it has to be in the language, not an external library function.
More information about the D.gnu
mailing list