T10 字符設備驅動補充

1 linux字符驅動設備開發流程

1.1 流程

  • 封裝本身的字符設備結構體類
  • 實現struct file_operations結構體
  • 模塊的入口函數
  • 實例化本身的字符設備結構體類(實例化對象),使用kmalloc
  • 初始化通用字符設備(cdev_init
/*
功能:初始化字符設備
參數:
	@cdev:cdev結構體
	@fops:操做字符設備的函數接口地址
返回值:無
*/
void cdev_init(struct cdev *cdev, const struct file_operation *fops)
  • 申請設備號(register_chrdev_region
/*
功能:註冊設備號
參數:
	@from:設備號
	@count:註冊的設備個數
	@name:設備名字
返回值:註冊成功返回0,失敗返回錯誤碼(負數)
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
  • 添加字符設備到操做系統(cdev_add
/*
功能:添加一個字符設備驅動到操做系統
參數:
	@p:cdev結構體地址
	@dev:設備號
	@count:次設備號個數
返回值:註冊成功返回0,失敗返回錯誤碼(負數)
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  • 模塊的出口函數
  • 釋放給字符設備類分配的空間(kfree
  • 移除註冊的字符設備(cdev_del
/*
功能:從系統中刪除一個字符設備驅動
參數:
	@p:cdev結構體地址
返回值:無
*/
void cdev_del(struct cdev *p)
  • 釋放申請的設備號(unregister_chrdev_region
/*
功能:釋放申請的設備號
參數:
	@from:設備號
	@count:次設備號個數
返回值:無
*/
void unregister_chrdev_region(dev_t from, unsigned count)

1.2 代碼

  • Makefile文件
obj-m := cdev_test.o
KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
  • 源文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    dev_t dev_no;                           //device major number
    char buffer[BUF_SIZR];                  //my private memory
    struct cdev cdev;                       //char device class
};

/* step1: malloc memory for char device */
static struct demo_cdev demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* step2: init for demo char device*/
    cdev_init(&demo_dev.cdev, &demo_operation);
    /* step3: apply a major device number for char device */
    demo_dev.dev_no = MKDEV(MAJOR_NUM, MINOR_NUM);
    /* step4: register char device number */ 
    ret = register_chrdev_region(demo_dev.dev_no, 1, "demo_chrdev");
    if (ret < 0) {
        printk(KERN_ERR "faild to register device number\n");
        return ret;
    }
    /* step4: add char device to OS */
    ret = cdev_add(&demo_dev.cdev, demo_dev.dev_no, 1);
    if (ret < 0) {
        printk(KERN_ERR "cdev add failed\n");
        /* failed to add cdev, we should unregister cdev before return*/
        unregister_chrdev_region(demo_dev.dev_no, 1);
        return ret;
    }
    printk(KERN_INFO "demo char device init done\n");
    return 0;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    cdev_del(&demo_dev.cdev);
    unregister_chrdev_region(demo_dev.dev_no, 1);
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");

1.3 裝載驅動

  • 建立設備節點(命令方式)
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo mknod /dev/cdev_test c 168 0
  • 查看節點是否建立成功
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -l /dev/cdev_test 
crw-r--r-- 1 root root 168, 0 3月   7 15:53 /dev/cdev_test
  • 裝載驅動
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test.ko
  • 查看信息
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg 
[24936.071335] Enter: demo_init
[24936.071337] demo char device init done
  • 測試file_operation功能
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[24936.071335] Enter: demo_init
[24936.071337] demo char device init done
[25542.812769] Enter: demo_open
[25542.812777] Enter: demo_read
[25542.812784] Enter: demo_release

1.4 改進

  • 不足
  • 不足1:上述驅動中,在註冊設備號的時候,咱們是直接指定一個特定的主設備號,若是系統中已經存在這個設備號,那麼將會致使驅動註冊失敗
  • 改進1:當指定的設備號註冊失敗的時候,咱們能夠採用動態註冊,讓linux系統給咱們分配一個未使用過的設備號
/*
功能:讓系統自動分配一個設備號,以後再註冊設備
參數:
	@dev:用來獲取系統分配的設備號
	@baseminor:第一個次設備號
	@count:次設備號的個數
	@name:設備名稱
返回值:成功返回0,失敗返回錯誤碼
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

/*
功能:建立一個類
參數:
	@owner:通常寫爲THIS_MODULE
	@name:字符串名字,本身寫
返回值:class結構體類型指針,可經過IS_ERR(cls)判斷是否建立失敗,成功返回0,失敗返回非0(可經過PTR_ERR(cls)來獲取失敗的返回碼)
*/
struct class *class_create(owner, name);

/*
功能:建立一個設備文件
參數:
	@class:class_create建立的class結構體類型指針
	@parent:父親,通常直接寫NULL
	@devt:設備號,經過MDKEV()宏建立
	@drvdata:私有數據,通常寫NULL
	@fmt:可變參數,通常爲字符串,表示設備節點名字
返回值:device類型指針
*/
struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, ...);
  • 不足2:每次裝載完驅動以後都須要手動去建立設備節點,麻煩node

  • 改進2:當咱們註冊完設備以後,讓linux系統自動在/dev目錄下建立設備節點linux

  • 不足3:字符設備讀寫函數接口沒有實際數據流向程序員

  • 改進3:使用xxx_read函數將設備裏面的內容拷貝到用戶空間;使用xxx_write函數將用戶空間的數據寫入設備內部buffershell

/*
功能:從用戶空間拷貝數據到內核空間(大量數據)
參數:
	@to:內核空間地址
	@from:用戶空間地址
	@n:數據大小
返回值:成功返回0,失敗返回未被拷貝的字節數
注意:此處__user是一個空的宏,主要用來顯示的告訴程序員它修飾的指針變量存放的是用戶空間的地址,這是一個好習慣
*/
int copy_from_user(void *to, const void __user *from, int n)
    
/*
功能:從內核空間拷貝數據到用戶空間(大量數據)
參數:
	@to:用戶空間地址
	@from:內核空間地址
	@n:數據大小
返回值:成功返回0,失敗返回未被拷貝的字節數
*/
int copy_to_user(void __user *to, const void *from, int n)

/*
宏功能:檢查用戶空間傳遞到內核空間的地址是否合法
參數:
	@type:VERIFY_READ:用戶空間的地址是否可讀	VERIFY_WRITE:用戶空間的地址是否可寫
	@addr:用戶空間傳遞的地址
	@szie:檢查的空間大小
返回值:成功返回非0,失敗返回0
*/
access_ok(type, addr, size)
    
/*
宏功能:拷貝單個數據到用戶空間
參數:
	@x:須要拷貝到用戶空間的地址
	@ptr:用戶空間的地址
返回值:成功返回0,失敗返回-EFAULT
注意:此宏使用前須要使用access_ok宏檢查ptr是否有效
*/
put_user(x, ptr)	/	__put_user(x, ptr)
    
/*
宏功能:拷貝單個數據到內核空間
參數:
	@x:須要拷貝到用戶空間的地址
	@ptr:用戶空間的地址
返回值:成功返回0,失敗返回-EFAULT
注意:此宏使用前須要使用access_ok宏檢查ptr是否有效
*/
get_user(x, ptr)	/	__get_user(x, ptr)
  • 不足4:設備的空間是在data段分配的,假如設備內部buff過大,將佔用data段很大一部份內存
  • 改進4:分配堆內存給設備
/*
功能:分配內存
參數:
	@size:內存大小
	@flags:經常使用GFP_KERNEL
返回值:成功返回分配的內存首地址,失敗返回NULL
*/
static __always_inline void *kmalloc(size_t size, gfp_t flags)
  • 內核讀寫函數API
/*
功能:對應用戶空間的read,將內核空間數據發送給用戶空間
參數:
	@filp:file結構體指針,裏面包含文件描述符fd
	@buff:用戶空間地址,用來保存內核發送過去的數據
	@count:用戶空間要讀的數據長度
	@fops:用戶空間讀取數據的當前偏移量
注意:關於fops的一些解釋
		假設用戶空間調用read(fd, buf, 10)函數,此時內核空間一共給用戶空間發送1024個字節,可是用戶空間此刻只讀	取了10個字節,故此刻偏移量爲10,那麼下次用戶再次讀取10字節的時候就須要從11的偏移量開始。
		1.因此在編寫內核的read函數時候,咱們須要先判斷當前偏移量是否已經超過了內核發送給用戶數據的最大值,此處不	  能大於等於1024。
		2.由於用戶是按10字節爲單位進行讀取,那麼讀到最後確定不是10的倍數,假設此時內核還剩餘4字節數據沒被用戶讀    取,那麼此刻用戶再讀10字節的時候內核就應該只返回4字節數據
*/
ssize_t xxx_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)

