linux驅動學習4:scull驅動
要點:學習ioctl()驅動編寫,如何傳入命令來控制硬體。
1)ioctl知識
使用者空間的ioctl()呼叫如下:
int ioctl(int fd, unsigned long cmd, …);
2)驅動中ioctl:
Int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg );
主要工作:傳入裝置對應的inode,根據命令cmd和arg來修改filp。
3)實現
分為兩步:
n 定義命令(一般在標頭檔案中, scull.h);
n 實現命令, switch
i. 定義命令
編寫ioctl之前先需要定義命令,命令號在系統範圍內必須是唯一的。命令cmd被劃分為4個位段:型別type(幻數:確保唯一)、序號(對應該裝置驅動的命令的序號)、傳送方向、引數大小。
type:幻數(型別):表明哪個裝置的命令,在參考了ioctl-number.txt之後選出,8位寬
number:序號,表明裝置命令中的第幾個,8位寬
direction:資料傳送的方向,可能的值是_IOC_NONE(沒有資料傳輸),_IOC_READ,_IOC_WRITE。資料傳送是從應用程式的觀點來看的,_IOC_READ意思是從裝置讀
size:使用者資料的大小。(13/14
核心提供了下列巨集來幫助定義命令:
_IO(type, nr):沒有引數傳遞的命令。(那麼direction的值為_IOC_NONE,size的值為0)
_IOR(type, nr, datatype):從驅動中讀資料(4個值已經確定)
_IOW(type, nr, datatype):寫資料到驅動
_IOWR(type, nr, datatype):雙向傳送,type和number成員作為引數被傳遞
定義命令(範例)
#define MEM_IOC_MAGIC 'm' //定義幻數,一個字母剛好是8位,必須唯一
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC, 0, int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)
ii. 實現
1) 返回值:switch;
2) 傳入引數:如果是整數,則可直接使用;如果是指標,則需要先檢驗該指標是否有效(用access_ok()檢驗該指標是否是使用者空間合法的)。
注:
不需要檢測的函式:copy_to_user(), copy_from_user(), get_user(), put_user();
需要用access_ok檢驗的:__get_user()核心從使用者空間讀資料, __put_user()核心向用戶空間寫資料.(此處要注意access_ok() 與_IOC_READ的反向問題)即如下語句:
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
_IOC_READ:表示使用者讀取核心裝置的資料(主體是使用者程式);
access_ok()的write是驗證核心可以向用戶空間的指標寫資料(主體是核心)。
4)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.
5)ioctl程式碼:先檢驗
int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
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 SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}