驱动实现——驱动模型

142 阅读5分钟

4月「掘金·日新计划」第21天

2.3、平台总线(驱动模型)

image.png

  1. 平台总线的设计是基于设备模型的
  2. 实现device(中断/地址)和driver(操作逻辑)分离
  3. 实现一个driver代码能够驱动多个平台相似的模块

2.3.1、平台总线工作流程

  1. bus中注册platform
  2. 提供platform_deviceplatform_driver结构体
  3. platform的match函数匹配设备和驱动 匹配成功,driver的probe函数初始化驱动和安装
  4. platform的注册是通过下面的注册安装函数完成
  5. platdata设备注册时有关数据,如gpio等 这些数据在match后由设备提供给驱动 驱动是没有数据的,算法数据分离 保持驱动的独立和适应性

2.3.2、平台总线三要素

设备,驱动分离 算法,数据分离 为了设备驱动相遇probe执行

  1. bus总线对象

    1. 不需要自己创建,开机的时候自动创建。结构体对象

      struct bus_type platform_bus_type = {
          .name        = "platform",
          .dev_groups    = platform_dev_groups,
          .match        = platform_match,
          .uevent        = platform_uevent,
          .pm        = &platform_dev_pm_ops,
      };
      
    2. linux内核自动匹配设备驱动,匹配规则

      1. 优先匹配pdriver中的id_table,里面包含了支持不同的平台的名字
      2. 直接匹配driver中名字和device中名字
  2. device设备对象

    1. 为驱动提供硬件信息,描述性内容。结构体对象

      struct platform_device {
          const char  *name;  //用于做匹配
          int     id;  // 一般都是直接给-1
          struct device   dev; // 继承了device父类
          u32     num_resources; // 资源的个数
          struct resource *resource; // 资源:包括了一个设备的地址和中断
      }
      
    2. 注册和注销

      int  platform_device_register(struct platform_device * pdev)void  platform_device_unregister(struct platform_device * pdev);
      
  3. driver驱动对象

    1. 实现功能接口,动作性描述。结构体对象

      struct platform_driver {
              int (*probe)(struct platform_device *); //匹配成功之后被调用的函数
              int (*remove)(struct platform_device *);//device移除的时候调用的函数
              struct device_driver driver; //继承了driver父类
              const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来
      }
      
    2. 注册和注销

      int platform_driver_register(struct platform_driver *drv);
      void platform_driver_unregister(struct platform_driver *drv)
      

2.3.3、使用(input子系统)

2.3.3.1、概念
  1. platform 驱动框架分为总线、设备和驱动

    1. 总线在Linux内核提供,不需要我们进行考虑,所以只需考虑设备和驱动。
    2. 因为目前的Linux内核都支持设备树了,所以platform_device设备信息都通过设备树进行描述了,因此只需要在代码中编写platform_driver驱动代码(我实现的还是老方法,设备驱动编写)
  2. 应用层 + input event + input core + 硬件驱动

    1. 当按下鼠标,硬件中断执行判断是什么设备
    2. 然后上报core(负责封装为input_event结构体)
    3. 应用层解析事件结构体处理
  3. 结构体struct input_event,事件

    1. time时间

    2. type驱动类型,例如鼠标

      1. type EV_SYN 同步类型,一般3个数据包,最后一个就是0,0,0 EV_KEY 按键 EV_REL 相对的,鼠标移动 EV_ABS 绝对的,触摸屏 EV_MSC 键盘
    3. code鼠标左键

      1. 哪一个按键
    4. value按下了

      1. 非0按下
2.3.3.2、代码流程分析
  1. 应用层操作驱动有2条路,/dev设备文件,和属性文件/sys,input使用的是/dev

  2. 核心层

    1. input.c 主要创建类,注册主设备驱动(提供了第一种驱动的结构体file_operations)
    2. class_register 创建类
    3. input_proc_init /proc/bus/input
    4. register_chrdev 注册设备驱动,主设备号
  3. 设备驱动层

    1. drivers/input/xxx
    2. input_allocate_device,申请设备内存
    3. input_set_capability,输入设备的能力set_bit
    4. input_register_device,注册
  4. 事件驱动层

    1. evdev.c

      input_report_key,上报应用层

    2. input_register_handler

    3. input_register_handle

    4. 设备和handler匹配 成功注册驱动,生成文件/dev/input/xxx

