简介
在 Linux 下有 3 种设备驱动:
(1)字符设备: 字符设备的特点是在 IO 传输的过程中是没有缓冲的,比如串口,i2c,等。
(2)块设备: 块设备也就是存储设备,例如 ddr 内存条,sd 卡,u 盘,emmc,nandflash 这种,它
们的传输是有缓冲的。
(3)网络设备: 网络设备例如以太网,wifi,蓝牙,2/3/4G 模块。一种器件可以同时是 2 种设备,例如蓝牙,它是网络设备,也是字符设备。
一、Linux杂项驱动意义
有很多简单的外围字符设备,它们功能相对简单,一个设备占用一个主设备号对于内核资源来说太浪费。所以对于这些简单的字符设备它们共用一个主设备号,不同的设备使用不同的次设备号。
二、杂项驱动特点
(1)主设备号相同,次设备号不同。
(2)在文件系统中自动生成设备节点。
三、过程概述
编写杂项设备驱动节点给用户使用,编写应用层app.c测试驱动功能是否实现。
四、常用API
包含的头文件#include <linux/miscdevice.h>
。
int misc_register(struct miscdevice * misc); //注册
int misc_deregister(struct miscdevice *misc); //卸载
五、重要结构体
(1)miscdevice
结构体。 包含的头文件:#include <linux/miscdevice.h>
。
struct miscdevice {
int minor; //次设备号
const char *name; //驱动的名字,/dev 下驱动节点的名字也是这个
const struct file_operations *fops; //文件操作集,重点
struct list_head list; //内核链表
struct device *parent; //父设备
struct device *this_device; //额外的参数
const char *nodename;
umode_t mode;
};
(2)file_operations
结构。 包含的头文件:#include <linux/fs.h>
。
这个结构体是文件操作集结构体,记录了应用层的 IO 和内核函数的映射关系。杂项设备中应用层和内核层传输数据就是通过文件操作集来实现的。
struct file_operations {
struct module *owner; //拥有者
//当应用层 read 驱动节点时,会触发这个指针指向的函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t*);
//当应用层 write 驱动节点时,就会触发这个指针指向的函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//当应用层 ioctl 驱动节点时,就会触发这个指针指向的函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//当应用层 open 驱动节点时,就会触发这个指针指向的函数
int (*open) (struct inode *, struct file *);
//当应用层 close 驱动节点时,就会触发这个指针指向的函数
int (*release) (struct inode *, struct file *);
};
六、应用层内核层交互函数。
包含的头文件:#include <linux/uaccess.h>
。
(1)读取用户层传来的数据
copy_from_user(void * to, const void __user * from, unsigned long n);
//to: 拷贝到哪里
//from: 数据来源
//n: 拷贝的字节数
(2)拷贝内核的数据给用户
copy_to_user(void __user *to, const void *from, unsigned long n);
//to: 拷贝到哪里
//from: 数据来源
//n: 拷贝的字节数
七、杂项设备注册流程
(1) 定义文件操作集。
struct file_operations misc_fops = {
.owner = THIS_MODULE, //拥有者是自己 //其它的先不管,先写这么多
};
(2) 定义一个杂项设备。
struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR, //次设备号可以写死,也可以动态分配,MISC_DYNAMIC_MINOR 是动态分配的意思
.name = "qjl", //在/dev 下生成的节点名字
.fops = &misc_fops, //绑定文件操作集,规定触发关系
};
(3) 把杂项设备注册到内核。
int ret;
ret = misc_register(&misc);
if(ret < 0){
printk("misc register error\n");
return ret;
}
八、驱动编写
(1)杂项设备驱动misc.c
编写
#include <linux/module.h> //模块
#include <linux/kernel.h> //内核
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#define KBUF_SIZE (1024)
#define MIN(x,y) ( (x)<(y)? (x):(y) ) //判断哪个小,选哪个
char kbuf[KBUF_SIZE];
int misc_open(struct inode * node, struct file * fp)
{
return 0;
}
int misc_release(struct inode *node, struct file *fp)
{
return 0;
}
ssize_t misc_read(struct file *fp, char __user *ubuf , size_t size, loff_t *offset)
{
int ret;
ret=copy_to_user(ubuf, kbuf, MIN(KBUF_SIZE,size));
if(ret !=0){
pr_err("copy_to_user error\r\n");
return -EINVAL;
}
return MIN(KBUF_SIZE,size); //返回成功读取的字节数
}
ssize_t misc_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *offset)
{
int ret;
ret=copy_from_user(kbuf, ubuf, MIN(KBUF_SIZE,size));
if(ret !=0){
pr_err("copy_from_user error\r\n");
return -EINVAL;
}
pr_err("%s\r\n",kbuf); //打印出用户写来的数据
return MIN(KBUF_SIZE,size); //返回成功写入的字节数
}
//描述一个文件操作集
const struct file_operations misc_fops={
.owner=THIS_MODULE, //文件操作集的拥有者是 本模块。
.open =misc_open, //应用层对本模块的驱动节点open时触发的函数
.release=misc_release, //应用层对本模块的驱动节点close时触发的函数
.read=misc_read, //应用层对本模块的驱动节点read时触发的函数
.write=misc_write, //应用层对本模块的驱动节点write时触发的函数
};
//描述一个杂项设备
struct miscdevice misc={
.minor=MISC_DYNAMIC_MINOR, //由内核自动分配次设备号,misc的主设备号为10.
.name ="qjl", //在/dev下生成的驱动节点的名称,不能有空格!
.fops =&misc_fops,
};
static int __init misc_init(void)
{
int ret;
ret=misc_register(&misc); //向内核注册一个杂项设备
if(ret <0){
pr_err("misc_register error\r\n");
return -1;
}
return 0;
}
static void __exit misc_exit(void)
{
misc_deregister(&misc); //从内核注销一个杂项设备
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
(2)应用层 app.c
编写
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
size_t w_size;
size_t r_size;
char w_buff[128]="hello world!"; //向内核写进的数据
char r_buff[128];
fd = open("/dev/qjl",O_RDWR);
if(fd <0){
perror("open error");
return -1;
}
w_size=write(fd, w_buff, strlen(w_buff));
printf("w_size:%ld\n",w_size);
r_size= read(fd, r_buff, sizeof(r_buff));
printf("w_size:%ld data:%s\r\n", w_size, r_buff);
close(fd);
return 0;
}
(2) Makefile
编写
#多个c文件生成一个ko文件。
obj-m +=misc.o #把.c文件的名字加.o写到这里
# KDIR 内核源码路径,根据自己需要设置
KDIR:=/home/qjl/work/lichee/linux-3.10
all:
#ARCH: 指当前编译的驱动模块的架构
#CROSS_COMPILE:指明交叉编译器的前缀
#-C: 指定去$(KDIR)目录下执行Makefile
#M:告知Makefile,需要的编译文件在哪
#modules: 这个规则是用于编译驱动模块的
@make ARCH=arm64 CROSS_COMPILE=aarch64-linux- -C $(KDIR) M=$(PWD) modules
@rm -fr .tmp_versions *.o *.mod.o *.mod.c *.bak *.symvers *.markers *.unsigned *.order *~ .*.*.cmd .*.*.*.cmd
clean:
@make ARCH=arm64 CROSS_COMPILE=aarch64-linux- -C $(KDIR) M=$(PWD) modules clean
@rm -rf *.ko
九、实现
(1)将编译好的app 和misc.ko文件上传到开发板系统。
(2)安装杂项设备。insmod misc.ko
。
(3)卸载杂项设备。rmmod misc.ko
。
(3)通过cat /proc/misc
查看杂项设备驱动。
或使用 ls /dev
查看杂项设备驱动。
(4)执行app程序。