本文主要记录一下使用单一驱动,控制多个设备,且每个设备拥有私有数据。
使每个设备拥有私有数据的主要思路为:在驱动侧 open 函数中,读取 inode 的设备号,通过次设备号,从而判断打开的是哪一个设备,从而将其对应的私有数据传递给 file 结构体。在 write/read 函数中就可以通过 file->private_data 获取到本设备的私有数据。
驱动源码
./Driver/chrdevs.h
#ifndef __CHRDEVS_H__
#define __CHRDEVS_H__
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/string.h>
#define NEWCHRDEV_CNT 3 //要申请的设备号个数
#define CHRDEVBASE_NAME "chrdev" //设备class名
#define BASEMINOR 0 //次设备号偏移
struct chrdev;
struct dataType;
struct chrdev{
dev_t base_devid; //该类设备的基设备号
struct cdev cdev; //cdev
struct class *class; //class
struct device *device_list[NEWCHRDEV_CNT]; //多个设备
struct device *parent; //父设备
struct dataType *drvdata[NEWCHRDEV_CNT]; //设备私有数据
char *drvname[NEWCHRDEV_CNT]; //设备名
};
struct dataType{
int num;
char name[20];
float score;
};
static int chrdev_open(struct inode *inode, struct file *filp);
static ssize_t chrdev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt);
static ssize_t chrdev_write(struct file *filp, const char *buf, size_t cnt, loff_t *offt);
static int chrdev_release(struct inode *inode, struct file *filp);
static int __init chrdev_init(void);
static void __exit chrdev_exit(void);
#endif
./Driver/chrdevs.c
/*
* file name : chrdevs.c
* description : 一个驱动,管理多个设备,每个设备拥有独立的私有数据
* author : 今朝无言
* date : 2022.9.27
*/
#include "chrdevs.h"
struct dataType d1 = {
.num = 202201,
.name = "Zhang San",
.score = 78
};
struct dataType d2 = {
.num = 202202,
.name = "Li Si",
.score = 69
};
struct dataType d3 = {
.num = 202203,
.name = "Wang Wu",
.score = 92
};
struct chrdev chrdev_inst = {
.drvname = {
"chrdev1","chrdev2","chrdev3"}, //设备名,必须不同
.drvdata = {
&d1, &d2, &d3} //私有数据
};
static char readbuf[100]; //读缓冲区
static char writebuf[100]; //写缓冲区
static int chrdev_open(struct inode *inode, struct file *filp){
printk("chrdev open!%d:%d\n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
//inode->i_rdev 为该 inode 的设备号
struct dataType* data = (struct dataType*)dev_get_drvdata(
chrdev_inst.device_list[MINOR(inode->i_rdev)-BASEMINOR]); //根据次设备号,判断打开的是哪一个设备,将其私有数据传递给filp
filp->private_data = (void*)data;
return 0;
}
static ssize_t chrdev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
int retvalue = 0;
struct dataType* data = (struct dataType*)(filp->private_data);
memcpy(readbuf, data->name, sizeof(data->name)); //将设备的私有数据传递给用户
retvalue = copy_to_user(buf, readbuf, cnt);
if(retvalue == 0){
printk("kernel send data ok!\n");
}
else {
printk("kernel send data failed!\n");
}
return 0;
}
static ssize_t chrdev_write(struct file *filp, const char *buf, size_t cnt, loff_t *offt){
int retvalue = copy_from_user(writebuf, buf, cnt);
retvalue = copy_from_user(writebuf, buf, cnt);
if(retvalue == 0){
printk("kernel receive data: %s \n",writebuf);
}
else {
printk("kernel receive data failed!\n");
}
return 0;
}
static int chrdev_release(struct inode *inode, struct file *filp){
//printk("chrdev release! \n");
return 0;
}
static struct file_operations chrdev_fops = {
.owner = THIS_MODULE,
.open = chrdev_open,
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release
};
static int __init chrdev_init(void){
//申请设备号
alloc_chrdev_region(&chrdev_inst.base_devid, BASEMINOR, NEWCHRDEV_CNT, CHRDEVBASE_NAME);
//初始化 cdev
chrdev_inst.cdev.owner = THIS_MODULE;
cdev_init(&chrdev_inst.cdev, &chrdev_fops);
//添加 cdev
cdev_add(&chrdev_inst.cdev, chrdev_inst.base_devid, NEWCHRDEV_CNT);
//创建 class
chrdev_inst.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_inst.class)){
return PTR_ERR(chrdev_inst.class);
}
//动态创建设备
chrdev_inst.parent = NULL;
int i;
for(i=0; i<NEWCHRDEV_CNT; i++){
chrdev_inst.device_list[i] = device_create(chrdev_inst.class, chrdev_inst.parent,
chrdev_inst.base_devid+i, (void*)chrdev_inst.drvdata[i], chrdev_inst.drvname[i]);
}
printk("chrdev init...\n");
return 0;
}
static void __exit chrdev_exit(void){
//注销cdev
cdev_del(&chrdev_inst.cdev);
//释放设备号
unregister_chrdev_region(chrdev_inst.base_devid, NEWCHRDEV_CNT);
//注销设备
int i;
for(i=0; i<NEWCHRDEV_CNT; i++){
device_destroy(chrdev_inst.class, chrdev_inst.base_devid+i);
}
//注销class
class_destroy(chrdev_inst.class);
printk("chrdev exit...\n");
return;
}
//指定驱动入口和出口函数
module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("今朝无言");
./Driver/makefile
KERNELDIR := /lib/modules/`uname -r`/build
CURRENT_PATH := $(shell pwd)
obj-m := chrdevs.o
chrdevs.ko: chrdevs.c chrdevs.h
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean: rm_ko
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
load_ko: chrdevs.ko
cp $< /lib/modules/$(shell uname -r)/$<
depmod
modprobe $(basename $<)
rm_ko:
-rmmod chrdevs.ko
# load/rm_ko 记得给予 sudo 权限
test_ko:
make -C ../App/ test
# dmesg | tail -6
用户测试代码
./App/chrdevbaseAPP.c
/*
* file name : chrdevbaseAPP.c
* description : chrdevbase驱动的测试程序
* author : 今朝无言
* date : 2022.9.27
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]){
int fd, retvalue;
char *filename;
char readbuf[100], writebuf[100]; //读写缓冲
//检查参数
if(argc != 2){
printf("Error Usage!\n");
return -1;
}
filename = argv[1];
//打开驱动文件
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s!\n", filename);
return -2;
}
//从驱动文件读取数据
retvalue = read(fd, readbuf, 50);
if(retvalue < 0){
printf("read file %s failed!\n", filename);
}
else {
printf("read data: %s\n", readbuf);
}
//向驱动写数据
memcpy(writebuf, readbuf, sizeof(readbuf)); //回传读到的数据
retvalue = write(fd, writebuf, 50);
if(retvalue < 0){
printf("write file %s failed!\n", filename);
}
else {
printf("write file success!\n");
}
//关闭设备
retvalue = close(fd);
if(retvalue < 0){
printf("Can't close file %s!\n", filename);
return -3;
}
return 0;
}
./App/makefile
build:
gcc chrdevbaseAPP.c -o chrdevbaseAPP
clean:
rm chrdevbaseAPP
test: chrdevbaseAPP
./$< /dev/chrdev1
./$< /dev/chrdev2
测试
首先在 ./App/ 中进行编译,获得测试程序 chrdevbaseAPP
在 ./Driver/ 中执行
sudo make load_ko
进行编译以及驱动加载,然后执行
sudo make test_ko
进行测试,测试结果如下
可以看到用户顺利读到了设备的私有数据,并将数据进行了回传。
查看设备,可以看到一个驱动创建并管理了多个驱动设备
几个设备的主设备相同(243),次设备号分别为 0、1、2 。