Next, we’ll walk through an example of a complete HIDL HAL implementation from driver to app.
The task of this section is to implement a simple driver in the kernel and complete an application layer Native program to test if our driver works properly.
1 Write a simple Linux kernel driver
1.1 Writing the driver
A Linux driver is actually a Linux kernel module.
First, we need to understand what is a kernel module? Simply put, a kernel module is a piece of “fixed-format” code, like a “plug-in”, which can be dynamically loaded and executed by the linux kernel, or compiled into the kernel and executed when the kernel starts.
Here we write a simple linux driver. If you don’t know much about driver development, you can first study the Linux
driver chapter to learn how to prepare for Binder.
Add hello_driver.c to the kernel’s drivers/char
directory
#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>
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;
#define MIN(a, b) (a < b ? a : b)
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;
}
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,
};
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 */
return 0;
}
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");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
The driver implementation here is very simple, even if it simply copies the data.
1.2 Compiling modules into the kernel
Next, we modify the /drivers/char/Kconfig file to make our hello_driver module appear in the kernel’s compilation options.
Add it to the Kconfig file in /drivers/char:
config HELLO_DRIVER_MODULE
bool "hello driver module support"
default y
Then add it to the Makefile file under /drivers/char:
obj-$(CONFIG_HELLO_DRIVER_MODULE) += hello_driver.o
When hello module support is checked in the make menuconfig build menu, the value of CONFIG_HELLO_MODULE is y, and the value of m is unchecked (we define y as the default value):
obj-y += hello_driver.o means compile hello_driver.o into the kernel.
obj-m += hello_driver.o means that the file hello_driver.o is compiled as a “module”, which will not be compiled into the kernel, but will generate a separate “hello_driver.ko” file, which can be loaded into the kernel with the command insmod.
Finally configure the kernel:
cp ./arch/x86/configs/x86_64_ranchu_defconfig .config
make menuconfig
cp .config ./arch/x86/configs/x86_64_ranchu_defconfig
Character devices
Here you can see the option we just added, which is checked by default.
Then execute the compilation:
make clean
sh build.sh
Go to the AOSP source code to launch the simulator:
source build/envsetup.sh
lunch aosp_x86_64-eng
emulator -kernel ~/kernel/goldfish/arch/x86_64/boot/bzImage
View power-up information:
adb shell dmesg | grep hello
2. Configuration of authority
To integrate the driver into the system, you also need to add some permission-related configuration:
Added in system/core/rootdir/ueventd.rc
:
/dev/hello 0666 root root
Add (create file if it doesn’t exist) in device/jelly/rice14/sepolicy/device.te
:
type hello_dev_t, dev_type;
At device/jelly/rice14/sepolicy/file_contexts
(create file if it doesn’t exist).
/dev/hello u:object_r:hello_dev_t:s0
Added in device/jelly/rice14/rice14.mk
:
BOARD_SEPOLICY_DIRS += \
device/jelly/rice14/sepolicy
3. Write a Native program to test our driver.
Create the following directory structure under the frameworks/base/native/
directory:
hello_drv_test/
├── Android.bp
└── hello_drv_test.c
Where hello_drv_test.c has the following contents:
#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;
}
fd = open("/dev/hello", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}
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;
}
The test program is as simple as reading and writing the /dev/hello
file based on command line arguments.
Next, write the Android.bp file:
cc_binary {
name: "hello_drv_test",
srcs: ["hello_drv_test.c"],
cflags: ["-Werror"],
}
Compile the program and upload the emulator:
cd frameworks/base/native/hello_drv_test
mm
cd -
adb push ./out/target/product/rice14/system/bin/hello_drv_test /data/local/tmp
adb shell
cd /data/local/tmp
./hello_drv_test -w "hello"
./hello_drv_test -r