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

By hbb

Leave a Reply

Your email address will not be published. Required fields are marked *