1. 程式人生 > >Android開發中的JNI之ioctl的使用

Android開發中的JNI之ioctl的使用

最近開發專案中,涉及到一個訊飛硬體降噪模組的功能除錯;在與底層驅動溝通後,被告知底層已經實現好了ioctl,需要上層編寫service直接呼叫;作為一個聽都沒聽過ioctl的小白,簡直是懵X狀態。提前瞭解的上層到底層的呼叫關係,經過HAL層,現在被告知不涉及HAL層,更覺得不可思議???發火簡直了,於是乖乖地參考專案經驗,跟了幾天終於搞定了。下面介紹下一整個對訊飛硬體降噪模組的除錯

一、硬體訪問服務開發

歸根到底,不論是HAL還是ioctl,都需要先建立一個硬體訪問服務,為上層提供訪問支援

1、定義硬體訪問服務介面

1.1定義介面

在這裡和基本的AIDL定義一樣,先定義一個AIDL的介面檔案,宣告需要使用的方法;

frameworks/base/core/java/android/chipctrl/IChipCtrl.aidl

package android.chipctrl;

/** @hide */
interface IChipCtrl
{
    int openDevice(String devName);
    void closeDevice(int handle);
    byte[] loadInfo(in byte []orig, int handle);
    boolean setDenoiseMode(int mode);//主要使用此方法切換硬體降噪模組的功能
}

1.2新增到編譯指令碼

由於此服務介面使用AIDL語言描述,我們需要將其新增到編譯指令碼中,這樣系統才能將其轉換為Java檔案,然後再對它進行編譯。進入到framework/base下的Android.mk,修改LOCAL_SRC_FILES的值,新增此檔案

LOCAL_SRC_FILES += \
    core/java/android/chipctrl/IChipCtrl.aidl \

然後使用mmm命令對其編譯:mmm ./framework/base/ 編譯出的framework.jar檔案中就包含有IChipCtrl介面。AIDL是程序間通訊,Server端和Client端通過Binder來互動,Server通過Binder.Stub來監聽Client程序傳送的程序間通訊請求,而Client端則需要先獲得一個Binder代理物件介面(Proxy),然後通過這個Binder代理物件介面向它傳送程序間通訊請求。關於Binder這方面的知識後續會繼續研究,此處重點關注JNI。

2、實現硬體訪問服務

實現AIDL介面定義的服務,此處是真正的服務,AIDL介面只是供跨程序通訊使用;

frameworks/base/services/java/com/android/server/chipctrlservice/ChipCtrlService.java

package com.android.server.chipctrlservice;

import android.chipctrl.IChipCtrl;

public class ChipCtrlService extends IChipCtrl.Stub {
    public static final String TAG = "ChipCtrlService";
    public static final String Name = "chipctrlservice";

    public int openDevice(String devName) {
        return nativeOpenDevice(devName);
    }

    public void closeDevice(int handle) {
        nativeCloseDevice(handle);
    }

    public byte []loadInfo(byte []orig, int handle) {
        return nativeLoadInfo(orig, handle);
    }

    public boolean setDenoiseMode(int mode) {
        return nativeSetDenoiseMode(mode);
    }

    public native byte[] nativeLoadInfo(byte[] msg, int handle);//這裡就是宣告的Native方法,供JNI層實現
    public native int nativeOpenDevice(String devName);
    public native void nativeCloseDevice(int handle);
    public native boolean nativeSetDenoiseMode(int mode);
}

編寫完成後進行編譯 mmm ./frameworks/base/services/java/ 將其編譯到services.jar中。

3、實現硬體訪問服務的JNI方法

通常把硬體訪問服務的JNI方法實現放在frameworks/base/services/jni目錄下com_android_server_chipctrlservice_ChipCtrlService.cpp

