第二章:硬件访问服务(4)-HAL编写

通过前面的学习,我们知道怎么通过andriod硬件访问服务控制硬件,再次贴出框图如下
在这里插入图片描述
其中红色圈出部分我们已经完成,我们知道在andriod系统中,java程序是无法直接访问硬件的,他访问硬件需要service_manager.c发出请求,才能操控硬件,之前我们通过com_android_server_LedService.cpp直接访问硬件,这种方法一般不被推荐,主要有以下几点

1.单我们需要修改对硬件的操作C函数时,必须修改com_android_server_LedService.cpp,每次都要编译整个framework重新生成system.img。
2.linux是遵循GPL协议的,则会公开你的com_android_server_xxxService.cpp文件,倒是很多厂家并不想公开自己对硬件操作的代码。

那么为了解决以上问题,我们可以让com_android_server_xxxService.cpp文件。仅仅注册本地C函数,对硬件的操作我们把他封装成.so文件导入工程。这样我们以上的两个问题就得以解决了,需要修改对硬件的操作函数时,我们只需要修改.生成.so的文件即可。

源码解读

既然我们的要把对硬件操作部分分离出来,那么
1.com_android_server_xxxService.cpp:成了一个纯粹的JIN文件,主要有两个作用,向上提供本地C函数,向下加载hal文件(Hardware Abstraction Layer:硬件抽象层)并且调用HAL函数
2.HAL(Hardware Abstraction Layer:硬件抽象层):负责访问驱动程序,执行硬件操作。
在android中,com_android_server_xxxService.cpp加载hal文件,并不是直接声明函数,指定库就可以了,他内部有一套封装的方法。即调用dlopen,那么他是怎么调用dlopen的呢?
调用过程如下

hw_get_module("led")
	//1.模块名==>文件名
	hw_get_module_by_class("led",NULL)
		name = "led"
			property_get   //获取属性值
			hw_module_exists  //判断是否存在led.xxx.so
	//2.加载
			load
				dlopen(filename)
				dlsym("HMI"//从so文件中获取HMI的hw_module_t结构体				
				strcmp(id,hmi->id)//判断名字是否一致(hmi->id,"led")

上述是一个大概的过程,现在进行详细讲解,我们先在源码中查看hw_get_module函数,如下

int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module);
}

传入了两个参数,module为我们自己定义的hw_module_t **类型对象,调用hw_get_module_by_class,其中参数id为“led”,他可以看到其中多次调用hw_module_exists函数,他主要用来判断开发板以下3个路径下面是否存在led.xxx.so文件

1.HAL_LIBRARY_PATH 环境变量
2./vendor/lib/hw
3./system/lib/hw

那么led.xxx.so中的xxx具体是什么内容?我们查看hw_get_module_by_class中的property_get函数如下:

 if (property_get(variant_keys[i], prop, NULL) == 0)

其中传入的参数variant_keys如下所示

static const char *variant_keys[] = {
    "ro.hardware",  /* This goes first so that it can pick up a different
                       file on the emulator. */
    "ro.product.board",
    "ro.board.platform",
    "ro.arch"
};

我们在开板上分别输入

rk3399_all:/ $ getprop ro.hardware                                             
rk30board
rk3399_all:/ $ getprop ro.product.board                                        
rk30sdk
rk3399_all:/ $ getprop ro.board.platform" 
rk3399                                                                         
rk3399_all:/ $ getprop ro.arch     
                                            
rk3399_all:/ $

