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;
}