本章节记录实现LED驱动的大概步骤,且编程框架实现分离分层。
分离分层:

  • 上层:系统 相关。如模块注册于注销。

  • 下层:硬件操作。如提供 file_operations 。分离

    • 设备。提供板卡信息,如使用哪一个引脚。
    • 驱动。引脚的具体操作。
  • 以下以 LED 为例。

步骤

  • 模块

    • 入口函数
    • 出口函数
    • 协议
  • 驱动

    • 驱动代码:实现 file_operations
    • 申请设备号
    • 初始化内核设备文件结构体+绑定驱动代码 file_operations
    • 添加内核设备文件结构体到内核+绑定设备号
    • 创建设备类
    • 创建设备节点+绑定设备号
  • 具体实现参考《linux-驱动-3-字符设备驱动》或往下看。

把一个字符设备驱动工程分层分离。(看章前分析
得出以下目录树:

  1. dev_drv
  2. |__ xxx_module.c
  3. |__ include
  4. | |__ xxx_resource.h
  5. |__ device
  6. | |__ xxx_dev_a.c
  7. | |__ xxx_dev_a.h
  8. |__ driver
  9. |__ xxx_drv.c
  10. |__ xxx_drv.h

目录树分析:

  • dev_drv:字符设备模块目录

    • xxx_module.c:上层。系统。用于注册、注销模块,及操作驱动与内核的联系部分。
    • include:系统、设备、驱动共用的自定义头文件。

      • xxx_resource.h:资源文件。包含了设备资源传给驱动文件的结构体。
    • device:设备目录。硬件设备内容,提供给驱动文件使用,即是提供资源。

      • xxx_dev_a.c:板卡a。以规定的格式提供硬件资源。
      • xxx_dev_a.h:板卡a。头文件。
    • driver:驱动目录。实现驱动 file_operations 的目录。

      • xxx_drv.c:驱动。实现驱动内容。
      • xxx_drv.c:驱动。头文件。

主要内容:

  • 提供设备资源;
  • 提供获取设备资源接口。

现在设备资源格式文件中第一好格式:

  • 设备资源:(led_resource.h)
  1. /* led 资源结构体 */
  2. struct LED_RESOURCE_T
  3. {
  4.     unsigned long pa_dr; // 数据寄存器  物理地址
  5.     unsigned long pa_gdir; // 输入输出寄存器  物理地址
  6.     unsigned long pa_iomuxc_mux; // 端口复用寄存器  物理地址
  7.     unsigned long pa_ccm_ccgrx; // 端口时钟寄存器  物理地址
  8.     unsigned long pa_iomux_pad; // 电气属性寄存器  物理地址
  9.     unsigned int pin; // 引脚号
  10.     unsigned int clock_offset; // 时钟偏移
  11. };
  12. typedef struct LED_RESOURCE_T led_resource_t;
  • 获取设备资源接口:
  1. /** @brief  get_led_resource 获取资源句柄
  2.   * @param  led 参数
  3.   * @retval 
  4.   * @author lzm
  5.   */
  6. led_resource_t *get_led_resource(char ch)
  7. {
  8.     if(ch == 'R' || ch == 'r' || ch == '0')
  9.         return &led_r;
  10.     else if(ch == 'G' || ch == 'g' || ch == '1')
  11.         return &led_g;
  12.     else if(ch == 'B' || ch == 'b' || ch == '2')
  13.         return &led_b;
  14.     
  15.     return 0;
  16. }

实现驱动内容:

  • file_operations

  • 使用设备数组模式,实现统一管理,且达到时间复杂度为 O(1) 的性能。

  • file_operations

    • int led_dev_open(struct inode *inode, struct file *filp):打开设备节点。
    • int led_dev_release(struct inode *inode, struct file *filp):关闭设备节点。
    • ssize_t led_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos):写函数。
    • ssize_t led_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos):读函数。
  • 设备数组:

    • static led_dev_t led_dev_elem[LED_DEV_CNT];:led 设备列表。使用 id 作为下标去定位哪一个设备。
    • 设备结构体:
  1. /* my led device struct */
  2. typedef void (*led_init_f)(unsigned char);
  3. typedef void (*led_exit_f)(unsigned char);
  4. typedef void (*led_ctrl_f)(unsigned char, unsigned char);
  5. struct LED_DEV_T
  6. {
  7.     /* 设备 ID 次设备号-1 因为次设备号0一般代表所有设备*/
  8.     unsigned char id;
  9.     /* 设备名称 */
  10.     char name[10]; // 限定十个字符
  11.     /* 设备参数 */
  12.     dev_t dev_num; // 设备号
  13.     struct cdev dev; // 内核设备文件 结构体
  14.     /* led 状态 */
  15.     unsigned char status; // 0: 关闭; 1:开
  16.     /* 引脚参数 */
  17.     led_pin_t *pin_data;
  18.     /* 设备函数 */
  19.     led_init_f init; // 初始化函数
  20.     led_exit_f exit; // 出口函数
  21.     led_ctrl_f ctrl; // 控制函数
  22. };
  23. typedef struct LED_DEV_T led_dev_t; 

