android驱动开发基础

目录

    • 基本环境
    • 一、build
    • 二、编写Android.mk
    • 三、LOG系统
    • 四、init.rc
    • 五、JNI
      • 1. java调用jni接口
      • 2. aosp中开发native代码
      • 3. Android Studio中开发native代码
    • 六、传统HAL
      • 1. 接口声明
      • 2. 接口实现
      • 3. 添加编译规则
      • 4. 使用HAL的接口
    • 参考文献

基本环境

  • 开发板:hikey960
  • 代码:aosp,Android R
  • 开发环境:64bit ubuntu 16.04,

一、build

添加新的lunch选项(新产品):赋值COMMON_LUNCH_CHOICESPRODUCT_MAKEFILES如下 (参考device/sample/products/AndroidProducts.mk,实例参考device/linaro/hikey/AndroidProducts.mk)

PRODUCT_MAKEFILES := \
  $(LOCAL_DIR)/sample_addon.mk
 
COMMON_LUNCH_CHOICES := sample_addon-userdebug

二、编写Android.mk

Android.mk模板如下所示:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
        hello.c

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE := hello_elf_arm64

include $(BUILD_EXECUTABLE)
  • 编译报unused parameters错时,需加上:LOCAL_CFLAGS := -Wno-unused-parameter 以忽略掉该报错。

三、LOG系统

  • log系统的设备节点:
hikey960:/ # ls /dev/socket/log* -l                                            
srw-rw-rw- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logd
srw-rw-rw- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logdr
s-w--w--w- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logdw
  • 转载来自文章《Android Log系统介绍 (基于Android N)》的log系统核心图如下所示,更多细节请参考此文。
    android驱动开发基础_第1张图片                      图1 Android log系统

  • Android Q、R上面log的缓冲区有:mainsystemradioeventscrash

  • c/c++中打印调试信息:
     ① 宏定义标签及添加头文件:
      #define LOG_TAG "hello"
      #include
     ② Android.mk中声明所依赖的动态库:
      LOCAL_SHARED_LIBRARIES := \
        liblog
     ③ 使用宏ALOGV()ALOGD(“xxx”)ALOGI()ALOGW()ALOGE(),进行打印log

  • java中打印log信息:
     ① 导入包:import android.util.Log;
     ② 在类内部定义标签:private final String TAG = "listview";
     ③ 使用方法Log.d(TAG, "xxx");Log.i(TAG, "xxx");Log.w(TAG, "xxx");Log.e(TAG, "xxx");进行打印

四、init.rc

参考system/core/init/init.cpp中如下代码,可知/init祖先进程通过解析/init.rc的内容来进行启动各种本地服务

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");
  • android init语言说明文档:system/core/init/README.md
  • 在厂商.rc中添加规则,实例:device/linaro/hikey/init.common.rc

五、JNI

以控制LED灯为背景,进行JNI编程技术的介绍。aosp中JNI技术参考代码:development/samples/SimpleJNI/

1. java调用jni接口

将native接口的声明组织到LedControl\app\src\main\java\com\example\lowlevel\LedNative.java

package com.example.lowlevel;

public class LedNative {
	/* 1) 加载jni动态库 */
    static {
        System.loadLibrary("led_jni");
    }
	/* 2) 使用关键字native声明jni接口 */
    public native int openDev();
    public native int closeDev();
    public native int devOn();
    public native int devOff();
}

然后在class MainActivity中进行调用:

/* 3) 使用上面定义的LedNative类生成对象后调用接口 */
LedNative ledNative = new LedNative();
ledNative.devOn();
  • 注意:普通app没有权限加载jni动态库依赖的库,需将apk预置或传入到手机的/system/app/下作为system_app

2. aosp中开发native代码

 ① 定义JAVA和C/C++间的映射表如下所示。其中()I是java中native方法的签名,在APP目录LedControl\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes下执行javap -s com.example.lowlevel.LedNative可查看各方法的签名,再把他们分别填充到映射表。

