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个字符设备文件提供应用程序控制
- Linux设备驱动开发学习(1)--字符设备驱动
- linux设备驱动学习笔记(1)-字符设备驱动
- linux驱动开发-字符设备
- linux字符设备驱动开发
- Linux字符设备驱动程序开发(1)-使用字符设备驱动
- 【驱动】linux设备驱动·字符设备驱动开发
- linux字符设备驱动学习
- Linux字符设备驱动学习
- Linux驱动学习字符设备
- linux学习--字符设备驱动
- LINUX字符设备驱动学习
- Linux驱动开发学习--字符设备驱动结构
- Linux 驱动开发-字符设备驱动
- 字符设备驱动 - linux驱动开发
- linux字符设备驱动学习笔记1
- Linux字符设备驱动学习1
- Linux驱动学习笔记;字符设备驱动
- Hasen的linux设备驱动开发学习之旅--支持多设备的字符设备驱动
- 程序员的十层楼,你在第几层???
- EditText
- RAC 的一些概念性和原理性的知识
- shell判断文件是否存在
- PHP中判断数组是否为空的方法
- Linux设备驱动开发学习(1)--字符设备驱动
- 12 Bash For Loop Examples for Your Linux Shell Scripting
- 男人与女人之间灰常有趣的35条定律
- 一个关于数组中满足条件的元素选择、及再次排序得c语言函数。
- 创建链接服务器出现“解密过程中出错”问题
- PMD规则之Basic Rules
- shell 数组建立及使用技巧
- HDU 3826 Squarefree number
- PMD规则之Braces Rules