HIDL Hal 开发指南7 —— 驱动开发

前言

HIDL HAL 的整体架构如下图所示:

接下来我们就来完成一个从驱动到 App 的完整 HIDL HAL 实现示例。

本节的任务是在内核中实现一个简单的驱动,并完成一个应用层 Native 程序来测试我们的驱动是否正常工作。

1 编写一个简单的 Linux 内核驱动

1.1 编写驱动

Linux 驱动实际就是一个 Linux 内核模块。

首先,我们需要理解什么是内核模块?简单来说,内核模块是一段 "固定格式" 的代码,像一个"插件"一样,linux 内核可以动态的加载并执行这段代码,也可以把这段代码编译进内核,在内核启动的时候来执行这段代码。

下面我们写一个简单的 linux 驱动,如果你对驱动开发还不太了解,可以先学习[学习 Binder 的预备知识]的Linux驱动章节。

在内核的 drivers/char 目录中添加 hello_driver.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 1. 确定主设备号                                                                 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;

#define MIN(a, b) (a < b ? a : b)

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return MIN(1024, size);
}

static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));
	return MIN(1024, size);
}

static int hello_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int hello_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_drv_open,
	.read    = hello_drv_read,
	.write   = hello_drv_write,
	.release = hello_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{
	int err;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */

	//提供设备信息,自动创建设备节点。
	// /dev/hello
	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "hello");
		return -1;
	}

	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
	//到这里我们就可以通过 /dev/hello 文件来访问我们的驱动程序了。
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit hello_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	unregister_chrdev(major, "hello");
}

/* 7. 其他完善:提供设备信息,自动创建设备节点*/

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

这里的驱动实现非常简单,即使简单的拷贝一下数据。

1.2 将模块编译进内核

接下来我们修改 /drivers/char/Kconfig 文件,使得我们的 hello_driver 模块,能出现在内核的编译选项中。

在 /drivers/char 中的 Kconfig 文件中添加:

config HELLO_DRIVER_MODULE
	bool "hello driver module support"
	default y

然后在 /drivers/char 下的 Makefile 文件中添加:

obj-$(CONFIG_HELLO_DRIVER_MODULE)       += hello_driver.o

当在 make menuconfig 编译菜单中选中了 hello module support, CONFIG_HELLO_MODULE 的值是 y,没有选中值是 m(我们定义的默认值是 y):

  • obj-y += hello_driver.o 的意思是将 hello_driver.o 编译进内核
  • obj-m += hello_driver.o 的意思是文件 hello_driver.o 作为"模块"进行编译,不会编译到内核,但是会生成一个独立的 "hello_driver.ko" 文件,可以使用 insmod 命令将模块加载到内核中

最后配置内核:

cp ./arch/x86/configs/x86_64_ranchu_defconfig .config
make menuconfig
# 完成后续配置后执行
cp .config ./arch/x86/configs/x86_64_ranchu_defconfig 

进入 Device Drivers 选项:

进入 Character devices

这里就可以看见我们刚才添加的选项,默认是选上的。

然后执行编译:

# clean 一下,防止出错
make clean
# 执行之前的编译脚本
sh build.sh

进入 AOSP 源码启动模拟器:

source build/envsetup.sh
lunch aosp_x86_64-eng
emulator -kernel ~/kernel/goldfish/arch/x86_64/boot/bzImage

查看开机信息:

# dmesg 用于显示开机信息
adb shell dmesg | grep hello

2. 权限配置

要把驱动集成到系统中,还需要添加一些权限相关的配置:

system/core/rootdir/ueventd.rc 中添加:

/dev/hello 				  0666   root       root

device/jelly/rice14/sepolicy/device.te 中添加(如果文件不存在,创建文件):

type hello_dev_t, dev_type;

device/jelly/rice14/sepolicy/file_contexts (如果文件不存在,创建文件):

/dev/hello                  u:object_r:hello_dev_t:s0

device/jelly/rice14/rice14.mk 中添加:

BOARD_SEPOLICY_DIRS += \
    device/jelly/rice14/sepolicy

3. 写一个 Native 程序,测试我们的驱动程序

frameworks/base/native/ 目录下创建如下的目录结构:

hello_drv_test/
├── Android.bp
└── hello_drv_test.c

其中 hello_drv_test.c 的内容如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./hello_drv_test -w abc
 * ./hello_drv_test -r
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;

	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 1024);
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}

	close(fd);

	return 0;
}

测试程序的内容很简单,就是根据命令行参数读写 /dev/hello 文件。

接着编写 Android.bp 文件:

cc_binary {                
    name: "hello_drv_test",          
    srcs: ["hello_drv_test.c"],     //源文件列表
    cflags: ["-Werror"],            //添加编译选项
}

编译程序并上传模拟器:

# 编译
cd frameworks/base/native/hello_drv_test
mm
# 打开模拟器,流程略
# 上传可执行文件
cd -
adb push ./out/target/product/rice14/system/bin/hello_drv_test /data/local/tmp
# 进入到模拟器 shell
adb shell
# 执行程序
cd /data/local/tmp


./hello_drv_test -w "hello"
./hello_drv_test -r

执行程序的结果如下所示:


如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

相关推荐
ROBIN__dyc几秒前
表达式
算法
无限大.1 分钟前
c语言200例 067
java·c语言·开发语言
余炜yw3 分钟前
【Java序列化器】Java 中常用序列化器的探索与实践
java·开发语言
攸攸太上3 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
无限大.4 分钟前
c语言实例
c语言·数据结构·算法
Kenny.志6 分钟前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
六点半8888 分钟前
【C++】速通涉及 “vector” 的经典OJ编程题
开发语言·c++·算法·青少年编程·推荐算法
不修×蝙蝠8 分钟前
八大排序--01冒泡排序
java
@haihi16 分钟前
冒泡排序,插入排序,快速排序,选择排序
数据结构·算法·排序算法
quaer19 分钟前
Open-Sora全面开源?
开发语言·算法·机器学习·matlab·矩阵