设备树的概念(三) :处理资源(Handling resources)

发布时间 2023-03-27 19:47:52作者: 闹闹爸爸

驱动程序的主要目的是处理和管理设备,大多数时候将它们的功能暴露给用户空间。这里的目标是收集设备的配置参数,特别是资源(内存区域、中断线、DMA通道、时钟等)。

下面是我们将在本文中使用的设备节点。它是i.MX6 UART设备的节点,定义在arch/arm/boot/dts/imx6qdl.dtsi中:

uart1: serial@02020000 {
    compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <0 26 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6QDL_CLK_UART_IPG>,
             <&clks IMX6QDL_CLK_UART_SERIAL>;
    clock-names = "ipg", "per";
    dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
    dma-names = "rx", "tx";
    status = "disabled";
};        

命名资源的概念

当驱动程序需要一个特定类型的资源列表时,你不能保证列表是按照驱动程序所期望的方式排序的,因为编写板级设备树的人通常不是编写驱动程序的人。例如,一个驱动程序可能期望它的设备节点有两条IRQ线,一条用于索引0的Tx事件,另一条用于索引1的Rx事件。如果不遵守命令会发生什么?  驱动会有不受欢迎的行为。为了避免这种不匹配,引入了命名资源(时钟、irq、dma、reg)的概念。它包括定义我们的资源列表并命名它们,因此无论它们的索引是什么,给定的名称总是与资源匹配。

资源命名对应的属性如下:

  • reg-names: 这是针对reg属性中的内存区域列表。
  • clock-names: 这是在clocks属性中的命名时钟。
  • interrupt-names: 这将在interrupts属性中为每个中断提供一个名称。
  • dma-names: 这是为了dma属性。

我们创建一个假设备节点条目来解释:

fake_device {
    compatible = "packt,fake-device";
    reg = <0x02020000 0x4000>, <0x4a064800 0x200>,         
          <0x4a064c00 0x200>;
    reg-names = "config", "ohci", "ehci";
    interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>, <0 67 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-names = "ohci", "ehci";
    clocks = <&clks IMX6QDL_CLK_UART_IPG>, <&clks IMX6QDL_CLK_UART_SERIAL>;
    clock-names = "ipg", "per";
    dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
    dma-names = "rx", "tx";
};

驱动程序中提取每个命名资源的代码如下所示:

struct resource *res1, *res2;
res1 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ohci");
res2 = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config");

struct dma_chan *dma_chan_rx, *dma_chan_tx; dma_chan_rx = dma_request_slave_channel(&pdev->dev, "rx"); dma_chan_tx = dma_request_slave_channel(&pdev->dev, "tx");
int txirq, rxirq; txirq
= platform_get_irq_byname(pdev, "ohci");
rxirq = platform_get_irq_byname(pdev, "ehci");

struct clk *clck_per, *clk_ipg;
clk_ipg = devm_clk_get(&pdev->dev, "ipg");
clk_ipg = devm_clk_get(&pdev->dev, "pre");

通过这种方式,您可以确保将正确的名称映射到正确的资源,而不需要再使用索引。

访问寄存器

在这里,驱动程序将获得内存区域的所有权,并将其映射到虚拟地址空间。

struct resource *res;
void __iomem *base;
res
= platform_get_resource(pdev, IORESOURCE_MEM, 0); /* * Here one can request and map the memory region * using request_mem_region(res->start, resource_size(res), pdev->name) * and ioremap(iores->start, resource_size(iores) * */ base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base))   return PTR_ERR(base);

platform_get_resource()将根据第一个(索引0)reg分配中的内存区域设置struct res的开始和结束字段。请记住platform_get_resource()的最后一个参数表示资源索引。在前面的示例中,索引0意味着该资源类型的第一个值,以防设备在DT节点中被分配多个内存区域。在我们的示例中,它是reg = <0x02020000 0x4000>,这意味着分配的区域从物理地址0x02020000开始,大小为0x4000字节。platform_get_resource()将设置 res.start = 0x02020000 和 res.end = 0x02023fff。

处理中断

中断接口实际上分为两部分——消费者端和控制器端。DT中有四个属性用来描述中断连接:

控制器是向消费者公开IRQ线路的设备。在控制器侧,设备具有以下属性:

  • interrupt-controller: 一个空(布尔值)属性,你应该定义它来标记设备为中断控制器。
  • #interrupt-cells: 这是中断控制器的一个属性。它表示使用了多少个cell为该中断控制器指定一个中断。

消费者是生成IRQ的设备。消费者绑定需要以下属性:

  • interrupt-parent: 对于产生中断的设备节点,它是一个包含指向设备所连接的控制节点中断的phandle指针的属性。如果省略,则设备从其父节点继承该属性。
  • interrupts:这是中断说明符。

中断binding和中断说明符绑定到中断控制器设备。用于定义中断输入的cell数量取决于中断控制器,它是通过其唯一的#interrupt-cells属性决定的。例如在i.MX6中,中断控制器是一个全局中断控制器(GIC)。它的binding在Documentation/devicetree/bindings/arm/gic.txt中有很好的解释。

 中断处理程序

