All of the various hardware subsystems and drivers need to read/write to registers. Due to them being written by different people at different times, or being brought in from differing origins, they can differ drastically in how they are addressed, defined, or accessed. If this were standardized, any amount of the code would be more readable and serve as better documentation.
The Maple code might be an exemplar of how to go about this:
In https://github.com/KallistiOS/KallistiOS/blob/master/kernel/arch/dreamcast/include/dc/maple.h#L68
We define a base address, then follow it with defines that have the offset of each register from that base:
#define MAPLE_BASE 0xa05f6c00 /**< \brief Maple register base */ #define MAPLE_DMAADDR (MAPLE_BASE+0x04) /**< \brief DMA address register */
A macros are then defined:
`#define maple_read(A) ( ((vuint32)(A)) )
/** \brief Maple memory write macro. */
#define maple_write(A, V) ( ((vuint32)(A)) = (V) )`
That is used like this:
maple_write(MAPLE_RESET1, MAPLE_RESET1_MAGIC);
On the other end of the scale is the video/framebuffer system:
In https://github.com/KallistiOS/KallistiOS/blob/master/kernel/arch/dreamcast/hardware/video.c#L410
We make a pointer to the registers base:
static vuint32 *regs = (uint32*)0xA05F8000;
Then set them all by raw offsets to that base with raw magic numbers:
/* Blank screen and reset display enable (looks nicer) */ regs[0x3A] |= 8; /* Blank */ regs[0x11] &= ~1; /* Display disable */
Sort of halfway between is something like https://github.com/KallistiOS/KallistiOS/blob/master/kernel/arch/dreamcast/include/dc/ubc.h#L32 :
#define BARA (*((vuint32*)0xFF200000)) /**< \brief BARA register. */ #define BASRA (*((vuint8*)0xFF000014)) /**< \brief BASRA register. */
Here the register addresses are each in a define, but not built off each other, not named as verbosely, and the casting is built-in.
Reading and writing is then handled by inline functions without intermediary macros:
static inline void ubc_break_data_write(uint32 address) { BASRA = 0; /* ASID = 0 */ BARA = address; /* Break address */
What I'd propose to proceed would be to choose the best sorts of characteristics from different examples we have (maple seems to be the most well-structured, but might be overkill too). This could go hand in hand with some apparatus in the doxygen/documentation that would help easily view/search these values. Among other things, it would be determining:
- How should we name register ranges, and individual ones? Based on examples it would seem "SUBSYSTEM_BASE" is how many have been done so far: MAPLE_BASE in maple.h or GAPS_BASE in broadband_adapter.c. There are also a number though that go for "SUBSYSTEM_TYPE_BASE" like SPU_RAM_BASE (duplicated in snd_stream.c and snd_iface.c), PVR_RAM_BASE in pvr.h. With this sort of naming, we might rename maple to MAPLE_REGS_BASE. Then individual ones would swap out BASE for the specific register name. So "SUBSYSTEM_REGS_REGNAME".
- How should we name register values/contents and bitwise masks? Certainly this naming should flow from the above, but having the whole reg define be a prefix for each piece of content in it or each mask may be overly verbose.
- How should we go about reading/writing to these? It might make sense to have the maple defines be something along the lines of a globally included macro, with variations for 8 and 16 bit. Could also include bitwise reads/writes to match whatever standard we come up with for defining values/shifts/masks.
- Hows about undefined register spaces? Certainly these have been noted in places where writing to a register is required for operation (like PVR_UNK_00A8 et al) or needs to be defined for proper operation (like unks in packed flashrom blocks). Typically if we have a list of values 1, 4, 8; I'm in favor of listing the '2' even if we don't know what it is. It's, to my mind, like leaving a todo note.