/*
功能:
*/
ssize_t xxx_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
  • 有關於struct file結構體描述,該結構體描述以下,其中有一個變量void *private_data,這個私有數據能夠用來保存咱們的私有變量,因此咱們能夠將咱們建立的驅動保存到這個變量下面,當使用xxx_readxxx_write等函數的時候就能夠經過private來訪問咱們驅動內部的數據
struct file {
    ...
	/* needed for tty driver, and maybe others */
	void			*private_data;
	...
}

/*用法示例*/
struct demo_cdev {
    dev_t dev_no;                           //device major number
    char *buffer;                           //my private memory
    struct cdev cdev;                       //char device class
    struct class *cls;
    struct device *device;
};

struct demo_cdev *demo_dev;

static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
}

static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
}
  • 改進後代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    dev_t dev_no;                           //device major number
    char *buffer;                           //my private memory
    struct cdev cdev;                       //char device class
    struct class *cls;
    struct device *device;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
};

static int __init demo_init(void)
{
    int ret = -1;
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* step2: init for demo char device*/
    cdev_init(&demo_dev->cdev, &demo_operation);
    /* step3: apply a major device number for char device */
    demo_dev->dev_no = MKDEV(MAJOR_NUM, MINOR_NUM);
    /* step4: register char device number */ 
    ret = register_chrdev_region(demo_dev->dev_no, 1, "demo_chrdev");
    if (ret < 0) {
        /* if we faild to register c_dev number,we can let OS to register cdev number */
        ret = alloc_chrdev_region(&demo_dev->dev_no, 0, 1, "demo_chrdev");
        if (ret < 0) {
            printk(KERN_ERR "faild to register device number\n");
            goto ERROR_CHRDEV_REGION;
        }
    }
    /* step5: add char device to OS */
    ret = cdev_add(&demo_dev->cdev, demo_dev->dev_no, 1);
    if (ret < 0) {
        printk(KERN_ERR "cdev add failed\n");
        /* failed to add cdev, we should unregister cdev before return*/
        goto ERROR_CDEV_ADD;
    }
    /* create a cdev class in /sys/class/cdev_test/ */
    demo_dev->cls = class_create(THIS_MODULE, "cdev_test");
    if (IS_ERR(demo_dev->cls)) {
        ret = PTR_ERR(demo_dev->cls);
        goto ERR_CLASS_CREATE;
    }
    /* create a cdev device in /sys/class/cdev_test/cdev_mem */
    demo_dev->device = device_create(demo_dev->cls, NULL, demo_dev->dev_no, NULL, "cdev_mem");
    if (IS_ERR(demo_dev->device)) {
        ret = PTR_ERR(demo_dev->device);
        goto ERROR_DEVICE_CREATE;
    }
    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_DEVICE_CREATE:
    class_destroy(demo_dev->cls);
ERR_CLASS_CREATE:
    cdev_del(&demo_dev->cdev);
ERROR_CDEV_ADD:
    unregister_chrdev_region(demo_dev->dev_no, 1);
ERROR_CHRDEV_REGION:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    cdev_del(&demo_dev->cdev);
    unregister_chrdev_region(demo_dev->dev_no, 1);

    kfree(demo_dev);
    demo_dev = NULL;
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 調試
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls
cdev_test_v1.c  cdev_test_v3.c   cdev_test_v3.mod    cdev_test_v3.mod.o  Makefile       Module.symvers
cdev_test_v2.c  cdev_test_v3.ko  cdev_test_v3.mod.c  cdev_test_v3.o      modules.order
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test_v3.ko 
[sudo] hq 的密碼: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -al /dev/cdev_mem 
crw------- 1 root root 168, 0 3月   8 15:48 /dev/cdev_mem
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/cdev_mem 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hq" -> /dev/cdev_mem 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem 
hq -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hanqi" -> /dev/cdev_mem 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem 
hanqi -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$

2 ioctl

2.1 定義

ioctl是設備驅動程序中設備控制接口函數,一個字符設備驅動一般會實現設備打開、關閉、讀、寫等功能,在一些須要細分的情境下,若是須要擴展新的功能,一般以增設 ioctl() 命令的方式實現。api

2.2 相關函數

  • 用戶層
#include <sys/ioctl.h> 

/*
功能:實現IO控制
參數:
	@fd:文件描述符
	@cmd:交互協議,設備驅動將根據 cmd 執行對應操做
	@...:可變參數 arg,依賴 cmd 指定長度以及類型
返回值:執行成功時返回0,失敗則返回-1並設置全局變量errorno值
*/
int ioctl(int fd, int cmd, ...) ;
  • 內核層:當經過ioctl調用xxx_ioctl時候有3種狀況
  • 不傳遞參數給xxx_ioctl
  • 傳遞參數給xxx_ioctl,並但願能將指令寫入設備(如設置串口波特率)
  • 調用xxx_ioctl函數獲取硬件信息(如獲取當前串口波特率)
/*
功能:unlocked_ioctl,在無大內核鎖(BKL)的狀況下調用;compat_ioctl,compat 全稱 compatible(兼容的),主要目的是爲 64 位系統提供 32 位 ioctl 的兼容方法,也是在無大內核鎖的狀況下調用
參數:
	@file:file結構體,其中保存了文件描述符
	@cmd:用戶空間傳遞給內核的指令
	@arg:可變參數,可供用戶空間與內核空間之間傳參
*/
long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);

2.3 協議

  • ioctl函數中的cmd不該該隨意設置,須要作到大統一,好linux爲咱們提供了一種 ioctl 命令的統一格式,將 32 位 int 型數據劃分爲四個位段
| 設備類型 | 序列號 | 方向 |數據尺寸|
|----------|--------|-------|-----------|
| 8 bit     | 8 bit  |2 bit |8~14 bit|
|----------|--------|-------|-----------|
  • 設備類型:表明一類設備,通常用一個字母或者一個8bit的數字表示。在一些文獻中翻譯爲 「幻數」 或者 「魔數」,能夠爲任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要做用是使 ioctl 命令有惟一的設備標識;
  • 序列號:表示這個設備的第幾個命令
  • 方向:這個命令用來操做的方式,如:只讀,只寫,讀寫或者其餘; _IOC_NONE(無數據)、_IOC_READ(讀數據)、_IOC_WRITE(寫數據)、_IOC_READ | _IOC_WRITE(讀寫數據)
  • 數據尺寸:即用戶傳遞的數據大小
  • 貌似挺麻煩,不過linux系統已經爲咱們封裝好系列宏,故咱們只須要調用宏來設計命令而不用本身一個位一個位地去設計,只須要指定設備類型、命令序列、數據類型三個字段便可
/*生成命令*/
/*
@type:設備類型
@nr:命令序號
@size:用戶傳遞的數據類型(int,char,struct name...)
*/
#define _IO(type,nr)      		//沒有數據傳遞的命令  
#define _IOR(type,nr,size)  	//從驅動中讀取數據
#define _IOW(type,nr,size)  	//向驅動中寫入數據
#define _IOWR(type,nr,size) 	//雙向傳輸數據
-----------------------------------------------------------------------------------------------------
_IOC_NONE					  //值爲0,表示無數據傳輸
IOC_READ					  //值爲1,表示從驅動中讀取數據
_IOC_WRITE					  //值爲2,向驅動中寫入數據
IOC_READ | _IOC_WRITE           //雙向數據傳輸
-----------------------------------------------------------------------------------------------------
/*解析命令*/
#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)		//從命令中提取數據流向
#define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)		//從命令中提取設備類型
#define _IOC_NR(nr)     (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)			    ///從命令中提取序列號
#define _IOC_SIZE(nr)       (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)		///從命令中提取數據大小
  • 命令生成示例
