Create a gist now

Instantly share code, notes, and snippets.

@lisovy /C_hw_access
Last active Jul 23, 2018

Embed
What would you like to do?
Multiplatform programming
=========================
Issues to be aware of:
* Integer types sizes (int -- 16 or 32 bits? long -- 32 or 64 bits?)
- Storing pointers into integer types
(unsigned long on 64-bit Linux vs. 64-bit Windows)
* Endianess (big, little)
* Memory access width (byte access, word access)
* Unaligned memory access
Interacting with HW in C
========================
Device drivers interact with hardware by reading/writing to its
registers (i.e. register region).
Following few paragraphs describe what we should watch out when
doing so in C.
Possible issues:
* Some HW registers accessible only through IO ports (no MMIO)
http://wiki.osdev.org/I/O_Ports
* Endiannes (when accessing 16/32 registers)
https://en.wikipedia.org/wiki/Endiannes
* Compiler optimizing out our HW/register access
http://stackoverflow.com/questions/7083482/
/how-to-prevent-compiler-optimization-on-a-small-piece-of-code
Possible solution: use volatile keyword
* Compiler reordering instruction sequence
Possible solution: "Compiler memory barrier"
Accessing to registers using specified functions only
(e.g. ioread32() in linux kernel)
* CPU reordering instruction execution (out-of-order execution)
Possible solution: "CPU memory barrier"
https://en.wikipedia.org/wiki/Memory_barrier
* When using overlay structures, think about default field alignment
Possible solution: use __attribute__ ((packed));
* Beware unaligned access on some architectures
Possible solution: use __attribute__ ((aligned (XX)))
* C code readability/reusability
Don't do following:
https://elixir.bootlin.com/linux/v3.6.9/source/drivers/media/video/gspca/ov519.c#L1650
/* Settings for (color) OV7620 camera chip */
static struct ovcamchip_regvals regvals_init_7620[] = {
{ 0x12, 0x80 }, /* reset */
{ 0x00, OV7620_DFL_GAIN },
{ 0x01, 0x80 },
{ 0x02, 0x80 },
{ 0x03, OV7620_DFL_SAT },
{ 0x06, OV7620_DFL_BRIGHT },
{ 0x07, 0x00 },
{ 0x0c, 0x24 },
{ 0x0c, 0x24 },
{ 0x0d, 0x24 },
/* ... 45 lines of this stuff removed ... */
{ 0x74, 0x00 },
{ 0x75, 0x8e },
{ 0x76, 0x00 },
{ 0x77, 0xff },
{ 0x78, 0x80 },
{ 0x79, 0x80 },
{ 0x7a, 0x80 },
{ 0x7b, 0xe2 },
{ 0x7c, 0x00 },
{ 0xff, 0xff }, /* END MARKER */
};
################################################################################
# How to make register access readable #
################################################################################
Define offset address of each register
Add this offset to base address
Use specific functions for writing/reading (they will handle endiannes
issues when handling 16/32 registers and disable reordering
/* BAR0 regs */
#define ADCTRL_reg 0x0
#define ADLO_reg 0x0
#define ADHI_reg 0x1
iowrite8((chan) | ADCTRL_DEFAULT | ADCTRL_BIP | ADCTRL_RNG, devpriv->BAR0_io + ADCTRL_reg);
dat = ioread8(devpriv->BAR0_io + ADLO_reg);
dat |= (ioread8(devpriv->BAR0_io + ADHI_reg) << 8);
################################################################################
Make an overlay structure
Positions of fields in this structure will represent relative position of
registers inside the "register region";
Use a pointer to this structure; Set the pointer address to register region
(base address)
Use specific functions for accessing 16/32 fields -- because of endianness OR
Define the struct to be compiled correctly for particular endianning
typedef volatile struct sciBase
{
uint32_t GCR0; /**< 0x0000 Global Control Register 0 */
uint32_t GCR1; /**< 0x0004 Global Control Register 1 */
uint32_t GCR2; /**< 0x0008 Global Control Register 2 */
uint32_t SETINT; /**< 0x000C Set Interrupt Enable Register */
uint32_t CLRINT; /**< 0x0010 Clear Interrupt Enable Register */
uint32_t SETINTLVL; /**< 0x0014 Set Interrupt Level Register */
uint32_t CLRINTLVL; /**< 0x0018 Set Interrupt Level Register */
uint32_t FLR; /**< 0x001C Interrupt Flag Register */
uint32_t INTVECT0; /**< 0x0020 Interrupt Vector Offset 0 */
uint32_t INTVECT1; /**< 0x0024 Interrupt Vector Offset 1 */
uint32_t LENGTH; /**< 0x0028 Format Control Register */
uint32_t BAUD; /**< 0x002C Baud Rate Selection Register */
#if ((__little_endian__ == 1) || (__LITTLE_ENDIAN__ == 1))
uint8_t ED; /**< 0x0033 Emulation Register */
uint32_t : 24U;
uint8_t RD; /**< 0x0037 Receive Data Buffer */
uint32_t : 24U;
uint8_t TD; /**< 0x003B Transmit Data Buffer */
uint32_t : 24U;
#else
uint32_t : 24U;
uint8_t ED; /**< 0x0033 Emulation Register */
uint32_t : 24U;
uint8_t RD; /**< 0x0037 Receive Data Buffer */
uint32_t : 24U;
uint8_t TD; /**< 0x003B Transmit Data Buffer */
#endif
uint32_t FUN; /**< 0x003C Pin Function Register */
uint32_t DIR; /**< 0x0040 Pin Direction Register */
uint32_t DIN; /**< 0x0044 Pin Data In Register */
uint32_t DOUT; /**< 0x0048 Pin Data Out Register */
uint32_t SET; /**< 0x004C Pin Data Set Register */
uint32_t CLR; /**< 0x0050 Pin Data Clr Register */
uint32_t ODR; /**< 0x0054: Pin Open Drain Output Enable Register */
uint32_t PD; /**< 0x0058: Pin Pullup/Pulldown Disable Register */
uint32_t PSL; /**< 0x005C: Pin Pullup/Pulldown Selection Register */
} sciBASE_t;
#define sciREG ((sciBASE_t *)0xFFF7E500U)
/** - bring SCI out of reset */
sciREG->GCR0 = 1U;
/** - Disable all interrupts */
sciREG->CLRINT = 0xFFFFFFFFU;
sciREG->CLRINTLVL = 0xFFFFFFFFU;
/** - global control 1 */
sciREG->GCR1 = (1 << 25) /* enable transmit */
| (1 << 24) /* enable receive */
| (1 << 5) /* internal clock (device has no clock pin) */
| ((1-1) << 4) /* number of stop bits */
| (0 << 3) /* even parity, otherwise odd */
| (0 << 2) /* enable parity */
| (1 << 1); /* asynchronous timing mode */
/** - set baudrate */
sciREG->BAUD = 520; /* baudrate */
/** - tranmision length */
sciREG->LENGTH = 8 - 1; /* length */
/** - set SCI pins functional mode */
sciREG->FUN = (1 << 2) /* tx pin */
| (1 << 1) /* rx pin */
| (0); /* clk pin */
/** - set interrupt level */
sciREG->SETINTLVL = (0 << 26) /* Framing error */
| (0 << 25) /* Overrun error */
| (0 << 24) /* Pariry error */
| (0 << 9) /* Receive */
| (1 << 8) /* Transmit */
| (0 << 1) /* Wakeup */
| (0); /* Break detect */
################################################################################
Access register region as an array of bytes
Use datatype of particular size to match registers size
volatile uint32 *_hwreg;
enum VRegister {
VTCTRL = 0x0000/4, /* Beware of the /4 ! */
VTSTATUS = 0x0008/4,
VTFRTIMER = 0x1048/4,
VTEICS = 0x1520/4,
VTEIMS = 0x1524/4,
VTEIMC = 0x1528/4,
VTEIAC = 0x152C/4,
VTEIAM = 0x1530/4,
VTEICR = 0x1580/4,
VTEITR0 = 0x1680/4,
VTEITR1 = 0x1684/4,
VTEITR2 = 0x1688/4,
VTIVAR = 0x1700/4,
VTIVAR_MISC = 0x1740/4,
}
_hwreg[VTIVAR] = 0x00008080;
_hwreg[VTIVAR_MISC] = 0x81;
_hwreg[VTEIAC] = 3;
_hwreg[VTEIAM] = 3;
assert(_hwreg[RDT0] == 0);
... however this does not solve the reordering or endiannes issue
################################################################################
How to name flag values, shifts, offsets in correct way?
To be done...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment