1. 程式人生 > >android硬體抽象層(HAL)詳解

android硬體抽象層(HAL)詳解

今天給大家講一下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.so

ro.hardware

ro.product.board

ro.board.platform

ro.arch

/**
 * 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開啟和載入模組共享庫的具體實現過程。

/**
 * 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結構體指標

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); //執行載入和解析共享庫的工作
}
  1. 首先根據class_id生成module name,這裡就是hw_get_module函式傳進來的id;
  2. 根據屬性名稱“ro.hardware.<id>”獲取屬性值,如果存在,則作為variant值呼叫前面提到的hw_module_exits檢查目標是否存在。如果存在,執行load。
  3. 如果不存在,則遍歷variant_keys陣列中定義的屬性名稱來獲取屬性值,得到目標模組庫名字,檢查其是否存在;
  4. 如果根據屬性值都沒有找到模組共享庫,則嘗試檢查default的庫是否存在;如果仍然不存在,返回錯誤。
  5. 如果上述任何一次嘗試找到了目標共享庫,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得到硬體的介面函式。