[原创] Helper2416-35——Linux驱动——第一个驱动程序(点亮LED)

yuanlai2010   2014-8-26 14:29 楼主
Linux驱动——第一个驱动程序(点亮LED)
参与Helper2416开发板助学计划心得
经过几天的琢磨,今天终于完成了我的第一个驱动,也算是敲开了驱动编写的大门
述:
Linux驱动程序大致分为三种:字符设备驱动、块设备驱动、网络驱动,.这个LED驱动程序就是最简单的字符驱动程序中的最简单的一个实例,不过却也涵盖了驱动程序开发的基本步骤。
先上源码,然后再一点点解剖:
  1. #include <linux/module.h>
  2. #include <linux/types.h>
  3. #include <linux/fs.h>
  4. #include <linux/init.h>
  5. #include <linux/platform_device.h>
  6. #include <linux/device.h>
  7. #include <linux/io.h>
  8. #include <linux/miscdevice.h>
  9. #include <linux/uaccess.h>
  10. #include <asm/uaccess.h>
  11. #include <asm/atomic.h>
  12. #include <asm/unistd.h>
  13. static int LED_Major = 0;
  14. static struct class *led_driver_class;
  15. volatile unsigned long *GPBCON, *GPBDAT;
  16. static int s3c24xx_led_open(struct inode *inode, struct file *file)
  17. {
  18. printk("s3c24xx_led_open\n");
  19. (*GPBCON) |= 1<<2;
  20. return 0;
  21. }
  22. static int s3c24xx_led_close(struct inode *inode, struct file *file)
  23. {
  24. printk("s3c24xx_led_close\n");
  25. return 0;
  26. }
  27. static int s3c24xx_led_write(struct file *file, char __user *buff, size_t count, loff_t *offp)
  28. {
  29. int value;
  30. copy_from_user(&value, buff, count);
  31. if(value == 1){
  32. (*GPBDAT) &= ~(1<<1);
  33. }else if(value == 0){
  34. (*GPBDAT) |= 1<<1;
  35. }else{
  36. printk("zhe writed value must be 1 or 0\n");
  37. return 1;
  38. }
  39. return 0;
  40. }
  41. static struct file_operations led_fops = {
  42. .owner = THIS_MODULE,
  43. .open = s3c24xx_led_open,
  44. .write = s3c24xx_led_write,
  45. .release = s3c24xx_led_close,
  46. };
  47. static int __init led_init(void)
  48. {
  49. LED_Major = register_chrdev(LED_Major, "led_driver", &led_fops);
  50. led_driver_class = class_create(THIS_MODULE, "led_driver");
  51. device_create(led_driver_class, NULL, MKDEV(LED_Major, 0), NULL, "led_driver");
  52. GPBCON = (volatile unsigned long*)ioremap(0x56000010,8);
  53. GPBDAT = GPBCON + 1;
  54. printk("led_init\n");
  55. return 0;
  56. }
  57. static void __exit led_exit(void)
  58. {
  59. unregister_chrdev(LED_Major, "led_driver");
  60. device_destroy(led_driver_class, MKDEV(LED_Major, 0) );
  61. class_destroy(led_driver_class);
  62. iounmap(GPBCON);
  63. printk("led_exit\n");
  64. }
  65. module_init(led_init);
  66. module_exit(led_exit);
  67. MODULE_AUTHOR("yuanlai");
  68. MODULE_DESCRIPTION("helper2416 led Driver");
  69. MODULE_LICENSE("GPL");
