Linux dtsi文件解析

您所在的位置:网站首页 sfp格式怎么打开看 Linux dtsi文件解析

Linux dtsi文件解析

2024-03-15 18:52| 来源: 网络整理| 查看: 265

目录

来历

驱动代码解析

driver加载

dtsi文件内容解析

        四个重要解析函数     

​​​​​​​

来历

        Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正。在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。读者有兴趣可以统计下常见的s3c2410、s3c6410等板级目录,代码量在数万行。         社区必须改变这种局面,于是PowerPC等其他体系架构下已经使用的Flattened Device Tree(FDT)进入ARM社区的视野。Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。         Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

以下为一个dtsi示例

#include #include #include / { #address-cells = ; #size-cells = ; interrupt-parent = ; aliases { ethernet0 = &emac; i2c0 = &i2c0; i2c1 = &i2c1; mshc0 = &emmc; mshc1 = &mmc0; serial0 = &uart0; serial1 = &uart1; spi0 = &spi0; spi1 = &spi1; }; amba { compatible = "simple-bus"; #address-cells = ; #size-cells = ; ranges; dmac1_s: dma-controller@20018000 { compatible = "arm,pl330", "arm,primecell"; reg = ; interrupts = , ; #dma-cells = ; arm,pl330-broken-no-flushp; clocks = ; clock-names = "apb_pclk"; }; dmac1_ns: dma-controller@2001c000 { compatible = "arm,pl330", "arm,primecell"; reg = ; interrupts = , ; #dma-cells = ; arm,pl330-broken-no-flushp; clocks = ; clock-names = "apb_pclk"; status = "disabled"; }; dmac2: dma-controller@20078000 { compatible = "arm,pl330", "arm,primecell"; reg = ; interrupts = , ; #dma-cells = ; arm,pl330-broken-no-flushp; clocks = ; clock-names = "apb_pclk"; }; }; uart0: serial@10124000 { compatible = "snps,dw-apb-uart"; reg = ; interrupts = ; reg-shift = ; reg-io-width = ; clock-names = "baudclk", "apb_pclk"; clocks = , ; status = "disabled"; }; uart1: serial@10126000 { compatible = "snps,dw-apb-uart"; reg = ; interrupts = ; reg-shift = ; reg-io-width = ; clock-names = "baudclk", "apb_pclk"; clocks = , ; status = "disabled"; }; usb_otg: usb@10180000 { compatible = "rockchip,rk3066-usb", "snps,dwc2"; reg = ; interrupts = ; clocks = ; clock-names = "otg"; dr_mode = "otg"; g-np-tx-fifo-size = ; g-rx-fifo-size = ; g-tx-fifo-size = ; phys = ; phy-names = "usb2-phy"; status = "disabled"; }; usb_host: usb@101c0000 { compatible = "snps,dwc2"; reg = ; interrupts = ; clocks = ; clock-names = "otg"; dr_mode = "host"; phys = ; phy-names = "usb2-phy"; status = "disabled"; }; emac: ethernet@10204000 { compatible = "snps,arc-emac"; reg = ; interrupts = ; #address-cells = ; #size-cells = ; rockchip,grf = ; clocks = , ; clock-names = "hclk", "macref"; max-speed = ; phy-mode = "rmii"; status = "disabled"; }; emmc: dwmmc@1021c000 { compatible = "rockchip,rk2928-dw-mshc"; reg = ; interrupts = ; clocks = , ; clock-names = "biu", "ciu"; dmas = ; dma-names = "rx-tx"; fifo-depth = ; resets = ; reset-names = "reset"; status = "disabled"; }; pmu: pmu@20004000 { compatible = "rockchip,rk3066-pmu", "syscon", "simple-mfd"; reg = ; reboot-mode { compatible = "syscon-reboot-mode"; offset = ; mode-normal = ; mode-recovery = ; mode-bootloader = ; mode-loader = ; }; }; i2c0: i2c@2002d000 { compatible = "rockchip,rk3066-i2c"; reg = ; interrupts = ; #address-cells = ; #size-cells = ; rockchip,grf = ; clock-names = "i2c"; clocks = ; status = "disabled"; }; wdt: watchdog@2004c000 { compatible = "snps,dw-wdt"; reg = ; clocks = ; interrupts = ; status = "disabled"; }; saradc: saradc@2006c000 { compatible = "rockchip,saradc"; reg = ; interrupts = ; #io-channel-cells = ; clocks = , ; clock-names = "saradc", "apb_pclk"; resets = ; reset-names = "saradc-apb"; status = "disabled"; }; spi0: spi@20070000 { compatible = "rockchip,rk3066-spi"; clocks = , ; clock-names = "spiclk", "apb_pclk"; interrupts = ; reg = ; #address-cells = ; #size-cells = ; dmas = , ; dma-names = "tx", "rx"; status = "disabled"; }; lradc: lradc@20080000 { compatible = "test,adc-keys"; reg = ; interrupts = ; vref-supply = ; button@191 { label = "Volume Up"; linux,code = ; channel = ; voltage = ; }; button@392 { label = "Volume Down"; linux,code = ; channel = ; voltage = ; }; button@601 { label = "Menu"; linux,code = ; channel = ; voltage = ; }; button@795 { label = "Enter"; linux,code = ; channel = ; voltage = ; }; button@987 { label = "Home"; linux,code = ; channel = ; voltage = ; }; }; };

        以上涉及到usb、uart、IIC、EMMC、spi等等基本涵盖了Linux常用驱动类型,对比发现它们都有compatible、status这两个,这两个也是必不可少的。compatible是驱动作为platform设备之后,内核加载的时候进行匹配,如果compatible里面字串和你写的驱动里compatible的不一样就会加载失败;status一般是disabled或者okay,如果你用到这个设备,就设定为okay,如果不用到它就设定为disabled。

