驱动_Linux驱动框架

应用实现策略,驱动实现机制

驱动框架


驱动框架 (字符设备 /  块设备 /  网络设备)

#include <linux/init.h>

#include <linux/module.h>

加载 {

    1.申请设备编号 (动静与主副)

    2.创建设备节点 (自动与手动) 

    3.硬件的初始化 (映射与中断)

    4.硬件接口函数 (实现f o p s)

    error :

}

卸载{

  释放空间

}

           认证声明


模块加载函数:安装模块时被系统自动调用的函数,通过module_init宏来指定。

模块卸载函数:卸载模块时被系统自动调用的函数,通过module_exit宏来指定。

模块可选信息

许可证申明:MODULE_LICENSE("GPL");

      用来告知内核该模块带有一个许可证。有效的许可证有“GPL”,“GPLv2”等。

作者申明:MODULE_AUTHOR(“LuckY”);

模块描述:MODULE_DESCRIPTION(“hello world module”);

模块版本:MODULE_VERSION(“V1.0”);

模块别名:MODULE_ALIAS(“simple module”);

常用函数


①:申请设备编号

    内核提供了三个函数来注册一组字符设备编号,这三个函数分别是:

  1.     static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)    动静申请主设备号 

           //参数1 ---- major不为0,表示要申请的主设备号,该设备号由我们自己指定,这种叫静态申请;

                major为0,这时register_chrdev会返回主设备号,由系统自动分配,这种叫动态申请

             //参数2 ---- 字符串,表示驱动的描述信息,自定义

             //参数3 ---- 文件操作对象的指针

             //返回值:major不为0,成功---返回0,失败---返回错误码; major为0,成功----主设备号,失败--返回错误码 

  2.  静:  int register_chrdev_region(dev_t from, unsigned count, const char *name)

          //参数1 ---- dev_t 静态指定的设备号
          //参数2 ---- 设备的个数
          //参数3 ---- 字符串,描述驱动的名称,自定义
          //返回值-----成功:0,失败---错误码

    

     动:  int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

          //参数1 ---- 存放设备号的变量的地址
          //参数2 ---- 次设备号
          //参数3 ---- 设备的个数
          //参数4 ---- 字符串,描述驱动的信息,自定义
          //返回值-----成功:0,失败---错误码   

          struct cdev {

          struct kobject kobj;
          struct module *owner;//填充时,值要为 THIS_MODULE,表示模块
          const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量
          struct list_head list;
          dev_t dev;//设备号,主设备号+次设备号
          unsigned int count;//次设备号个数
          };

          struct cdev *cdev_alloc(void)                   // 申请cdev的空间

          void cdev_init(struct cdev *cdev, const struct file_operations *fops) // 初始化cdev的成员

          int cdev_add(struct cdev *p, dev_t dev, unsigned count)                    // 将cdev加入到内核中----链表(只有注册到内核,内核才能统一管理)

          cdev_del                               // 从内核中注销掉一个驱动注销驱动
                    

②:创建设备节点

  1. 手动创建:mknod  设备节点名称  类型  主设备号  次设备号 (mknod    /dev/hellow   c   254   0 )

  2. 自动创建:

  struct class * class_create(struct module *owner, const char *name) 

     //参数1 ---- 当前模块 THIS_MODULE用这个宏代替

     //参数2 ---- 字符串,类的描述信息,自定义

     //返回值:成功-----struct class结构体的地址,失败----NULL

   struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...)

    //参数1 ---- struct class * 类型的指针

    //参数2 ---- 父类,一般为NULL

    //参数3 ---- 设备号:dev_t
      #define MINORBITS 20
      #define MINORMASK ((1U << MINORBITS) - 1)

      #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
      #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
      #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
       //参数4 ---- 私有数据,一般为NULL

       //参数5 ---- 设备文件的名称

    //变参----与参数5一起使用,用来定义设备文件的名称

    //返回值: 成功---struct device 结构体的地址,失败-----NULL

 

