1. 程式人生 > >(2.1)file_operation 實現設具體操作:概述與實現

(2.1)file_operation 實現設具體操作:概述與實現

/* AUTHOR: Pinus

* Creat on : 2018-10-11

* KERNEL : linux-4.4.145

* BOARD : JZ2440(arm9 s3c2440)

* REFS : 韋東山視訊教程第二期

《LINUX核心原始碼情景分析》

*/

概述

       Unix類系統將裝置也看作是檔案,通過操作檔案的方式操作硬體。而操作檔案的方式無非就是open、read、write、close等,將這些通用的檔案操作函式抽象出來,就是file_operation結構體。(實際核心定義如下,可以看見各種會用到的函式裡面都有)

struct file_operations {
    struct module *owner;
    ...
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ...
    int (*open) (struct inode *, struct file *);
    ...

};

      那麼顯然,要想編寫出實際有用的程式碼,我們起碼要實現對應的函式,這樣當你在應用程式中呼叫open,就會實際呼叫裝置驅動的open。其重要性可見一斑。【思考一:核心是如何將app裡的操作函式和驅動裡的操作函式聯絡上的呢?】先通過led實驗瞭解如何簡單實用,和驅動的簡單框架。

實驗

目標:點亮開發板上的led燈

一、包含標頭檔案,寫出入口和出口函式,新增必要協議和修飾

標頭檔案,協議和修飾就略了,要是這不知道就去看前一篇吧。《(1) 裝置驅動的最基本框架》

1. 入口函式的實現

unsigned int major;

static int __init leds_drv_init(void) /* 入口函式 */
{
    unsigned int minor = 0;

    gpio_va = ioremap(0x56000000, 0x100000);
    if (!gpio_va) {
        return -EIO;
    }

    major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //註冊 告訴核心
    if (major < 0)
    {
        printk("leds_dev can't register major number\n");
        return major;
    }

    leds_class = class_create(THIS_MODULE, "leds_class"); //建立一個類

    leds_dev[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds"); //在類下建立裝置節點"leds"
    for (minor = 1; minor < 4; minor++)
    {
        leds_dev[minor] = device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);     //建立裝置節點"ledx" 次裝置號不同
    }

    printk("leds_dev initialized\n");
    return 0;
}

2.主裝置號的概念:linux中使用ls -l 檢視某個裝置的具體資訊時,可以看見主裝置號和次裝置號,核心通過主裝置來選擇對應的裝置和驅動,次裝置號則是純軟體的概念,用於用同一個裝置驅動使用幾個不同的外設,比如一個led_drv驅動led0,led1,led2... 【思考二;核心怎樣建立裝置,主裝置號具體應用是什麼呢?】

/* 註冊字元裝置
* 引數為主裝置號、裝置名字、file_operations結構;
* 這樣,主裝置號就和具體的file_operations結構聯絡起來了,
* 操作主裝置為LED_MAJOR的裝置檔案時,就會呼叫s3c24xx_leds_fops中的相關成員函式
* LED_MAJOR可以設為0,表示由核心自動分配主裝置號
*/

major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //註冊 告訴核心

核心提供了一個register_chrdev()函式用來註冊裝置(英文翻譯一下register char device),

        第一個引數:0,寫0表示讓核心自動分配主裝置號,並返回給major;

        第二個引數:“leds_dev”, 裝置名;

        第三個引數;&jz2440_leds_fops,指向一個真實自定義file_operation結構體

static const struct file_operations jz2440_leds_fops = {
    .owner = THIS_MODULE,
    .open = leds_drv_open,
    .write = leds_drv_write,
};

顯然正確的實現這個結構體便是重點。

3. 註冊裝置類,在類下注冊具體裝置節點

【思考三:為什麼要建立類呢?】

leds_class = class_create(THIS_MODULE, "leds_class"); //建立一個類

【思考四:建立裝置節點的具體過程?】

leds_dev[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds"); //建立裝置節點"leds"
for (minor = 1; minor < 4; minor++)
{
    leds_dev[minor] = device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);    //建立裝置節點"ledx" 次裝置號不同
}

從這裡也可以看出,主裝置號是核心用的,次裝置號是純軟的概念,給我們程式設計用的

4. 操作硬體所需要的具體實體地址對映,將實體地址對映為核心的虛擬地址,ioremap為核心提供的函式  

static unsigned long gpio_va;

#define GPIO_OFT(x) ((x) - 0x56000000)      /* 具體實體地址有s3c2440晶片手冊得來 */
#define GPFCON (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))

