ranges属性的应用?若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 Windows Windows11 Ubuntu Ubuntu20.04.2的64位版本 VMware® Workstation 17 Pro 17.6.0 build-24238078 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license)) Win32DiskImager Win32DiskImager v1.0 Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板 uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
点击查看相关文件下载
1. 怎么获取资源? 前面我们知道,对于platform_device来说,我们有两种方式可以获取对应的节点,一是of操作函数,就正常的查找对应节点即可,二是 platform_device .dev .of_node 。通过这两种方式我们可以获取到节点的地址,从而获取节点中各种资源。
由于设备树在系统启动的时候有部分节点会转化为 platform 设备,对于platform_device设备来说
(1)如果在设备树节点里使用 reg 属性,那么内核生成对应的 platform_device时会用 reg 属性来设置 IORESOURCE_MEM 类型的资源。
(2)如果在设备树节点里使用 interrupts 属性 ,那么内核生成对应的platform_device 时会用 reg 属性来设置 IORESOURCE_IRQ 类型的资源。(对于interrupts 属性,内核会检查它的有效性,所以不建议在设备树里使用该属性来表示其他资源。 )
所以,实际上当设备树中的节点被转换为platform_device 后,设备树中的 reg 属性、 interrupts 属性也会被转换为“ resource”,内核为我们提供了函数platform_get_resource() 来获取这些不同的资源。接下来就来看一下吧。
platform_get_resource() 函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct resource *platform_get_resource (struct platform_device *dev, unsigned int type, unsigned int num) { int i; for (i = 0 ; i < dev->num_resources; i++) { struct resource *r = &dev->resource[i]; if (type == resource_type(r) && num-- == 0 ) return r; } return NULL ; } EXPORT_SYMBOL_GPL(platform_get_resource);
参数说明:
dev:struct platform_device类型指针,包含了设备树节点的相关信息。
type:资源的类型,IORESOURCE_MEM、 IORESOURCE_REG、IORESOURCE_IRQ 等
num:这类资源中的哪一个。
3. 一个报错 那么我们用上面的platform_get_resource() 函数来尝试获取一下资源。
3.1 设备树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /dts-v1/; / { model = "Freescale i.MX6 UlltraLite ALPHA EMMC Board" ; compatible = "fsl,imx6ull-alpha-emmc" , "fsl,imx6ull" ; alpha { #address-cells = <1> ; #size-cells = <1> ; compatible = "simple-bus" ; sdev_led { #address-cells = <1> ; #size-cells = <1> ; compatible = "led" ; status = "okay" ; reg = < 0X020C406C 0X04 0X020E0068 0X04 0X020E02F4 0X04 0X0209C000 0X04 0X0209C004 0X04 >; }; }; };
3.2 demo源码 源码可以看这里:11_device_tree/06_ranges_property · 苏木/imx6ull-driver-demo - 码云 - 开源中国 。主要是添加一个platform_get_resource() 函数获取资源的部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static int sdrv_probe (struct platform_device *pdev) { int ret = 0 ; struct resource *p_resources ; PRT("probing platform device & driver!pdev->name=%s\n" , pdev->name); p_resources = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); if (p_resources == NULL ) { PRTE("platform_get_resource is error\n" ); ret = -1 ; goto err_platform_get_resource; } PRT("%s start: 0x%x, end: 0x%x\n" , p_resources->name, p_resources->start, p_resources->end + 1 ); return 0 ; err_platform_get_resource: return ret; }
3.3 开发板测试 我们编译后,加载对应的模块,出现下面的报错:
可以看到使用 platform_get_resource() 函数获取 reg 资源的函数失败了,后面我们就来分析一下为什么。
4. 获取资源失败分析 4.1 返回错误值分析 platform_get_resource() 定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct resource *platform_get_resource (struct platform_device *dev, unsigned int type, unsigned int num) { int i; for (i = 0 ; i < dev->num_resources; i++) { struct resource *r = &dev->resource[i]; if (type == resource_type(r) && num-- == 0 ) return r; } return NULL ; }
该函数返回 NULL 符合上面报错的时候的情况, 这里返回 NULL 的情况有两种可能性, 一种是没进入上面的 for 循环直接返回了 NULL, 另外一种是进入了 for 循环, 但是类型匹配不正确, 跳出 for循环之后再返回 NULL。
这里的类型一定是匹配的, 所以我们就来寻找为什么没有进入 for 循环,这里只有一种可能, 也就是 dev->num_resources 为 0。所以现在的目标来到了寻找 dev->num_resources 是在哪里进行的赋值, 前面已经知道过了由设备树转换为 platform 的过程, 而且在系统启动后, 在对应目录下也有了相应的节点:
1 ls /sys/bus/platform/devices/
证明转换是没问题的, 所以继续寻找中间转换过程中有关资源数量的相关函数, 定位到 of_platform_device_create_pdata() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static struct platform_device *of_platform_device_create_pdata ( struct device_node *np, const char *bus_id, void *platform_data, struct device *parent) { struct platform_device *dev ; if (!of_device_is_available(np) || of_node_test_and_set_flag(np, OF_POPULATED)) return NULL ; dev = of_device_alloc(np, bus_id, parent); if (!dev) goto err_clear_flag; dev->dev.coherent_dma_mask = DMA_BIT_MASK(32 ); if (!dev->dev.dma_mask) dev->dev.dma_mask = &dev->dev.coherent_dma_mask; dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; of_msi_configure(&dev->dev, dev->dev.of_node); if (of_device_add(dev) != 0 ) { platform_device_put(dev); goto err_clear_flag; } return dev; err_clear_flag: of_node_clear_flag(np, OF_POPULATED); return NULL ; }
第13行:函数调用 of_device_alloc() 分配一个平台设备结构体, 并将设备节点指针、 设备标识符和父设备指针传递给它, 正是该函数决定的 resource.num。
of_device_alloc() 函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 struct platform_device *of_device_alloc (struct device_node *np, const char *bus_id, struct device *parent) { struct platform_device *dev ; int rc, i, num_reg = 0 , num_irq; struct resource *res , temp_res ; dev = platform_device_alloc("" , PLATFORM_DEVID_NONE); if (!dev) return NULL ; while (of_address_to_resource(np, num_reg, &temp_res) == 0 ) num_reg++; num_irq = of_irq_count(np); if (num_irq || num_reg) { res = kcalloc(num_irq + num_reg, sizeof (*res), GFP_KERNEL); if (!res) { platform_device_put(dev); return NULL ; } dev->num_resources = num_reg + num_irq; dev->resource = res; for (i = 0 ; i < num_reg; i++, res++) { rc = of_address_to_resource(np, i, res); WARN_ON(rc); } if (of_irq_to_resource_table(np, res, num_irq) != num_irq) pr_debug("not all legacy IRQ resources mapped for %pOFn\n" , np); } dev->dev.of_node = of_node_get(np); dev->dev.fwnode = &np->fwnode; dev->dev.parent = parent ? : &platform_bus; if (bus_id) dev_set_name(&dev->dev, "%s" , bus_id); else of_device_make_bus_id(&dev->dev); return dev; }
在第 28 行出现了 for 循环的 dev->num_resources = num_reg + num_irq; reg 的 number 和 irq的 number, 由于在设备树中并没有添加中断相关的属性 num_irq 为 0, 那这里的 num_reg 是哪里确定的呢。我们向上找到 14、 15 行, 具体内容如下所示:
1 2 3 4 while (of_address_to_resource(np, num_reg, &temp_res) == 0 ) num_reg++; num_irq = of_irq_count(np);
所以这里我们需要去看一下 of_address_to_resource() 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int of_address_to_resource (struct device_node *dev, int index, struct resource *r) { const __be32 *addrp; u64 size; unsigned int flags; const char *name = NULL ; addrp = of_get_address(dev, index, &size, &flags); if (addrp == NULL ) return -EINVAL; of_property_read_string_index(dev, "reg-names" , index, &name); return __of_address_to_resource(dev, addrp, size, flags, name, r); }
第 9 行:获取 reg 属性的地址、 大小和类型, 在设备树中 reg 属性已经存在了, 所以这里会正确返回。
第 14 行: 读取 reg-names 属性, 由于设备树中没有定义这个属性, 所以该函数不会有影响.
第 16 行:最后具有决定性作用的函数就是返回的 __of_address_to_resource() 函数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static int __of_address_to_resource(struct device_node *dev, const __be32 *addrp, u64 size, unsigned int flags, const char *name, struct resource *r) { u64 taddr; if (flags & IORESOURCE_MEM) taddr = of_translate_address(dev, addrp); else if (flags & IORESOURCE_IO) taddr = of_translate_ioport(dev, addrp, size); else return -EINVAL; if (taddr == OF_BAD_ADDR) return -EINVAL; memset (r, 0 , sizeof (struct resource)); r->start = taddr; r->end = taddr + size - 1 ; r->flags = flags; r->name = name ? name : dev->full_name; return 0 ; }
reg 属性的 flags 为 IORESOURCE_MEM, 所以又会执行第 7 行的 of_translate_address() 函数,看一下这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 u64 of_translate_address (struct device_node *dev, const __be32 *in_addr) { struct device_node *host ; u64 ret; ret = __of_translate_address(dev, in_addr, "ranges" , &host); if (host) { of_node_put(host); return OF_BAD_ADDR; } return ret; }
该函数的重点在第 6 行, 上述函数实际上是 __of_translate_address() 函数的封装, 其中传入的第三个参数“ranges”是我们要关注的重点.
继续跳转到__of_translate_address() 函数的定义:
1 2 3 4 5 6 static u64 __of_translate_address(struct device_node *dev, const __be32 *in_addr, const char *rprop, struct device_node **host) { }
1 2 3 4 5 parent = of_get_parent(dev); if (parent == NULL ) goto bail; bus = of_match_bus(parent);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (;;) { if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop)) break ; na = pna; ns = pns; bus = pbus; of_dump_addr("one level translation:" , addr, na); }
使用 of_translate_one() 函数进行转换, 其中 rprop 参数表示要转换的资源属性, 该参数的值为传入的“ranges”
of_translate_one() 函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static int of_translate_one (struct device_node *parent, struct of_bus *bus, struct of_bus *pbus, __be32 *addr, int na, int ns, int pna, const char *rprop) { const __be32 *ranges; unsigned int rlen; int rone; u64 offset = OF_BAD_ADDR; ranges = of_get_property(parent, rprop, &rlen); if (ranges == NULL && !of_empty_ranges_quirk(parent)) { pr_debug("no ranges; cannot translate\n" ); return 1 ; } if (ranges == NULL || rlen == 0 ) { offset = of_read_number(addr, na); memset (addr, 0 , pna * 4 ); pr_debug("empty ranges; 1:1 translation\n" ); goto finish; } }
在该函数的第 26 行使用 of_get_property() 函数获取“ranges”属性, 但由于在我们添加的设备树节点中并没有该属性, 所以这里的 ranges 值就为 NULL, 第 27 行的条件判断成立, 也就会返回 1。
4.6 一步一步返回 of_get_property() 函数返回NULL,所以 of_translate_one() 返回1
1 2 3 4 5 6 7 u64 result = OF_BAD_ADDR; for (;;) { if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop)) break ; }
这里直接跳出循环,最终会返回 OF_BAD_ADDR 。再上一级的 of_address_to_resource() 返回值也是 OF_ BAD _ADDR, 继续向上查找 __of_address_to_resource() 函数会返回 EINVAL, of_address_to_resource() 返回 EINVAL, 所以 num_reg 为 0;
4.7 总结 到这里关于为什么 platform_get_resource() 函数获取资源失败的问题就找到了, 只是因为在设备树中并没有这个名为 ranges 这个属性, 所以只需要对设备树进行 ranges 属性的添加即可。
5. 问题解决 5.1 设备树 修改设备树:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /dts-v1/; / { model = "Freescale i.MX6 UlltraLite ALPHA EMMC Board" ; compatible = "fsl,imx6ull-alpha-emmc" , "fsl,imx6ull" ; alpha { #address-cells = <1> ; #size-cells = <1> ; ranges; compatible = "simple-bus" ; sdev_led { #address-cells = <1> ; #size-cells = <1> ; compatible = "led" ; status = "okay" ; reg = < 0X020C406C 0X04 0X020E0068 0X04 0X020E02F4 0X04 0X0209C000 0X04 0X0209C004 0X04 >; }; }; };
5.2 开发板测试 我们更新开发板的设备树文件,然后重新加载驱动,就会发现,资源获取成功了:
二、ranges属性? 虽然上面的问题解决了, 但这里并没有结束, 我们在这里添加的 ranges 属性的作用是啥呢?为什么要有这个ranges属性?
1. ranges 属性介绍 ranges 属性是一种用于描述设备之间地址映射关系的属性。 它在设备树(Device Tree) 中使用, 用于描述子设备地址空间如何映射到父设备地址空间。 设备树是一种硬件描述语言, 用于描述嵌入式系统中的硬件组件和它们之间的连接关系。
设备树中的每个设备节点都可以具有 ranges 属性, 其中包含了地址映射的信息。 下面是一个常见的格式:
1 2 3 ranges = <child-bus-address parent-bus-address length>; ranges;
child-bus-address: 子设备地址空间的起始地址。 它指定了子设备在父设备地址空间中的位置。 具体的字长由 ranges 所在节点的 #address-cells 属性决定。
parent-bus-address: 父设备地址空间的起始地址。 它指定了父设备中用于映射子设备的地址范围。 具体的字长由 ranges 的父节点的 #address-cells 属性决定。
length: 映射的大小。 它指定了子设备地址空间在父设备地址空间中的长度。 具体的字长由 ranges 的父节点的 #size-cells 属性决定。
当 ranges 属性的值为空时, 表示子设备地址空间和父设备地址空间具有完全相同的映射,即 1:1 映射。 这通常用于描述内存区域, 其中子设备和父设备具有相同的地址范围。
当 ranges 属性的值不为空时, 按照指定的映射规则将子设备地址空间映射到父设备地址空间。 具体的映射规则取决于设备树的结构和设备的特定要求。
2. 举个例子 2.1 实例1 2.1.1 设备树实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 / { #address-cells = <1> ; #size-cells = <1> ; demo_level0 { compatible = "simple-bus" ; ranges = <0x0 0x3000000 0x3000 >; #address-cells = <1> ; #size-cells = <1> ; range@0 { compatible = "range" ; reg = <0x100 0x200 >; reg-names = "range0" ; }; range@1 { compatible = "range" ; reg = <0x300 0x200 >; reg-names = "range1" ; }; range@2 { compatible = "range" ; reg = <0x600 0x200 >; reg-names = "range2" ; }; demo_level1 { compatible = "simple-bus" ; ranges = <0x0 0x1000 0x1000 >; #address-cells = <1> ; #size-cells = <1> ; range@3 { compatible = "range" ; reg = <0x100 0x200 >; reg-names = "range3" ; }; demo_level1-1 { compatible = "simple-bus" ; ranges = <0x0 0x300 0x500 >; #address-cells = <1> ; #size-cells = <1> ; range@4 { compatible = "range" ; reg = <0x100 0x200 >; reg-names = "range4" ; }; range@5 { compatible = "range" ; reg = <0x300 0x100 >; reg-names = "range5" ; }; demo_level1-1 -1 { compatible = "simple-bus" ; ranges = <0x0 0x400 0x100 >; #address-cells = <1> ; #size-cells = <1> ; range@6 { compatible = "range" ; reg = <0x50 0x30 >; reg-names = "range6" ; }; demo_level1-1 -1 -1 { compatible = "simple-bus" ; ranges = <0x0 0x20 0x20 >; #address-cells = <1> ; #size-cells = <1> ; range@7 { compatible = "range" ; reg = <0x10 0x10 >; reg-names = "range7" ; }; range@8 { compatible = "range" ; reg = <0x0 0x10 >; reg-names = "range8" ; }; }; }; }; range@9 { compatible = "range" ; reg = <0x800 0x50 >; reg-names = "range9" ; }; demo_level1-2 { compatible = "simple-bus" ; ranges = <0x0 0x900 0x100 >; #address-cells = <1> ; #size-cells = <1> ; range@10 { compatible = "range" ; reg = <0x0 0x50 >; reg-names = "range10" ; }; demo_level1-2 -1 { compatible = "simple-bus" ; ranges; #address-cells = <1> ; #size-cells = <1> ; range@11 { compatible = "range" ; reg = <0x50 0x30 >; reg-names = "range11" ; }; }; }; }; demo_level2 { compatible = "simple-bus" ; ranges; #address-cells = <1> ; #size-cells = <1> ; range@12 { compatible = "range" ; reg = <0x2000 0x1000 >; reg-names = "range12" ; }; }; } };
2.1.2 驱动demo 下面是一个简单的驱动,功能很简单,只是在probe函数中将memory资源的start和(end+1)打印出来.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> static int demo_range_probe (struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); printk(KERN_INFO "%s start: 0x%x, end: 0x%x\n" , res->name, res->start, res->end + 1 ); return 0 ; } static int demo_range_remove (struct platform_device *pdev) { return 0 ; } static const struct of_device_id demo_range_of_match [] = { { .compatible = "range" }, {}, }; static struct platform_driver demo_range_driver = { .driver = { .name = "demo_range" , .owner = THIS_MODULE, .of_match_table = demo_range_of_match, }, .probe = demo_range_probe, .remove = demo_range_remove, }; module_platform_driver(demo_range_driver); MODULE_LICENSE("GPL v2" );
在驱动中会获得memory资源,然后将start和(end+1)打印出来,之所以这里用(end+1),仅仅是为了便于理解下面的kernel log。
2.1.3 开发板测试 编译驱动,然后加载,可以看到下面的打印信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@vexpress mnt]# insmod demo_range.ko [ 382.940402] range0 start: 0x3000100, end: 0x3000300 [ 382.940697] range1 start: 0x3000300, end: 0x3000500 [ 382.941448] range2 start: 0x3000600, end: 0x3000800 [ 382.941657] range3 start: 0x3001100, end: 0x3001300 [ 382.941855] range4 start: 0x3001400, end: 0x3001600 [ 382.942057] range5 start: 0x3001600, end: 0x3001700 [ 382.942262] range6 start: 0x3001750, end: 0x3001780 [ 382.942470] range7 start: 0x3001730, end: 0x3001740 [ 382.942684] range8 start: 0x3001720, end: 0x3001730 [ 382.949796] range9 start: 0x3001800, end: 0x3001850 [ 382.950023] range10 start: 0x3001900, end: 0x3001950 [ 382.950603] range11 start: 0x3001950, end: 0x3001980 [ 382.950805] range12 start: 0x3002000, end: 0x3003000
画成框图帮助理解就是这样的:
2.1.4 总结
(1)ranges属性值的格式 <**local地址** **parent地址** **size**>, 表示将local地址向parent地址的转换。
比如对于#address-cells和#size-cells都为1的话,以<0x0 0x10 0x20>为例,表示将local的从0x0~(0x0 + 0x20)的地址空间映射到parent的0x10~(0x10 + 0x20)
其中,local地址 的个数取决于当前含有ranges属性的节点的#address-cells属性的值,size 取决于当前含有ranges属性的节点的#size-cells属性的值。而parent地址 的个数取决于当前含有ranges属性的节点的parent节点的#address-cells的值。
(2)对于含有ranges属性的节点的子节点来说,其reg都是基于local地址 的
(3)ranges属性值为空的话,表示1:1映射
(4)对于没有ranges属性的节点,代表不是memory map区域。
2.2 实例2 以下面的设备树为例进行 ranges 属性的学习:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /dts-v1/; / { compatible = "acme,coyotes-revenge" ; #address-cells = <1> ; #size-cells = <1> ; external-bus { #address-cells = <2> ; #size-cells = <1> ; ranges = <0 0 0x10100000 0x10000 1 0 0x10160000 0x10000 2 0 0x30000000 0x30000000 >; };
在 external-bus 节点中#address-cells 属性值为 2 表示 child-bus-address 由两个值表示, 也就是 0 和 0, 父节点的 #address-cells 属性值和#size-cells 属性值为 1, 表示 parent-bus-address和 length 都由一个表示, 也就是 0x10100000 和 0x10000 , 该 ranges 值表示将子地址空间(0x0-0xFFFF) 映射到父地址空间 0x10100000 - 0x1010FFFF, 这里的例子为带参数 ranges 属性映射, 不带参数的 ranges 属性为 1: 1 映射, 较为简单。
2.3 总结 在嵌入式系统中, 不同的设备可能连接到相同的总线或总线控制器上, 它们需要在物理地址空间中进行正确的映射, 以便进行数据交换和通信。 例如, 一个设备可能通过总线连接到主处理器或其他设备, 而这些设备的物理地址范围可能不同。 ranges 属性就是用来描述这种地址映射关系的 。
3. 设备分类 根据上面讲解的映射关系可以将设备分为两类: 内存映射型设备和非内存映射型设备。
3.1 内存映射型设备 内存映射型设备是指可以通过内存地址进行直接访问的设备。 这类设备在物理地址空间中的一部分被映射到系统的内存地址空间中, 使得 CPU 可以通过读写内存地址的方式与设备进行通信和控制。有以下特点:
(1) 直接访问: 内存映射型设备可以被 CPU 直接访问, 类似于访问内存中的数据。 这种直接访问方式提供了高速的数据传输和低延迟的设备操作。
(2) 内存映射: 设备的寄存器、 缓冲区等资源被映射到系统的内存地址空间中, 使用读写内存的方式与设备进行通信。
(3) 读写操作: CPU 可以通过读取和写入映射的内存地址来与设备进行数据交换和控制操作。
在设备树中, 内存映射型设备的设备树例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /dts-v1/; / { #address-cells = <1> ; #size-cells = <1> ; ranges; serial@101f 0000 { compatible = "arm,pl011" ; reg = <0x101f0000 0x1000 >; }; gpio@101f 3000 { compatible = "arm,pl061" ; reg = <0x101f3000 0x1000 0x101f4000 0x10 >; }; spi@10115000 { compatible = "arm,pl022" ; reg = <0x10115000 0x1000 >; }; };
第 5 行的 ranges 属性表示该设备树中会进行 1: 1 的地址范围映射。
3.2 非内存映射型设备 非内存映射型设备是指不能通过内存地址直接访问的设备。 这类设备可能采用其他方式与CPU 进行通信, 例如通过 I/O 端口、 专用总线或特定的通信协议。有以下特点:
(1) 非内存访问: 非内存映射型设备不能像内存映射型设备那样直接通过内存地址进行访问。 它们可能使用独立的 I/O 端口或专用总线进行通信。
(2) 特定接口: 设备通常使用特定的接口和协议与 CPU 进行通信和控制, 例如 SPI、 I2C、UART 等。
(3) 驱动程序: 非内存映射型设备通常需要特定的设备驱动程序来实现与 CPU 的通信和控制。
在设备树中, 非内存映射型设备的设备树例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /dts-v1/; / { compatible = "acme,coyotes-revenge" ; #address-cells = <1> ; #size-cells = <1> ; external-bus { #address-cells = <2> ; #size-cells = <1> ; ranges = <0 0 0x10100000 0x10000 1 0 0x10160000 0x10000 2 0 0x30000000 0x30000000 >; ethernet@0 ,0 { compatible = "smc,smc91c111" ; reg = <0 0 0x1000 >; }; i2c@1 ,0 { compatible = "acme,a1234-i2c-bus" ; #address-cells = <1> ; #size-cells = <0> ; reg = <1 0 0x1000 >; rtc@58 { compatible = "maxim,ds1338" ; reg = <0x58 >; }; }; }; };
4. 映射地址计算 接下来以上面列举的非内存映射型设备的设备树中的 ethernet@0 节点为例, 计算该网卡设备的映射地址。
首先, 找到 ethernet@0 所在的节点, 并查看其 reg 属性。 在给定的设备树片段中, ethernet@0 的 reg 属性为<0 0 0x1000>。 在根节点中, #address-cells 的值为 1, 表示地址由一个单元格组成。
接下来, 根据 ranges 属性进行地址映射计算。 在 external-bus 节点的 ranges 属性中, 有三个映射条目:
第一个映射条目为“0 0 0x10100000 0x10000” , 表示外部总线的地址范围为 0x10100000到 0x1010FFFF。 该映射条目的第一个值为 0, 表示与 external-bus 节点的第一个子节点(ethernet@0,0) 相关联。
第二个映射条目: “1 0 0x10160000 0x10000” , 表示外部总线的地址范围为 0x10160000到 0x1016FFFF。该映射条目的第一个值为 1, 表示与 external-bus 节点的第二个子节点(i2c@1,0)相关联。
第三个映射条目:“2 0 0x30000000 0x30000000”, 表示外部总线的地址范围为 0x30000000 到 0x5FFFFFFF。 该映射条目的第一个值为 2, 表示与 external-bus 节点的第三个子节点相关联。
由于ethernet@0与external-bus的第一个子节点相关联, 并且它的reg属性为<0 0 0x1000>,我们可以进行以下计算:
1 2 3 4 ethernet@0 的物理起始地址 = 外部总线地址起始值=0x10100000 ethernet@0 的物理结束地址 = 外部总线地址起始值 + (ethernet@0 的 reg 属性的第二个值-1) = 0x10100000 + 0xFFF = 0x10100FFF
因此, ethernet@0 的物理地址范围为 0x10100000 - 0x10100FFF。
参考资料
设备树中ranges属性分析(1) - dolinux - 博客园