Android硬件访问服务-HAL

版权声明:欢迎转载,转载请注明出处 http://blog.csdn.net/itdo_just https://blog.csdn.net/itdo_just/article/details/76187192

一般情况下,APP不直接访问硬件,而是由SystemServer通过HAL和JNI来访问硬件,实质即java访问C,建议把对硬件的操作写在一个HAL文件里面,对上一章写的代码com_android_server_LedService.cpp这个JNI文件做进一步的修改,除了向上注册本地函数之外,还要去加载HAL文件,然后调用HAL里面的函数。

使用HAL有两个好处:

1.	容易修改,只需直接修改HAL文件然后编译成so文件放入系统即可
2.	保密作用,linux基于GPL协议,一旦使用linux内核就必须公开linux内核的源代码,如果我们在linux内核里面实现这些硬件操作的话就需要把这些操作全部公开出去,很多人厂家不愿这么做,所以他们只在内核写一个最简单的驱动程序来操作寄存器,而硬件内部的复杂操作放在HAL文件里面,厂家只提供HAL文件的so文件给客户,这样就避开了GPL协议,android基于Apache协议,你可以去修改它的代码,而不需要去公开你的修改。

对于HAL层,摘取老罗博客中的一段来进行简述:
Android的硬件抽象层,简单来说,就是对Linux内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节。也就是说,把对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在用户空间,而linux内核驱动程序运行在内核空间。为什么要这样安排呢?把硬件抽象层和内核驱动整合在一起放在内核空间不可行吗?从技术实现的角度来看,是可以的,然而从商业的角度来看,把对硬件的支持逻辑都放在内核空间,可能会损害厂家的利益。我们知道,Linux内核源代码版权遵循GNU License,而android源代码版权遵循Apache License,前者在发布产品时,必须公布源代码,而后者无须发布源代码。如果把对硬件支持的所有代码都放在Linux驱动层,那就意味着发布时要公开驱动程序的源代码,而公开源代码就意味着把硬件的相关参数和实现都公开了,在手机市场竞争激烈的今天,这对厂家来说,损害是非常大的。因此,Android才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问硬件逻辑,例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中去了,这样就可以把商业秘密隐藏起来了。也正是由于这个分层的原因,Android被踢出了Linux内核主线代码树中。大家想想,Android放在内核空间的驱动程序对硬件的支持是不完整的,把Linux内核移植到别的机器上去时,由于缺乏硬件抽象层的支持,硬件就完全不能用了,这也是为什么说Android是开放系统而不是开源系统的原因。
博客原文:http://blog.csdn.net/luoshengyang/article/details/6567257


所以android里面对硬件的操作我们一般分为两个文件,一个是JNI文件(向上提供(注册)本地函数,向下加载HAL文件并调用HAL文件的函数),一个是HAL文件(负责访问驱动程序执行具体的硬件操作),这两个文件都是C/C++语言写的,所以JNI来加载HAL的实质就是怎么使用dl_open来加载动态库,android里面对dlopen又做了一层封装,我们使用的是“hw_get_module”这个函数(位于Hardware.c),函数实现过程如下

hw_get_module(“led”);//假设模块名为led,最终由这个模块名转为文件名
代码路径:external\chromium_org\third_party\hwcplus\src\Hardware.c

调用过程:

  1. 模块名(“led”)===>文件名(filename)
    hw_get_module
    ----hw_get_module_by_class(“led”, NULL)
    ------name = “led”
    ---------property_get(根据名字获得某个属性值)
    ---------hw_module_exists(去目录下判断某个模块是否存在)
  2. 加载
    dlopen(filename)------>(android源目录使用man dlopen查看使用方法)

上面最后两个函数的具体解析如下:

hw_module_exists(char *path, size_t path_len, const char *name, const char *subname)
它是用来判断”name”.”subname”.so文件是否存在,总共在三个目录下查找

a. HAL_LIBRARY_PATH
b. /vendor/lib/hw
c. /system/lib/hw