万事俱备,只欠东风。
下层硬件的资源和驱动函数都准备好了,现在只需要实现模块即可。
主要三个点:

  • static int __init led_chrdev_init(void):入口函数。(module_init(led_chrdev_init)
  • static void __exit led_chrdev_exit(void):出口函数。(module_exit(led_chrdev_exit)
  • MODULE_LICENSE("GPL"):协议。

以上两个函数的内容可以参考字符设备驱动实现步骤来实现。
除了以上三个函数外,还有把驱动内容填入驱动文件中 file_operations 实体。

给出 led_module.c 文件参考:

  1. /** @file         led_module.c
  2.  *  @brief        驱动。
  3.  *  @details      led 模块文件。
  4.  *  @author       lzm
  5.  *  @date         2021-03-06 10:23:03
  6.  *  @version      v1.0
  7.  *  @copyright    Copyright By lizhuming, All Rights Reserved
  8.  *
  9.  **********************************************************
  10.  *  @LOG 修改日志:
  11.  **********************************************************
  12. */
  13. /* 系统库 */
  14. #include <linux/init.h>
  15. #include <linux/module.h>
  16. #include <linux/cdev.h>
  17. #include <linux/fs.h>
  18. #include <linux/uaccess.h>
  19. #include <linux/io.h>
  20. /* 私人库 */
  21. #include "led_resource.h"
  22. #include "led_drv.h"
  23. /* 变量 */
  24. dev_t led_dev_num_start; // 开始设备号
  25. static struct cdev led_cdev[LED_DEV_CNT+1]; // 全设备+LED_DEV_CNT个子设备
  26. struct class *led_dev_class; // 设备类 (*用于设备节点*)
  27. /* [drv][file_operations] */
  28. static struct file_operations led_dev_fops = 
  29. {
  30.     .owner = THIS_MODULE,
  31.     .open = led_dev_open,
  32.     .release = led_dev_release,
  33.     .write = led_dev_write,
  34.     .read = led_dev_read,
  35. };
  36. /* [module][1] */
  37. /** @brief   led_chrdev_init
  38.   * @details led module 入口函数
  39.   * @param  
  40.   * @retval 
  41.   * @author lzm
  42.   */
  43. static int __init led_chrdev_init(void)
  44. {
  45.     unsigned char i;
  46.     printk("chrdev_init\n");
  47.     /* my 设备文件初始化 */
  48.     led_dev_init();
  49.     /* [module][1][1] 申请设备号 */
  50.     alloc_chrdev_region(&led_dev_num_start, 0, LED_DEV_CNT+1, LED_DEV_NAME);
  51.     /* [module][1][2] 创建设备节点 */
  52.     led_dev_class = class_create(THIS_MODULE, LED_DEV_CLASS);    
  53.     for(i=0; i<LED_DEV_CNT+1; i++)
  54.     {
  55.         /* [module][1][3] 初始化内核设备文件 */
  56.         cdev_init(&led_cdev[i], &led_dev_fops); // 把驱动程序初始化到内核设备文件中
  57.         /* [module][1][4] 把内核设备文件注册到内核 */
  58.         cdev_add(&led_cdev[i], MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i), 1); // 内核设备文件绑定设备号,并注册到内核
  59.         /* [module][1][5] 创建设备节点 */
  60.         if(!i)
  61.             device_create(led_dev_class, NULL, MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i), NULL, LED_DEV_NAME); // 总设备
  62.         else
  63.             device_create(led_dev_class, NULL, MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i), NULL, LED_DEV_NAME"_%d",i);
  64.     }
  65.     return 0;
  66. }
  67. module_init(led_chrdev_init);
  68. /* [module][2] */
  69. /** @brief   led_chrdev_exit
  70.   * @details led module 出口函数
  71.   * @param  
  72.   * @retval 
  73.   * @author lzm
  74.   */
  75. static void __exit led_chrdev_exit(void)
  76. {
  77.     unsigned char i;
  78.     printk("chrdev_exit!\n");
  79.     for(i=0; i<LED_DEV_CNT+1; i++)
  80.     {
  81.         /* [module][2][1] 删除设备节点 */
  82.         device_destroy(led_dev_class, MKDEV(MAJOR(led_dev_num_start), MINOR(led_dev_num_start)+i));
  83.         /* [module][2][2] 注销设备文件 */
  84.         cdev_del(&led_cdev[i]);
  85.     }
  86.     /* [module][2][3] 归还设备号 */
  87.     unregister_chrdev_region(led_dev_num_start, LED_DEV_CNT+1);
  88.     /* [module][2][4] 删除设备类 */
  89.     class_destroy(led_dev_class);
  90.     return;
  91. }
  92. module_exit(led_chrdev_exit);
  93. /* [module][3] 协议 */
  94. MODULE_AUTHOR("lizhuming");
  95. MODULE_LICENSE("GPL");

