ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux驱动—实现一个驱动支持多个设备

2022-06-02 01:34:11  阅读:152  来源: 互联网

标签:cdev 多个 VSER dev static vser Linux 驱动 struct


前面内容:
1 Linux驱动—内核模块基本使用

2 Linux驱动—内核模块参数,依赖(进一步讨论)

3 字符设备驱动

4 虚拟串口设备驱动

Linux驱动—实现一个驱动支持多个设备

每个设备都写一个驱动太麻烦了,所以要Linux驱动—实现一个驱动支持多个设备。

对于多设备引入的变化:
我们首先要向 内核注册多个设备号
其次就是在添加cdev对象时指明改cdev对象管理了多个设备;
或者添加多个cdev对象,每个cdev对象管理一个设备。

接下来最麻烦的部分在于读写操作,因为设备是多个,那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。 在读写操作时,怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言,就是要确定对哪个FIFO进行操作) ?

观察读和写函数,没有发现能够区别设备的形参。
再观察open接口,我们会发现有一个inode 形参,通过前面的内容我们知道,inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。

因此,我们可以在open接口函数中取出这些信息(inode里面包含了对应设备的设备号以及所对应的cdev对象的地址),并存放在file结构对象的某个成员中,再在读写的接口函数中获取该file结构的成员,从而可以区分出对哪个设备进行操作。

下面首先展示用一个cdev实现对多个设备的支持

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"

static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);

static int vser_open(struct inode *inode, struct file *filp)
{
switch (MINOR(inode->i_rdev)) {
default:
case 0:
filp->private_data = &vsfifo0;
break;
case 1:
filp->private_data = &vsfifo1;
break;
}
return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct kfifo *vsfifo = filp->private_data;

kfifo_to_user(vsfifo, buf, count, &copied);

return copied;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct kfifo *vsfifo = filp->private_data;

kfifo_from_user(vsfifo, buf, count, &copied);

return copied;
}

static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};

static int __init vser_init(void)
{
int ret;
dev_t dev;

dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;

cdev_init(&vsdev, &vser_ops);
vsdev.owner = THIS_MODULE;

ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;

return 0;

add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}

static void __exit vser_exit(void)
{

dev_t dev;

dev = MKDEV(VSER_MAJOR, VSER_MINOR);

cdev_del(&vsdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");

来分析下代码:

这里把CNT改成2 说明支持2个设备

static struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);
1
2
3
定义了二个FIFO,vsfifo0和1 (这里用的是动态分配fifo要更好,但是后面会涉及内存分配的知识,所以先用静态的)

在open接口函数中根据次设备号的值来确定保存哪个FIFO结构的(vsfifo0还是1)地址到file结构中的private_data 的值,即FIFO结构的地址

static int vser_open(struct inode *inode, struct file *filp)
{
switch (MINOR(inode->i_rdev)) {
default:
case 0:
filp->private_data = &vsfifo0;
break;
case 1:
filp->private_data = &vsfifo1;
break;
}
return 0;
}


次设备号 0 选择存 vsfifo0
次设备号 1 选择存 vsfifo1

接下来演示如何将每一个cdev对象对应到一个设备来实现一个驱动对多个设备的支持

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME "vser"

static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);

struct vser_dev {
struct kfifo *fifo;
struct cdev cdev;
};

static struct vser_dev vsdev[2];

static int vser_open(struct inode *inode, struct file *filp)
{
filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct vser_dev *dev = filp->private_data;

kfifo_to_user(dev->fifo, buf, count, &copied);

return copied;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
unsigned int copied = 0;
struct vser_dev *dev = filp->private_data;

kfifo_from_user(dev->fifo, buf, count, &copied);

return copied;
}

static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};

static int __init vser_init(void)
{
int i;
int ret;
dev_t dev;

dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;

for (i = 0; i < VSER_DEV_CNT; i++) {
cdev_init(&vsdev[i].cdev, &vser_ops);
vsdev[i].cdev.owner = THIS_MODULE;
vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;

ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
if (ret)
goto add_err;
}

return 0;

add_err:
for (--i; i > 0; --i)
cdev_del(&vsdev[i].cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}

static void __exit vser_exit(void)
{
int i;
dev_t dev;

dev = MKDEV(VSER_MAJOR, VSER_MINOR);

for (i = 0; i < VSER_DEV_CNT; i++)
cdev_del(&vsdev[i].cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_ALIAS("virtual-serial");

代码:

struct vser_dev {
struct kfifo *fifo;
struct cdev cdev;
};
1
2
3
4
这里定义了一个结构类型 vser_dev,代表一种具体的设备类

通常和设备有关的内容都可以跟cdev一起定义到一个结构中这样更容易

cdev 是所有字符设备的一个抽象,是一个基类,而一个具体类型的设备应该是由该基类派生出来的一个子类,子类包含了特定设备所特有的属性,比如vser_ dev 中的fifo,这样子类就更能刻画好一类具体的设备。

代码

static struct vser_dev vsdev[2];
1
创建了两个vser_dev 类型的对象,和C++不同的是,创建这两个对象仅仅是为其分配了内存,并没有调用构造函数来初始化这两个对象,但在代码的第74行到第77行完成了这个操作。

for (i = 0; i < VSER_DEV_CNT; i++) {
cdev_init(&vsdev[i].cdev, &vser_ops);
vsdev[i].cdev.owner = THIS_MODULE;
vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
1
2
3
4
你看,初始化init cdev

查看内核源码,会发现这种面向对象的思想处处可见,只能说因为语言的特性,并没有把这种形式体现得很明显而已。

代码的第74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作,并且初始化了fifo 成员的指向。

for (i = 0; i < VSER_DEV_CNT; i++) {
cdev_init(&vsdev[i].cdev, &vser_ops);
vsdev[i].cdev.owner = THIS_MODULE;
vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;

ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
if (ret)
goto add_err;
}

如果i为0是第一个,指向vsfifo0这个结构的地址
如果i为1是第二个,指向vsfifo1这个结构的地址

这里需要说明的是,用DEFINE_ KFIFO 定义的FIFO,每定义一个FIFO就会新定义一种数据类型,所以严格来说vsfifo0和vsfifo1是两种不同类型的对象,但好在这里能和struct kfifo类型兼容。

代码第26行

filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
1
用到了一个container_ of 宏,这是在Linux内核中设计得非常巧妙的一个宏,在整个Linux内核源码中几乎随处可见。

它的作用就是**根据结构成员的地址来反向得到结构的起始地址**

在代码中, inode->i_cdev 给出了struct vser_dev 结构类型中cdev成员的地址(见图3.2),通过container_ of宏就得到了包含该cdev的结构地址。


使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。



标签:cdev,多个,VSER,dev,static,vser,Linux,驱动,struct
来源: https://www.cnblogs.com/wanghuaijun/p/16336302.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有