#define LOG_TAG "chipctrljni native.cpp"
#include <utils/Log.h>
#include <stdio.h>
#include "jni.h"
#include "JNIHelp.h"
extern "C" {
#include "chipctrl/chip204.h"
}
namespace android {

static jint nativeOpenDevice(JNIEnv *env, jclass clazz, jstring devName)
{
    const char *rawDevName = env->GetStringUTFChars(devName, NULL);
    int handle = open_i2c_device(rawDevName);
    env->ReleaseStringUTFChars(devName, rawDevName);

    if (handle < 0) {
        ALOGE("open chip %s failed!", devName);
    } else {
        ALOGI("open chip %s success!", devName);
    }

    return handle;
}

static void nativeCloseDevice(JNIEnv *env, jclass clazz, jint handle)
{
    if (handle > 0) {
        close_i2c_device(handle);
    }
}

static jbyteArray
nativeLoadInfo(JNIEnv *env, jclass clazz, jbyteArray msg, jint handle) {
    jbyte *cValues = env->GetByteArrayElements(msg, NULL);
    jbyte result[32];

    BOOL success = chip_loadinfo(handle, (const unsigned char *)cValues, (unsigned char *)result);
    env->ReleaseByteArrayElements(msg, cValues, 0);

    if (!success) {
        ALOGE("chip_loadinfo failed!");
        return NULL;
    }

    jbyteArray jValue = env->NewByteArray(32);
    env->SetByteArrayRegion(jValue, 0, 32, result);

    return jValue;
}

static jboolean nativeSetDenoiseMode(JNIEnv *env, jclass clazz, jint mode) {
    BOOL success = false;

    success = set_denoise_mode(mode);

    return success;
}

static const char *classPathName = "com/android/server/chipctrlservice/ChipCtrlService";

static JNINativeMethod methods[] = {
  {"nativeLoadInfo", "([BI)[B", (void*)nativeLoadInfo },
  {"nativeOpenDevice", "(Ljava/lang/String;)I", (void*)nativeOpenDevice },
  {"nativeCloseDevice", "(I)V", (void*)nativeCloseDevice },
  {"nativeSetDenoiseMode", "(I)Z", (void*)nativeSetDenoiseMode },
};

/*
 * Register native methods for all classes we know about.
 *
 * returns JNI_TRUE on success.
 */
int register_android_server_chipctrlservice_ChipCtrlService(JNIEnv* env)
{
  return jniRegisterNativeMethods(env, classPathName, methods, NELEM(methods));
}
}

我們在編寫完JNI實現後,需要把它註冊到系統JNI中(java虛擬機器),方法是修改此路徑下的onload.cpp檔案,

#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"

namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_chipctrlservice_ChipCtrlService(JNIEnv* env);//add for iflytek by bruce
};

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");

    register_android_server_PowerManagerService(env);
    register_android_server_SerialService(env);
    register_android_server_InputApplicationHandle(env);
    register_android_server_InputWindowHandle(env);
    register_android_server_InputManager(env);
    register_android_server_LightsService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbHostManager(env);
    register_android_server_VibratorService(env);
    register_android_server_SystemServer(env);
    register_android_server_location_GpsLocationProvider(env);
    register_android_server_location_FlpHardwareProvider(env);
    register_android_server_connectivity_Vpn(env);
    register_android_server_AssetAtlasService(env);
    register_android_server_ConsumerIrService(env);
    register_android_server_chipctrlservice_ChipCtrlService(env);//add for iflytek by bruce


    return JNI_VERSION_1_4;
}

之後,需要將其編譯進系統,方法是開啟此路徑下的Android.mk檔案,要注意,一定要把下面的C語言實現編譯出的lib庫也加入到mk檔案中,不然編譯會報錯的。

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

LOCAL_SRC_FILES:= \
..........
    com_android_server_chipctrlservice_ChipCtrlService.cpp \
    onload.cpp

.............

LOCAL_SHARED_LIBRARIES := \
.....
    libchipctrl \
...

在這個JNI中,實現了Service服務中宣告的Native方法;它的實現方法就是呼叫C/C++編寫的C層程式碼,從Include的標頭檔案我們可以看到chip204.h,frameworks/base/include/chipctrl下的三個標頭檔案中聲明瞭JNI呼叫的方法,主要看下面的實現就行了

#include <stddef.h>
#include <stdio.h>
#include <string.h>

typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1)

typedef int BOOL;
#define FALSE 0
#define TRUE 1

typedef int DE_NOISE_MODE;
//下面是對四種模式定義ioctrl的引數,這個定義要與驅動層完全一致,這是ioctrl使用的關鍵點。
#define VCDRIVER_CMD_FUNC_MODE_PHONE   _IOW('U', 0x10, unsigned long)
#define VCDRIVER_CMD_FUNC_MODE_NOISECLEAN   _IOW('U', 0x11, unsigned long)
#define VCDRIVER_CMD_FUNC_MODE_PASSBY   _IOW('U', 0x14, unsigned long)
#define VCDRIVER_CMD_FUNC_MODE_WAKEUP   _IOW('U', 0x15, unsigned long)
//下面是對四種模式的常量值定義
#define MODE_PHONE 1
#define MODE_NOISECLEAN 2
#define MODE_PASSBY 3
#define MODE_WAKEUP 4

