网站标题title中国新闻社主管部门

当前位置: 首页 > news >正文

网站标题title,中国新闻社主管部门,如何做网站外链,wordpress修改网站iconLinux驱动开发初识 文章目录 Linux驱动开发初识一、驱动的概念1.1 什么是驱动#xff1a;1.2 驱动的分类#xff1a; 二、设备的概念2.1 主设备号次设备号#xff1a;2.2 设备号的作用#xff1a; 三、设备驱动整体调用过程3.1 上层用户操控设备的流程#xff1a;3.2…Linux驱动开发初识 文章目录 Linux驱动开发初识一、驱动的概念1.1 什么是驱动1.2 驱动的分类 二、设备的概念2.1 主设备号次设备号2.2 设备号的作用 三、设备驱动整体调用过程3.1 上层用户操控设备的流程3.2 Linux驱动的运行方式 四、基于框架编写驱动代码4.1 基本字符设备驱动框架4.2 驱动代码的编译4.3 驱动的加载卸载4.4 驱动的测试 五、树莓派IO口驱动的编写5.1 BCM2835芯片手册导读5.2 Pin4引脚定位5.3 根据驱动框架编写树莓派Pin4引脚驱动5.4 编译测试Pin4引脚驱动 一、驱动的概念 1.1 什么是驱动 Linux内核驱动是指一段代码这段代码可以驱动底层硬件即驱动就是对底层硬件设备的操作进行封装并向上层提供函数接口。 1.2 驱动的分类 Linux驱动分为三个基础大类字符设备驱动块设备驱动网络设备驱动。 字符设备(Char Device) 字符设备是个能够像字节流类似文件一样被访问的设备对字符设备发出读/写请求时实际的硬件I/O操作一般紧接着发生字符设备驱动程序通常至少要实现open、close、read和write系统调用比如我们常见的lcd、触摸屏、键盘、led、串口等等他们一般对应具体的硬件都是进行出具的采集、处理、传输 块设备(Block Device) 一个块设备驱动程序主要通过传输固定大小的数据一般为512或1k来访问设备块设备通过buffer cache(内存缓冲区)访问可以随机存取即任何块都可以读写不必考虑它在设备的什么地方块设备可以通过它们的设备特殊文件访问但是更常见的是通过文件系统进行访问只有一个块设备可以支持一个安装的文件系统比如我们常见的电脑硬盘、SD卡、U盘、光盘等 网络设备(Net Device) 任何网络事务都经过一个网络接口形成即一个能够和其他主机交换数据的设备访问网络接口的方法仍然是给它们分配一个唯一的名字比如eth0但这个名字在文件系统中不存在对应的节点内核和网络设备驱动程序间的通信完全不同于内核和字符以及块驱动程序之间的通信内核调用一套和数据包传输相关的函socket函数而不是read、write等比如我们常见的网卡设备、蓝牙设备 二、设备的概念 在学习驱动和其开发之前首先要知道所谓驱动其对象就是设备。 2.1 主设备号次设备号 在Linux中各种设备都以文件的形式存在/dev目录下称为设备文件。最上层的应用程序可以打开关闭读写这些设备文件从而完成对设备的操作。 为了管理这些设备系统为设备编了号每个设备都拥有主设备号和次设备号。主设备号用于区分不同种类的设备而次设备号用于区分同一类型的多个设备。对于常用的设备如硬盘Linux赋予的主设备号一般是3 在/dev目录下输入ls -l就可以看到设备文件对应的主次设备号 2.2 设备号的作用 在了解了什么是主次设备号之后就要了解设备号的用处 在用户态中当用户调用了如open, read, write等函数想要操作设备文件时需要两个参数第一个是文件名第二个就是设备号在内核态中存在着一个驱动链表用于管理所有设备的驱动而驱动在链表中的位置就由设备号来检索 三、设备驱动整体调用过程 3.1 上层用户操控设备的流程 C语言上层调用open函数。open(“/dev/pin4”,O_RDWR);调用/dev下的pin4以可读可写的方式打开。对于上层open调用到内核时会发生一次软中断中断号是0X80从用户空间进入到内核空间。 open会调用到system_call(内核函数)system_call会根据/dev/pin4设备名去找出需要的设备号。 再调到虚拟文件VFS ,调用VFS里的sys_opensys_open会找到在驱动链表里面根据主设备号和次设备号找到引脚4里的open函数引脚4里的open是对寄存器操作及对硬件的操作。 3.2 Linux驱动的运行方式 将驱动编译进 Linux 内核中当 Linux 内核启动的时就会自动运行驱动程序将驱动编译成模块Linux 下模块扩展名为.ko并在Linux 内核启动以后使用相应命令加载驱动模块 四、基于框架编写驱动代码 4.1 基本字符设备驱动框架 #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_namepin4; //模块名//_open函数 static int pin4_open(struct inode *inode,struct file *file) {printk(pin4_open\n); //内核的打印函数和printf类似return 0; }//_write函数 static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) {printk(pin4_write\n); //内核的打印函数和printf类似return 0; }static struct file_operations pin4_fops { //结构体的类型是“file_operations”名字可以自定义 //该结构体的成员就包含实现open和write的驱动函数 //当上层用户想要open或者write这个设备时就会最终跳转到这个驱动代码中实现的open和write操作函数 //此处只赋值了该结构体中的三个成员变量在keil中是不能这样写的linux中可以这个结构体其实有很多成员如果想要实现更多的驱动函数可以把更多的该结构体成员赋值并在这段代码中重写.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); //注册驱动告诉内核把这个驱动加入到内核驱动的链表中//以下两句代码目的是“生成设备文件”也可以通过“mknod”命令手动生成但是一般不会这样做pin4_classclass_create(THIS_MODULE,myfirstdemo); //先创建‘类’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); //入口内核加载驱动的时候这个宏会被调用 module_exit(pin4_drv_exit); MODULE_LICENSE(GPL v2); //linux内核遵循GPL协议4.2 驱动代码的编译 进入Linux源码树目录下的驱动目录因为驱动的是字符设备所以进入的是驱动目录下的char目录。/home/shiyahao/SYSTEM/linux-rpi-4.19.y/drivers/char 在这个路径下创建一个新的C文件pin4driver.c内容为我们刚刚的字符设备驱动 修改当前路径字符设备驱动下的Makefile确保这个新的驱动会被编译到 回到linux内核源码的路径运行以下指令尝试编译 ARCHarm CROSS_COMPILEarm-linux-gnueabihf- KERNELkernel7 make modules将编译好的驱动模块传到树莓派中 scp ./drivers/char/pin4driver.ko pi192.168.31.123:/home/pi4.3 驱动的加载卸载 由于现在刚刚把驱动编译成了.ko的模块所以需要运行以下指令来加载驱动模块 sudo insmod pin4driver.ko //加载驱动模块 sudo rmmod pin4driver.ko //卸载驱动模块此时驱动名字后不用加.ko运行成功后就可以在/dev下看到生成的设备文件“pin4”了 使用ls -l指令查看这个设备的主设备号次设备号和框架代码中的设置一样 给pin驱动加权限 sudo chmod 666 /dev/pin44.4 驱动的测试 在树莓派下写一个测试驱动的C代码 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.hint main() {int fd;fd open(/dev/pin4, O_RDWR); //打开GPIO4口设备文件if(fd 0){printf(open pin4 failed\n);}else{printf(open pin4 success\n);}write(fd, 1, 1); //输出高电平return 0; }执行测试程序后用dmesg 查看内核打印信息发现打印了驱动函数的信息 可见内核也按照框架代码中的printk成功打印了信息驱动测试成功 同时结果也再次印证了当用户在最上层对 驱动文件 调用C库的open函数后最后的结果还是调用最底层 驱动文件里实现的open驱动函数 五、树莓派IO口驱动的编写 ​ 前面我们通过一个基本的字符设备驱动框架来测试了驱动的运行但是在“pin4_open”和“pin4_write”这两个驱动函数的函数体里只写了一句内核打印的代码作为一个真正的驱动文件这显然是不够的。 ​ 同时在之前就提到过驱动位于内核态的最底层其下方就直接是硬件所以驱动函数的目标就是直接操控硬件也就是直接操控寄存器。在我的pin4驱动函数中应该添加的也就是根据函数功能操作寄存器从而实现I/O口操控的代码。 5.1 BCM2835芯片手册导读 明确了目标后就产生了这个问题我怎么知道应该使用哪些寄存器又应该怎么使用呢 答案是根据开发平台的芯片手册/电路图来找到具体的描述由于我是在树莓派3B上玩驱动的开发所以我应该查阅这款树莓派的芯片也就是BCM2835的芯片手册。 此处我只使用了芯片手册就定位了寄存器而没有用电路图原因是树莓派的这个芯片手册已经把用什么寄存器写的很清楚了 在BCM2835芯片手册的第六章描述了General Purpose I/O (GPIO)外设相关寄存器。这里驱动pin4引脚需要用到的寄存器有 GPIO Function Select Registers (GPFSELn) 功能选择寄存器 该寄存器共有五组每个寄存器都有32位以GPIO Alternate function select register 0为例其中 29-0位 每三位对于一个引脚比如29-27对应的是GPIO Pin 9,26-24对应的是GPIO Pin 8且这三位取不同的值代表该三位对应的引脚选择不同的功能。比如当29-27位为000时表示GPIO Pin 9是输入功能29-27位为001时表示GPIO Pin 9是输出的功能。 GPIO Pin Output Set Registers (GPSETn) 置位寄存器 该寄存器共两组每个寄存器都有32位将寄存器某一位置1即将对应的引脚置1。 GPIO Pin Output Clear Registers (GPCLRn) 清0寄存器 与置位寄存器用法一至将对应位数引脚置0。 所需寄存器的地址说明
​ 在编写驱动程序时IO空间的起始地址位0X3F000000加上GPIO的偏移量0X200000因此GPIO的物理地址是从0X3F200000开始的而编程所需的地址是虚拟地址需要通过MMU内存虚拟化管理将地址映射到虚拟地址上。 5.2 Pin4引脚定位 Pin4引脚指的是BCM4号对应WiringPi库第7号物理引脚的7脚 5.3 根据驱动框架编写树莓派Pin4引脚驱动 #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_namepin4; //模块名//首先定义所要用的寄存器为了防止地址被编译器优化需要用到volatile关键字 volatile unsigned int *GPFSEL0 NULL; volatile unsigned int *GPSET0 NULL; volatile unsigned int *GPCLR0 NULL;static int pin4_open(struct inode *inode,struct file *file) {printk(pin4_open\n); //内核的打印函数和printf类似//配置引脚4的寄存器将其配置为输出模式,即将GPFSEL0寄存器的第14-12位配置成001*GPFSEL0 0XFFFF9FFF; //将第14,13位置0*GPFSEL0 | 0X00001000; //将第12位置1return 0; }static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos) {int usercmd;printk(pin4_write\n);copy_from_user(usercmd,buf,count);//获取应用层write函数写入的内容if(usercmd 1){printk(set 1\n);*GPSET0 |(0x1 4); //将Pin4引脚置1}else if (usercmd 0){printk(set 0\n);*GPCLR0 |(0X1 4);//将Pin4引脚置0}else{printk(undo\n);}return 0; }static struct file_operations pin4_fops {.owner THIS_MODULE,.open pin4_open,//当应用层调用open函数时内核会调用pin4_open..write pin4_write,//当应用层调用write函数时内核会调用pin4_write. };int __init pin4_drv_init(void) //真实的驱动入口 {int ret;devno MKDEV(major,minor); //创建设备号ret register_chrdev(major, module_name,pin4_fops); //注册驱动 告诉内核把这个驱动加入到内核驱动的链表中pin4_classclass_create(THIS_MODULE,myfirstdemo);//由代码在dev下自动生成设备pin4_class_dev device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件GPFSEL0 (volatile unsigned int *)ioremap(0X3f200000,4);//需要将物理地址映射位虚拟地址 ipremap第一个参数需要被映射的物理地址。第二个参数位映射的字节数GPSET0 (volatile unsigned int *)ioremap(0X3f20001C,4);//通过芯片手册可以看到该寄存器在基础地址上偏移了1CGPCLR0 (volatile unsigned int *)ioremap(0X3f200028,4);//通过芯片手册可以看到该寄存器在基础地址上偏移了28return 0; }void __exit pin4_drv_exit(void) {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);然后在树莓派上编写测试代码 #include sys/types.h #include sys/stat.h #include fcntl.h #include stdio.h #include unistd.h #include string.h int main(int argc, char const *argv[]) {int fd,cmd;fd open(/dev/pin4,O_RDWR);printf(input 0 ro 1 , 0 :Pin4 Set 0,1:Pin4 Set 1\n);scanf(%d,cmd);printf(cmd %d \n,cmd);write(fd,cmd,1);return 0; }5.4 编译测试Pin4引脚驱动 将驱动代码编译后生成驱动模块放置在树莓派上进行测试 ARCHarm CROSS_COMPILEarm-linux-gnueabihf- KERNELkernel7 make modules将生成的驱动模块拷贝至树莓派 scp ./drivers/char/pin4driver.ko pi192.168.31.123:/home/pi在树莓派上安装驱动并给驱动权限 sudo insmod pin4driver.ko sudo chmod 666 /dev/pin4运行测试程序