linux驱动----设备树

简介: linux驱动----设备树

设备树可以说是platform平台的升级版,经过前面的学习,我知道了platform分为两部分,平台设备和平台驱动。而设备树就是替代了平台设备。平台设备是以C文件的方式加载的,而设备树是以配置文件加载的(xxx.dtb,就够有点类似于json),在linux系统启动的时候就加载设备树文件。其实平时我们只需要会修改设备树就好了,不需要自己去写出全部内容,芯片厂家会提供相关设备树的,你只需要根据需要去添加外设就好了。


1. 设备树的语法

1.1 设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设

备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。

label: node-name@unit-address {
  // 各种属性(1.2讲解)
  // 子节点(节点是可以嵌套的)
}
  • lable: 你给这个节点的别名,设备树的其他要要用到的就可以使用**&label**来引用。
  • node-name: 节点名字,一般命名应该与功能挂钩
  • unit-address: 一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要

1.2 属性的数据类型

每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流

1.2.1 字符串

compatible = "arm,cortex-a7"  // 这种类型的字符串是内置的,在平台驱动加载到系通过,匹配时的name来源就是这里

1.2.1 32位无符号整数

这种类型一般用来表示寄存器的地址,以及字符串的偏移量。用<>包裹起来,可有多个整数,用空格隔开

reg = <0x2020>;
reg = <0x2020 0x20200 0x12344>;

1.2.2 字符串列表

用逗号隔开

compatible = "fsl,imx6ull-fire-nand", "fsl,imx6ull-gpmi-nand";

1.3 标准属性

1.3.1 compatible属性

这个属性是很重要的,该属性是一个字符串类型的,用于将设备和驱动绑定起来,在驱动被加载到系统的时候会有一个匹配的过程,设备端的依据就是该属性,驱动就是那个of匹配表

// 驱动的of匹配表实例
static const struct of_device_id rgb_led[] = {
  {.compatible = "fire,my_rgb_led"},
};
static struct platform_driver rgb_led_platform_driver = {
  .probe = rgb_led_probe,
  .remove = rgb_led_remove,
  .driver = {
    .name = "rgb-leds-device-tree",
    .owner = THIS_MODULE,
    //.remove = rgb_led_remove,
    .of_match_table = rgb_led,   // match匹配的时候会用到这个列表里面的compatible和设备树里面的compatible对比
  }
};

1.3.2 status属性

该属性与设备状态有关

描述
“okay” 设备可操作
“disabled” 设备不可操作
“fail” 设备因为检测到错误而不可操作
“fail-sss” 与"fail"相同,后面的sss部分是检测到的错误内容

1.3.3 reg属性

reg属性的值一般是(address, length)对。address表示该设备的起始地址,length表示该设备的地址长度

1.3.4 #address-cells和#size-cells

这两个属性都是unsigned int 类型的,用于描述子节点的地址信息。前者决定了reg里面的地址信息所占的字长(32位),后者决定了reg里面的长度所占的字长(32位)。

1.3.5 model属性

该属性值是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的

2. 设备树常用的OF操作函数

OF操作函数的目的就是为了从设备树中提取驱动需要的资源

2.1 查找节点的OF函数

2.1.1 of_find_node_by_name

通过节点名字查找节点

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
  • from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
  • name: 要查找的节点名字

2.1.2 of_find_compatible_node

通过节点的compatible查找属性(因为device_type已经不使用了,直接赋值为NULL)

struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
  • from: 开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
  • 要查找的device_type 属性值,直接给NULL,忽略掉它
  • 要查找的节点所对应的 compatible 属性列表

2.1.3 of_find_node_by_path

通过全路径查找节点

inline struct device_node *of_find_node_by_path(const char *path)
  • path: 从根节点开始的路径,类似于绝对路径

2.2 查找父/子节点的OF函数

2.2.1 of_get_parent

获得指定节点的父节点

struct device_node *of_get_parent(const struct device_node *node)

2.2.2 of_get_next_child

用迭代的方式查找子节点

struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
  • node: 父节点
  • prev: 该节点为父节点下的一个节点,我们最终获取的就是该节点后面一个节点,如果该节点是父节点最后一个子节点,name就会返回NULL