对应源码及注解如下:
getenv(“HAL_LIBRARY_PATH”); //获得环境变量,在这个目录(“HAL_LIBRARY_PATH”)所指示的那个目录下查到是否有那个so文件

snprintf(path, path_len, “%s/%s.%s.so”,HAL_LIBRARY_PATH2, name, subname);//在目录(HAL_LIBRARY_PATH2)下查找是否有那个文件存在

snprintf(path,path_len,"%s/%s.%s.so", HAL_LIBRARY_PATH1, name, subname); //在目录(HAL_LIBRARY_PATH1)下查找是否有那个文件存在

上面的宏对应如下:
#ifndef HAL_LIBRARY_PATH1
#define HAL_LIBRARY_PATH1 “/system/lib” _64 “/hw”
#endif
#ifndef HAL_LIBRARY_PATH2
#define HAL_LIBRARY_PATH2 “/vendor/lib” _64 “/hw”
#endif

这里写图片描述

直接到android下进行搜索,我们只能搜索到存在一个 system/lib/hw 这个文件,所以最终会去这个文件里面搜索so文件

另一个函数的解析如下:

property_get
这个函数涉及到android的一个重要系统:属性系统(可以从name里面获得一个value),对应代码如下:
属性< 键,值 >< name, value > 可以从名字获得一个值

/* First try a property specific to the class and possibly instance */
    snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
    if (property_get(prop_name, prop, NULL) > 0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
            goto found;
        }
    }

传入 prop_name 获得某个 prop(这个属性就是它的值), 这个prop会传入hw_module_exists里面的subname作为参数
比如传入的prop_name是led,则他会去查找以下文件:
Led.XXX.so(XXX来源于prop这个属性,查找"ro.hardware.led"属性是否存在),查找结果如下:

这里写图片描述

没有找到

接着去数组中定义的属性中查找

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”
};

依次查找到属性值的结果如下:
这里写图片描述

找到这个属性则会去判断文件是否存在, 在这三个目录(HAL_LIBRARY_PATH、/vendor/lib/hw、/system/lib/hw)下面依次查找这两个led文件
led.nanopi3.so
led.slsiap.so

如果这三个目录都不存在这两个so文件,依据源码
hw_module_exists(path, sizeof(path), name, “default”)
会去搜索
led.defualt.so
如果找到了某个so文件,则会把这个文件的绝对路径就会保存在path数组里面

整个过程可以总结如下:

总结:
hw_module_exists
property_get
这两个函数用来判断是否存在led.xxx.so是否存在(xxx是属性),一旦查找到就用dlopen(filename)加载进来
load(class_id, path, module)
-----dlopen(filename)—>加so文件
-----dlsym(“HMI”)—>从so文件中获得名为HMI的hw_module_t结构体
-----strcmp(id, hmi->id)—>判断这个hw_module_t结构体跟我们找的文件的名字是否一样,如比较(“led”, hmi->id)是否一致


下面是HAL代码实现:

JNI 怎么使用 HAL
参考com_android_lights_LightsService.cpp

  1. hw_get_module -> get_device获得一个hw_module_t结构体
  2. 调用 module->methods->open(module, device_name, &device)
    根据device_name的名字获得一个hw_device_t(设备)结构体,并且把hw_device_t结构体转换为设备自定义的结构体
    (light_device_t*)device;
    为什么能直接这样转换的原因是light_device_t这个结构体第一个成员就是hw_device_t类型的参数。

HAL 怎么写

  1. 实现一个名为HMI的hw_module_t结构体
  2. 实现一个open函数, 它会根据name返回一个设备自定义的结构体
    这个设备自定义的结构体的第1个成员是 hw_device_t结构体
    还可以定义设备相关的成员

编写代码实现过程:

  1. 首先是JNI文件的实现:
#define LOG_TAG "LedService"

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

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

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

namespace android
{
	static led_device_t* led_device;

