一个完整的驱动程序及功能测试
驱动程序的编写
一个驱动程序通常包括3个部分:.h头文件、.c文件和Makefile。其中,.h头文件一般会定义一些宏和这个设备相关的结构体;.c文件是驱动功能的实现;Makefile则是对模块生成规则的一些定义。
在这个例子中,我们定义了一个功能非常简单,结构却十分完整的字符型驱动程序。之所以说功能简单,是因为这个设备只能输出一些调试信息、存储一个int型的数据;而结构完整,则是因为它具备了在"/sys/"、"/dev/"、"/proc/"三个目录下生成设备文件的能力,能在"/dev/"下自动挂载,而且还提供了ioctl接口功能。
首先给这个虚拟设备取个名称——"vircdev",然后我们分别来说一下这三个文件:
一、头文件
//vircdev.h
#ifndef _VIRCDEV_H_
#define _VIRCDEV_H_
#define VIRCDEV_NUM (1)
#define VIRCDEV_CLASS_NAME "vircdev_class"
#define VIRCDEV_DEVICE_NAME "vircdev_device"
#define VIRCDEV_NODE_NAME "vircdev_node"
#define VIRCDEV_PROC_NAME "vircdev_proc"
#define VIRCDEV_IOC_MAGIC 'k'
#define VIRCDEV_IOHI _IO(VIRCDEV_IOC_MAGIC, 0)
#define VIRCDEV_IOWBYTE _IOW(VIRCDEV_IOC_MAGIC, 1, int)
#define VIRCDEV_IORBYTE _IOR(VIRCDEV_IOC_MAGIC, 2, int)
struct vircdev_cntx {
int r1;
struct semaphore sem;
struct cdev cdev;
};
#endif
这里首先是定义了设备的个数,因为linux中设备是由一个主设备号和一个副设备号区分的,而系统只会区分主设备号,并在一些通用文件访问(如read、write)时转向设备对应的驱动程序,而副设备号系统则是完全漠视,直接传给驱动程序,由驱动进行处理的,这里的VIRCDEV_NUM则代表了设备的个数(自然也决定了副设备号的个数)。
接着,是定义了一些名称,这些名称将会在创建设备文件时使用。
而接下来这些宏,如VIRCDEV_IOHI,则是用于ioctl中的cmd命令;VIRCDEV_IOC_MAGIC是我们通常所说的幻数,这个数的定义要比较小心,一般是取系统中未使用的,以确保ioctl中cmd命令的唯一性,这样在对错误的文件使用这些命令时便不会出现不可预知的错误。
二、vircdev.c文件
1、全局变量的定义:
/*====global====*/
static int g_vircdev_major = 0;
static int g_vircdev_minor = 0;
struct vircdev_cntx *g_vircdev_ptr = NULL;
struct class *g_vircdev_class = 0;
static struct file_operations g_vircdev_fops = {
.owner = THIS_MODULE,
.read = vircdev_read,
.write = vircdev_write,
.unlocked_ioctl = vircdev_unlocked_ioctl,
.open = vircdev_open,
.release = vircdev_release,
};
g_vircdev_major、g_vircdev_minor为主副设备号;g_vircdev_ptr为设备结构体变量指针;g_vircdev_class是在构建/dev/设备文件时需要保留的一个struct class中间变量,在exit时要使用这个变量进行destroy;g_vircdev_fops,这个就不用过多解释了,设备的大部分操作都在这里面。
2、定义传统设备文件的访问方法,主要是定义vircdev_read、vircdev_write、vircdev_unlocked_ioctl、vircdev_open和vircdev_release:
ssize_t vircdev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {
printk(KERN_ALERT "vircdev read.\n");
return 0;
}
ssize_t vircdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
printk(KERN_ALERT "vircdev write.\n");
return count;
}
long vircdev_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
int err = 0;
long retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != VIRCDEV_IOC_MAGIC) return -ENOTTY;
//if (_IOC_NR(cmd) > VIRCDEV_IOC_MAXNR) return -ENOTTY;
/*
* to verify *arg is in user space
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch(cmd)
{
case VIRCDEV_IOHI:
printk(KERN_ALERT "ioctl:you said hi.\n");
break;
case VIRCDEV_IOWBYTE:
//retval = __get_user(g_vircdev_ptr->r1, (int __user *)arg);
g_vircdev_ptr->r1 = (int __user *)arg;
printk(KERN_ALERT "ioctl:write r1 to %i.\n", g_vircdev_ptr->r1);
break;
case VIRCDEV_IORBYTE:
retval = __put_user(g_vircdev_ptr->r1, (int __user *)arg);
printk(KERN_ALERT "ioctl:read r1:%i.\n", g_vircdev_ptr->r1);
break;
default:
printk(KERN_ALERT "ioctl:you got the wrong command.\n");
break;
}
return retval;
}
int vircdev_open(struct inode *inode, struct file *filp) {
return 0;
}
int vircdev_release(struct inode *inode, struct file *filp) {
return 0;
}
3、动态分配设备号函数:
//alloc device major
static int vircdex_alloc_major(void) {
dev_t devt = 0;
int result = 0;
result = alloc_chrdev_region(&devt, g_vircdev_minor, VIRCDEV_NUM, VIRCDEV_NODE_NAME);
g_vircdev_major = MAJOR(devt);
return result;
}
static int vircdev_release_major(void) {
dev_t devt = MKDEV(g_vircdev_major, g_vircdev_minor);
unregister_chrdev_region(devt, 1);
return 0;
}
4、字符设备安装函数:
static int vircdev_setup_dev(struct vircdev_cntx *dev) {
int err, devno = MKDEV(g_vircdev_major, g_vircdev_minor);
cdev_init(&(dev->cdev), &g_vircdev_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if(err){
return err;
}
//init_MUTEX(&(dev->sem));
sema_init(&(dev->sem), 1);
return 0;
}
static int vircdev_unsetup_dev(struct vircdev_cntx *dev) {
cdev_del(&(dev->cdev));
return 0;
}
5、创建设备文件函数:
static int vircdev_create_devfiles(dev_t devt) {//, const struct device_attribute *attr) {
int err = -1;
struct device *dev = NULL;
g_vircdev_class = class_create(THIS_MODULE, VIRCDEV_CLASS_NAME);
if(IS_ERR(g_vircdev_class)) {
err = PTR_ERR(g_vircdev_class);
printk(KERN_ALERT "Failed to create class.\n");
goto CLASS_CREATE_ERR;
}
dev = device_create(g_vircdev_class, NULL, devt, NULL, VIRCDEV_DEVICE_NAME);
//dev = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
//device_create( my_class, NULL, MKDEV(hello_major, 0), "hello" "%d", 0 );
//dev = device_create(g_vircdev_class, NULL, MKDEV(MYDRIVER_Major, 0), NULL, DEVICE_NAME);
if(IS_ERR(dev)) {
err = PTR_ERR(dev);
printk(KERN_ALERT "Failed to create device.\n");
goto DEVICE_CREATE_ERR;
}
/*err = device_create_file(dev, attr);
if(err < 0) {
printk(KERN_ALERT"Failed to create attribute file.");
goto DEVICE_CREATE_FILE_ERR;
}*/
printk(KERN_ALERT "seems ok.\n"); //zmk@@debug
return 0;
DEVICE_CREATE_FILE_ERR:
device_destroy(g_vircdev_class, devt);
DEVICE_CREATE_ERR:
class_destroy(g_vircdev_class);
CLASS_CREATE_ERR:
return err;
}
static int vircdev_delete_devfiles(dev_t devt) {
device_destroy(g_vircdev_class, devt);
class_destroy(g_vircdev_class);
//device_remove_file(dev, attr);
return 0;
}
6、创建/proc调试文件函数:
static int vircdev_read_proc(char *buf, char **start, off_t offset,
int count, int *eof, void *data)
{
int len =sprintf(buf, "vircdev read proc.\n");
return len;
}
static int vircdev_create_proc_file(void) {
struct proc_dir_entry *entry = NULL;
entry = create_proc_read_entry(VIRCDEV_PROC_NAME, 0,
NULL, vircdev_read_proc,
NULL);
if(entry)
{
return 0;
}
else
{
return -1;
}
}
static int vircdev_delete_proc_file(void) {
remove_proc_entry(VIRCDEV_PROC_NAME, NULL);
return 0;
}
7、设备驱动模块的加载和释放:
MODULE_LICENSE("GPL");
static int vircdev_init(void) {
int err = -1;
dev_t devt = 0;
//[1] alloc node number
err = vircdex_alloc_major();
if(0 > err)
{
printk(KERN_ALERT"alloc major failed.\n");
goto ALLOC_MAJOR_ERR;
}
devt = MKDEV(g_vircdev_major, g_vircdev_minor);
//[2] device object init
g_vircdev_ptr = kmalloc(sizeof(struct vircdev_cntx), GFP_KERNEL);
if(!g_vircdev_ptr) {
err = -ENOMEM;
printk(KERN_ALERT"kmalloc failed.\n");
goto KMALLOC_ERR;
}
memset(g_vircdev_ptr, 0, sizeof(struct vircdev_cntx));
//[3] setup device
err = vircdev_setup_dev(g_vircdev_ptr);
if(0 > err)
{
printk(KERN_ALERT"device setup failed.\n");
goto DEVICE_SETUP_ERR;
}
//[4] create files in directory "/dev/" and "/sys/"
///err = vircdev_create_devfiles(devt, attr);
err = vircdev_create_devfiles(devt);
if(0 > err)
{
printk(KERN_ALERT"devfiles create failed.\n");
goto DEVFILES_CREATE_ERR;
}
//[5] create proc file
err = vircdev_create_proc_file();
if(0 > err)
{
printk(KERN_ALERT"proc file create failed.\n");
goto PROC_FILE_CREATE_ERR;
}
return 0;
PROC_FILE_CREATE_ERR:
vircdev_delete_devfiles(devt);
DEVFILES_CREATE_ERR:
vircdev_unsetup_dev(g_vircdev_ptr);
DEVICE_SETUP_ERR:
kfree(g_vircdev_ptr);
ALLOC_MAJOR_ERR:
vircdev_release_major();
KMALLOC_ERR:
return err;
}
static void vircdev_exit(void) {
dev_t devt = MKDEV(g_vircdev_major, g_vircdev_minor);
vircdev_delete_proc_file();
vircdev_delete_devfiles(devt);
vircdev_unsetup_dev(g_vircdev_ptr);
kfree(g_vircdev_ptr);
vircdev_release_major();
}
module_init(vircdev_init);
module_exit(vircdev_exit);
三、Makefile
obj-m := vircdev.o KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
关于Makefile,在《驱动模块中Makefile的自我理解》一文中有过说明,这里不再作过多解释。
四、验证
1.进入vircdev文件夹,编译:
$:cd vircdev $:make
查看是否生成.ko文件:
$:ls
结果:
Makefile Module.symvers vircdev.h vircdev.mod.c vircdev.o modules.order vircdev.c vircdev.ko vircdev.mod.o
2.载入模块:
$:sudo insmod ./vircdev.ko
查看模块是否载入成功:
$:lsmod |grep vir
结果:
vircdev 12980 0
3.分别使用cat、echo命令验证驱动模块的open、write、read和close功能:
$:cat /dev/vircdev_device $:echo abcd > /dev/vircdev_device $:dmesg |grep vircdev
结果:
[34058.091485] vircdev read. [34134.980198] vircdev write.
4.验证/proc调试功能:
$:cat /proc/vircdev_proc
结果:
vircdev read proc.
附:驱动程序源文件下载:文件:Vircdev.zip
ioctl测试程序的编写
鉴于read、write函数已经通过cat、echo验证ok了,现在这里编写的验证函数主要是为了验证ioctl函数。
//vircdev_ioctl_test.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <asm/ioctls.h>
#define DEVICE_NAME "/dev/vircdev_device"
#define VIRCDEV_IOC_MAGIC 'k'
#define VIRCDEV_IOHI _IO(VIRCDEV_IOC_MAGIC, 0)
#define VIRCDEV_IOWBYTE _IOW(VIRCDEV_IOC_MAGIC, 1, int)
#define VIRCDEV_IORBYTE _IOR(VIRCDEV_IOC_MAGIC, 2, int)
int main(int argc, char *argv[])
{
char cmd = *argv[1];
int arg = *(argv[1]+1) - 48;
int fd = 0;
printf("cmd = %c, arg = %d\n", cmd, arg);
fd = open(DEVICE_NAME, O_RDWR);
switch(cmd)
{
case 'h':
ioctl(fd, VIRCDEV_IOHI, arg);
break;
case 'w':
ioctl(fd, VIRCDEV_IOWBYTE, arg);
break;
case 'r':
ioctl(fd, VIRCDEV_IORBYTE, &arg);
printf("read value:%d\n", arg);
break;
default:
break;
}
close(fd);
return 0;
}
函数首先是定义了之前在驱动模块vircdev.h中定义过的宏,这里只是拷贝过来,不过为规范起见,建议是直接将该头文件包含过来,另外由于使用了_IOW等宏,要包含"asm/ioctls.h"头文件。
这个函数是取得用户在命令行输入的命令,然后控制对应的ioctl:
h -- VIRCDEV_IOHI,使设备驱动打印消息"ioctl:you said hi."
w -- VIRCDEV_IOWBYTE,写vircdev设备中的r1寄存器
r -- VIRCDEV_IORBYTE,读vircdev设备中的r1寄存器
编译:
$:gcc gcc vircdev_ioctl_test.c
读寄存器(自然前提是vircdev驱动模块已载入到系统):
$:./a.out r1
cmd = r, arg = 1 read value:9
写寄存器:
$:./a.out w5
cmd = w, arg = 5
再读出当前寄存器的值:
$:./a.out r1
cmd = r, arg = 1 read value:5
与写入值相等,sucess!!!
vircdev_ioctl_test.c下载:文件:Vircdev ioctl test.c.zip