• 【linux驱动开发】-驱动入门之LED


    1.什么是驱动框架

    驱动主要是由两种人去写的,一部分就是驱动开发工程师,一部分内核维护者;

    内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。譬如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。又比如中断号也是一种资源,驱动在使用前也必须去申请。这也是驱动框架的一部分!

    内核中驱动部分的维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同的部分留出来接口给具体的驱动开发工程师来实现,这就叫驱动框架!

    2.内核中驱动框架中LED的源码分析

    LED驱动基本目录与分类

    (1)drivers/leds目录,这个目录就是驱动框架中规定的LED这种硬件的驱动应该在的地方。

    (2)led-class.c和led-core.c,这两个文件加起来属于LED驱动框架的第一部分,这两个文件是内核开发者提供的,他们描述的是内核中所有厂家的不同LED硬件的相同部分的逻辑除了这两部分之外的其他文件几乎都是各个厂家的工程师来写。

    (3)leds-xxx.c,这个文件是LED驱动框架的第2部分,是由不同的厂商驱动工程师编写的添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用第一部分提供的接口和驱动框架进行交互,最终实现驱动的功能。

    驱动行业的工作分支

    (1)内核开发者对驱动框架进行开发和维护、升级,对应着led-class.c和led-core.c

    (2)Soc厂商的驱动工程师对设备驱动源码进行编写、调试、提供参考版本,对应着leds-xxx.c

    (3)做产品的厂商驱动工程师以Soc厂商提供的驱动源码为基础,来做移植和调试!

      其实在实际生活中,我们最有可能从事的就是第2和第3部分的工作,第2就是芯片原厂,第3就是产品商!

    LED的源码分析

    我理解的有无驱动框架的区别

    经过基本分析发现,LED驱动框架中内核开发者实现的部分主要是led-class.c并且我发现led-class.c就是一个内核模块,所以对led-class.c就应该从下往上分析。正是内核开发者希望驱动框架是可以被装载/卸载的,所以才把它实现成了一个模块。这样我们就可以用时取,不用时卸!

    通过这个图片上文字我们可以知道,之前我们在字符的高级篇也是用class_create创建一个类,然后注册字符驱动,那会我们对一个驱动就是直接编写直接注册,现在我们是按一定的框架进行编写注册,此时我们所做的工作更具有框架性,也可以举个简单的例子说明,以前的工作就是“散养的”我们写一个就像内核注册一个,现在就更像“家养”,我们制定了规矩在里面,比如把注册的这部分工作就封装在xxx-class.c中,让一切的工作有迹可循,框架制度化,对内核这么庞大的体系来说,相当于增加了效率!

    分析源码了解内核启动顺序原理

    通过分析led模块源码,通过对其中两个比较重要的宏进行追踪(subsys_initcall、module_exit)可发现,他们两个的作用是一样的,都是将声明的函数放到一个特别地段.initcalln.int!(n=1-8)

    内核在启动过程中需要顺序的做很多事,内核如何实现按照先后顺序去做很多初始化的操作呢?

    解决方案:给内核启动时要调用的所有函数归类,然后每个类按照一定的次序去调用执行,这些类名就叫initcalln.int!(n=1-8)。内核开发者在编写代码时只要将函数设置合适的级别,这些函数就会被连接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可!

    继续分析

    刚才我们分析了led-class.c部分的subsys_initcall、module_exit这两个宏,并且从宏的角度分析了内核启动顺序的一个机制!下面我们继续分析一个函数led_class_attrs!

    上面这两句话创建两个文件,这两个文件是将来存放在/sys/class/leds中,一个文件叫brightness,一个叫max_brightness.这些文件其实就是sysfs开放给应用层的一些操作接口(非常类似与/dev下面的设备文件)!

    attribute的作用其实就是让应用程序可以通过/sys/class/leds/目录下面的属性文件来操作驱动进而操作硬件设备!他其实就是另一条驱动实现的路线,有区别与之前的file_operation!

    3.在内核中添加或去除某个驱动

    去除驱动

    在make menucofig中去掉选择项(比如我们要去掉x210-led,在/sys/devices/platform/x210-led目录)

    用n去掉这个*号,然后保存。重新make编译生成一个ZImage,把ZImage复制到TFTP共享目录中去,然后从开发板启动这个ZImage即可!

    为什么make menucofig就能去掉这个驱动呢?

    原因:根据驱动文件的Makefile语句表明中的宏来配置的。在ubuntu中把CONFIG_X210_LED_DRIVER设置为n之后,x210-led.c文件就不会被编译到内核中。CONFIG_X210_LED_DRIVER是y还是n取决于Kconfig这个选项!(涉及到的图片论证如下三图

    安装驱动框架

    首先我们通过make menuconfig进行配置;

     我们选中y进入这个目录,之后我们可以看到

     然后我们选择y添加到LED Class support这个选项,此时我们已经有leds的类了!然后通过Make编译得到一个新的ZImage重新启动这个ZImage就能得到一个新的leds类!

    分析:因为我们上面装载了leds,所以可以判定led-class.c文件被加载了进来,此文件中subsys_initcall(led_init)装载函数得到了执行,被执行的函数调用了class_create函数,这个函数创建了一个leds类,但是我们通过深入去观察,发现这个类里面是空的,如果想要这个类里面有内容,就需要device_create,这个函数在led_classdev_register函数中!所以此时我们是已经有leds相关的类,要是有实际的灯,就需要用led_classdev_register去注册!当我们使用led驱动框架去编写的驱动的时候,这个led_classdev_register就相当于之前我们使用file_operation方式去注册字符设备驱动的register_chrdev内核函数!

     4.基于驱动框架写LED驱动

            前两节,也就是在字符设备基础和高级部分,我们都是基于内核提供的接口去写的驱动,本节将用驱动框架的方式来写驱动,也就是用led-class.c这里面已经做出来的东西去写!也就是做的厂商驱动工程师的工作!

          以前 我们注册一个驱动,使用的是内核接口,比如register_chrdev,那个时候,这个函数就是重中之重,但是现在我们使用的驱动框架去写,驱动框架在led-class.c已经封装好了,那我们就用它里面的函数去注册,在这里我们就关注led_classdev_register

    可以清晰的看出与之前的区别,之前我们写"散养的"驱动时,我们首先需要注册,这中间就需要调用很多的内核函数,除此之外我们要在sys/class/下面创建类和设备,也需要调用其他的函数,而在驱动框架里,所有的一切都在框架里帮我们完成了,也就是在led-class.c中的led_classdev_register函数!都能够实现,并且程序里使用的 led_classdev结构体,也是驱动框架里提供,我们直接用就可以,他的一些变量,比如name、brightness、brightness_set函数指针都是可以直接调用的!brightness_set是一个函数指针,主要填充的是对硬件的操作

    1. #include // module_init module_exit
    2. #include // __init __exit
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. ****这个驱动是用led框架写的,但是不涉及goiolib管理*/
    10. #define GPJ0CON S5PV210_GPJ0CON
    11. #define GPJ0DAT S5PV210_GPJ0DAT
    12. static struct led_classdev mydev1; // 定义结构体变量 此处的结构体驱动框架中定义的
    13. static struct led_classdev mydev2; // 定义结构体变量
    14. static struct led_classdev mydev3; // 定义结构体变量
    15. // 这个函数就是要去完成具体的硬件读写任务的
    16. static void s5pv210_led1_set(struct led_classdev *led_cdev,
    17. enum led_brightness value)
    18. {
    19. printk(KERN_INFO "s5pv210_led1_set\n");
    20. writel(0x11111111, GPJ0CON);
    21. // 在这里根据用户设置的值来操作硬件
    22. // 用户设置的值就是value
    23. if (value == LED_OFF)
    24. {
    25. // 用户给了个0,希望LED灭
    26. //writel(0x11111111, GPJ0CON);
    27. // 读改写三部曲
    28. writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
    29. }
    30. else
    31. {
    32. // 用户给的是非0,希望LED亮
    33. //writel(0x11111111, GPJ0CON);
    34. writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);
    35. }
    36. }
    37. static void s5pv210_led2_set(struct led_classdev *led_cdev,
    38. enum led_brightness value)
    39. {
    40. printk(KERN_INFO "s5pv2102_led_set\n");
    41. writel(0x11111111, GPJ0CON);
    42. // 在这里根据用户设置的值来操作硬件
    43. // 用户设置的值就是value
    44. if (value == LED_OFF)
    45. {
    46. // 用户给了个0,希望LED灭
    47. //writel(0x11111111, GPJ0CON);
    48. // 读改写三部曲
    49. writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
    50. }
    51. else
    52. {
    53. // 用户给的是非0,希望LED亮
    54. //writel(0x11111111, GPJ0CON);
    55. writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
    56. }
    57. }
    58. static void s5pv210_led3_set(struct led_classdev *led_cdev,
    59. enum led_brightness value)
    60. {
    61. printk(KERN_INFO "s5pv210_led3_set\n");
    62. writel(0x11111111, GPJ0CON);
    63. // 在这里根据用户设置的值来操作硬件
    64. // 用户设置的值就是value
    65. if (value == LED_OFF)
    66. {
    67. // 用户给了个0,希望LED灭
    68. //writel(0x11111111, GPJ0CON);
    69. // 读改写三部曲
    70. writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);
    71. }
    72. else
    73. {
    74. // 用户给的是非0,希望LED亮
    75. //writel(0x11111111, GPJ0CON);
    76. writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);
    77. }
    78. }
    79. static int __init s5pv210_led_init(void)
    80. {
    81. // 用户insmod安装驱动模块时会调用该函数
    82. // 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
    83. int ret = -1;
    84. // led1
    85. mydev1.name = "led1";
    86. mydev1.brightness = 255;
    87. mydev1.brightness_set = s5pv210_led1_set;
    88. ret = led_classdev_register(NULL, &mydev1);
    89. if (ret < 0) {
    90. printk(KERN_ERR "led_classdev_register failed\n");
    91. return ret;
    92. }
    93. // led2
    94. mydev2.name = "led2";
    95. mydev2.brightness = 255;
    96. mydev2.brightness_set = s5pv210_led2_set;
    97. ret = led_classdev_register(NULL, &mydev2);
    98. if (ret < 0) {
    99. printk(KERN_ERR "led_classdev_register failed\n");
    100. return ret;
    101. }
    102. // led3
    103. mydev3.name = "led3";
    104. mydev3.brightness = 255;
    105. mydev3.brightness_set = s5pv210_led3_set;
    106. ret = led_classdev_register(NULL, &mydev3);
    107. if (ret < 0) {
    108. printk(KERN_ERR "led_classdev_register failed\n");
    109. return ret;
    110. }
    111. return 0;
    112. }
    113. static void __exit s5pv210_led_exit(void)
    114. {
    115. led_classdev_unregister(&mydev1);
    116. led_classdev_unregister(&mydev2);
    117. led_classdev_unregister(&mydev3);
    118. }
    119. module_init(s5pv210_led_init);
    120. module_exit(s5pv210_led_exit);
    121. // MODULE_xxx这种宏作用是用来添加模块描述信息
    122. MODULE_LICENSE("GPL"); // 描述模块的许可证
    123. MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模块的作者
    124. MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
    125. MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息

  • 相关阅读:
    HDU - 2955 - Robberies
    学习记录682@查准率与查全率真的必然负相关吗?
    1 线程池-手写线程池
    vue3移动端项目构建,vue3+vant+vite+axios+pinia+sass
    线性表-----栈(栈的初始化、建立、入栈、出栈、遍历、清空等操作)
    使用koa搭建服务器(一)
    java多线程面试相关的一些问题
    solidworks底部状态栏显示不出来
    OLED透明拼接屏的完美融合,唐山的历史遗迹与现代科技
    vue-cli-service build 不同环境的配置
  • 原文地址:https://blog.csdn.net/weixin_49176627/article/details/126414575