#define DEV_FIFO_TYPE		'k'
#define DEV_FIFO_CLEAN		_IO(DEV_FIFO_TYPE, 0x10)
#define DEV_FIFO_GETVALUE	_IOR(DEV_FIFO_TYPE, 0x11, int)
#define DEV_FIFO_SETVALUE	_IOW(DEV_FIFO_TYPE, 0x12, int)

2.4 代碼

  • 驅動代碼頭文件cdev_test_v4.h
#ifndef __CDEV_TEST_V4_H__
#define __CDEV_TEST_V4_H__

#include <linux/ioctl.h>

#define CDEV_TEST_V4_TYPE               'h'
#define CDEV_TEST_V4_CLEAN              _IO(CDEV_TEST_V4_TYPE, 0x10)
#define CDEV_TEST_V4_SETVAL             _IOW(CDEV_TEST_V4_TYPE, 0x11, int)
#define CDEV_TEST_V4_GETVAL             _IOR(CDEV_TEST_V4_TYPE, 0x12, int)

#endif
  • 驅動代碼源文件cdev_test_v4.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    dev_t dev_no;                           //device major number
    char *buffer;                           //my private memory
    int value;
    struct cdev cdev;                       //char device class
    struct class *cls;
    struct device *device;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static int __init demo_init(void)
{
    int ret = -1;
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* step2: init for demo char device*/
    cdev_init(&demo_dev->cdev, &demo_operation);
    /* step3: apply a major device number for char device */
    demo_dev->dev_no = MKDEV(MAJOR_NUM, MINOR_NUM);
    /* step4: register char device number */ 
    ret = register_chrdev_region(demo_dev->dev_no, 1, "demo_chrdev");
    if (ret < 0) {
        /* if we faild to register c_dev number,we can let OS to register cdev number */
        ret = alloc_chrdev_region(&demo_dev->dev_no, 0, 1, "demo_chrdev");
        if (ret < 0) {
            printk(KERN_ERR "faild to register device number\n");
            goto ERROR_CHRDEV_REGION;
        }
    }
    /* step5: add char device to OS */
    ret = cdev_add(&demo_dev->cdev, demo_dev->dev_no, 1);
    if (ret < 0) {
        printk(KERN_ERR "cdev add failed\n");
        /* failed to add cdev, we should unregister cdev before return*/
        goto ERROR_CDEV_ADD;
    }
    /* create a cdev class in /sys/class/cdev_test/ */
    demo_dev->cls = class_create(THIS_MODULE, "cdev_test");
    if (IS_ERR(demo_dev->cls)) {
        ret = PTR_ERR(demo_dev->cls);
        goto ERR_CLASS_CREATE;
    }
    /* create a cdev device in /sys/class/cdev_test/cdev_mem */
    demo_dev->device = device_create(demo_dev->cls, NULL, demo_dev->dev_no, NULL, "cdev_mem");
    if (IS_ERR(demo_dev->device)) {
        ret = PTR_ERR(demo_dev->device);
        goto ERROR_DEVICE_CREATE;
    }
    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_DEVICE_CREATE:
    class_destroy(demo_dev->cls);
ERR_CLASS_CREATE:
    cdev_del(&demo_dev->cdev);
ERROR_CDEV_ADD:
    unregister_chrdev_region(demo_dev->dev_no, 1);
ERROR_CHRDEV_REGION:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    device_destroy(demo_dev->cls, MKDEV(MAJOR_NUM, MINOR_NUM));
    class_destroy(demo_dev->cls);
    cdev_del(&demo_dev->cdev);
    unregister_chrdev_region(demo_dev->dev_no, 1);
    
