一、ioctl 的功能
1、大部分驅動除了需要具備讀寫裝置的能力之外,還需要具備對硬體控制的能力。例如:改變裝置的傳輸速率,要求裝置報告錯誤資訊,這些操作常常是通過ioctl 方法來實現的。
二、ioctl 使用方法
1、在使用者空間,使用 ioctl 系統調用來控制裝置,函數原型如下:
int ioctl ( int fd, unsigned long cmd, 【...】 );
註:參數列表中的 省略符號“...”,表示一個可選的參數,這些參數的存在與否依賴於控制命令(第2個參數 cmd)。
2、驅動方法中的 ioctl (ioctl 驅動方法有和使用者空間版本不同的原型):
int ( * ioctl) ( struct inode * inode, struct file * filp, unsigned int cmd, unsigned long arg);
參數:
== inode:記錄一些裝置號等資訊
==filp:對應一個開啟的檔案,其中記錄了一些諸如檔案的讀寫位置等資訊
==cmd:這個參數從使用者空間傳下來,
==arg:可選的參數, 不管它是一個整數或一個指標, arg 都以一個unsigned long 的形式傳遞。如果第3個參數cmd命令不涉及資料轉送,則第3個參數arg的值毫無意義。
三、實現 ioctl 方法:(步驟)
1、定義命令
(1)從實質上講,一個命令就是一個32位的整數。為了防止對錯誤的裝置使用正確的命令,命令號應該在系統範圍內是唯一的。
(2)ioctl 命令編碼被劃分為4個位段,(在 include / asm / ioctl.h 中定義了這些位欄位),他們分別是:
a、類型(幻數) b、序號 c、傳送方向 d、參數的大小
(3)定義命令的正確方法是使用4個位段,這個列表中介紹的符號定義在 <linux / ioctl.h>
== Type (幻數:類型)
表明那個裝置的命令,在參考了ioctl-number.txt之後選出,8位寬。
eg:
我們給每一個裝置指定一個類型(幻數)假如說我們指定了某裝置的幻數為3,那麼所有Type段的值為3的命令都是給這個裝置服務的
==Number(序號)
一個裝置有多個控制命令,這個欄位的值用來表明,當前命令是所有裝置控制命令中的第幾個命令,8位寬
==Direction(資料傳送的方向)
涉及資料轉送的命令需要指明資料傳送的方向(比如設定傳輸速率),資料傳送的方向是從應用程式的觀點來看待的,可能的值有:
--- _IOC_NONE:沒有資料轉送
--- _IOC_READ:從裝置讀
--- _IOC_WRITE :向裝置寫
==Size(使用者資料的大小)
13/14 位寬,視處理器而定
(4)核心提供了下列宏來協助定義命令:
== _IO ( type , nr ) , 用於構造沒有參數的命令
由於沒有參數,所以這個命令的 Direction (等於_IOC_NONE)和 Size(等於0) 就都明確了,不需要再設定。
所以使用這個宏,並傳入命令的type欄位和nr欄位的值,它就會幫你構造並返回一個沒有參數的命令
== _IOR(type , nr , datatype),從驅動中讀資料
這裡不需要直接提供size的大小,這個宏會根據你提供的參數類型datatype,自動的計算資料size的大小
== _IOW ( type, nr , datatype ),寫資料到驅動
== _IOWR ( type, nr , datatype ),雙向傳送,type 和 number 成員作為參數被傳遞
eg:
#define MEM_IOC_MAGIC 'm' //定義幻數,由於幻數是一個8位寬的,所以其實就是一個char類型變數,我們當然可以用一個字元對他進行賦值#define MEM_IOCSET _IOW(MEM_IOC_MAGIC, 0 , int); //定義一個命令MEM_IOCSET,用於向裝置中寫資料#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC,1,int) //向裝置中讀資料
2、實現命令(ioctl 函數的實現包括如下 3個技術環節:a、傳回值 b、參數設定 c、命令操作)
(1)ioctl 函數的實現 主體是一個 switch 語句,switch的 參數是cmd。當命令號不能匹配任何一個命令時,返回 -EINVAL(error invalid 非法參數,注意前面有一個負號)
(2)如何使用 ioctl 函數中的第4個參數arg(從使用者空間傳下來的)?如果是一個整數,可以直接使用。如果是指標,我們必須確保這個使用者地址是有效,因此使用前需要進行正確的檢查。
==通過以下的函數使用此指標不需要進行檢測(因為在這些函數內部已經實現了檢測)(4個):
--- copy_from_user
--- copy_to_user
--- get_user
--- put_user
==通過以下的函數使用此指標需要進行檢測(2個):
--- __get_user
--- __put_user
== 怎麼樣對這個指標進行檢測?
使用 int access_ok ( int type, const void *addr,unsigned long size ); 函數
參數:
--- type:可以使用的值是VERIFY_READ 或者 VERIFY_WRITE ,用來表明讀使用者記憶體還是寫使用者記憶體
---addr:指明要操作的使用者記憶體位址
---size:操作的長度,eg:如果需要從使用者空間讀一個整數,那麼size參數等於sizeof(int)
傳回值:
access_ok返回一個布爾值:1是成功(存取沒問題),0是失敗(存取有問題)。如果該函數返回失敗,則ioctl 應當返回 -EFAULT
eg:
if( _IOC_DIR(cmd) & _IOC_READ) err = access_oc(VERIFY_WRITE,(void __user*)arg,_IOC_SIZE(cmd));//為什麼_IOC_READ 對應 VERIFY_WRITE?因為IOC_READ表示從裝置中讀取資料然後傳到使用者空間,所以這個資料最終要留向使用者空間,即寫進使用者空間,寫到使用者空間這個arg指標所指向的地區。所以這裡我們要驗證的是寫else if( _IOC_DIR(cmd) & _IOC_WRITE) err = access_ok(VERIFY_WRITE,(void __user*)arg,_IOC_SIZE(cmd));if(err) return -EFAULT;