//開啟裝置節點
HANDLE open_device(const char * devname);

//控制選擇降噪模組的模式
BOOL control_denoise_device(HANDLE handle, DE_NOISE_MODE mode);

//關閉裝置節點
void close_device(HANDLE handle);

BOOL set_denoise_mode(DE_NOISE_MODE mode);

#endif

下面是C語言的實現,我們需要將其編譯成lib庫,供JNI層呼叫。在framework/base/libs/chipctrl/下新增Android.mk編譯檔案

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

LOCAL_MODULE_TAGS := optional

# This is the target being built.
LOCAL_MODULE:= libchipctrl//將其編譯為此庫,上面的jni編譯時是會呼叫此庫的。


# All of the source files that we will compile.
LOCAL_SRC_FILES:= chip204.c I2CDevice.c DeNoise.c

# All of the shared libraries we link against.
LOCAL_SHARED_LIBRARIES := \
	libutils liblog

# No special compiler flags.
LOCAL_CFLAGS +=

include $(BUILD_SHARED_LIBRARY)

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <linux/ioctl.h>

#include "chipctrl/DeNoise.h"

#include "cutils/log.h"

#define LOG_NDEBUG 0

#define LOG_TAG "DeNoise"


HANDLE open_device(const char * devname)
{
    HANDLE fd;
    //open device
    fd = open(devname, O_RDWR);

    if (fd < 0) {
        ALOGD("%s: open %s failed", __func__, devname);
        return INVALID_HANDLE_VALUE;
    } else {
        ALOGD("%s: open %s success handle = %d", __func__, devname, fd);
    }

    return fd;
}

BOOL control_denoise_device(HANDLE handle, DE_NOISE_MODE mode)
{
    int ret = -1;

    ALOGD("%s: handle %d, denoise mode %d", __func__, handle, mode);
    switch (mode) {//根據設定的不同模式,來匹配傳入不同的引數,傳入的引數就是供ioctrl匹配用的,它是一種cmd命令值,其定義已在.h中聲明瞭,這個定義要保證驅動層與呼叫層完全一致,因為這是ioctrl使用的重點重點重重點
        case MODE_PHONE:
                ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_PHONE, 0);
                break;
        case MODE_NOISECLEAN:
                ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_NOISECLEAN, 0);
                break;
        case MODE_PASSBY:
                ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_PASSBY, 0);
                break;
        case MODE_WAKEUP:
                ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_WAKEUP, 0);
                break;
        default:
                ALOGE("%s: mode invaild", __func__);
    }

    if (ret != 0){
        ALOGD("%s: ret %d errno type %s", __func__, ret, strerror(errno));
    }

    return ret == 0;
}

void close_device(HANDLE handle)
{
    if (handle != INVALID_HANDLE_VALUE) {
        close(handle);
    }
}

BOOL set_denoise_mode(DE_NOISE_MODE mode)
{
    const char *devName = "/dev/xf6000ye_CV";//底層提供的裝置節點
    HANDLE mFD;
    BOOL success;

    mFD = open_device(devName);

    if (mFD < 0) {
        ALOGE("%s: open device failed", __func__);
        return FALSE;
    }

    success = control_denoise_device(mFD, mode);
    ALOGD("%s: success %d", __func__, success);
    close_device(mFD);
    return success;
}

寫到這裡就到所謂的ioctrl了,我們可以從上面的c語言實現中的control_denoise_device方法中看到對不同的值匹配後,都會呼叫ioctrl函式,這個就是ioctrl的使用了。具體驅動層是如何實現的,待後續再學習,本篇重點不在這。

4、啟動硬體訪問服務

上面已經完成硬體訪問服務的一整套實現,我們需要把它加入到系統程序System中啟動它,才能供上層呼叫;我們找到SystemServer.java檔案,參考其他服務,將此服務加入其中即可;

            //Add ChipCtrlService for iflytek ,by bruce
            Slog.i(TAG,"chipctrlservice");
            ServiceManager.addService("chipctrlservice",new ChipCtrlService());

重新編譯 mmm ./frameworks/base/services/java/

最後,需要提升下硬體裝置節點的許可權,在device/下的init.rc中

新增chmod 0666 /dev/xxxxx即可