1.讀寫函數
用戶空間函數原型:
ssize_t write(int fd, const void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
驅動中對應的函數原型:
ssize_t (*write) (struct file *flp, const char __user *buf, size_t count , loff_t *poff);
ssize_t (*write) (struct file *flp, const char __user *buf, size_t count , loff_t *poff);
poff: 是指向存放文件當前讀寫位置變量的地址的指針;每次讀寫數據後poff會變化。但其用處需要自己在驅動函數write/read中使用;否則無作用。
比如:
read(fd,buf,0) 讀寫位置不變,驅動直接返回 0.
read(fd,buf,1) 讀寫位置 變成 1
read(fd,buf,1) 讀寫位置 變成 2
read(fd,buf,1) 讀寫位置 變成 3
read(fd,buf,1) 讀寫位置 變成 4,並且驅動只讀取一個數據。
read(fd,buf,1) 讀寫位置 還是 4,沒有讀取任何一個數據。
代碼例子:
static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t count, loff_t *poff)
{
int i;
char kbuf[LED_NUM] = {0};
loff_t cur_pos = *poff; //取出當前讀寫位置值
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
//進行參數修正:1)count如是0,函數返回0.
if(count == 0) {
return 0;
}
//進行參數修正:2) 如果文件讀寫位置已經到末尾,不管count是多少,都不能讀,返回0;
if(cur_pos >= LED_NUM) {
return 0;
}
//進行參數修正:3) 判斷 count + *poff 是否大於文件大小
if(cur_pos + count > LED_NUM) {
count = LED_NUM - cur_pos;
}
//準備數據:讀取4個led燈狀態
for(i = 0; i < LED_NUM; i++) {
//滅:返回'0',使用正邏輯方式表,也要和write邏輯相同
if(GPM4DAT & 1 << i) {
kbuf[i] = '0' ;
}
//亮:返回'1',使用正邏輯方式表,也要和write邏輯相同
else {
kbuf[i] = '1' ;
}
}
//複製數據給用戶空間
if (copy_to_user(buf, &kbuf[cur_pos], count) ) {
printk(KERN_EMERG" error: copy_to_user\r\n");
return -EFAULT;
}
//更新文件讀寫位置,不能少,少了得不到正確的結果
*poff += count;
return count;
}
static ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t count, loff_t *poff)
{
int i = 0;
char kbuf[LED_NUM] = {0};
loff_t cur_pos = *poff; //取出當前讀寫位置值
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
//進行參數修正:1)count如是0,函數返回0.
if(count == 0) {
return 0;
}
//進行參數修正:2) 如果文件讀寫位置已經到末尾,不管count是多少,都不能寫,返回0;
if(cur_pos >= LED_NUM) {
return 0;
}
//進行參數修正:3) 判斷 count + *poff 是否大於文件大小
if(cur_pos + count > LED_NUM) {
count = LED_NUM - cur_pos;
}
//使用專用的函數進行復制,得到到對應 的元素位置
if (copy_from_user(&kbuf[cur_pos], buf, count) ) {
printk(KERN_EMERG" error: copy_from_user\r\n");
return -EFAULT;
}
//根據用戶空間傳遞下來的內容進行處理
for(i = 0; i < count; i++) {
if(kbuf[i + cur_pos] == '0') {
//熄滅第i個燈:通過配置數據寄存器
GPM4DAT |= 1 << (i + cur_pos) ; //把第i個IO口配置爲高電平,
//當使用%d時候要注意cur_pos類型,否則輸出會不正確
printk(KERN_EMERG"第%d個燈滅\r\n", i + 1 + (int)cur_pos);
}
else if(kbuf[i + cur_pos ] == '1') {
//點亮第i個燈:通過配置數據寄存器
GPM4DAT &= ~( 1 << (i + cur_pos)); //把第i個IO口配置爲低電平,
//當使用%d時候要注意cur_pos類型,否則輸出會不正確
printk(KERN_EMERG"第%d個燈亮\r\n", i + 1 + (int)cur_pos);
}
}
//更新文件讀寫位置,不能少,少了得不到正確的結果
*poff += count;
return count;
}
2.文件指針
用戶空間函數原型:
off_t lseek(int fd, off_t offset, int whence);
驅動中對應的函數原型:
loff_t xxx_llseek(struct file *file, loff_t offset, int whence)
作用: 按照 whence 指定的方式把 fd 文件的讀寫指針調整 offset 偏移。
offset: 需要調整的偏移,值是可正數,可負數
whence: 調整方式,只有三種值: SEEK_SEK,SEEK_CUR,SEEK_END
whence | |
---|---|
SEEK_SET: | 以文件開頭( 0)爲參照點移動到 offset 處。 |
SEEK_CUR: | 以文件指針 [當前位置 + offset ]做爲新的 offset 指針位置。 |
SEEK_END: | 以文件[末尾位置 + offset] 做爲新的 offset 指針位置。 |
返回值:
成功: 移動後文件指針位置fpos(以 0 爲參照點);
失敗: -1,並且設置全局變量 errno 值爲對應的錯誤碼(來源碼驅動)
說明:f_pos需要自己在驅動函數中使用,並且返回;否則無作用。
例子代碼:
static loff_t xxx_llseek(struct file *pfile, loff_t off, int whence)
{
loff_t temp;
printk(KERN_EMERG"file:%s\r\nline:%d, %s is call\n", __FILE__, __LINE__, __FUNCTION__);
switch(whence) {
case SEEK_SET:
temp = off;
break;
case SEEK_CUR:
temp = pfile->f_pos + off; //當前位置加上調整值
break;
case SEEK_END:
temp = LED_NUM + off; //文件大小加上調整值
break;
default:
return -EINVAL; //告訴應用程序具體錯誤原因是參數無效
}
//檢測最後調整結果是否合法
if((temp < 0) || (temp > LED_NUM) ) {
return -EINVAL;
}
//更新調整後結果到文件讀寫位置變量中
pfile->f_pos = temp;
//返回調整後的結果
return temp;
}
3.標準 ioctl 接口
用戶空間函數原型:
int ioctl(int fd, int request, ...);
驅動中對應的函數原型:
long (*unlocked_ioctl) (struct file *pfile, unsigned int cmd, unsigned long args);
作用: 通過命令形式來控制硬件設備,相當 linux 系統給我們提供擴展系統功能的一個接口,可以由用戶自定義命令來讓硬件執行不同的代碼。
參數:
pfile: 文件結構指針,間接對應於系統調用的 fd 參數(文件描述符)
cmd:命令,對應於系統調用的 request
args: args 系統調用的…參數(可選參數)
返回值:
cmd 命令執行成功:>0 ,值是什麼含義由驅動程序中決定。
cmd 命令執行失敗:<0,返回失敗錯誤碼, 比如 args 參數非法,則返回-EFAULT,所返回的錯誤碼會被系統
特別注意:這裏解釋了 cmd 值的組成格式:
bits | meaning |
---|---|
31-30 | 用戶程序和驅動數據交換模式 |
29-16 | 當用戶程序和驅動程序有數據傳遞時候纔有效,表示要傳遞的數據大小。 |
15-8 | 魔數,幻數, 給每一個驅動分配一個惟一的 ASCII 值,用於區分一個驅動的cmd 和別的不一樣。 |
7-0 | 同一個設備驅動中所有 cmd 命令的編號,範圍 0~255,一般情況同一個驅動值都是連續的。 |
用戶程序和驅動數據交換模式:
00 – 用戶程序和驅動沒有數據傳遞 uses _IO macro
01 – 用戶程序向驅動寫數據: _IOW
10 – 用戶程序從驅動讀取數據: _IOR
11 - 先用戶程序寫數據到驅動,再從驅動中讀取數據(先寫數據然後讀取數據回來) : _IOWR
內核在(Ioctl.h linux-3.5\include\Asm-generic)提供了相應的宏給我們使用,我們可以在應用層使用:
_IO(type,nr) :定義沒有數據傳遞的命令
_IOR(type,nr,size) :定義從驅動中讀取數據的命令
_IOW(type,nr,size) : 定義向驅動寫入數據的命令
_IOWR(type,nr,size) : 定義數據交換類型的命令,先寫入數據,再讀取數據這類命令。
type: 表示命令組成的魔數,也就是 8~15 位
nr: 表示命令組成的編號,也就是 0~7 位
size: 表示命令組成的參數傳遞大小, 但是這裏不傳遞數字,而是數據類型, 如要傳遞 4 字節,可以寫 int,如要傳遞一個結構體數據給驅動,則把結構類型做 size 參數。
代碼例子:
long chrdev_unlocked_ioctl (struct file *pfile,unsigned int cmd, unsigned long arg)
{
switch ( cmd ) {
case 0 :
printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
break;
case 1 :
printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
break;
case 2 :
printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
break;
case 3 :
printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
break;
case 4 :
printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
break;
default:
printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
return -EINVAL;
}
return arg;
}