    kferr(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 用戶層測試源文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#include "cdev_test_v4.h"


#define DEVICE_PATH             "/dev/cdev_mem"

int main(void)
{
    int fd;
    int ret;
    int n;
    int value;
    char buf[10] = "hanqi";

    fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        printf("failed to open\n");
        return -1;
    }

    n = write(fd, buf, sizeof(buf));
    if (n < 0) {
        printf("failed to write\n");
        close(fd);
        return -1;
    }
    printf("write to cdev %d bytes\n", n);

    ret = ioctl(fd, CDEV_TEST_V4_GETVAL, &value);
    if (ret < 0) {
        printf("failed to ioctl\n");
        close(fd);
        return -1;
    }
    printf("value = %d\n", value);

    ret = ioctl(fd, CDEV_TEST_V4_SETVAL, 520);
    if (ret < 0) {
        printf("failed to ioctl\n");
        close(fd);
        return -1;
    }

    ret = ioctl(fd, CDEV_TEST_V4_GETVAL, &value);
    if (ret < 0) {
        printf("failed to ioctl\n");
        close(fd);
        return -1;
    }
    printf("value = %d\n", value);

    close(fd);
    return 0;
}
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test_v4.ko 
[sudo] hq 的密碼: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/cdev_mem 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ gcc test_cdev4.h -o test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test 
write to cdev 10 bytes
value = 1
value = 520
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem 
hanqi
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[ 1970.376549] Enter: demo_init
[ 1970.376612] demo char device init done
[ 3117.086360] Enter: demo_open
[ 3117.086368] Enter: demo_write
[ 3117.086515] cmd: getval
[ 3117.086521] cmd: setval
[ 3117.086522] cmd: getval
[ 3117.086527] Enter: demo_release
[ 3263.306387] Enter: demo_open
[ 3263.306403] Enter: demo_read
[ 3263.306523] Enter: demo_read
[ 3263.306536] Enter: demo_release

3 Misc設備驅動

上述過程建立一個字符設備驅動流程比較複雜,爲此引入了Misc設備驅動,簡化字符設備驅動的建立流程。其主設備號是固定的爲10bash

