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


目录
相关文章
|
3月前
|
Linux SoC
Linux设备树(DTS)
Dts:DTS即Device Tree Source,是一个文本形式的文件,用于描述硬件信息。一般都是固定信息,无法变更,无法overlay。 设备树由来 linux内核源码中,之前充斥着大量的平台相关(platform Device)配置,而这些代码大多是杂乱且重复的,这使得ARM体系结构的代码维护者和内核维护者在发布一个新的版本的时候有大量的工作要做,以至于LinusTorvalds 在2011年3月17日的ARM Linux邮件列表中宣称“Gaah.Guys,this whole ARM thing is a f*cking pain in the ass”这使得整个ARM社区不得不
Linux设备树(DTS)
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
47 5
|
3月前
|
Ubuntu NoSQL Linux
Linux内核和驱动
Linux内核和驱动
26 2
|
3月前
|
数据采集 Linux
Linux源码阅读笔记20-PCI设备驱动详解
Linux源码阅读笔记20-PCI设备驱动详解
|
4月前
|
存储 JSON Linux
|
2月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
3月前
|
存储 Unix Linux
揭秘Linux硬件组成:从内核魔法到设备树桥梁,打造你的超级系统,让你的Linux之旅畅通无阻,震撼体验来袭!
【8月更文挑战第5天】Linux作为顶级开源操作系统,凭借其强大的功能和灵活的架构,在众多领域大放异彩。本文首先概述了Linux的四大核心组件:内核、Shell、文件系统及应用程序,并深入探讨了内核的功能模块,如存储、CPU及进程管理等。接着介绍了设备树(Device Tree),它是连接硬件与内核的桥梁,通过DTS/DTB文件描述硬件信息,实现了跨平台兼容。此外,还简要介绍了Linux如何通过本地总线高效管理硬件资源,并阐述了文件系统与磁盘管理机制。通过这些内容,读者可以全面了解Linux的硬件组成及其核心技术。
52 3
|
3月前
|
Linux
【linux】【驱动】<specifier>-map-pass-thru讲解
【linux】【驱动】<specifier>-map-pass-thru讲解
20 0
|
3月前
|
Linux
【linux】【驱动】phy接口类型
【linux】【驱动】phy接口类型
19 0