android内核字符驱动设备实战之设备驱动程序篇
一. 进入到kernel/goldfish/drivers目录,新建testdev目录:
~/Android$ cd kernel/goldfish/drivers
~/Android/kernel/goldfish/drivers$ mkdir testdev
二. 在hello目录中增加testdev.h文件:
//条件指示符#ifndef...#endif 最主要目的是防止头文件的重复包含和编译
#ifndef _TESTDEV_ANDROID_H_
#define _TESTDEV_ANDROID_H_
#include <linux/cdev.h>
#define TEST_DEVICE_NODE_NAME "testdev"
#define TEST_DEVICE_FILE_NAME "testdev"
#define TEST_DEVICE_CLASS_NAME "testdev"
//虚拟的硬件设备的,字符设备结构体
struct test_android_dev
{
int val; //设备要操作的成员变量
struct cdev dev; //内嵌标准的字符设备结构体,自定义字符设备驱动必须包含该结构
};
#endif
三.在testdev目录中增加testdev.c文件
//需要包含的头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include "testdev.h"
//定义主设备号和从设备号
static int testdev_major = 0;
static int testdev_minor = 0;
//定义设备结构体变量
static struct test_android_dev* test_dev = NULL;
static struct class* testdev_class = NULL;
//定义标准的设备文件操作方法 ,返回值和参数必须按这个标准定义
static int testdev_open(struct inode* inode, struct file* filp);
static int testdev_release(struct inode* inode, struct file* filp);
static ssize_t testdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t testdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
//设备文件操作结构
static struct file_operations testdev_fops = {
.owner = THIS_MODULE,
.open = testdev_open,
.release = testdev_release,
.read = testdev_read,
.write = testdev_write,
};
/*-----------start---设备文件操作函数的实现------------*/
//打开设备
static int testdev_open(struct inode* inode, struct file* filp)
{
struct test_android_dev* dev;
//根据该设备的信息节点inode,获取设备结构体的指针
dev = container_of(inode->i_cdev, struct test_android_dev, dev);
//将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用
// 因为设备读写操作函数只有文件参数file,而没有信息节点参数
filp->private_data = dev;
return 0;
}
//释放设备,空实现,真实设备需要的话,可以在此做最后的清理工作
static int testdev_release(struct inode* inode, struct file* filp)
{
return 0;
}
//设备读取操作,返回值为读取的大小,如果为0则读取失败
static ssize_t testdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos)
{
ssize_t ret = 0;
//获取设备结构体指针
struct test_android_dev* dev = filp->private_data;
//如果用户空间的BUFF小于设备变量的值 ,则退出
if(count < sizeof(dev->val))
{
goto out;
}
//将内核空间设备变量的值拷贝到用户空间的BUF
if(copy_to_user(buf, &(dev->val), sizeof(dev->val)))
{
ret = -EFAULT;
goto out;
}
ret = sizeof(dev->val);
out:
return ret;
}
//设备写入操作,把用户空间的值写入到设备
static ssize_t testdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
ssize_t ret = 0;
//获取设备结构体指针
struct test_android_dev* dev = filp->private_data;
//如果用户空间BUF的大小同设备的不同,则退出
if(count != sizeof(dev->val))
{
goto out;
}
//将用户空间的数据拷贝到设备中去
if(copy_from_user(&(dev->val), buf, count))
{
ret = -EFAULT;
goto out;
}
ret = sizeof(dev->val);
out:
return ret;
}
/*------------end---设备文件操作函数的实现------------*/
//设备初始化注册函数,有模块加载入口函数调用
static int __testdev_setup_dev(struct test_android_dev* dev)
{
int err;
//根据设备的主设备号和从设备号生成设备编号
dev_t devno = MKDEV(testdev_major, testdev_minor);
memset(dev, 0, sizeof(struct test_android_dev));
//设备初始化,向系统注册该设备的操作函数表
cdev_init(&(dev->dev), &testdev_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &testdev_fops;
dev->val = 0;
//添加设备到系统,激活设备
err = cdev_add(&(dev->dev),devno, 1);
if(err)
{
return err;
}
return 0;
}
//模块加载入口函数
static int __init testdev_init(void)
{
int err = -1;
dev_t dev = 0;
printk(KERN_ALERT"Initializing testdev device.\n");
//动态分配主设备和从设备号
err = alloc_chrdev_region(&dev, 0, 1, TEST_DEVICE_NODE_NAME);
if(err < 0)
{
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
//根据设备编号获取主设备号和从设备号
testdev_major = MAJOR(dev);
testdev_minor = MINOR(dev);
//给设备结构体分配空间
test_dev = kmalloc(sizeof(struct test_android_dev), GFP_KERNEL);
if(!test_dev)
{
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc test_dev.\n");
goto unregister;
}
/*初始化设备*/
err = __testdev_setup_dev(test_dev);
if(err) {
printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
goto cleanup;
}
//*****模拟器测试很关键----注册一个类,使mdev可以在"/dev/"目录下 面建立设备节点
testdev_class = class_create(THIS_MODULE, TEST_DEVICE_FILE_NAME);
//*****模拟器测试很关键--创建一个设备节点,节点名为TEST_DEVICE_FILE_NAME
device_create(testdev_class, NULL, dev, "%s", TEST_DEVICE_FILE_NAME);
printk(KERN_ALERT"Succedded to initialize testdev device.\n");
return 0;
cleanup:
kfree(test_dev);
unregister:
unregister_chrdev_region(MKDEV(testdev_major, testdev_minor), 1);
fail:
return err;
}
//模块卸载函数
static void __exit testdev_exit(void)
{
dev_t devno = MKDEV(testdev_major, testdev_minor);
printk(KERN_ALERT"Destroy testdev device.\n");
if(test_dev)
{ //注销设备
cdev_del(&(test_dev->dev));
//释放设备内存
kfree(test_dev);
}
//释放设备号
unregister_chrdev_region(devno, 1);
}
//模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染的警告
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test Android Driver");
//声明模块的初始化入口函数和退出函数
module_init(testdev_init);
module_exit(testdev_exit);
四.在testdev录中新增Kconfig和Makefile两个文件
内核模块或驱动编译时,在模块目录内必须有这两个文件。
分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文档相关的内核配置菜单。
Kconfig文件的内容
config TESTDEV #配置的菜单项为TESTDEV
bool类型的只能选中或不选中,选中为y,不选中为n.
tristate类型的菜单项为值可为三种值,多了编译成内核模块的选项。其值可为y,n,m.
string类型表示需要用户输入一串字符串。
hex类型则需要用户输入一个16进制数。
int类型表示用户输入一个整型.
Makefile文件的内容
五. 修改arch/arm/Kconfig和drivers/kconfig两个文件
在menu "Device Drivers"和endmenu之间添加一行:
八. 编译: