Linux设备驱动开发学习(1)--字符设备驱动

来源:互联网 发布:ubuntu 终端 中文 编辑:程序博客网 时间:2024/05/22 23:28

     前段时间开始学习linux设备驱动开发,主要参考资料<<Linux设备驱动开发详解>>和韦东山的视频教程。这篇文章主要总结Linux设备驱动的内核模块和字符设备的驱动结构,同时在驱动程序中使用udev的功能自动创建多个主设备号相同,不同次设备号的设备节点提供应用程序访问设备,最后介绍一个网上找的通用的Makefile模板来编译驱动以及测试程序的编写。

一、Linux内核模块介绍

    Linux内核模块主要由以下几个部分组成

  •     模块加载函数(必须)
  •     模块卸载函数 (必须)
  •     模块许可证声明 (必须)
  •     模块参数 (可选)
  •     模块导出符号 (可选)
  •     模块作者信息 (可选) 

    我的程序中的几个内核模块代码如下

MODULE_AUTHOR("Lxp");

MODULE_LICENSE("Dual BSD/GPL");

module_init(gec2440_gpio_init);

module_exit(gec2440_gpio_exit);

    其中module_init是内核模块加载函数, module_exit 是内核模块的卸载函数, MODULE_LICENSE 是模块许可证声明,最后是作者信息。其中 gec2440_gpio_init, gec2440_gpio_exit 是字符设备具体的加载和卸载函数。


二、字符设备驱动结构
    Linux使用cdev结构体描述字符设备,主要是设备号dev_t、文件操作结构体file_operation。使用MAJOR(dev_t)、MINOR(dev_t)可以根据设备号得到主次设备号,MKDEV(major, minor)根据主次设备号得到dev_t设备号。file_operation结构包含对设备的读,写,控制等操作。
    程序中的相关函数如下
    alloc_chrdev_region(&devno, 0, 1, "gec2440_gpio"); // 动态的分配设备号赋值给dev_t,第二个参数0表示次设备号从0开始分配。
    cdev_init(&gec2440_gpio, &gec2440_gpio_fops);     //字符设备初始化,初始化cdev结构体成员并且建立cdev和file_operation的连接
    static const struct file_operations gec2440_gpio_fops =     //给文件操作结构体绑定函数。gec2440_gpio_open,gec2440_gpio_write文件操作的接口,提供应用函数调用
    {
.owner =THIS_MODULE,
.open =gec2440_gpio_open,
.write = gec2440_gpio_write,
    };
    cdev_add(&gec2440_gpio, devno, 5);    //向系统添加一个cdev,完成字符设备的注册。注意第二个参数,这个参数对应可以生成的设备文件的个数,比如这里是5,而且我们设置过基础的次设备号是0,因此总共可以创建5个设备文件。在开发板上的/dev/目录下可以用ls -l查看到他们的主次设备号。
    gec2440_gpio_exit()函数中完成在gec2440_gpio_init()函数中申请过的资源的释放工作。

三、udev的使用
    在编写好基本的设备驱动的函数以后,我们可以下载到开发板上,加载驱动,然后通过mknod命令手动创建设备文件。利用udev,我们可以让系统自动创建出设备文件。主要涉及到class类和device_create函数。
    程序中的相关函数如下
    gec2440_gpio_class = class_create(THIS_MODULE, "gec2440_gpio_class");    //创建一个类
    device_create( gec2440_gpio_class, NULL, devno, NULL, "LED_ALL");    //根据类创建一个设备文件,设备号是devno ,设备名是LED_ALL

四、驱动程序代码
    该程序在动态分配主设备号,自动创建次设备号是0~4的设备文件。在file_operation结构绑定的函数中更具次设备号执行不同的操作。
