自制简单字符型设备驱动程序——LED驱动

来源:互联网 发布:csgo正在检索游戏数据 编辑:程序博客网 时间:2024/06/12 01:20

这周抽空学以致用,参考LDD3及嵌入式系统接口设计与Linux驱动程序开发这两本书,结合自己的开发板,做了LED驱动的程序,自己写程序和看书感觉真的是不一样啊,过程中犯了不少错误,但最终还是完成了程序,很高兴!

硬件平台:tq2440

内核版本:2.6.30.4

1. 硬件介绍

下面首先介绍一下我的开发板的接线,我的开发板是天嵌公司的tq2440,它的LED接线如图所示: 

其中,nLED_1接S3C2440的GPB5引脚,其他依次接GPB6,GPB7,GPB8,低电平时LED点亮。这些条件就够我们写驱动使用了。

2. LED驱动程序

把程序分成几块分别说明一下。首先是最前面的一部分,这一部分主要是头文件及调试用的预定义,同时定义了一些参数,默认情况下,主次设备号均为0,有4个LED,同时给出了一个关键的结构,我的LED设备的结构,它包含了LED所接的引脚信息,LED的状态,信号量及一个标志他是字符型设备的cdev结构。

[plain] view plaincopy
  1. #include <linux/module.h>                      
  2. #include <linux/init.h>  
  3. #include <mach/regs-gpio.h>               /*S3C2410_GPB5_OUTP and S3C2410_GPB5*/  
  4. #include <linux/kernel.h>                     /*printk*/  
  5. #include <linux/fs.h>  
  6. #include <linux/types.h>                      /*size_t and atomic_t*/  
  7. #include <linux/cdev.h>                       /*cdev*/  
  8. #include <mach/hardware.h>               /*s3c2410_gpio_cfgpin and s3c2410_gpio_setpin*/  
  9. #include <asm/uaccess.h>                   /*copy_*_user*/  
  10.   
  11. /*for debug use*/  
  12. #undef DEBUG                                            
  13. //#define DEBUG  
  14. #ifdef DEBUG  
  15. #define PRINTK printk("Success!\n")  
  16. #else  
  17. #define PRINTK  
  18. #endif  
  19.   
  20. /*LED start pin*/  
  21. #define LEDSTARTPIN S3C2410_GPB5  
  22.   
  23. int yjpLED_major = 0;  
  24. int yjpLED_minor = 0;  
  25. int nr_LED = 4;  
  26.   
  27. module_param(yjpLED_major, int, S_IRUGO);  
  28. module_param(yjpLED_minor, int, S_IRUGO);  
  29. module_param(nr_LED, int, S_IRUGO);  
  30.   
  31. struct yjpLED  
  32. {  
  33.     unsigned int LEDpin;  
  34.     unsigned char status;  
  35.     struct semaphore sem;  
  36.     struct cdev cdev;  
  37. };  
  38.   
  39. struct yjpLED *yjpLEDs;  

最后面的一部分,是一些协议信息及作者信息,但最关键的在与还指出了初始化函数及退出函数。

[plain] view plaincopy
  1. module_init(yjpLED_init);  
  2. module_exit(yjpLED_exit);  
  3.   
  4. MODULE_LICENSE("GPL");  
  5. MODULE_AUTHOR("Yjp");  


下面先看我的初始化函数

[plain] view plaincopy
  1. int __init yjpLED_init(void)  
  2. {  
  3.     int result, i;  
  4.     dev_t dev;  
  5.       
  6.     if(yjpLED_major){  
  7.         dev = MKDEV(yjpLED_major, yjpLED_minor);  
  8.         result = register_chrdev_region(dev, nr_LED, "yjpLED");  
  9.     }else{  
  10.         result = alloc_chrdev_region(&dev, yjpLED_minor, nr_LED, "yjpLED");  
  11.         yjpLED_major=MAJOR(dev);  
  12.     }  
  13.     if(result < 0){  
  14.         printk(KERN_ALERT "yjpLED: can't get major %d\n", yjpLED_major);  
  15.         return result;   
  16.     }  
  17.   
  18.     yjpLEDs = kmalloc(nr_LED * sizeof(struct yjpLED), GFP_KERNEL);  
  19.       
  20.     if (!yjpLEDs) {  
  21.         result = -ENOMEM;  
  22.         goto fail;   
  23.           
  24.     memset(yjpLEDs, 0, nr_LED * sizeof(struct yjpLED));  
  25.     }  
  26.       
  27.     for(i = 0; i < nr_LED; i++){   
  28.         yjpLEDs[i].LEDpin= LEDSTARTPIN + i;   
  29.         yjpLEDs[i].status = 1;  
  30.     }  
  31.   
  32.     for(i = 0; i < nr_LED; i++){  
  33.         s3c2410_gpio_cfgpin(yjpLEDs[i].LEDpin, S3C2410_GPB5_OUTP << (i*2));  
  34.         s3c2410_gpio_setpin(yjpLEDs[i].LEDpin, yjpLEDs[i].status);  
  35.         init_MUTEX(&yjpLEDs[i].sem);  
  36.         setup_yjpLED_cdev(&yjpLEDs[i].cdev, i);  
  37.     }  
  38.   
  39.     if (!yjpLEDs) {  
  40.         result = -ENOMEM;  
  41.         goto fail;    
  42.     }  
  43.   
  44.     printk(KERN_ALERT "yjpLED init!\n");  
  45.       
  46.     return result;  
  47.       
  48.     fail:  
  49.         yjpLED_exit();  
  50.           
  51.     return result;  
  52. }  

该函数完成的工作为:

1. 注册设备,为设备分配设备号。注册成功后,在/proc/devices下就可以看到设备了。