2.3.3.3、使用input
  1. 应用层

    1. 打开文件
    2. 读取结构体变量,解析结构体变量
  2. 驱动

    1. 定时器轮询

    2. platform和input platform设备驱动注册,相遇调用probe

    3. probe

      1. 申请,设置上拉,输入模式gpio

      2. input_dev结构体指针 动态分配内存input_allocate_device 填充结构体set_bit 注册设备input_register_device

      3. struct timer_list结构体 内核定时器初始化init_time 设置绑定函数 .function 时间 .expires 启动定时器 add_time del_timer删除定时器

        1. 定时器处理函数time_handler 读取gpio值gpio_get_value 判断值和上次是否一样if 上报应用层input_report_key 更新定时器mod_timer
  3. 验证使用

    1. /dev/input/下多出event0 /sys/bus/platform/devices出现设备fire /sys/bus/platform/drivers出现驱动fire

    2. 数据+心跳包

    3. 驱动

      #include <linux/input.h> 
      #include <linux/module.h> 
      #include <linux/init.h>
      #include <asm/irq.h> 
      #include <asm/io.h>
      ​
      #include <linux/gpio.h>
      #include <linux/platform_device.h>
      ​
      #define GPIO_FIRE   17  //io口号,可以使用命令gpio read
      static struct input_dev *input; //指针,后面动态分配内存
      static struct timer_list timer; //定时器结构体
      static int history;     //记录上次io值//定时器处理函数
      static void fire_timer_handler(unsigned long data) 
      { 
          int flag;
          flag = gpio_get_value(GPIO_FIRE);
          if(flag != history){    //和上次值比较
              if(flag){
                  input_report_key(input, KEY_OK, 1); //上报应用层
              }else{
                  input_report_key(input, KEY_OK, 0);
              }
              history = flag;
          }
          input_sync(input);  //同步包mod_timer(&timer, jiffies + HZ/100);    //更新定时器
      }
      ​
      //匹配成功注册设备
      static int fire_button_probe(struct platform_device *pdev)
      {
          
          int error;
          
          error = gpio_request(GPIO_FIRE, "GPIO_0_FIRE"); //申请io
          if(error){
              printk("fire.c: request gpio GPIO_0 fail");
          }   
          gpio_direction_input(GPIO_FIRE);    //输入模式
          history = gpio_get_value(GPIO_FIRE);
              
          input = input_allocate_device();    //输入设备结构体,实例化
          if (!input) 
          { 
              printk(KERN_ERR "fire.c: Not enough memory\n");
              error = -ENOMEM;
              goto err_free_mem;
          }
      ​
          //填充结构体
          set_bit(EV_KEY, input->evbit);  //按键类型
          set_bit(KEY_OK, input->keybit); //哪一个按键
          //注册
          error = input_register_device(input);
          if (error) 
          { 
              printk(KERN_ERR "fire.c: Failed to register device\n");
              goto err_free_device;
          }
          
          //定时器
          init_timer(&timer);
          timer.function = fire_timer_handler;    //处理函数
          timer.expires = jiffies + (HZ/100); //定时时间
          add_timer(&timer);      启动
          return 0;
      //倒影式处理错误
      err_free_device:
          input_free_device(input);
      err_free_mem:
          gpio_free(GPIO_FIRE);
          return error;
      }
      ​
      //注销
      static int fire_button_remove(struct platform_device *dev)
      {
          del_timer(&timer);
          input_unregister_device(input); 
          gpio_free(GPIO_FIRE);   
          return 0;
      }
      ​
      //设备
      static struct platform_device fire_device_button = {
          .name       = "fire",
          .id = -1,
      };
      ​
      //驱动
      static struct platform_driver fire_button_device_driver = {
          .probe = fire_button_probe,
          .remove     = fire_button_remove,
          .driver     = {
              .name       = "fire",
              .owner      = THIS_MODULE,
          },
      };
      ​
      //平台总线
      static int __init button_init(void) 
      {
          platform_device_register(&fire_device_button);
          return platform_driver_register(&fire_button_device_driver);
      }
      static void __exit button_exit(void) 
      { 
          platform_driver_unregister(&fire_button_device_driver);
          platform_device_unregister(&fire_device_button);
      }
      ​
      module_init(button_init); 
      module_exit(button_exit); 
      ​
      MODULE_LICENSE("GPL");
      MODULE_AUTHOR("xiaowei");
      MODULE_DESCRIPTION("fire");
      
    4. 应用层

      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
      #include <linux/input.h>
      #include <string.h>#define DEVICE_KEY          "/dev/input/event1"
      #define DEVICE_MOUSE        "/dev/input/event3"
      #define X210_KEY            "/dev/input/event1"
      int main(void)
      {
          int fd = -1, ret = -1;
          struct input_event ev;
          
          // 打开驱动文件
          fd = open(X210_KEY, O_RDONLY);
          if (fd < 0)
          {
              perror("open");
              return -1;
          }
          
          while (1)
          {
              // 初始化事件结构体
              memset(&ev, 0, sizeof(struct input_event));
              ret = read(fd, &ev, sizeof(struct input_event));
              if (ret != sizeof(struct input_event))
              {
                  perror("read");
                  close(fd);
                  return -1;
              }
              
              // 输出解析
              printf("-------------------------\n");
              printf("type: %hd\n", ev.type); //按键类型
              printf("code: %hd\n", ev.code);  //哪一个按键
              printf("value: %d\n", ev.value);    //值
              printf("\n");
          }
          
          
          // µÚ4²½£º¹Ø±ÕÉ豸
          close(fd);
          
          return 0;
      }