字符設備驅動接口

1.讀寫函數
用戶空間函數原型:

ssize_t write(int fd, const void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

驅動中對應的函數原型:

ssize_t (*write) (struct file *flp, const char __user *buf, size_t count , loff_t *poff);
ssize_t (*write) (struct file *flp, const char __user *buf, size_t count , loff_t *poff);

poff: 是指向存放文件當前讀寫位置變量的地址的指針;每次讀寫數據後poff會變化。但其用處需要自己在驅動函數write/read中使用;否則無作用。
比如:
read(fd,buf,0) 讀寫位置不變,驅動直接返回 0.
read(fd,buf,1) 讀寫位置 變成 1
read(fd,buf,1) 讀寫位置 變成 2
read(fd,buf,1) 讀寫位置 變成 3
read(fd,buf,1) 讀寫位置 變成 4,並且驅動只讀取一個數據。
read(fd,buf,1) 讀寫位置 還是 4,沒有讀取任何一個數據。
代碼例子:

static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t count, loff_t *poff)
{
    int i;
    char kbuf[LED_NUM] = {0};
    loff_t cur_pos = *poff;   //取出當前讀寫位置值

    printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

    //進行參數修正:1)count如是0,函數返回0.
    if(count == 0) {
        return 0;
    }

    //進行參數修正:2)   如果文件讀寫位置已經到末尾,不管count是多少,都不能讀,返回0;
    if(cur_pos >= LED_NUM) {
        return 0;
    }

    //進行參數修正:3)   判斷 count + *poff 是否大於文件大小
    if(cur_pos + count > LED_NUM) {
        count = LED_NUM - cur_pos;
    }

    //準備數據:讀取4個led燈狀態
    for(i = 0; i < LED_NUM; i++) {
        //滅:返回'0',使用正邏輯方式表,也要和write邏輯相同
        if(GPM4DAT & 1 << i) {
            kbuf[i] = '0' ;
        }
        //亮:返回'1',使用正邏輯方式表,也要和write邏輯相同
        else {
            kbuf[i] = '1' ;
        }
    }

    //複製數據給用戶空間
    if (copy_to_user(buf, &kbuf[cur_pos], count) ) {
        printk(KERN_EMERG" error: copy_to_user\r\n");
        return -EFAULT;
    }

    //更新文件讀寫位置,不能少,少了得不到正確的結果
    *poff += count;

    return count;
}

static ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t count, loff_t *poff)
{
    int i = 0;
    char kbuf[LED_NUM] = {0};
    loff_t cur_pos = *poff;   //取出當前讀寫位置值

    printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);


    //進行參數修正:1)count如是0,函數返回0.
    if(count == 0) {
        return 0;
    }

    //進行參數修正:2)   如果文件讀寫位置已經到末尾,不管count是多少,都不能寫,返回0;
    if(cur_pos >= LED_NUM) {
        return 0;
    }

    //進行參數修正:3)   判斷 count + *poff 是否大於文件大小
    if(cur_pos + count > LED_NUM) {
        count = LED_NUM - cur_pos;
    }


    //使用專用的函數進行復制,得到到對應 的元素位置
    if (copy_from_user(&kbuf[cur_pos], buf, count) ) {
        printk(KERN_EMERG" error: copy_from_user\r\n");
        return -EFAULT;
    }


    //根據用戶空間傳遞下來的內容進行處理
    for(i = 0; i < count; i++) {
        if(kbuf[i + cur_pos] == '0') {
            //熄滅第i個燈:通過配置數據寄存器
            GPM4DAT  |=  1 << (i + cur_pos) ; //把第i個IO口配置爲高電平,
            //當使用%d時候要注意cur_pos類型,否則輸出會不正確
            printk(KERN_EMERG"第%d個燈滅\r\n", i + 1 + (int)cur_pos);
        }

        else if(kbuf[i + cur_pos ] == '1') {
            //點亮第i個燈:通過配置數據寄存器
            GPM4DAT  &= ~( 1 << (i + cur_pos)); //把第i個IO口配置爲低電平,

            //當使用%d時候要注意cur_pos類型,否則輸出會不正確
            printk(KERN_EMERG"第%d個燈亮\r\n", i + 1 + (int)cur_pos);
        }
    }

    //更新文件讀寫位置,不能少,少了得不到正確的結果
    *poff += count;  
    return count;
}

2.文件指針
用戶空間函數原型:

off_t lseek(int fd, off_t offset, int whence);

驅動中對應的函數原型:

loff_t xxx_llseek(struct file *file, loff_t offset, int whence)

作用: 按照 whence 指定的方式把 fd 文件的讀寫指針調整 offset 偏移。
offset: 需要調整的偏移,值是可正數,可負數
whence: 調整方式,只有三種值: SEEK_SEK,SEEK_CUR,SEEK_END

whence
SEEK_SET: 以文件開頭( 0)爲參照點移動到 offset 處。
SEEK_CUR: 以文件指針 [當前位置 + offset ]做爲新的 offset 指針位置。
SEEK_END: 以文件[末尾位置 + offset] 做爲新的 offset 指針位置。

返回值:
成功: 移動後文件指針位置fpos(以 0 爲參照點);
失敗: -1,並且設置全局變量 errno 值爲對應的錯誤碼(來源碼驅動)
說明:f_pos需要自己在驅動函數中使用,並且返回;否則無作用。
例子代碼:

static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence)
{
    loff_t  temp;

    printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);

    switch(whence) {
        case SEEK_SET:
            temp = off;
            break;

        case SEEK_CUR:
            temp = pfile->f_pos + off;   //當前位置加上調整值
            break;


        case SEEK_END:
            temp = LED_NUM + off;       //文件大小加上調整值
            break;

        default:
            return -EINVAL;    //告訴應用程序具體錯誤原因是參數無效

    }

    //檢測最後調整結果是否合法
    if((temp < 0)  || (temp > LED_NUM) ) {
        return  -EINVAL;
    }

    //更新調整後結果到文件讀寫位置變量中
    pfile->f_pos = temp;

    //返回調整後的結果
    return temp;

}

3.標準 ioctl 接口
用戶空間函數原型:

int ioctl(int fd, int request, ...);

驅動中對應的函數原型:

  long (*unlocked_ioctl) (struct file *pfile, unsigned int cmd, unsigned long args);

作用: 通過命令形式來控制硬件設備,相當 linux 系統給我們提供擴展系統功能的一個接口,可以由用戶自定義命令來讓硬件執行不同的代碼。
參數:
pfile: 文件結構指針,間接對應於系統調用的 fd 參數(文件描述符)
cmd:命令,對應於系統調用的 request
args: args 系統調用的…參數(可選參數)
返回值:
cmd 命令執行成功:>0 ,值是什麼含義由驅動程序中決定。
cmd 命令執行失敗:<0,返回失敗錯誤碼, 比如 args 參數非法,則返回-EFAULT,所返回的錯誤碼會被系統
特別注意:這裏解釋了 cmd 值的組成格式:

bits meaning
31-30 用戶程序和驅動數據交換模式
29-16 當用戶程序和驅動程序有數據傳遞時候纔有效,表示要傳遞的數據大小。
15-8 魔數,幻數, 給每一個驅動分配一個惟一的 ASCII 值,用於區分一個驅動的cmd 和別的不一樣。
7-0 同一個設備驅動中所有 cmd 命令的編號,範圍 0~255,一般情況同一個驅動值都是連續的。

用戶程序和驅動數據交換模式:
00 – 用戶程序和驅動沒有數據傳遞 uses _IO macro
01 – 用戶程序向驅動寫數據: _IOW
10 – 用戶程序從驅動讀取數據: _IOR
11 - 先用戶程序寫數據到驅動,再從驅動中讀取數據(先寫數據然後讀取數據回來) : _IOWR

內核在(Ioctl.h linux-3.5\include\Asm-generic)提供了相應的宏給我們使用,我們可以在應用層使用:
_IO(type,nr) :定義沒有數據傳遞的命令
_IOR(type,nr,size) :定義從驅動中讀取數據的命令
_IOW(type,nr,size) : 定義向驅動寫入數據的命令
_IOWR(type,nr,size) : 定義數據交換類型的命令,先寫入數據,再讀取數據這類命令。
type: 表示命令組成的魔數,也就是 8~15 位
nr: 表示命令組成的編號,也就是 0~7 位
size: 表示命令組成的參數傳遞大小, 但是這裏不傳遞數字,而是數據類型, 如要傳遞 4 字節,可以寫 int,如要傳遞一個結構體數據給驅動,則把結構類型做 size 參數。

代碼例子:

long chrdev_unlocked_ioctl (struct file *pfile,unsigned int cmd, unsigned long arg)
{
    switch ( cmd ) {
        case 0 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 1 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 2 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 3 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        case 4 :
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            break;
        default:
            printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
            return -EINVAL;
    }

        return arg;

    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章