驱动认知 驱动的编写

1.驱动的认知

打开文件 打开的是 文件名 (存放在/dev下面)
设备号

设备号又分为 主设备号
次设备号
主设备号就像华为手机,苹果手机
次设备号就是主设备号旗下的手机型号华为mate P 等

2.驱动链表

驱动插入到链表的顺序通过设备号进行查改
驱动的链表包括添加,查找
添加:将书写的驱动程序添加到驱动的链表当中
查找:通过设备号找到驱动程序,用户空间进行调用该驱动
分为三个层次 第一个是用户层
第二个是内核态
第三个是硬件层

该图简单的介绍了驱动如何从用户层进入到内核中
用户使用设备名找到设备号,进行调用
VFS虚拟文件系统调用sys_open 通过设备名找到设备号,进入到设备的open函数中进行调用

在这里插入图片描述

2.驱动框架解读

内核中printk使用的

  1. 设备从入口module_init(pin4_drv_init); //1.入口 进入
  2. 进入到_init函数中
    1. 首先进行的是创建设备号 通过主设备号和次设备号创建生成设备号devno = MKDEV(major,minor);
    2. 把写好的主设备号,设备号,创建的结构体写入驱动链表当中
    3. 注册驱动设备在内核中
      1. 首先创建生成设备
      2. 创建生成文件 创建生成文件成功后会在/dev下面生成相应的驱动程序
  3. 卸载驱动 卸载驱动的顺序是相反的 首先销毁设备文件,销毁生成的设备 最后卸载驱动
    i2c_dev_class = class_create(THIS_MODULE, “i2c-dev”); //创建一个名称为i2c-dev的class /sys/class
    确实在/sys/class里面看到了下面定义的那个文件名
#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;  //pin4 的类
static struct device *pin4_class_dev;  //pin4 的设备

static dev_t devno;                //设备号(设备名)
static int major =231;  		   //主设备号
static int minor =4;			   //次设备号
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类似
   
    //配置pin4引脚为输出引脚
    //通过或运算与运算实现14 13 12 位为001
    *GPFSEL0&=~(0x6 << 12 );  //左移12位取反以后就变为了001 相与见零为零
    //所以保障了第14  13 位为0
    *GPFSEL0|=(0x1<<12);  //左移12位取反变成了 001 进行或运算  见一为一
    // 所以保障了第12位为1  
    return 0;
}
static ssize_t pin4_read(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_read\n");
    return 0;
}
//pin4_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    int userCmd;
    printk("pin4_write\n");
    //获取上层write函数的值
    copy_from_user(&userCmd,buf,count);  //获取上层的write的值
    //根据值来操作io口    低电平或者高电平
    printk("get value\n");
    if(userCmd==1){
    *GPSET0 |= (0x1 << 4);  //把引脚4置1 (置一)
    printk("get 1\n");  
    }else if(userCmd==0){
    *GPCLR0 |= (0x1 << 4); //把引脚4置1 (清零)
    printk("get 0\n");        
    }else{
    printk("undo\n");
    }
    return 0;
}
//static表示该命名仅在该驱动程序中有效  防止在其他的驱动文件中出现重复命名
//把该结构体放入到内核驱动的链表中
static struct file_operations pin4_fops = {

    .owner = THIS_MODULE,      //内核中书写结构体指定某一位书写
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read
};

int __init pin4_drv_init(void)      //2.真实驱动入口
{

    int ret;
                 //主设备号 //次设备号
    devno = MKDEV(major,minor);  //3.创建设备号
    //把上面的结构体 pin4_fops 放入驱动链表当中
                        //  主设备号    设备名  结构体
    ret   = register_chrdev(major, module_name,&pin4_fops);  //4.注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
    //让代码生成设备的逻辑类 
    pin4_class=class_create(THIS_MODULE,"myfirstdemo");  //5.让代码在dev中生成设备  (华为手机)
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件 (mate30 )           设备的类         设备号     模块名
    //创建完成就会在/dev生成模块名对应的驱动程序
   //配置   把物理地址转换成虚拟地址
   
   
 
    return 0;
}

void __exit pin4_drv_exit(void)  //卸载的时候是反着卸载的
{
//1.卸载映射空间
//2.销毁设备文件
//3.销毁生成设备
//4.卸载驱动
    iounmap(GPFSEL0);
    iounmap(GPSET0 );
    iounmap(GPCLR0 );  //卸载映射的虚拟空间
    
    device_destroy(pin4_class,devno); //销毁pin4的设备文件
    class_destroy(pin4_class); //销毁pin4 的生成设备
    unregister_chrdev(major, module_name);  //卸载驱动

}

module_init(pin4_drv_init);  //1.入口   内核加载该驱动时,会启动宏
module_exit(pin4_drv_exit);   //6.卸载驱动
MODULE_LICENSE("GPL v2");

3.驱动代码的编译