驱动代码解析 driver加载

        我们以一个adc按键驱动为例,它在上面dtsi文件的最后一个,驱动加载

static const struct of_device_id adc_of_match[] = { { .compatible = "test,adc-keys", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, adc_of_match); static struct platform_driver adc_driver = { .driver = { .name = "adc-keys", .of_match_table = of_match_ptr(adc_of_match), }, .probe = adc_probe, }; module_platform_driver(adc_driver);

        上面compatible字段的字符串要跟dtsi文件的字符串一致才可以被加载上,匹配完之后即可执行probe函数

static int adc_probe(struct platform_device *pdev) { struct adc_data *lradc; struct device *dev = &pdev->dev; int i; int error; lradc = devm_kzalloc(dev, sizeof(struct adc_data), GFP_KERNEL); if (!lradc) return -ENOMEM; error = adc_load_dt_keymap(dev, lradc); if (error) return error; lradc->vref_supply = devm_regulator_get(dev, "vref"); if (IS_ERR(lradc->vref_supply)) return PTR_ERR(lradc->vref_supply); lradc->dev = dev; lradc->input = devm_input_allocate_device(dev); if (!lradc->input) return -ENOMEM; lradc->input->name = pdev->name; lradc->input->phys = "adc/input0"; lradc->input->open = adc_open; lradc->input->close = adc_close; lradc->input->id.bustype = BUS_HOST; lradc->input->id.vendor = 0x0001; lradc->input->id.product = 0x0001; lradc->input->id.version = 0x0100; __set_bit(EV_KEY, lradc->input->evbit); for (i = 0; i < lradc->chan0_map_count; i++) __set_bit(lradc->chan0_map[i].keycode, lradc->input->keybit); input_set_drvdata(lradc->input, lradc); lradc->base = devm_ioremap_resource(dev, platform_get_resource(pdev, IORESOURCE_MEM, 0)); if (IS_ERR(lradc->base)) return PTR_ERR(lradc->base); error = devm_request_irq(dev, platform_get_irq(pdev, 0), adc_irq, 0, "adc-keys", lradc); if (error) return error; error = input_register_device(lradc->input); if (error) return error; return 0; } dtsi文件内容解析

        上面probe函数跑到adc_load_dt_keymap就会从dtsi中读取按键配置信息,然后存到adc_data对应结构体中,下面来重点分析一下对dtsi文件的解析

static int adc_load_dt_keymap(struct device *dev, struct adc_data *lradc) { struct device_node *np, *pp; int i; int error; np = dev->of_node; if (!np) return -EINVAL; lradc->chan0_map_count = of_get_child_count(np); if (lradc->chan0_map_count == 0) { dev_err(dev, "keymap is missing in device tree\n"); return -EINVAL; } lradc->chan0_map = devm_kmalloc_array(dev, lradc->chan0_map_count, sizeof(struct adc_keymap), GFP_KERNEL); if (!lradc->chan0_map) return -ENOMEM; i = 0; for_each_child_of_node(np, pp) { struct adc_keymap *map = &lradc->chan0_map[i]; u32 channel; error = of_property_read_u32(pp, "channel", &channel); if (error || channel != 0) { dev_err(dev, "%s: Inval channel prop\n", pp->name); return -EINVAL; } error = of_property_read_u32(pp, "voltage", &map->voltage); if (error) { dev_err(dev, "%s: Inval voltage prop\n", pp->name); return -EINVAL; } error = of_property_read_u32(pp, "linux,code", &map->keycode); if (error) { dev_err(dev, "%s: Inval linux,code prop\n", pp->name); return -EINVAL; } i++; } return 0; }         四个重要解析函数     

        of_get_child_count从dtsi中获取节点的数目,具体代码实现如下,从实现中可以看出它调用的是for_each_child_of_node

static inline int of_get_child_count(const struct device_node *np) { struct device_node *child; int num = 0; for_each_child_of_node(np, child) num++; return num; }

        devm_kmalloc_array根据节点数目来分配空间,本示例根据我们定义的device和上面得到的节点数目分配一个块内存空间供后面从dsti查找相关关键字后填充。这里用的是数组型malloc,最终也是用到devm_kmalloc

static inline void *devm_kmalloc_array(struct device *dev, size_t n, size_t size, gfp_t flags) { if (size != 0 && n > SIZE_MAX / size) return NULL; return devm_kmalloc(dev, n * size, flags); }

        for_each_child_of_node轮询节点,用的是一个宏定义循环查找

#define for_each_child_of_node(parent, child) \ for (child = of_get_next_child(parent, NULL); child != NULL; \ child = of_get_next_child(parent, child))

        of_property_read_u32根据设备节点device_node查找字符串

static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) { return of_property_read_u32_array(np, propname, out_value, 1); } static inline int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz) { int ret = of_property_read_variable_u32_array(np, propname, out_values, sz, 0); if (ret >= 0) return 0; else return ret; } int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max) { size_t sz, count; const __be32 *val = of_find_property_value_of_size(np, propname, (sz_min * sizeof(*out_values)), (sz_max * sizeof(*out_values)), &sz); if (IS_ERR(val)) return PTR_ERR(val); if (!sz_max) sz = sz_min; else sz /= sizeof(*out_values); count = sz; while (count--) *out_values++ = be32_to_cpup(val++); return sz; }

