Zephyr Device Driver Model

Zephyr provides a unified device driver model that abstracts hardware access behind consistent APIs.

Architecture Overview

flowchart TB
    subgraph Application
        App[Your Code]
    end

    subgraph DeviceLayer["Device Layer"]
        DevAPI[Device API]
        DevObj[Device Objects]
    end

    subgraph DriverLayer["Driver Layer"]
        DrvAPI[Driver API Struct]
        DrvImpl[Driver Implementation]
        DrvData[Driver Data]
        DrvConfig[Driver Config]
    end

    subgraph Hardware
        Regs[Hardware Registers]
    end

    App --> DevAPI
    DevAPI --> DevObj
    DevObj --> DrvAPI
    DrvAPI --> DrvImpl
    DrvImpl --> DrvData
    DrvImpl --> DrvConfig
    DrvImpl --> Regs

Getting a Device Reference

Compile-Time (Preferred)

#include <zephyr/device.h>

/* Get device from devicetree node */
const struct device *gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio0));

/* Check if device is ready */
if (!device_is_ready(gpio_dev)) {
    printk("GPIO device not ready\n");
    return -ENODEV;
}

Runtime (Legacy)

/* Get device by name (deprecated, use DT macros) */
const struct device *dev = device_get_binding("GPIO_0");
if (dev == NULL) {
    printk("Device not found\n");
    return -ENODEV;
}

Device Structure

Every device in Zephyr is represented by a struct device:

struct device {
    const char *name;           /* Device name */
    const void *config;         /* Device configuration (ROM) */
    const void *api;            /* Driver API functions */
    void *data;                 /* Driver runtime data (RAM) */
    /* ... internal fields ... */
};

Device Lifecycle

sequenceDiagram
    participant Boot as System Boot
    participant Init as Init System
    participant Driver as Driver
    participant App as Application

    Boot->>Init: Start initialization
    Init->>Driver: Call init function
    Driver->>Driver: Configure hardware
    Driver-->>Init: Return status
    Init->>App: Application starts
    App->>Driver: device_is_ready()
    Driver-->>App: true/false
    App->>Driver: Use device APIs

Initialization Levels

Drivers initialize at different stages:

/* Initialization levels (early to late) */
#define PRE_KERNEL_1    /* Before kernel, no services */
#define PRE_KERNEL_2    /* Before kernel, basic services */
#define POST_KERNEL     /* After kernel starts */
#define APPLICATION     /* After threads start */

Example driver initialization:

static int my_driver_init(const struct device *dev)
{
    /* Initialize hardware */
    return 0;
}

/* Initialize at POST_KERNEL level, priority 50 */
DEVICE_DT_DEFINE(DT_NODELABEL(my_device),
                 my_driver_init,
                 NULL,                    /* PM device */
                 &my_data,                /* Driver data */
                 &my_config,              /* Driver config */
                 POST_KERNEL,             /* Init level */
                 50,                      /* Priority */
                 &my_api);                /* API struct */

Using Device APIs

Each device type has a specific API. Here’s the pattern:

#include <zephyr/drivers/gpio.h>

/* Get the device */
const struct device *gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0));

/* Check readiness */
if (!device_is_ready(gpio)) {
    return -ENODEV;
}

/* Use type-specific API */
gpio_pin_configure(gpio, 13, GPIO_OUTPUT_ACTIVE);
gpio_pin_set(gpio, 13, 1);

Devicetree Integration

Devices are defined in devicetree and automatically instantiated:

/* In board DTS file */
/ {
    aliases {
        led0 = &led0;
    };

    leds {
        compatible = "gpio-leds";
        led0: led_0 {
            gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
            label = "Green LED";
        };
    };
};

Access in code:

/* Use alias */
#define LED0_NODE DT_ALIAS(led0)

/* Get GPIO spec from devicetree */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

void main(void)
{
    if (!gpio_is_ready_dt(&led)) {
        return;
    }

    gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    gpio_pin_set_dt(&led, 1);
}

Common Patterns

Checking Multiple Devices

const struct device *devices[] = {
    DEVICE_DT_GET(DT_NODELABEL(gpio0)),
    DEVICE_DT_GET(DT_NODELABEL(i2c0)),
    DEVICE_DT_GET(DT_NODELABEL(spi1)),
};

int init_devices(void)
{
    for (int i = 0; i < ARRAY_SIZE(devices); i++) {
        if (!device_is_ready(devices[i])) {
            printk("Device %d not ready\n", i);
            return -ENODEV;
        }
    }
    return 0;
}

Using DT Spec Structures

Many subsystems provide “spec” structures for convenient devicetree access:

/* GPIO spec includes device + pin + flags */
struct gpio_dt_spec {
    const struct device *port;
    gpio_pin_t pin;
    gpio_dt_flags_t dt_flags;
};

/* I2C spec includes bus device + address */
struct i2c_dt_spec {
    const struct device *bus;
    uint16_t addr;
};

/* SPI spec includes bus + config */
struct spi_dt_spec {
    const struct device *bus;
    struct spi_config config;
};

Usage:

/* Define at file scope */
static const struct gpio_dt_spec button =
    GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);

static const struct i2c_dt_spec sensor =
    I2C_DT_SPEC_GET(DT_NODELABEL(temp_sensor));

/* Use in code */
gpio_pin_configure_dt(&button, GPIO_INPUT);
i2c_write_dt(&sensor, data, sizeof(data));

Error Handling

Always check device readiness:

int sensor_init(void)
{
    const struct device *i2c = DEVICE_DT_GET(DT_NODELABEL(i2c0));

    if (!device_is_ready(i2c)) {
        LOG_ERR("I2C device not ready");
        return -ENODEV;
    }

    /* Device is ready, proceed with initialization */
    return 0;
}

Best Practices

  1. Use compile-time device access - DEVICE_DT_GET() over device_get_binding()
  2. Always check device_is_ready() - Devices may fail to initialize
  3. Use DT spec structures - They encapsulate device + configuration
  4. Check return values - Driver APIs can fail
  5. Use devicetree aliases - For board-portable code

Next Steps

Learn about Devicetree Bindings to understand how hardware is described.


Back to top

Zephyr RTOS Programming Guide is not affiliated with the Zephyr Project or Linux Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.