LED设备驱动

来源:互联网 发布:优化驱动器多少遍 编辑:程序博客网 时间:2024/05/17 03:44

去年开的博客,到今天只有两篇文章,再看看同学的博客,惭愧啊。
写博客虽然额外多花了些时间,不过对自学知识有复习与总结的作用,还能提高语言组织能力,而且,如果有朋友能从我的博客中获益那就再好不过了。希望自己养成写博客的习惯,就从这一篇开始吧。


LED驱动是最简单的字符设备,可以说是Linux设备驱动程序里的HelloWorld,适合用来熟悉字符设备驱动程序开发的基本流程
笔者使用的是JZ2440开发板,板上有三个LED,分别对应GPF4,GPF5,GPF6引脚。本节将实现4个led设备:
/dev/leds
/dev/led0
/dev/led1
/dev/led2
第一个设备对应3个led的整体,后面3个分别对应其中一个led

先看看设备驱动编写时涉及到的基本知识,包括一些数据结构和函数:

设备编号

设备编号包括主次编号,主编号用来告诉系统使用哪个驱动程序去驱动该设备,次编号用来指定特定的设备
以下结构用于记录设备的编号:

dev_t dev;

在32位机上dev_t为32位数据,其中12位用来记录主编号,20位用于记录次编号。
我们使用以下两个宏来从dev_t中获取主次编号:

MAJOR(dev_t dev);MINOR(dev_t dev);

反之,在已知主次编号时用以下的宏来得出dev_t:

MKDEV(int major, int minor);

分配和释放设备编号

分配设备编号有几种方法,第一种是静态分配:

int register_chrdev_region(dev_t first, unsigned int count, char *name);

first 是要分配的起始设备编号;count是要分配的设备编号总数。
通常编译到内核的设备驱动或常用的设备驱动使用静态方法,为了保证主编号的唯一性,我们自己编写驱动时推荐使用动态方法:

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,    unsigned int count, char *name);

firstminor为要分配的第一个次编号,通常取0;count同上
这两个函数通常在模块初始化中调用
释放设备编号:

void unregister_chrdev_region(dev_t first, unsigned int count);

通常在模块退出时调用

加载设备驱动后可以在/proc/devices中找到设备的主次编号

其他重要的数据结构

struct file_operations f_ops;

一个函数指针的集合,这些函数负责实现系统调用如open,close,read,write,ioctl等

struct cdev cdev;

系统用来记录字符设备的结构

字符设备注册与注销

方法一:
注册:

int register_chrdev(unsigned int major, const char *name,    struct file_operations *fops);

这里其实用一个函数完成了两个功能:使用fops初始化主编号为major的字符设备并且向内核注册该设备
注销:

int unregister_chrdev(unsigned int major, const char *name);

方法二:
把方法一中提到的“两个功能”分两步完成
先调用

void cdev_init(struct cdev *cdev, struct file_operations *fops);

这一步使用fops初始化cdev
再调用

int cdev_add(struct cdev *cdev, dev_t num, unsigned int count);

这一步向系统注册cdev
如果你想定义自己的设备结构并且在其中包含struct cdev成员,显然你只能使用这个方法

使用这个方法注册的设备要用以下函数注销:

void cdev_del(struct cdev *dev);

用户空间的访问

在f_ops中的read,write函数通常需要调用以下函数来与用户空间进行数据交互:

unsigned long copy_to_user(void __user *to,    const void *from,unsigned long count);unsigned long copy_from_user(void *to,    const void __user *from,unsigned long count);

为何不直接用memcpy()?
很显然to和from不在同一个地址空间,所以直接memcpy的话将会发生不可预料的事。


有了这些基础之后,就可以开始编写基本的led驱动程序了

以下是完整代码:

//leds.c#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/cdev.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#define DEVICE_NAME "led"#define LED_DEV_NR 4#define LED_ON 0#define LED_OFF 1/*  * 笔者自己定义的表示led设备的结构,之所以写成这样是为了以后编写更复杂的设备 * 驱动时可以拿这个当模板用,到时侯可能往此结构中添加成员 */struct led_dev {    struct cdev cdev;};struct led_dev *led_devs;    //指向led_dev数组的指针char leds_status;   //记录3个led状态的位图,灯亮对应位置位,灯灭对应位复位DECLARE_MUTEX(leds_lock);   //对全局变量操作时要锁住此互斥锁,这是为了保证操作的可重入性/* * open()系统调用的实现,这里什么都不做 */static int led_open(struct inode *inode, struct file *filp){    return 0;}/*  * read()系统调用的实现,功能是读取led设备的状态 */static int led_read(struct file *filp, char __user *buff,                     size_t count, loff_t *offp){    int minor = MINOR(filp->f_dentry->d_inode->i_rdev); //获得目标文件对应的设备的次编号    char val;    switch (minor) {        case 0:   // minor=0对应/dev/leds,对其进行读操作将读到3个led的状态对应的二进制值            down(&leds_lock);            val = leds_status;            up(&leds_lock);            if (copy_to_user(buff, (const void *)&val, 1))                 return -EFAULT;            break;        case 1:   //minor=1,2,3时对应/dev/led0,1,2,对其进行读操作将读到对应led的状态(亮1灭0)            down(&leds_lock);            val = leds_status & 0x1;            up(&leds_lock);            if(copy_to_user(buff, (const void *)&val, 1))                 return -EFAULT;            break;        case 2:            down(&leds_lock);            val = (leds_status >> 1) & 0x1;            up(&leds_lock);            if (copy_to_user(buff, (const void *)&val, 1))                 return -EFAULT;            break;        case 3:            down(&leds_lock);            val = (leds_status >> 2) & 0x1;            up(&leds_lock);            if (copy_to_user(buff, (const void *)&val, 1))                    return -EFAULT;            break;    }    return 1;}/*  * write系统调用的实现,将改变led设备的状态 */static ssize_t led_write(struct file *filp, const char __user *buff,                size_t count, loff_t *offp){    int minor = MINOR(filp->f_dentry->d_inode->i_rdev);    char val;    if (copy_from_user(&val, buff, 1))        return -EFAULT;    switch (minor) {        case 0:  //对应/dev/leds,同时改变3个led的状态            down(&leds_lock);            s3c2410_gpio_setpin(S3C2410_GPF4, !(val & 0x1));            s3c2410_gpio_setpin(S3C2410_GPF5, !((val >> 1) & 0x1));            s3c2410_gpio_setpin(S3C2410_GPF6, !((val >> 2) & 0x1));            leds_status = val;            up(&leds_lock);            //printk("led: %d\n", leds_status);            break;        case 1:            s3c2410_gpio_setpin(S3C2410_GPF4, !val);            if (val == 0) {                down(&leds_lock);                leds_status &= ~(1<<0);                up(&leds_lock);            } else {                down(&leds_lock);                leds_status |= (1<<0);                up(&leds_lock);            }            break;        case 2:            s3c2410_gpio_setpin(S3C2410_GPF5, !val);            if (val == 0) {                down(&leds_lock);                leds_status &= ~(1<<1);                up(&leds_lock);            } else {                down(&leds_lock);                leds_status |= (1<<1);                up(&leds_lock);            }            break;        case 3:            s3c2410_gpio_setpin(S3C2410_GPF6, !val);            if (val == 0) {                down(&leds_lock);                leds_status &= ~(1<<2);                up(&leds_lock);            } else {                down(&leds_lock);                leds_status |= (1<<2);                up(&leds_lock);            }            break;    }    return 1;}/*  * 模块close()时将调用的函数 */int led_release(struct inode *inode, struct file *filp){    return 0;}struct file_operations led_fops = {     .owner = THIS_MODULE,    .read = led_read,    .write = led_write,    .open = led_open,    .release = led_release,};dev_t led_dev_num;    //记录起始led设备号int led_major;      //记录led设备主编号/*  * 模块卸载(rmmod)时将调用的函数 */static void __exit leds_cleanup_module(void){    int i;    if (led_devs) {        for (i = 0; i < LED_DEV_NR; i ++) {            cdev_del(&led_devs[i].cdev);        }        kfree(led_devs);    }    unregister_chrdev_region(led_dev_num, LED_DEV_NR);    printk(DEVICE_NAME " uninstalled.\n");}static void leds_lowlevel_init(void){    down(&leds_lock);    s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);    s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);    s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);    s3c2410_gpio_setpin(S3C2410_GPF4, !0);    s3c2410_gpio_setpin(S3C2410_GPF5, !0);    s3c2410_gpio_setpin(S3C2410_GPF6, !0);    leds_status = 0;    up(&leds_lock);}static int __init leds_init_module(void){    int result;    int i;    struct cdev *cdev = NULL;    if ((result = alloc_chrdev_region(&led_dev_num, 0, LED_DEV_NR, DEVICE_NAME)) < 0) {   //分配LED_DEV_NR个设备号,起始次编号为0,起始设备号存储在led_dev_num中        printk(DEVICE_NAME " fail to get major.\n");        return result;    }    led_major = MAJOR(led_dev_num);    led_devs = kmalloc(LED_DEV_NR * sizeof(struct led_dev), GFP_KERNEL);    //分配struct led_dev结构,这是我自己定义的一个表示led设备的结构,其中包含struct cdev    if (!led_devs) {        printk(DEVICE_NAME " fail to allocate mem.\n");        result = -ENOMEM;        goto fail;    }    memset(led_devs, 0, LED_DEV_NR * sizeof(struct led_dev));    leds_lowlevel_init();   //底层硬件初始化可在模块加载时完成    for (i = 0; i < LED_DEV_NR; i ++) {            cdev = &led_devs[i].cdev;        cdev_init(cdev, &led_fops);  //初始化cdev        cdev->owner = THIS_MODULE;        if(cdev_add(cdev, MKDEV(led_major, i), 1)) {   //注册cdev            printk(DEVICE_NAME": error when adding led%d", i);            result = -1000;            goto fail;        }    }    printk(DEVICE_NAME " initialized.\n");    return 0;fail:    leds_cleanup_module();   //如果初始化过程中出错,务必将已经分配的资源还给系统再返回!    return result;}module_init(leds_init_module);module_exit(leds_cleanup_module);MODULE_AUTHOR("ZZ");MODULE_LICENSE("Dual BSD/GPL");//end of leds.c

make生成leds.ko,将其拷贝到开发板上,接着加载之:

insmod leds.ko

接下来创建设备节点。先找到主编号:

cat /proc/device

找到上面定义的DEVICE_NAME(即”led”)对应的编号,即led设备的主编号,我这里为252。接着创建节点:

mknod /dev/leds c 252 0mknod /dev/led0 c 252 1mknod /dev/led1 c 252 2mknod /dev/led2 c 252 3

然后就可以向设备文件里读写数据测试了!
下面是我写的测试程序:

//chled.c#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc, char **argv){    int led_no;    int fd;    char val;    char *filename = NULL;    if (argc < 2) {        printf("Input filename.\n");    }    else {        filename = argv[1];   //第一个参数是设备文件名        fd = open (filename, O_RDWR);        if (fd < 0) {            printf("Fail to open %s\n", filename);            return 1;        }        if (argc == 2) {   //若(除命令名外)只有1个参数,则读出设备状态            read(fd, &val, 1);            printf("%d\n", val);            return 0;        }        else if (argc == 3) {   //若有2个参数则将第二个参数指定的数字写入设备            val = argv[2][0] - '0';            write(fd, &val, 1);            return 0;        }        close(fd);    }    return 1;}//end of chled.c
0 0