2.3 提取属性的OF函数

2.3.1 of_find_property

用于查找指定的属性

property *of_find_property(const struct device_node *np, const char *name, int *lenp)
  • np: 设备节点
  • name: 属性名字
  • lenp: 属性的字节数

2.3.2 of_property_count_elems_of_size

用于获取属性中的元素数量,比如reg有多个元素

int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
  • np: 设备节点
  • propname: 需要统计的属性名
  • elem_size: 元素长度
  • 返回值: 元素数量

2.3.3 of_property_read_u32_index

从属性中获取指定标号的u32类型的数据值

int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
  • np: 设备节点
  • propname: 要读取的属性名字
  • index: 要读取的值标号(从0开始)
  • out_value: 读取的数据存放地址
  • 返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。

2.3.4 批量读取

下面四个函数会以数组的方式一次性读取多个值

int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
  • np: 设备节点
  • propname: 要读取的属性名
  • out_values: 存放数据的数组的起始地址
  • sz: 要读取的个数
  • 返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。

2.3.5 单个读取

当reg只有一个值的时候,可以使用下面的函数读取

int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)

2.3.6 of_property_read_string

用户读取属性中的字符串值

int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
  • np: 设备节点
  • propname: 属性名
  • out_string: 读取到的字符串值

2.3.7 of_n_addr_cells

获取#address-cells属性值

int of_n_addr_cells(struct device_node *np)

2.3.8 of_n_size_cells

获取#size-cells属性值

int of_n_size_cells(struct device_node *np)

2.3 其他OF函数

2.3.1 of_iomap

可用于直接内存映射(映射的就是reg的值),比ioremap更加方便,但我不知道我的板子用不了这个函数

void __iomem *of_iomap(struct device_node *np, int index)
  • np: 设备节点
  • index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0
  • 返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败

3. 实例(野火I.MX6ULL pro开发板)

3.1 设备树

在野火官方提供的设备树的根节点下添加如下内容

/* RGB_LED设备节点 */
my_rgb_led {
  #address-cells = <1>;
  #size-cells = <1>;
  compatible = "fire,my_rgb_led";  // 驱动的of_device_id表要和这里的一直
  led_red@0x20C406C {
    compatible = "fire,led_red";
    reg = <0x020C406C 0x00000004
        0x020E006C 0x00000004
        0x020E02F8 0x00000004
        0x0209C004 0x00000004
        0x0209C000 0x00000004>;
    pin = <4>;             /* 引脚*/
    clock_offset = <26>;   /* 时钟配置位的偏移量 */ 
    status = "okay";
  };
  led_green@0x020C4074 {
    compatible = "fire,led_green";
    reg = <0x020C4074 0x00000004
        0x020E01E0 0x00000004
        0x020E046C 0x00000004 
        0x020A8004 0x00000004 
        0x020A8000 0x00000004>;
    pin = <20>;
    clock_offset = <12>;
    status = "okay";
  };
  led_blue@0x020C4074 {
    compatible = "fire,led_blue";
    reg = <0x020C4074 0x00000004
        0x020E01DC 0x00000004
        0x020E0468 0x00000004
        0x020A8004 0x00000004
        0x020A8000 0x00000004>;
    pin = <19>;
    clock_offset = <12>;
    status = "okay";
  };
};  

3.2 驱动文件