gpio_va = ioremap(0x56000000, 0x100000);    /* 引數:物理起始地址,要對映的大小1M */

5.出口函式的實現

static void __exit leds_drv_exit(void)

{

    unsigned int minor;

    iounmap(gpio_va);
    unregister_chrdev(major, "leds_dev"); //解除安裝裝置驅動,注意第二個引數要與註冊時保持一致
    for (minor = 0; minor < 4; minor++)

    {
       device_unregister(leds_dev[minor]); //解除安裝類下的裝置
    }
    class_destroy(leds_class);             //解除安裝類

}

看那些函式,就是呼叫了在入口函式中呼叫函式的相反函式,上面註冊,下面登出,一一對應

二、file_operation結構體內函式的實現

那麼長時間終於再次回到了我們的課題,file_operation

static const struct file_operations jz2440_leds_fops = {
    .owner = THIS_MODULE,
    .open = leds_drv_open,
    .write = leds_drv_write,
};

file_operations 可以用來實現很多功能,這裡我們只要實現兩個,open和write

static int leds_drv_open(struct inode *inode, struct file *file)
{
    /*配置GPF4,5,6*/
    GPFCON &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); //現將對應位清零
    GPFCON |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); // 01 輸出
    return 0;
}

函式名自己取,需要傳遞的引數呢從file_operation結構體複製

有過裸板開發的應該很清楚,想點亮led的無非兩步,

        a、配置對應GPIO的配置暫存器,使其為輸出模式,

        b、再修改對應的資料暫存器,使其輸出,就可以點亮led燈了。

顯然這裡的leds_drv_open()函式,就是配置暫存器

static ssize_t leds_drv_write(struct file *file, const char *buf, size_t count, loff_t *pos)

{
    unsigned int minor = MINOR(file->f_inode->i_rdev); //MINOR(inode->i_cdev); 得到次裝置號
    char val;

    copy_from_user(&val, buf, count); //將資料從使用者空間傳到核心空間 反之copy_to_user

    switch (minor) /* 看,這就是次裝置號的純軟的概念,和它的基本應用 */
    {
        case 0: /* /dev/leds */
        {
            printk("/dev/leds: %d %d\n", minor, val);
            if(val==1)
            {
                GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
            }
            else
            {
                GPFDAT |= (1<<4) | (1<<5) | (1<<6);
            }
            break;
        }

        case 1: /* /dev/led1 */
        {
            if(val==1)
            {
                GPFDAT &= ~(1<<4);
            }
            else
            {
                GPFDAT |= (1<<4);
            }
            break;
        }
        case 2: /* /dev/led2 */
        {
            if(val==1)
            {
                GPFDAT &= ~(1<<5);
            }
            else
            {
                GPFDAT |= (1<<5);
            }
            break;
        }
        case 3: /* /dev/led3 */
        {
            if(val==1)
            {
                GPFDAT &= ~(1<<6);
            }
            else
            {
                GPFDAT |= (1<<6);
            }
            break;

        }
        default: printk("/dev/leds: %d %d\ ?? n", minor, val);
    }

    return 0;
}

這樣我們就實現了具體的file_operation結構體的具體功能函式,

major = register_chrdev(0, "leds_dev", &jz2440_leds_fops); //在這裡傳入,使之與具體的裝置關聯在一起

三、測試程式的實現

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

void print_usage(char *drv);

int main(int argc, char **argv)
{
    int fd;
    char* filename;
    char val = 1;

    if (argc != 3)
    {
        print_usage(argv[0]);
        return 1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
        printf("can't open dev nod !\n");

    if (strcmp(argv[2], "on") == 0)
    {
        val = 1;
    }
    else if (strcmp(argv[2], "off") == 0)
    {
        val = 0;
    }
    else
    {
        print_usage(argv[0]);
        return 1;
    }

    printf("%s : %d\r\n", argv[1], val);
    write(fd, &val, 1);

    return 0;
}

/* 列印用法 */ 
void print_usage(char *drv)
{
    printf("Usage : \n");
    printf("%s <dev> <on|off>\n", drv);
    printf("eg. \n");
    printf("%s /dev/leds on\n", drv);
    printf("%s /dev/leds off\n", drv);
    printf("%s /dev/led1 on\n", drv);
    printf("%s /dev/led1 off\n", drv);
    // <>表示內部的引數不可省略,|表示或 ?不把argv[0]打印出來避免一個越界錯誤
}

編譯之後也放在板子上,安裝模組,就可以測試了

文中提到的思考題就在下一篇在解答吧,本人也是初學,如有不足請大佬指正

具體程式可以通過下面下載實現程式碼: