天天看點

Android HAL開發Android硬體抽象層(HAL)概要介紹和學習計劃

參考老羅的Android之旅

Android硬體抽象層(HAL)概要介紹和學習計劃

基于android5.1.1系統源碼,清華鏡像站https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/下載下傳系統源碼和kernel3.4源碼

1.在Android核心源代碼中,編寫Linux驅動程式(driver)

    (1).進入到kernel/goldfish/drivers目錄,建立hello目錄;

    (2). 在hello目錄中增加hello.h檔案;定義了一個字元裝置結構體hello_android_dev;

    (3).在hello目錄中增加hello.c檔案,這是驅動程式的實作部分;定義三種通路裝置寄存器的方法;

            定義傳統的裝置檔案通路方法

            定義通過devfs檔案系統通路方法

            定義通過proc檔案系統通路方法

            定義子產品加載和解除安裝方法

    (4)..在hello目錄中新增Kconfig和Makefile兩個檔案;

        Kconfig檔案的内容

               config HELLO

                   tristate "First Android Driver"

                   default n

                   help

                   This is the first android driver.

              Makefile檔案的内容

              obj-$(CONFIG_HELLO) += hello.o

    (5).修改arch/arm/Kconfig和drivers/kconfig兩個檔案;

        在menu "Device Drivers"和endmenu之間添加一行:

              source "drivers/hello/Kconfig"

        在arch/arm/Kconfig中沒有找到enu "Device Drivers"則添加

            menu "Device Drivers"

            source "drivers/hello/Kconfig"

            endmenu

    (6).修改drivers/Makefile檔案,添加一行: obj-$(CONFIG_HELLO) += hello/;

    (7).配置編譯選項:make menuconfig;  

        找到"Device Drivers" => "First Android Drivers"選項,設定為y。

    (8).編譯:make;

        編譯成功後,就可以在hello目錄下看到hello.o檔案了,這時候編譯出來的zImage已經包含了hello驅動。

    編譯遇到的問題:

        a.在make menuconfig之前,沒有設定環境變量

        export ARCH=arm  

        export SUBARCH=arm  

        export CROSS_COMPILE=arm-eabi-

        b.在make後報錯

        error: 'struct proc_dir_entry' has no member named 'owner'

        即proc_dir_entry結構體沒有成員變量owner,proc_dir_entry定義在include/linux/proc_fs.h檔案中,在此檔案中添加成員變量owner,

        struct module *owner;

        重新編譯即可。

        c.在make後報錯

         error: implicit declaration of function 'init_MUTEX' [-Werror=implicit-function-declaration] init_MUTEX(&(dev->sem));

        在新版本的Linux核心中,init_mutex已經被廢除了,新版本使用sema_init函數。

        将

        init_MUTEX(&(dev->sem));

        改為

        sema_init(&(dev->sem),1);

        重新編譯即可。

哈哈哈!!!編譯成功!!!

    (9).驗證,啟動模拟器

    emulator -kernel arch/arm/boot/zImage &

    adb shell

      進入到dev目錄,可以看到hello裝置檔案:

        [email protected]:/ # cd dev

        [email protected]:/dev # ls

        進入到proc目錄,可以看到hello檔案:

        [email protected]:/ # cd proc

        [email protected]:/proc # ls

        通路hello檔案的值:

        [email protected]:/proc # cat hello

        [email protected]:/proc # echo '55' > hello

        [email protected]:/proc # cat hello

        55

        進入到sys/class目錄,可以看到hello目錄:

        [email protected]:/ # cd sys/class

        [email protected]:/sys/class # ls

        進入到hello目錄,可以看到hello目錄:

        [email protected]:/sys/class # cd hello

        [email protected]:/sys/class/hello # ls

        進入到下一層hello目錄,可以看到val檔案:

        [email protected]:/sys/class/hello # cd hello

        [email protected]:/sys/class/hello/hello # ls

        通路屬性檔案val的值:

        [email protected]:/sys/class/hello/hello # cat val

        55

        [email protected]:/sys/class/hello/hello # echo '101'  > val

        [email protected]:/sys/class/hello/hello # cat val

        101

ok,驗證通過!!!

2.在Android系統中增加C可執行程式來通路硬體驅動程式(kernel ------> driver)

    (1).驗證Linux核心驅動程式按1.(9)進行;

    (2).進入到Android源代碼工程的external目錄,建立hello目錄,在hello目錄中建立hello.c檔案;

    (3). 在hello目錄中建立Android.mk檔案;

        内容為

            LOCAL_PATH := $(call my-dir)

                  include $(CLEAR_VARS)

                  LOCAL_MODULE_TAGS := optional

                  LOCAL_MODULE := hello

                  LOCAL_SRC_FILES := $(call all-subdir-c-files)

                  include $(BUILD_EXECUTABLE)

        注意,BUILD_EXECUTABLE表示我們要編譯的是可執行程式。

    (4).使用mmm指令進行hello子產品編譯;

        mmm /external/hello

    (5).重新打包Android系統檔案system.img;

        make snod

    (6).運作模拟器驗證,使用/system/bin/hello可執行程式來通路Linux核心驅動程式;

        emulator -kernel ./kernel/common/arch/arm/boot/zImage &

        adb shell

        ./system/bin/hello

    ok!這一步比較簡單。

3.在Android硬體抽象層增加接口子產品來通路硬體驅動程式(HAL ------> kernel)

    (1).進入到hardware/libhardware/include/hardware目錄,建立hello.h檔案;

    (2).進入到hardware/libhardware/modules目錄,建立hello目錄,并添加hello.c檔案;

    (3).在hello目錄下建立Android.mk檔案;

        内容為

        LOCAL_PATH := $(call my-dir)

              include $(CLEAR_VARS)

              LOCAL_MODULE_TAGS := optional

              LOCAL_PRELINK_MODULE := false

              LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw

              LOCAL_SHARED_LIBRARIES := liblog

              LOCAL_SRC_FILES := hello.c

              LOCAL_MODULE := hello.default

              include $(BUILD_SHARED_LIBRARY)

         注意,LOCAL_MODULE的定義規則,hello後面跟有default,hello.default能夠保證我們的子產品總能被硬象抽象層加載到。

    (4). 給hello添加root權限,類似于Linux的udev規則,打開Android源代碼工程目錄下,進入到system/core/rootdir目錄,

        裡面有一個名為ueventd.rc檔案,往裡面添加一行:

              /dev/hello 0666 root root;

    (5). 編譯;

         mmm hardware/libhardware/modules/hello

        ###編譯的時候遇到的問題LOGI或LOGE沒有有定義的問題,即需要包好頭檔案。

        編譯成功後,就可以在out/target/product/generic/system/lib/hw目錄下看到hello.default.so檔案了。

    (6). 重新打包Android系統鏡像system.img;

        make snod

        重新打包後,system.img就包含我們定義的硬體抽象層子產品hello.default了。

    這一步也比較簡單。    

4.在Android系統中編寫JNI方法在應用程式架構層提供Java接口通路硬體(frameworks(JNI接口) ------> HAL)

    (1).進入到frameworks/base/services/core/jni目錄,建立com_android_server_HelloService.cpp檔案;

           在com_android_server_HelloService.cpp檔案中,實作JNI方法。注意檔案的指令方法,com_android_server字首表示的是包名,

        表示硬體服務HelloService是放在frameworks/base/services/core/java目錄下的com/android/server目錄的

 注意,在hello_init函數中,通過Android硬體抽象層提供的hw_get_module方法來加載子產品ID為HELLO_HARDWARE_MODULE_ID的硬體抽象層子產品,其中,HELLO_HARDWARE_MODULE_ID是在<hardware/hello.h>中定義的。Android硬體抽象層會根據HELLO_HARDWARE_MODULE_ID的值在Android系統的/system/lib/hw目錄中找到相應的子產品,然後加載起來,并且傳回hw_module_t接口給調用者使用。在jniRegisterNativeMethods函數中,第二個參數的值必須對應HelloService所在的包的路徑,即com.android.server.HelloService。

    (2). 修改同目錄下的onload.cpp檔案。

        a.首先在namespace android增加register_android_server_HelloService函數聲明:

                  namespace android {

                      ...

                      int register_android_server_HelloService(JNIEnv *env);

                  };

        b.在JNI_onLoad增加register_android_server_HelloService函數調用:

                  extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved)

                  {

                       ...

                       register_android_server_HelloService(env);

                       ...

                  }

    (3).修改同目錄下的Android.mk檔案,在LOCAL_SRC_FILES變量中增加一行:

              LOCAL_SRC_FILES:= \

              com_android_server_AlarmManagerService.cpp \

              com_android_server_BatteryService.cpp \

              com_android_server_InputManager.cpp \

             com_android_server_LightsService.cpp \

              com_android_server_PowerManagerService.cpp \

              com_android_server_SystemServer.cpp \

              com_android_server_UsbService.cpp \

              com_android_server_VibratorService.cpp \

              com_android_server_location_GpsLocationProvider.cpp \

        #添加hello子產品的javaj接口

              com_android_server_HelloService.cpp \

              onload.cpp

        注意要在onload.cpp之前添加。

    (4).編譯和重新打包system.img;

        mmm frameworks/base/services/core/jni

        make snod

           這樣,重新打包的system.img鏡像檔案就包含我們剛才編寫的JNI方法了,也就是我們可以通過Android系統的Application Frameworks層

        提供的硬體服務HelloService來調用這些JNI方法,進而調用低層的硬體抽象層接口去通路硬體了。

    這一步也很簡單。

