您的位置:首页 > 运维架构 > Linux

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结构绑定的函数中更具次设备号执行不同的操作。

#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个字符设备文件提供应用程序控制
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: