1.简介
主要是描述一下linux驱动中,使用的设备树解析API,在实际使用中,设备树节点,翻译为platform_device的过程中,会主动翻译 我们常见的内容,部分客制化内容,需要我们手动进行翻译解析。 主要了解内容概述:
- 1.哪些资源自动解析,比如寄存器地址、中断、时钟、DMA等等。
- 2.非标准资源,怎么使用ofproperty 接口解析。
- 3.分清楚物理地址,虚拟地址,内核操作虚拟地址,如何将物理地址、硬件中断转化为虚拟地址、软中断号。
备注: Open Firmware 开源固件,体现到linux开发上,主要是设备树(Device Tree)机制。
- 当 CONFIG_OF=y 时,内核支持解析设备树(.dtb 文件),通过设备树获取硬件信息(如内存地址、中断号、设备兼容性等)。
- 当 CONFIG_OF=n 时,内核不支持设备树,通常用于传统架构(如早期 x86)或无设备树的系统。 IS_ENABLED(CONFIG_OF) //OF 是 Open Firmware 的简写, 使用这个宏进行判断。
2.主动解析部分
Linux内核中,platform_device 的创建过程会自动解析设备树中的资源(比如:中断号、内存地址等等,),驱动开发工作就不需要 在probe函数中重复解析设备树。 当设备树解析生成platform_device时,内核会自动完成以下资源解析: 设备树代码示例(使用全志F1C200S):
i2c0: i2c@1C27000 { //0x1C27000为I2C0的寄存器首地址
compatible = "allwinner,sun6i-a31-i2c";
reg = <0x01C27000 0x400>;//寄存器范围
interrupts = <7>; //i2c0中断号
clocks = <&ccu CLK_BUS_I2C0>;
resets = <&ccu RST_BUS_I2C0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pd_pins>; //i2c0引脚需要为pio中复用的引脚
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
lradc: lradc@1c23400 {
compatible = "allwinner,sun4i-a10-lradc-keys";
reg = <0x01C23400 0x400>;
interrupts = <22>;
};
2.1 中断号解析
上面的示例中IIC的interupts表示硬件中断号 7。(硬件中断号,从芯片手册上获取) platform_device的resource数组中会包含中断资源,驱动可以通过platform_get_irq()直接获取中断号。
int irq = platform_get_irq(pdev, 0); /* 直接获取第一个中断号,是该设备节点的第一个,如果存在多个,请注意 */
获取中断号后可以进行中断处理:
error = devm_request_irq(dev, platform_get_irq(pdev, 0),
sun4i_lradc_irq, 0,
"sun4i-a10-lradc-keys", lradc);
static irqreturn_t sun4i_lradc_irq(int irq, void *dev_id)
{
struct sun4i_lradc_data *lradc = dev_id;
u32 i, ints, val, voltage, diff, keycode = 0, closest = 0xffffffff;
ints = readl(lradc->base + LRADC_INTS);//这里将物理地址已经转换到了虚拟地址!才能操作!
if (ints & CHAN0_KEYUP_IRQ) {//通过中断寄存器,判断是哪一位触发的,也就是何种中断发生了
input_report_key(lradc->input, lradc->chan0_keycode, 0);
lradc->chan0_keycode = 0;
}
if ((ints & CHAN0_KEYDOWN_IRQ) && lradc->chan0_keycode == 0) {
val = readl(lradc->base + LRADC_DATA0) & 0x3f;
voltage = val * lradc->vref / 63;
printk("vol:(%d)_(%d)\r\n", val, voltage);
for (i = 0; i < lradc->chan0_map_count; i++) {
diff = abs(lradc->chan0_map[i].voltage - voltage);
if (diff < closest) {
closest = diff;
keycode = lradc->chan0_map[i].keycode;
}
}
lradc->chan0_keycode = keycode;
input_report_key(lradc->input, lradc->chan0_keycode, 1);
}
input_sync(lradc->input);
writel(ints, lradc->base + LRADC_INTS); //清中断标志,等待下一轮中断的发生!
return IRQ_HANDLED;
}
中断号相关备注:(从硬件中断源-->软件中断号)
解析设备树属性:of_irq_get 首先读取 interrupts 属性,拿到 “硬件中断号”(如 34)和对应的中断控制器节点(通过 interrupt-parent 属性关联,默认是系统根中断控制器);
中断控制器映射:调用中断控制器驱动的 xlate 回调(不同中断控制器如 GIC、PIC 有不同实现),将 “硬件中断号” 转换为内核可识别的 软件 IRQ 号;
返回软件 IRQ 号:这个软件 IRQ 号是内核全局唯一的编号(如 56),后续驱动可通过 request_irq 注册中断处理函数。
2.2 内存/IO地址解析
paltform_device的resource数组中会包含有内存资源,驱动可以通过platform_get_resource()获取。 设备树:(仍然使用上面提及的设备adc key)
lradc: lradc@1c23400 {
compatible = "allwinner,sun4i-a10-lradc-keys";
reg = <0x01C23400 0x400>;
interrupts = <22>;
};
上面寄存器地址为0x1c23400,宽度为0x400,
//常使用API
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
void __iomem *base = devm_ioremap_resource(&pdev->dev, res);
解析内存地址:
struct sun4i_lradc_data {
struct device *dev;
struct input_dev *input;
void __iomem *base;
...
};
//probe里面处理
void xxx_probe(platform_device *pdev)
{
struct sun4i_lradc_data *lradc;
struct device *dev = &pdev->dev;
//分配资源
lradc = devm_kzalloc(dev, sizeof(struct sun4i_lradc_data), GFP_KERNEL);
if (!lradc)
return -ENOMEM;
//获取reg的地址(从物理地址,转换到虚拟地址,注意非devm接口,要unioremap!!)
lradc->base = devm_ioremap_resource(dev,
platform_get_resource(pdev, IORESOURCE_MEM, 0));
if (IS_ERR(lradc->base))
return PTR_ERR(lradc->base);
//操作reg(上层),比如清除中断flag
u32 ints = readl(lradc->base + LRADC_INTS);
writel(ints, lradc->base + LRADC_INTS);
}
2.3 其它标准资源
其它资源,比如:时钟, DMA通道,电源管理解析等等,驱动在probe函数中可以直接通过platform_device接口获取解析好的 资源,无需手动解析设备树
时钟:
of_device_add_clocks(), 解析clocks和clocks-name的属性
DMA:
of_device_add_dma(), 解析dma dma-names。
电源:
解析power-domains等属性。
自动解析,可以从arch_initcall_sync开始,遍历了设备树节点。 参考:(函数调用链)
arch_initcall_sync(of_platform_default_populate_init) → of_platform_populate() → 遍历设备树节点并调用 of_platform_bus_create()
3.用户客制化内容
也就是非标准属性(比如自定义参数、非标准设备树结构),这个需要通过of_propertyread*()进行手动解析! 参考:(以全志F1C200S为参考)
//------fake device tree node parse test--------//
xiaodong_fake_device_tree {
compatible = "xiaodong_fake_device_tree_compatible";
status="okay";
is_fake_value; //解析测试
value = <200>;
interrupt-parent = <&pio>;//中断控制器
interrupts = <4 12 IRQ_TYPE_EDGE_FALLING>;//硬件中断号 12
device_tree_node1 {
node_name = "node_name_1";
node_value = <1>;
};
device_tree_node2 {
node_name = "node_name_2";
node_value = <2>;
};
};
static int xiaodong_device_tree_parse_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node; //first_layer_node---父节点指针,第一级开始
struct device_node *next; //second_layer_node---子节点指针
//动态分配存储按键信息的内存,这个函数使用后不用手动释放,驱动卸载时会自动释放
_device_node_info = devm_kzalloc(dev, sizeof(device_tree_node_parse_struct), GFP_KERNEL);
if (_device_node_info == NULL) {
printk("[%s]{%d},no memory!\r\n",__FUNCTION__, __LINE__);
return -ENOMEM;
}
//当前node节点中,子node数量
_device_node_info->device_node_number = of_get_child_count(np);
printk("node_count: %d\r\n", _device_node_info->device_node_number);
//当前node节点中,bool变量类型值获取
_device_node_info->is_fake_value = of_property_read_bool(np, "is_fake_value");
printk("is_fake_value: %d\r\n", _device_node_info->is_fake_value);
//当前node节点,string变量值获取
of_property_read_string(np, "status", &(_device_node_info->status) );
printk("status: %s \r\n", _device_node_info->status);
//当前node节点,u32值获取
of_property_read_u32(np, "value", &(_device_node_info->value) );
printk("value: %d \r\n", _device_node_info->value);
//解析中断控制信息
{
int irq;
char *data_to_irq = "dong";
//获取第一个中断号[从设备树中获取]--从np节点中获取,第一个中断号
irq = of_irq_get(np, 0);
if(irq <= 0)
{
// dev_error(&pdev->dev, "Failed to get IRQ\r\n");
return -ENXIO;
}
printk("-the soft irq: %d \r\n", irq);
//注册中断处理函数(IRQF_SHARED,多个设备可共享同一中断号[0-可以设置为非共享中断])
devm_request_irq(&pdev->dev, irq,my_irq_handle,
IRQF_SHARED, "xiaodong", data_to_irq);//dev_name(&pdev->dev)
}
//遍历子节点的信息
for_each_child_of_node(np, next)
{
const char *node_name = NULL;
u32 node_value = 0;
static int index = 1;
//读取 string, 这是子节点里面的,也就是next节点!
of_property_read_string(next, "node_name", &(node_name));
of_property_read_u32(next, "node_value", &(node_value));
printk("node[%d]-------------\r\n",index);
printk("node_name: %s \r\n", node_name);
printk("node_value: %d \r\n", node_value);
}
return 0;
}
4.总结
了解设备树,从基本流程概念开始,主要注意各类接口使用。
- 1.明确哪些设备树内部资源是标准化,系统已经解析,可以使用platform接口进行操作
- 2.对非标准资源,请注意解析接口,比如:of_propertyread*()