/* 编写要点
 1. 完成平台驱动(platform_driver_register())的注册,probe, remove函数的实现
 2. 完成驱动(driver_register())的注册,增加设备节点,实现file_operation结构体的相关函数
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/io.h>
static int major = 0;  // 主设备号
struct class *led_class;
// 获取到的资源存在该结构体内
struct led_resource
{ 
  //rgb_led_red 的设备树节点
  struct device_node *device_node;
  // 存放物理地址转换后的虚拟地址
  u32 *virtual_CCM_CCGR;
  u32 *virtual_IOMUXC_SW_MUX_CTL_PAD;
  u32 *virtual_IOMUXC_SW_PAD_CTL_PAD;
  u32 *virtual_GDIR;
  u32 *virtual_DR;
  int pin;
  int clock_offset;
};
struct led_resource rgb_led_resource[3] = {0};
// 配置引脚
static int gpio_init(int which)
{
  /* 初始化引脚 */
  // 开时钟
  *(rgb_led_resource[which].virtual_CCM_CCGR) |= (3<<(rgb_led_resource[which].clock_offset));
  // 设置为GPIO模式
  *(rgb_led_resource[which].virtual_IOMUXC_SW_MUX_CTL_PAD) &= ~0xf;
  *(rgb_led_resource[which].virtual_IOMUXC_SW_MUX_CTL_PAD) |= 5;
  // 设置引脚属性
  //*(leds[which].va_iomuxc_pad_ctl) = 0x1F838;
  // 设置为输出
  *(rgb_led_resource[which].virtual_GDIR) |= (1<<(rgb_led_resource[which].pin));
  return 0;
}
// 获得资源, 并将物理地址转为虚拟地址
static void pyaddr_to_viaddr(void)
{
  int i = 0;
  int ret = 0;
  u32 regdata[20];
  if (rgb_led_resource[i].device_node == NULL) return;
  for (i=0;i<ARRAY_SIZE(rgb_led_resource);i++) {
    // 获取引脚的位置   ,        时钟配置位在寄存器的偏移量
    ret = of_property_read_u32(rgb_led_resource[i].device_node, "pin", &(rgb_led_resource[i].pin));
    if (ret != 0) {
      printk("%s,%s,%d error: not find pin \n", __FILE__, __FUNCTION__, __LINE__);
      return;
    }
    ret = of_property_read_u32(rgb_led_resource[i].device_node, "clock_offset", &(rgb_led_resource[i].clock_offset));
    if (ret != 0) {
      printk("%s,%s,%d error: not find clock_offset \n", __FILE__, __FUNCTION__, __LINE__);
      return;
    }
    ret = of_property_read_u32_array(rgb_led_resource[i].device_node, "reg", regdata, 10);
    if (ret != 0) {
      printk("%s,%s,%d error: not find regdata \n", __FILE__, __FUNCTION__, __LINE__);
      return;
    }
    rgb_led_resource[i].virtual_CCM_CCGR = ioremap(regdata[0], 4);
    rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD = ioremap(regdata[2], 4);
    rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD = ioremap(regdata[4], 4);
    rgb_led_resource[i].virtual_GDIR = ioremap(regdata[6], 4);
    rgb_led_resource[i].virtual_DR = ioremap(regdata[8], 4);
    //rgb_led_resource[i].virtual_CCM_CCGR = of_iomap(rgb_led_resource[i].device_node, 0);
    //rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(rgb_led_resource[i].device_node, 1);
    //rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(rgb_led_resource[i].device_node, 2);
    //rgb_led_resource[i].virtual_GDIR = of_iomap(rgb_led_resource[i].device_node, 3);
    //rgb_led_resource[i].virtual_DR = of_iomap(rgb_led_resource[i].device_node, 4);
    //printk("======debug ccgr addr: %x=========\n", rgb_led_resource[i].virtual_CCM_CCGR);
    //printk("======debug mux addr: %x=========\n", rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD);
    //printk("======debug pad addr: %x=========\n", rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD);
    //printk("======debug gdir addr: %x=========\n", rgb_led_resource[i].virtual_GDIR);
    //printk("======debug dr addr: %x=========\n", rgb_led_resource[i].virtual_DR);
    //printk("======debug pin: %d=========\n", rgb_led_resource[i].pin);
    //printk("======debug clock_offset: %d=========\n", rgb_led_resource[i].clock_offset);
    printk("%s\n", __FUNCTION__);
  }
}
static int led_drv_open(struct inode *node, struct file *file)
{
  int minor = iminor(node);
  printk("=============%s\n============", __FUNCTION__);
  // 配置gpio模式
  gpio_init(minor);
  printk("===============%s===============\n", __FUNCTION__);
  return 0;
}
static int led_drv_close(struct inode *node, struct file *file)
{
  printk("%s\n", __FUNCTION__);
  return 0;
}
static ssize_t led_drv_read(struct file *file, char __user * buf, size_t size, loff_t *offset)
{
  printk("%s\n", __FUNCTION__);
  return 0;
}
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
  int minor;
  char status;
  struct inode *node = file_inode(file);
  minor = iminor(node);
  copy_from_user(&status, buf, 1);
  if (status) {
    // 开灯
    *(rgb_led_resource[minor].virtual_DR) &= ~(1<<(rgb_led_resource[minor].pin));
  } else {
    *(rgb_led_resource[minor].virtual_DR) |= 1<<(rgb_led_resource[minor].pin);
  }
  printk("%s\n", __FUNCTION__);
  return 0;
}
static struct file_operations led_drv = {
  .owner = THIS_MODULE,  //gcc用法
  .open = led_drv_open,
  .read = led_drv_read,
  .write = led_drv_write,
  .release = led_drv_close,
};
static int rgb_led_probe(struct platform_device *pdev)
{
  int err;
  // 获取资源
  struct device_node *rgb_device_node = NULL;
  // led节点的父节点
  rgb_device_node = of_find_node_by_path("/my_rgb_led");
  if (rgb_device_node == NULL) {
    printk("%s,%s,%d error: not find /my_reg_led\n", __FILE__, __FUNCTION__, __LINE__);
    return -1;
  }
  // red_led资源
  rgb_led_resource[0].device_node = of_find_node_by_name(rgb_device_node, "led_red");
  if (rgb_led_resource[0].device_node == NULL) {
    printk("%s,%s,%d error: not find led_red\n", __FILE__, __FUNCTION__, __LINE__);
    return -1;
  }
  // red_green资源
  rgb_led_resource[1].device_node = of_find_node_by_name(rgb_device_node, "led_green");
  if (rgb_led_resource[1].device_node == NULL) {
    printk("%s,%s,%d error: not find led_green\n", __FILE__, __FUNCTION__, __LINE__);
    return -1;
  }
  // red_blue资源
  rgb_led_resource[2].device_node = of_find_node_by_name(rgb_device_node, "led_blue");
  if (rgb_led_resource[2].device_node == NULL) {
    printk("%s,%s,%d error: not find led_blue\n", __FILE__, __FUNCTION__, __LINE__);
    return -1;
  }
  pyaddr_to_viaddr();  // 地址转换
  // 完成字符设备的注册
  major = register_chrdev(0, "rgb_led", &led_drv);
  // 注册设备类
  led_class = class_create(THIS_MODULE, "hxdled_class");
  err = PTR_ERR(led_class);
  if (IS_ERR(led_class)) {
    unregister_chrdev(major, "rgb_led");
    return -1;
  }
  // 注册设备节点,/dev/rgb_led%
  device_create(led_class, NULL, MKDEV(major, 0), NULL, "led_red");
  device_create(led_class, NULL, MKDEV(major, 1), NULL, "led_green");
  device_create(led_class, NULL, MKDEV(major, 2), NULL, "led_blue");
  printk("=======================%s========================\n", __FUNCTION__);
  return 0;
}
static int rgb_led_remove(struct platform_device *dev)
{
  int i =0;
  // 释放虚拟地址
  for (i=0;i<ARRAY_SIZE(rgb_led_resource);i++) {
    iounmap(rgb_led_resource[i].virtual_CCM_CCGR);
    iounmap(rgb_led_resource[i].virtual_IOMUXC_SW_MUX_CTL_PAD);
    iounmap(rgb_led_resource[i].virtual_IOMUXC_SW_PAD_CTL_PAD);
    iounmap(rgb_led_resource[i].virtual_GDIR);
    iounmap(rgb_led_resource[i].virtual_DR);
  }
  // 释放设备节点
  device_destroy(led_class, MKDEV(major, 0));
  device_destroy(led_class, MKDEV(major, 1));
  device_destroy(led_class, MKDEV(major, 2));
  // 释放设备类
  class_destroy(led_class);
  // 释放注册字符设备
  unregister_chrdev(major, "rgb_led");
  printk("%s\n", __FUNCTION__);
  return 0;
}
static const struct of_device_id rgb_led[] = {
  {.compatible = "fire,my_rgb_led"},
};
static struct platform_driver rgb_led_platform_driver = {
  .probe = rgb_led_probe,
  .remove = rgb_led_remove,
  .driver = {
    .name = "rgb-leds-device-tree",
    .owner = THIS_MODULE,
    //.remove = rgb_led_remove,
    .of_match_table = rgb_led,   // match匹配的时候会用到这个列表里面的compatible和设备树里面的compatible对比
  }
};
// 平台驱动入口函数
static int __init  platform_rgb_led_init(void)
{
  int ret = 0;
  // 完成平台驱动的注册
  ret = platform_driver_register(&rgb_led_platform_driver);
  printk("%s\n", __FUNCTION__);
  return 0;
}
// 平台驱动出口函数
static void __exit platform_rgb_led_exit(void)
{
  // 完成平台驱动的移除
  platform_driver_unregister(&rgb_led_platform_driver);
  printk("%s\n", __FUNCTION__);
  return;
}
module_init(platform_rgb_led_init);
module_exit(platform_rgb_led_exit);
MODULE_LICENSE("GPL");