这包括从DT获取IRQ号,并将其映射到Linux IRQ,从而为它注册一个函数回调。这样做的驱动程序代码如下:

int irq = platform_get_irq(pdev, 0);
ret = request_irq(irq, imx_rxint, 0, dev_name(&pdev->dev), sport);

platform_get_irq()调用将返回irq号;这个数字可以被devm_request_irq()使用(irq在/proc/interrupts中可见)。

第二个参数0表示我们需要在设备节点中指定的第一个中断。如果有多个中断,我们可以根据需要的中断更改这个索引,或者只使用命名的资源。

在前面的例子中,设备节点包含一个中断说明符,如下所示:

interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>;
  • 根据ARM GIC,第一个单元告诉我们中断类型:
    • 0: 共享外围中断(Shared peripheral interrupt, SPI),表示核间共享的中断信号,可以由GIC路由到任何核
    • 1: 私有外围中断(PPI),用于私有于单个核心的中断信号

文档可以在http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0407e/CCHDBEBE.html找到。

  • 第二个cell保存中断号。这个数字取决于中断线是PPI还是SPI。
  • 第三个cell,在本例中是IRQ_TYPE_LEVEL_HIGH,表示触发方式(IRQ_TYPE_LEVEL_HIGH表示高电平触发)。所有可用的检测级别都定义在include/linux/irq.h中。

中断控制器代码

interrupt-controller属性用于将一个设备声明为中断控制器。#interrupt-cells属性定义了定义单个中断线必须使用多少个cell。

提取特定于应用程序的数据

特定于应用程序的数据是超出公共属性的数据(既不是resources也不是gpio、调节器等等)。这些是可以分配给设备的任意属性和子节点。这样的属性名通常以制造商code作为前缀。这些可以是任何字符串、布尔值或整数值,以及它们在Linux源代码的drivers/of/base.c中定义的API。下面我们讨论的例子并不详尽。现在让我们重用前面定义的节点:

node_label: nodename@reg{
    string-property = ""a string"";
    string-list = ""red fish"", ""blue fish"";
    one-int-property = <197>; /* One cell in this property */
    int-list-property = <0xbeef 123 0xabcd4>;/* each number (cell) is 32 a 
                                              * bit integer(uint32). There
                                              * are 3 cells in this property
                                              */
    mixed-list-property = "a string", <0xadbcd45>, <35>, [0x01 0x23 0x45]
    byte-array-property = [0x01 0x23 0x45 0x67];    
    one-cell-property = <197>;
    boolean-property;
};

文本字符串

下面是一个字符串属性:

string-property = "a string";

回到驱动程序中,您应该使用of_property_read_string()来读取字符串值。其原型定义如下:

int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string)

下面的代码展示了如何使用它:

const char *my_string = NULL;
of_property_read_string(pdev->dev.of_node, "string-property", &my_string);

Cells和32位无符号整数

以下是我们的int属性:

one-int-property = <197>;
int-list-property = <1350000 0x54dae47 1250000 1200000>;

你应该使用of_property_read_u32()来读取cell值。其原型定义如下:

int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)

回到驱动程序中,编写以下代码:

unsigned int number;
of_property_read_u32(pdev->dev.of_node, "one-int-property", &number);

可以使用of_property_read_u32_array读取cell列表。其原型如下:

int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);

这里,sz是要读取的数组元素的个数。看一下drivers/of/base.c,看看如何解释它的返回值:

unsigned int cells_array[4];
if (of_property_read_u32_array(pdev->dev.of_node, "int-list-property", cells_array, 4)) {
    dev_err(&pdev->dev, "list of cells not specified\n");
    return -EINVAL;
}

布尔值 

你应该使用of_property_read_bool()来读取在函数的第二个参数中给出的布尔属性:

bool my_bool = of_property_read_bool(pdev->dev.of_node, "booleanproperty");
if(my_bool){
    /* boolean is true */
} else
    /* Bolean is false */
}

提取和解析子节点

您可以在设备节点中添加任何子节点。给定一个表示闪存设备的节点,分区可以表示为子节点。对于处理一组输入和输出GPIO的设备,每一组都可以表示为一个子节点。示例节点如下所示:

eeprom: ee24lc512@55 {
    compatible = "microchip,24xx512";
    reg = <0x55>;
    
    partition1 {
        read-only;
        part-name = "private";
        offset = <0>;
        size = <1024>;
    };
    
    partition2 {
        part-name = "data";
        offset = <1024>;
        size = <64512>;
    };
};

可以使用for_each_child_of_node()遍历给定节点的子节点:

struct device_node *np = pdev->dev.of_node;
struct device_node *sub_np;
for_each_child_of_node(np, sub_np) {
    /* sub_np will point successively to each sub-node */
    [...]
    int size;
    of_property_read_u32(client->dev.of_node, "size", &size);
    ...
}