第九章 理一理驱动程序的编写

              很想把这些天跟着老师学习的内容再重新整理出来,并不按照讲课的顺序,而是按照逐次使用的顺序写出来,如上图!在此...


        attachments-2020-05-knjMsWCs5ec0cf8b72965.png

  很想把这些天跟着老师学习的内容再重新整理出来,并不按照讲课的顺序,而是按照逐次使用的顺序写出来,如上图!在此期间问了自己14个问题:

1什么是pinctrl子系统?

2什么是gpio子系统?

3gpio和pinctrl子系统怎么编写?

4gpio和pinctrl子系统怎么转化到内核中?

5platform_driver_register是怎么进行驱动注册的?

6platform_driver如何匹配到platform_device?

7_probe函数的形参是谁给的?

8gpiod_get函数是怎么找到设备树中的led节点?

9register_chrdev函数是怎么把100ask_led注册到内核中的?

10为什么要创建设备类class?class_create是怎么在内核中创建的class?

11用register_chrdev要创建主设备,device_create创建次设备,怎么创建的?

12gpiod_direction_output是怎么实现gpio初始化的?

13gpiod_set_value是怎么实现设置的gpio的?

14假如有2个led1/2呢?怎么去区分led0和led1的?

   ————————————————————————————————————————————————————————————————

9.1.驱动程序进化之路

    总结起来老师用了3种方法来写led驱动,传统法、基于platform法和设备树法,下图是进化之路,它们的核心都是分配、设置、注册 file_operations结构体,以及对应的open、read、write和close函数,根据方法的不同函数的内容也有所不同,主要是open和write的不同。

attachments-2020-05-If4OmOjg5ec0d02c9ba38.png

    3种方法的主要区别是硬件资源的获取和配置,小结如下表,但是感觉太简单了。

   

1.1传统方法

    传统方法,简单粗暴,不分层级结构,直接在驱动程序中的init和ctl函数中操作寄存器,缺点也很明显,更换硬件类型,需重新编写,例如同样是使能某个gpio的时钟,imx6ull和rk3399是不一样的。

看下传统方法的主要架构:(原图发上来太大,智能模糊处理了)

attachments-2020-05-6U8AHqJM5ec0d80ed4ab7.png

 为此大牛们想出了分层和分离的思想,具体是哪些大牛就不知道了,分层的意思是将驱动程序分成2个层级,上层用来完成注册file_operations等工作,不涉及硬件操作;下层实现硬件操作,不涉及内核注册等工作。分离呢,就将下层根据每种单板实现分别控制(即多个.c文件)。另外,上下层之间需要一个中间文件来连接,承上启下,chip_demo_gpio.c,向上接收驱动函数的调用指令,向下操作硬件。多个单板,用一个连接文件调用,就必须统一接口,也就用到了面向对象的思想,特意去搜了下面向对象的定义,引用:

    “面向过程——步骤化,面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可。

    面向对象——行为化,面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成对象,创建了对象不是为了完成某一个步骤,而是描述某个事物在解决问题的步骤中的行为”

    有点抽象,总之就是把各个单板的和led相关的gpio资源抽象成一个led_resource结构体,例如:

static struct led_resource board_A_led = {
.pin = GROUP_PIN(3,1),
};

然后在chip_demo_gpio.c中获取这个资源。

    好像也没省多少代码,只是把各个单板独立开来。

1.2 platform法

    这里参考了正点原子的《I.MX6U嵌入式Linux驱动开发指南V1.2》第54章内容,(应该不算侵权吧)讲到多个SOC平台都有MPU6050 这个 I2C 接口的六轴传感器,那就可以只写一个驱动程序,多个SOC平台可以共同使用这个驱动程序,如下图,这样就大大简化了内核的代码量。

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZlbmRvdWxhbnlhbg==,size_16,color_FFFFFF,t_70

    像I2C、SPI和USB等都有自己的总线,有些外设是没有总线,led应该没有吧,但还是想用这套模型该怎么办?Linux提出了platform这个虚拟总线,对应的设备为platform_device,对应的驱动为platform_driver,共同构成Linux 总线、驱动和设备模式,如下图:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZlbmRvdWxhbnlhbg==,size_16,color_FFFFFF,t_70

