diff --git a/zh-cn/device-dev/driver/driver-peripherals-pinauth-des.md b/zh-cn/device-dev/driver/driver-peripherals-pinauth-des.md new file mode 100644 index 0000000000000000000000000000000000000000..277b3d8a8a0a2caa47e58344bda0bf44ddbc7795 --- /dev/null +++ b/zh-cn/device-dev/driver/driver-peripherals-pinauth-des.md @@ -0,0 +1,565 @@ +# Pin_auth + +## 概述 + +### 功能简介 + +口令认证是端侧设备不可或缺的一部分,为设备提供一种用户认证能力,可应用于设备解锁、支付、应用登录等身份认证场景。用户注册口令后,口令认证模块就可为设备提供密码解锁的功能,保证设备的安全使用。口令识别的整体架构如图1。 + +基于HDF(Hardware Driver Foundation)驱动框架开发的pin_auth驱动,pin_auth驱动模型屏蔽硬件差异,为上层用户IAM子系统基础框架和口令认证SA提供稳定的口令认证基础能力,包括口令认证执行器列表查询、执行器信息查询、指定模板防暴信息查询、用户认证和执行器间的模板信息对账,以及口令的录入、认证、删除。 + +**图1** 口令认证架构图 + +![image](figures/口令认证架构图.png "口令认证架构图") + +### 基本概念 +用户认证框架与各个基础认证服务(包含口令认证、人脸识别等)组成的身份认证系统,支持用户认证凭据设置、删除、认证等基础功能。 + +- 执行器 + + 执行器是能够提供数据采集、处理、存储及比对能力的模块,各基础认证服务提供执行器能力,被身份认证框架调度完成各项基础能力。 + +- 执行器安全等级 + + 执行器提供能力时运行环境所达到的安全级别,如操作运行在无访问控制安全等级低于操作运行硬件可信执行 环境的安全等级。 + +- 执行器角色 + + - ​ 全功能执行器:执行器可独立处理一次凭据注册和身份认证请求,即可提供用户认证数据采集、处理、储存及比对能力。 + + - ​ 采集器:执行器提供用户认证时的数据采集能力,需要和认证期配合完成用户认证。 + + - ​ 认证器:认证器提供用户认证是时数据处理,读取存储凭据模板信息并完成比对。 + +- 执行器类型 + + 同一种身份认证类型的不同认证方式会产生认证算法差异,设备器件差异也会导致算法差异,执行器根据支持的算法类型差异或对接的器件差异,会定义不同的执行器类型。 + +- 用户认证框架公钥 & 执行器公钥 + + 用户身份认证处理需要保证用户数据安全以及认证结果的准确性,用户认证框架于基础认证服务间的关键交互信息需要做数据完整性保护,各基础认证服务将提供的执行器能力对接到用户认证框架时,需要交互各自的公钥,其中: + + - 执行器通过用户认证框架公钥校验调度指令的准确性,如锁定一个口令模板,这种情况导致无法使用口令认证功能,属于敏感操作,需要确保指令准确,才可处理。 + + - 执行器公钥可被用户认证框架用于校验认证结果的准确性,同时用于执行器交互认证时的校验交互信息的完整性。 + + +- 口令认证凭据模板 + + 认证凭据是在用户设置认证凭据时由认证服务产生并存储,每个模板有一个ID。用于索引模板信息文件,再认证时读取模板信息并用于与当次认证过程中产生的认证数据做对比,完成身份认证。 + +- 执行器对账 + + 用户认证框架统一管理用户身份和凭据ID的映射关系,执行器对接到用户认证框架时,会读取用户身份认证框架内保存的该执行器的模板ID列表,执行器需要与自己维护的模板ID列表进行比对,并删除冗余信息。 + +### 运作机制 + +Pin_auth驱动的主要工作是为上层用户认证框架和Pin_auth服务提供稳定的口令认证的基础能力,保证口令认证的功能可以正常运行。开发者可基于HDF框架对不同芯片进行各自驱动的开发以及HDI层接口的调用。 + +**图1** Pin_auth服务和pin_auth驱动接口 + +![image](figures/pin_auth服务与驱动交互.png "pin_auth服务与驱动交互") + +### 约束与限制 +口令认证的实现需要在TEE安全环境中实现,口令凭据等数据的保密信息需要在安全环境中存储。 +## 开发指导 + +### 场景介绍 +Pin_auth驱动的主要工作是为上层用户认证框架和Pin_auth服务提供稳定的口令认证基础能力,保证设备上口令认证功能可以正常运行。 + +### 接口说明 + +**表1** 接口功能介绍 + +| 接口名 | 功能介绍 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| GetExecutorList(std::vector>& executorList) | 获取执行器列表。 | +| GetExecutorInfo(ExecutorInfo& info) | 获取执行器信息。 | +| GetTemplateInfo(uint64_t templateId, TemplateInfo& info) | 获取指定templateId的模板信息。 | +| OnRegisterFinish(const std::vector& templateIdList,
const std::vector& frameworkPublicKey,
const std::vector& extraInfo) | 执行器注册成功后,获取用户认证框架的公钥信息;获取用户认证框架的template 列表用于对账。 | +| OnSetData(uint64_t scheduleId, uint64_t authSubType,
const std::vector &data) | 用于回调传pin码认证的子类型和脱敏数据。 | +| Enroll(uint64_t scheduleId, const std::vector& extraInfo,
const sptr& callbackObj) | pin码录入操作。 | +| Authenticate(uint64_t scheduleId, uint64_t templateId, const std::vector& extraInfo, const sptr& callbackObj) | pin码认证操作。 | +| Delete(uint64_t templateId) | 删除pin码模板。 | +| Cancel(uint64_t scheduleId) | 通过scheduleId取消指定操作。 | +| SendCommand(int32_t commandId, const std::vector& extraInfo,
const sptr& callbackObj) | pin码预留接口。 | + +**表2** 回调函数介绍 + +| 接口名 | 功能介绍 | +| ------------------------------------------------------------ | -------------------- | +| IExecutorCallback::OnResult(int32_t code, const std::vector& extraInfo) | 返回操作的最终结果。 | +| IExecutorCallback::OnGetData(uint64_t scheduleId, const std::vector& salt,
uint64_t authSubType)| 返回获取pin码数据信息。 | + +### 开发步骤 + +以下将基于pin_auth驱动框架,并以RK3568平台为例,介绍相关驱动开发的具体步骤。 + +1. 基于HDF驱动框架,按照驱动Driver Entry程序,完成pin_auth驱动开发,主要由Bind、Init、Release、Dispatch函数接口实现。 + + ```c++ + // 通过自定义的HdfPinAuthInterfaceHost对象包含IoService对象和真正的HDI Service实现PinAuthInterfaceService对象 + struct HdfPinAuthInterfaceHost { + struct IDeviceIoService ioService; + OHOS::sptr stub; + }; + + // 服务接口调用响应接口 + static int32_t PinAuthInterfaceDriverDispatch(struct HdfDeviceIoClient *client, int cmdId, struct HdfSBuf *data, struct HdfSBuf *reply) + { + IAM_LOGI("start"); + auto *hdfPinAuthInterfaceHost = CONTAINER_OF(client->device->service, + struct HdfPinAuthInterfaceHost, ioService); + + OHOS::MessageParcel *dataParcel = nullptr; + OHOS::MessageParcel *replyParcel = nullptr; + OHOS::MessageOption option; + + if (SbufToParcel(data, &dataParcel) != HDF_SUCCESS) { + IAM_LOGE("%{public}s:invalid data sbuf object to dispatch", __func__); + return HDF_ERR_INVALID_PARAM; + } + if (SbufToParcel(reply, &replyParcel) != HDF_SUCCESS) { + IAM_LOGE("%{public}s:invalid reply sbuf object to dispatch", __func__); + return HDF_ERR_INVALID_PARAM; + } + + return hdfPinAuthInterfaceHost->stub->SendRequest(cmdId, *dataParcel, *replyParcel, option); + } + + // 初始化接口 + static int HdfPinAuthInterfaceDriverInit(struct HdfDeviceObject *deviceObject) + { + IAM_LOGI("start"); + std::shared_ptr pinHdi = + OHOS::UserIAM::Common::MakeShared(); + constexpr uint32_t SUCCESS = 0; + if (pinHdi == nullptr || pinHdi->Init() != SUCCESS) { + IAM_LOGE("Pin hal init failed"); + return HDF_FAILURE; + } + return HDF_SUCCESS; + } + + // PinAuth驱动对外提供的服务绑定到HDF框架 + static int HdfPinAuthInterfaceDriverBind(struct HdfDeviceObject *deviceObject) + { + IAM_LOGI("start"); + auto *hdfPinAuthInterfaceHost = new (std::nothrow) HdfPinAuthInterfaceHost; + if (hdfPinAuthInterfaceHost == nullptr) { + IAM_LOGE("%{public}s: failed to create create HdfPinAuthInterfaceHost object", __func__); + return HDF_FAILURE; + } + + hdfPinAuthInterfaceHost->ioService.Dispatch = PinAuthInterfaceDriverDispatch; + hdfPinAuthInterfaceHost->ioService.Open = NULL; + hdfPinAuthInterfaceHost->ioService.Release = NULL; + + auto serviceImpl = IPinAuthInterface::Get(true); + if (serviceImpl == nullptr) { + IAM_LOGE("%{public}s: failed to get of implement service", __func__); + return HDF_FAILURE; + } + + hdfPinAuthInterfaceHost->stub = OHOS::HDI::ObjectCollector::GetInstance().GetOrNewObject(serviceImpl, + IPinAuthInterface::GetDescriptor()); + if (hdfPinAuthInterfaceHost->stub == nullptr) { + IAM_LOGE("%{public}s: failed to get stub object", __func__); + return HDF_FAILURE; + } + + deviceObject->service = &hdfPinAuthInterfaceHost->ioService; + IAM_LOGI("success"); + return HDF_SUCCESS; + } + + // 释放PinAuth驱动中的资源 + static void HdfPinAuthInterfaceDriverRelease(struct HdfDeviceObject *deviceObject) + { + IAM_LOGI("start"); + auto *hdfPinAuthInterfaceHost = CONTAINER_OF(deviceObject->service, + struct HdfPinAuthInterfaceHost, ioService); + delete hdfPinAuthInterfaceHost; + IAM_LOGI("success"); + } + + static struct HdfDriverEntry g_pinAuthInterfaceDriverEntry = { + .moduleVersion = 1, + .moduleName = "pinauth_interface_service", + .Bind = HdfPinAuthInterfaceDriverBind, + .Init = HdfPinAuthInterfaceDriverInit, + .Release = HdfPinAuthInterfaceDriverRelease, + }; + + // 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出 + HDF_INIT(g_pinauthinterfaceDriverEntry); + ``` + + + +1. 完成获取执行器列表接口实现,向框架注册执行器。 + + ```c++ + // 执行器实现类 + class ExecutorImpl : public IExecutor, public NoCopyable { + public: + explicit ExecutorImpl(std::shared_ptr pinHdi); + virtual ~ExecutorImpl() {} + int32_t GetExecutorInfo(ExecutorInfo &info) override; + int32_t GetTemplateInfo(uint64_t templateId, TemplateInfo &info) override; + int32_t OnRegisterFinish(const std::vector &templateIdList, + const std::vector &frameworkPublicKey, const std::vector &extraInfo) override; + int32_t OnSetData(uint64_t scheduleId, uint64_t authSubType, const std::vector &data) override; + int32_t Enroll(uint64_t scheduleId, const std::vector &extraInfo, + const sptr &callbackObj) override; + int32_t Authenticate(uint64_t scheduleId, uint64_t templateId, const std::vector &extraInfo, + const sptr &callbackObj) override; + int32_t Delete(uint64_t templateId) override; + int32_t Cancel(uint64_t scheduleId) override; + int32_t SendCommand(int32_t commandId, const std::vector &extraInfo, + const sptr &callbackObj) override; + + private: + class ScheduleMap { + public: + uint32_t AddScheduleInfo(const uint64_t scheduleId, const uint32_t commandId, + const sptr callback, const uint64_t templateId, const std::vector salt); + uint32_t GetScheduleInfo(const uint64_t scheduleId, uint32_t &commandId, sptr &callback, + uint64_t &templateId, std::vector &salt); + uint32_t DeleteScheduleId(const uint64_t scheduleId); + + private: + struct ScheduleInfo { + uint32_t commandId; + sptr callback; + uint64_t templateId; + std::vector salt; + }; + + std::mutex mutex_; + std::map scheduleInfo_; + }; + + private: + uint32_t NewSalt(std::vector &salt); + void CallError(const sptr &callbackObj, const uint32_t errorCode); + std::shared_ptr pinHdi_; + ScheduleMap scheduleMap_; + }; + + // 获取执行器列表实现,创建执行器 (仅作示例) + int32_t PinAuthInterfaceService::GetExecutorList(std::vector> &executorList) + { + IAM_LOGI("start"); + std::shared_ptr pinHdi = + OHOS::UserIAM::Common::MakeShared(); + if (pinHdi == nullptr) { + IAM_LOGE("Generate pinHdi failed"); + return HDF_FAILURE; + } + sptr executor = new (std::nothrow) ExecutorImpl(pinHdi); + if (executor == nullptr) { + IAM_LOGE("Generate executor failed"); + return HDF_FAILURE; + } + executorList.push_back(executor); + IAM_LOGI("end"); + return HDF_SUCCESS; + } + ``` + + + +1. 完成执行器每个功能接口实现。 + + ```c++ + // 实现获取执行器信息接口(仅作示例) + int32_t ExecutorImpl::GetExecutorInfo(ExecutorInfo &info) + { + IAM_LOGI("start"); + constexpr unsigned short SENSOR_ID = 1; + info.sensorId = SENSOR_ID; + info.executorType = EXECUTOR_TYPE; + info.executorRole = ExecutorRole::ALL_IN_ONE; + info.authType = AuthType::PIN; + if (pinHdi_ == nullptr) { + IAM_LOGE("pinHdi_ is nullptr"); + return HDF_FAILURE; + } + uint32_t eslRet = 0; + int32_t result = pinHdi_->GetExecutorInfo(info.publicKey, eslRet); + if (result != SUCCESS) { + IAM_LOGE("Get ExecutorInfo failed, fail code : %{public}d", result); + return result; + } + info.esl = static_cast(eslRet); + + return HDF_SUCCESS; + } + + // 实现获取指定templateId的模板信息接口 + int32_t ExecutorImpl::GetTemplateInfo(uint64_t templateId, TemplateInfo &info) + { + IAM_LOGI("start"); + if (pinHdi_ == nullptr) { + IAM_LOGE("pinHdi_ is nullptr"); + return HDF_FAILURE; + } + OHOS::UserIAM::PinAuth::PinCredentialInfo infoRet = {}; + int32_t result = pinHdi_->QueryPinInfo(templateId, infoRet); + if (result != SUCCESS) { + IAM_LOGE("Get TemplateInfo failed, fail code : %{public}d", result); + return result; + } + /* subType is stored in extraInfo */ + info.extraInfo.resize(infoRet.subType); + if (memcpy_s(&(info.extraInfo[0]), sizeof(infoRet.subType), &(infoRet.subType), sizeof(infoRet.subType)) != EOK) { + IAM_LOGE("copy subType to extraInfo fail!"); + return HDF_FAILURE; + } + + info.executorType = EXECUTOR_TYPE; + info.remainTimes = infoRet.remainTimes; + info.freezingTime = infoRet.freezingTime; + + return HDF_SUCCESS; + } + + // 实现执行器注册成功后,获取用户认证框架的公钥信息、获取用户认证框架的template 列表接口,将公钥信息保持,template 列表用于和本地的template做对账 + int32_t ExecutorImpl::OnRegisterFinish(const std::vector &templateIdList, + const std::vector &frameworkPublicKey, const std::vector &extraInfo) + { + IAM_LOGI("start"); + static_cast(frameworkPublicKey); + static_cast(extraInfo); + if (pinHdi_ == nullptr) { + IAM_LOGE("pinHdi_ is nullptr"); + return HDF_FAILURE; + } + int32_t result = pinHdi_->VerifyTemplateData(templateIdList); + if (result != SUCCESS) { + IAM_LOGE("Verify templateData failed"); + return result; + } + + return HDF_SUCCESS; + } + + // 实现口令录入接口 + int32_t ExecutorImpl::Enroll(uint64_t scheduleId, const std::vector &extraInfo, + const sptr &callbackObj) + { + IAM_LOGI("start"); + if (callbackObj == nullptr) { + IAM_LOGE("callbackObj is nullptr"); + return HDF_FAILURE; + } + static_cast(extraInfo); + std::vector salt; + if (NewSalt(salt) != HDF_SUCCESS) { + IAM_LOGE("new salt failed"); + CallError(callbackObj, HDF_FAILURE); + return HDF_FAILURE; + } + int32_t result = scheduleMap_.AddScheduleInfo(scheduleId, ENROLL_PIN, callbackObj, 0, salt); + if (result != HDF_SUCCESS) { + IAM_LOGE("Add scheduleInfo failed, fail code : %{public}d", result); + CallError(callbackObj, HDF_FAILURE); + return result; + } + result = callbackObj->OnGetData(scheduleId, salt, 0); + if (result != SUCCESS) { + IAM_LOGE("Enroll Pin failed, fail code : %{public}d", result); + // If the enroll fails, delete scheduleId of scheduleMap + if (scheduleMap_.DeleteScheduleId(scheduleId) != HDF_SUCCESS) { + IAM_LOGI("delete scheduleId failed"); + } + return result; + } + + return HDF_SUCCESS; + } + + //实现回调数据获取的接口 + int32_t ExecutorImpl::OnSetData(uint64_t scheduleId, uint64_t authSubType, const std::vector &data) + { + IAM_LOGI("start"); + if (pinHdi_ == nullptr) { + IAM_LOGE("pinHdi_ is nullptr"); + return HDF_FAILURE; + } + std::vector resultTlv; + int32_t result = SUCCESS; + constexpr uint32_t INVALID_ID = 2; + uint32_t commandId = INVALID_ID; + sptr callback = nullptr; + uint64_t templateId = 0; + std::vector salt(0, 0); + if (scheduleMap_.GetScheduleInfo(scheduleId, commandId, callback, templateId, salt) != HDF_SUCCESS) { + IAM_LOGE("Get ScheduleInfo failed, fail code : %{public}d", result); + return HDF_FAILURE; + } + switch (commandId) { + case ENROLL_PIN: + result = pinHdi_->EnrollPin(scheduleId, authSubType, salt, data, resultTlv); + if (result != SUCCESS) { + IAM_LOGE("Enroll Pin failed, fail code : %{public}d", result); + } + break; + case AUTH_PIN: + result = pinHdi_->AuthPin(scheduleId, templateId, data, resultTlv); + if (result != SUCCESS) { + IAM_LOGE("Auth Pin failed, fail code : %{public}d", result); + } + break; + default: + IAM_LOGE("Error commandId"); + } + + if (callback->OnResult(result, resultTlv) != SUCCESS) { + IAM_LOGE("callbackObj Pin failed"); + } + // Delete scheduleId from the scheduleMap_ when the enroll and authentication are successful + if (scheduleMap_.DeleteScheduleId(scheduleId) != HDF_SUCCESS) { + IAM_LOGI("delete scheduleId failed"); + } + + return HDF_SUCCESS; + } + // 实现口令认证接口 + int32_t ExecutorImpl::Authenticate(uint64_t scheduleId, uint64_t templateId, const std::vector &extraInfo, + const sptr &callbackObj) + { + IAM_LOGI("start"); + if (callbackObj == nullptr) { + IAM_LOGE("callbackObj is nullptr"); + return HDF_FAILURE; + } + if (pinHdi_ == nullptr) { + IAM_LOGE("pinHdi_ is nullptr"); + CallError(callbackObj, HDF_FAILURE); + return HDF_FAILURE; + } + static_cast(extraInfo); + std::vector salt; + int32_t result = pinHdi_->GetSalt(templateId, salt); + if (result != SUCCESS) { + IAM_LOGE("get salt failed, fail code : %{public}d", result); + CallError(callbackObj, HDF_FAILURE); + return result; + } + result = scheduleMap_.AddScheduleInfo(scheduleId, AUTH_PIN, callbackObj, templateId, salt); + if (result != HDF_SUCCESS) { + IAM_LOGE("Add scheduleInfo failed, fail code : %{public}d", result); + CallError(callbackObj, HDF_FAILURE); + return result; + } + result = callbackObj->OnGetData(scheduleId, salt, 0); + if (result != SUCCESS) { + IAM_LOGE("Authenticate Pin failed, fail code : %{public}d", result); + // If the authentication fails, delete scheduleId of scheduleMap + if (scheduleMap_.DeleteScheduleId(scheduleId) != HDF_SUCCESS) { + IAM_LOGI("delete scheduleId failed"); + } + return result; + } + + return HDF_SUCCESS; + } + + // 实现删除口令模板接口 + int32_t ExecutorImpl::Delete(uint64_t templateId) + { + IAM_LOGI("start"); + if (pinHdi_ == nullptr) { + IAM_LOGE("pinHdi_ is nullptr"); + return HDF_FAILURE; + } + int32_t result = pinHdi_->DeleteTemplate(templateId); + if (result != SUCCESS) { + IAM_LOGE("Verify templateData failed, fail code : %{public}d", result); + return result; + } + + return HDF_SUCCESS; + } + + // 实现通过scheduleId取消指定操作接口 + int32_t ExecutorImpl::Cancel(uint64_t scheduleId) + { + IAM_LOGI("start"); + if (scheduleMap_.DeleteScheduleId(scheduleId) != HDF_SUCCESS) { + IAM_LOGE("scheduleId is not found"); + return HDF_FAILURE; + } + return HDF_SUCCESS; + } + + // 口令预留接口 + int32_t ExecutorImpl::SendCommand(int32_t commandId, const std::vector &extraInfo, + const sptr &callbackObj) + { + IAM_LOGI("Extension interface, temporarily useless"); + static_cast(commandId); + static_cast(extraInfo); + static_cast(callbackObj); + return HDF_SUCCESS; + } + ``` + + +### 调测验证 +驱动开发完成后,通过[用户认证 API接口](../../application-dev/reference/apis/js-apis-useriam-userauth.md)开发JS应用,基于RK3568平台验证。认证和取消功能验证的JS测试代码如下: + +```js +// API version 8 +import userIAM_userAuth from '@ohos.userIAM.userAuth'; +let auth = new userIAM_userAuth.UserAuth(); + +export default { + getVersion() { + console.info("start get version"); + let version = this.auth.getVersion(); + console.info("auth version = " + version); + }, + + startAuth() { + console.info("start auth"); + this.auth.auth(null, userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.AuthTrustLevel.ATL3, { + onResult: (result, extraInfo) => { + try { + console.info("auth onResult result = " + result); + console.info("auth onResult extraInfo = " + JSON.stringify(extraInfo)); + if (result == 'SUCCESS') { + // 此处添加认证成功逻辑 + } else { + // 此处添加认证失败逻辑 + } + } catch (e) { + console.info("auth onResult error = " + e); + } + } + }); + }, + + cancelAuth() { + console.info("start cancel auth"); + // contextId通过auth接口获取 + let contextId = auth.auth(null, userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.AuthTrustLevel.ATL3, { + onResult: (result, extraInfo) => { + console.info("auth onResult result = " + result); + }, + + onAcquireInfo: (module, acquire, extraInfo) => { + console.info("auth onAcquireInfo module = " + module); + } + }); + let cancelCode = this.auth.cancel(contextId); + if (cancelCode == userIAM_userAuth.Result.SUCCESS) { + console.info("cancel auth success"); + } else { + console.error("cancel auth fail"); + } + } +} +``` diff --git "a/zh-cn/device-dev/driver/figures/pin_auth\346\234\215\345\212\241\344\270\216\351\251\261\345\212\250\344\272\244\344\272\222.png" "b/zh-cn/device-dev/driver/figures/pin_auth\346\234\215\345\212\241\344\270\216\351\251\261\345\212\250\344\272\244\344\272\222.png" new file mode 100644 index 0000000000000000000000000000000000000000..2d806ab0bfde6fa32b54d31df698c3276c8c0978 Binary files /dev/null and "b/zh-cn/device-dev/driver/figures/pin_auth\346\234\215\345\212\241\344\270\216\351\251\261\345\212\250\344\272\244\344\272\222.png" differ diff --git "a/zh-cn/device-dev/driver/figures/\345\217\243\344\273\244\350\256\244\350\257\201\346\236\266\346\236\204\345\233\276.png" "b/zh-cn/device-dev/driver/figures/\345\217\243\344\273\244\350\256\244\350\257\201\346\236\266\346\236\204\345\233\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..32dadac7d8488783c6b6af7d4586f43621ce1881 Binary files /dev/null and "b/zh-cn/device-dev/driver/figures/\345\217\243\344\273\244\350\256\244\350\257\201\346\236\266\346\236\204\345\233\276.png" differ