  • Misc設備驅動結構體
struct miscdevice {
    int minor;							    //次設備號,若爲MISC_DYNAMIC_MINOR就自動分配
    const char *name;						//設備名
    const struct file_operations *fops;       //設備文件操做結構體
    struct list_head list;			         //misc_list鏈表頭
    struct device *parent;
    struct device *this_device;
    const char *nodename;
    mode_t mode; 
};

3.1 API

  • 註冊/卸載Misc設備驅動
/* 在加載模塊的時候會自動建立設備文件,其主設備號爲10 */
int misc_register(struct miscdevice* misc);

/* 在卸載模塊的時候自動刪除設備文件 */
int misc_dergister(struct miscdevice* misc);
  • 結構體初始化
static struct miscdevice misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = DEVICE_NAME,
    .fops = &dev_fops,
}

3.2 代碼

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");

3.3 測試

hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod misc_dev.ko 
[sudo] hq 的密碼: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -al /dev/misc_dev 
crw------- 1 root root 10, 56 3月  10 12:12 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hq666" -> /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
hq666 -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test 
write to cdev 10 bytes
value = 1
value = 520
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[ 4161.949170] Enter: demo_init
[ 4161.951759] demo char device init done
[ 4213.359167] Enter: demo_open
[ 4213.359182] Enter: demo_write
[ 4213.359187] Enter: demo_release
[ 4220.892937] Enter: demo_open
[ 4220.892945] Enter: demo_read
[ 4220.892974] Enter: demo_read
[ 4220.892983] Enter: demo_release
[ 4394.397552] Enter: demo_open
[ 4394.397556] Enter: demo_write
[ 4394.397648] cmd: getval
[ 4394.397651] cmd: setval
[ 4394.397652] cmd: getval
[ 4394.397656] Enter: demo_release
  • 咱們能夠進入/sys/class文件夾下面查看misc設備,其中就包含了咱們剛纔建立的設備
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cd /sys
hq@hq-virtual-machine:/sys$ ls
block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power
hq@hq-virtual-machine:/sys$ cd class/
hq@hq-virtual-machine:/sys/class$ ls
ata_device  devcoredump     extcon       input          mmc_host      ppdev         regulator     sound          tty
ata_link    devfreq         firmware     intel_scu_ipc  nd            ppp           remoteproc    spi_host       vc
ata_port    devfreq-event   gpio         iommu          net           pps           rfkill        spi_master     vfio
backlight   dma             graphics     leds           pci_bus       printer       rtc           spi_slave      virtio-ports
bdi         dma_heap        hidraw       lirc           pci_epc       ptp           scsi_device   spi_transport  vtconsole
block       dmi             hwmon        mdio_bus       phy           pwm           scsi_disk     thermal        wakeup
bsg         drm             i2c-adapter  mem            powercap      rapidio_port  scsi_generic  tpm            watchdog
dax         drm_dp_aux_dev  i2c-dev      misc           power_supply  rc            scsi_host     tpmrm
hq@hq-virtual-machine:/sys/class$ cd misc/
hq@hq-virtual-machine:/sys/class/misc$ ls
agpgart  cpu_dma_latency  ecryptfs  hpet       lightnvm      mcelog    psaux   snapshot  udmabuf  vfio         vmci
autofs   device-mapper    fuse      hw_random  loop-control  misc_dev  rfkill  tun       uinput   vga_arbiter  vsock
hq@hq-virtual-machine:/sys/class/misc$ ls -l
總用量 0
lrwxrwxrwx 1 root root 0 3月  10 12:23 agpgart -> ../../devices/virtual/misc/agpgart
lrwxrwxrwx 1 root root 0 3月  10 12:23 autofs -> ../../devices/virtual/misc/autofs
lrwxrwxrwx 1 root root 0 3月  10 12:23 cpu_dma_latency -> ../../devices/virtual/misc/cpu_dma_latency
lrwxrwxrwx 1 root root 0 3月  10 12:23 device-mapper -> ../../devices/virtual/misc/device-mapper
lrwxrwxrwx 1 root root 0 3月  10 12:23 ecryptfs -> ../../devices/virtual/misc/ecryptfs
lrwxrwxrwx 1 root root 0 3月  10 12:23 fuse -> ../../devices/virtual/misc/fuse
lrwxrwxrwx 1 root root 0 3月  10 12:23 hpet -> ../../devices/virtual/misc/hpet
lrwxrwxrwx 1 root root 0 3月  10 12:23 hw_random -> ../../devices/virtual/misc/hw_random
lrwxrwxrwx 1 root root 0 3月  10 12:23 lightnvm -> ../../devices/virtual/misc/lightnvm
lrwxrwxrwx 1 root root 0 3月  10 12:23 loop-control -> ../../devices/virtual/misc/loop-control
lrwxrwxrwx 1 root root 0 3月  10 12:23 mcelog -> ../../devices/virtual/misc/mcelog
lrwxrwxrwx 1 root root 0 3月  10 12:23 misc_dev -> ../../devices/virtual/misc/misc_dev
lrwxrwxrwx 1 root root 0 3月  10 12:23 psaux -> ../../devices/virtual/misc/psaux
lrwxrwxrwx 1 root root 0 3月  10 12:23 rfkill -> ../../devices/virtual/misc/rfkill
lrwxrwxrwx 1 root root 0 3月  10 12:23 snapshot -> ../../devices/virtual/misc/snapshot
lrwxrwxrwx 1 root root 0 3月  10 12:23 tun -> ../../devices/virtual/misc/tun
lrwxrwxrwx 1 root root 0 3月  10 12:23 udmabuf -> ../../devices/virtual/misc/udmabuf
lrwxrwxrwx 1 root root 0 3月  10 12:23 uinput -> ../../devices/virtual/misc/uinput
lrwxrwxrwx 1 root root 0 3月  10 12:23 vfio -> ../../devices/virtual/misc/vfio
lrwxrwxrwx 1 root root 0 3月  10 12:23 vga_arbiter -> ../../devices/virtual/misc/vga_arbiter
lrwxrwxrwx 1 root root 0 3月  10 12:23 vmci -> ../../devices/virtual/misc/vmci
lrwxrwxrwx 1 root root 0 3月  10 12:23 vsock -> ../../devices/virtual/misc/vsock
hq@hq-virtual-machine:/sys/class/misc$ cd misc_dev
hq@hq-virtual-machine:/sys/class/misc/misc_dev$ ls
dev  power  subsystem  uevent
hq@hq-virtual-machine:/sys/class/misc/misc_dev$ cat uevent 
MAJOR=10
MINOR=56
DEVNAME=misc_dev
hq@hq-virtual-machine:/sys/class/misc/misc_dev$
相關文章
相關標籤/搜索