(四)Linux設備驅動之多個同類設備共用一套驅動

本系列導航
(一)初識Linux驅動
(二)Linux設備驅動的模塊化編程
(三)寫一個完整的Linux驅動程序訪問硬件並寫應用程序進行測試
(四)Linux設備驅動之多個同類設備共用一套驅動
(五)Linux設備驅動模型介紹
(六)Linux驅動子系統-I2C子系統
(七)Linux驅動子系統-SPI子系統
(八)Linux驅動子系統-PWM子系統
(九)Linux驅動子系統-Light子系統
(十)Linux驅動子系統-背光子系統
(十一)Linux驅動-觸摸屏驅動

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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#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]並獲取到地址。
流程圖解析

編譯安裝驅動:
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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(char argc, char * argv[])
{
    int fd;
    int ret;
    char buf[64];
    
    if (argc != 2)
    {
        printf("Usage: %s <filename>\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!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章