ioctl的驅動接口一般是作用在一些标準接口無法實作的功能。如和主要晶片連結的很多外設ic,主要對這些晶片的功能設定以及狀态的擷取等。是以 ioctl 接口可以看成是系統給我們進行功能擴充的的專用接口。
系統調用接口原型:
int ioctl(int d, int request, ...);
這個函數是一個可變參數函數,最少需要2個參數
參數:
d: 是檔案描述符号
request: 通常是 cmd 。dongjieko
...:可變參數,可以有,也可以沒有,根據 request 情況而定。
示例:
一個指令: 所有燈開 -- 隻需要一個cmd ,不需要其他參數
一個指令:指定燈開 -- 需要一個cmd ,還需要知道操作哪個燈的, 這種情況下就需要第3個參數。
傳回值:
成功:通常是傳回 0, 如果是非标準的cmd,傳回是使用者自定義的正數。 如,可以使用這個接口實作llseek功能。
失敗:傳回 的是-1;
驅動程式 ioctl 接口原型:
3.0以上的核心,(2。6核心是 ioctl)
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg);
file: 檔案描述通過VFS轉換而來
cmd:就是應用程式傳遞下來的 request 參數
arg: 對應于應用程式傳遞下第三個參數(變參);
如以下的驅動執行個體:
//ioctl(fd,cmd,arg)
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;
}
比如:應用程式的代碼:
for(i=0;i<6;i++)
{
int ret ;
ret = ioctl(fd,i,10+i); //args 從10開始
sleep(1);
printf("\nret = %d\n",ret);
}
安裝子產品後執行應用程式輸出:
[[email protected]/home]# ./app
[ 8698.345000] line:102,cmd:0,arg:10
ret = 10
line:105,cmd:1,arg:11
ret = 11
//此處應該是12,但是沒有執行到。
ret = -1
line:111,cmd:3,arg:13
ret = 13
line:114,cmd:4,arg:14
ret = 14
line:117,cmd:5,arg:15
ret = -1
結果:
1。驗證以上所說的參數對應關系
2。驗證以上所的傳回值
3。當cmd的值為2時,驅動程式沒有執行!
重點分析一下第3個現象:
ioctl接口比較特殊,不像其他接口通過fd直接找到驅動,
ioclt 接口的cmd值在核心中有規範。不是随便可以使用一個數字就可以了。有特定的編碼規則。
當cmd值和系統一些預定義指令相同時,代碼所執行不會是我們的驅動代碼中的ioctl,而是被攔截了,去執行相同的預定義指令去了。
上面的2這個值就屬于這種現象。。。 從2。6核心到現在,cmd為2的值不能使用的。
要想安全使用ioctl接口,必須使用核心規定的定義方法來定義cmd值。
核心文檔 Ioctl-decoding.txt (documentation\ioctl) 有說明:
bit | 含義 |
31-30 | 00-no parameters: uses _IO macro系統調用 ioctl ,當cmd值的31:30為00時候,應該 ioctl(fd,cmd),不能有3個參數,否則[可能]和其他指令沖突。 10 - read: _IOR代表想通過ioctl從驅動中讀取資料回來,存放在 ioctl(fd,cmd,arg)中的argr所代表的記憶體空間中(arg應該是一個可寫記憶體位址)。 01 - write: _IOW代表想通過ioctl從向驅動中寫入資料,資料存放在 ioctl(fd,cmd,arg)中的arg所指向記憶體空間中(arg應該是一個可讀記憶體位址)。 11 - read/write: _IOWR代表想通過ioctl從向驅動中寫入資料,資料存放在 ioctl(fd,cmd,arg)中的arg所指向記憶體空間中(arg應該是一個可讀記憶體位址)。 |
29-16 | 就是參數大小 14位,最多隻能傳遞16K資料 當 cmd 31-30 不為0時候表示 調用方法 ioctl(fd,cmd,arg) ,表示有資料傳遞。 |
15-8 | 驅動 魔數/幻數,範圍00~FF用來辨別一個驅動,原則上講一個驅動有惟一值,隻要這個值不同,就不會和其他指令沖突。 核心中已經占用大部分的魔數。原則可以由使用者定義。核心文檔 Ioctl-number.txt (documentation\ioctl)告訴數字已經被使用了。 |
7-0 | function 指令功能 範圍:0~255 其實這個才是真正的指令碼。像前面的0,1,2,3 |
示例:
定義一個關燈指令:
#define LED_X_OFF 1<<30 | 1<<16 | 'A' <<8 | 0
定義一個開燈指令:
#define LED_X_ON 1<<30 | 1<<16 | 'A' <<8 | 1
定義一個全部燈關指令:
#define LED_ALL_OFF 0<<30 | 0<<16 | 'A' <<8 | 2
定義一個全部開燈指令:
#define LED_ALL_ON 0<<30 | 0<<16 | 'A' << 8 | 3
定義一個讀指定燈狀态指令:
#define LED_X_STATUS 2<<30 | 1<<16 | 'A' <<8 | 4
這樣太麻煩了,核心已經提供了相應的宏合成指令:
_IO(type,nr) :定義一個沒有參數的指令
_IOR(type,nr,size) :定義一個讀方向的指令
_IOW(type,nr,size) :定義一個寫方向的指令
_IOWR(type,nr,size) :定義一個資料雙向的指令
type:就是魔數
nr:功能碼
size:參數大小,其實應該傳遞參數類型。(非指針)
也提供反向分解代碼:
#define _IOC_DIR(nr) 取出方向值
#define _IOC_TYPE(nr) 取魔數
#define _IOC_NR(nr) 取出功能碼
#define _IOC_SIZE(nr) 取出大小
所在上面的定義可以修改:
示例:
定義一個關燈指令:
#define LED_MAGI 'A'
#define LED_MAX 4
#define LED_X_OFF _IOW(LED_MAGI,0,char) // 1<<30 | 1<<16 | 'A' <<8 | 0
定義一個開燈指令:
#define LED_X_ON _IOW(LED_MAGI,1,char) // 1<<30 | 1<<16 | 'A' <<8 | 1
定義一個全部燈關指令:
#define LED_ALL_OFF _IO(LED_MAGI,2) //0<<30 | 0<<16 | 'A' <<8 | 2
定義一個全部開燈指令:
#define LED_ALL_ON _IO(LED_MAGI,3) // 0<<30 | 0<<16 | 'A' << 8 | 3
定義一個讀指定燈狀态指令:
#define LEDS_STATUS _IOR(LED_MAGI,4,int) // 2<<30 | 1<<16 | 'A' <<8 | 4
//每位元組存放一個燈狀态, 驅動中有4個燈。
以上内容單獨定義在一個.h檔案中,然後把drv,app放在在相同目錄下。
drv,app都包含相同的頭檔案 就可以了。
應用程式使用:
int main()
{
....
char led_nr;
int state;
//關指定燈
led_nr =2;
ioctl(fd,LED_X_OFF,&lednr);
//關所有燈
ioctl(fd,LED_ALL_OFF);
//讀燈狀态
ioctl(fd,LEDS_STATUS,&state);
}
驅動代碼:
//ioctl(fd,cmd,arg)
long chrdev_unlocked_ioctl (struct file *pfile,
unsigned int cmd,
unsigned long arg)
{
char *pdata;
int count = _IOC_SIZE(cmd) ; //取得複制數量
//出于效率還要做一個判斷
if(_IOC_TYPE(cmd) != LED_MAGI)
return -1;
if(_IOC_NR(cmd) > LED_MAX)
return -1;
switch ( cmd )
{
case LED_X_OFF :
printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
pdata = kmalloc( _IOC_SIZE(cmd)); //配置設定緩沖區
if(_IOC_SIZE(cmd) > 4)
count = 4;
copy_from_user(pdata, (const void __user *)arg, count);
for(i=0;i<count;i++)
{
rGPM4DAT |= (1 << (0 + pdata[i])); /*滅 */
}
kfree(pdata);
case LED_X_ON :
printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
if(_IOC_SIZE(cmd) > 4)
count = 4;
copy_from_user(pdata, (const void __user *)arg, count);
for(i=0;i<count;i++)
{
rGPM4DAT &= ~ (1 << (0 + pdata[i])); /* 亮 */
}
break;
case LED_ALL_OFF :
printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
//for 可以
rGPM4DAT |= (1 << (0 + 0); /* 滅 */
rGPM4DAT |= (1 << (0 + 1); /* 滅 */
rGPM4DAT |= (1 << (0 + 2); /* 滅 */
rGPM4DAT |= (1 << (0 + 3); /* 滅 */
break;
case LED_ALL_ON:
printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
//for 可以
rGPM4DAT &= ~ (1 << (0 + 0); /* 滅 */
rGPM4DAT &= ~ (1 << (0 + 1); /* 滅 */
rGPM4DAT &= ~ (1 << (0 + 2); /* 滅 */
rGPM4DAT &= ~ (1 << (0 + 3); /* 滅 */
break;
case LEDS_STATUS :
printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
if(_IOC_SIZE(cmd) > 4)
count = 4;
pdata = kmalloc( count); //配置設定緩沖區
for(i=0;i< count;i++)
{
pdata[i] = !( rGPM4DAT & 1<<(0 + i) );
}
copy_to_user( arg, pdata, _IOC_SIZE(cmd));
kfree(pdata);
break;
default:
printk("line:%d,cmd:%d,arg:%ld",__LINE__,cmd,arg);
return - EINVAL;
}
return 0;
}