http://blog.csdn.net/skyflying2012/article/details/8210799 2012 大部分驅動需要 -- 除了讀寫設備的能力 -- 通過設備驅動進行各種硬件控制的能力. 大部分設備可進行超出簡單的數據傳輸之外的操作; 用戶空間必須常常能夠請求, 例如, 設備鎖上它的門, 彈出它的介質, 報告錯誤信息, 改變波特率, 或者自我銷毀. 這些操作常常通過 ioctl 方法來支持, 它通過相同名子的系統調用來實現. 在用戶空間, ioctl 系統調用有下面的原型: int ioctl(int fd, unsigned long cmd, ...);ioctl 驅動方法有和用戶空間版本不同的原型: int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);inode 和 filp 指針是對應應用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的相同參數. cmd 參數從用戶那里不改變地傳下來, 并且可選的參數 arg 參數以一個 unsigned long 的形式傳遞, 不管它是否由用戶給定為一個整數或一個指針. 如果調用程序不傳遞第 3 個參數, 被驅動操作收到的 arg 值是無定義的. 因為類型檢查在這個額外參數上被關閉, 編譯器不能警告你如果一個無效的參數被傳遞給 ioctl, 并且任何關聯的錯誤將難以查找. ioctl 命令數字應當在這個系統是唯一的, 為了阻止向錯誤的設備發(fā)出正確的命令而引起的錯誤. 為幫助程序員創(chuàng)建唯一的 ioctl 命令代碼, 這些編碼已被劃分為幾個位段. 定義 ioctl 命令號的正確方法使用 4 個位段, 它們有下列的含義. 這個列表中介紹的新符號定義在 <linux/ioctl.h>. type 魔數. 只是選擇一個數(在參考了 ioctl-number.txt之后)并且使用它在整個驅動中. 這個成員是 8 位寬(_IOC_TYPEBITS). number 序(順序)號. 它是 8 位(_IOC_NRBITS)寬. direction 數據傳送的方向,如果這個特殊的命令涉及數據傳送. 可能的值是 _IOC_NONE(沒有數據傳輸), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (數據在2個方向被傳送). 數據傳送是從應用程序的觀點來看待的; _IOC_READ 意思是從設備讀, 因此設備必須寫到用戶空間. 注意這個成員是一個位掩碼, 因此 _IOC_READ 和 _IOC_WRITE 可使用一個邏輯 AND 操作來抽取. size 涉及到的用戶數據的大小. 這個成員的寬度是依賴體系的, 但是常常是 13 或者 14 位. 你可為你的特定體系在宏 _IOC_SIZEBITS 中找到它的值. 你使用這個 size 成員不是強制的 - 內核不檢查它 -- 但是它是一個好主意. 正確使用這個成員可幫助檢測用戶空間程序的錯誤并使你實現向后兼容, 如果你曾需要改變相關數據項的大小. 如果你需要更大的數據結構, 但是, 你可忽略這個 size 成員. 我們很快見到如何使用這個成員. 定義宏來幫助建立命令號, 如下: _IO(type,nr)(給沒有參數的命令), _IOR(type, nre, datatype)(給從驅動中讀數據的), _IOW(type,nr,datatype)(給寫數據), _IOWR(type,nr,datatype)(給雙向傳送). type 和 number 成員作為參數被傳遞, 并且 size 成員通過應用 sizeof 到 datatype 參數而得到. 驅動中來解碼這個號: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), 和 _IOC_SIZE(nr).
/* Ioctlün */ #define MEM_IOC_MAGIC 'k'
#define MEM_IOC_RESET _IO(MEM_IOC_MAGIC, 0) #define MEM_IOC_GET _IOR(MEM_IOC_MAGIC, 1, int) #define MEM_IOC_SET _IOW(MEM_IOC_MAGIC, 2, int)
使用 ioctl 參數 在看 scull 驅動的 ioctl 代碼之前, 我們需要涉及的另一點是如何使用這個額外的參數. 如果它是一個整數, 就容易: 它可以直接使用. 如果它是一個指針, 但是, 必須小心些. 當用一個指針引用用戶空間, 我們必須確保用戶地址是有效的. 試圖存取一個沒驗證過的用戶提供的指針可能導致不正確的行為, 一個內核 oops, 系統崩潰, 或者安全問題. 它是驅動的責任來對每個它使用的用戶空間地址進行正確的檢查, 并且返回一個錯誤如果它是無效的. copy_from_user 和 copy_to_user 函數, 它們可用來安全地移動數據到和從用戶空間. 這些函數也可用在 ioctl 方法中, 但是 ioctl 調用常常包含小數據項, 可通過其他方法更有效地操作. 開始, 地址校驗(不傳送數據)由函數 access_ok 實現, 它定義在 <asm/uaccess.h>: int access_ok(int type, const void *addr, unsigned long size);第一個參數應當是 VERIFY_READ 或者 VERIFY_WRITE, 依據這個要進行的動作是否是讀用戶空間內存區(qū)或者寫它. addr 參數持有一個用戶空間地址, size 是一個字節(jié)量. 例如, 如果 ioctl 需要從用戶空間讀一個整數, size 是 sizeof(int). 如果你需要讀和寫給定地址, 使用 VERIFY_WRITE, 因為它是 VERIRY_READ 的超集. 不象大部分的內核函數, access_ok 返回一個布爾值: 1 是成功(存取沒問題)和 0 是失敗(存取有問題). 如果它返回假, 驅動應當返回 -EFAULT 給調用者. ioctl 命令的實現
long mem_ioctl( struct file *filp, unsigned int cmd, unsigned long arg) { struct mem_dev *dev = filp->private_data; int ret = 0, err = 0;
/* ünēАД */ if (_IOC_TYPE(cmd) != MEM_IOC_MAGIC) return -ENOTTY; if (_IOC_NR(cmd) > MEM_IOC_MAXNR) return -ENOTTY;
/* n */ 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;
/* nА switch(cmd) { case MEM_IOC_RESET: dev->size = 0; break;
case MEM_IOC_GET: ret = put_user(dev->size, (int __user *) arg); break;
case MEM_IOC_SET: ret = get_user(dev->size, (int __user *) arg); break;
default: return -EINVAL; } |
|