Linux中将设备分为三大类:字符设备(I2C、USB、SPI等)、块设备(存储器相关的设备如EMMC、SD卡、U盘等)和网络设备(网络相关的设备WIFI等)。
杂项设备归属于字符设备,每个设备节点都有主设备号和次设备号 ,设备号是识别设备的一种方式,Linux系统中有很多杂项设备,而杂项设备的主设备号固定为10。 使用命令<cat /proc/misc>可以查看各杂项设备。
杂项(misc)设备相关文件:kernel/include/linux/miscdevice.h
misc设备结构体miscdevice,其中minor、name、fops需要自己配置。
struct miscdevice {
int minor; //次设备号
const char *name; //设备名
const struct file_operations *fops; //文件操作集
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
misc设备需要配置次设备号,而次设备号不能配置成系统中已经使用过的,为了便捷可以将minor配置为MISC_DYNAMIC_MINOR(定义在miscdevice.h中),此时加载该设备时系统会自动设置次设备号。
#define PSMOUSE_MINOR 1
#define MS_BUSMOUSE_MINOR 2 /* unused */
#define ATIXL_BUSMOUSE_MINOR 3 /* unused */
/*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
#define ATARIMOUSE_MINOR 5 /* unused */
#define SUN_MOUSE_MINOR 6 /* unused */
。
。
。
#define UHID_MINOR 239
#define USERIO_MINOR 240
#define MISC_DYNAMIC_MINOR 255
misc_register注册misc设备函数:
原型:int misc_register(struct miscdevice * misc)
参数:misc 之前创建好的miscdevice 结构体
返回值:成功返回0,失败返回负数。
misc_deregister注销misc函数:
原型:int misc_deregister(struct miscdevice *misc)
参数:misc 要注销的miscdevice 结构体。
返回值:无。
① 填充miscdevice结构体;
② 填充file_operations结构体;
③ 调用注册、卸载函数注册、卸载设备节点。
#include //模块相关
#include
#include //文件操作
#include
#include //copy kernel or user fuction
在file_operations结构体中,owner默认配置成THIS_MODULE,open、read、write、release函数是在linux应用程序中定义的函数,运行在用户空间,misc_open、misc_read、misc_write、misc_release在驱动程序的定义,运行在内核空间,应用程序中的函数通过系统调用执行驱动函数。
在miscdevice结构体中,配置次设备号minor、设备节点名name和文件操作函数集配置;file_operations 中的成员函数实际是由drivers/char/misc.c 中misc 驱动核心层的misc_fops 成员函数间接调用的。
struct file_operations misc_fops=
{
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
struct miscdevice misc_dev =
{
.minor = MISC_DYNAMIC_MINOR,
.name = "qurry_misc",
.fops = &misc_fops,
};
驱动程序中,misc_open和misc_release打开、释放设备函数中不需要实现复杂的操作;应用程序中调用read函数读取设备节点信息,misc_read函数中copy_to_user()将内核空间信息复制到用户空间供read读取;应用程序中调用write函数写入信息到设备节点,misc_write函数中copy_from_user()将用户空间信息复制到内核空间。
static char writebuf[64];
const char kernel_data[]="this is kernel data.";
int misc_open(struct inode *inode,struct file *file)
{
printk("misc_open ok.\r\n");
return 0;
}
ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
if(copy_to_user(ubuf, kernel_data, sizeof(kernel_data)) != 0){
printk("copy_to_user fail!\r\n");
return -1;
}
else{
printk("copy_to_user succeed.\r\n");
}
printk("misc_read ok.\r\n");
return 0;
}
ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
if(copy_from_user(writebuf, ubuf, size) != 0){
printk("copy_from_user fail!\r\n");
return -1;
}
else{
printk("copy_from_user data:%s\r\n",writebuf);
}
printk("misc_write ok.\r\n");
return 0;
}
int misc_release(struct inode *inode,struct file *file)
{
printk("misc_release over.\r\n");
return 0;
}
misc_init和misc_exit是驱动的入、出口函数,调用module_init、module_exit来声明它们。 调用misc_register、misc_deregister函数对杂项设备注册和卸载。
(注意:因为驱动程序是运行在内核空间的,所以调试打印使用printk)。
static int misc_init(void)
{
if(misc_register(&misc_dev) < 0){
printk("misc_register failed!\r\n");
}
else{
printk("misc_register succeed.\r\n");
}
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk("misc_exit.\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
写完基本驱动后还需要使用MODULE_LICENSE()注明协议,否则可能会报错,也可以使用MODULE_AUTHOR()标明作者。
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Qurry");
将驱动程序编译成模块之前,需要先将linux源码编译通过。可以在对应驱动程序的目录下编写makefile,使用make命令将其编译,编译成功会生成 .ko 文件。
obj-m += miscdev.o #先写生成的中间文件的名字是什么,-m 的意思是把我们的驱动编译成模块
KDIR:=/home/workspace/linux_sdk/kernel #内核源码目录
PWD?=$(shell pwd) #获取当前目录路径的变量
all:
make -C $(KDIR) M=$(PWD) modules
#make 会进入内核源码的路径,然后把当前路径下的代码编译成模块
测试程序主要目的就是对前面注册的设备节点进行读写操作。
通过main函数传参,argv[1] 传递要打开的设备节点文件,argv[2] 传递读写操作,‘0’为读取文件信息,‘1’为向设备文件写入信息。最后将其编译成可以在目标板上执行的文件即可。
char *read_buf[64];
const char *user_data = "this is user data.";
int main(int argc,char *argv[])
{
int fd = -1;
char *filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0){
printf("open device file failed!\r\n");
return -1;
}
if(atoi(argv[2]) ==0){
read(fd,read_buf,sizeof(read_buf));
printf("read_buf:%s\r\n",read_buf);
}
else if(atoi(argv[2]) == 1){
write(fd,user_data,sizeof(user_data));
}
else{
printf("error!\r\n");
}
return 0;
}
测试文件通过nfs或者U盘拷贝到目标板上,使用u盘的话要先mount挂载u盘。
加载模块命令:insmod 模块名.ko
查看模块是否加载成功命令:lsmod
执行测试程序:./程序名 /dev/设备节点名 0(或者1)
卸载模块命令:rmmod 模块名.ko