Linux驱动——第一个驱动程序(点亮LED)
参与Helper2416开发板助学计划心得
经过几天的琢磨,今天终于完成了我的第一个驱动,也算是敲开了驱动编写的大门
描述:
Linux驱动程序大致分为三种:字符设备驱动、块设备驱动、网络驱动,.这个LED驱动程序就是最简单的字符驱动程序中的最简单的一个实例,不过却也涵盖了驱动程序开发的基本步骤。
先上源码,然后再一点点解剖:
- #include <linux/module.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/platform_device.h>
- #include <linux/device.h>
- #include <linux/io.h>
- #include <linux/miscdevice.h>
- #include <linux/uaccess.h>
-
- #include <asm/uaccess.h>
- #include <asm/atomic.h>
- #include <asm/unistd.h>
-
- static int LED_Major = 0;
- static struct class *led_driver_class;
- volatile unsigned long *GPBCON, *GPBDAT;
-
-
- static int s3c24xx_led_open(struct inode *inode, struct file *file)
- {
- printk("s3c24xx_led_open\n");
- (*GPBCON) |= 1<<2;
- return 0;
- }
-
- static int s3c24xx_led_close(struct inode *inode, struct file *file)
- {
- printk("s3c24xx_led_close\n");
- return 0;
- }
-
- static int s3c24xx_led_write(struct file *file, char __user *buff, size_t count, loff_t *offp)
- {
- int value;
-
- copy_from_user(&value, buff, count);
-
- if(value == 1){
- (*GPBDAT) &= ~(1<<1);
- }else if(value == 0){
- (*GPBDAT) |= 1<<1;
- }else{
- printk("zhe writed value must be 1 or 0\n");
- return 1;
- }
- return 0;
- }
-
- static struct file_operations led_fops = {
- .owner = THIS_MODULE,
- .open = s3c24xx_led_open,
- .write = s3c24xx_led_write,
- .release = s3c24xx_led_close,
- };
-
- static int __init led_init(void)
- {
- LED_Major = register_chrdev(LED_Major, "led_driver", &led_fops);
- led_driver_class = class_create(THIS_MODULE, "led_driver");
- device_create(led_driver_class, NULL, MKDEV(LED_Major, 0), NULL, "led_driver");
- GPBCON = (volatile unsigned long*)ioremap(0x56000010,8);
- GPBDAT = GPBCON + 1;
- printk("led_init\n");
- return 0;
- }
-
- static void __exit led_exit(void)
- {
- unregister_chrdev(LED_Major, "led_driver");
- device_destroy(led_driver_class, MKDEV(LED_Major, 0) );
- class_destroy(led_driver_class);
- iounmap(GPBCON);
- printk("led_exit\n");
- }
-
- module_init(led_init);
- module_exit(led_exit);
- MODULE_AUTHOR("yuanlai");
- MODULE_DESCRIPTION("helper2416 led Driver");
- MODULE_LICENSE("GPL");
第一步:搭好驱动程序的框架
首先就是头文件:
头文件比较多,暂且还不知道这些头文件是不是都有用到,但是至少驱动能够编译成功。
接着就是structfile_operations 这个结构体及驱动处理函数:
加载驱动的时候,就需要把这个struct file_operations的结构体注册进内核,该结构体在头文件linux/fs.h定义,应该是告诉系统这个驱动程序具体有哪些函数用来操作硬件的,然后内核就是通过这个结构体的成员(函数指针)来找到这些函数来操作硬件的,以下便是这个结构体看起来的样子:
- 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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
- ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
- ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
- int (*readdir) (struct file *, void *, filldir_t);
- unsigned int (*poll) (struct file *, struct poll_table_struct *);
- int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
- int (*mmap) (struct file *, struct vm_area_struct *);
- int (*open) (struct inode *, struct file *);
- int (*flush) (struct file *);
- int (*release) (struct inode *, struct file *);
- int (*fsync) (struct file *, struct dentry *, 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(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
- ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
- ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
- 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);
- };
对于具体要用到哪些成员,就需要根据驱动程序的具体情况来决定了。不过.owner = THIS_MODULE, 这条是一定要有的。由于只是控制一盏LED的亮灭状态,所以我也只用到了open、write、close这几个成员。
最后就是模块初始化与卸载处理函数:
模块的初始化这个函数(led_init)是在insmod的时候自动运行的,在代码中还需要用module_init()这个宏来包装一下。在这个函数中就需要把上面提到的structfile_operations这个结构体注册到内核里面去,这里使用register_chrdev()函数完成注册。
接着还需要在/dev/目录下创建相应设备节点,尽管可以通过shell用命令手工创建,但那是在太麻烦了点,所以通过先创建一个类,然后再在这个类下创建一个所需要的设备文件。
同样的模块退出函数(led_exit())是在rmmod的时候自动运行的,同样也需要module_exit()这个宏来包装一下。这里需要注销之前注册的structfile_operations结构体,然后销毁之前创建的类和设备(删除/dev/目录下的设备节点)
忘了,在最后的最后,还需要加上这些信息,尤其是MODULE_LICENSE("GPL");这条,不然很多时候编译会出错。
- MODULE_AUTHOR("yuanlai");
- MODULE_DESCRIPTION("helper2416 led Driver");
- MODULE_LICENSE("GPL");
第二部:完善硬件操作
由于这仅仅是一个最简单的一盏LED的驱动,所以硬件操作比较简单,不过由于Linux操作的都是虚拟地址,所以当操作实际硬件的时候,就需要把物理地址映射成虚拟地址,具体的虚拟地址是多少这个是由系统来决定的,我们可以通过ioremap()这个函数来获得硬件物理地址所对应的虚拟地址,当然在驱动卸载时也记得用iounmap()解除此次映射。为了方便,我在驱动加载的时候,就用ioremap完成了IO的映射,并在led_exit()中用iounmap取消映射。然后具体的硬件操作都体现在s3c24xx_led_open()、s3c24xx_led_write()这两个函数里面了,怎么样,是不是和裸机差不多?
第三步:编译模块(驱动)
编译模块是一定需要有内核源码树的,我这里直接使用的是BOSS提供的Fedora18虚拟机镜像里的/home/jyxtec/workspace/kernel/s3c-linux
下面是Makefile文件具体内容,直接执行make后就在当前目录生成了我期待已久的.ko文件了。
- obj-m := led_module.o
- KERNEL_DIR :=/home/jyxtec/workspace/kernel/s3c-linux
- PWD := $(shell pwd)
- all:
- make ARCH=arm CROSS_COMPILE=arm-linux- -C /home/jyxtec/workspace/kernel/s3c-linux M=$(PWD) modules
- clean:
- rm *.o *.ko
驱动测试
拿到.ko文件后当然就是急着去测试咯,只有测试成功了才是真正值得兴奋的时刻。
测试程序代码如下,实现频率为2秒的LED闪烁。
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <fcntl.h>
- #include <sys/stat.h>
-
- int main(int argc, char **argv)
- {
- int fd;
- int value;
-
- fd = open("/dev/led_driver", O_WRONLY);
- if(fd < 0)
- {
- perror("cannot open device led");
- exit(1);
- }
- while(1)
- {
- /* 点亮 LED */
- value = 1;
- write(fd, &value, 4);
- sleep(1);
-
- /* 熄灭 LED */
- value = 0;
- write(fd, &value, 4);
- sleep(1);
- }
- close(fd);
- return 0;
-
- }
首先需要把.ko 文件和测试程序的可执行文件发送到目标版(Helper2416)
- [root@jyxtec Linux]# ls
- led_module.ko led_test
- [root@jyxtec Linux]#
然后执行 insmod led_module.ko 打印出以下信息,模块应该是已经加载成功了。
- [root@jyxtec Linux]# insmod led_module.ko
- led_init
- [root@jyxtec Linux]#
为了验证是否整的已经加载,我们再来查看/dev/目录是否有我们所期待的设备文件 led_driver ., 确实是有的。
- [root@jyxtec Linux]# ls -l /dev/led*
- crw-rw---- 1 root root 252, 0 Jan 1 03:40 /dev/led_driver
- [root@jyxtec Linux]#
接着就是运行测试程序了,由于我在驱动程序的open函数中加入打印信息的语句,所以会打印下面这些信息,然后开发板上的LED已经闪烁起来,由于测试程序是个无限循环,所以最后只能通过Ctle + C 结束程序。
测试成功,第一个驱动程序成功的运行起来了!
论坛ID:yuanlai2010
发表时间:2014-08-26
本帖最后由 yuanlai2010 于 2014-8-26 14:29 编辑