参考 《一个通用驱动Makefile-V2-支持编译多目录》

以下只给出源码:

  1. # @file         Makefile
  2. # @brief        驱动。
  3. # @details      led 驱动模块 Makefile 例程。
  4. # @author       lzm
  5. # @date         2021-03-14 10:23:03
  6. # @version      v1.1
  7. # @copyright    Copyright By lizhuming, All Rights Reserved
  8. #
  9. # ********************************************************
  10. # @LOG 修改日志:
  11. # ********************************************************
  12. # 编译后内核路径
  13. KERNEL_DIR = /home/lss/work/kernel/imx6/ebf-buster-linux/build_image/build
  14. # 定义框架
  15. # ARCH 为 x86 时,编译链头为 
  16. # ARCH 为 arm 时,编译链头为 arm-linux-gnueabihf-
  17. ARCH = arm
  18. ifeq ($(ARCH),x86)
  19. CROSS_COMPILE = # 交叉编译工具头,如:
  20. else
  21. CROSS_COMPILE = arm-linux-gnueabihf-# 交叉编译工具头,如:arm-linux-gnueabihf-
  22. endif
  23. CC      = $(CROSS_COMPILE)gcc # 编译器,对 C 源文件进行编译处理,生成汇编文件
  24. # 共享到sub-Makefile
  25. export  ARCH  CROSS_COMPILE
  26. # 路径
  27. PWD := $(shell pwd)
  28. # 当前模块路径
  29. # $(src) 是内和文件定义并传过来的当前模块 M= 的路径。
  30. MODDIR := $(src)
  31. # 注意:驱动目标不要和文件名相同
  32. TARGET_DRV := led_device_driver
  33. TARGET_APP := led_app
  34. # 本次整个编译需要源 文件 和 目录
  35. # 这里的“obj-m” 表示该模块不会编译到zImage ,但会生成一个独立的xxx.ko 静态编译(由内核源码顶层Makefile识别)
  36. # 模块的多文件编译:obj-m 是告诉 makefile 最总的编译目标。而 $(TARGET)-y 则是告诉 makefile 该总目标依赖哪些目标文件。(也可以使用 xxx-objs)
  37. $(TARGET_DRV)-+= led_module.o
  38. $(TARGET_DRV)-+= ./device/led_dev_a.o
  39. $(TARGET_DRV)-+= ./driver/led_drv.o
  40. obj-:= $(TARGET_DRV).o
  41. # obj-m += $(patsubst %.c,%.o,$(shell ls *.c))
  42. # 编译条件处理
  43. # 指定头文件 由于该文件是 -C 后再被调用的,所以部分参数不能使用 $(shell pwd)
  44. # $(src) 是内和文件定义并传过来的当前模块 M= 的路径。
  45. ccflags-:= -I$(MODDIR)/include
  46. ccflags-+= -I$(MODDIR)/device
  47. ccflags-+= -I$(MODDIR)/driver
  48. # 第一个目标 CURDIR 是该makefile内嵌变量,自动设置为当前目录
  49. all :
  50.     @$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR)  modules
  51. #   make mobailes 就是是编译模块,上面是其添加参数的指令
  52. #   $(CROSS_COMPILE)gcc -o $(TARGET_APP) $(TARGET_APP).c
  53.     
  54. # 清理
  55. .PHONY:clean
  56. clean:
  57.     $(MAKE)  -C $(KERNEL_DIR) M=$(CURDIR) clean
  58. #   rm $(TARGET_APP)

版权声明:本文为lizhuming原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/lizhuming/p/14593464.html