第一步:搭好驱动程序的框架
首先就是头文件:
头文件比较多,暂且还不知道这些头文件是不是都有用到,但是至少驱动能够编译成功。
接着就是structfile_operations 这个结构体及驱动处理函数:
加载驱动的时候,就需要把这个struct file_operations的结构体注册进内核,该结构体在头文件linux/fs.h定义,应该是告诉系统这个驱动程序具体有哪些函数用来操作硬件的,然后内核就是通过这个结构体的成员(函数指针)来找到这些函数来操作硬件的,以下便是这个结构体看起来的样子:
  1. struct file_operations {
  2. struct module *owner;
  3. loff_t(*llseek) (struct file *, loff_t, int);
  4. ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
  5. ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
  6. ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
  7. ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
  8. int (*readdir) (struct file *, void *, filldir_t);
  9. unsigned int (*poll) (struct file *, struct poll_table_struct *);
  10. int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
  11. int (*mmap) (struct file *, struct vm_area_struct *);
  12. int (*open) (struct inode *, struct file *);
  13. int (*flush) (struct file *);
  14. int (*release) (struct inode *, struct file *);
  15. int (*fsync) (struct file *, struct dentry *, int datasync);
  16. int (*aio_fsync) (struct kiocb *, int datasync);
  17. int (*fasync) (int, struct file *, int);
  18. int (*lock) (struct file *, int, struct file_lock *);
  19. ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
  20. ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
  21. ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
  22. ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  23. unsigned long (*get_unmapped_area) (struct file *, unsigned long,
  24. unsigned long, unsigned long,
  25. unsigned long);
  26. };
对于具体要用到哪些成员,就需要根据驱动程序的具体情况来决定了。不过.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");这条,不然很多时候编译会出错。
  1. MODULE_AUTHOR("yuanlai");
  2. MODULE_DESCRIPTION("helper2416 led Driver");
  3. 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文件了。
  1. obj-m := led_module.o
  2. KERNEL_DIR :=/home/jyxtec/workspace/kernel/s3c-linux
  3. PWD := $(shell pwd)
  4. all:
  5. make ARCH=arm CROSS_COMPILE=arm-linux- -C /home/jyxtec/workspace/kernel/s3c-linux M=$(PWD) modules
  6. clean:
  7. rm *.o *.ko
驱动测试
拿到.ko文件后当然就是急着去测试咯,只有测试成功了才是真正值得兴奋的时刻。
测试程序代码如下,实现频率为2秒的LED闪烁。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <fcntl.h>
  6. #include <sys/stat.h>
  7. int main(int argc, char **argv)
  8. {
  9. int fd;
  10. int value;
  11. fd = open("/dev/led_driver", O_WRONLY);
  12. if(fd < 0)
  13. {
  14. perror("cannot open device led");
  15. exit(1);
  16. }
  17. while(1)
  18. {
  19. /* 点亮 LED */
  20. value = 1;
  21. write(fd, &value, 4);
  22. sleep(1);
  23. /* 熄灭 LED */
  24. value = 0;
  25. write(fd, &value, 4);
  26. sleep(1);
  27. }
  28. close(fd);
  29. return 0;
  30. }
首先需要把.ko 文件和测试程序的可执行文件发送到目标版(Helper2416)
  1. [root@jyxtec Linux]# ls
  2. led_module.ko led_test
  3. [root@jyxtec Linux]#
然后执行 insmod led_module.ko 打印出以下信息,模块应该是已经加载成功了。
  1. [root@jyxtec Linux]# insmod led_module.ko
  2. led_init
  3. [root@jyxtec Linux]#
为了验证是否整的已经加载,我们再来查看/dev/目录是否有我们所期待的设备文件 led_driver ., 确实是有的。
  1. [root@jyxtec Linux]# ls -l /dev/led*
  2. crw-rw---- 1 root root 252, 0 Jan 1 03:40 /dev/led_driver
  3. [root@jyxtec Linux]#
接着就是运行测试程序了,由于我在驱动程序的open函数中加入打印信息的语句,所以会打印下面这些信息,然后开发板上的LED已经闪烁起来,由于测试程序是个无限循环,所以最后只能通过Ctle + C 结束程序。
minicom.png
测试成功,第一个驱动程序成功的运行起来了!
论坛ID:yuanlai2010
发表时间:2014-08-26
本帖最后由 yuanlai2010 于 2014-8-26 14:29 编辑

回复评论 (4)

悄悄的在二楼放上源码走人     
LED_Module.rar (1.6 KB)
(下载次数: 11, 2014-8-26 15:01 上传)
点赞  2014-8-26 15:01
不错都开始搞驱动了
电工
点赞  2014-8-26 15:20
恭喜恭喜呀
点赞  2014-8-26 15:40
恭喜恭喜!
My dreams will go on... http://www.jyxtec.com
点赞  2014-8-27 12:19
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复