不完全是。设备树不能完全替代驱动,但它极大地简化了驱动的编写和使用。
设备树 vs. 驱动的关系
1. 设备树的作用:描述"有什么"
设备树只描述硬件配置:
有什么硬件设备
设备在哪个地址
使用哪些中断、GPIO、时钟
设备的参数和选项
bash
dts
// 设备树只描述,不实现功能
led0 {
label = "user-led";
gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>;
};
2. 驱动的作用:实现"怎么用"
驱动包含控制硬件的代码逻辑:
如何初始化设备
如何读写数据
如何处理中断
提供用户空间接口
bash
c
// 驱动实现功能
static int led_probe(struct platform_device *pdev)
{
// 从设备树获取GPIO信息
led->gpio = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW);
// 提供用户空间接口
led->cdev.brightness_set = led_brightness_set;
led_classdev_register(&pdev->dev, &led->cdev);
return 0;
}
实际情况分析
情况1:标准设备 + 通用驱动 = 无需编写新驱动
有些设备有通用驱动,设备树配置后即可使用:
bash
dts
// 设备树配置
leds {
compatible = "gpio-leds"; // 使用内核已有的通用LED驱动
power_led {
label = "power";
gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>;
default-state = "on";
linux,default-trigger = "heartbeat";
};
};
gpio-keys {
compatible = "gpio-keys"; // 使用内核已有的按键驱动
button {
label = "User Button";
gpios = <&gpio0 50 GPIO_ACTIVE_LOW>;
linux,code = <KEY_ENTER>;
};
};
✅ 这种情况:添加设备树配置后,内核已有的驱动会自动识别并控制设备。
✅ 用户空间:可以直接通过 /sys/class/leds/ 或 /dev/input/eventX 使用设备。
情况2:自定义设备 = 必须编写驱动
bash
dts
// 设备树配置
my_custom_device@0 {
compatible = "mycompany,mydevice"; // 需要对应的驱动
reg = <0x12340000 0x1000>;
interrupt-parent = <&gic>;
interrupts = <0 45 4>;
custom-param = <100>;
};
❌ 这种情况:必须编写驱动实现 mycompany,mydevice 的匹配和处理。
哪些设备可以"无驱动"使用?
- 内核已有通用驱动的设备:
设备类型 兼容字符串 用户空间接口
bash
GPIO LED "gpio-leds" /sys/class/leds/
GPIO 按键 "gpio-keys" /dev/input/eventX
PWM LED "pwm-leds" /sys/class/leds/
RTC时钟 "ds1307" /dev/rtc0
EEPROM "at24" /sys/bus/i2c/devices/
看门狗 "wdog" /dev/watchdog
温度传感器 "lm75" /sys/class/hwmon/
- 用户空间直接访问的设备:
bash
GPIO直接访问(不推荐生产环境)
bash
echo 23 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio23/direction
echo 1 > /sys/class/gpio/gpio23/value
I2C设备直接访问
bash
i2cset -y 1 0x50 0x00 0x12 # 写I2C设备
i2cget -y 1 0x50 0x00 # 读I2C设备
SPI设备直接访问
spidev_test -D /dev/spidev1.0 -v
- 通过libgpiod等库访问:
c
c
// 用户空间C程序
#include <gpiod.h>
int main() {
struct gpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0");
struct gpiod_line *line = gpiod_chip_get_line(chip, 23);
gpiod_line_request_output(line, "example", 0);
gpiod_line_set_value(line, 1);
return 0;
}
实际工作流程示例
场景:添加一个新的LED
bash
dts
// 步骤1:设备树中添加LED描述
/dts-v1/;
/ {
leds {
compatible = "gpio-leds";
my_led {
label = "my-led";
gpios = <&gpio0 27 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
};
};
bash
步骤2:编译并加载设备树
bash
dtc -O dtb -o myboard.dtb myboard.dts
通过bootloader加载或动态加载
步骤3:无需编写驱动,直接使用
bash
ls /sys/class/leds/
my_led 会出现
步骤4:从用户空间控制
bash
echo 1 > /sys/class/leds/my_led/brightness # 打开LED
echo 0 > /sys/class/leds/my_led/brightness # 关闭LED
echo heartbeat > /sys/class/leds/my_led/trigger # 设置为心跳闪烁
需要编写驱动的情况
场景:自定义的传感器设备
bash
dts
// 设备树
my_sensor@1 {
compatible = "mycompany,sensor-v1";
reg = <0x76>;
interrupt-parent = <&gpio0>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
measurement-interval-ms = <1000>;
};
c
c
// 必须编写驱动
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "mycompany,sensor-v1" },
{ }
};
static struct i2c_driver sensor_driver = {
.probe = sensor_probe,
.id_table = sensor_id,
.driver = {
.name = "my_sensor",
.of_match_table = sensor_of_match,
},
};
// 必须实现probe、read、write等函数
static int sensor_probe(struct i2c_client *client)
{
// 从设备树获取参数
of_property_read_u32(np, "measurement-interval-ms", &interval);
// 初始化硬件
// 注册字符设备或sysfs接口
// 设置中断处理函数
}
现代Linux的趋势
- 设备树绑定标准化
越来越多的设备有标准绑定,减少自定义驱动需求:
bash
dts
// IIO(工业IO)传感器标准绑定
adc@0 {
compatible = "ti,ads1015";
reg = <0x48>;
#address-cells = <1>;
#size-cells = <0>;
channel@4 {
reg = <4>;
ti,gain = <3>;
ti,datarate = <5>;
};
};
- 用户空间驱动框架
有些设备可以在用户空间处理:
UIO(Userspace I/O):将设备映射到用户空间
VFIO:虚拟化环境中的设备直通
libgpiod:用户空间GPIO控制
- 设备树覆盖
运行时修改设备树,无需重新编译内核:
bash
动态添加设备
bash
dtoverlay my-device-overlay.dtbo
总结对比
方面 设备树 驱动
作用 硬件描述 功能实现
内容 硬件配置参数 控制逻辑代码
位置 编译时/启动时加载 内核模块或内置
修改 修改文本文件 编写C代码
示例 gpios = <&gpio0 23 0> gpiod_set_value(gpio, 1)
结论
对于标准设备:设备树 + 内核已有驱动 = 无需编写驱动
GPIO LED、按键、标准I2C设备等
对于自定义设备:设备树 + 自定义驱动 = 必须编写驱动
专用传感器、自定义通信协议设备等
简单控制:有时可以在用户空间直接操作
GPIO、I2C、SPI的简单读写