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
支持以模块、内建和不编译三种编译方法。后面是模块的提示字符
default n #默认值:y-内建 m-模块,n-不编译;此处表示:在编译配置时,如果用户没有选择,则先择不编译
help #help相当于注释一样,在给编辑Kconfig文件的人看的,这样可以保持其可读性,下面为注释的内容
This is the test android driver.
注:模块的编译类型,有编译、还是不编译、以及编译成什么类型。类型可以是:
bool、tristate、string、hex和int。
bool类型的只能选中或不选中,选中为y,不选中为n.
tristate类型的菜单项为值可为三种值,多了编译成内核模块的选项。其值可为y,n,m.
string类型表示需要用户输入一串字符串。
hex类型则需要用户输入一个16进制数。
int类型表示用户输入一个整型.
Makefile文件的内容
obj-$(CONFIG_TESTDEV) +=testdev.o
hello.o这个目标是要编译进内核,还是作为模块编译:
obj-y +=testdev.o
obj-m+= testdev.o
obj-n += testdev.o //不编译
$(CONFIG_TESTDEV)决定,而它的只在内核模块编译配置时,有用户设置
五. 修改arch/arm/Kconfig和drivers/kconfig两个文件
在menu "Device Drivers"和endmenu之间添加一行:
source "drivers/testdev/Kconfig" #菜单连接项,执行时需要连接该目录下的Kconfig文件
六. 修改drivers/Makefile文件
添加如下内容:
obj-$(CONFIG_TESTDEV) += testdev/
表示testdev目录是否编译,当CONFIG_TESTDEV为y或者m的时候,会去找testdev目录下的Makefile文件
七. 配置编译选项:
/Android/kernel/goldfish$ make menuconfig
找到"Device Drivers" => "Test Android Drivers"选项,设置为y.
配置编译选项的工作流程:
1、执行编译选项配置命令:make menuconfig
arch/arm/Kconfig和drivers/kconfig,读取主配置菜单。
Device Drivers”的菜单,其菜单中有一个
菜单连接项source "drivers/testdev/Kconfig"
4、根据菜单连接项的指示,把testdev这个目录里的Kconfig文件连接进来。
5、根据testdev目录里Kconfig的内容,使在配置编译选项的配置界面中,
设备驱动菜单"Device Drivers"下,包含"Test Android Drivers"这个菜单项,
在此选择y,表示把testdev驱动编译进内核。
6、在配置完成后,goldfish目录下生成一个.config文件,这是个隐藏文件,
这个文件记录着各个选项的配置及值。供Makefile文件使用.
八. 编译:
/Android/kernel/goldfish$ make
编译成功后,就可以在testdev目录下看到testdev.o文件了,这时候编译出来的zImage已经包含了testdev驱动。
编译的工作流程:
1、执行编译命令:make
2、读取内核配置文档".config",可知CONFIG_TESTDEV值为y
执行drivers/Makefile->其内容
有:obj-$(CONFIG_TESTDEV) += testdev/这一项。
4、由于CONFIG_TESTDEV值为y,则编译testdev目录下内容,
调用testdev目录下编译文件Makefile
obj-$(CONFIG_TESTDEV) +=testdev.o:由于CONFIG_TESTDEV值为y,
testdev.o编译进内核。
九. 运行新编译的内核文件,验证testdev驱动程序是否已经正常安装:
/Android$ emulator -kernel ./kernel/goldfish/arch/arm/boot/zImage &
/Android$ adb shell
进入到dev目录,可以看到testdev设备文件:
root@android:/ # cd dev
root@android:/dev # ls
android 驱动注入
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
centos7 dpkg使用
一、准备环境 磨刀不误砍柴工,要想更好的进行dpdk源码分析,需要搭建一套dpdk环境,观察数据包的转发流程。由于个人电脑条件有限,只能在vmware虚拟机环境下搭建dpdk环境。dpdk源码分析系列的所有文章都是基于这套环境来分析。1、vmware虚拟机上安装ubuntun系统, ubuntu版本为12.04; linux内核版本
centos7 dpkg使用 dpdk编译 dpdk安装 dpdk环境搭建 ubuntu dpdk