(四)Linux设备驱动之多个同类设备共用一套驱动

本系列导航
(一)初识Linux驱动
(二)Linux设备驱动的模块化编程
(三)写一个完整的Linux驱动程序访问硬件并写应用程序进行测试
(四)Linux设备驱动之多个同类设备共用一套驱动
(五)Linux设备驱动模型介绍
(六)Linux驱动子系统-I2C子系统
(七)Linux驱动子系统-SPI子系统
(八)Linux驱动子系统-PWM子系统
(九)Linux驱动子系统-Light子系统
(十)Linux驱动子系统-背光子系统
(十一)Linux驱动-触摸屏驱动

文章目录

      • 1. 应用场景
      • 2. 如何区分不同的设备
      • 3. 代码实现
      • 4. 写应用程序进行测试 app.c

1. 应用场景

比如我们的设备上有很多一样的usb接口,这些usb接口都需要有驱动才能工作,那么是每个usb都一套单独的驱动程序么?显然不是的,这些usb接口属于同一类设备,用户对他们的操作方法完全一致,只不过不是同一个设备,所以他们可以复用同一套驱动代码,在代码中去判断用户要操作哪个设备,然后去open/read/write这个设备。

2. 如何区分不同的设备

前面说过,每个设备都有一个唯一的标识符–设备号,那么对于同一类设备,它们的主设备号是一样的,次设备号是不一样的,用来区分它们,当用户想要操作哪个具体的设备,就会打开这个设备对应的设备文件(inode结构体),并自动在内核中创建对应的file结构体,这个file结构体中就保存了用户操作的所有信息,最终会传给我们的内核驱动,驱动再根据这个file结构体和inode结构体来判断用户具体要操作的哪个设备,然后去read/write这个具体的设备。

案例:

hanp@hanp:/dev/usb$ ls -l
crw------- 1 root root 180, 0  311 17:29 hiddev0
crw------- 1 root root 180, 1  311 17:29 hiddev1

我的主机下面的两个usb设备,他们共用了一套usb驱动,但是他们的设备号是不一样的(180,0)和(180,1),主设备号都是180表示都属于同一类设备(usb设备),次设备号分别是0和1,表示这是两个不同的设备。

3. 代码实现

#include 
#include 
#include 
#include 
#include 
#include 

#define NUM_OF_DEVICES    2

int major = 255;

/* 两个设备,所以有两套结构体 */
/* 设备0对应的设备结构体是hello_dev[0], 设备1对应的设备结构体是hello_dev[1] */
struct hello_device {
    dev_t devno;
    struct cdev cdev;
    char data[128];
    char name[16];
}hello_dev[NUM_OF_DEVICES];

struct class * hello_class;

const char DEVNAME[] = "hello_device";

int hello_open(struct inode * ip, struct file * fp)
{
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 获取用户打开的设备对应的设备结构体 hello_dev[0] 或者 hello_dev[1] */
    struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);
    
    /* open的时候,通过container_of能够获取到用户要打开的那个设备的设备结构体,所有需要把这个结构体通过file指针的
     * private_data参数传递给read/write */
    fp->private_data = dev;
    
    /* 一般用来做初始化设备的操作 */
    /* ... */
    
    return 0;
}

int hello_close(struct inode * ip, struct file * fp)
{
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 一般用来做和open相反的操作,open申请资源,close释放资源 */
    /* ... */
    
    return 0;
}

ssize_t hello_read(struct file * fp, char __user * buf, size_t count, loff_t * loff)
{
    int ret;
    
    /* 通过file指针,获取到用户要操作的设备对应的设备结构体 */
    struct hello_device * dev = fp->private_data;
    
    /* 将用户需要的数据从内核空间copy到用户空间(buf) */
    printk("%s : %d\n", __func__, __LINE__);
    if (count <=0 || count > 128)
        count = 128;
    if ((ret = copy_to_user(buf, dev->data, count)))
    {
        printk("copy_to_user err\n");
        return -1;
    }
    
    return count;
}

ssize_t hello_write(struct file * fp, const char __user * buf, size_t count, loff_t * loff)
{
    int ret;
    struct hello_device * dev = fp->private_data;
    
    /* 将用户需要的数据从内核空间copy到用户空间(buf) */
    printk("%s : %d\n", __func__, __LINE__);
    if (count <=0 || count > 128)
        count = 128;
    if ((ret = copy_from_user(dev->data, buf, count)))
    {
        printk("copy_from_user err\n");
        return -1;
    }
    
    return count;
}
/* 2. 分配file_operations结构体 */
struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open  = hello_open,
    .release = hello_close,
    .read = hello_read,
    .write = hello_write
};

