今天給大家講一下android框架中的硬件抽象層HAL(hardware abstract layer),硬件抽象層在軟件與硬件之間起到了橋樑作用,作爲一個framework工程師是必須掌握的,如果你是一個應用軟件工程師或者framework工程師,向驅動工程師轉型,hal層也是很好的入門。並且個人認爲,掌握hal層相關原理能夠大大提高你整個底層到上層垂直開發能力,下面開始講解。
一、定義及作用
HAL全稱hardware abstract layer,即硬件抽象層,它是對底層硬件驅動進行了一層封裝,向framework層提供調用驅動的通用接口,廠家只要按照HAL規範,實現相應接口,並且以共享庫的形式存放在特定目錄下,那麼我上層只要加載這個共享庫並找到相應的模塊對應的設備的指針,一旦拿到真個設備的指針就可以操作底層硬件。
二、背景
那麼爲什麼google會增加這一層(HAL)呢,我們知道手機行業競爭是很激烈的,特別是手機廠商爲了追求手機的個性化,而往往這些個性化的東西類似控制算法或者圖像算法往往是跟底層硬件打交道的,如果放在內核空間的Linux層,那麼這些算法就要遵循GUN Lisence協議進行開源,而如果將這些算法放到android用戶空間,而android代碼是遵循Apache Lisence協議可以公開也可以不公開代碼,如果一旦代碼公開,這些廠商勢必損失利益,因此可以說,google爲了維護各個廠家的利益,設計了這麼一個硬件抽象層。也不難理解android源碼是開放而不是開源的。
三、HAL調用流程
幾個重要的結構體:
hw_module_t 這個結構體是通用模塊結構體,是具體模塊的一個基類,如果你要實現音頻模塊就需要繼承這個通用模塊
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;
uint16_t module_api_version;
#define version_major module_api_version
uint16_t hal_api_version;
#define version_minor hal_api_version
/** Identifier of module */
const char *id;
/** Name of this module */
const char *name;
/** Author/owner/implementor of the module */
const char *author;
/** Modules methods */
struct hw_module_methods_t* methods;
/** module's dso */
void* dso;
/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];
} hw_module_t;
hw_module_methods_t 這個結構體是定義一個打開具體設備的函數open
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
hw_device_t 這個結構體是通用設備結構體,上層找到對應模塊之後需要定位具體設備,一個模塊可以有多個設備(根據device id來區別),上面的open函數就是去初始化這個設備的各個參數的
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** padding reserved for future use */
uint32_t reserved[12];
/** Close this device */
int (*close)(struct hw_device_t* device);
} hw_device_t;
HAL_MODULE_INFO_SYM是定義具體模塊的結構體變量,結構體內部是一個hw_module_t結構體,這種包含關係可以理解爲,具體模塊繼承通用模塊hw_module_t結構體
/**
* Name of the hal_module_info
*/
#define HAL_MODULE_INFO_SYM HMI
下面是audio模塊的一個定義,內部hw_module_t定義了一個common變量
struct audio_module HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = AUDIO_MODULE_API_VERSION_0_1,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = AUDIO_HARDWARE_MODULE_ID,
.name = "Default audio HW HAL",
.author = "The Android Open Source Project",
.methods = &hal_module_methods,
},
};
下面這個函數供上層獲取模塊用的函數,需要傳入module id和指向hw_module_t的指針的指針
int hw_get_module(const char *id, const struct hw_module_t **module);
這個函數纔是真正的通過module id去獲取具體的module,上面那個hw_get_module函數也是調用的這個函數,之後都是通過hardware\libhardware\hardware.c文件中的load方法去打開(dlopen, dlsym)共享庫,通過module id找到對應的模塊,最後返回的就是上面的HMI值也就是HAL_MODULE_INFO_SYM
/**
* Get the module info associated with a module instance by class 'class_id'
* and instance 'inst'.
*
* Some modules types necessitate multiple instances. For example audio supports
* multiple concurrent interfaces and thus 'audio' is the module class
* and 'primary' or 'a2dp' are module interfaces. This implies that the files
* providing these modules would be named audio.primary.<variant>.so and
* audio.a2dp.<variant>.so
*
* @return: 0 == success, <0 == error and *module == NULL
*/
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module);
具體模塊也就是load的加載過程如下:
我們知道每個硬件抽象模塊都對應一個動態鏈接庫,這個是廠商提供的,存放在默認的路徑下;HAL在需要的時候會去匹配和加載動態鏈接庫。那麼HAL是如何找到某個硬件模塊對應的正確的共享庫呢?
首先,每個模塊對應的動態鏈接庫的名字是遵循HAL的命名規範的。舉例說明,以GPS模塊爲例,典型的共享庫名字如下:
gps.mt6753.so
<MODULE_ID>.variant.soro.hardware
ro.product.board
ro.board.platform
ro.arch
硬件抽象模塊的動態鏈接庫文件名命名規範定義在:\hardware\libhardware\hardware.c:
/**
* There are a set of variant filename for modules. The form of the filename
* is "<MODULE_ID>.variant.so" so for the led module the Dream variants
* of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
*
* led.trout.so
* led.msm7k.so
* led.ARMV6.so
* led.default.so
*/
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"
};
//後面會用到
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
HAL會按照variant_keys[]定義的屬性名稱的順序逐一來讀取屬性值,若值存在,則作爲variant的值加載對應的動態鏈接庫。如果沒有讀取到任何屬性值,則使用<MODULE_ID>.default.so
作爲默認的動態鏈接庫文件名來加載硬件模塊。
有了模塊的文件名字規範,那麼共享庫的存放路徑也是有規範的。HAL規定了2個硬件模塊動態共享庫的存放路徑
/** Base path of the hal modules */
#if defined(__LP64__)
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#else
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#endif
也就是說硬件模塊的共享庫必須放在/system/lib/hw 或者 /vendor/lib/hw 這2個路徑下的其中一個。HAL在加載所需的共享庫的時候,會先檢查HAL_LIBRARY_PATH2路徑下面是否存在目標庫;如果沒有,繼續檢查HAL_LIBRARY_PATH1路徑下面是否存在。具體實現在函數hw_module_exists
/*
* Check if a HAL with given name and subname exists, if so return 0, otherwise
* otherwise return negative. On success path will contain the path to the HAL.
*/
static int hw_module_exists(char *path, size_t path_len, const char *name,
const char *subname)
{
//檢查/vendor/lib/hw路徑下是否存在目標模塊
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (access(path, R_OK) == 0)
return 0;
//檢查/system/lib/hw路徑下是否存在目標模塊
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
if (access(path, R_OK) == 0)
return 0;
return -ENOENT;
}
name:其實對應上面提到的MODULE_ID
subname: 對應從上面提到的屬性值variant
現在我們知道了HAL是如何命名和存放模塊共享庫的,以及HAL基於這種機制來檢查目標模塊庫是否存在的方法。下面來看上傳framework打開和加載模塊共享庫的具體實現過程。
上傳framework和應用打開HAL庫的入口函數爲hw_get_module,定義如下hardware/libhardware/include/hardware/hardware.h:
/**
* Get the module info associated with a module by id.
*
* @return: 0 == success, <0 == error and *module == NULL
*/
int hw_get_module(const char *id, const struct hw_module_t **module);
傳入目標模塊的唯一id,得到表示該模塊的hw_module_t結構體指針
具體實現在文件hardware/libhardware/hardware.c,下面我們具體來分析。
int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}
hw_get_module實際上調用了hw_get_module_by_class來執行實際的工作。
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int i = 0;
char prop[PATH_MAX] = {0};
char path[PATH_MAX] = {0};
char name[PATH_MAX] = {0};
char prop_name[PATH_MAX] = {0};
//根據id生成module name,這裏inst爲NULL
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* First try a property specific to the class and possibly instance */
//首先查詢特定的屬性名稱來獲取variant值
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; //存在,找到了
}
}
/* Loop through the configuration variants looking for a module */
//逐一查詢variant_keys數組定義的屬性名稱
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
//檢查目標模塊共享庫是否存在
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
//沒有找到,嘗試默認variant名稱爲default的共享庫
/* Nothing found, try the default */
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
found:
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
return load(class_id, path, module); //執行加載和解析共享庫的工作
}
- 首先根據class_id生成module name,這裏就是hw_get_module函數傳進來的id;
- 根據屬性名稱“ro.hardware.<id>”獲取屬性值,如果存在,則作爲variant值調用前面提到的hw_module_exits檢查目標是否存在。如果存在,執行load。
- 如果不存在,則遍歷variant_keys數組中定義的屬性名稱來獲取屬性值,得到目標模塊庫名字,檢查其是否存在;
- 如果根據屬性值都沒有找到模塊共享庫,則嘗試檢查default的庫是否存在;如果仍然不存在,返回錯誤。
- 如果上述任何一次嘗試找到了目標共享庫,path就是目標共享庫的文件路徑,調用load執行真正的加載庫的工作。
下面來看load函數:
/**
* Load the file defined by the variant and if successful
* return the dlopen handle and the hmi.
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status = -EINVAL;
void *handle = NULL;
struct hw_module_t *hmi = NULL;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
//使用dlopen打開path定義的目標共享庫,得到庫文件的句柄handle
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
//出錯,通過dlerror獲取錯誤信息
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR; //"HMI"
//使用dlsym找到符號爲“HMI”的地址,這裏應該是hw_module_t結構體的地址;並且賦給hmi
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
//檢查模塊id是否匹配
if (strcmp(id, hmi->id) != 0) {
ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
}
//保存共享庫文件的句柄
hmi->dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, *pHmi, handle);
}
//返回得到的hw_module_t結構體的指針
*pHmi = hmi;
return status;
}
load函數的主要工作時通過dlopen來打開目標模塊共享庫,打開成功後,使用dlsym來得到符號名字爲"HMI"的地址。這裏的HMI應該是模塊定義的hw_module_t結構體的名字,如此,就得到了模塊對應的hw_module_t的指針。
至此,我麼終於得到了表示硬件模塊的hw_module_t的指針,有了這個指針,就可以對硬件模塊進行操作了。HAL是如何查找和加載模塊共享庫的過程就分析完了,最終還是通過dlopen和dlsym拿到了模塊的hw_module_t的指針,就可以爲所欲爲了。
四、GPS HAL加載過程
前面分析完了HAL的框架和機制,以GPS HAL的加載過程爲例把上面的知識串起來。我們從framework層的hw_get_module函數作爲入口點,初步拆解分析。
加載GPS HAL的入口函數定義在frameworks/base/services/core/jni/com_android_server_location_GpsLocationProvider.cpp:
static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
// skip unrelate code
hw_module_t* module;
//獲取GPS模塊的hw_module_t指針
err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
hw_device_t* device;
//調用open函數得到GPS設備的hw_device_t指針
err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
if (err == 0) {
//指針強轉爲gps_device_t類型
gps_device_t* gps_device = (gps_device_t *)device;
//得到GPS模塊的inteface接口,通過sGpsInterface就可以操作GPS設備了
sGpsInterface = gps_device->get_gps_interface(gps_device);
}
}
GPS_HARDWARE_MODULE_ID定義在hardware/libhardware/include/hardware/gps.h中:
/**
* The id of this module
*/
#define GPS_HARDWARE_MODULE_ID "gps"
調用hw_get_module得到GPS模塊的hw_module_t指針,保存在module變量中;我們來看下GPS模塊的hw_module_t長得什麼樣。以hardware/qcom/gps/loc_api/libloc_api_50001/gps.c爲例:
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = 1,
.hal_api_version = 0,
.id = GPS_HARDWARE_MODULE_ID,
.name = "loc_api GPS Module",
.author = "Qualcomm USA, Inc.",
.methods = &gps_module_methods, //自定義的函數指針,這裏既是獲取hw_device_t的入口了
};
接着調用GPS模塊自定義的hw_module_t的methods中的open函數,獲取hw_device_t指針。上面的代碼中我們看到,GPS模塊的hw_module_t的methods成員的值爲gps_module_methods
,其定義如下:
static struct hw_module_methods_t gps_module_methods = {
.open = open_gps
};
OK,我們來看open_gps
函數做了什麼:
static int open_gps(const struct hw_module_t* module, char const* name,
struct hw_device_t** device)
{
//爲gps_device_t分配內存空間
struct gps_device_t *dev = (struct gps_device_t *) malloc(sizeof(struct gps_device_t));
if(dev == NULL)
return -1;
memset(dev, 0, sizeof(*dev));
//爲gps_device_t的common成員變量賦值
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
//通過下面的函數就能得到GPS模塊所有interface
dev->get_gps_interface = gps__get_gps_interface;
//將gps_device_t指針強轉爲hw_device_t指針,賦給device
*device = (struct hw_device_t*)dev;
return 0;
}
我們看到open_gps創建了gps_device_t結構體,初始化完成後,將其轉爲hw_device_t。所以module->methods->open
得到實際上是gps_device_t結構體指針。這裏我們可以理解爲gps_device_t是hw_device_t的子類,將子類對象轉爲父類對象返回,是很正常的使用方法。爲什麼可以這麼理解,看一下gps_device_t長得什麼樣子就明白了。
hardware/libhardware/include/hardware/gps.h:
struct gps_device_t {
struct hw_device_t common;
/**
* Set the provided lights to the provided values.
*
* Returns: 0 on succes, error code on failure.
*/
const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);
};
啊哈,第一個成員是名爲common的hw_device_t類型的變量;所以可以理解爲gps_device_t繼承了hw_device_t。
得到GPS的hw_device_t指針後將其強轉回gps_devcie_t指針,然後調用GPS device定義的get_gps_interface
接口,得到保存GPS 接口的GpsInterface結構體指針。
在open_gps中,get_gps_interface被賦值爲gps__get_gps_interface函數指針,其主要工作就是返回GPS模塊的GpsInterface結構體指針。
GpsInterface定義在hardware/libhardware/include/hardware/gps.h:
/** Represents the standard GPS interface. */
typedef struct {
/** set to sizeof(GpsInterface) */
size_t size;
/**
* Opens the interface and provides the callback routines
* to the implementation of this interface.
*/
int (*init)( GpsCallbacks* callbacks );
/** Starts navigating. */
int (*start)( void );
/** Stops navigating. */
int (*stop)( void );
/** Closes the interface. */
void (*cleanup)( void );
/** Injects the current time. */
int (*inject_time)(GpsUtcTime time, int64_t timeReference,
int uncertainty);
/** Injects current location from another location provider
* (typically cell ID).
* latitude and longitude are measured in degrees
* expected accuracy is measured in meters
*/
int (*inject_location)(double latitude, double longitude, float accuracy);
/**
* Specifies that the next call to start will not use the
* information defined in the flags. GPS_DELETE_ALL is passed for
* a cold start.
*/
void (*delete_aiding_data)(GpsAidingData flags);
/**
* min_interval represents the time between fixes in milliseconds.
* preferred_accuracy represents the requested fix accuracy in meters.
* preferred_time represents the requested time to first fix in milliseconds.
*
* 'mode' parameter should be one of GPS_POSITION_MODE_MS_BASE
* or GPS_POSITION_MODE_STANDALONE.
* It is allowed by the platform (and it is recommended) to fallback to
* GPS_POSITION_MODE_MS_BASE if GPS_POSITION_MODE_MS_ASSISTED is passed in, and
* GPS_POSITION_MODE_MS_BASED is supported.
*/
int (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence,
uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time);
/** Get a pointer to extension information. */
const void* (*get_extension)(const char* name);
} GpsInterface;
GpsInterface定義了操作GPS模塊的基本的標準接口,得到了GpsInterface就可以通過這些接口操作GPS了,終於可以硬件打交道了。某一個具體的GPS模塊會將GpsInterface中的接口初始化爲其平臺相關的具體實現。比如:hardware/qcom/gps/loc_api/libloc_api_50001/loc.cpp
// Defines the GpsInterface in gps.h
static const GpsInterface sLocEngInterface =
{
sizeof(GpsInterface),
loc_init,
loc_start,
loc_stop,
loc_cleanup,
loc_inject_time,
loc_inject_location,
loc_delete_aiding_data,
loc_set_position_mode,
loc_get_extension
};
到這裏,整個GPS HAL的加載過程就結束了,後面就可以通過GpsInterface操作GPS模塊了。
本文分析了Android HAL定義背景以及機制流程,介紹了它的核心數據結構,分析了硬件模塊的查詢和加載過程;然後以GPS爲例說明了如何通過HAL得到硬件的接口函數。