Android O之HAL層開發
這裡介紹的是一種簡單HAL的寫法與呼叫。
我將會編寫一個app直接呼叫HAL的介面,而HAL層程式碼將直接讀寫驅動的節點。
簡介
Android O的一項新元素是 Project Treble。這是 Android 作業系統框架在架構方面的一項重大改變,旨在讓製造商以更低的成本更輕鬆、更快速地將裝置更新到新版 Android 系統。
在Android O之前,HAL是一個個的.so庫,通過dlopen來進行開啟,庫和framework位於同一個程序。
新的架構之下,framework和hal運行於不同的程序,所有的HAL採用新的HIDL技術來完成。作為此變化的一部分,執行 Android 8.0 的裝置必須支援繫結式或直通式 HAL:
繫結式 HAL。以 HAL 介面定義語言 (HIDL) 表示的 HAL。這些 HAL 取代了早期 Android 版本中使用的傳統 HAL 和舊版 HAL。在繫結式 HAL 中,Android 框架和 HAL 之間通過 Binder 程序間通訊 (IPC) 呼叫進行通訊。所有在推出時即搭載了 Android 8.0 或後續版本的裝置都必須只支援繫結式 HAL。
直通式 HAL。以 HIDL 封裝的傳統 HAL 或舊版 HAL。這些 HAL 封裝了現有的 HAL,可在繫結模式和
Same-Process(直通)模式下使用。升級到 Android 8.0 的裝置可以使用直通式 HAL。
一看就知道繫結式HAL是主推方法了。後面的HAL也將用繫結式HAL方式編寫。
編寫.hal檔案
原始碼裡面有一個指紋識別,路徑在/android/hardware/interfaces/biometrics/fingerprint。
這裡將編寫一個簡單版的fingerprint HAL。只有set、subscribe、unsubscribe和callback回撥方法。直接往驅動節點裡面讀寫資料,操作驅動節點的方法主要有三個open、write、read。
我們需要寫三個.hal檔案,types.hal、IFingerprint.hal和IFingerprintCallback.hal。
types.hal定義的是一些資料結構,IFingerprint.hal定義的是從Framework往HAL呼叫的介面,而IFingerprintCallback.hal則是HAL往Framework回撥的介面。
首先在/hardware/interfaces目錄下新建一個fingerprint資料夾,然後在fingerprint裡面再新建一個名叫1.0的資料夾,在1.0的資料夾裡面新增hal檔案。
//types.hal
package [email protected]1.0;
struct FingerprintValue {
/** Property identifier */
int32_t propId;
vec<int32_t> values;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
這裡只定義一個簡單的結構體,用於儲存指紋資料。
//IFingerprint.hal
package [email protected]1.0;
import IFingerprintCallback;
interfaceIFingerprint {
subscribe(IFingerprintCallback callback) generates (bool retval);
unsubscribe() generates (bool retval);
set(FingerprintValue fingerprintValue) generates (bool retval);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
這裡定義了三個方法,訂閱IFingerprintCallback回撥、取消訂閱、設定資料到驅動。
//IFingerprintCallback.hal
package android.hardware.fingerprint@1.0;
interface IFingerprintCallback {
oneway onFingerprintEvent(FingerprintValue fingerprintValue);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
這裡就是剛才訂閱的回撥方法,用於回撥資料給應用。
寫完這三個hal檔案後,在原始碼根目錄下導環境變數,然後進入到/hardware/interfaces目錄執行如下命令:
./update-makefiles.sh
- 1
這樣就會生成一些bp檔案、mk檔案和一些必要的東西。
編寫主要邏輯
在1.0資料夾裡面新建一個default資料夾。
編寫Fingerprint.h和Fingerprint.cpp,我們將在Fingerprint.cpp裡面實現我們剛才在hal檔案裡面定義的方法,讀寫驅動節點資料。程式碼如下:
//Fingerprint.h
#ifndef ANDROID_HARDWARE_FINGERPRINT_V1_0_FINGERPRINT_H
#define ANDROID_HARDWARE_FINGERPRINT_V1_0_FINGERPRINT_H
#include <android/hardware/fingerprint/1.0/IFingerprint.h>
namespaceandroid {
namespacehardware {
namespacefingerprint {
namespaceV1_0 {
namespaceimplementation {
using ::android::hardware::fingerprint::V1_0::IFingerprint;
using ::android::hardware::fingerprint::V1_0::IFingerprintCallback;
using ::android::hardware::fingerprint::V1_0::FingerprintValue;
struct Fingerprint : public IFingerprint {
Fingerprint();
~Fingerprint();
Return<bool> subscribe(const sp<IFingerprintCallback>& callback) override;
Return<bool> unsubscribe() override;
Return<bool> set(const FingerprintValue& fingerprintValue) override;
private:
void fingerprint_message_recv_thread_init();
void fingerprint_message_recv_thread_destroy();
void fingerprint_message_recv_thread_subscribe();
void fingerprint_message_recv_thread_unsubscribe();
};
extern "C" IFingerprint* HIDL_FETCH_IFingerprint(const char* name);
} // namespace implementation
} // namespace V1_0
} // namespace fingerprint
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_FINGERPRINT_V1_0_FINGERPRINT_H
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
//Fingerprint.cpp
#define LOG_TAG "[email protected]"
#include <stdbool.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <signal.h>
#include <semaphore.h>
#include <log/log.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include "Fingerprint.h"
#define REMOTE_MESSAGE_BUFFER_SIZE 36
#define REMOTE_MESSAGE_PRINT_BUFFER_SIZE 256
#define REMOTE_MESSAGE_PRINT_MAX_COUNT 64
namespace android {
namespace hardware {
namespace fingerprint {
namespace V1_0 {
namespace implementation {
struct hw_fingerprint_device_impl {
int fd;
};
struct hw_fingerprint_recv_thread
{
pthread_t t_id;
pthread_attr_t t_attr;
bool t_exitflag;
};
static sp<IFingerprintCallback> mCallback = 0;
static struct hw_fingerprint_device_impl g_fingerprint_impl =
{
.fd = -1,
};
static struct hw_fingerprint_recv_thread g_fingerprint_recv_thread =
{
.t_id = 0,
.t_exitflag = false,
};
static void* fingerprint_message_recv_thread_proc(void *arg)
{
ALOGD("%s", __FUNCTION__);
unsigned char data[REMOTE_MESSAGE_BUFFER_SIZE];
FingerprintValue fingerprintValue;
while (g_fingerprint_recv_thread.t_exitflag == false)
{
if(g_fingerprint_impl.fd != -1)
{
memset(data, 0, REMOTE_MESSAGE_BUFFER_SIZE);
int len = read(g_fingerprint_impl.fd, data, REMOTE_MESSAGE_BUFFER_SIZE); //從驅動節點讀取資料
if (len > 0){
if( data[0] != 0 ){
//report message
fingerprintValue.values.resize(len);
for(int i = 0; i < len ; i++){
fingerprintValue.values[i] = data[i];
}
if(mCallback != 0)
{
mCallback->onFingerprintEvent(fingerprintValue);//回撥資料給應用
}else{
ALOGE("Try to mCallback->onFingerprintEvent but mCallback==0!!!");
}
}else{
usleep(100000);
}
}
}
else
{
ALOGE("Device has not been initialized!");
}
}
ALOGD("%s END!", __FUNCTION__);
return NULL;
}
Fingerprint::Fingerprint()
{
ALOGD("%s", __FUNCTION__);
if(-1 == g_fingerprint_impl.fd)
{
g_fingerprint_impl.fd = open("/dev/i2c-6", O_RDWR | O_NOCTTY | O_NDELAY);//開啟驅動節點
if(-1 == g_fingerprint_impl.fd)
{
ALOGE("Open device \"/dev/i2c-6\" failed!");
}
}
}
Fingerprint::~Fingerprint() {
if(-1 != g_fingerprint_impl.fd)
{
close(g_fingerprint_impl.fd);
g_fingerprint_impl.fd = -1;
}
}
void Fingerprint::fingerprint_message_recv_thread_subscribe()
{
ALOGD("%s", __FUNCTION__);
g_fingerprint_recv_thread.t_exitflag = false;
if (pthread_create(&g_fingerprint_recv_thread.t_id,
NULL,
fingerprint_message_recv_thread_proc,
NULL))
{
g_fingerprint_recv_thread.t_id = 0;
}
}
void Fingerprint::fingerprint_message_recv_thread_unsubscribe()
{
ALOGD("%s", __FUNCTION__);
if(g_fingerprint_recv_thread.t_id != 0)
{
g_fingerprint_recv_thread.t_exitflag = true;
void* lpv = NULL;
pthread_join(g_fingerprint_recv_thread.t_id, &lpv);
g_fingerprint_recv_thread.t_id = 0;
}
}
//訂閱的時候開啟一個執行緒從驅動節點一直read資料
Return<bool> Fingerprint::subscribe(const sp<IFingerprintCallback>& callback) {
ALOGD("%s", __FUNCTION__);
mCallback = callback;
if(-1 != g_fingerprint_impl.fd)
{
fingerprint_message_recv_thread_subscribe();//建立執行緒read資料
return true;
}
return false;
}
Return<bool> Fingerprint::unsubscribe() {
ALOGD("%s", __FUNCTION__);
//signal(SIGIO, SIG_IGN);
if(-1 != g_fingerprint_impl.fd)
{
fingerprint_message_recv_thread_unsubscribe();
}
return true;
}
Return<bool> Fingerprint::set(const FingerprintValue& fingerprintValue) {
ALOGD("%s", __FUNCTION__);
std::vector<uint8_t> vecData;
unsigned int write_size = 0;
for (auto value : fingerprintValue.values) {
vecData.push_back((uint8_t)value);
ALOGD("%s , value : %d", __FUNCTION__,value);
}
int msg_length = vecData.size();
if(-1 != g_fingerprint_impl.fd){
write_size = write(g_fingerprint_impl.fd, (char *)vecData.data(), msg_length);//往驅動節點寫資料
if(msg_length != write_size){
ALOGE("Fingerprint Send message failed! write_size: %d != msg_length: %d !", write_size, msg_length);
return false;
}
}else{
ALOGE("Fingerprint Device has not been initialized!");
return false;
}
return true;
}
IFingerprint* HIDL_FETCH_IFingerprint(const char* /* name */) {
return new Fingerprint();
}
} // namespace implementation
} // namespace V1_0
} // namespace fingerprint
} // namespace hardware
} // namespace android
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
編寫完上面兩個檔案,再執行一下./update-makefiles.sh更新一下。
之後還有編寫[email protected] 啟動指令碼, service.cpp入口 ,Android.bp用於編譯。
//android.hardware.fingerprint@1.0-service.rc
service fingerprint-hal-1-0 /vendor/bin/hw/android.hardware.fingerprint@1.0-service
class hal
user system
group system
- 1
- 2
- 3
- 4
- 5
//service.cpp
#define LOG_TAG "[email protected]"
#include <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include <android/hardware/fingerprint/1.0/IFingerprint.h>
#include <hidl/LegacySupport.h>
#include "Fingerprint.h"
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::hardware::fingerprint::V1_0::implementation::Fingerprint;
int main() {
configureRpcThreadpool(4, true);
Fingerprint fingerprint;
auto status = fingerprint.registerAsService();
CHECK_EQ(status, android::OK) << "Failed to register fingerprint HAL implementation";
joinRpcThreadpool();
return 0; // joinRpcThreadpool shouldn't exit
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
這是繫結式HAL的service.cpp寫法。
//Android.bp
cc_binary {
vendor: true,
relative_install_path: "hw",
name: "android.hardware.fingerprint@1.0-service",
init_rc: ["android.hardware.fingerprint@1.0-service.rc"],
srcs: [
"Fingerprint.cpp",
"service.cpp",
],
cflags: [
"-Wall",
],
shared_libs: [
"liblog",
"libutils",
"libhidltransport",
"android.hardware.fingerprint@1.0",
"libdl",
"libhardware",
"libhidlbase",
"libbase",
],
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
好了,主要程式碼算是寫完了。
之後還需要修改一下配置檔案,使我們寫的hal能使用。
修改系統配置
編寫完上面的檔案,如果直接呼叫介面,會出現如下錯誤提示:
3004.17> 05-09 16:47:40.519 2909 2909 I example.com.fingerprinttest: Looking for service android.hardware.fingerprint@1.0::IFingerprint/default
3004.17> 05-09 16:47:40.521 473 473 W /system/bin/hwservicemanager: getTransport: Cannot find entry android.hardware.fingerprint@1.0::IFingerprint/default in either framework or device manifest.
3004.17> 05-09 16:47:40.521 701 1474 I AudioPolicyManagerCustom: FLAG None hence request for a primary output
3004.17> 05-09 16:47:40.522 2909 2909 E example.com.fingerprinttest: service android.hardware.fingerprint@1.0::IFingerprint declares transport method EMPTY but framework expects hwbinder.
3004.17> 05-09 16:47:40.522 2909 2909 D AndroidRuntime: Shutting down VM
3004.17> 05-09 16:47:40.523 701 1474 I AudioPolicyManagerCustom: FLAG None hence request for a primary output
3004.17> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: FATAL EXCEPTION: main
3004.17> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: Process: uidq0161.example.com.fingerprinttest, PID: 2909
3004.17> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: java.util.NoSuchElementException
3004.18> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: at android.os.HwBinder.getService(Native Method)
3004.18> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: at android.hardware.fingerprint.V1_0.IFingerprint.getService(IFingerprint.java:44)
3004.18> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: at example.com.fingerprinttest.MainActivity.onClick(MainActivity.java:40)
3004.18> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: at android.view.View.performClick(View.java:6294)
3004.18> 05-09 16:47:40.523 2909 2909 E AndroidRuntime: at android.view.View$PerformClick.run(View.java:24774)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
這就是在提示我們需要修改manifest.xml檔案,增加以下程式碼才能使應用呼叫該hal的時候不會出錯。
// android/device/qcom/msm8996/manifest.xml
<halformat="hidl">
<name>android.hardware.fingerprint</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IFingerprint</name>
<instance>default</instance>
</interface>
</hal>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
另外需要在android/device/qcom/msm8996/msm8996.mk檔案
增加android.hardware.fingerpr[email protected]這樣開機就會啟動[email protected]
msm8996是高通自己修改的檔名,不同廠商命名會不同,但是修改還是一樣的。
除了上面的錯誤,我們可能還會如下的報錯:
Line 254: [ 42.716694] init: computing context for service 'fingerprint_hal_service'
Line 255: [ 42.717950] init: service fingerprint_hal_service does not have a SELinux domain defined
- 1
- 2
這是因為fingerprint_hal_service未啟動,原因為SELinux保護機制使得HWService未啟動。
因為Google 要求init 啟動service 時,都要進行SELinux domain切換,即從init domain切換到另外的domain。這個是從安全方面考慮的,預設init domain 的SELinux 許可權很大,可以做很多危險行為,比如mount image, kill process 等等。如果普通service 使用 init domain,就增大了可攻擊的安全面。
Google 在CTS 中有對這一項進行檢查, CTS fail of android.security.cts.SELinuxDomainTest # testInitDomain
通常情況下,如果init 啟動的是一個可快速執行完的oneshot 的service, 即非deamon 程式, “一閃而過” 的話,可以不進行domain 切換. 此類CTS 檢測不到. 如果init 啟動的是常駐記憶體的deamon service, 那麼一定要進行domain 切換.(L0/L1 版版本)
但在M版本上,Google 增強了這塊的約束,通過使用neverallow init { file_type fs_type}:file execure_no_trans;嚴格限制了init 啟動service 都必須進行domain 切換,否則service 無法啟動!!!
解決方法如下:
修改 android/device/qcom/sepolicy/msm8996/file_contexts,增加如下程式碼:
/(vendor|system/vendor)/bin/hw/android.hardware.fingerprint@1.0-service u:object_r:hal_xiaoheifingerprint_default_exec:s0
- 1
上面的hal_xiaoheifingerprint_default是我們自己定義的,為什麼要加個xiaohei呢?因為如果不加就跟源生的衝突了。。
還需要android/device/qcom/sepolicy/msm8996/下增加hal_xiaoheifingerprint_default.te檔案,程式碼如下:
//hal_xiaoheifingerprint_default.te
type hal_xiaoheifingerprint_default, domain; //Set a new domain called hal_xiaoheifingerprint_default
type hal_xiaoheifingerprint_default_exec, exec_type, vendor_file_type, file_type; //Set your exec file type
init_daemon_domain(hal_xiaoheifingerprint_default) //Setup fordomain transition
hal_server_domain(hal_xiaoheifingerprint_default, hal_fingerprint); //Set your domainas server domainof hal_fingerprint in which define by AOSP already
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這樣hal的程式碼就完成啦!
編寫APP直接呼叫hal介面
編譯上面的hal檔案會產生HIDL的檔案,所以我們可以直接呼叫介面,而不用一定要像之前一樣寫個JNI出來給APP使用。寫一個JNI出來也是可以的,如果需要在JNI中加一些簡單邏輯可以寫個JNI,看需求而定。具體可參考收音機模組,路徑android/hardware/interfaces/broadcastradio,JNI在android/frameworks/base/services/core/jni/BroadcastRadio
直接拋程式碼了。
//MainActivity.java
package example.com.fingerprinttest;
import android.hardware.fingerprint.V1_0.FingerprintValue;
import android.hardware.fingerprint.V1_0.IFingerprint;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public classMainActivityextendsActivityimplementsView.OnClickListener {
Button btn_subscribe, btn_unsubscribe, btn_set;
IFingerprint fingerprintService;
FingerprintCallback fingerprintCallback = new FingerprintCallback();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
fingerprintService = IFingerprint.getService();//獲取service
} catch (RemoteException e) {
e.printStackTrace();
}
btn_subscribe = (Button) this.findViewById(R.id.btn_subscri