驱动

linux应用编程其实都是通过调用c库,c库给上层应用提供支配内核(进程、网络等)干活的接口

 

驱动:

  在内核中,有个驱动链表管理类所有的设备驱动,驱动插入链表的顺序由设备号检索;

  设备号分为主设备号和次设备号,主设备号被系统用来确定要调用哪个驱动程序设备类型:如USB设备,硬盘设备),主设备号相同的设备是同类设备,使用同一个驱动程序,次设备号被驱动程序用来确定具体哪一个设备。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。

 

驱动的开发大致分为两个步骤:

  1. 添加驱动:编写驱动程序,添加到内核驱动链表

    如何编写驱动程序?按照驱动框架,分为如下几步:

      a. 设备名

      b. 设备号  

      c. 向驱动框架中添加驱动函数(即操作相关的寄存器)

  2. 调用驱动:调用open、reead、write函数,操作硬件

    内核是如何知道调用哪个驱动呢?

      因为open时由设备名,通过设备名就能知道设备号,然后在驱动链表中就能找到对应的驱动

所以驱动的开发大致流程如下:

  当向内核添加驱动后,用户在用户空间调用open,会触发系统调用sys_call,sys_call会调用到sys_open,

  然后sys_open会在驱动链表里面通过设备名找到设备号,从而确定对应的驱动,然后再调用驱动函数里的open,在驱动函数里的open就操作硬件寄存器

 

驱动框架:

#include <linux/fs.h>         //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>     //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件
 
static struct class *pin4_class;  
static struct device *pin4_class_dev;
 
static dev_t devno;                //设备号
static int major =231;             //主设备号
static int minor =0;               //次设备号
static char *module_name="pin4";   //模块名
 
//pin4_read函数
//static int pin4_read(struct file *file,char __user *buf,size_t count, loff_t *ppos)
//{
//    printk("pin4_write\n");
//    return 0;
//}
 
//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数,和printf类似
   
    return 0;
}
 
//pin4_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_write\n");
    return 0;
}
 
static struct file_operations pin4_fops = {
 
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};
 
int __init pin4_drv_init(void)   //驱动的真正入口
{
 
    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
 
    pin4_class=class_create(THIS_MODULE,"myfirstdemo");  //由代码在/dev下自动生成设备
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件
 
 
    return 0;
}
 
void __exit pin4_drv_exit(void)
{
 
    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);  //卸载驱动
 
}
 
module_init(pin4_drv_init);  //入口:内核加载驱动的时候(insmod),这个宏会被调用,而真正的驱动入口是它调用的函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

这样通过驱动框架编写好一个驱动程序,前面提到驱动可以直接编译进内核,也可以通过模块的方式加载进内核,这里我们以模块的方式。

  1. 将这个驱动代码放到内核源码下的linux-rpi-4.14.y/drivers/char,然后仿照该目录下的makefile,添加将这个驱动代码编译成模块的指令,如

 

   2. 然后再执行编译驱动的命令(将其编译成模块):

    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules

  3. 然后在树莓派中装驱动

    sudo insmod pin4driver.ko

  4. 查看添加的驱动文件是否成功/查看内核中挂载的驱动

    ls -l /dev/pin4   或  lsmod

  5. 修改/dev/pin4权限

    sudo chmod 666 /dev/pin4

demsg:查看内核打印信息,pintk是内核的输出函数。

删除驱动:sudo rmmod pin4drive

GPIO驱动;

#include <linux/fs.h>         //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>     //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件
 
static struct class *pin4_class;  
static struct device *pin4_class_dev;
 
static dev_t devno;                //设备号
static int major =231;             //主设备号
static int minor =0;               //次设备号
static char *module_name="pin4";   //模块名
 
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;
 
//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数,和printf类似
    //open的时候配置pin4为输出引脚
    *GPFSEL0 &= ~(0x6 << 12);
    *GPFSEL0 |= (0x1 << 12);
    
    return 0;
}
 
//pin4_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    int userCmd;//上层写的是整型数1,底层就要对应起来用int.如果是字符则用char
 
    printk("pin4_write\n");
    //获取上层write的值
    copy_from_user(&userCmd,buf,count);//用户空间向内核空间传输数据
    
    //根据值来执行操作
    if(userCmd == 1){
        printk("set 1\n");
        *GPSET0 |= 0x1 << 4;
    }else if(userCmd == 0){
        printk("set 0\n");
        *GPCLR0 |= 0x1 << 4;
    }else{
        printk("cmd error\n");
    }
    
    return 0;
}
 
static struct file_operations pin4_fops = {
 
    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
};
 
int __init pin4_drv_init(void)   //驱动的真正入口
{
 
    int ret;
    printk("insmod driver pin4 success\n");
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
 
    pin4_class=class_create(THIS_MODULE,"myfirstdemo");  //由代码在/dev下自动生成设备
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件
 
    GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);//0x3f200000是物理地址,代码操作的是虚拟地址,所以要将物理地址映射成虚拟地址
    GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
    GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
     
     return 0;
}
 
void __exit pin4_drv_exit(void)//可以发现和init刚好是相反的执行顺序。
{
    iounmap(GPFSEL0);
    iounmap(GPSET0);
    iounmap(GPCLR0);
    
    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);  //卸载驱动
 
}
 
module_init(pin4_drv_init);  //入口:内核加载驱动的时候,这个宏会被调用,而真正的驱动入口是它调用的函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

  前面提到驱动有两种加载方式:以模块的方式和直接编译进内核,这里我们以模块的方式

  1. 将编写好的驱动代码拷贝到内核目录下(如/linux-rpi-4.14.y/driver/char,也可以放在其他目录,但要修改对应目录下的makefile),已达到将驱动编译

  2. 修改相应目录下的makefile,如过将驱动代码放在/linux-rpi-4.14.y/driver/char下,就修改该目录下的makefile,以到达将驱动编译成模块   

    

  3. 返回内核源码,进行模块编译(会在放源码的位置生成驱动模块pin4_drive.ko):

    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

  4. 将pin4_drive.ko拷贝到树莓派,然后加载内核驱动:

    sudo insmod pin4_drive.ko

  5. 查看驱动是否加载成功  / 查看系统的驱动模块

    ls -l /dev/pin4  或 lsmod

  6. 如果用户空间open /dev/pin4权限不够:

    chmod 666 /dev/pin4

demsg:查看内核的打印信息,相当与用户空间的printf

sudo rmmod +驱动名(不需要写.ko):卸载驱动

posted @ 2023-04-16 13:57  踏浪而来的人  阅读(72)  评论(0编辑  收藏  举报