③:硬件的初始化

   1. 地址映射:

       

    gpco_conf = ioremap(0x1F02C04,8);
    gpco_data = ioremap(0x1F02C10,8);

   

    *gpco_conf &= ~(0x07<<8);   //8--10清0
    *gpco_conf |= 0x01<<8;         //8---10赋值:00010001

    代码示例https://www.cnblogs.com/panda-w/p/10966500.html

        

    

  2. 中断申请:

    (1).申请

    unsigned int irqno  = IRQ_EINT(1);

    static inlineint gpio_to_irq(unsigned gpio)

    返回中断编号传给request_irq()和free_irq()

    static inline int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)    

      //参数1 ----- 中断号
      //参数2 ----- 中断处理函数:irq_handler_t 等价 typedef irqreturn_t (*irq_handler_t)(int, void *)
      //参数3 ----- 中断触发方式:
      #define IRQF_TRIGGER_NONE 0x00000000 //内部中断触发
      #define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发
      #define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发
      #define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发
      #define IRQF_TRIGGER_LOW 0x00000008 //低电平触发
      //参数4 ----- 字符串,描述信息,自定义
      //参数5 ----- 传给中断处理函数的参数
      //返回值 ----- 成功:0,失败:错误码

    (2).处理    

    irqreturn_t xxx_irq_svc(int irqno, void *dev){

    ........

    return IRQ_HANDLED;
    }

    (3).释放

    void free_irq(unsigned int irq, void *dev_id)
      //参数1 ----- 中断号
      //参数2 ----- 必须与request_irq的最后一个参数保持一致

④:硬件接口函数

  struct file_operations {
    ① struct module *owner;

    ② int (*open) (struct inode *, struct file *);

    ③ int (*release) (struct inode *, struct file *);

    ④ ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    ⑤ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ⑥ loff_t (*llseek) (struct file *, loff_t, int);

    ⑦ long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    ⑧ int (*mmap) (struct file *, struct vm_area_struct *);

    ⑨ unsigned int (*poll) (struct file *, struct poll_table_struct *);

    ⑩ int (*flock) (struct file *, int, struct file_lock *);    

    ⑪ int (*flush) (struct file *, fl_owner_t id);

    };

  int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);

    通过发送命令的方式,来控制设备

  

重要结构体


常用机制


①:中断申请

  代码示例:https://www.cnblogs.com/panda-w/p/10991402.html   

  中断下半部

  代码示例:https://www.cnblogs.com/panda-w/p/10991450.html

③:poll多路复用    和        轮询

  应用空间:
    #include <poll.h>

    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd {
    int fd; /* file descriptor */
    short events; /* requested events */
    short revents; /* returned events */
    };

  ----------------------------------------------------

   内核驱动:  

    static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    //参数1-----file结构体指针
    //参数2-----等待队列头
    //参数3-----与等待队列相关联的表

  代码示例:https://www.cnblogs.com/panda-w/p/10991424.html

②:mmap应用

  应用空间:
   #include <sys/mman.h>
   void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
   //参数1 ----- 指定映射之后的虚拟空间的的位置,一般为NULL,表示由系统自动分配映射的虚拟空间
   //参数2 ----- 映射的空间长度
   //参数3 ----- 对内存的操作权限:PROT_EXEC PROT_READ PROT_WRITE PROT_NONE
   //参数4 ----- 是否允许其他进程映射这块内存:MAP_SHARED MAP_PRIVATE
   //参数5 ----- 打开的文件的描述符
   //参数6 ----- 从(物理)内存的偏移多少字节的位置开始映射
   //返回值:成功----映射后的虚拟空间的起始地址,失败----NULL
  ------------------------------------------------------------------------------------------
  内核驱动:
   int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)
   //参数1 ---- 表示映射空间的相关信息
   //参数2 ---- 映射到应用空间的起始地址
   //参数3 ---- 被映射的物理内存的页地址
   //参数4 ---- 映射的空间大小
   //参数5 ---- 映射的空间的权限

  代码示例:https://www.cnblogs.com/panda-w/p/10991438.html

