1. 程式人生 > >第16章 驅動開發之字元裝置驅動程式框架

第16章 驅動開發之字元裝置驅動程式框架

16.1 字元裝置驅動程式框架簡介

我們在學習 C 語言的時候,知道每個應用程式的入口函式,即第一個被執行的函式是 main函式,那麼,我們自己的驅動程式,哪個函式是入口函式呢?
在寫驅動程式的時候,如果函式的名字可以任意取,常常為 xxxx_init(),當實現好這個 xxxx_init()函式以後,核心其實並不知道這個就是我們驅動的入口函式,因此我們要想辦法告訴核心,我們的入口函式是哪個?我們通過 module_init()函式來告訴核心,具體如下。

module_init(openwrt_drv_init);

通過上面的修飾以後, openwrt_drv_init()這個函式就變成了我們的驅動程式的入口函數了。當然,有入口函式,自然還需要一個出口函式,我們通過 module_exit()函式來告訴核心,具體如下。

module_exit(openwrt_drv_exit);

從上一章中,我們知道,應用程式是通過 open、read、write …函式來和我們的驅動程式進行互動的,那麼我們的驅動程式是怎麼給應用程式提供這些介面的呢?我們在寫驅動程式的時候,我們首先需要定義出一個 file_operations 結構體,該結構體便是驅動和應用程式互動的介面。具體定義如下。

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct
file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long,loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long,loff_t); int (*readdir) (struct
file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); };

我們的驅動程式要給應用程式提供哪些介面,就只需要填充相應的成員即可。比如我們想提供 open、read、write 這三個介面,就應該如下定義。

static struct file_operations openwrt_drv_fops = {
.owner = THIS_MODULE, /*這是一個巨集,推向編譯模組時自動建立的__this_module 變數 */
.open = openwrt_drv_open,
.read = openwrt_drv_read,
.write = openwrt_drv_write,
};

當 file_operations 結構體定義、設定好以後,我們只需要通過 register_chrdev()函式將該機構圖註冊進核心即可。

16.2 字元裝置驅動程式框架實現

經過前面部分的講解,相信大家一定對如何寫一個自己的驅動程式,有所感悟了。接下來,給大家一個簡單的驅動程式的例子,可以用於作為框架模板,以後的驅動都可以基於該驅動進行修改。

#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/raw.h>
#include <linux/tty.h>
#include <linux/capability.h>
#include <linux/ptrace.h>
#include <linux/device.h>
#include <linux/highmem.h>
#include <linux/crash_dump.h>
#include <linux/backing-dev.h>
#include <linux/bootmem.h>
#include <linux/splice.h>
#include <linux/pfn.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/aio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/uaccess.h>

/* 載入模式後,執行”cat /proc/devices”命令看到的裝置名稱 */
#define DEVICE_NAME "openwrt"
#define OPENWRT_MAJOR 0 /* 主裝置號 */
static struct class *openwrt_drv_class;
static int openwrt_drv_open(struct inode *inode, struct file *file)
{
    // printk 用於驅動中新增列印,用法和應用程式中的 printf 一樣
    printk("%s:Hello openwrt\n", __FUNCTION__);
    return 0;
}
static ssize_t openwrt_drv_read(struct file *file, char __user *buf, size_t size,loff_t *ppos)
{
    // printk 用於驅動中新增列印,用法和應用程式中的 printf 一樣
    printk("%s:Hello openwrt\n", __FUNCTION__); 
    return 0;
}
static ssize_t openwrt_drv_write(struct file *file, const char __user *buf, size_t
size, loff_t *ppos)
{
    // printk 用於驅動中新增列印,用法和應用程式中的 printf 一樣
    printk("%s:Hello openwrt\n", __FUNCTION__);
    return 0;
}
/*
*這個結構是字元裝置驅動程式的核心
*當應用程式操作裝置檔案時所呼叫的 open、read、write 等函式,
*最終會呼叫這個結構中指定的對應函式
*/
static struct file_operations f403tech_drv_fops = {
/*這是一個巨集,推向編譯模組時自動建立的__this_module 變數 */
.owner = THIS_MODULE, 
.open = openwrt_drv_open,
.read = openwrt_drv_read,
.write = openwrt_drv_write,
};
int major;
/*
*執行 insmod 命令時就會呼叫這個函式
*/
static int __init f403tech_drv_init(void)
{
    /* 註冊字元裝置
    *這步是寫字元裝置驅動程式所必須的
    *引數為主裝置號、裝置名字、file_operations 結構;
    *這樣,主裝置號就和具體的 file_operations 結構聯絡起來了,
    *操作主裝置為 OPENWRT_MAJOR 的裝置檔案時,就會呼叫 openwrt_drv_fops 中的相關成員函式
    *OPENWRT_MAJOR 可以設為 0,表示由核心自動分配主裝置號
    */
    major = register_chrdev(OPENWRT_MAJOR, DEVICE_NAME, &openwrt_drv_fops);
    if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number\n");
        return major;
    }
    /*
    *以下兩行程式碼用於建立裝置節點,是必須的。
    *當然,如果不寫這兩行,那麼就必須在 linux 系統命令列中通過 mknod 這個命令來建立裝置節點
    */
    /* 建立類 */
    openwrt_drv_class = class_create(THIS_MODULE, "openwrt");
    /* 類下面建立裝置節點 */
    device_create(openwrt_drv_class, NULL, MKDEV(major, 0), NULL, "openwrt");
    // /dev/openwrt
    //列印一個除錯資訊
    printk("%s:Hello openwrt\n", __FUNCTION__); 
    return 0;
}
/*
*執行 rmmod 命令時就會呼叫這個函式
*/
static void __exit openwrt_drv_exit(void)
{
    // 與入口函式的 register_chrdev函式配對使用
    unregister_chrdev(major, "openwrt"); 
    //與入口函式的device_create 函式配對使用
    device_destroy(openwrt_drv_class, MKDEV(major, 0));
    class_destroy(openwrt_drv_class); // 與入口函式的 class_create 函式配對使用
    printk("%s:Hello openwrt\n", __FUNCTION__); // printk 用於驅動中新增列印
}
/* 這兩行指定驅動程式的初始化函式和解除安裝函式 */
module_init(openwrt_drv_init);
module_exit(openwrt_drv_exit);
/* 描述驅動程式的一些資訊,不是必須的 */
MODULE_AUTHOR("openwrt");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("RT5350 FIRST Driver");
MODULE_LICENSE("GPL");