Linux系统读取设备树硬件信息
1. 设备树简介
设备树(Device Tree)是一种描述硬件信息的数据结构,用于在Linux系统中传递硬件信息给内核。它替代了传统的硬编码方式,使内核能够更好地适应不同的硬件平台。
设备树的主要作用:
- 描述硬件组件的存在和配置
- 传递硬件参数给内核驱动
- 减少内核中的平台特定代码
- 提高系统的可移植性
2. 读取设备树的方法
2.1 内核空间基础API
2.1.1 OF API(Open Firmware API)
OF API是内核中最常用的设备树访问接口,用于在驱动中获取设备树信息。
设备树示例:
my-device {
compatible = "vendor,my-device";
reg = <0x10000000 0x1000>;
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
clock-frequency = <24000000>;
enable = <1>;
status = "okay";
#address-cells = <1>;
#size-cells = <1>;
ranges;
sub-device {
compatible = "vendor,sub-device";
reg = <0x0 0x100>;
};
};
主要函数:
- of_find_node_by_name():通过节点名称查找设备树节点
- of_find_node_by_path():通过路径查找设备树节点
- of_find_compatible_node():通过compatible属性查找设备树节点
- of_property_read_u32():读取32位整数值
- of_property_read_string():读取字符串值
- of_property_read_bool():读取布尔值
- of_property_read_u32_array():读取32位整数数组
- of_property_count_elems_of_size():计算属性元素个数
- of_get_property():获取属性的原始数据
- of_device_is_available():检查设备是否可用
示例代码:
#include <linux/of.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
u32 value;
const char *string;
int ret;
// 读取32位整数值
ret = of_property_read_u32(node, "reg", &value);
if (ret) {
dev_err(dev, "Failed to read reg property: %d\n", ret);
return ret;
}
dev_info(dev, "reg value: %d\n", value);
// 读取字符串值
ret = of_property_read_string(node, "compatible", &string);
if (ret) {
dev_err(dev, "Failed to read compatible property: %d\n", ret);
return ret;
}
dev_info(dev, "compatible: %s\n", string);
// 检查布尔属性
if (of_property_read_bool(node, "enable")) {
dev_info(dev, "Enable property is set\n");
}
return 0;
}
2.1.2 GPIO相关API
专门用于获取GPIO相关信息的API,包括传统的of_gpio_*函数和现代的gpiod_*函数族。
设备树示例:
leds {
compatible = "my-led-driver";
led1-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; // 低电平有效
led2-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; // 高电平有效
enable-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
// 多个相同功能的GPIO
led-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>,
<&gpio0 5 GPIO_ACTIVE_LOW>,
<&gpio0 6 GPIO_ACTIVE_LOW>;
};
主要函数:
*传统of_gpio_函数:
- of_get_gpio():从设备树中获取GPIO编号
- of_get_gpio_flags():从设备树中获取GPIO编号和标志
- of_gpio_named_count():计算指定名称的GPIO数量
- of_gpio_get():获取指定名称的GPIO
*现代gpiod_函数族:
- gpiod_get():获取单个GPIO描述符
- gpiod_get_array():获取多个GPIO描述符
- gpiod_get_index():通过索引获取GPIO描述符
- gpiod_put():释放GPIO描述符
- gpiod_put_array():释放多个GPIO描述符
- gpiod_direction_input():设置GPIO为输入方向
- gpiod_direction_output():设置GPIO为输出方向并设置初始值
- gpiod_get_value():读取GPIO的值
- gpiod_set_value():设置GPIO的值
示例代码:
*传统of_gpio_函数示例:
#include <linux/of_gpio.h>
static int my_led_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
int led1_gpio, led2_gpio, enable_gpio;
int num_leds, i;
int ret;
// 获取单个GPIO
led1_gpio = of_get_gpio_flags(node, "led1-gpios", 0, NULL);
if (led1_gpio < 0) {
dev_err(dev, "Failed to get led1-gpios: %d\n", led1_gpio);
return led1_gpio;
}
led2_gpio = of_get_gpio_flags(node, "led2-gpios", 0, NULL);
if (led2_gpio < 0) {
dev_err(dev, "Failed to get led2-gpios: %d\n", led2_gpio);
return led2_gpio;
}
enable_gpio = of_get_gpio_flags(node, "enable-gpios", 0, NULL);
if (enable_gpio < 0) {
dev_err(dev, "Failed to get enable-gpios: %d\n", enable_gpio);
return enable_gpio;
}
// 获取多个GPIO
num_leds = of_gpio_named_count(node, "led-gpios");
dev_info(dev, "Found %d LEDs\n", num_leds);
for (i = 0; i < num_leds; i++) {
int gpio = of_get_gpio_flags(node, "led-gpios", i, NULL);
if (gpio < 0) {
dev_err(dev, "Failed to get led-gpios[%d]: %d\n", i, gpio);
return gpio;
}
dev_info(dev, "LED %d GPIO: %d\n", i, gpio);
}
return 0;
}
*现代gpiod_函数族示例:
#include <linux/gpio/consumer.h>
static struct gpio_desc *led1_gpio;
static struct gpio_desc *led2_gpio;
static struct gpio_desc *enable_gpio;
static struct gpio_descs *led_array;
static int my_led_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret;
// 获取单个GPIO
led1_gpio = gpiod_get(dev, "led1", GPIOD_OUT_LOW);
if (IS_ERR(led1_gpio)) {
ret = PTR_ERR(led1_gpio);
dev_err(dev, "Failed to get led1 GPIO: %d\n", ret);
return ret;
}
led2_gpio = gpiod_get(dev, "led2", GPIOD_OUT_LOW);
if (IS_ERR(led2_gpio)) {
ret = PTR_ERR(led2_gpio);
dev_err(dev, "Failed to get led2 GPIO: %d\n", ret);
gpiod_put(led1_gpio);
return ret;
}
enable_gpio = gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(enable_gpio)) {
ret = PTR_ERR(enable_gpio);
dev_err(dev, "Failed to get enable GPIO: %d\n", ret);
gpiod_put(led1_gpio);
gpiod_put(led2_gpio);
return ret;
}
// 获取多个GPIO
led_array = gpiod_get_array(dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(led_array)) {
ret = PTR_ERR(led_array);
dev_err(dev, "Failed to get led array: %d\n", ret);
gpiod_put(led1_gpio);
gpiod_put(led2_gpio);
gpiod_put(enable_gpio);
return ret;
}
dev_info(dev, "Found %zu LEDs in array\n", led_array->ndescs);
// 使用GPIO
gpiod_set_value(enable_gpio, 1); // 启用设备
gpiod_set_value(led1_gpio, 1); // 打开LED1
gpiod_set_value(led2_gpio, 1); // 打开LED2
// 使用GPIO数组
for (int i = 0; i < led_array->ndescs; i++) {
gpiod_set_value(&led_array->desc[i], 1); // 打开所有LED
}
return 0;
}
static int my_led_driver_remove(struct platform_device *pdev)
{
// 释放GPIO
if (led_array) {
gpiod_put_array(led_array);
}
if (enable_gpio) {
gpiod_put(enable_gpio);
}
if (led2_gpio) {
gpiod_put(led2_gpio);
}
if (led1_gpio) {
gpiod_put(led1_gpio);
}
return 0;
}
2.1.3 平台数据API
通过平台数据结构获取设备树信息。
设备树示例:
my-platform-device {
compatible = "vendor,my-platform-device";
reg = <0x10000000 0x1000>,
<0x20000000 0x2000>;
reg-names = "control", "data";
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
clock-frequency = <24000000>;
status = "okay";
};
主要函数:
- platform_get_resource():获取平台资源
- platform_get_irq():获取中断号
- platform_get_resource_byname():通过名称获取平台资源
示例代码:
#include <linux/platform_device.h>
static int my_platform_driver_probe(struct platform_device *pdev)
{
struct resource *res;
int irq;
// 获取内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -EINVAL;
}
dev_info(&pdev->dev, "Memory resource: start=0x%lx, end=0x%lx\n",
res->start, res->end);
// 获取中断资源
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get irq\n");
return irq;
}
dev_info(&pdev->dev, "IRQ: %d\n", irq);
// 通过名称获取资源
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
if (res) {
dev_info(&pdev->dev, "Control memory: start=0x%lx, end=0x%lx\n",
res->start, res->end);
}
return 0;
}
2.2 内核空间专用API
2.2.1 时钟相关API
专门用于获取时钟相关信息的API。
设备树示例:
my-clock-device {
compatible = "vendor,my-clock-device";
reg = <0x10000000 0x1000>;
clocks = <&clk_sys>, <&clk_peri>;
clock-names = "core", "peripheral";
clock-frequency = <24000000>;
status = "okay";
};
主要函数:
- of_clk_get():从设备树中获取时钟
- of_clk_get_by_name():通过名称从设备树中获取时钟
- of_clk_count():计算设备的时钟数量
- of_clk_add_provider():添加时钟提供者
- of_clk_set_defaults():设置默认时钟配置
示例代码:
#include <linux/clk.h>
#include <linux/of.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct clk *clk;
int num_clocks;
int ret;
// 计算时钟数量
num_clocks = of_clk_count(node);
dev_info(dev, "Found %d clocks\n", num_clocks);
// 获取默认时钟
clk = of_clk_get(node, 0);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(dev, "Failed to get clock: %d\n", ret);
return ret;
}
// 启用时钟
ret = clk_prepare_enable(clk);
if (ret) {
dev_err(dev, "Failed to enable clock: %d\n", ret);
clk_put(clk);
return ret;
}
dev_info(dev, "Clock rate: %lu Hz\n", clk_get_rate(clk));
// 禁用时钟
clk_disable_unprepare(clk);
clk_put(clk);
// 通过名称获取时钟
clk = of_clk_get_by_name(node, "core");
if (!IS_ERR(clk)) {
dev_info(dev, "Core clock rate: %lu Hz\n", clk_get_rate(clk));
clk_put(clk);
}
return 0;
}
2.2.2 中断相关API
专门用于获取中断相关信息的API。
设备树示例:
my-interrupt-device {
compatible = "vendor,my-interrupt-device";
reg = <0x10000000 0x1000>;
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 11 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "rx", "tx";
status = "okay";
};
主要函数:
- of_irq_get():从设备树中获取中断号
- of_irq_get_byname():通过名称从设备树中获取中断号
- of_irq_count():计算设备的中断数量
- of_irq_to_resource():将中断转换为资源
- of_msi_map():映射MSI中断
示例代码:
#include <linux/of_irq.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
int irq;
int num_irqs;
// 计算中断数量
num_irqs = of_irq_count(node);
dev_info(dev, "Found %d interrupts\n", num_irqs);
// 获取默认中断
irq = of_irq_get(node, 0);
if (irq < 0) {
dev_err(dev, "Failed to get irq: %d\n", irq);
return irq;
}
dev_info(dev, "Got interrupt: %d\n", irq);
// 通过名称获取中断
irq = of_irq_get_byname(node, "rx");
if (irq > 0) {
dev_info(dev, "Got RX interrupt: %d\n", irq);
}
return 0;
}
2.2.3 I2C相关API
专门用于获取I2C总线和设备信息的API。
设备树示例:
i2c@1c2ac00 {
compatible = "allwinner,sun8i-h3-i2c";
reg = <0x01c2ac00 0x400>;
clocks = <&ccu CLK_BUS_I2C0>;
resets = <&ccu RST_BUS_I2C0>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
status = "okay";
my-i2c-device@50 {
compatible = "vendor,my-i2c-device";
reg = <0x50>;
power-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
status = "okay";
};
};
主要函数:
- of_i2c_get_address():从设备树中获取I2C设备地址
- of_i2c_register_device():注册I2C设备
- of_find_i2c_device_by_node():通过节点查找I2C设备
- i2c_new_device():创建新的I2C设备
- i2c_new_ancillary_device():创建辅助I2C设备
示例代码:
#include <linux/i2c.h>
static int my_i2c_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device_node *node = dev->of_node;
u32 address;
int ret;
// 获取I2C设备地址
ret = of_i2c_get_address(node, 0, &address);
if (ret) {
dev_err(dev, "Failed to get I2C address: %d\n", ret);
return ret;
}
dev_info(dev, "I2C address: 0x%x\n", address);
// 读取设备树属性
if (of_property_read_bool(node, "power-gpios")) {
dev_info(dev, "Power GPIO property found\n");
}
return 0;
}
2.2.4 SPI相关API
专门用于获取SPI总线和设备信息的API。
设备树示例:
spi@1c68000 {
compatible = "allwinner,sun8i-h3-spi";
reg = <0x01c68000 0x1000>;
clocks = <&ccu CLK_BUS_SPI0>;
resets = <&ccu RST_BUS_SPI0>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
status = "okay";
my-spi-device@0 {
compatible = "vendor,my-spi-device";
reg = <0>;
spi-max-frequency = <10000000>;
spi-cpha;
spi-cpol;
status = "okay";
};
};
主要函数:
- of_find_spi_device_by_node():通过节点查找SPI设备
- spi_new_device():创建新的SPI设备
- spi_parse_dt():解析SPI设备树节点
- of_spi_max_speed_hz():获取SPI最大速度
示例代码:
#include <linux/spi/spi.h>
static int my_spi_driver_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct device_node *node = dev->of_node;
u32 max_speed;
int ret;
dev_info(dev, "SPI device registered: %s\n", dev_name(dev));
dev_info(dev, "SPI mode: %d\n", spi->mode);
dev_info(dev, "SPI bits per word: %d\n", spi->bits_per_word);
dev_info(dev, "SPI max speed: %d Hz\n", spi->max_speed_hz);
// 读取设备树属性
ret = of_property_read_u32(node, "max-speed", &max_speed);
if (!ret) {
dev_info(dev, "max-speed from DT: %d Hz\n", max_speed);
}
return 0;
}
2.2.5 通用资源API
用于获取各种硬件资源的通用API。
设备树示例:
my-resource-device {
compatible = "vendor,my-resource-device";
reg = <0x10000000 0x1000>, /* 控制寄存器 */
<0x20000000 0x4000>; /* 数据寄存器 */
reg-names = "control", "data";
interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_sys>;
clock-names = "core";
status = "okay";
};
主要函数:
- of_address_to_resource():将设备树地址转换为资源
- of_iomap():映射设备树中的内存区域
- of_get_address():获取设备树中的地址信息
- of_resource_count():计算设备的资源数量
- of_find_property():查找设备树属性
示例代码:
#include <linux/of_address.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct resource res;
void __iomem *base;
int ret;
// 获取地址资源
ret = of_address_to_resource(node, 0, &res);
if (ret) {
dev_err(dev, "Failed to get address resource: %d\n", ret);
return ret;
}
dev_info(dev, "Memory resource: start=0x%lx, end=0x%lx\n",
res.start, res.end);
// 映射内存区域
base = of_iomap(node, 0);
if (!base) {
dev_err(dev, "Failed to map memory\n");
return -ENOMEM;
}
// 使用映射的内存
dev_info(dev, "Memory mapped successfully\n");
// 取消映射
iounmap(base);
return 0;
}
3. 设备树示例
3.1 LED GPIO设备树示例
leds {
compatible = "my-led-driver";
led1-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; // 低电平有效
led2-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; // 高电平有效
enable-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
// 多个相同功能的GPIO
led-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>,
<&gpio0 5 GPIO_ACTIVE_LOW>,
<&gpio0 6 GPIO_ACTIVE_LOW>;
};
3.2 读取示例
3.2.1 内核驱动读取示例
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
static int my_led_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct gpio_desc *led1, *led2, *enable;
struct gpio_descs *leds;
int ret, i;
dev_info(dev, "Probing LED driver\n");
// 获取单个GPIO
led1 = gpiod_get(dev, "led1", GPIOD_OUT_LOW);
if (IS_ERR(led1)) {
ret = PTR_ERR(led1);
dev_err(dev, "Failed to get led1 GPIO: %d\n", ret);
return ret;
}
led2 = gpiod_get(dev, "led2", GPIOD_OUT_LOW);
if (IS_ERR(led2)) {
ret = PTR_ERR(led2);
dev_err(dev, "Failed to get led2 GPIO: %d\n", ret);
gpiod_put(led1);
return ret;
}
enable = gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(enable)) {
ret = PTR_ERR(enable);
dev_err(dev, "Failed to get enable GPIO: %d\n", ret);
gpiod_put(led1);
gpiod_put(led2);
return ret;
}
// 获取多个GPIO
leds = gpiod_get_array(dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(leds)) {
ret = PTR_ERR(leds);
dev_err(dev, "Failed to get led GPIOs: %d\n", ret);
gpiod_put(led1);
gpiod_put(led2);
gpiod_put(enable);
return ret;
}
dev_info(dev, "Found %zu LEDs in array\n", leds->ndescs);
// 测试GPIO操作
gpiod_set_value(enable, 1); // 启用LEDs
gpiod_set_value(led1, 1); // 打开LED1
msleep(1000);
gpiod_set_value(led1, 0); // 关闭LED1
gpiod_set_value(led2, 1); // 打开LED2
msleep(1000);
gpiod_set_value(led2, 0); // 关闭LED2
// 测试LED数组
for (i = 0; i < leds->ndescs; i++) {
gpiod_set_value(leds->desc[i], 1);
msleep(500);
}
for (i = 0; i < leds->ndescs; i++) {
gpiod_set_value(leds->desc[i], 0);
msleep(500);
}
gpiod_set_value(enable, 0); // 禁用LEDs
// 释放资源
gpiod_put_array(leds);
gpiod_put(enable);
gpiod_put(led2);
gpiod_put(led1);
return 0;
}
static int my_led_driver_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Removing LED driver\n");
return 0;
}
static const struct of_device_id my_led_driver_of_match[] = {
{ .compatible = "my-led-driver" },
{ }
};
static struct platform_driver my_led_driver = {
.driver = {
.name = "my-led-driver",
.of_match_table = my_led_driver_of_match,
},
.probe = my_led_driver_probe,
.remove = my_led_driver_remove,
};
module_platform_driver(my_led_driver);
MODULE_DESCRIPTION("LED driver using device tree");
MODULE_LICENSE("GPL");
3.2.2 用户空间读取示例
#!/bin/bash
# 查看LED设备树节点
LED_NODE="/sys/firmware/devicetree/base/leds"
if [ -d "$LED_NODE" ]; then
echo "Found LEDs node"
# 读取compatible属性
if [ -f "$LED_NODE/compatible" ]; then
echo -n "compatible: "
cat "$LED_NODE/compatible"
echo
fi
# 读取GPIO属性
for gpio in led1-gpios led2-gpios enable-gpios led-gpios; do
if [ -f "$LED_NODE/$gpio" ]; then
echo -n "$gpio: "
hexdump -C "$LED_NODE/$gpio"
echo
fi
done
else
echo "LEDs node not found"
fi
4. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 节点查找失败 | 节点路径或名称错误 | 检查设备树结构,确保路径正确 |
| 属性读取失败 | 属性不存在或类型错误 | 检查设备树中的属性定义,确保类型匹配 |
| GPIO获取失败 | GPIO编号错误或被占用 | 检查设备树中的GPIO定义,确保GPIO可用 |
| 权限错误 | 权限不足 | 使用root权限或正确设置文件权限 |
| 设备树格式错误 | 设备树语法错误 | 使用dtc工具检查设备树语法 |
5. 总结
Linux系统提供了多种方法来读取设备树硬件信息,包括内核空间的OF API、GPIO相关API、平台数据API,以及用户空间的sysfs接口、dtc工具和libfdt库。
正确使用这些方法可以帮助开发人员:
- 更好地理解硬件配置
- 编写更通用的设备驱动
- 快速调试硬件相关问题
- 提高系统的可移植性
随着嵌入式系统的不断发展,设备树在Linux系统中的作用越来越重要,掌握设备树的读取方法对于嵌入式开发人员来说是一项必备技能。

177

被折叠的 条评论
为什么被折叠?



