write:向设备写入数据,单独这个接口并不能满足现实设备控制的全部需求。例如一个LCD
控制器:主要作用是驱动lcd
屏,要显示就是通过write
接口把显示数据发给lcd
控制器指定的显存。而参数设置类参数通过write
接口设置就可能回和普通的显示数据弄混了。为了解决这个问题,内核提供一个接口ioctl
对设备进行控制(参数设置,参数查询等功能)。ioctl
主要实现不太好实现的功能。
#include
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:执行失败
文件操作结构体中的定义如下;
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:参数无效
设计思路:由于控制程序ioctl
函数中request
参数对应驱动程序cmd
参数,而应用程序通过传递不同的值来告诉驱动程序做不同的事情,所以,驱动中unlocked_ioctl
接口代码函数内部一定是要判断cmd
值执行不同的代码段,所以就是一个if
语句或者是一个switch
语句。完成对cmd
的判断。
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:命令
//例如用户空间有
unsigned char buf[100];
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);
//固定写4个字节,这样写不太规范实际应该从命令中获取数据数目
}
long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//这里args就是把用户空间传下来的buf的地址转换成数字
//这里再把数字还原成指针
copy_to_user(kbuf,(void*)args,4);
}
long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//这里args就是把用户空间传下来的buf的地址转换成数字
//这里再把数字还原成指针
copy_from_user(kbuf,(void*)args,4);
//固定写4个字节,这样写不太规范实际应该从命令中获取数据数目
······
copy_to_user(kbuf,(void*)args,4);
//固定写4个字节,这样写不太规范实际应该从命令中获取数据数目
}
long leddriver_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
unsigned char LEN_NUM=4;
int ret=0;
int nr=0;
switch(cmd)
{
case LED_ALL_ON:
GPM4DAT &= ~(0xf<<0);
break;
case LED_ALL_OFF:
GPM4DAT |= (0XF<<0);
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 &= ~(1<else{
GPM4DAT |= (1<break;
default:
return -EINVAL;
break;
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //增加自动创建设备头文件
#include
#include "iocmd.h"
//定义字符设备结构体
static struct cdev *leddriver_cdev;
//定义设备号(包含主次)
static dev_t leddriver_num=0;
//定义设备类
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[10],i,LED_NUM=4;
//读取数据长度为0什么也不做 返回0 退出程序的执行
if(size<=0)
{
return 0;
}
//读取位置在末尾 无论size是多少都不能读出数据 数据有效区域越界
if(cur_pos>=LED_NUM)
{
return 0;
}
//判断size+当前位置是大于文件大小,只读取有效位的内容
if(cur_pos+size>LED_NUM)
{
size=LED_NUM-cur_pos;
}
for(i=0;iif(GPM4DAT &(1<1;
else
led_statue[i]=0;
}
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[10],i,LED_NUM=4;
//写入数据大小为0 什么也不操作返回0退出
if(size<=0)
{
return 0;
}
//当前位置大于等于文件最大有效数据,即使写入数据也是无效,所依不进行操作 返回0退出
if(cur_pos>=LED_NUM)
{
return 0;
}
//如果当前位置加上所要读取数据的长度大于剩余有效位 只读取有效数据位的数值
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=0;iif(led_statue[i+cur_pos]==0)
GPM4DAT &= ~(1<<(i+cur_pos));
else
GPM4DAT |= (1<<(i+cur_pos));
}
*loft+=size;
return size;
}
int leddriver_open (struct inode *node, struct file *pfile)
{
printk("files open is success\r\n");
return 0;
}
loff_t leddriver_llseek(struct file *pfile, loff_t loft, int whence){
loff_t tmp;
unsigned char LED_NUM=4;
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<0 || 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 0;
}
long leddriver_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
unsigned char LEN_NUM=4;
int ret=0;
int nr=0;
switch(cmd)
{
case LED_ALL_ON:
GPM4DAT &= ~(0xf<<0);
break;
case LED_ALL_OFF:
GPM4DAT |= (0XF<<0);
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 &= ~(1<else{
GPM4DAT |= (1<break;
default:
return -EINVAL;
break;
}
return 0;
}
//文件操作函数结构体
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, 0, 1, LEDDRIVER_NAME);
//错误判断
if(err<0)
{
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,1);
if(err<0)
{
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, 4);
gpm4dat=ioremap(GPM4DAT_ADDR, 4);
GPM4CON &= ~(0XFFFF<<0);
GPM4CON |= (0x1111<<0);
GPM4DAT |= (0XF<<0);
printk("leddriver init is success\r\n");
return 0;
err_device_create:
class_destroy(leddriver_class);
err_class_create:
cdev_del(leddriver_cdev);
err_cdev_add:
unregister_chrdev_region(leddriver_num, 1);
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, 1);
printk("leddriver is exit\r\n");
}
module_init(ldedriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
#include
#include
#include
#include
#include
#include
#include "iocmd.h"
int main(int argc,char *argv[])
{
int led_fd,i;
led_fd = open(argv[1],O_RDWR);
while(1)
{
ioctl(led_fd,LED_ALL_ON);
sleep(2);
for(i=0;i<4;i++)
{
ioctl(led_fd,LED_OFF_N,i);
sleep(1);
}
for(i=0;i<4;i++)
{
ioctl(led_fd,LED_ON_N,i);
sleep(1);
}
ioctl(led_fd,LED_ALL_OFF);
sleep(2);
}
sleep(1);
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_
KERN_DIR = /zhangchao/linux3.5/linux-3.5
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