HomeCore 0.1.3 (07b5544)
Bare-metal personal computer firmware for Cortex-M
Loading...
Searching...
No Matches
Drivers

How device drivers are described, generated, initialized, and written.

HomeCore's hardware is described in YAML, not in code. A board names its SoC, enables the peripherals it uses, and adds board-level devices such as LEDs and ramdisks. At configure time a generator checks the description against each driver's binding and writes C code that instantiates and initializes exactly the enabled devices. Nothing is parsed at run time, and a driver is compiled only when some enabled device needs it.

Page Contents
Serial ports UARTs: the console, /dev/uartN, interrupt-driven receive
GPIO ports GPIO ports: pin modes, outputs, inputs
LEDs LEDs: /dev/ledN, the led_*() API, BASIC, and emulator stand-ins
Block devices Block devices: the ramdisk that filesystems mount
Device bindings Generated reference of every compatible and its properties

From description to code

The layers merge in order (SoC, board, overlays), later ones winning per key. The Development guide's "Device description" section covers the keys each layer may set; this page covers what drivers see.

For each enabled device node <node> with a binding whose driver prefix is <driver>, the generator writes:

static const <driver>_config_t dt_<node>_config = { /* one field per property */ };
static <driver>_t dt_<node> = {.config = &dt_<node>_config};

The configuration holds the description's values and lives in flash. The instance holds run-time state in RAM; its first member is always the config pointer, and the driver owns everything else in it.

Bindings

A binding, src/drivers/<class>/<compatible>.yaml, declares the driver prefix, its sources, what it provides, and its properties:

compatible: homecore,gpio-led
description: LED driven by a GPIO output pin, with a readable and writable /dev file.
driver: gpio_led
sources: [gpio_led.c]
led: true
properties:
gpio: {type: gpio, required: true, field: port}
pin: {type: int, required: true, maximum: 15, unique: true, unique-with: gpio, field: pin}
active-low: {type: bool, default: false, field: active_low}
devname: {type: devpath, field: path}
Property type Description value C field value
int An integer; minimum and maximum bound it 123U, or hexadecimal for reg
size A byte count such as 64K; minimum and multiple bound it Bytes; with buffer: <field>, also a generated .bss buffer
bool true or false true or false
string A non-empty string A string literal
clock A name from the board's clocks The clock frequency in Hz
devpath A name of a-z, 0-9, and _; defaults to the node name "/dev/<name>", unique across devices
gpio The node name of an enabled GPIO port A gpio_port_t; the port is initialized first
Binding flag The driver provides Used by
console: true const console_ops_t <driver>_console_ops chosen.console, Console
isr: true void <driver>_isr(<driver>_t *), with an irq property dt_irq_dispatch()
gpio: true const gpio_ops_t <driver>_gpio_ops gpio properties, GPIO
led: true const led_ops_t <driver>_led_ops dt_led_table, LEDs
block: true const block_ops_t <driver>_block_ops Board mounts, Block devices

Every driver also provides void <driver>_init(<driver>_t *). The generated binding reference lists every compatible, its properties, and the boards that enable it.

Initialization

dt_init() calls each enabled device's init function in description order, except that a device referenced by another (a GPIO port named by an LED's gpio property) is initialized first. Init functions run with the clocks and console pins already configured by board_init(), and:

  • must not print: the console is not open until k_init();
  • may call board_panic() for a description the hardware cannot honor, such as a pin the port rejects;
  • register their VFS nodes, which exist from then on.

Interrupts

A binding with isr: true and an irq property claims that interrupt; the generator rejects two devices claiming the same one. The driver enables it with arch_irq_enable() in its init function. Interrupt handlers must be short and bounded, must not allocate or print, and share data with thread code only through structures designed for it, such as the serial receive ring.

Driver catalog

Class Compatible Boards Validation
Serial st,stm32-usart STM32F4DISCOVERY, STM32VLDISCOVERY QEMU (STM32VLDISCOVERY), host contract test; hardware pending
Serial ti,stellaris-uart LM3S6965EVB QEMU, host contract test
GPIO st,stm32f4-gpio STM32F4DISCOVERY Host contract test; hardware pending
GPIO st,stm32f1-gpio STM32VLDISCOVERY Host contract test; hardware pending (QEMU models no GPIO)
LED homecore,gpio-led STM32F4DISCOVERY, STM32VLDISCOVERY Host test; hardware pending
LED homecore,console-led LM3S6965EVB, STM32VLDISCOVERY in QEMU QEMU, host test
Block homecore,ramdisk LM3S6965EVB, STM32F4DISCOVERY QEMU (littlefs on LM3S), host tests

Writing a driver

  1. Binding src/drivers/<class>/<compatible>.yaml with compatible, description, driver, sources, capability flags, and properties, each with a type and the C field it fills.
  2. Header <driver>.h next to it: <driver>_config_t (constants, one member per field), <driver>_t (state; first member const <driver>_config_t *config), void <driver>_init(<driver>_t *), and the operation tables its flags promise.
  3. Source: access registers through the SoC's CMSIS types from soc_cmsis.h, at the address in config->base. Return 0 or a count, and -1 with errno set, from operations. Keep interrupt handlers bounded.
  4. Description: add SoC peripherals to soc.yaml with status: disabled; boards enable them. Board-level devices go straight into board.yaml.
  5. Tests: a host test against a fake soc_cmsis.h in tests/fakes/, as tests/uart_contract_test.c and tests/gpio_contract_test.c do, and generator cases in tests/devicetree_generate_test.py for new property rules. Register host tests in scripts/check_quality.py.

Public interfaces that other code uses (console, GPIO, LEDs, block devices) are in include/homecore/drivers; driver headers stay in src/drivers.