5.在Android系統的應用程式架構層增加硬體服務接口(frameworks(aidl的Service) ------> frameworks(JNI接口))

    (1).進入到frameworks/base/core/java/android/os目錄,新增IHelloService.aidl接口定義檔案;

    (2).傳回到frameworks/base目錄,打開Android.mk檔案,修改LOCAL_SRC_FILES變量的值,增加IHelloService.aidl源檔案:;

    (3).編譯IHelloService.aidl接口;

        mmm frameworks/base

        這樣,就會根據IHelloService.aidl生成相應的IHelloService.Stub接口。

    (4).進入到frameworks/base/services/core/java/com/android/server目錄,新增HelloService.java檔案;

    (5). 修改同目錄的SystemServer.java檔案;

        在ServerThread::run函數中調用了startOtherServices函數,startOtherServices函數中增加加載HelloService的代碼:

                try {

                  Slog.i(TAG, "Hello Service");

                  ServiceManager.addService("hello", new HelloService());

                } catch (Throwable e) {

                  Slog.e(TAG, "Failure starting Hello Service", e);

                }

    (6).修改external/sepolicy/service_contexts檔案,添加一句:

        hello u:object_r:system_server_service:s0

        hello作為系統服務,這樣ServiceManager.addService才有效。

    (7).編譯HelloService和重新打包system.img;

        mmm frameworks/base/services

        make snod

!!!編譯程中,出現com_android_server_HelloService.cpp檔案中LOGI沒有定義問題,先注釋掉。

    這樣,重新打包後的system.img系統鏡像檔案就在Application Frameworks層中包含了我們自定義的硬體服務HelloService了,并且會在系統啟動的時候,

    自動加載HelloService。這時,應用程式就可以通過Java接口來通路Hello硬體服務了。

6.在Android系統中編寫APP通過應用程式架構層通路硬體服務(APP -----> frameworks(aidl的Service))

        這就簡單多了,JNI程式設計嘛,so easy!

    (1).現在eclipse或AS中建好Hello項目,然後複制到package/apps/Hello目錄下;

    (2).建立Android.mk檔案;

        内容為

        LOCAL_PATH:= $(call my-dir)

        include $(CLEAR_VARS)

        LOCAL_MODULE_TAGS := optional

        LOCAL_SRC_FILES := $(call all-subdir-java-files)

        LOCAL_PACKAGE_NAME := Hello

        include $(BUILD_PACKAGE)

    (3).添加Hello項目的編譯,進入device/htc/flounder目錄,修改device.mk檔案;

        在最後添加一句

        PRODUCT_PACKAGES += HALHello

    (4).添加Hello項目到手機system/app中,進入到/build/target/product目錄中,修改core.mk檔案,把自已的項目加入編譯行列中;

         PRODUCT_PACKAGES := \

            framework-res \

            Hello \

    (5).編譯和重新打包;

        make -j8

        make snod

    (6).運作模拟器驗證

        emulator -kernel arch/arm/boot/zImage

編譯過程中可能報錯:

     1.注: 某些輸入檔案使用或覆寫了已過時的 API。 注: 有關詳細資訊, 請使用 -Xlint:deprecation 重新編譯。 24 個警告;

    解決:進入需要編譯的目錄,修改Android.mk檔案:

        a.将 android.policy_phone中的_phone删掉

        b.注釋掉LOCAL_UNINSTALLABLE_MODULE := true

        修改後的檔案内容如下:

        LOCAL_MODULE := android.policy

        #LOCAL_UNINSTALLABLE_MODULE := true

        使用make android.policy 指令編譯。

     2.修改了framework,android對API的修改有一定的規則,要麼隐藏,要麼更新API:

    ******************************

    You have tried to change the API from what has been previously approved.

    To make these errors go away, you have two choices:

       1) You can add "@hide" javadoc comments to the methods, etc. listed in the

          errors above.

       2) You can update current.txt by executing the following command:

             make update-api

          To submit the revised current.txt to the main Android repository,

          you will need approval.

    ******************************

    解決:說得很清楚,兩個選擇。

        a.在方法上添加@hide,隐藏API

        b.更新API。

        a.在方法名和aidl的類名上添加注釋:

        b.更新API:make update-api

            系統就會自動的把我們新增的API 寫入 frameworks/base/api/current.xml 檔案中。

     3.編譯程中,可能會出現com_android_server_HelloService.cpp或其他本項目cpp檔案中LOGI沒有定義問題,

    是因為沒有導包的原因,先注釋掉,解決:

    (1).修改Android.mk檔案配置,添加如下語句

    LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog

    (2).在.c檔案中修改為如下語句

    #include<android/log.h>

    (3).使用方法

    #define LOG_TAG "HelloService"

    #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)

    #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)

    #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)

    (4).列印語句

    LOGI("test log!!!!");

    LOGI("the string is: %s \n",buff);

    4.系統啟動報錯:/system_process E/HelloService: Hello JNI: failed to get hello stub module.這是自己列印的,

