copilot-swe-agent[bot] b10da2d554 [documentation][device_driver_model] Add comprehensive PIC documentation (EN) - critical module 1/6 1 тиждень тому
..
clk eb66335efa [documentation][device_driver_model] Add clock framework Chinese documentation 1 тиждень тому
nvmem c7dd64349a [documentation][device_driver_model] Add NVMEM Chinese documentation - complete module 5/26 1 тиждень тому
pic b10da2d554 [documentation][device_driver_model] Add comprehensive PIC documentation (EN) - critical module 1/6 1 тиждень тому
pinctrl c4e97d69a5 [documentation][device_driver_model] Add pinctrl Chinese documentation - complete module 4/26 1 тиждень тому
regulator cd8156220c [documentation][device_driver_model] Add Chinese regulator docs and INDEX 1 тиждень тому
reset 038be9167a [documentation][device_driver_model] Add reset controller Chinese documentation - complete module 3/26 1 тиждень тому
INDEX.md cd8156220c [documentation][device_driver_model] Add Chinese regulator docs and INDEX 1 тиждень тому
README.md 17ecb56cb5 [documentation][device_driver_model] Add main DM documentation (EN/CN) 1 тиждень тому
README_zh.md 17ecb56cb5 [documentation][device_driver_model] Add main DM documentation (EN/CN) 1 тиждень тому
SUMMARY.md b6857a69a2 [documentation][device_driver_model] Add comprehensive project summary 1 тиждень тому

README.md

RT-Thread Device Driver Model (DM)

Overview

The RT-Thread Device Driver Model (DM) is a comprehensive framework that provides a standardized, hierarchical approach to managing hardware devices and their drivers. Enabled through the RT_USING_DM configuration option, it introduces a sophisticated device-driver matching mechanism, bus abstraction, and seamless integration with device tree (Open Firmware) support.

The DM framework significantly enhances RT-Thread's capability to handle complex hardware systems, particularly those requiring dynamic device discovery, power management, clock control, and other hardware resource management.

Key Features

  • Bus-Driver-Device Architecture: Hierarchical organization of hardware components
  • Device Tree Integration: Full Open Firmware (OFW) support for hardware description
  • Dynamic Device Management: Runtime device discovery and driver binding
  • Resource Management: Unified APIs for clocks, regulators, resets, power domains, etc.
  • Platform Device Model: Abstraction for memory-mapped and non-discoverable devices
  • Reference Counting: Safe resource lifecycle management
  • Extensible Framework: Easy addition of new bus types and device classes

Architecture

Core Components

The DM framework consists of several key components:

┌─────────────────────────────────────────────────────────────┐
│                    Application Layer                         │
│  (Uses device APIs: clk, regulator, pinctrl, gpio, etc.)   │
└────────────────────┬────────────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────────────┐
│                Device Driver Model (DM)                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │   Bus    │  │  Driver  │  │  Device  │  │ Platform │   │
│  │ Subsys   │  │ Subsys   │  │ Subsys   │  │  Layer   │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘   │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐  │
│  │        OFW (Open Firmware/Device Tree)               │  │
│  │  - FDT parsing  - Property reading  - IRQ mapping   │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────┬────────────────────────────────────────┘
                     │
┌────────────────────┴────────────────────────────────────────┐
│                Hardware Resources                            │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │  Clocks  │  │Regulator │  │  Resets  │  │  GPIOs   │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │ Pinctrl  │  │   DMA    │  │   IRQs   │  │  Memory  │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘   │
└─────────────────────────────────────────────────────────────┘

1. Bus Subsystem

The bus subsystem (rt_bus) provides the foundation for organizing devices and drivers:

  • Bus Registration: Buses are registered with the system and maintain lists of devices and drivers
  • Device-Driver Matching: Each bus implements a match function to pair compatible drivers with devices
  • Probe/Remove: Buses coordinate the lifecycle of device-driver bindings
  • Lock Management: Thread-safe operations on device and driver lists

Key Structures:

