简介:
HAL层又称硬件抽象层,HAL层在Android体系中有着深远的意义,因为Android究竟是完
全开源还是完全不开源的秘密就在这一层·Google将硬件厂商的驱动程序放在这一层,正是因为
这一层的代码没有开源,所以Android被Linux家族删除.本章将详细介绍HAL层的基本知识,
为本书后面的驱动开发和移植打下坚实的基础
1、认识 HAL 层
HAL层(硬件抽象层)是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象
化。它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,这样就可以在多种平台上进行移植。从软、硬件测试的角度来看,软、硬件的测试工作都可分别基于硬件抽象层来完成,从此亻吏软、硬件测试工作的并行进行成为可能。HAL层的位置结构如冬6-1所示。
由图6-1可知,HAL的目的是为了把Android Framework(Android框架)与Linux Kernel(Linux
内核)隔离。让Android不至过度依赖Linux Kemel,从而让Android Framework的开发可在不
考虑驱动程序的前提下进行。HAL层主要包含GPS、Vibrator、Wi-Fi、Copybit、Audio、Camera、Lights、RiI、Overlay等模块。
1.1.1 HAL 层的发展
硬件抽象层大概分为一下 6种HAL:
- 上层软件
- 虚拟驱动,设置管理模块
- 内部通信Server
- 内部以太网
- 内部通信Client
- 用户接入口
定义硬件抽象层接口的代码具有一下5个特点:
- 硬件抽象层具有与硬件的密切相关性
- 硬件抽象层具有与操作系统无关性
- 接口定义的功能应该包含硬件或者系统所需硬件支持的所有功能
- 接口简单明了,太多接口函数会增加软件模拟的复杂性
- 具有可预测的接口设计有利于系统的软、硬件测试和集成。
在Android 源码中,HAL 主要被保存在下面的目录中:
- libhardware_legacy :过去的目录,采取链接库模块观念来架构
- libhardware: 新版的目录,被调整为用HAL stub 观念来架构
- ril: 是Radio 接口层
- msm7k : 和QUAL 平台相关的信息
接下来将对目前HAL的现况做一个简单的分析。目前,Android的HAL层仍旧散布在不同
的地方,如分为camera、Wi-Fi等,因此上述的目录并不包含所有的HAL程序代码。成熟前的
HAL架构如图6.2所示,现在的HAL架构如图6.3所示。
从现在HAL层的结构可以看出,当前的HAL stub模式是一种代理人(Proxy)的概念,虽然
stub仍以 *.so 档的形式存在,但是HAL己经将 *.so 档隐藏了。Stub向HAL提供了功能强大的操作
函数(Operations),而 runtime 则从 HAL获取特定模块(Stub)的函数,然后再回调这些操作函
数。这种以 lndirect Function CAII 模式的架构,让 HAL stub 变成了一种“包含”关系,也就是说,
在 HAL 中包含了许多 Stub(代理人)。Runtime只要说明moduleID(类型),就可以取得操作函数。
在当前的HAL模式中,Android定义了HAL层结构框架,这样,通过接口访问硬件时就形成了统
一的调用方式。
1.1.2 过去和现在的区别
为了使读者明白过去结构和现在结构的差别,接下来对HALlegacy和HAL做一个对比。
- HAL_legacy:这是过去HAL的模块,采用共享库形式,在编译时会调用到。由于采用functroncall形式调用,因此可被多个进程使用,但会被mapping到多个进程窄间中,造成浪费,同时需要考虑代码能否安全重入的问题(ThreadSafe)。
- HAL:这是新式的HAL,采用了HALmodule和HAL stub 结合形式。HALstub不是一个共
享库,在编译时上层只拥有访问HALstub的函数指针,并不需要HAL stub-在上层通过HAL
module提供的统一接口获取并操作HAL stub,所以,文件只会被映射到一个进程,而不会存
在重复映射和重入问题。
2.2 分析HAL 层源码
通过上节的学习,了解了Android中为了给用户提供统一的硬件接口和硬件形态,在Linux
Kemel和用户空间之间提供了一个HAL层,即硬件抽象层。这使得 Android 中的Framework只需要
关心HAL中的内容,而不用关心具体的硬件实现。本节将简单分析HAL层的代码,当然不是具体
驱动的代码,而是HAL层中的各个接口的代码。
2.2.1 分析 HAL moudle
在 HAL moudle 中主要分为如下3个结构:
- struct hw_module
- struct hw_modulemethods_t
- struct hw_device_t
上述 3 个结构的集成关系 如图 6-4 所示
上述3个抽象概念在文件hardware.c中进行了详细述。HAL模块的源代码保存在harware目
录中,对于不同的 hardware 的HAL,其对应的lib命名规则是id.variant.so,比如gralloc.msm7k.so,
表示其id是gralloc,msm7k是vanant。variant的取值范围是在该文件中的定义的vanant_keys对应
的值。
接下来分析 hardware.c 源码。
(1)函数hw_get_module()。此函数根据模块ID寻找硬件模块动态链接库的地址,然后调用load去打开动态链接库并从中获取硬件模块结构体地址。执行后首先得到的是根据固定的符号
HAL_MODULE_INFO_SYM寻找到 hw_module_t 结构体,然后在hw_moule_t中hw_module_methods_t 结构体成员函数提供的open结构打开相应的模块,并进行初始化。因为用户在调用 open() 时通常都会传 入一个指向 hw_device_t 指针的指针。这样,函数 open() 就将对模块的操作函数结构保存到hw_device_t 结构体中,用户通过它可以和模块进行交互。
函数hw_get_module() 的具体实现代码如下:
代码 /hardware/libhardware/hardware.c
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{``````(1) 通过配置变量 循环寻找一个模块
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {·······
(2)数组variant_keys 在上述函数中,需要用到数组variant_keys,因为HAL_VARIANT_KEYS_COUNT就是数组variant_keys的大小。定义此数组的代码如下:
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"
};
然后通过此数组,并使用如下代码获取操作权限:
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}在此,variant_keys[il对应有3个值,分别是trout、msm7k和ARMV6,
(3) 将路径和文件名保存到 path , 接下来将通过如下代码将路径和文件保存在path 中:
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);通过上述代码,把 HAL_LIBRARY_PATH/id.***.so 保存到 path 中,其中 **** 就是上面 variant_keys 中各个元素的值。
(4) 载入相应的库,并把他们的 HMI 不保存到 module 中,具体代码如下
/* 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);(5)打开相应的库并获得 hw_module_t 结构体,具体代码如下:
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;
#ifdef __ANDROID_VNDK__
const bool try_system = false;
#else
const bool try_system = true;
#endif/*
* 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
*/
if (try_system &&
strncmp(path, HAL_LIBRARY_PATH1, strlen(HAL_LIBRARY_PATH1)) == 0) {
/* If the library is in system partition, no need to check
* sphal namespace. Open it with dlopen.
*/
handle = dlopen(path, RTLD_NOW);
} else {
#if defined(__ANDROID_RECOVERY__)
handle = dlopen(path, RTLD_NOW);
#else
handle = android_load_sphal_library(path, RTLD_NOW);
#endif
}
if (handle == NULL) {
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 = (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 */
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, hmi, handle);
}*pHmi = hmi;
return status;
}
2.2.2 分析mokoid 工具
mokoid 工程中提供了一个LedTest示例程序,此示例是我国台湾Jollen的培训教程。了解这个
工程源码,对于理解Android层次结构、HAL编程方法都非常有意义。此工程文件码可以从网络
中获取,在Linux中的下载命令如下;
# svn checkout http://mokoid.googlecode.com/svn/trunk/mokoid-read-only
下载 mokoid 工程之后,目录结构如图6-5所示,
需要通过 JNI 来实现 Android 的HAL, JNI 是Java 程序可以调用C/C++ 写的动态链接库,所以HALkeyi shiyong C/C++语言编写,这样效率更高。在Android 下有以下两种访问HAL 的方式:
- Android 的app 直接通过 service 调用.so 格式的JNI : 此方法比较简单高效,但是不正规。
- 经过 Manager 调用 Service : 此方法实现起来比较复杂,但是更符合目前的Android 框架。在此方法中,在进程 LegManager 和 LedService (Java) 中需要通过进程通信的方式实现通信。
在 mokoid 工程中分别实现上述两种方法,下面将详细介绍这两种方法实现原理:
1.直接调用 Service 方法的实现代码
(1)HAL 层的实现代码
文件 hardware/modules/led/led.c 的实现代码如下:
#define LOG_TAG "MokoidLedStub"
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>#include <cutils/log.h>
#include <cutils/atomic.h>#include <mokoid/led.h>
/*****************************************************************************/
int led_device_close(struct hw_device_t* device)
{
struct led_control_device_t* ctx = (struct led_control_device_t*)device;
if (ctx) {
free(ctx);
}
return 0;
}int led_on(struct led_control_device_t *dev, int32_t led)
{
LOGI("LED Stub: set %d on.", led);return 0;
}int led_off(struct led_control_device_t *dev, int32_t led)
{
LOGI("LED Stub: set %d off.", led);return 0;
}static int led_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device)
{
struct led_control_device_t *dev;dev = (struct led_control_device_t *)malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = module;
dev->common.close = led_device_close;dev->set_on = led_on; // 实例化支持的操作
dev->set_off = led_off;*device = &dev->common; // 将实例化的 led_control_device_t 地址返回给 Jni 层
success:
return 0;
}static struct hw_module_methods_t led_module_methods = {
open: led_device_open
};const struct led_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: LED_HARDWARE_MODULE_ID,
name: "Sample LED Stub",
author: "The Mokoid Open Source Project",
methods: &led_module_methods,
}
/* supporting APIs go here */
};
(2) JNI 层的实例代码
文件 frameworks/base/service/jni/com_mokoid_server_LedService.cpp 的实现代码如下:
#define LOG_TAG "MokoidPlatform"
#include "utils/Log.h"#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>#include <jni.h>
#include <mokoid/led.h>// ----------------------------------------------------------------------------
struct led_control_device_t *sLedDevice = NULL;
static jboolean mokoid_setOn(JNIEnv* env, jobject thiz, jint led) {
LOGI("LedService JNI: mokoid_setOn() is invoked.");
if (sLedDevice == NULL) {
LOGI("LedService JNI: sLedDevice was not fetched correctly.");
return -1;
} else {
return sLedDevice->set_on(sLedDevice, led); // 调用 HAL 层的注册方法
}
}static jboolean mokoid_setOff(JNIEnv* env, jobject thiz, jint led) {
LOGI("LedService JNI: mokoid_setOff() is invoked.");
if (sLedDevice == NULL) {
LOGI("LedService JNI: sLedDevice was not fetched correctly.");
return -1;
} else {
return sLedDevice->set_on(sLedDevice, led); // 调用 HAL 层的注册方法
}
}/** helper APIs */
// JNI 通过 LED_HARDSOFTWARE_MODULE_ID 找到对应的stub
static inline int led_control_open(const struct hw_module_t* module,
struct led_control_device_t** device) {
return module->methods->open(module,
LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}static jboolean
mokoid_init(JNIEnv *env, jclass clazz)
{
led_module_t* module;if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
LOGI("LedService JNI: LED Stub found.");
if (led_control_open(&module->common, &sLedDevice) == 0) {
LOGI("LedService JNI: Got Stub operations.");
return 0;
}
}LOGE("LedService JNI: Get Stub operations failed.");
return -1;
}// ----------------------------------------------------------------------------
/*
* Array of methods.
*
* Each entry has three fields: the name of the method, the method
* signature, and a pointer to the native implementation.* JNI NativeMethod 是java 层的注册方法
*/
static const JNINativeMethod gMethods[] = {
{"_init", "()Z", // Framwork 层调用 _init 时触发
(void*)mokoid_init},
{ "_set_on", "(I)Z",
(void*)mokoid_setOn },
{ "_set_off", "(I)Z",
(void*)mokoid_setOff },
};static int registerMethods(JNIEnv* env) {
static const char* const kClassName =
"com/mokoid/server/LedService";
jclass clazz;/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
LOGE("Can't find class %s\n", kClassName);
return -1;
}/* register all the methods */
if (env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
{
LOGE("Failed registering methods for %s\n", kClassName);
return -1;
}/* fill out the rest of the ID cache */
return 0;
}// ----------------------------------------------------------------------------
/*
* This is called by the VM when the shared library is first loaded.
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);if (registerMethods(env) != 0) {
LOGE("ERROR: PlatformLibrary native registration failed\n");
goto bail;
}/* success -- return valid version number */
result = JNI_VERSION_1_4;bail:
return result;
}
(3) Service 的实现代码
这里的service 属于 Framwork 层,文件 framworks/base/service/java/com/mokoid/server/LedService.java 的实现代码:
package com.mokoid.server;
import android.util.Config;
import android.util.Log;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.IBinder;
import mokoid.hardware.ILedService;public final class LedService extends ILedService.Stub {
static {
System.load("/system/lib/libmokoid_runtime.so");
}public LedService() {
Log.i("LedService", "Go to get LED Stub...");
_init();
}/*
* Mokoid LED native methods.
*/
public boolean setOn(int led) {
Log.i("MokoidPlatform", "LED On");
return _set_on(led);
}public boolean setOff(int led) {
Log.i("MokoidPlatform", "LED Off");
return _set_off(led);
}private static native boolean _init();
private static native boolean _set_on(int led);
private static native boolean _set_off(int led);
}
(4) App 测试程序的实现代码
这里的测试程序属于 APP 层,文件 apps/LedClient/src/com/mokoid/LedClient/LedClient.java 的实现代码如下:
package com.mokoid.LedClient;
import com.mokoid.server.LedService;import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;public class LedClient extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);// Call an API on the library.
LedService ls = new LedService();
ls.setOn(1);
TextView tv = new TextView(this);
tv.setText("LED 0 is on.");
setContentView(tv);
}
}
2、通过 manager 调用 Service 的实现代码
(1) Manager 的实现代码
APP 通过 此 Manager 和 Service 实现通信功能,文件 framwork/base/core/java/mokoid/hardware/Ledmanager.java
package mokoid.hardware;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.util.Log;
import mokoid.hardware.ILedService;/**
* Class that lets you access the Mokoid LedService.
*/
public class LedManager
{
private static final String TAG = "LedManager";
private ILedService mLedService;public LedManager() {
mLedService = ILedService.Stub.asInterface(
ServiceManager.getService("led"));if (mLedService != null) {
Log.i(TAG, "The LedManager object is ready.");
}
}public boolean LedOn(int n) {
boolean result = false;try {
result = mLedService.setOn(n);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in LedManager.LedOn:", e);
}
return result;
}public boolean LedOff(int n) {
boolean result = false;try {
result = mLedService.setOff(n);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in LedManager.LedOff:", e);
}
return result;
}
}
因为 LedService 和 Ledmanager 分别不属于不同的进程,所以在此需要考虑不同进程间的通信问题,此时,在 manager 中可以增加一个aidl 文件来描述通信接口,文件
framworks/base/core/java/mokoid/hardware/ILedService.aidl 的代码实现如下:
package mokoid.hardware;
interface ILedService
{
boolean setOn(int led);
boolean setOff(int led);
}
(2) SystemService 的实现代码
此处的 SystemServer 属于 App 层,文件 apps /LedTest/src/com/mokoid/LedTest/Ledsystem/Server.java 的代码如下:
package com.mokoid.LedTest;
import com.mokoid.server.LedService;
import android.os.IBinder;
import android.os.ServiceManager;
import android.util.Log;
import android.app.Service;
import android.content.Context;
import android.content.Intent;public class LedSystemServer extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}public void onStart(Intent intent, int startId) {
Log.i("LedSystemServer", "Start LedService...");/* Please also see SystemServer.java for your interests. */
LedService ls = new LedService();try {
ServiceManager.addService("led", ls);
} catch (RuntimeException e) {
Log.e("LedSystemServer", "Start LedService failed.");
}
}
}
3.App 测试程序
这里的app测试程序属于APP 层,文件 mokoid-read-only/apps/LedTest/src/com/mokoid/LedTest/LedTest.java 的实现代码如下:
package com.mokoid.LedTest;
import mokoid.hardware.LedManager;
import com.mokoid.server.LedService;import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Button;
import android.content.Intent;
import android.view.View;public class LedTest extends Activity implements View.OnClickListener {
private LedManager mLedManager = null;@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);// Start LedService in a seperated process.
startService(new Intent("com.mokoid.systemserver"));// Just for testing. !! PLEASE DON't DO THIS !!
//LedService ls = new LedService();Button btn = new Button(this);
btn.setText("Click to turn LED 1 On");
btn.setOnClickListener(this);setContentView(btn);
}public void onClick(View v) {
// Get LedManager.
if (mLedManager == null) {
Log.i("LedTest", "Creat a new LedManager object.");
mLedManager = new LedManager();
}if (mLedManager != null) {
Log.i("LedTest", "Got LedManager object.");
}/** Call methods in LedService via proxy object
* which is provided by LedManager.
*/
mLedManager.LedOn(1);TextView tv = new TextView(this);
tv.setText("LED 1 is On.");
setContentView(tv);
}
}
4、使用HAL 的方法 总结
(1)Native code 通过hw_get_module 调用获取 HAL stub.
hw_get_module (LED_HARDWARE_MODULE_ID,(const hw_module_t** )&module)
(2) 通过继承 hw_module_methods_t 的 callback 拉打开设备。
module->methods->open(module,LED_HARDWARE_MODULE_ID,(struct hw_device_t**)device);
(3) 通过继承 hw_device_t 的callback (回访) 来控制设备
sLedDevice -> set_on(sLedDevice,led);
sLedDevice-> set_off(sLedDevice,led);
4.1. 编写 HAL stub 的方法
编写HAL stub 的基本流程入下图所示。
1)自定义 HAL 结构体,编写 头文件 led.h 和 hardware/hardware.h 主要代码如下:
struct ledmodulet {
struct hw_module_t common;
}
struct led_control_device_t {
struct hw_device_t common;
int fd ; // 文件描述符 的 LED 装置
int (*set_on) (struct led_control_device_t *dev, int32_t led); // 支持控制的 api
int (*set_off) (struct led_control_device_t *dev,int32_t led);
}
(2) 编写 文件 led.c 实现 HAL stub 注册功能
(3)设置 led_module_methods 继承于 hw_module_methods_t,并实现对 open() 方法的回访,
struct hw_module_methods_t led_module_methods = {
open : led_device_open
};
(4) 使用 HAL_MODULE_INFO_SYM 实例,led_module_t, 注意 这个名称不可修改,
const struct led_module_t HAL_MODULE_INFO_SYM = {
common : {
tag : HARDWARE_MODULE_TAG,
version_major:1,
version_minor:0,
id:LED_HARDWARE_MODULE_ID,
name:"Sample LED Stub",
author: "The Mokoid Open Source Project",
methods: &led_module_methods,
}
// 支持 api
};
(5) open() 是一个必须实现的回调 API ,用于负责 申请 结构体空间并填充信息,还可以注册具体操作API 接口, 并打开 Linux 驱动。但是系因为存在多重继承关系,所以只需要对结构体hw_device_t 对象申请空间即可。
static int led_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device)
{
struct led_control_device_t *dev;dev = (struct led_control_device_t *)malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = module;
dev->common.close = led_device_close;dev->set_on = led_on;
dev->set_off = led_off;*device = &dev->common;
// 初始化硬件接口
dev->fd = open(LED_DEVICE,o_RDONLY);
if (dev->fd < 0 ) {
return -1;
}
led_off(dev,LED_C608);
led_off(dev,LED_C609);
success:
return 0;
}
(6) 填充具体 API 操作,具体代码如下:
int led_on (struct led_control_device_t *dev,int32_t led) {
int fd;
LOGI("LED Stub: set %d on.",led);
fd = dev->fd;
switch(led) {
case LED_C608:
ioctl(fd,1,&led);
break;
case LED_C609:
ioctl(fd,1,&led)
break;
default:
return -1;
}
return 0;
}
int led_off (struct led_control_device_t *dev,int32_t led) {
int fd;
LOGI("LED Stub : set %d off.",led);
fd = dev-> fd;
switch(led) {
case LED_C608:
ioctl(fd,2,&led);
break;
case LED_C609:
ioctl(fd,2,&led);
break;
default:
return -1;
}
return 0;
}
文章摘自:
《Android 底层接口和驱动开发技术详解》
版权声明:本文为CSDN博主「放大的EZ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_27061049/article/details/122481220
暂无评论