字符设备驱动开发的基本步骤可以看上一篇,本节就以 chrdevbase 这个虚拟设备为例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备,方便讲解字符设备的开发而引入的一个虚拟设备。chrdevbase 设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 chrdevbase 设备的写缓冲区中写入数据,从读缓冲区中读取数据。chrdevbase 这个虚拟设备的功能很简单,但是它包含了字符设备的最基本功能。
| 需求目标
应用程序调用 open 函数打开 chrdevbase 这个设备,打开以后可以使用 write 函数向chrdevbase 的写缓冲区 writebuf 中写入数据(不超过 100 个字节),也可以使用 read 函数读取读缓冲区 readbuf 中的数据操作,操作完成以后应用程序使用 close 函数关闭 chrdevbase 设备。
| 实现过程
1. 新建文件一个nxp文件夹,然后把原厂内核文件复制过来
2、创建linux_drivers用于存放驱动文件,创建01_chrdevbase用于存放chrdevbase实验文件,chrdevbase.c是底层驱动代码,chrdevbaseapp.c是应用代码,makefile编译底层驱动;
3、编写代码
chrdevbase.c文件
#include #include #include #include #include #include #define chrdevbase_major 200 /* 主设备号 */#define chrdevbase_name chrdevbase /* 设备名 */static char readbuf[100]; /* 读缓冲区 */static char writebuf[100]; /* 写缓冲区 */static char kerneldata[] = {kernel data!};/* * @description : 打开设备 * @param - inode : 传递给驱动的inode * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 * 一般在open的时候将private_data指向设备结构体。 * @return : 0 成功;其他 失败 */static int chrdevbase_open(struct inode *inode, struct file *filp){ //printk(chrdevbase open!); return 0;}/* * @description : 从设备读取数据 * @param - filp : 要打开的设备文件(文件描述符) * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){ int retvalue = 0; /* 向用户空间发送数据 */ memcpy(readbuf, kerneldata, sizeof(kerneldata)); retvalue = copy_to_user(buf, readbuf, cnt); if(retvalue == 0){ printk(kernel senddata ok!); }else{ printk(kernel senddata failed!); } //printk(chrdevbase read!); return 0;}/* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){ int retvalue = 0; /* 接收用户空间传递给内核的数据并且打印出来 */ retvalue = copy_from_user(writebuf, buf, cnt); if(retvalue == 0){ printk(kernel recevdata:%s, writebuf); }else{ printk(kernel recevdata failed!); } //printk(chrdevbase write!); return 0;}/* * @description : 关闭/释放设备 * @param - filp : 要关闭的设备文件(文件描述符) * @return : 0 成功;其他 失败 */static int chrdevbase_release(struct inode *inode, struct file *filp){ //printk(chrdevbase release!); return 0;}/* * 设备操作函数结构体 */static struct file_operations chrdevbase_fops = { .owner = this_module, .open = chrdevbase_open, .read = chrdevbase_read, .write = chrdevbase_write, .release = chrdevbase_release,};/* * @description : 驱动入口函数 * @param : 无 * @return : 0 成功;其他 失败 */static int __init chrdevbase_init(void){ int retvalue = 0; /* 注册字符设备驱动 */ retvalue = register_chrdev(chrdevbase_major, chrdevbase_name, &chrdevbase_fops); if(retvalue < 0){ printk(chrdevbase driver register failed); } printk(chrdevbase init!); return 0;}/* * @description : 驱动出口函数 * @param : 无 * @return : 无 */static void __exit chrdevbase_exit(void){ /* 注销字符设备驱动 */ unregister_chrdev(chrdevbase_major, chrdevbase_name); printk(chrdevbase exit!);}/* * 将上面两个函数指定为驱动的入口和出口函数 */module_init(chrdevbase_init);module_exit(chrdevbase_exit);/* * license和作者信息 */module_license(gpl);module_author(zuozhongkai);
chrdevbaseapp.c文件
#include stdio.h#include unistd.h#include sys/types.h#include sys/stat.h#include fcntl.h#include stdlib.h#include string.hstatic char usrdata[] = {usr data!};/* * @description : main主程序 * @param - argc : argv数组元素个数 * @param - argv : 具体参数 * @return : 0 成功;其他 失败 */int main(int argc, char *argv[]){ int fd, retvalue; char *filename; char readbuf[100], writebuf[100]; if(argc != 3){ printf(error usage!); return -1; } filename = argv[1]; /* 打开驱动文件 */ fd = open(filename, o_rdwr); if(fd < 0){ printf(can't open file %s, filename); return -1; } if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */ retvalue = read(fd, readbuf, 50); if(retvalue < 0){ printf(read file %s failed!, filename); }else{ /* 读取成功,打印出读取成功的数据 */ printf(read data:%s,readbuf); } } if(atoi(argv[2]) == 2){ /* 向设备驱动写数据 */ memcpy(writebuf, usrdata, sizeof(usrdata)); retvalue = write(fd, writebuf, 50); if(retvalue < 0){ printf(write file %s failed!, filename); } } /* 关闭设备 */ retvalue = close(fd); if(retvalue < 0){ printf(can't close file %s, filename); return -1; } return 0;}
makefile文件
kerneldir := /home/noah/linux/nxp/linux-imx-rel_imx_4.1.15_2.1.0_gacurrent_path := $(shell pwd)obj-m := chrdevbase.obuild: kernel_moduleskernel_modules: $(make) -c $(kerneldir) m=$(current_path) modulesclean: $(make) -c $(kerneldir) m=$(current_path) clean
相关解析:
第 1 行,kerneldir 表示开发板所使用的 linux 内核源码目录,使用绝对路径,大家根据自己的实际情况填写即可。第 2 行,current_path 表示当前路径,直接通过运行“pwd”命令来获取当前所处路径。第 3 行,obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。第 8 行,具体的编译命令,后面的 modules 表示编译模块,-c 表示将当前的工作目录切换到指定目录中,也就是 kernerldir 目录。m 表示模块源码目录,“make modules”命令中加入 m=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。
4、编译驱动
直接使用make命令会报错的,因为kernel中没有指定编译器和架构,使用了默认的x86平台编译报错。
5、修改内核的makefile文件
直接定义arch和cross_compile 这两个的变量值为 arm 和 arm-linux-gnueabihf-
6、再次编译驱动
编译通过,会生成不少编译文件;
7、编译应用程序
应用程序只有一个文件,在ubuntu对应文件夹,直接输入指令进行编译:
arm-linux-gnueabihf-gcc chrdevbaseapp.c -o chrdevbaseapp
8、sd卡启动系统
插入sd卡,把拨码开关调到sd卡启动,然后ping一下网关,确认网络通畅才能继续的进行,如果ping不通就请看看前几节;
9、启动内核
在uboot界面输入下面指令启动系统,
tftp 80800000 zimagetftp 83000000 imx6ull-14x14-evk.dtbbootz 80800000 - 83000000
10、创建目录并复制驱动文件
检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。
将 chrdevbase.ko 和 chrdevbaseapp 复制到 rootfs/lib/modules/4.1.15 目录中:
11、加载设备驱动
自制的根文件系统,有些命令是不支持的;
// 加载驱动insmod chrdevbase.ko// 查看驱动lsmod// 指令查看devices 信息cat /proc/devices
效果如下图:
12、创建设备节点文件
驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件:
mknod /dev/chrdevbase c 200 0其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看,结果如图:
13、验证读写
使用 chrdevbaseapp 软件操作 chrdevbase 这个设备,看看读写是否正常,首先进行读操作,输入如下命令:
// 读./chrdevbaseapp /dev/chrdevbase 1// 写./chrdevbaseapp /dev/chrdevbase 2
相关解析:
三个参数“./chrdevbaseapp”、“/dev/chrdevbase”和“1”,这三个参数分别对应 argv[0]、argv[1]和 argv[2]。
第一个参数表示运行 chrdevbaseapp 这个软件,
第二个参数表示测试app要打开/dev/chrdevbase这个设备。
第三个参数就是要执行的操作,1表示从chrdevbase中读取数据,2 表示向 chrdevbase 写数据。
效果如下:
14、卸载驱动模块
如果不再使用某个设备的话可以将其驱动卸载掉,命令如下:
// 卸载chrdevbase.kormmod chrdevbase.ko// 查看驱动lsmod
至此,chrdevbase 这个设备的整个驱动就验证完成了,驱动工作正常。
新生物联网可以为半导体公司提供广阔的机会
QCY T17“双11”狂欢备受瞩目,其魅力何在
移相全桥零电压PWM软开关电路
外媒上调预测记录:5G智能手机出货量将超2.6亿部
关于导热泥的性能优势及应用领域的介绍
i.MX6ULL|字符设备驱动开发实践
MOSFET与硅控整流器过压保护的比较
工业互联网新秀凭借开放平台实现跨行业互联互通?
发电机有刷还是无刷好
对于云服务器和本地存储你选择哪一个
华宇电子亮相2022世界集成电路大会
华为mate9和p9的区别,16nm工艺处理器给力升级但mate9曲屏版或跳票
C语言零基础项目:生命游戏!详细思路+源码分享
基于PLC的机器人伺服运动控制系统设计详解
用MDaemon搭建邮件服务器-设置篇
松下进一步增强加码动力电池业务的决心
谷歌发布第三季度财报,总营收461.73亿美元
华为 P20在上海发布 还有全球首款全面屏笔记本
怎么应对刘海屏机型的海报设计
相控阵雷达性能的基石:宽禁带半导体