Linux设备驱动开发学习(1)--字符设备驱动
2012-08-31 18:18
447 查看
前段时间开始学习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结构绑定的函数中更具次设备号执行不同的操作。
五、为驱动程序编写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
六、应用程序测试代码
结束语
使用应用程序操作驱动的设备文件,可以根据打开不同的次设备号的文件执行不同的操作。 该驱动程序使用了udev自动创建5个字符设备文件提供应用程序控制
一、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 0 struct 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个字符设备文件提供应用程序控制
相关文章推荐
- linux设备驱动开发学习之旅--简单字符驱动实例globalmem
- [Linux驱动]字符设备驱动学习笔记(一)
- Linux 字符设备驱动开发基础(三)—— read()、write() 相关函数解析
- Linux驱动开发之字符设备
- 基于mini6410的linux驱动学习总结(二 字符设备与块设备的区别)
- “手把手教你学linux驱动开发”OK6410系列之03---LED字符设备驱动
- Linux设备驱动之简单字符设备驱动开开发
- linux驱动学习--第十二天:第六章 Linux 字符设备驱动(二) 之 globalmem 设备驱动
- Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析
- linux驱动开发--字符设备:动态分配设备号
- linux驱动开发--字符设备:简单的file_operations示例
- linux字符设备驱动开发模板及Makefile
- Linux学习二, 中断编程和字符设备驱动
- Linux字符设备驱动程序开发(1)-使用字符设备驱动
- linux驱动开发之字符设备驱动编程步骤简述
- linux驱动开发:1.字符设备驱动开发
- “手把手教你学linux驱动开发”OK6410系列之02---LED字符设备驱动 .
- linux字符设备驱动学习笔记(一):简单的字符设备驱动
- [置顶] 整理--Linux字符设备驱动开发基础
- Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动