3.3 应用层

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
int main(int argc, char **argv)
{
  int fd;
  char status;
  if (argc != 3) {
    printf("Usage %s <dev-path> <on/off>\n", argv[0]);
    return -1;
  }
  fd = open(argv[1], O_RDWR);
  if (fd < 0) {
    perror("open error");
    return -1;
  }
  if (strcmp(argv[2], "on") == 0) {
    // 开灯
    status = 1;
    write(fd, &status, 1);
  } else if (strcmp(argv[2], "off") == 0) {
    // 关灯
    status = 0;
    write(fd, &status, 1);
  } else {
    printf("Usage %s <dev-path> <on/off>\n", argv[0]);
    return -1;
  }
  close(fd);
  return 0;
}

3.4 Makefile

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export  ARCH  CROSS_COMPILE
KERN_DIR = /home/hxd/workdir/ebf_linux_kernel_6ull_depth1/build_image/build
all:
  make -C $(KERN_DIR) M=`pwd` modules 
  $(CROSS_COMPILE)gcc -o led_test led_test.c 
clean:
  make -C $(KERN_DIR) M=`pwd` modules clean
  rm -rf modules.order
  rm -f hello_drv_test
obj-m += led_driver.o


目录
相关文章
|
1月前
|
Linux 编译器 开发者
Linux设备树解析:桥接硬件与操作系统的关键架构
在探索Linux的庞大和复杂世界时🌌,我们经常会遇到许多关键概念和工具🛠️,它们使得Linux成为了一个强大和灵活的操作系统💪。其中,"设备树"(Device Tree)是一个不可或缺的部分🌲,尤其是在嵌入式系统🖥️和多平台硬件支持方面🔌。让我们深入了解Linux设备树是什么,它的起源,以及为什么Linux需要它🌳。
Linux设备树解析:桥接硬件与操作系统的关键架构
|
2月前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
33 0
|
2月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
33 1
|
2月前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
39 0
|
6天前
|
Linux
linux驱动层输出dev_dbg打印信息
linux驱动层输出dev_dbg打印信息
11 0
|
15天前
|
存储 监控 Linux
【专栏】在 Linux 中,了解已安装驱动器是系统管理的关键
【4月更文挑战第28天】在 Linux 中,了解已安装驱动器是系统管理的关键。本文介绍了三种方法:1) 使用 `lsblk` 命令显示设备名、大小和类型;2) `fdisk -l` 命令提供详细分区信息;3) `gnome-disks` 等系统管理工具展示驱动器信息。此外,还讨论了驱动器类型识别、挂载点概念及其应用。通过这些方法,用户能有效地监控和管理 Linux 系统中的驱动器。
|
28天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
79 0
|
1月前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
23 0
|
2月前
|
Linux 编译器 测试技术
探索Linux设备树:硬件描述与驱动程序的桥梁
探索Linux设备树:硬件描述与驱动程序的桥梁
93 0
|
2月前
|
Linux
Linux内核中USB设备驱动实现
Linux内核中USB设备驱动实现
30 0