static JNINativeMethod ledMethod[] = { 
  {"openDev", "()I", (void*)openLed},
  {"closeDev", "()I", (void*)closeLed},
  {"devOn", "()I", (void*)ledOn},
  {"devOff", "()I", (void*)ledOff},
};

 ② 通过定义jni层的回调函数jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/),来将上述映射表注册到java虚拟机:

static const char *classPathName = "com/example/lowlevel/LedNative";

jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
    jint ret;
    JNIEnv* env = NULL;

    ALOGD("------%s", __FUNCTION__);
    ret = vm->GetEnv((void**)&env, JNI_VERSION_1_4);
    if (ret != JNI_OK) {
        ALOGE("ERROR: GetEnv failed");
        return -1; 
    }   

    jclass cls = env->FindClass(classPathName);
    if (cls == NULL) {
        ALOGE("Native registration unable to find class '%s'", classPathName);
        return JNI_FALSE;
    }

    ret = env->RegisterNatives(cls, ledMethod, sizeof(ledMethod)/sizeof(ledMethod[0]));
    if (ret < 0) {
        ALOGE("RegisterNatives failed for '%s'", classPathName);
        return JNI_FALSE;
    }

    return JNI_VERSION_1_4;
}

 ③ Android.mk中指定生成的是动态库和编译工具分别如下:

LOCAL_MODULE:= libled_jni
include $(BUILD_SHARED_LIBRARY)
  • 注意:测试时,需要修改设备节点权限:# chmod 0666 /sys/devices/platform/leds/leds/user_led3/brightness,以及关闭selinux:# setenforce 0

3. Android Studio中开发native代码

 参考我的另一篇文章有详细开发步骤:Android Studio开发NDK代码。

六、传统HAL

本章介绍开发传统HAL代码和调用HAL的主要流程,如下出现的代码场景是控制LED灯。

1. 接口声明

开发HAL在头文件,定义相应硬件模块和设备结构,以继承基础模块结构(struct hw_module_t)和基础设备结构(struct hw_device_t)。另外还有暴露接口给调用者的目的。具体开发流程如下所示:

#ifndef __LED_HAL_H__
#define __LED_HAL_H__

#include 

__BEGIN_DECLS

/* 1) 定义生成的动态库前缀的名称, xxx.default.so */
#define LED_HARDWARE_MODULE_ID "led_hal"

/* 2) 定义专有硬件模块,继承 hw_module_t,并扩展要暴露的模块控制接口 */
typedef struct led_module {
    struct hw_module_t common;
} led_module_t;

/* 3) 定义专有硬件硬件,继承 hw_device_t */
typedef struct led_device {
    struct hw_device_t common;
/* 4) 扩展要暴露出来的设备控制接口 */
    int (*control)(int enable);
} led_device_t;

/* 5) 用来调用 hw_module_t.methods->open() */
static inline int led_hal_open(const struct hw_module_t* module,
        led_device_t** device) {
    return module->methods->open(module, NULL,
            TO_HW_DEVICE_T_OPEN(device));
}
/* 6) 用来调用 hw_device_t.close() */
static inline int led_hal_close(led_device_t* device) {
    return device->common.close(&device->common);
}

__END_DECLS

#endif

2. 接口实现

 ① 实例化自己派生的专有硬件模块结构led_module_t

led_module_t HAL_MODULE_INFO_SYM = {		                /* 1.实例名必须为 HAL_MODULE_INFO_SYM */
    .common = {
        .tag = HARDWARE_MODULE_TAG,							/* 2.基础结构标签必为 HARDWARE_MODULE_TAG */
        .module_api_version = HARDWARE_MODULE_API_VERSION(1,0),		/* 3.模块版本为1.0 */
        .hal_api_version = HARDWARE_HAL_API_VERSION,		/* 4.固定 */
        .id = LED_HARDWARE_MODULE_ID,						/* 5.绑定最后生成的动态库名:[id].default.so */
        .name = "Hikey960 led HAl",
        .author = "[email protected]",
        .methods = &led_module_methods,						/* 6.指定激活模块下设备的方法,下文中介绍 */
    },
};

 ② 实例化struct hw_module_methods_t,该结构下成员只有open