struct rt_bus {
    struct rt_object parent;
    const char *name;
    rt_list_t list;
    rt_list_t dev_list;      /* List of devices on this bus */
    rt_list_t drv_list;      /* List of drivers on this bus */
    rt_bool_t (*match)(rt_driver_t drv, rt_device_t dev);
    rt_err_t  (*probe)(rt_device_t dev);
    rt_err_t  (*remove)(rt_device_t dev);
    rt_err_t  (*shutdown)(rt_device_t dev);
};

2. Driver Subsystem

Drivers implement hardware-specific functionality and are bound to compatible devices:

  • Driver Registration: Drivers register with a specific bus
  • Device Operations: Standard operations (init, open, close, read, write, control)
  • Probe/Remove Callbacks: Initialization and cleanup when bound to devices
  • Reference Counting: Tracks the number of active device bindings

Key Structures:

struct rt_driver {
    struct rt_object parent;
    struct rt_bus *bus;
    rt_list_t node;
    rt_uint32_t ref_count;
    
    /* Device operations */
    rt_err_t  (*init)(rt_device_t dev);
    rt_err_t  (*open)(rt_device_t dev, rt_uint16_t oflag);
    rt_err_t  (*close)(rt_device_t dev);
    rt_ssize_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_ssize_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
    rt_err_t  (*control)(rt_device_t dev, int cmd, void *args);
    
    /* Lifecycle callbacks */
    int (*probe)(struct rt_device *dev);
    int (*remove)(struct rt_device *dev);
    int (*shutdown)(struct rt_device *dev);
};

3. Platform Device Model

The platform device model is the most common usage of DM in RT-Thread, handling memory-mapped devices:

  • Platform Bus: A virtual bus for devices not on a physical bus
  • Device Tree Integration: Automatic device creation from FDT nodes
  • Resource Abstraction: Unified access to memory regions, IRQs, clocks, etc.
  • Driver Binding: Automatic matching based on compatible strings

Key Structures:

struct rt_platform_device {
    struct rt_device parent;
    int dev_id;
    const char *name;
    const struct rt_ofw_node_id *id;
    void *priv;
};

struct rt_platform_driver {
    struct rt_driver parent;
    const char *name;
    const struct rt_ofw_node_id *ids;  /* Compatible strings */
    
    rt_err_t (*probe)(struct rt_platform_device *pdev);
    rt_err_t (*remove)(struct rt_platform_device *pdev);
    rt_err_t (*shutdown)(struct rt_platform_device *pdev);
};

4. OFW (Open Firmware) Support

The OFW subsystem provides device tree support:

  • FDT Parsing: Flattened Device Tree (FDT) binary format parsing
  • Device Node Management: Hierarchical device tree node representation
  • Property Reading: APIs to read device tree properties
  • Resource Mapping: Translation of DT resources to system resources (IRQ, memory, etc.)

Device Lifecycle

The typical lifecycle of a device in the DM framework:

     Device Tree          Platform           Driver
         │                   │                  │
         │  Parse DT         │                  │
         ├──────────────────>│                  │
         │                   │                  │
         │  Create Platform  │                  │
         │     Device        │                  │
         │                   │                  │
         │  Register Device  │                  │
         │  on Platform Bus  │                  │
         │                   │                  │
         │                   │  Match Device    │
         │                   │  with Driver     │
         │                   ├─────────────────>│
         │                   │                  │
         │                   │  Driver Probe    │
         │                   │<─────────────────┤
         │                   │                  │
         │                   │  Initialize HW   │
         │                   │                  │
         │                   │  Register APIs   │
         │                   │                  │
         │                   │    RUNNING       │
         │                   │                  │
         │                   │  Driver Remove   │
         │                   ├─────────────────>│
         │                   │                  │
         │                   │  Cleanup         │
         │                   │<─────────────────┤
         │                   │                  │

Kconfig Configuration

Main DM Option

The Device Driver Model is enabled through the main Kconfig option:

config RT_USING_DM
    bool "Enable device driver model with device tree"
    default n
    help
        Enable device driver model with device tree (FDT). It will use more memory
        to parse and support device tree feature.

Location in menuconfig: RT-Thread Components → Device Drivers → Enable device driver model with device tree

Related Options

Device Bus

config RT_USING_DEV_BUS
    bool "Using Device Bus device drivers"
    default y if RT_USING_SMART
    default n

Open Firmware (OFW)

menuconfig RT_USING_OFW
    bool "Using Open Firmware (OFW)"
    select RT_USING_ADT
    select RT_USING_ADT_REF
    select RT_USING_ADT_BITMAP
    select RT_USING_MEMBLOCK
    depends on RT_USING_DM
    default n

Location: RT-Thread Components → Device Drivers → Using Open Firmware (OFW)

Builtin FDT

config RT_USING_BUILTIN_FDT
    bool "Using builtin fdt in kernel"
    depends on RT_USING_OFW
    default n

config RT_BUILTIN_FDT_PATH
    string "Builtin fdt path, will rebuild if have dts"
    depends on RT_USING_BUILTIN_FDT
    default "rtthread.dtb"

Device Tree Integration

Device Tree Basics

When RT_USING_DM and RT_USING_OFW are enabled, RT-Thread can parse and use device tree (FDT) to describe hardware:

Example Device Tree Node

/ {
    compatible = "myboard,example";
    #address-cells = <1>;
    #size-cells = <1>;
    
    clocks {
        osc24M: osc24M_clk {
            #clock-cells = <0>;
            compatible = "fixed-clock";
            clock-frequency = <24000000>;
            clock-output-names = "osc24M";
        };
    };
    
    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;
        
        uart0: serial@10000000 {
            compatible = "myvendor,uart";
            reg = <0x10000000 0x1000>;
            interrupts = <32>;
            clocks = <&osc24M>;
            clock-names = "baudclk";
            status = "okay";
        };
    };
    
    regulators {
        vcc_3v3: regulator-vcc-3v3 {
            compatible = "regulator-fixed";
            regulator-name = "vcc-3v3";
            regulator-min-microvolt = <3300000>;
            regulator-max-microvolt = <3300000>;
            regulator-always-on;
            regulator-boot-on;
        };
    };
};

Using Device Tree in Drivers

Getting Device Node

#include <drivers/ofw.h>

struct rt_ofw_node *np = dev->ofw_node;

Reading Properties

/* Read string property */
const char *status;
rt_ofw_prop_read_string(np, "status", &status);

/* Read u32 property */
rt_uint32_t freq;
rt_ofw_prop_read_u32(np, "clock-frequency", &freq);

/* Read array */
rt_uint32_t reg[2];
rt_ofw_prop_read_u32_array(np, "reg", reg, 2);

Getting Resources

/* Get memory resources */
rt_uint64_t addr, size;
rt_dm_dev_get_address(dev, 0, &addr, &size);

/* Get IRQ */
int irq = rt_dm_dev_get_irq(dev, 0);

/* Map MMIO */
void *base = rt_dm_dev_iomap(dev, 0);

/* Get clock */
struct rt_clk *clk = rt_clk_get_by_name(dev, "baudclk");

DM-Dependent Modules

The Device Driver Model supports numerous hardware subsystems. Each module provides standardized APIs for both application layer and driver implementation:

Module Description Documentation
clk Clock management framework clk.md
regulator Voltage/current regulation regulator.md
pinctrl Pin multiplexing and configuration pinctrl.md
reset Reset controller management reset.md
pmdomain Power domain management pmdomain.md
pic Platform interrupt controller pic.md
nvmem Non-volatile memory framework nvmem.md
mailbox Mailbox/doorbell communication mailbox.md
thermal Thermal management thermal.md
mfd Multi-function device mfd.md
dma DMA engine management dma.md
iio Industrial I/O subsystem iio.md
phy PHY (physical layer) framework phy.md
phye Ethernet PHY framework phye.md
pci PCI bus support pci.md
ofw Open Firmware/Device Tree ofw.md