“当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。”

    回到led驱动程序,前面一小节的哪些部分是platform_device?哪些是platform_driver?led的操作应该是driver,led的相关gpio资源应该是device,看下代码。

/* 定义led引脚资源 */
static struct resource resources[] = {
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ,
                .name = "",
        },
        {
                .start = GROUP_PIN(5,8),
                .flags = IORESOURCE_IRQ,
                .name = "",
        },
};
/* 定义名为100ask_led的platform_device */
static struct platform_device board_A_led_dev = {
        .name = "",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};

    代码中定义了platform_device结构体board_A_led_dev,其中有name、num_resources、resource和dev.release成员。再看下platform_driver的代码,定义了platform_driver结构体chip_demo_gpio_driver,成员如下,这里通过“100ask_led”这个name字符串来匹配device和driver,具体的匹配过程以及driver中probe()的执行过程后续再说。

/* 定义名为100ask_led的platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "",
    },
};

    各个.c和.h之间的关系如下图,图片压缩之后有点看不清了。

attachments-2020-05-P8HrZK9W5ec0d9084f38f.png    其实发现好像没有简化多少代码,还是蛮复杂的,设备越多,驱动越多,.c和.h就越多,Linux 之父 linus不愿意了,Linux开始引入设备树!

1.3设备树法

    还是参考了正点原子的开发指南,第43章,设备树,Device Tree,描述文件,DTS,Device Tree Source,示意图:

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZlbmRvdWxhbnlhbg==,size_16,color_FFFFFF,t_70

    DTS 是设备树源码文件,DTB是将DTS编译以后得到的二进制文件。

    还是回到led驱动,也就是将各个单板的gpio资源写到.dts文件中,而不是用.c和.h,看下100ask_led.dts:

#define GROUP_PIN(g,p) ((g<<16) | (p))
/ {
	100ask_led@0 {
		compatible = "";
		pin = <GROUP_PIN(3, 1)>;
	};
 
	100ask_led@1 {
		compatible = "";
		pin = <GROUP_PIN(5, 8)>;
	};
};

    使用设备树的整个文件框架,如下图,关于driver是怎么来调用的设备树信息,后面再说。

attachments-2020-05-QAharaCF5ec0d976e5aeb.png

    有种大功告成的感觉,但是再去一看发现init()和ctl()还是存在啊,还是得去配置寄存器操作led啊,和传统方法没啥区别啊,折腾半天就是把led相关的gpio资源抽象出来由.c定义变成了设备树定义。有什么办法可以不去操作寄存器?有的,Linux的pinctrl和gpio子系统。

    还是参考正点原子的开发指南,比较详细,韦老师的讲课文档属于浓缩,总结下,pinctrl子系统主要用来设置pin的复用功能以及电气特性,gpio子系统为pinctrl和driver提供“交流”的接口函数。驱动程序中不用专门去操作寄存器了,大大简化了代码,简化的框架如下图:

attachments-2020-05-G5WQD5JG5ec0da285d03a.png

  到这里的时候自己问了自己14个问题,趁着清明假期把韦老师18年将的设备树专题视频过来一遍,很多东西又清晰了,还是忍不住想写写,不想放弃,但是会花很多时间。

  下面就针对前面提出的问题写出自己的理解吧。

9.2.什么是pinctrl子系统?

……

1 条评论&回复

请先 登录 后评论
zxq
zxq

11 篇文章

作家榜 »

  1. 百问网-周老师 18 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. 星星之火 6 文章
  7. 谢工 5 文章
  8. Litchi_Zheng 5 文章