	/* 获取模块并打开设备 */
	jint ledOpen(JNIEnv *env, jobject cls)
	{
		jint err;
		hw_module_t* module;
		hw_device_t* device;

		ALOGI("native ledOpen ...");

		/* 1. hw_get_module */
		err = hw_get_module("led", (hw_module_t const**)&module);//name跟android.mk一致
		if (err == 0) {
			/* 2. get device : module->methods->open */
			err = module->methods->open(module, NULL, &device);
			if (err == 0) {
				/* 3. call led_open */
				led_device = (led_device_t *)device;
				return led_device->led_open(led_device);
			} else {
				return -1;
			}
		}
		
		return -1;	
	}

	void ledClose(JNIEnv *env, jobject cls)
	{
		//ALOGI("native ledClose ...");
		//close(fd);
	}

	/* 根据从模块中获得的设备调用它的相关控制函数 */
	jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
	{
		ALOGI("native ledCtrl %d, %d", which, status);
		return led_device->led_ctrl(led_device, which, status);
	}

	/* 定义JNI字段描述符,实现映射数组的内容 */
	static const JNINativeMethod methods[] = {
		{"native_ledOpen", "()I", (void *)ledOpen},
		{"native_ledClose", "()V", (void *)ledClose},
		{"native_ledCtrl", "(II)I", (void *)ledCtrl},
	};
		
	/* 注册Service */
	int register_android_server_LedService(JNIEnv *env)
	{
		return jniRegisterNativeMethods(env, "com/android/server/LedService",
				methods, NELEM(methods));
	}
}

2.编写HAL文件,需要实现 led_hal.c 和 led_hal.h 两个文件

led_hal.c代码如下:

#define LOG_TAG "LedHal"


/* 参考: \hardware\libhardware\modules\vibrator\Vibrator.c */

#include <hardware/vibrator.h>
#include <hardware/hardware.h>

#include <cutils/log.h>

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

#include <hardware/led_hal.h>

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

static int fd;

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_nano", O_RDWR);//可读可写
   ALOGI("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, status, which);

  ALOGI("ledCtrl %d, %d, %d", which, status, ret);
  
  return ret;
}


/* 3.实现设备自定义(led_device_t)的结构体 */

static 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 __unused,
                      hw_device_t** device __unused) 
{
  *device = &led_dev; 
  return 0;
}

/* 2.实现一个open函数, 它会根据name返回一个设备自定义(led_device_t)的结构体*/

static struct hw_module_methods_t led_module_methods = {
    .open = led_device_open,
};

/* 1.实现一个名为HMI的hw_module_t结构体 */

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_LIGHTS_INTERFACE_H

重新上传以上文件进行编译即可:

JNI: frameworks/base/services/core/jni/com_android_server_LedService.cpp
hal.c:hardware/libhardware/modules/led/led_hal.c
hal.h:hardware/libhardware/include/hardware/led_hal.h

修改 hardware/libhardware/modules/led/Android.mk
Android.mk内容如下:`

# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

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 := optional

include $(BUILD_SHARED_LIBRARY)


编译:
mmm frameworks/base/services
mmm hardware/libhardware/modules/led
make snod

验证(在android系统命令行中输入搜索):
Logcat | grep “LedHal”

Android打印信息简介:

	1.有三类打印信息: app, system, radio
	程序里使用 ALOGx, SLOGx, RLOGx来打印
	>
	2. x表示6种打印级别,有:
	  V    Verbose
	  D    Debug
	  I    Info
	  W    Warn
	  E    Error
	  F    Fatal
	比如: 
	#define LOG_TAG "LedHal"
	ALOGI("led_open : %d", fd);
	>
	3. 打印出来的格式为:
      I/LedHal  ( 1987): led_open : 65
      (级别) LOG_TAG  进程号   打印信息  
    >
    4. 使用 logcat 命令查看  
	    logcat LedHal:I *:S

猜你喜欢

转载自blog.csdn.net/itdo_just/article/details/76187192