`    即加載hello module 失敗,原因是找不到hello.default.so庫檔案。

    解決:

    重新編譯子產品 hardware下的hello子產品

    mmm hardware/libhardware/modules/hello

    out/target/product/generic/system/lib/hw目錄下,就能看到生成的hello.default.so庫檔案了。

    5.系統啟動報錯:

    /system_process E/HelloService: Hello JNI: failed to open hello device.

    /system_process E/HelloStub: Hello Stub: failed to open /dev/hello -- Permission denied.

    這是自己列印的,即打開hello module失敗,原因是沒有root權限。

解決:

    (1)、添加檔案權限

    進入到Android源代碼工程目錄下,修改system/core/rootdir目錄下的ueventd.rc檔案,添加一行:

    /dev/hello 0666 root root

    将修改後的ueventd.rc檔案拷貝到out/target/product/generic/root目錄下,并且最終打包在ramdisk.img鏡像檔案中。

    但是現在還不能完全解決這個問題,因為Android L以後引入了SELinux防火牆機制,每個程序對檔案的通路是有規定的,是以不可忽視的一條log資訊:

    12-20 15:13:26.500 311-311/? W/system_server: type=1400 audit(0.0:4): avc: denied { read write } for name="hello" dev="tmpfs" ino=1583         scontext=u:r:system_server:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=0

    12-20 15:13:26.530 311-311/? W/system_server: type=1400 audit(0.0:5): avc: denied { read write } for name="hello" dev="tmpfs" ino=1583         scontext=u:r:system_server:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=0

    這句話的意思是system_server 程序想要通路device:chr_file 缺少read和write的權限!這就需要在修改SELinux的政策,賦予system_server 這個權限。

    (2)、修改SELinux政策

    第一步:找到需要通路該核心節點的程序(process),筆者自己這個節點由system_server程序來通路

    第二步:打開檔案AndroidL/android/external/sepolicy/file_contexts.be

    仿照這個檔案裡的寫法,為這個定義一個你想要的名字:

    # We add here

    /dev/hello              u:object_r:hello_device:s0

    wf_bt_device是自定義,其他左右兩邊的内容都和上面的範例一緻。

    第三步:打開檔案AndroidL/android/external/sepolicy/device.te

    仿照這個檔案裡的寫法,将剛剛第二步寫的wf_bt_device聲明為dev_type:

    # We add here

    type hello_device, dev_type;

    第四步:AndroidL/android/external/sepolicy/目錄下很多.te檔案都是以程序名來結尾的,比如有針對surfaceflinger程序的surfaceflinger,

    有針對vold程序的vold.te,

    剛剛從第一步得到,這個節點是由system_server程序來通路,是以,我們找到system_server.te打開,加入允許這個程序對/dev/wf_bt的讀寫權限,

    # Read/Write to /dev/hello

    allow system_server hello_device:chr_file rw_file_perms;

    # chr_file表示字元裝置檔案,如果是普通檔案用file,目錄請用dir

    # rw_file_perms代表讀寫權限

    allow system_server hello_device:chr_file rw_file_perms;

    這句話的意思是:允許system_server程序擁有對hello_device的這個字元裝置的讀寫權限。

    改了這些之後,你就可以make clean;make -j8編譯在make snod重新打包來驗證權限是否擷取成功。

    6.open成功,但卻無法讀寫,或讀寫無效open傳回0

    解決:

    将

        if(mDevice->fd = open(DEVICE_NAME, O_RDWR) == -1)

    分開寫成

        mDevice->fd = open(DEVICE_NAME, O_RDWR);

        if(mDevice->fd == -1)

    或者加括号

    if((mDevice->fd = open(DEVICE_NAME, O_RDWR)) == -1)

    ==判定條件符,幾乎是級别最低的,至于,為什麼這樣,我也無法了解,可能是編譯器的問題。

檢視Kernel列印的log。

a. emulator -kernel arch/arm/boot/zImage -show-kernel &

b. adb shell

cat /proc/kmsg