3.1驱动代码的编译

  1. 打开内核文件
    进入到 drivers 文件夹下面
    创建的是字符设备 因此我们需要进入到char文件夹字符文件
    进入到 char文件夹下面

  2. 创建pin4drivers.c文件

  3. 放到哪里改哪里的Makefile
    保存完成后修改Makefile

  4. 进入vi Makefile
    书写成编译成为模块的方式
    该创建创建的是以模块的方式加载到内核 obj-m m表示的是模块
    obj-m += pin4drivers.o (这个名字和创建的pin4drivers名字一样,只是后缀不同)
    写完后就配置完成

  5. 返回到内核源码文件夹 (liunx…)
    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
    进行编译

  6. 编译没有错误生成pin4drivers.ko
    传给树莓派
    scp ./drivers/char/pin4drivers.ko pi@192.168.43.58:/home/pi
    同时通过交叉编译把主程序发送

3.2在树莓派:

  1. 加载内核驱动
sudo insmod pin4drivers2.ko     
  1. 卸载内核驱动 不需要写.ko 卸载内核驱动
sudo rmmod pin4drivers2 
  1. 查看当前内核的驱动模块
lsmod   
  1. 把dev里面的pin4设置为所有用户可读可写
sudo chmod 666 /dev/pin4   
  1. 查看用户权限
ls -l 

在这里插入图片描述

  1. 查看内核驱动
dmesg   

可以查看到pin4的驱动
在这里插入图片描述

在这里插入图片描述

GPIO的寄存器

GPFSELn   输入模式选择寄存器 pin0~pin9  每个GPIO引脚占3000 = GPIO Pin 9 is an input 
          001 = GPIO Pin 9 is an output
GPSETn   将指定引脚置一
GPCLRn   将指定位置引脚清零

volatile 的用处

       vloatile关键字表示提醒当前编译器该变量是易变的,
       即每次使用和读取该变量的时候都需要重新向变量地址中读取数据

地址

      在编写驱动的时候IO口的驱动地址是在0x3f00 0000加上GPIO的偏移量0x20 0000 
       因此GPIO的物理地址是从0x3f20 0000开始的
       在这个基础上通过Linux系统的MMU内存虚拟化管理,映射到虚拟内存

寄存器的偏移地址

     寄存器的地址是物理地址加上寄存器的偏移地址
     寄存器的地址是物理地址需要使用ioremap函数把物理地址转换成虚拟地址

ioremap函数

   ioremap(物理地址,长度);    

寄存器代码的编写

   1.首先在最开始的地方定义全局变量
   volatile unsigned int * GPFSEL1 =NULL;
   2.在入口函数处把物理地址转换成虚拟地址
    GPFSEL1  = (volatile unsigned int *)ioremap(0x3f20004,4);  
   3.在函数中进行寄存器的按位操作  初始化的pin17引脚
   *GPFSEL1 &= ~(0x6 << 21) ;//该操作是把23 22位置0 不确定21位当前的状态
   *GPFSEL1 |=  (0x1 << 21);   //该操作是不改变其他位的状态,只让21位的值是1
   4.在驱动结束的函数卸载映射的内存空间

4.驱动编写

驱动中有两个函数
read 和write

read
ssize_t (*read)(struct file *filp, char __user *buf, size_t  count, lofft *f_pos);
filp:待操作的设备文件file结构体指针
buf:待写入所读取数据的用户空间缓冲区指针
count:待读取数据字节数
f_pos:待读取数据文件位置,读取完成后根据实际读取字节数重新定位
返回:成功实际读取的字节数,失败返回负值

内核空间-->用户空间
copy_to_user函数
unsigned long copy_to_user(void *to, const void *from, unsigned long n)
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
write  
ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
filp:待操作的设备文件file结构体指针
buf:待写入所读取数据的用户空间缓冲区指针
count:待读取数据字节数
f_pos:待读取数据文件位置,写入完成后根据实际写入字节数重新定位
返回:成功实际写入的字节数,失败返回负值

用户空间-->内核空间
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
外设驱动组的岗位主要负责开发和维护与外部设备(例如键盘、鼠标、打印机、摄像头等)交互的驱动程序。以下是对外设驱动组岗位的一般认知: 1. 驱动程序开发:外设驱动组的主要职责是开发和调试外设驱动程序。这些驱动程序负责与硬件进行通信,使操作系统能够正确地识别和使用外部设备。开发驱动程序可能涉及底层编程和了解硬件的特性和功能。 2. 兼容性测试:外设驱动组通常需要进行兼容性测试,以确保驱动程序能够与多种不同型号和制造商的外部设备正常工作。这可能包括编写测试用例、执行测试和分析测试结果。 3. 故障排除和优化:当用户遇到与外部设备相关的问题时,外设驱动组可能需要参与故障排除和问题解决。这可能涉及调试驱动程序、分析日志文件和与其他团队合作解决问题。 4. 技术支持:外设驱动组可能需要提供技术支持,回答用户对外部设备的疑问,解决与驱动程序相关的问题,并提供操作指导。 5. 跨团队合作:外设驱动组通常需要与硬件团队、操作系统团队和应用程序开发团队等其他团队紧密合作。这包括与硬件工程师讨论设备规格和功能,与操作系统开发人员协调驱动程序的集成,以及与应用程序开发人员解决设备相关的问题。 总之,外设驱动组的岗位需要对底层编程、硬件和操作系统原理有深入的了解,并具备故障排除和跨团队合作的能力。这个岗位对于确保外部设备与操作系统的兼容性和正常工作至关重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值