接前一篇文章嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API4内存映射 API如何将设备树地址转换为可访问的虚拟地址前面讲了如何从设备树里读取地址值但那些只是物理地址或者总线地址。驱动程序要访问这些地址还需要把它们映射到内核虚拟地址空间。这一步通常用ioremap()来完成。但OF API提供了更便捷的方法把“读reg属性”和“ioremap”两步合成一步。of_iomap一步到位的地址映射这是驱动里最常用的函数之一void __iomem *of_iomap(struct device_node *np, int index);参数说明np设备节点indexreg属性的索引从0开始返回值是映射后的内核虚拟地址失败返回NULL。这个函数会自动完成以下步骤1从reg属性里读取第index组地址2处理地址转换如果需要的话3调用ioremap()建立映射。我们的LED驱动用它来映射所有寄存器地址/* 5. 使用 of_iomap 进行寄存器地址映射 */ led.ccm_ccgr1 of_iomap(led.device_tree_node, 0); led.sw_mux_gpio of_iomap(led.device_tree_node, 1); led.sw_pad_gpio of_iomap(led.device_tree_node, 2); led.gpio_dr of_iomap(led.device_tree_node, 3); led.gpio_gdir of_iomap(led.device_tree_node, 4); if (!led.ccm_ccgr1 || !led.sw_mux_gpio || !led.sw_pad_gpio || !led.gpio_dr || !led.gpio_gdir) { pr_err(ioremap failed!\n); of_node_put(led.device_tree_node); return -ENOMEM; }这里连续调用了5次of_iomap()每次传入不同的索引。这些索引对应reg属性里的5组地址reg 0X020C406C 0X04 /* 索引 0: CCM_CCGR1_BASE */ 0X020E0068 0X04 /* 索引 1: SW_MUX_GPIO1_IO03_BASE */ 0X020E02F4 0X04 /* 索引 2: SW_PAD_GPIO1_IO03_BASE */ 0X0209C000 0X04 /* 索引 3: GPIO1_DR_BASE */ 0X0209C004 0X04 ; /* 索引 4: GPIO1_GDIR_BASE */注意这里有个重要的错误处理我们检查了所有映射是否成功只要有一个失败就报错退出。这点很重要因为部分成功会导致后续代码访问空指针引发内核panic。of_get_address获取地址原始数据有时候你不想直接映射而是想先拿到地址的原始数据这时候可以用of_get_address()const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags);参数说明dev设备节点indexreg属性的索引size输出参数返回地址长度flags输出参数返回标志比如IORESOURCE_MEM返回值是读取到的地址数据指针大端格式的u32数组失败返回NULL。这个函数返回的是设备树里的原始数据可能还需要地址转换才能变成CPU物理地址。of_translate_address地址转换设备树里的地址有时是总线地址需要转换成CPU物理地址u64 of_translate_address(struct device_node *dev, const __be32 *in_addr);参数说明dev设备节点in_addr从of_get_address()拿到的地址返回值是转换后的物理地址如果是OF_BAD_ADDR表示转换失败。of_address_to_resource转换成标准资源结构Linux内核用struct resource统一描述各种资源。这个函数把设备树里的reg直接转成resourceint of_address_to_resource(struct device_node *dev, int index, struct resource *r);参数说明dev设备节点indexreg属性的索引r输出的resource结构体返回值是0表示成功负值表示失败。这个函数在某些场景下很实用比如你需要把地址信息传递给其它子系统时。但在简单的字符设备驱动里直接用of_iomap()往往更方便。资源管理 API如何正确释放引用到这里我们讲的都是获取资源的 API但Linux内核编程有个黄金法则有获取就必须有释放。OF API也不例外。of_node_put释放节点引用当你用of_find_xxx()系列函数获取了一个device_node指针后你就有了对这个节点的引用。内核用引用计数来管理这些节点当你用完后必须调用of_node_put()来释放引用void of_node_put(struct device_node *node);参数node是你要释放的节点指针。我们的LED驱动在出错处理和反初始化函数里都用到了它/* 出错处理 */ ret of_property_read_u32_array(led.device_tree_node, reg, regdata, 10); if (ret 0) { pr_err(reg property read failed!\n); of_node_put(led.device_tree_node); /* 释放节点引用 */ return -EINVAL; } /* 反初始化函数 */ void led_hw_deinit(void) { /* ... 先 unmap 所有地址 ... */ if (led.device_tree_node) { of_node_put(led.device_tree_node); led.device_tree_node NULL; } }这里有个小技巧我们在释放引用后把指针设为NULL。这样即使deinit()函数被多次调用也不会 double-free。你可能会问of_find_property()需要配合of_node_put()吗答案是不需要。property结构体是device_node的一部分它的生命周期由节点管理。你只需要在用完整个节点后调用一次of_node_put()就行了。更多内容请看下回。