#include <linux/errno.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <asm/io.h>#include <asm/uaccess.h>#include <linux/cdev.h>#include <linux/device.h>#define gMajor  0struct cdev gec2440_gpio;dev_t devno = MKDEV(gMajor, 0);struct class *gec2440_gpio_class;unsigned int gpio_va;#define GPIO_OFT(x) ((x) - 0x56000000)#define rGPFCON (*(volatile unsigned int *)(gpio_va + GPIO_OFT(0x56000050)))#define rGPFDAT (*(volatile unsigned int  *)(gpio_va + GPIO_OFT(0x56000054)))static int gec2440_gpio_open(struct inode *inode, struct file *filp){    int minor = MINOR(inode->i_rdev);    switch (minor)    {        case 0:            rGPFCON &= ~(0x3<<8|0x3<<10|0x3<<12|0x3<<14);            rGPFCON |= (0x1<<8|0x1<<10|0x1<<12|0x1<<14);            break;        case 1:            rGPFCON &= ~(0x3<<8);            rGPFCON |= 0x1<<8;            break;        case 2 :            rGPFCON &= ~(0x3<<10);            rGPFCON |= 0x1<<10;            break;        case 3 :            rGPFCON &= ~(0x3<<14);            rGPFCON |= 0x1<<14;            break;        case 4 :            rGPFCON &= ~(0x3<<12);            rGPFCON |= 0x1<<12;            break;        default:            break;    }        printk(KERN_INFO "gec2440_gpio_open\n\r");    return 0;}static ssize_t gec2440_gpio_write(struct file* filp, const char __user *buffer, size_t count, loff_t *ppos){    int minor = MINOR(filp->f_dentry->d_inode->i_rdev);    char val;    copy_from_user(&val, buffer, 1);    switch ( minor )    {        case 0 :        {            rGPFDAT &= ~(0x1<<4|0x1<<5|0x1<<6|0x1<<7);            rGPFDAT |= (val<<4|val<<5|val<<6|val<<7);        }            break;        case 1 :        {            rGPFDAT &= ~(0x1<<4);            rGPFDAT |= (val<<4);        }            break;        case 2 :        {            rGPFDAT &= ~(0x1<<5);            rGPFDAT |= (val<<5);        }            break;        case 3 :        {            rGPFDAT &= ~(0x1<<7);            rGPFDAT |= (val<<7);        }            break;        case 4 :        {            rGPFDAT &= ~(0x1<<6);            rGPFDAT |= (val<<6);        }            break;        default:            break;    }    return 0;}static const struct file_operations gec2440_gpio_fops = {.owner =THIS_MODULE,.open =gec2440_gpio_open,.write = gec2440_gpio_write,};static int gec2440_gpio_init(void){        int result, minor;    gpio_va = (unsigned int)ioremap(0x56000000, 0x100000);    if (!gpio_va)     {return -EIO;}        if (gMajor)    {        result = register_chrdev_region(devno, 1, "gec2440_gpio");    }    else    {        result = alloc_chrdev_region(&devno, 0, 1, "gec2440_gpio");    }    if ( result < 0)    {        return result;    }        cdev_init(&gec2440_gpio, &gec2440_gpio_fops);    gec2440_gpio.owner = THIS_MODULE;    gec2440_gpio.ops = &gec2440_gpio_fops;    result = cdev_add(&gec2440_gpio, devno, 5);    if(result)    {        printk(KERN_NOTICE "Error %d adding gec2440_gpio", result);    }       gec2440_gpio_class = class_create(THIS_MODULE, "gec2440_gpio_class");    if ( IS_ERR(gec2440_gpio_class))    {        printk(KERN_ERR "failed in class_create\n\r");        return -1;    }        device_create( gec2440_gpio_class, NULL, devno, NULL, "LED_ALL");    for( minor = 1; minor < 5; minor++)    {        device_create( gec2440_gpio_class, NULL, MKDEV(MAJOR(devno), minor), NULL, "LED_""%d", minor);    }        printk(KERN_INFO "gec2440_gpio_init\n\r");    return 0;}void gec2440_gpio_exit(void){    int minor;        cdev_del(&gec2440_gpio);    for ( minor = 0 ; minor < 5; minor++ )    {        device_destroy(gec2440_gpio_class, MKDEV(MAJOR(devno), minor)); //delete device     }    class_destroy(gec2440_gpio_class);                 //delete class         unregister_chrdev_region(devno, 1);    iounmap((void __iomem *)gpio_va);    printk(KERN_INFO "gec2440_gpio_exit\n\r");}MODULE_AUTHOR("Lxp");MODULE_LICENSE("Dual BSD/GPL");module_init(gec2440_gpio_init);module_exit(gec2440_gpio_exit);
五、为驱动程序编写Makefile
    这个Makefile是网上找的模板,简单修改就可以用来编译驱动模块或者编译驱动到内核。具体使用参考http://blog.chinaunix.net/uid-20543672-id-3241147.html
MODULE_NAME = gpio
MODULE_CONFIG = CONFIG_GPIO_CONTROL
CROSS_CONFIG = y
# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y

ifneq ($(KERNELRELEASE),)
# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
DEBFLAGS = -O -g -DDEBUG # "-O" is needed to expand inlines
else
DEBFLAGS = -O2
endif

ccflags-y += $(DEBFLAGS)
obj-$($(MODULE_CONFIG)) := $(MODULE_NAME).o
#for Multi-files module
#$(MODULE_NAME)-objs := hello_linux_simple_dep.o ex_output.o
else
ifeq ($(CROSS_CONFIG), y)
#for Cross-compile
KERNELDIR = /home/share/linux-2.6.30.4/
ARCH = arm
#FIXME:maybe we need absolute path for different user. eg root
#CROSS_COMPILE = arm-none-linux-gnueabi-
CROSS_COMPILE =  /opt/EmbedSky/4.3.3/bin/arm-linux-
#INSTALLDIR := (目标模块所安装的根文件系统路径)
INSTALLDIR := $(shell pwd)
else
#for Local compile
KERNELDIR = /lib/modules/$(shell uname -r)/build
ARCH = x86
CROSS_COMPILE =
INSTALLDIR := /
endif

################################
PWD := $(shell pwd)
.PHONY: modules modules_install clean
modules:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) $(MODULE_CONFIG)=m -C $(KERNELDIR) M=$(PWD) modules
modules_install: modules
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) $(MODULE_CONFIG)=m -C $(KERNELDIR) INSTALL_MOD_PATH=$(INSTALLDIR) M=$(PWD) modules_install
clean:
@rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order .*.o.d modules.builtin
endif

六、应用程序测试代码
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#include <stdlib.h>void print_usage(char *file){    printf("Usage:\n");    printf("%s <dev> <on|off>\n",file);    printf("eg. \n");    printf("%s /dev/LED_ALL on\n", file);    printf("%s /dev/LED_1 off\n", file);}int main(int argc, char **argv){    int fd;    char* filename;    char val;    if (argc != 3)    {        print_usage(argv[0]);        return 0;    }    filename = argv[1];    fd = open(filename, O_RDWR);    if (fd < 0)    {        printf("error, can't open %s\n", filename);        return 0;    }    if (!strcmp("on", argv[2]))    {        // 亮灯        val = 0;        write(fd, &val, 1);    }    else if (!strcmp("off", argv[2]))    {        // 灭灯        val = 1;        write(fd, &val, 1);    }    else    {        print_usage(argv[0]);        return 0;    }            return 0;}

结束语
使用应用程序操作驱动的设备文件,可以根据打开不同的次设备号的文件执行不同的操作。 该驱动程序使用了udev自动创建5个字符设备文件提供应用程序控制

 

原创粉丝点击