LED驱动.docx
《LED驱动.docx》由会员分享,可在线阅读,更多相关《LED驱动.docx(17页珍藏版)》请在冰豆网上搜索。
![LED驱动.docx](https://file1.bdocx.com/fileroot1/2022-11/28/ca5ca4c6-5f66-40d0-9ced-6b65a4f1d9cf/ca5ca4c6-5f66-40d0-9ced-6b65a4f1d9cf1.gif)
LED驱动
Linux系统下的LED驱动(X86平台)
1.Linux设备驱动与整个软硬件系统的关系
如图所示,除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数即可访问字符设备和块设备。
所有的字符设备和块设备都被统一地呈现给用户。
块设备比字符设备复杂,在它上面会首先建立一个磁盘/Flash文件系统,如FAT、Ext3、YAFFS、JFFS等。
2.Linux系统下的LED驱动
硬件环境:
PC104
软件环境:
使用的系统为红旗LinuxNotebook5.0(内核版本Linux2.6.16.9-9smpSMPPENTIUMgcc-3.4)编译的内核为2.6.16(注意版本的匹配,前面三位要一致)
Linux提供了这样一种机制——模块。
模块具有以下特点:
1模块本身不被编译进内核
2模块一旦被加载,它就和内核中的其他部分完全一样。
先看一个最简单的内核模块“HelloWorld”,代码如下所示
#include
#include
MODULE_LICENSE(“DualBSD/GPL”);
staticinthello_init(void)
{
printk(KERN_ALERT“HelloWorldenter\n”);
return0;
}
staticvoidhello_exit(void)
{
printk(KERN_ALERT“HelloWorldexit\n”);
}
module_init(hello_init);
module_eixt(hello_exit);
这个最简单的内核模块只包含内核加载函数、卸载函数和对GPL许可权限的声明以及一些描述信息。
编译它会产生hello.ko目标文件,通过“insmod./hello.ko”命令可以加载它,通过“rmmodhello”命令可以卸载它,加载时输出“HelloWorldenter”,卸载时输出“HelloWorldexit”。
加载完成后可以用modinfohello.ko命令查看模块的信息,包括作者、模块的说明。
模块所支持的参数以及编译这个模块用到的环境信息。
从上面这个最简单的例子中可以看出一个Linux内核模块主要由以下几个部分组成。
模块加载函数
模块卸载函数
模块许可证声明
前面三个部分是必须的,还有一些可选的,由于这里没有用到,所以留做以后再做说明。
模块加载函数必须以“module_init(函数名)”的形式被指定。
模块卸载函数必须以“module_exit(函数名)”的形式来指定。
2.1Linux字符设备驱动结构
2.1.1.cdev结构体
在Linux2.6内核中使用cdev结构体来描述字符设备,cdev结构体的定义代码如下:
structcdev
{
structkobjectkobj;
structmodule*owner;
structfile_operations*ops;
structlist_headlist;
dev_tdev;
unsignedintcount;
};
cdev结构体
cdev结构体的dev_t成员定义了设备号,为32位,其中高12为主设备号,低20位为次设备号。
使用下面宏可以从dev_t获得主设备号和次设备号。
MAJOR(dev_tdev)
MINOR(dev_tdev)
而使用下列宏则可以通过主设备号和次设备号生成dev_t
MKDEV(intmajor,intminor)
cdev结构体的另外一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。
Linux2.6内核提供了一组函数用于操作cdev结构体,就使用了的函数说明一下
voidcdev_init(structcdev*,structfile_operations*);初始化cdev的成员,并建立cdev和file_operations之间的连接
intcdev_add(structcdev*,dev_t,unsigned);模块加载函数中
voidcdev_del(structcdev*);模块卸载函数中
这两个函数是向系统添加和删除一个cdev,完成字符设备的注册和注销。
2.1.2.分配和释放设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或者alloc_chrdev_region()函数向系统申请设备号,这两个函数原型如下:
intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name);
用于已知设备的设备号
intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name);
向系统动态申请未被占用的设备号的情况。
相反地,在调用dev_del()函数从系统注销字符设备后,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下:
voidunregister_chrdev_region(dev_tfrom,unsignedcount);
2.1.3.file_operations结构体
file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open(),write(),read(),close()等系统调用时最终被调用。
File_operations的数据结构如下:
structmodule*owner
第一个file_operations成员根本不是一个操作;它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载.几乎所有时间中,它被简单初始化为THIS_MODULE,一个在中定义的宏.
loff_t(*llseek)(structfile*,loff_t,int);
llseek方法用作改变文件中的当前读/写位置,并且新位置作为(正的)返回值.loff_t参数是一个"longoffset",并且就算在32位平台上也至少64位宽.错误由一个负返回值指示.如果这个函数指针是NULL,seek调用会以潜在地无法预知的方式修改file结构中的位置计数器(在"file结构"一节中描述).
ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);
用来从设备中获取数据.在这个位置的一个空指针导致read系统调用以-EINVAL("Invalidargument")失败.一个非负返回值代表了成功读取的字节数(返回值是一个"signedsize"类型,常常是目标平台本地的整数类型).
ssize_t(*aio_read)(structkiocb*,char__user*,size_t,loff_t);
初始化一个异步读--可能在函数返回前不结束的读操作.如果这个方法是NULL,所有的操作会由read代替进行(同步地).
ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);
发送数据给设备.如果NULL,-EINVAL返回给调用write系统调用的程序.如果非负,返回值代表成功写的字节数.
ssize_t(*aio_write)(structkiocb*,constchar__user*,size_t,loff_t*);
初始化设备上的一个异步写.
int(*readdir)(structfile*,void*,filldir_t);
对于设备文件这个成员应当为NULL;它用来读取目录,并且仅对文件系统有用.
unsignedint(*poll)(structfile*,structpoll_table_struct*);
poll方法是3个系统调用的后端:
poll,epoll,和select,都用作查询对一个或多个文件描述符的读或写是否会阻塞.poll方法应当返回一个位掩码指示是否非阻塞的读或写是可能的,并且,可能地,提供给内核信息用来使调用进程睡眠直到I/O变为可能.如果一个驱动的poll方法为NULL,设备假定为不阻塞地可读可写.
int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);
ioctl系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道,这不是读也不是写).另外,几个ioctl命令被内核识别而不必引用fops表.如果设备不提供ioctl方法,对于任何未事先定义的请求(-ENOTTY,"设备无这样的ioctl"),系统调用返回一个错误.
int(*mmap)(structfile*,structvm_area_struct*);
mmap用来请求将设备内存映射到进程的地址空间.如果这个方法是NULL,mmap系统调用返回-ENODEV.
int(*open)(structinode*,structfile*);
尽管这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法.如果这个项是NULL,设备打开一直成功,但是你的驱动不会得到通知.
int(*flush)(structfile*);
flush操作在进程关闭它的设备文件描述符的拷贝时调用;它应当执行(并且等待)设备的任何未完成的操作.这个必须不要和用户查询请求的fsync操作混淆了.当前,flush在很少驱动中使用;SCSI磁带驱动使用它,例如,为确保所有写的数据在设备关闭前写到磁带上.如果flush为NULL,内核简单地忽略用户应用程序的请求.
int(*release)(structinode*,structfile*);
在文件结构被释放时引用这个操作.如同open,release可以为NULL.
int(*fsync)(structfile*,structdentry*,int);
这个方法是fsync系统调用的后端,用户调用来刷新任何挂着的数据.如果这个指针是NULL,系统调用返回-EINVAL.
int(*aio_fsync)(structkiocb*,int);
这是fsync方法的异步版本.
int(*fasync)(int,structfile*,int);
这个操作用来通知设备它的FASYNC标志的改变.异步通知是一个高级的主题,在第6章中描述.这个成员可以是NULL如果驱动不支持异步通知.
int(*lock)(structfile*,int,structfile_lock*);
lock方法用来实现文件加锁;加锁对常规文件是必不可少的特性,但是设备驱动几乎从不实现它.
ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);
ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);
这些方法实现发散/汇聚读和写操作.应用程序偶尔需要做一个包含多个内存区的单个读或写操作;这些系统调用允许它们这样做而不必对数据进行额外拷贝.如果这些函数指针为NULL,read和write方法被调用(可能多于一次).
ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);
这个方法实现sendfile系统调用的读,使用最少的拷贝从一个文件描述符搬移数据到另一个.例如,它被一个需要发送文件内容到一个网络连接的web服务器使用.设备驱动常常使sendfile为NULL.
ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);
sendpage是sendfile的另一半;它由内核调用来发送数据,一次一页,到对应的文件.设备驱动实际上不实现sendpage.
unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.这个任务通常由内存管理代码进行;这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求.大部分驱动可以置这个方法为NULL.
int(*check_flags)(int)
这个方法允许模块检查传递给fnctl(F_SETFL...)调用的标志.
int(*dir_notify)(structfile*,unsignedlong);
这个方法在应用程序使用fcntl来请求目录改变通知时调用.只对文件系统有用;驱动不需要实现dir_notify.
2.2Linux字符设备驱动的组成
2.2.1字符设备驱动模块加载与卸载函数
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注册。
习惯上将设备定义为一个设备相关的结构体,其中包括该设备所涉及的cdev、私有数据及信号量等信息。
设备结构体
structled_dev_t
{
structcdevcdev;
….
}led_dev;
设备驱动模块加载函数
staticint__initled_init(void)
{
…
cdev_init(&led_dev.cdev,&led_fops);//初始化cdev
led_dev.cdev.owner=THIS_MODULE;
获取字符设备号
if(led_major)
{
register_chrdev_region(led_dev_no,1,DEV_NAME);
}
else
{
alloc_chrdev_region(&led_dev_no,0,1,DEV_NAME);
}
ret=cdev_add(&led_dev.cdev,led_dev_no,1);
…
}
设备驱动模块卸载函数
staticvoid__exitled_exit(void)
{
unregister_chrdev_region(led_dev_no,1);//释放占用的设备号
cdev_del(&led_dev.cdev);
…
}
2.2.2字符设备驱动的file_operation结构体中成员函数
大多数字符设备驱动会实现read()、write()、和ioctl()函数,常见的字符设备驱动的这三个函数的形式如代码清单所示。
读设备
ssize_tled_read(structfile*filp,char__user*buf,size_tcount,loff_t*f_pos)
{
…
copy_to_user(buf,…,…);
…
}
写设备
ssize_tled_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*f_pos)
{
…
copy_from_user(…,buf,…);
…
}
ioctl函数
intled_ioctl(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg)
{
…
switch(cmd)
{
caseLED_CMD1:
…
break;
caseLED_CMD2:
…
break;
default:
return–ENOTTY;
}
return0;
}
设备驱动的读函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count是要读的字节数,f_pos是读的位置相对于文件开头的偏移。
由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_from_user()完成用户空间到内核空间的复制,函数cop_to_user()完成内核空间到用户空间的复制。
I/O控制函数的cmd参数为事先定义的I/O控制命令,而arg为对应于该命令的参数。
ls-l
在字符设备驱动中,需要定义一个file_operations的实例,并将具体设备驱动的函数赋值给file_operations的成员,代码如下
structfile_operationsled_fops=
{
.owner=THIS_MODULE,
.read=led_read,
.write=led_write,
.ioctl=led_ioctl,
…
};
上述led_fops在语句cdev_init($led_dev.cdev,&led_fops)中建立与cdev的连接。
2.3LED设备驱动
在LED字符驱动中,应包含它要使用的头文件,并定义LED设备结构体及相关宏
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineLIGHT_MAJOR250设置led的主设备号
#defineLIGHT_ON0x1
#defineLIGHT_OFF0x2
led设备结构体
structlight_dev
{
structcdevcdev;字符设备cdev结构体
unsignedcharvalue;LED亮时为1熄灭时为0,用户可读写此值
};
intport;
voidset_light(void)
{
port=check_region(0x378,1);
if(port==0)
{
printk("Theportsareavailableinthatrange.\n");
request_region(0x378,1,"LED");
}
else
printk("Theportcannotreserve0x378\n");
}
voidlight_on(void)
{
outb(0x1,0x378);
}
voidlight_off(void)
{
outb(0x0,0x378);
}
structlight_dev*light_devp;
intlight_major=LIGHT_MAJOR;
MODULE_LICENSE("DualBSD/GPL");
文件打开函数
intlight_open(structinode*inode,structfile*filp)
{
structlight_dev*dev;
获得设备结构指针
dev=container_of(inode->i_cdev,structlight_dev,cdev);
让设备结构体作为设备的私有信息
filp->private_data=dev;
return0;
}
文件释放函数
intlight_release(structinode*inode,structfile*filp)
{
return0;
}
读写函数
ssize_tlight_read(structfile*filp,char__user*buf,size_tcount,loff_t*f_pos)
{
structlight_dev*dev=filp->private_data;
if(copy_to_user(buf,&(dev->value),1))
{
return-EFAULT;
}
return1;
}
ssize_tlight_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*f_pos)
{
structlight_dev*dev=filp->private_data;获得设备结构体
if(copy_from_user(&(dev->value),buf,1))
{
return-EFAULT;
}
根据写入的值点亮和熄灭LED
if(dev->value==1)
light_on();
else
light_off();
return1;
}
intlight_ioctl(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg)
{
structlight_dev*dev=filp->private_data;
switch(cmd)
{
caseLIGHT_ON:
dev->value=1;
light_on();
break;
caseLIGHT_OFF:
dev->value=0;
light_off();
break;
default:
return-ENOTTY;
}
return0;
}
led设备驱动文件操作结构体
structfile_operationslight_fops=
{
.owner=THIS_MODULE,
.read=light_read,