以上of相关的代码都可以通过Linux源码找到相关的定义,有兴趣的读者可以继续深挖代码是如何实现的。

完整代码如下

#include #include #include #include #include #include #include #include #include #include #define LRADC_CTRL 0x00 #define LRADC_INTC 0x04 #define LRADC_INTS 0x08 #define LRADC_DATA0 0x0c #define LRADC_DATA1 0x10 /* LRADC_CTRL bits */ #define FIRST_CONVERT_DLY(x) ((x) 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; } static int adc_open(struct input_dev *dev) { struct adc_data *lradc = input_get_drvdata(dev); int error; error = regulator_enable(lradc->vref_supply); if (error) return error; /* lradc Vref internally is divided by 2/3 */ lradc->vref = regulator_get_voltage(lradc->vref_supply) * 2 / 3; /* * Set sample time to 4 ms / 250 Hz. Wait 2 * 4 ms for key to * stabilize on press, wait (1 + 1) * 4 ms for key release */ writel(FIRST_CONVERT_DLY(2) | LEVELA_B_CNT(1) | HOLD_EN(1) | SAMPLE_RATE(0) | ENABLE(1), lradc->base + LRADC_CTRL); writel(CHAN0_KEYUP_IRQ | CHAN0_KEYDOWN_IRQ, lradc->base + LRADC_INTC); return 0; } static void adc_close(struct input_dev *dev) { struct adc_data *lradc = input_get_drvdata(dev); /* Disable lradc, leave other settings unchanged */ writel(FIRST_CONVERT_DLY(2) | LEVELA_B_CNT(1) | HOLD_EN(1) | SAMPLE_RATE(2), lradc->base + LRADC_CTRL); writel(0, lradc->base + LRADC_INTC); regulator_disable(lradc->vref_supply); } static int adc_load_dt_keymap(struct device *dev, struct adc_data *lradc) { struct device_node *np, *pp; int i; int error; np = dev->of_node; if (!np) return -EINVAL; lradc->chan0_map_count = of_get_child_count(np); if (lradc->chan0_map_count == 0) { dev_err(dev, "keymap is missing in device tree\n"); return -EINVAL; } lradc->chan0_map = devm_kmalloc_array(dev, lradc->chan0_map_count, sizeof(struct adc_keymap), GFP_KERNEL); if (!lradc->chan0_map) return -ENOMEM; i = 0; for_each_child_of_node(np, pp) { struct adc_keymap *map = &lradc->chan0_map[i]; u32 channel; error = of_property_read_u32(pp, "channel", &channel); if (error || channel != 0) { dev_err(dev, "%s: Inval channel prop\n", pp->name); return -EINVAL; } error = of_property_read_u32(pp, "voltage", &map->voltage); if (error) { dev_err(dev, "%s: Inval voltage prop\n", pp->name); return -EINVAL; } error = of_property_read_u32(pp, "linux,code", &map->keycode); if (error) { dev_err(dev, "%s: Inval linux,code prop\n", pp->name); return -EINVAL; } i++; } return 0; } static int adc_probe(struct platform_device *pdev) { struct adc_data *lradc; struct device *dev = &pdev->dev; int i; int error; lradc = devm_kzalloc(dev, sizeof(struct adc_data), GFP_KERNEL); if (!lradc) return -ENOMEM; error = adc_load_dt_keymap(dev, lradc); if (error) return error; lradc->vref_supply = devm_regulator_get(dev, "vref"); if (IS_ERR(lradc->vref_supply)) return PTR_ERR(lradc->vref_supply); lradc->dev = dev; lradc->input = devm_input_allocate_device(dev); if (!lradc->input) return -ENOMEM; lradc->input->name = pdev->name; lradc->input->phys = "adc/input0"; lradc->input->open = adc_open; lradc->input->close = adc_close; lradc->input->id.bustype = BUS_HOST; lradc->input->id.vendor = 0x0001; lradc->input->id.product = 0x0001; lradc->input->id.version = 0x0100; __set_bit(EV_KEY, lradc->input->evbit); for (i = 0; i < lradc->chan0_map_count; i++) __set_bit(lradc->chan0_map[i].keycode, lradc->input->keybit); input_set_drvdata(lradc->input, lradc); lradc->base = devm_ioremap_resource(dev, platform_get_resource(pdev, IORESOURCE_MEM, 0)); if (IS_ERR(lradc->base)) return PTR_ERR(lradc->base); error = devm_request_irq(dev, platform_get_irq(pdev, 0), adc_irq, 0, "adc-keys", lradc); if (error) return error; error = input_register_device(lradc->input); if (error) return error; return 0; } static const struct of_device_id adc_of_match[] = { { .compatible = "test,adc-keys", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, adc_of_match); static struct platform_driver adc_driver = { .driver = { .name = "adc-keys", .of_match_table = of_match_ptr(adc_of_match), }, .probe = adc_probe, }; module_platform_driver(adc_driver); MODULE_DESCRIPTION("adc attached tablet keys driver"); MODULE_LICENSE("GPL");



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3