简介
原来曾经写过一个led灯的驱动,调用read write函数对灯的亮灭进行操作,虽然达到了控制的目的,学过系统编程的人知道,其实标准的read write函数的用法,并不是这样的,在对文件操作时,我们读取相关文件内容时,每当我们读取一部分内容时,文件内位置指针会随着移动,在进行文件内容读取时,读取内容会在当前位置读取一定数量的内容。写操作同样。当我们写一些内容到一个文件,例如将1234456789
写到new.txt
中,先写1234
,在写56789
,打开文件我们会发现,写入内容为123456789
,后写入的56789
并没有将原来写入的1234
覆盖掉(这两次写操作均是在一次文件打开中执行,关闭后重新打开会覆盖掉原来的内容)。我们写的read
write
函数,进行例如读取灯的状态时,每次读一个灯的状态,往往读取的都是第一个灯的状态,并不会随着我们读取次数的增加,而灯的位置发生变化,驱动比较笨不会进行位置偏移,或者写的并不符合标准,就这个问题将给出标准的基于led
read
write
函数的写法,实现像操作文件一样去操作灯。
驱动层read
write
函数原型
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
我们可以看到这两个函数与我们写上层应用时,参数是不一样的,多了一个loff_t *
。这就是驱动层与应用层的区别,而我们实现驱动led
灯,像操作文件一样,这个参数至关总要。这个参数是干什么的呢?这个参数记录着当前操作位置。也就是一个文件内有123456789
内容我先读取了1234
这几项内容,假如文件没有关闭,再继续读文件会从5开始读取,并不会从1开始读取,实现这个功能就是loff_t *
的作用,它会把当前文件操作的位置记录下来,方便下次操作。
之前的reead write代码
ssize_t leddriver_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
unsigned char LED_STATUE;
copy_from_user(&LED_STATUE,usr,1);
printk("leddriver read is %d\r\n",LED_STATUE);
switch(LED_STATUE)
{
case 10:GPM4DAT |= (0x1<<0);break;
case 11:GPM4DAT &=~(0X1<<0);break;
case 20:GPM4DAT |= (0x1<<1);break;
case 21:GPM4DAT &=~(0X1<<1);break;
case 30:GPM4DAT |= (0x1<<2);break;
case 31:GPM4DAT &=~(0X1<<2);break;
case 40:GPM4DAT |= (0x1<<3);break;
case 41:GPM4DAT &=~(0X1<<3);break;
}
return 0;
}
只能传入参数控制某个灯亮
新的标准led read write函数
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;i<LED_NUM;i++)
{
if(GPM4DAT &(1<<i))
led_statue[i]=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;i<size;i++)
{
if(led_statue[i+cur_pos]==0)
GPM4DAT &= ~(1<<(i+cur_pos));
else
GPM4DAT |= (1<<(i+cur_pos));
}
*loft+=size;
return size;
}
验证led write的app函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int led_fp,i;
unsigned char led_statue[4]={1,1,1,1},statue[4]={0};
led_fp = open(argv[1],O_RDWR);
write(led_fp,led_statue,2);
sleep(2);
write(led_fp,led_statue,2);
close(led_fp);
}
运行这个app函数会发现,两个灯先亮,两秒之后,剩余两个灯再次亮起。实现箱操作文件那样,可以在原来的基础上进行操作的用法。
验证led read的app函数
因为lseek函数还没写,所以文件在上一步写完后会定位到,文件末尾,在进行读操作,什么也读不到。所以进行写操作后,需要将文件关闭,再次打开在进行读取验证,否则验证结果不正确。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int led_fp,i;
unsigned char led_statue[4]={1,1,0,1},statue[4]={0};
led_fp = open(argv[1],O_RDWR);
read(led_fp,statue,2);
for(i=0;i<2;i++)
{
if(statue[i]==0)
{
printf("第%d个灯亮\r\n",i+1);
}
else
{
printf("第%d个灯灭\r\n",i+1);
}
}
sleep(1);
read(led_fp,statue,2);
for(i=0;i<2;i++)
{
if(statue[i]==0)
{
printf("第%d个灯亮\r\n",i+1);
}
else
{
printf("第%d个灯灭\r\n",i+1);
}
}
close(led_fp);
}
运行app后会依次读取灯的状态。
总结
像操作文件一样操作led灯,就要依赖文件偏移参数。
总的驱动代码
#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>
//定义字符设备结构体
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;i<LED_NUM;i++)
{
if(GPM4DAT &(1<<i))
led_statue[i]=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;i<size;i++)
{
if(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 *file)
{
printk("files open is success\r\n");
return 0;
}
int leddriver_release (struct inode *node, struct file *file)
{
printk("leddriver close is success\r\n");
return 0;
}
//文件操作函数结构体
static struct file_operations leddriver_fops={
.owner=THIS_MODULE,
.open=leddriver_open,
.release=leddriver_release,
.read=leddriver_read,
.write=leddriver_write,
};
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");