嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(5)
接前一篇文章:嵌入式Linux驱动开发 —— 从DTS到代码的桥梁与简单OF系列API(4)
内存映射 API:如何将设备树地址转换为可访问的虚拟地址
前面讲了如何从设备树里读取地址值,但那些只是物理地址(或者总线地址)。驱动程序要访问这些地址,还需要把它们映射到内核虚拟地址空间。这一步通常用ioremap()来完成。
但OF API提供了更便捷的方法,把“读reg属性”和“ioremap”两步合成一步。
of_iomap:一步到位的地址映射
这是驱动里最常用的函数之一:
void __iomem *of_iomap(struct device_node *np, int index);参数说明:
- np:设备节点
- index:reg属性的索引(从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:设备节点
- index:reg属性的索引
- 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直接转成resource:
int of_address_to_resource(struct device_node *dev, int index, struct resource *r);参数说明:
dev:设备节点index:reg属性的索引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()就行了。
更多内容请看下回。