struct cdev cdev;

static int hello_init(void)
{
    int i;
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 1. 生成并注册两个设备的设备号 */
    /* 3. 分配、设置、注册两套cdev结构体 */
    for (i = 0; i < NUM_OF_DEVICES; i++)
    {
        hello_dev[i].devno = MKDEV(major, i);
        sprintf(hello_dev[i].name, "%s%d", DEVNAME, i);
        register_chrdev_region(hello_dev[i].devno, 1, hello_dev[i].name);
        
        hello_dev[i].cdev.owner = THIS_MODULE;
        cdev_add(&hello_dev[i].cdev, hello_dev[i].devno, 1);
        cdev_init(&hello_dev[i].cdev, &hello_fops);
        
        /* 初始化两个设备各自的存储空间 */
        sprintf(hello_dev[i].data, "Hi, I am hello device %d", i);
    }
    
    /* 在/sys/class目录下创建hello类,并在这个类下面创建hello_device0和hello_device1 */
    hello_class = class_create(THIS_MODULE, DEVNAME);
    for (i = 0; i < NUM_OF_DEVICES; i++)
    {
        device_create(hello_class, NULL, hello_dev[i].devno, NULL, "%s%d", DEVNAME, i);
        printk("success!\n");
    }
    return 0;
}

static void hello_exit(void)
{
    int i;
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 释放资源 */
    for (i = 0; i < NUM_OF_DEVICES; i++)
    {
        device_destroy(hello_class, hello_dev[i].devno);
        cdev_del(&hello_dev[i].cdev);
        unregister_chrdev_region(hello_dev[i].devno, 1);
    }
    class_destroy(hello_class);
}

MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

解释:

container_of:
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member)

功能:根据结构体中某个成员的地址,从而获取到整个结构体的首地址
@ptr: 已知结构体成员的地址
@type: 要获取的结构体的类型
@member: 已知结构体成员的名字
我们用到的实例解析:
struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);
文章最后的图解和上篇文章中我讲到file结构体和inode结构体的关系,其中inode结构体和文件系统下的文件是一一对应的关系,里面保存了这个字符设备对应的cdev结构体:
struct cdev * i_cdev,而这个cdev结构体又包含在设备结构体hello_device中,其中hello_dev[0]中包含的是设备0的cdev,hello_dev[1]中包含的是设备1的cdev,那么
container_of函数就可以根据这个cdev来判断用户打开的是hello_dev[0]还是hello_dev[1]并获取到地址。
(四)Linux设备驱动之多个同类设备共用一套驱动_第1张图片

编译安装驱动:
sudo insmod hello.ko

hanp@hanp:/dev$ ls hello_device*
hello_device0  hello_device1
hanp@hanp:/dev$ cat /proc/devices | grep hello
255 hello_device0
255 hello_device1

可以看到在/proc/devices下注册了两个设备hello_device0和hello_device1,这两个设备的主设备一样都是255,但是次设备号不一样(cat /dev/hello_deviceX可以查看次设备号)。

4. 写应用程序进行测试 app.c

#include 
#include 
#include 
#include 
#include 
#include 

int main(char argc, char * argv[])
{
    int fd;
    int ret;
    char buf[64];
    
    if (argc != 2)
    {
        printf("Usage: %s \n", argv[0]);
        return -1;
    }
    
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        perror("fail to open file");
        return -1;
    }
    
    /* read data */
    ret = read(fd, buf, sizeof(buf));
    if (ret < 0)
    {
        printf("read err!");
        return -1;
    }
    printf("buf = %s\n", buf);
    
    /* write data */
    strcpy(buf, "write data from app!");
    ret = write(fd, buf, sizeof(buf));
    if (ret < 0)
    {
        printf("write err!");
        return -1;
    }
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);
    
    close(fd);
    return 0;
}

测试:

$ gcc app.c
$ sudo ./a.out /dev/hello_device0
buf = Hi, I am hello device 0
buf = write data from app!
$ sudo ./a.out /dev/hello_device1
buf = Hi, I am hello device 1
buf = write data from app!

你可能感兴趣的:(linux,Linux驱动)