1. Ioctl 用來做什麼?
大部分驅動除了需要具備讀寫設備的能力外,還需要具備對硬件控制的能力。例如,要求設備報告錯誤信息,改變波特率,這些操作常常通過ioctl方法來實現。
1.1 用戶使用方法
在用戶空間,使用ioctl 系統調用來控制設備,原型如下:
int ioctl(int fd,unsigned long cmd,...)
原型中的點表示這是一個可選的參數,存在與否依賴於控制命令(第2 個參數)是否涉及到與設備的數據交互。
1.2 驅動ioctl方法
ioctl 驅動方法有和用戶空間版本不同的原型:
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
cmd參數從用戶空間傳下來,可選的參數arg 以一個unsigned long 的形式傳遞,不管它是一個整數或一個指針。如果cmd命令不涉及數據傳輸,則第3 個參數arg的值無任何意義。
2. Ioctl實現
2.1 實現Ioctl方法的步驟:
1) 定義命令
2.) 實現命令
2.2 定義命令
在編寫ioctl代碼之前,首先需要定義命令。為了防止對錯誤的設備使用正確的命令,命令號應該在系統范圍內是唯一的。ioctl 命令編碼被劃分為幾個位段,include/asm/ioctl.h中定義了這些位字段:類型(幻數),序號,傳送方向,參數的大小。Documentation/ioctl-number.txt文件中羅列了在內核中已經使用了的幻數。
定義ioctl 命令的正確方法是使用4 個位段, 這個列表中介紹的符號定義在<linux/ioctl.h>中:
1) Type
幻數(類型): 表明哪個設備的命令,在參考了ioctlnumber.txt之後選出,8 位寬。
2) Number
序號,表明設備命令中的第幾個,8 位寬
3) Direction
數據傳送的方向,可能的值是_IOC_NONE(沒有數據傳輸),_IOC_READ, _IOC_WRITE。數據傳送是從應用程序的觀點來看待的,_IOC_READ 意思是從設備讀。
4) Size
用戶數據的大小。(13/14位寬,視處理器而定)
內核提供了下列宏來幫助定義命令:
1) _IO(type,nr)
沒有參數的命令
2) _IOR(type,nr,datatype)
從驅動中讀數據
3) _IOW(type,nr,datatype)
寫數據到驅動
4) _IOWR(type,nr,datatype)
雙向傳送,type 和number 成員作為參數被傳遞。
定義命令(范例)
#define MEM_IOC_MAGIC ‘m’ //定義幻數
#define MEM_IOCSET
_IOW(MEM_IOC_MAGIC, 0, int)
#define MEM_IOCGQSET
_IOR(MEM_IOC_MAGIC, 1, int)
2.3 Ioctl函數實現
定義好了命令,下一步就是要實現Ioctl函數了,Ioctl函數的實現包括如下3個技術環節:
1) 返回值
2) 參數使用
3) 命令操作
2.3.1 Ioctl函數實現(返回值)
Ioctl函數的實現通常是根據命令執行的一個switch語句。但是,當命令號不能匹配任何一個設備所支持的命令時,通常返回-EINVAL(“非法參數”)。
2.3..2 Ioctl函數實現(參數)
如何使用Ioctl中的參數?
如果是一個整數,可以直接使用。如果是指針,我們必須確保這個用戶地址是有效的,因此使用前需進行正確的檢查。
2.3.3 Ioctl函數實現(參數檢查)
不需要檢測:
1) copy_from_user
2) copy_to_user
3) get_user
4) put_user
需要檢測:
1) __get_user
2) __put_user
int access_ok(int type, const void *addr, unsigned long size)
第一個參數是VERIFY_READ 或者VERIFY_WRITE,用來表明是讀用戶內存還是寫用戶內存。addr 參數是要操作的用戶內存地址,size 是操作的長度。如果ioctl 需要從用戶空間讀一個整數,那麼size參數等於sizeof(int)。access_ok 返回一個布爾值: 1 是成功(存取沒問題)和0 是失敗(存取有問題),如果該函數返回失敗, 則Ioctl應當返回–EFAULT 。
3. Ioctl函數實現范例
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg,_IOC_SIZE(cmd)); //why _IOC_READ 對應VERIFY_WRITE ???
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg,_IOC_SIZE(cmd));
if (err)
return -EFAULT;
switch(cmd)
{
case MEM_IOCSQUANTUM: /* Set: arg points to the value */
retval = __get_user(scull_quantum, (int *)arg);
break;
case MEM_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int *)arg);
break;
default:
return –EINVAL;
}