故此,他查找的分别为led.rk30board.so
led.rk30sdk .so,led.rk30sdk .so,led…so文件,如果都没有查找到,则查找led.default .so文件。那么他为什么是在
1.HAL_LIBRARY_PATH 环境变量2./vendor/lib/hw
3./system/lib/hw
下查找呢?因为在hw_module_exists函数中,有如下代码

    char *hal_library_path = getenv("HAL_LIBRARY_PATH");
    if (hal_library_path) {
---------------------------------------------------
    snprintf(path, path_len, "%s/%s.%s.so",
             HAL_LIBRARY_PATH2, name, subname);
    if (access(path, R_OK) == 0)
    ---------------------------------------------------
   snprintf(path, path_len, "%s/%s.%s.so",
             HAL_LIBRARY_PATH1, name, subname);

然后调用

	load
		dlopen(filename)
			dlsym("HMI"//从so文件中获取HMI的hw_module_t结构体				
			strcmp(id,hmi->id)//判断名字是否一致(hmi->id,"led")

其中hw_module_t结构体会赋值给hw_get_module()传入的第二个参数。

总结:我们调用hw_get_module()函数,传入“led”,会获得一个和led相关的hw_module_t结构体,这个结构体在后续中,会多次使用。

JNF使用HAL

前面做了那么多的讲解,都是为我们的JNF使用HAL做铺垫,那么我们的JNF应该怎么使用HAL呢?
我们参考源代码com_android_server_lights_LightsService文件,在 init_native函数中,先调用hw_get_module()获得一个hw_module_t module结构体,然后把module当作实参传入到get_device,我们可以发现get_device()被多次调用,说明一个moudle可能支持多个device。get_device()实现如下(参考)

static light_device_t* get_device(hw_module_t* module, char const* name)
{
    int err;
    hw_device_t* device;
    err = module->methods->open(module, name, &device);
    if (err == 0) {
        return (light_device_t*)device;
    } else {
        return NULL;
    }
}

可以知get_device()函数中,调用传入参数module中的方法,获得一个hw_device_t结构体(前面提到过hw_module_t中应该包括多个hw_device_t),并且强制装换为light_device_t(设备自定义的结构体)类型返回,那么他为什么可以直接这样强制装换呢?我们再源码中查看light_device_t是怎么定义的,在/sdk/hardware/libhardware/include/hardware/ lights.h中可以看到如下定义:

struct light_device_t {
    struct hw_device_t common;
    int (*set_light)(struct light_device_t* dev,
            struct light_state_t const* state);
};

可以看到,最前面包含了struct hw_device_t ,所有能强制转化为light_device_t。

HAL实现

根据前面的分析,com_android_server_xxxService文件中,需要一个hw_module_t module结构体,并且调用module->methods->open(module, name, &device)。那我们编写HAL需要实现两点

1.实现一个名为HMI(HAL_MODULE_INFO_SYM:一个宏)的hw_module_t结构体
2.实现一个open函数,他会根据反回name一个设备自定义的结构体,这个设备自定义的结构体,第一个成员是hw_device_t结构体
还可以定义一些设备相关的成员,比如一些操控硬件函数。

下面我们开始编写led_hal.c文件,代码如下(参考hardware/libhardware/modules/vivrator/vivrator.c):

#define LOG_TAG "LedHal"

#include <hardware/hardware.h>

#include <cutils/log.h>

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <math.h>

#include <utils/Log.h>

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include <hardware/led_hal.h>



static int fd;

static int led_close(struct hw_device_t* device)
{
	close(fd);
	return 0;
}

static int led_open(struct led_device_t* dev)
{
	fd = open("/dev/leds_drv", O_RDWR);
	ALOGE("ledOpen : %d", fd);
	if (fd >= 0)
		return 0;
	else
		return -1;
}static int led_ctrl(struct led_device_t* dev, int which, int status)
{
	int ret = ioctl(fd, which, status);
	ALOGE("ledCtrl : %d, %d, %d", which, status, ret);
	return ret;
}

struct led_device_t led_dev = {
	.common = {
		.tag = HARDWARE_DEVICE_TAG,
		.close = led_close, 
	},
	.led_open = led_open,
	.led_ctrl = led_ctrl,
};


static int led_device_open(const hw_module_t* module, const char* id,
hw_device_t** device) 
{
	*device = &led_dev;
	return 0;
}


static struct hw_module_methods_t led_module_methods = {
	/*璇ュ嚱鏁帮紝com_android_server_LedService.cpp鏂囦欢涓皟鐢?
	hw_get_module鏃朵細琚皟鐢紝鍏朵富瑕佺洰鐨勬槸杩斿洖鎴戜滑鑷畾涔夌殑
	缁撴瀯浣擄紝渚汮NI鎿嶄綔*/
    .open = led_device_open, 
};

//浣跨敤HAL_MODULE_INFO_SYM瀹氫箟涓€涓粨鏋勪綋锛圚MI锛?
struct hw_module_t HAL_MODULE_INFO_SYM = {
	.tag = HARDWARE_MODULE_TAG,
    .id = "led",
    .methods = &led_module_methods,
};

led_hal.h

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H

#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>

#include <hardware/hardware.h>

__BEGIN_DECLS



struct led_device_t {
    struct hw_device_t common;
	int (*led_open)(struct led_device_t* dev);
	int (*led_ctrl)(struct led_device_t* dev, int which, int status);
};


__END_DECLS

#endif  // ANDROID_LED_INTERFACE_H

除此之外我们还需修改com_android_server_LedService.cpp如下:

#define LOG_TAG "LedService"

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"

#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/led_hal.h>

#include <stdio.h>

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

namespace android
{
	static led_device_t* led_device;

	jint ledOpen(JNIEnv *env, jobject cls)
	{
		int err;
		hw_module_t* module;
		hw_device_t* device;
		
		/*1. hw_get_module*/
		err = hw_get_module("led", (hw_module_t const**)&module);
		ALOGE("native ledOpen");
		if(err == 0){
			/*2. get device:  module->methods->open*/
			err = module->methods->open(module, NULL, &device);
			if (err == 0) {
				led_device = (led_device_t*)device;
				
				/*3. call led_open*/
				return led_device->led_open(led_device);
			} else {
				return -1;
			}
		}
		return -1;		
	}

	void ledClose(JNIEnv *env, jobject cls)
	{
		ALOGE("native ledClose");
	}

	jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
	{
		ALOGE("native ledCtrl : %d, %d", which, status);
		return led_device->led_ctrl(led_device, which, status);
	}

	static const JNINativeMethod method_table[] = {
		{"native_ledOpen", "()I", (void *)ledOpen},
		{"native_ledClose", "()V", (void *)ledClose},
		{"native_ledCtrl", "(II)I", (void *)ledCtrl},
	};

	
	int register_android_server_LedService(JNIEnv *env)
	{
		return jniRegisterNativeMethods(env, "com/android/server/LedService",
				method_table, NELEM(method_table));
	}

};

该文件主要通过hw_get_module获得与led对应的module,再通过module->methods->open调用获得我们自定义的结构体struct led_device_t.

系统编译

编写完以上代码之后,我们需要编译 进系统

1. JNI上传
SDK/frameworks/base/services/core/jni/com_android_server_LedService.cpp
2. HAL上传
hardware/libhardware/include/hardware/led_hal.h
hardware/libhardware/modules/led/led_hal.c
hardware/libhardware/modules/led/Android.mk

其中没有的目录和文件就自己创建,Android.mk的内容如下

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := led.default

# HAL module implementation stored in
# hw/<VIBRATOR_HARDWARE_MODULE_ID>.default.so
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := led_hal.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := userdebug

include $(BUILD_SHARED_LIBRARY)

该文档参考hardware/libhardware/modules/virator/Android.mk 编写,其中LOCAL_MODULE_TAGS := userdebug是因为我们在编译源码的时候选择的是lunch rk3399_all-userdebug。
然后执行编译指令
source build/envsetup.sh
lunch rk3399_all-userdebug
mmm frameworks/base/services/
mmm hardware/libhardware/modules/led/
make snod -j3
生成system.img烧写到开发板
运行APP我们局可以操控led了

打印简介

1.在android中有三类打印信息:app,system,radio,程序里使用ALOGx,SLOGx,RLOGx来打印
2.其中x代表打印级别,有如下可选:
 V			Verbose
 D			Debug
 W			Info
 E			Warn
 F			Fatal

比如:
#define LOG_TAG "LedHal"
ALOGI("led_open" :%d",fd);

3 打印出来的格式如下
    	I/Led         (1987):led_open :65
    (级别)LOG_TAG	   进程号    打印信息
4.那我们怎么查看打印信息呢?
	可以在串口终端上执行logcat命令
	筛选想要的信息:
	1.  logcat | grep “LedHal”  
	2.  logcat         LedHal:I       *:s(该处死两层过滤,可以过滤最多层)

编译优化

我们APP需要导入自己编译生成classes.jar包,该文件接近16M左右,编译生成的app/build/outpus/apk/debug/app-arm64-v8a-debug.apk也达到了8.8M,仅仅是点亮LED却占用了这么大的空间,当然并不是我们想要的结果,那么我们怎么对其进行优化呢?

打开AS工程,选择File->Projiect Structure
在这里插入图片描述
点击app->Dpendencies,选择classes.jar为compile模式
在这里插入图片描述
然后重新编译,我么可以看到此次APK大约1M左右。

小节回顾

本小节我们讲解了一下几点:

  1. 参考源码com_android_server_lights_LightsService,模仿使用hw_get_module与module->methods->open实现JNI
  2. 参考hardware/libhardware/modules/vivrator/vivrator.c文件编写led_hal.c
  3. 打印调试信息相关了解

下小节将修改APP,使用反射的方式操控硬件。

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/86764795