在mtd-util的flash_eraseall中發現這樣的用法:
----------------------------------------------------------------
#define MEMGETINFO _IOR('M', 1, struct mtd_info_user)
......
ioctl(fd, MEMGETINFO, &meminfo)
----------------------------------------------------------------
找了一下解釋如下:
對於系統支援裝置的ioctl號,你可以在/usr/include下面的標頭檔中找到,對於你自己的裝置,如果需要使用ioctl介面,則需要定義自己的ioctl號。以前的2.4中有個問題是,大家都隨便定義自己的ioctl號,造成很大可能性的重複性。一個壞處是難以管理,另外一個是容易造成錯誤,例如如果使用者本來希望開啟一個串口裝置,結果通過open開啟了網口,如果串口的某個ioctl號正好是網口的關閉操作,這樣就會造成錯誤。在2.6裡面,你定義自己的ioctl號最好使用_IO, _IOR, _IOW和_IORW來定義,這些宏考慮了第三個參數的長度,裝置的magic number,以及操作的方向等,避免了2.4中的問題
在驅動程式裡, ioctl() 函數上傳送的變數 cmd 是應用程式用於區別裝置驅動程式請求處理內容的值。cmd除了可區別數字外,還包含有助於處理的幾種相應資訊。 cmd的大小為 32位,共分 4 個域:
bit31~bit30 2位為 “區別讀寫” 區,作用是區分是讀取命令還是寫入命令。
bit29~bit15 14位為 "資料大小" 區,表示 ioctl() 中的 arg 變數傳送的記憶體大小。
bit20~bit08 8位為 “魔數"(也稱為"幻數")區,這個值用以與其它裝置驅動程式的 ioctl 命令進行區別。
bit07~bit00 8位為 "區別序號" 區,是區分命令的命令順序序號。
像命令碼中的 “區分讀寫區” 裡的值可能是 _IOC_NONE (0值)表示無資料轉送,_IOC_READ (讀), _IOC_WRITE (寫) , _IOC_READ|_IOC_WRITE (雙向)。
核心定義了 _IO() , _IOR() , IOW() 和 _IOWR() 這 4 個宏來輔助產生上面的 cmd 。下面分析 _IO() 的實現,其它的類似。
在 asm-generic/ioctl.h 裡可以看到 _IO() 的定義:
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) |
再看 _IOC() 的定義:
#define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT)) |
可見,_IO() 的最後結果由 _IOC() 中的 4 個參數移位組合而成。
再看 _IOC_DIRSHIT 的定義:
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) |
_IOC_SIZESHIFT 的定義:
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) |
_IOC_TYPESHIF 的定義:
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) |
_IOC_NRSHIFT 的定義:
_IOC_NRBITS 的定義:
_IOC_TYPEBITS 的定義:
由上面的定義,往上推得到:
引用 _IOC_TYPESHIFT = 8
_IOC_SIZESHIFT = 16
_IOC_DIRSHIFT = 30
所以,(dir) << _IOC_DIRSHIFT) 表是 dir 往左移 30 位,即移到 bit31~bit30 兩位上,得到方向(讀寫)的屬性;
(size) << _IOC_SIZESHIFT) 位左移 16 位得到“資料大小”區;
(type) << _IOC_TYPESHIFT) 左移 8位得到"魔數區" ;
(nr) << _IOC_NRSHIFT) 左移 0 位( bit7~bit0) 。
這樣,就得到了 _IO() 的宏值。
關於IOCTL驅動的編寫方法LDD這本書確實寫的比較明白了,在這呢我就簡單的做一個介紹。這裡我主要描述自己編寫IOCTL驅動時所遇到的問題及其原因。
驅動裡的ioctl函數主要實現不用read,write函數的與使用者空間的簡單資料互動及無參數的命令控制。那麼我們如何?這幾種功能的IOCTL函數呢?ioctl驅動中以SWITCH{case A,case B}結構以實現對不同命令的響應,首先我們要對我們要使用的“A”,“B”命令定義一個整個作業系統內唯一的標識,同時這個標識又能夠表明我們的操作類型及傳遞參數的類型(如果需要的話)。linux系統的每一個命令號被分為多個位欄位,這些位欄位包括type,number,direction,size,分別表示幻數(與裝置相關的一個字母,以避免與核心衝突),序數(命令編號),方向位(以使用者空間為參照的讀,寫和無資料轉送),size(傳遞參數類型)。核心中/include/asm/ioctl.h,/Documentation/ioctl-number.txt兩個檔案表明了我們應該如何定義ioctl命令編號及自訂的幻數,。
看起來構造IOTCL命令這麼複雜,慶幸的是核心已經為我們編寫了構造命令編號的宏命令。_IO(type,nr),_IOR(type,nr,datatype),_IOW(type,nr,datatype)。它們在<linux/ioctl.h>包含的<asm/ioctl.h>中有定義,所以在我們的驅動中應包含這個標頭檔。其中_IO()用做無參數的命令編號,_IOR()用做從驅動中讀取資料的命令編號,_IOW()用做寫入資料命令。 LDD中用:define SCULL_IOC_MAGIC 'k' ,define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,1,int)表示SCULL_IOCSQUANTUM命令編號為向驅動中寫資料,命令編號為1,傳送參數類型為int。
這裡把我所寫的部分代碼貼出來:
驅動程式部分代碼:
#define MOTOR_MAGIC 'k'
#define SET_PULSE _IOW(MOTOR_MAGIC, 1,int)
.
.
static int motor_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{ int data=0;
int retval;
switch (cmd) {
case SET_PULSE:
if (copy_from_user(&data, (int *)arg, sizeof(int)))
return -EFAULT;
.
.
.
}
應用程式部分代碼:
#define MOTOR_MAGIC 'k'
#define SET_PULSE _IOW(MOTOR_MAGIC, 1,int)
int pulse,fileno;
fileno = open("/tmp/usb",O_RDWR);
if (fileno == -1) {
printf("open device key error!\n");
return 0;
}
.
.
printf("Please input the pulse:\n"
scanf("%d",pulse);
if(ioctl(fileno,SET_PULSE,pulse)<0){
perror("ioctl error");
exit(1);
}
.
.
這裡我們一定要注意的是data 和pulse的資料類型一定要與SET_PULSE _IOW(MOTOR_MAGIC, 1,int)中定義的int類型嚴格一致,即使是unsigned int 和int類型之間也是有很大差別的,我就是因為設定了unsigned int類型的data和pulse卻使用SET_PULSE _IOW(MOTOR_MAGIC, 1,int)搞了我兩三天才搞明白。 再有就是ioctl函數的聲明一定要與 static int motor_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);相一致,我剛開始就是定義錯了,也是搞了我好長時間才明白。因為核心非常的相信你,它不會檢查你的IOTCL定義有沒有問題,它只負責把你的IOCTL函數映射到驅動的ioctl操作上。其中inode和filp對應應用程式傳遞的檔案描述符,這和傳遞給open方法的參數一樣,參數cmd 由使用者空間不經修改的傳遞給驅動程式,可選的arg參數無論使用者空間傳遞的是指標還是值,它都以unsigned long的形式傳遞給驅動程式。
還有就是我在驅動中明明是用的是使用地址的copy_from_usr函數,但使用者程式卻只能傳遞值.搞了半天最後才知道,原來我的使用者程式中的scanf(x),'x'沒有加'&'符號,所以這時從終端把讀到的資料寫到了'x'所代表的地址處,也就是說現在'x'所代表的地址正是所讀入的資料.呵呵,總結出的經驗就是----->對庫函數的參數類型一定要搞清楚.