本章以两种方式讲述一下LED驱动的编写方法,并从这两种方法中学习字符设备驱动编写的框架。这两种方法分别是:以杂项设备驱动框架来编写LED驱动和以标准字符设备驱动框架来编写LED驱动。
首先讲述以杂项设备驱动方式来驱动LED。杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。杂项设备的注册和注销函数分别是misc_register(struct miscdevice *misc)和misc_deregister(struct miscdevice *misc)。这两个函数分别在注册模块module_init和注销模块module_exit时被调用。杂项设备注册的关键就是实现struct miscdevice结构体的成员函数功能。struct miscdevice结构体定义如下:
struct miscdevice {
int minor; /*次设备号(主设备号为10)*/
const char *name; /*misc设备名*/
const struct file_operations *fops; /*misc设备文件操作结构体*/
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
其中minor一般选择MISC_DYNAMIC_MINOR(自动分配次设备号),name是注册设备时分配的设备名(动态加载该驱动模块后会在/dev/路径中发现以name命名的设备文件,这也是应用程序驱动LED的设备文件名),struct file_operations 结构体包含驱动字符设备的各种操作函数。struct file_operations结构体定义如下:
struct file_operations {
struct module *owner; // 模块所有者
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); // IO控制函数
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
owner是模块所有者,一般定义为THISZ_MODULE。在驱动LED时我们只需实现unlocked_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)(注意各参数的类型和含义,在实现该函数时须明白每个形参的含义)即可。在编写LED的驱动时,这两个结构体分别赋值如下:
static struct file_operations mini210_led_dev_fops = {
.owner = THIS_MODULE, // “.”加结构体成员的写法的含义是对于未赋值的成员编译器自动为其赋值NULL
.unlocked_ioctl = mini210_leds_ioctl,
};
static struct miscdevice mini210_led_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &mini210_led_dev_fops,
};
最后,在模块注册时(module_init)时需要对IO口做初始化,在注销模块时(module_exit)需要回复IO口配置。对IO口操作的一些IO接口函数就不一一详解了,如果需要可以查看gpiolib.c源代码(linux-3.0.8/drivers/gpio/gpiolib.c)。
前面讲述了,如何以杂项设备的驱动框架来编写LED驱动,下面讲解如何测试编写好的驱动。
1、按上一章中动态加载内核模块的操作方法,编译出mini210_led.ko(驱动源代码为mini210_led.c)内核模块。
2、对编写好的测试程序进行交叉编译(Makefile已经编写完成)。
3、将mini210_led.ko内核模块和交叉编译生成的可执行程序(led)拷贝到/home/nfs路径中。
4、在tiny210的终端中挂载ubuntu的/home/nfs路径。如图14:
图14
5、动态加载模式加载mini210_led.ko,并查看/dev/路径下是否生成了myleds(编写驱动时为struct miscdevice的name成员赋的值)。如图15:
图15
6、运行测试程序,观察LED的状态是否如测试程序所设定。如图16:
图16
下面以标准字符设备驱动框架来讲述LED驱动的编写方法。
标准字符设备驱动编写主要是实现注册模块(module_init)和注销模块(module_exit)两个函数。注册模块主要实现外设初始化以及调用int register_chrdev(unsigned int major, const char *name, struct file_operations *fops)函数来注册设备,为此需要给将要注册的设备分配主设备号major(如果major为0,则表示动态分配主设备号,分配的主设备号将作为register_chrdev()函数的返回值)、为设备驱动命名(此处的命名无需和mknod生成设备节点时命名一致,应用程序调用设备文件时是以mknod所命的名字为准的,但是在mknod时需要和注册设备时采用同样的设备号)以及实现file_operations结构体。在此我们只需实现ssize_t (*write) (struct file *, const char __user *buf, size_t count, loff_t *)函数即可,buf指向将要写入设备的数据,count指的是要写入设备的数据个数。在open设备时可以调用int (*open) (struct inode *, struct file *)函数可以打印主次设备号,在close时调用int (*release) (struct inode *, struct file *)函数释放设备。最后在注销设备(module_exit)时可以恢复外设配置并调用int unregister_chrdev(unsigned int major, const char *name)函数注销设备,此时的major和name应该和注册时一致。
下面讲述一下如何测试编写好的LED驱动:
1、按上一章中动态加载内核模块的操作方法,编译出tiny210_led.ko(驱动源代码为tiny210_led.c)内核模块。
2、对编写好的测试程序进行交叉编译(此处需记下open函数调用的设备名参数,以备下面mknod生成设备节点时分配设备名)。
3、将tiny210_led.ko内核模块和交叉编译生成的可执行程序(led)拷贝到/home/nfs路径中并在tiny210开发板上挂载nfs路径。
4、动态加载模式加载tiny210_led.ko模块,并观察设备号(编写驱动时我采用的是动态分配的方式分配设备号),如图17:
图17
5、mknod生成设备节点文件(注意设备名称和设备号),如图18:
图18
6、运行测试程序观察实验现象,如图19:
图19
文章评论(0条评论)
登录后参与讨论