Common APIs

Device Management

/* Find device by master_id and device_id */
rt_device_t rt_dm_device_find(int master_id, int device_id);

/* Set device name */
int rt_dm_dev_set_name(rt_device_t dev, const char *format, ...);
int rt_dm_dev_set_name_auto(rt_device_t dev, const char *prefix);

/* Get device name */
const char *rt_dm_dev_get_name(rt_device_t dev);
int rt_dm_dev_get_name_id(rt_device_t dev);

Device Tree APIs

/* Get address resources */
int rt_dm_dev_get_address_count(rt_device_t dev);
rt_err_t rt_dm_dev_get_address(rt_device_t dev, int index,
                               rt_uint64_t *out_address, rt_uint64_t *out_size);
rt_err_t rt_dm_dev_get_address_by_name(rt_device_t dev, const char *name,
                                       rt_uint64_t *out_address, rt_uint64_t *out_size);

/* Map MMIO regions */
void *rt_dm_dev_iomap(rt_device_t dev, int index);
void *rt_dm_dev_iomap_by_name(rt_device_t dev, const char *name);

/* Get IRQ resources */
int rt_dm_dev_get_irq_count(rt_device_t dev);
int rt_dm_dev_get_irq(rt_device_t dev, int index);
int rt_dm_dev_get_irq_by_name(rt_device_t dev, const char *name);

Property Reading

/* Read various property types */
int rt_dm_dev_prop_read_u8_array_index(rt_device_t dev, const char *propname,
                                       int index, int nr, rt_uint8_t *out_values);
int rt_dm_dev_prop_read_u32_array_index(rt_device_t dev, const char *propname,
                                        int index, int nr, rt_uint32_t *out_values);
int rt_dm_dev_prop_read_string_index(rt_device_t dev, const char *propname,
                                     int index, const char **out_string);

/* Simplified single-value reading */
#define rt_dm_dev_prop_read_u32(dev, propname, out_value) \
    rt_dm_dev_prop_read_u32_array_index(dev, propname, 0, 1, out_value)

Platform Device APIs

/* Register platform driver */
rt_err_t rt_platform_driver_register(struct rt_platform_driver *pdrv);

/* Register platform device */
rt_err_t rt_platform_device_register(struct rt_platform_device *pdev);

/* Create platform device from device tree */
rt_err_t rt_platform_ofw_device_probe_child(struct rt_ofw_node *np);

Writing a Platform Driver

Basic Driver Structure

#include <rtthread.h>
#include <drivers/platform.h>
#include <drivers/ofw.h>

struct mydevice_data {
    void *base;
    int irq;
    struct rt_clk *clk;
};

static rt_err_t mydevice_probe(struct rt_platform_device *pdev)
{
    struct mydevice_data *data;
    struct rt_device *dev = &pdev->parent;
    
    /* Allocate private data */
    data = rt_calloc(1, sizeof(*data));
    if (!data)
        return -RT_ENOMEM;
    
    /* Map MMIO region */
    data->base = rt_dm_dev_iomap(dev, 0);
    if (!data->base) {
        rt_free(data);
        return -RT_ERROR;
    }
    
    /* Get IRQ */
    data->irq = rt_dm_dev_get_irq(dev, 0);
    
    /* Get clock */
    data->clk = rt_clk_get_by_name(dev, "baudclk");
    if (data->clk) {
        rt_clk_prepare_enable(data->clk);
    }
    
    /* Store private data */
    pdev->priv = data;
    
    /* Initialize hardware */
    /* ... */
    
    return RT_EOK;
}

static rt_err_t mydevice_remove(struct rt_platform_device *pdev)
{
    struct mydevice_data *data = pdev->priv;
    
    /* Cleanup hardware */
    /* ... */
    
    /* Release resources */
    if (data->clk) {
        rt_clk_disable_unprepare(data->clk);
        rt_clk_put(data->clk);
    }
    
    rt_free(data);
    
    return RT_EOK;
}