⑤:非/阻塞IO

  1,阻塞IO的实现:(在应用空间中,有很多函数默认就是阻塞IO:scanf(), accept(),connect(),recv(),read()等 )  

    1》 需要初始化一个等待队列头  ------------ 驱动的加载函数中,中断申请之后调用

      init_waitqueue_head(wait_queue_head_t *q)

    2》根据条件决定是否让进程入休眠状态 ---------- 在xxx_read()中调用

      wait_event_interruptible(wait_queue_head_t wq,int condition)

        //参数1 ---- 等待队列头 

        //参数2 ---- 一个条件变量: 0-----休眠,1-----不休眠

    3》当资源可用时,必须唤醒阻塞的进程 ----------------- 在中断处理函数中唤醒进程

      wake_up_interruptible(wait_queue_head_t * x)

    代码示例:https://www.cnblogs.com/panda-w/p/10991359.html

      

  2,非阻塞IO的实现:

    应用程序中:
      fd = open("/dev/button",O_RDWR|O_NONBLOCK);

      read() ------- 有数据,则读出数据,没有数据,则直接返回,返回一个错误码-EAGAIN

      -------------------------------------------------------------------------
    内核驱动中:
      ssize_t xxx_read(struct file *filp , char __user *buf , size_t size, loff_t *flags)
      {
       int ret;
       printk("--------^_^ %s------------ ",__FUNCTION__);
       //判读open时,有没有设置flags为NONBLOCK
       if(filp->f_flags & O_NONBLOCK && !button_dev->have_data)
       return -EAGAIN;

       ...........
      }

    代码示例:https://www.cnblogs.com/panda-w/p/10991411.html

⑥:数据传递

 1. static inline long copy_to_user(void __user *to,const void *from, unsigned long n)

   //内核空间传递数据给应用空间  ---------- 实现read接口

   //参数1 ----- 用户空间的地址

   //参数2 ----- 内核空间数据的地址

   //参数3 ----- 传递的数据的长度

      //返回值------ 成功:0,失败:未传递的数据的字节数

 2.  static inline long copy_from_user(void *to, const void __user * from, unsigned long n)

   //应用空间传递数据给内核空间  ---------- 实现wirte接口

   //参数1 ----- 内核的空间地址

   //参数2 ----- 用户空间数据的地址

   //参数3 ----- 传递的数据的长度

   //返回值------ 成功:0,失败:未传递的数据的字节数

   代码示例:https://www.cnblogs.com/panda-w/p/10991322.html

⑦:GPIO函数

  7,将某个gpio口配置为特定功能
  int s3c_gpio_cfgpin(unsigned int pin, unsigned int config)

  8,将某个gpio口内部上拉或者下拉
  int s3c_gpio_setpull(unsigned int pin, s3c_gpio_pull_t pull)   

⑧:ioctl应用

   通过发送命令的方式,来控制设备

   long xxx_ioctl(struct file *filp, unsigned int cmd , unsigned long args)

   代码示例:https://www.cnblogs.com/panda-w/p/10991312.html

  


<笔记>

1. 在linux中,设备号用32位的整数表示:
   分两部分:
   主设备号: 表示一类设备 ------ 高12位
   次设备号: 表示某一类设备中的某个具体设备----低20位

2. 查看设备编号:cat /proc/devices

3. 查看设备节点:ls   /dev

4. 常用头文件 : 

  #include <linux/×××.h> 平台无关
  #include <asm/×××.h> CPU体系结构
  #include <plat/×××.h> IC公司相关
  #include <mach/×××.h> 开发板相关

5. 驱动查看位置:/sys/module

6. 主次设备号表示:不同类别设备与同一类别的不同设备

7. PCI :外围器件互联

8. ioctl:是应用层的API  

9. boot  0x******  从开始运行

10.  驱动设备的寄存器,完成设备的轮询、中断处理、DMA通信,最终让通信设备可以收发数据

11.  cat /proc/devices         lsmod    查看设备信息

12.  驱动是异步机制

13.  

Stay hungry, stay foolish 待续。。。
原文地址:https://www.cnblogs.com/panda-w/p/10922316.html