struct hw_module_methods_t led_module_methods = {
    .open = led_module_open,								/* 在下文中定义 */
};

 ③ 定义hw_module_methods_t.open(),它的任务是实例化struct hw_device_t的派生结构体led_device_t

int led_module_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device)
{
    led_device_t* led_dev = (led_device_t*)calloc(1, sizeof(led_device_t)); /* 1.申请内存实例化派生结构 */

    led_dev->common.tag = HARDWARE_DEVICE_TAG;				/* 2.HAL设备标签,值固定 */
    led_dev->common.version = HARDWARE_DEVICE_API_VERSION(1, 0);
    led_dev->common.module = (struct hw_module_t*)module;
    led_dev->common.close = led_device_close;				/* 2.close函数与本函数任务相反,用于释放相关资源 */
    led_dev->control = led_control;							/* 3.对接自定义的接口 */

    *device = (hw_device_t*) led_dev;	/* 4.通过参数返回实例化后设备指针,HAL调用者通过此指针调用设备的控制接口 */

    return 0;
};

 ④ 定义hw_device_t.close(),HAL调用者调用此函数用于关闭设备释放hw_module_methods_t.open()中申请的资源

int led_device_close(struct hw_device_t* device)
{
    led_device_t* priv = (led_device_t*) device;
    if (priv)
        free(priv);
    return 0;
}

 ⑤ 实现暴露给HAL调用者的接口,本场景要实现int led_control(int enable);

3. 添加编译规则

 ① 动态库名应为[id].default.so,所以LOCAL_MODULE:= led_hal.default
 ② HAL动态库应部署到/system/lib64/hw//system/lib/hw/下,需加上规则LOCAL_MODULE_RELATIVE_PATH := hw
 ③ 导出暴露给调用者的接口头文件。部署到system分区的HAL库,使用LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include;部署到vendor分区的HAL库,应使用LOCAL_HEADER_LIBRARIES := libhardware_headers

  • 若要HAL动态库部署到/vendor/lib64/hw//vendor/lib/hw/,还需加上规则LOCAL_PROPRIETARY_MODULE := true
    HAL参考代码:hardware/libhardware/modules/gralloc

4. 使用HAL的接口

 ① 加载硬件的HAL模块,使用 int hw_get_module(const char *id, const struct hw_module_t **module)

  • id——HAL动态库名字的前缀,本场景值为led_hal
  • module——通过此参数获得hw_module_t 实例
#include 
#include 

static led_module_t* pModule = NULL;	/* 全局变量 */
int ret = hw_get_module(LED_HARDWARE_MODULE_ID, (const struct hw_module_t **)&pModule);
if (ret != 0) {
    ALOGE("get hardware module '%s' failed", LED_HARDWARE_MODULE_ID);
    return -1; 
}

 ② 通过hw_module_t 的派生结构体led_module_t,获取hw_device_t的派生结构体led_device_t,方法如下所示。但为了方便调用者,HAL接口头文件一般会将下面的代码实现到内联函数中,如调用前文的led_hal_open((const struct hw_module_t*)pModule, &pDevice);

static led_device_t* pDevice = NULL;
pModule->common.methods->open((const struct hw_module_t *)pModule, NULL, (struct hw_device_t**)&pDevice);

 ③使用HAL接口进行硬件控制的形式:pDevice->foo(xxx);;如本场景使用:pDevice->control(1);来点亮LED;
 ④ 在需要关闭设备和释放有关内存资源时,需调用hw_device_t.close(),所以调用方法如下所示。但为了方便调用者,HAL接口头文件一般会将下面的代码实现到内联函数中,如调用前文的led_hal_close(pDevice);

pDevice->common.close((struct hw_device_t*)pDevice);
  • 注:调用HAL动态库的代码,需要链接动态库libhardware,故Android.mkLOCAL_SHARED_LIBRARIES := libhardware
    aosp中调用HAL的参考代码:frameworks/native/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp

参考文献

  [1] moasm. Android Log系统介绍 (基于Android N)[EB/OL].简书,2019

你可能感兴趣的:(嵌入式arm,linux,Android,Android,驱动开发,JNI,HAL)