【Linux驱动】Linux设备树(三)—— 在驱动代码中读取设备树属性
【Linux驱动】Linux设备树(三)—— 在驱动代码中读取设备树属性
Linux 内核给我们提供了一系列的函数来获 取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”。
一、设备树节点数据类型
设备都是以节点的形式“挂”到设备树上的,每个节点都包含某个设备的属性信息,Linux 内核使用 device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 中
struct device_node {
const char *name; /* 节点名称 */
const char *type; /* 设备类型 */
phandle phandle;
const char *full_name; /* 节点全名 */
struct fwnode_handle fwnode;
struct property *properties; /* 属性 */
struct property *deadprops; /* removed properties */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
...
};
其中 property 结构体用于描述设备树节点的属性信息,该结构体也定义在 include/linux/of.h
struct property {
char *name;
int length;
void *value;
struct property *next;
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
二、查找设备树节点
1、of_find_node_by_path
通过设备树的绝对路径来查找指定的节点,比如根目录下的 chosen 节点的路径为 " /chosen "
/**
* param - path: 从哪个节点开始向下查找
* return: 失败返回NULL,成功返回找到的节点
*/
struct device_node *of_find_node_by_path(const char *path)
示例: 获取根节点下的 alientek_led 节点
struct device_node* dtsNode;
/* 获取设备节点: /alientek_led */
chrdev.dtsNode = of_find_node_by_path("/alientek_led");
if(chrdev.dtsNode == NULL)
{
printk("node cannot be found!\n");
return -1;
}
三、获取设备树节点的属性值
1、of_find_property(以结构体的形式)
of_find_property 用于获取某个设备节点包含的指定属性。
/**
* param - np: 设备节点
* param - name: 属性名
* param - lenp: 输出型参数,表示属性值的字节数
* return: 失败返回NULL,成功返回获取到的属性
*/
property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
示例:获取 compatible 属性
struct property* proper;
proper = of_find_property(dtsNode, "compatible", NULL);
if(proper == NULL)
{
printk("compatible property read failed!\n");
return -1;
}
printk("compatible: %s\n", (char*)proper->value);
2、of_property_read_string(以字符串的形式)
of_property_read_string 函数用于读取属性中字符串值。
/**
* param - np: 设备节点
* param - propname: 属性名
* param - out_string: 输出型参数,表示读取到的字符串值
* return: 成功返回 0,失败返回负值
*/
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string)
示例:获取 status 属性
int ret = of_property_read_string(dtsNode, "status", &str);
if(ret < 0)
{
printk("status property read failed!\n");
return -1;
}
printk("status: %s\n", str);
3、of_property_read_u32_array(以数组的形式)
of_property_read_u32_array 用于读取属性中 u32 类型的数组数据,比如大多数的 reg 属性就是数组数据。类似的还有
- of_property_read_u8_array:读取属性中 u8 类型的数组数据
- of_property_read_u16_array:读取属性中 u16 类型的数组数据
- of_property_read_u32_array:读取属性中 u32 类型的数组数据
- of_property_read_u64_array:读取属性中 u64 类型的数组数据
/**
* param - np: 设备节点
* param - propname: 属性名
* param - out_string: 输出型参数,表示读取到的u32类型数组
* param - sz: 输入型参数,表示要读取的数组元素数量
* return: 成功返回 0,以下几种情况表示读取失败
1、-EINVAL 表示属性不存在
2、-ENODATA 表示没有要读取的数据
3、-EOVERFLOW 表示属性值列表太小
*/
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz)
示例:获取 reg 属性
u32 regData[14]; // 数组的大小取决于 reg 中数据个数
int ret = of_property_read_u32_array(dtsNode, "reg", regData, 10);
if(ret < 0)
{
printk("reg property read failed!\n");
return -1;
}
四、地址映射转换
Linux系统为了保护物理内存,防止进程直接访问物理地址修改数据,一般会在上电时初始化 MMU(内存管理单元)。我们在设备树中填入的寄存器地址是物理地址,由于 MMU 的存在,无法直接访问物理地址。
因此,我们需要先得到物理地址在Linux系统中的虚拟地址,通过虚拟地址来间接访问物理地址。
1、ioremap
ioremap 函数用于将物理地址映射到虚拟地址。该宏的定义在 arch/arm/include/asm/io.h 文件中
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
extern void __iomem *__arm_ioremap(phys_addr_t, size_t, unsigned int);
cookie:要映射的物理起始地址
size:要映射的内存空间大小
返回值:__iomem 类型的指针,指向映射后虚拟地址
static void __iomem* CCM_CCGR1;
static void __iomem* SW_MUX_GPIO1_IO03;
static void __iomem* SW_PAD_GPIO1_IO03;
static void __iomem* GPIO1_DR;
static void __iomem* GPIO1_GDIR;
// 一般需要搭配 of_property_read_u32_array 使用
CCM_CCGR1 = ioremap(regData[0], regData[1]);
SW_MUX_GPIO1_IO03 = ioremap(regData[2], regData[3]);
SW_PAD_GPIO1_IO03 = ioremap(regData[4], regData[5]);
GPIO1_GDIR = ioremap(regData[6], regData[7]);
GPIO1_DR = ioremap(regData[8], regData[9]);
2、of_iomap
of_iomap 的作用和ioremap一样,但 of_iomap 更多的需要搭配设备树使用,可以通过 of_iomap 获取 reg 属性中的物理地址,并自动转换成虚拟地址。因为 reg 可能存在多个地址,所以需要通过 参数 index 指定获取哪个地址。
该函数的声明在 linux/of_address.h 目录下
void __iomem *of_iomap(struct device_node *device, int index);
device: 设备树节点
index: 要获取虚拟地址映射的段,如果reg只有一段,index设为 0
static void __iomem* CCM_CCGR1;
static void __iomem* SW_MUX_GPIO1_IO03;
static void __iomem* SW_PAD_GPIO1_IO03;
static void __iomem* GPIO1_DR;
static void __iomem* GPIO1_GDIR;
// of_iomap 先从设备树节点获取到 reg 属性的内容,然后再映射到虚拟地址
CCM_CCGR1 = of_iomap(dtsNode, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsNode, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsNode, 2);
GPIO1_GDIR = of_iomap(dtsNode, 3);
GPIO1_DR = of_iomap(dtsNode, 4);
五、读写虚拟内存
即便是虚拟内存,一般也可以直接通过指针解引用来访问某个虚拟地址的内容,但是 Linux 内核不建议这么做,推荐使用 readl 和 writel 函数来读写内存。
1、readb / readw / readl
读操作函数有以下几个,readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作
- 参数 addr:要读取数据的内存地址
- 返回值:读取到的数据。如果是 readb,一次读取 1一个字节的数据
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
2、writeb / writew / writel
写操作函数有如下几个,writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作
- 参数 value:要写入的数据
- 参数 addr:要写入数据的地址
- 返回值:无
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
3、实际应用
一般读写操作函数是搭配使用的,比如我们要修改 CCM_CCGR1 寄存器中 bit 26、bit27 的配置(假设是置 11),通常思路是先将 bit 26、bit 27 清零,然后重新赋值
u32 val;
val = readl(CCM_CCGR1); // 读取原本的值
val &= ~(3 << 26); // 仅清除 bit 26、bit 27的配置
val |= (3 << 26); // bit 26、bit 27赋予新值
writel(val, CCM_CCGR1); // 再写回到原本的地址
注意:先读取原本内容的目的是,避免对其他 bit 位的配置造成影响
更多推荐
所有评论(0)