static const struct rt_ofw_node_id mydevice_ofw_ids[] = {
    { .compatible = "myvendor,mydevice" },
    { /* sentinel */ }
};

static struct rt_platform_driver mydevice_driver = {
    .name = "mydevice",
    .ids = mydevice_ofw_ids,
    .probe = mydevice_probe,
    .remove = mydevice_remove,
};

/* Auto-register driver at boot */
RT_PLATFORM_DRIVER_EXPORT(mydevice_driver);

Device Tree Binding

mydev: mydevice@10000000 {
    compatible = "myvendor,mydevice";
    reg = <0x10000000 0x1000>;
    interrupts = <32>;
    clocks = <&osc24M>;
    clock-names = "baudclk";
    status = "okay";
};

Best Practices

1. Resource Management

  • Always check return values when getting resources
  • Use reference counting for shared resources (clocks, regulators)
  • Clean up resources in the remove callback
  • Handle probe deferral for dependencies

2. Device Tree

  • Use standard bindings when available
  • Document custom properties in driver documentation
  • Use labels for phandle references
  • Keep device tree and driver in sync

3. Error Handling

static rt_err_t mydevice_probe(struct rt_platform_device *pdev)
{
    rt_err_t ret;
    struct mydevice_data *data;
    
    data = rt_calloc(1, sizeof(*data));
    if (!data)
        return -RT_ENOMEM;
    
    data->base = rt_dm_dev_iomap(&pdev->parent, 0);
    if (!data->base) {
        ret = -RT_ERROR;
        goto err_free;
    }
    
    data->clk = rt_clk_get_by_name(&pdev->parent, "baudclk");
    if (!data->clk) {
        ret = -RT_ERROR;
        goto err_unmap;
    }
    
    ret = rt_clk_prepare_enable(data->clk);
    if (ret)
        goto err_put_clk;
    
    /* Success */
    pdev->priv = data;
    return RT_EOK;
    
err_put_clk:
    rt_clk_put(data->clk);
err_unmap:
    rt_iounmap(data->base);
err_free:
    rt_free(data);
    return ret;
}

4. Thread Safety

  • Use spinlocks for hardware register access
  • Use mutexes for long operations
  • Consider interrupt context in ISRs
  • Protect shared data structures

Performance Considerations

Memory Usage

DM adds memory overhead:

  • Device tree parsing requires memory for FDT and node structures
  • Each device/driver adds object overhead
  • Additional metadata for resource management

Recommendations:

  • Only enable DM for systems that need it
  • Consider memory constraints when designing device trees
  • Use static allocation where possible

Boot Time

  • Device tree parsing happens early in boot
  • Driver probing can be deferred
  • Parallel initialization where possible

Debugging

Enable Debug Output

#define DBG_TAG "mydriver"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>

LOG_D("Debug message");
LOG_I("Info message");
LOG_W("Warning message");
LOG_E("Error message");

DM-Specific Debugging

/* In menuconfig, enable OFW debugging */
RT-Thread Components → Device Drivers → Using Open Firmware (OFW)

Common Issues

  1. Device not probing: Check compatible string matches
  2. Resource not found: Verify device tree properties
  3. Probe deferral loop: Check for circular dependencies
  4. Memory access fault: Verify MMIO mapping is correct

Migration Guide

From Traditional RT-Thread Drivers

Traditional RT-Thread drivers:

rt_device_t dev = rt_device_find("uart0");
rt_device_open(dev, RT_DEVICE_OFLAG_RDWR);

With DM:

/* Device automatically created from device tree */
rt_device_t dev = rt_device_find("uart0");
rt_device_open(dev, RT_DEVICE_OFLAG_RDWR);
/* API remains the same for users */

Driver implementation changes:

  • Add platform driver structure
  • Implement probe/remove callbacks
  • Use DM APIs for resource management
  • Add device tree binding

Related Documentation

References