ioctl接口作用
write:向裝置寫入資料,單獨這個接口并不能滿足現實裝置控制的全部需求。例如一個
LCD
控制器:主要作用是驅動
lcd
屏,要顯示就是通過
write
接口把顯示資料發給
lcd
控制器指定的顯存。而參數設定類參數通過
write
接口設定就可能回和普通的顯示資料弄混了。為了解決這個問題,核心提供一個接口
ioctl
對裝置進行控制(參數設定,參數查詢等功能)。
ioctl
主要實作不太好實作的功能。
ioctl系統調用接口
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
功能:
給系統通過指令形式,控制硬體裝置,相當于
linux
系統給我們提供了系統擴充功能的一個接口,
read`` write
等固定用法,而
ioctl
可由使用者自定義指令來執行不同代碼。
參數:
d:檔案描述符
request:指令(可以是系統指令,也可以是自定義的)
···:表示變參,相當于
printf
參數一樣,可以有,可以沒有。是否需要和
request
指令有關
示例說明可變參的用法:
1)
0x10
表示開全部燈,
2)
0x20
表示第
N
個燈,
3)
0x30
表示關第
N
個燈,
4)
0x40
表示關全部燈。
關閉、開啟第幾個燈N可以由可變參傳入來決定
傳回值:
>=0:成功,>0具體什麼含義由驅動程式決定
-1:執行失敗
ioctl接口驅動模闆
檔案操作結構體中的定義如下;
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
是以函數原型應該是:
long xxx_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long args);
功能:
對應于系統調用API的ioctl函數
int ioctl(int d, int request, ...);
參數對應關系:
pfile:檔案結構指針,間接對應與使用者空間ioctl函數的fd參數
cmd:直接對應于request
args:對應于可選參數···
傳回值:
>=0:成功,>0具體什麼含義由驅動程式決定
<1:執行失敗 傳回失敗錯誤碼,不一定是
-1
,但是隻要是錯誤碼,在上層一定是
-1
,這裡的錯誤碼提供給核心,比如記憶體不足,傳回
-EFAULT
,所傳回的錯誤碼會被儲存在系統全局變量
errno
中。
EFAULT:args 非法
EINVAL:參數無效
接口測試實驗
寫一個驅動代碼實作unlocked_ioctl接口
設計思路:由于控制程式
ioctl
函數中
request
參數對應驅動程式
cmd
參數,而應用程式通過傳遞不同的值來告訴驅動程式做不同的事情,是以,驅動中
unlocked_ioctl
接口代碼函數内部一定是要判斷
cmd
值執行不同的代碼段,是以就是一個
if
語句或者是一個
switch
語句。完成對
cmd
的判斷。
标準ioctl接口指令的合成
ioctl指令規則
1.系統自定義指令:執行優先級高于使用者自定義指令
2.使用者自定義指令
ioctl指令執行:不直接就執行驅動中的
unlocked_ioctl
接口,而是先根據
cmd
情況是否屬于預定義指令,如果是則去執行,完成後傳回,傳回後可能執行使用者自定義的指令,也可能直接傳回,不執行使用者指令。
避免指令沖突:核心為解決這個問題,定義了一個規則,指令是特定格式組成的,
核心說明文檔:
ioctl-decoding.txt \linux-3.5\Documentation\ioctl
ioctl-number.txt \linux-3.5\Documentation\ioctl
編碼格式:
位段 | 含義 |
---|---|
31-30 | 指令傳輸方向 |
00 | no parameters: uses _IO macro 沒有參數:使用宏_IO |
10 | read: _IOR 從核心讀取讀取:使用宏_IOR |
01 | write: _IOW 向核心寫入:使用宏_IOW |
11 | read/write: _IOWR 讀/寫:使用宏_IOWR |
29-16 | size of arguments 使用者和驅動之間有資料傳輸時有效表示資料大小 |
15-8 | ascii character supposedly unique to each driver 給每一個驅動配置設定一個字元區分 |
7-0 | function #同一個驅動中所有cmd指令編号0~255,一般值都是連續的 |
核心合成宏:
路徑: 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:
b
表示指令組成參數傳遞的大小,但是這裡傳遞的不是數字,而是資料類型
分解宏 | 含義 |
---|---|
_IOC_DIR(nr) | 分解指令的方向 |
_IOC_TYPE(nr) | 分解指令的魔數 |
_IOC_NR(nr) | 分解指令的編号 |
_IOC_SIZE(nr) | 分解指令的大小 |
參數:
nr:指令
利用ioctl向驅動寫資料
//例如使用者空間有
unsigned char buf[];
ioctl(fd,cmd,(unsigned long)buf);
long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//這裡args就是把使用者空間傳下來的buf的位址轉換成數字
//這裡再把數字還原成指針
copy_from_user(kbuf,(void*)args,);
//固定寫4個位元組,這樣寫不太規範實際應該從指令中擷取資料數目
}
利用ioctl從驅動讀取資料
long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//這裡args就是把使用者空間傳下來的buf的位址轉換成數字
//這裡再把數字還原成指針
copy_to_user(kbuf,(void*)args,);
}
互動資料 先讀取使用者空間 再傳給使用者空間
long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//這裡args就是把使用者空間傳下來的buf的位址轉換成數字
//這裡再把數字還原成指針
copy_from_user(kbuf,(void*)args,);
//固定寫4個位元組,這樣寫不太規範實際應該從指令中擷取資料數目
······
copy_to_user(kbuf,(void*)args,);
//固定寫4個位元組,這樣寫不太規範實際應該從指令中擷取資料數目
}
驅動接口函數接口函數示例
long leddriver_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
unsigned char LEN_NUM=;
int ret=;
int nr=;
switch(cmd)
{
case LED_ALL_ON:
GPM4DAT &= ~(<<);
break;
case LED_ALL_OFF:
GPM4DAT |= (<<);
break;
case LED_ON_N:
case LED_OFF_N:
ret=copy_from_user(&nr,(void *)args,_IOC_SIZE(cmd));
//資料拷貝失敗 傳回錯誤碼
if(ret)
{
return -EFAULT;
}
//如果燈的标号大于等于4 傳回錯誤碼
if(nr>= LED_NUM)
{
return -EINVAL;
}
if(cmd == LED_ON_N)
{
GPM4DAT &= ~(<<nr);
}else{
GPM4DAT |= (<<nr);
}
break;
default:
return -EINVAL;
break;
}
}
完整代碼
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自動建立裝置頭檔案
#include<linux/uaccess.h>
#include "iocmd.h"
//定義字元裝置結構體
static struct cdev *leddriver_cdev;
//定義裝置号(包含主次)
static dev_t leddriver_num=;
//定義裝置類
static struct class *leddriver_class;
//定義裝置結構體
static struct device *leddriver_device;
//定義錯誤傳回類型
static int err;
//定義裝置名稱
#define LEDDRIVER_NAME "myled"
#define GPM4CON_ADDR 0x110002E0
#define GPM4DAT_ADDR 0X110002E4
static volatile unsigned long *gpm4con=NULL;
static volatile unsigned long *gpm4dat=NULL;
#define GPM4CON *gpm4con
#define GPM4DAT *gpm4dat
ssize_t leddriver_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
loff_t cur_pos=*loft;//取出目前讀寫位置值
unsigned char led_statue[],i,LED_NUM=;
//讀取資料長度為0什麼也不做 傳回0 退出程式的執行
if(size<=)
{
return ;
}
//讀取位置在末尾 無論size是多少都不能讀出資料 資料有效區域越界
if(cur_pos>=LED_NUM)
{
return ;
}
//判斷size+目前位置是大于檔案大小,隻讀取有效位的内容
if(cur_pos+size>LED_NUM)
{
size=LED_NUM-cur_pos;
}
for(i=;i<LED_NUM;i++)
{
if(GPM4DAT &(<<i))
led_statue[i]=;
else
led_statue[i]=;
}
if(copy_to_user(usr,&led_statue[cur_pos],size)){
printk("copy to user err\r\n");
return -EFAULT;
};
//指針重新定位目前位置
*loft+=size;
return size;
}
ssize_t leddriver_write (struct file *file, const char __user *usr, size_t size, loff_t *loft)
{
loff_t cur_pos=*loft;
unsigned char led_statue[],i,LED_NUM=;
//寫入資料大小為0 什麼也不操作傳回0退出
if(size<=)
{
return ;
}
//目前位置大于等于檔案最大有效資料,即使寫入資料也是無效,所依不進行操作 傳回0退出
if(cur_pos>=LED_NUM)
{
return ;
}
//如果目前位置加上所要讀取資料的長度大于剩餘有效位 隻讀取有效資料位的數值
if(size+cur_pos>LED_NUM)
{
size=LED_NUM-cur_pos;
}
if(copy_from_user(&led_statue[cur_pos],usr,size))
{
printk("copy from user err\r\n");
return -EFAULT;
}
for(i=;i<size;i++)
{
if(led_statue[i+cur_pos]==)
GPM4DAT &= ~(<<(i+cur_pos));
else
GPM4DAT |= (<<(i+cur_pos));
}
*loft+=size;
return size;
}
int leddriver_open (struct inode *node, struct file *pfile)
{
printk("files open is success\r\n");
return ;
}
loff_t leddriver_llseek(struct file *pfile, loff_t loft, int whence){
loff_t tmp;
unsigned char LED_NUM=;
switch(whence)
{
case SEEK_SET:
tmp=loft;
break;
case SEEK_CUR:
tmp=pfile->f_pos+loft; //目前位置加上調整值
break;
case SEEK_END:
tmp=LED_NUM+loft;
break;
default:
return -EINVAL;//告訴程式具體錯誤原因 參數無效
break;
}
//檢測最後的結果是否合法
if(tmp< || tmp>LED_NUM)
{
return -EINVAL;
}
//更新檔案調整後的結果到檔案結構體中
pfile->f_pos=tmp;
//傳回調整後的結果
return tmp;
}
int leddriver_release (struct inode *node, struct file *file)
{
printk("leddriver close is success\r\n");
return ;
}
long leddriver_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
unsigned char LEN_NUM=;
int ret=;
int nr=;
switch(cmd)
{
case LED_ALL_ON:
GPM4DAT &= ~(<<);
break;
case LED_ALL_OFF:
GPM4DAT |= (<<);
break;
case LED_ON_N:
case LED_OFF_N:
ret=copy_from_user(&nr,(void *)args,_IOC_SIZE(cmd));
//資料拷貝失敗 傳回錯誤碼
if(ret)
{
return -EFAULT;
}
//如果燈的标号大于等于4 傳回錯誤碼
if(nr>= LED_NUM)
{
return -EINVAL;
}
if(cmd == LED_ON_N)
{
GPM4DAT &= ~(<<nr);
}else{
GPM4DAT |= (<<nr);
}
break;
default:
return -EINVAL;
break;
}
return ;
}
//檔案操作函數結構體
static struct file_operations leddriver_fops={
.owner=THIS_MODULE,
.open=leddriver_open,
.release=leddriver_release,
.read=leddriver_read,
.write=leddriver_write,
.llseek=leddriver_llseek,
unlocked_ioctl=leddriver_ioctl,
};
static __init int ldedriver_init(void)
{
//配置設定字元裝置結構體,前面隻是定義沒有配置設定空間
leddriver_cdev=cdev_alloc();
//判斷配置設定成功與否
if(leddriver_cdev==NULL)
{
err=-ENOMEM;
printk("leddriver alloc is err\r\n");
goto err_leddriver_alloc;
}
//動态配置設定裝置号
err=alloc_chrdev_region(&leddriver_num, , , LEDDRIVER_NAME);
//錯誤判斷
if(err<)
{
printk("alloc leddriver num is err\r\n");
goto err_alloc_chrdev_region;
}
//初始化結構體
cdev_init(leddriver_cdev,&leddriver_fops);
//驅動注冊
err=cdev_add(leddriver_cdev,leddriver_num,);
if(err<)
{
printk("cdev add is err\r\n");
goto err_cdev_add;
}
//建立裝置類
leddriver_class=class_create(THIS_MODULE,"led_class");
err=PTR_ERR(leddriver_class);
if(IS_ERR(leddriver_class))
{
printk("leddriver creat class is err\r\n");
goto err_class_create;
}
//建立裝置
leddriver_device=device_create(leddriver_class,NULL, leddriver_num,NULL, "leddevice");
err=PTR_ERR(leddriver_device);
if(IS_ERR(leddriver_device))
{
printk("leddriver device creat is err \r\n");
goto err_device_create;
}
//led燈寄存器配置
gpm4con=ioremap(GPM4CON_ADDR, );
gpm4dat=ioremap(GPM4DAT_ADDR, );
GPM4CON &= ~(<<);
GPM4CON |= (<<);
GPM4DAT |= (<<);
printk("leddriver init is success\r\n");
return ;
err_device_create:
class_destroy(leddriver_class);
err_class_create:
cdev_del(leddriver_cdev);
err_cdev_add:
unregister_chrdev_region(leddriver_num, );
err_alloc_chrdev_region:
kfree(leddriver_cdev);
err_leddriver_alloc:
return err;
}
static __exit void leddriver_exit(void)
{
//取消映射
iounmap(gpm4con);
iounmap(gpm4dat);
device_destroy(leddriver_class,leddriver_num);
class_destroy(leddriver_class);
cdev_del(leddriver_cdev);
unregister_chrdev_region(leddriver_num, );
printk("leddriver is exit\r\n");
}
module_init(ldedriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
app函數
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "iocmd.h"
int main(int argc,char *argv[])
{
int led_fd,i;
led_fd = open(argv[],O_RDWR);
while()
{
ioctl(led_fd,LED_ALL_ON);
sleep();
for(i=;i<;i++)
{
ioctl(led_fd,LED_OFF_N,i);
sleep();
}
for(i=;i<;i++)
{
ioctl(led_fd,LED_ON_N,i);
sleep();
}
ioctl(led_fd,LED_ALL_OFF);
sleep();
}
sleep();
close(led_fp);
}
公共頭檔案
#ifndef _IOCMD_H_
#define _IOCMD_H_
#define LED_ALL_ON _IO('L',0)
#define LED_ALL_OFF _IO('L',1)
#define LED_ON_N _IOW('L',2,int)
#define LED_OFF_N _IOW('L',3,int)
#endif //_IOCMD_H_
Makefile
KERN_DIR = /zhangchao/linux3./linux-
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp ./* /zhangchao/rootfs/zhangchao
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led.o