2. 为设备结构分配空间,并初始化为0.

3. 硬件初始化。

4. cdev结构的初始化。

其中,cdev结构初始化由下面的函数完成:

[plain] view plaincopy
  1. void setup_yjpLED_cdev(struct cdev *cdev, int index)  
  2. {  
  3.     int err, devno = MKDEV(yjpLED_major, yjpLED_minor + index);  
  4.       
  5.     cdev_init(cdev, &yjpLED_fops);  
  6.     (*cdev).owner = THIS_MODULE;  
  7.     err = cdev_add(cdev, devno, 1);  
  8.     PRINTK;  
  9.   
  10.     if(err){  
  11.         printk(KERN_ALERT "Error %d adding yjpLED%d", err, index);  
  12.     }  
  13. }  

初始化了cdev的必要的成员,并激活cdev。LED已经“活”了。下面看退出函数:

[plain] view plaincopy
  1. void __exit yjpLED_exit(void)  
  2. {  
  3.     int i, devno = MKDEV(yjpLED_major, yjpLED_minor);  
  4.       
  5.     if(yjpLEDs){  
  6.         for(i = 0; i < nr_LED; i++){  
  7.             cdev_del(&yjpLEDs[i].cdev);  
  8.             PRINTK;  
  9.         }  
  10.         kfree(yjpLEDs);  
  11.     }  
  12.     printk(KERN_ALERT "yjpLED exit!\n");  
  13.       
  14.     unregister_chrdev_region(devno, nr_LED);      
  15. }  

删除每个设备的cdev结构并注销设备。

然后看一下关键的结构体,file_operations

[plain] view plaincopy
  1. struct file_operations yjpLED_fops = {  
  2.     .owner = THIS_MODULE,  
  3.     .write = yjpLED_write,  
  4.     .open = yjpLED_open,  
  5.     .release = yjpLED_release,  
  6. };  

定义了设备的打开,读及写操作。open操作的函数:

[plain] view plaincopy
  1. int yjpLED_open(struct inode *inode, struct file *filp)  
  2. {  
  3.     struct yjpLED *dev;  
  4.       
  5.     dev = container_of(inode->i_cdev, struct yjpLED, cdev);  
  6.     filp->private_data = dev;  
  7.     PRINTK;  
  8.   
  9.     return 0;  
  10. }  

将所打开的设备的指针存放在代表打开文件的指针filp的private_data成员中,以便其他操作函数使用。

release操作:

[plain] view plaincopy
  1. int yjpLED_release(struct inode *inode, struct file *filp)  
  2. {  
  3.     filp->private_data = NULL;  
  4.     return 0;  
  5. }  

释放由open分配的保存在filp->private_data中的所用内容。

write操作:

[plain] view plaincopy
  1. ssize_t yjpLED_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)  
  2. {     
  3.     ssize_t retval;  
  4.     struct yjpLED *dev;  
  5.   
  6.     dev = filp->private_data;  
  7.   
  8.     if(down_interruptible(&dev->sem))  
  9.         return -ERESTARTSYS;      
  10.       
  11.     if(copy_from_user(&dev->status, buf, sizeof(dev->status)))  
  12.     {  
  13.         printk(KERN_ALERT "after copy but error!\n");  
  14.         retval = -EFAULT;  
  15.         goto out;  
  16.     }  
  17.     retval = sizeof(dev->status);  
  18.   
  19.     dev->status =~ (dev->status) & 1;  
  20.       
  21.     s3c2410_gpio_setpin(dev->LEDpin, dev->status);  
  22.     PRINTK;  
  23.   
  24.     out:  
  25.         up(&dev->sem);  
  26.         return retval;  
  27. }  

完成的工作主要有:

1. 获取设备信号量,防止竞态发生。

2. 从用户空间获取数据,如果成功则置位相应的引脚,否则释放信号量并退出。

以上就是整个程序,将其复制到内核目录下的drivers/char/,然后修改该目录下的Kconfig文件和Makefile文件,编译出模块yjpLED.ko,将其复制到文件系统的/lib/目录下,注意要修改权限为可执行。

这里为了方便加载模块,还修改了LDD3中的scull_load脚本,也将其复制到文件系统/lib目录下,修改权限为可执行,该脚本内容如下:

[plain] view plaincopy
  1. #!/bin/sh  
  2. # $Id: scull_load,v 1.4 2004/11/03 06:19:49 rubini Exp $  
  3. module="yjpLED"  
  4. device="yjpLED"  
  5. mode="664"  
  6. group="root"  
  7.   
  8. # invoke insmod with all arguments we got  
  9. # and use a pathname, as insmod doesn't look in . by default  
  10. /sbin/insmod ./$module.ko $* || exit 1  
  11.   
  12. # retrieve major number  
  13. major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)  
  14.   
  15. # Remove stale nodes and replace them, then give gid and perms  
  16. # Usually the script is shorter, it's scull that has several devices in it.  
  17.   
  18. rm -f /dev/${device}[0-3]  
  19. mknod /dev/${device}0 c $major 0  
  20. mknod /dev/${device}1 c $major 1  
  21. mknod /dev/${device}2 c $major 2  
  22. mknod /dev/${device}3 c $major 3  
  23.   
  24. chgrp $group /dev/${device}[0-3]   
  25. chmod $mode  /dev/${device}[0-3]  

启动开发板,进入/lib目录,加载模块并运行,测试使用echo命令向设备文件写入,写入单数点亮对应的LED,写入双数关闭对应的LED。

如echo -n 1 > /dev/yjpLED2 点亮第三个LED(从0开始编号)

而echo -n 2 > /dev/yjpLED2 则关闭第三个LED。