/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "VersionedInterfaces.h" #include "Callbacks.h" #include "ExecutionBurstController.h" #include "Tracing.h" #include "Utils.h" #include <android-base/logging.h> #include <android-base/scopeguard.h> #include <android-base/thread_annotations.h> #include <functional> #include <type_traits> namespace android { namespace nn { // anonymous namespace namespace { using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; const Timing kBadTiming = {.timeOnDevice = UINT64_MAX, .timeInDriver = UINT64_MAX}; void sendFailureMessage(const sp<IPreparedModelCallback>& cb) { cb->notify(ErrorStatus::GENERAL_FAILURE, nullptr); } void sendFailureMessage(const sp<PreparedModelCallback>& cb) { sendFailureMessage(static_cast<sp<IPreparedModelCallback>>(cb)); } void sendFailureMessage(const sp<IExecutionCallback>& cb) { cb->notify(ErrorStatus::GENERAL_FAILURE); } void sendFailureMessage(const sp<ExecutionCallback>& cb) { sendFailureMessage(static_cast<sp<IExecutionCallback>>(cb)); } // This class is thread safe template <typename ICallback> class DeathHandler : public hardware::hidl_death_recipient { public: void serviceDied(uint64_t /*cookie*/, const wp<hidl::base::V1_0::IBase>& /*who*/) override { LOG(ERROR) << "DeathHandler::serviceDied -- service unexpectedly died!"; std::lock_guard<std::mutex> hold(mMutex); std::for_each(mCallbacks.begin(), mCallbacks.end(), [](const auto& cb) { sendFailureMessage(cb); }); } [[nodiscard]] base::ScopeGuard<std::function<void()>> protectCallback( const sp<ICallback>& callback) { registerCallback(callback); return ::android::base::make_scope_guard( [this, callback] { unregisterCallback(callback); }); } private : void registerCallback(const sp<ICallback>& callback) { std::lock_guard<std::mutex> hold(mMutex); mCallbacks.push_back(callback); } void unregisterCallback(const sp<ICallback>& callback) { std::lock_guard<std::mutex> hold(mMutex); mCallbacks.erase(std::remove(mCallbacks.begin(), mCallbacks.end(), callback), mCallbacks.end()); } std::mutex mMutex; std::vector<sp<ICallback>> mCallbacks GUARDED_BY(mMutex); }; } // anonymous namespace class IDeviceDeathHandler : public DeathHandler<IPreparedModelCallback> {}; class IPreparedModelDeathHandler : public DeathHandler<IExecutionCallback> {}; static std::shared_ptr<VersionedIPreparedModel> makeVersionedIPreparedModel( sp<V1_0::IPreparedModel> preparedModel) { // verify input if (!preparedModel) { LOG(ERROR) << "makeVersionedIPreparedModel -- passed invalid preparedModel object."; return nullptr; } // create death handler object sp<IPreparedModelDeathHandler> deathHandler = new (std::nothrow) IPreparedModelDeathHandler(); if (!deathHandler) { LOG(ERROR) << "makeVersionedIPreparedModel -- Failed to create IPreparedModelDeathHandler."; return nullptr; } // linkToDeath registers a callback that will be invoked on service death to // proactively handle service crashes. If the linkToDeath call fails, // asynchronous calls are susceptible to hangs if the service crashes before // providing the response. const Return<bool> ret = preparedModel->linkToDeath(deathHandler, 0); if (!ret.isOk() || ret != true) { LOG(ERROR) << "makeVersionedIPreparedModel -- Failed to register a death recipient for the " "IPreparedModel object."; return nullptr; } // return a valid VersionedIPreparedModel object return std::make_shared<VersionedIPreparedModel>(std::move(preparedModel), std::move(deathHandler)); } VersionedIPreparedModel::VersionedIPreparedModel(sp<V1_0::IPreparedModel> preparedModel, sp<IPreparedModelDeathHandler> deathHandler) : mPreparedModelV1_0(std::move(preparedModel)), mPreparedModelV1_2(V1_2::IPreparedModel::castFrom(mPreparedModelV1_0).withDefault(nullptr)), mDeathHandler(std::move(deathHandler)) {} VersionedIPreparedModel::~VersionedIPreparedModel() { // It is safe to ignore any errors resulting from this unlinkToDeath call // because the VersionedIPreparedModel object is already being destroyed and // its underlying IPreparedModel object is no longer being used by the NN // runtime. mPreparedModelV1_0->unlinkToDeath(mDeathHandler).isOk(); } ErrorStatus VersionedIPreparedModel::execute(const Request& request, MeasureTiming measure, const sp<ExecutionCallback>& callback) { const auto scoped = mDeathHandler->protectCallback(callback); if (mPreparedModelV1_2 != nullptr) { Return<ErrorStatus> ret = mPreparedModelV1_2->execute_1_2(request, measure, callback); if (!ret.isOk()) { sendFailureMessage(callback); LOG(ERROR) << "execute_1_2 failure: " << ret.description(); return ErrorStatus::GENERAL_FAILURE; } if (ret != ErrorStatus::NONE) { sendFailureMessage(callback); LOG(ERROR) << "execute_1_2 returned " << toString(static_cast<ErrorStatus>(ret)); return static_cast<ErrorStatus>(ret); } callback->wait(); return static_cast<ErrorStatus>(ret); } else if (mPreparedModelV1_0 != nullptr) { Return<ErrorStatus> ret = mPreparedModelV1_0->execute(request, callback); if (!ret.isOk()) { sendFailureMessage(callback); LOG(ERROR) << "execute failure: " << ret.description(); return ErrorStatus::GENERAL_FAILURE; } if (ret != ErrorStatus::NONE) { sendFailureMessage(callback); LOG(ERROR) << "execute returned " << toString(static_cast<ErrorStatus>(ret)); return static_cast<ErrorStatus>(ret); } callback->wait(); return static_cast<ErrorStatus>(ret); } else { sendFailureMessage(callback); LOG(ERROR) << "execute called with no preparedModel"; return ErrorStatus::GENERAL_FAILURE; } } std::tuple<ErrorStatus, hidl_vec<OutputShape>, Timing> VersionedIPreparedModel::executeSynchronously(const Request& request, MeasureTiming measure) { const std::tuple<ErrorStatus, hidl_vec<OutputShape>, Timing> kFailure = { ErrorStatus::GENERAL_FAILURE, {}, kBadTiming}; if (mPreparedModelV1_2 != nullptr) { std::tuple<ErrorStatus, hidl_vec<OutputShape>, Timing> result; Return<void> ret = mPreparedModelV1_2->executeSynchronously( request, measure, [&result](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes, const Timing& timing) { result = std::make_tuple(error, outputShapes, timing); }); if (!ret.isOk()) { LOG(ERROR) << "executeSynchronously failure: " << ret.description(); return kFailure; } return result; } else { // Simulate synchronous execution. sp<ExecutionCallback> callback = new ExecutionCallback(); ErrorStatus ret = execute(request, measure, callback); if (ret != ErrorStatus::NONE) { return {ret, {}, kBadTiming}; } callback->wait(); // callback->getOutputShapes() will always return an empty hidl vector. // callback->getTiming() will always return values indicating no measurement. return {callback->getStatus(), callback->getOutputShapes(), callback->getTiming()}; } } std::shared_ptr<ExecutionBurstController> VersionedIPreparedModel::configureExecutionBurst( bool blocking) const { if (mPreparedModelV1_2 != nullptr) { return ExecutionBurstController::create(mPreparedModelV1_2, blocking); } else { return nullptr; } } bool VersionedIPreparedModel::operator==(nullptr_t) const { return mPreparedModelV1_0 == nullptr; } bool VersionedIPreparedModel::operator!=(nullptr_t) const { return mPreparedModelV1_0 != nullptr; } std::shared_ptr<VersionedIDevice> VersionedIDevice::create(std::string serviceName, sp<V1_0::IDevice> device) { auto core = Core::create(std::move(device)); if (!core.has_value()) { LOG(ERROR) << "VersionedIDevice::create -- Failed to create Core."; return nullptr; } // return a valid VersionedIDevice object return std::make_shared<VersionedIDevice>(std::move(serviceName), std::move(core.value())); } VersionedIDevice::VersionedIDevice(std::string serviceName, Core core) : mServiceName(std::move(serviceName)), mCore(std::move(core)) {} std::optional<VersionedIDevice::Core> VersionedIDevice::Core::create(sp<V1_0::IDevice> device) { // verify input if (!device) { LOG(ERROR) << "VersionedIDevice::Core::create -- passed invalid device object."; return {}; } // create death handler object sp<IDeviceDeathHandler> deathHandler = new (std::nothrow) IDeviceDeathHandler(); if (!deathHandler) { LOG(ERROR) << "VersionedIDevice::Core::create -- Failed to create IDeviceDeathHandler."; return {}; } // linkToDeath registers a callback that will be invoked on service death to // proactively handle service crashes. If the linkToDeath call fails, // asynchronous calls are susceptible to hangs if the service crashes before // providing the response. const Return<bool> ret = device->linkToDeath(deathHandler, 0); if (!ret.isOk() || ret != true) { LOG(ERROR) << "VersionedIDevice::Core::create -- Failed to register a death recipient for the " "IDevice object."; return {}; } // return a valid Core object return Core(std::move(device), std::move(deathHandler)); } // HIDL guarantees all V1_1 interfaces inherit from their corresponding V1_0 interfaces. VersionedIDevice::Core::Core(sp<V1_0::IDevice> device, sp<IDeviceDeathHandler> deathHandler) : mDeviceV1_0(std::move(device)), mDeviceV1_1(V1_1::IDevice::castFrom(mDeviceV1_0).withDefault(nullptr)), mDeviceV1_2(V1_2::IDevice::castFrom(mDeviceV1_0).withDefault(nullptr)), mDeathHandler(std::move(deathHandler)) {} VersionedIDevice::Core::~Core() { if (mDeathHandler != nullptr) { CHECK(mDeviceV1_0 != nullptr); // It is safe to ignore any errors resulting from this unlinkToDeath call // because the VersionedIDevice::Core object is already being destroyed and // its underlying IDevice object is no longer being used by the NN runtime. mDeviceV1_0->unlinkToDeath(mDeathHandler).isOk(); } } VersionedIDevice::Core::Core(Core&& other) noexcept : mDeviceV1_0(std::move(other.mDeviceV1_0)), mDeviceV1_1(std::move(other.mDeviceV1_1)), mDeviceV1_2(std::move(other.mDeviceV1_2)), mDeathHandler(std::move(other.mDeathHandler)) { other.mDeathHandler = nullptr; } VersionedIDevice::Core& VersionedIDevice::Core::operator=(Core&& other) noexcept { if (this != &other) { mDeviceV1_0 = std::move(other.mDeviceV1_0); mDeviceV1_1 = std::move(other.mDeviceV1_1); mDeviceV1_2 = std::move(other.mDeviceV1_2); mDeathHandler = std::move(other.mDeathHandler); other.mDeathHandler = nullptr; } return *this; } template <typename T_IDevice> std::pair<sp<T_IDevice>, sp<IDeviceDeathHandler>> VersionedIDevice::Core::getDeviceAndDeathHandler() const { return {getDevice<T_IDevice>(), mDeathHandler}; } template <typename T_IDevice, typename T_Callback> Return<ErrorStatus> callProtected( const char* context, const std::function<Return<ErrorStatus>(const sp<T_IDevice>&)>& fn, const sp<T_IDevice>& device, const sp<T_Callback>& callback, const sp<IDeviceDeathHandler>& deathHandler) { const auto scoped = deathHandler->protectCallback(callback); Return<ErrorStatus> ret = fn(device); // Suppose there was a transport error. We have the following cases: // 1. Either not due to a dead device, or due to a device that was // already dead at the time of the call to protectCallback(). In // this case, the callback was never signalled. // 2. Due to a device that died after the call to protectCallback() but // before fn() completed. In this case, the callback was (or will // be) signalled by the deathHandler. // Furthermore, what if there was no transport error, but the ErrorStatus is // other than NONE? We'll conservatively signal the callback anyway, just in // case the driver was sloppy and failed to do so. if (!ret.isOk() || ret != ErrorStatus::NONE) { // What if the deathHandler has signalled or will signal the callback? // This is fine -- we're permitted to signal multiple times; and we're // sending the same signal that the deathHandler does. // // What if the driver signalled the callback? Then this signal is // ignored. if (ret.isOk()) { LOG(ERROR) << context << " returned " << toString(static_cast<ErrorStatus>(ret)); } else { LOG(ERROR) << context << " failure: " << ret.description(); } sendFailureMessage(callback); } callback->wait(); return ret; } template <typename T_Return, typename T_IDevice> Return<T_Return> callProtected(const char*, const std::function<Return<T_Return>(const sp<T_IDevice>&)>& fn, const sp<T_IDevice>& device, const std::nullptr_t&, const sp<IDeviceDeathHandler>&) { return fn(device); } template <typename T_Return, typename T_IDevice, typename T_Callback> Return<T_Return> VersionedIDevice::recoverable( const char* context, const std::function<Return<T_Return>(const sp<T_IDevice>&)>& fn, const T_Callback& callback) const EXCLUDES(mMutex) { CHECK_EQ(callback == nullptr, (std::is_same_v<T_Callback, std::nullptr_t>)); sp<T_IDevice> device; sp<IDeviceDeathHandler> deathHandler; std::tie(device, deathHandler) = getDeviceAndDeathHandler<T_IDevice>(); Return<T_Return> ret = callProtected(context, fn, device, callback, deathHandler); if (ret.isDeadObject()) { { std::unique_lock lock(mMutex); // It's possible that another device has already done the recovery. // It's harmless but wasteful for us to do so in this case. auto pingReturn = mCore.getDevice<T_IDevice>()->ping(); if (pingReturn.isDeadObject()) { VLOG(DRIVER) << "VersionedIDevice::recoverable(" << context << ") -- Recovering " << mServiceName; sp<V1_0::IDevice> recoveredDevice = V1_0::IDevice::tryGetService(mServiceName); if (recoveredDevice == nullptr) { VLOG(DRIVER) << "VersionedIDevice::recoverable got a null IDEVICE for " << mServiceName; return ret; } auto core = Core::create(std::move(recoveredDevice)); if (!core.has_value()) { LOG(ERROR) << "VersionedIDevice::recoverable -- Failed to create Core."; return ret; } mCore = std::move(core.value()); } else { VLOG(DRIVER) << "VersionedIDevice::recoverable(" << context << ") -- Someone else recovered " << mServiceName; // Might still have a transport error, which we need to check // before pingReturn goes out of scope. (void)pingReturn.isOk(); } std::tie(device, deathHandler) = mCore.getDeviceAndDeathHandler<T_IDevice>(); } ret = callProtected(context, fn, device, callback, deathHandler); // It's possible that the device died again, but we're only going to // attempt recovery once per call to recoverable(). } return ret; } std::pair<ErrorStatus, Capabilities> VersionedIDevice::getCapabilities() { const std::pair<ErrorStatus, Capabilities> kFailure = {ErrorStatus::GENERAL_FAILURE, {}}; std::pair<ErrorStatus, Capabilities> result; if (getDevice<V1_2::IDevice>() != nullptr) { NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_INITIALIZATION, "getCapabilities_1_2"); Return<void> ret = recoverable<void, V1_2::IDevice>( __FUNCTION__, [&result](const sp<V1_2::IDevice>& device) { return device->getCapabilities_1_2( [&result](ErrorStatus error, const Capabilities& capabilities) { result = std::make_pair(error, capabilities); }); }); if (!ret.isOk()) { LOG(ERROR) << "getCapabilities_1_2 failure: " << ret.description(); return {ErrorStatus::GENERAL_FAILURE, {}}; } } else if (getDevice<V1_1::IDevice>() != nullptr) { NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_INITIALIZATION, "getCapabilities_1_1"); Return<void> ret = recoverable<void, V1_1::IDevice>( __FUNCTION__, [&result](const sp<V1_1::IDevice>& device) { return device->getCapabilities_1_1( [&result](ErrorStatus error, const V1_1::Capabilities& capabilities) { // Time taken to convert capabilities is trivial result = std::make_pair(error, convertToV1_2(capabilities)); }); }); if (!ret.isOk()) { LOG(ERROR) << "getCapabilities_1_1 failure: " << ret.description(); return kFailure; } } else if (getDevice<V1_0::IDevice>() != nullptr) { NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_INITIALIZATION, "getCapabilities"); Return<void> ret = recoverable<void, V1_0::IDevice>( __FUNCTION__, [&result](const sp<V1_0::IDevice>& device) { return device->getCapabilities( [&result](ErrorStatus error, const V1_0::Capabilities& capabilities) { // Time taken to convert capabilities is trivial result = std::make_pair(error, convertToV1_2(capabilities)); }); }); if (!ret.isOk()) { LOG(ERROR) << "getCapabilities failure: " << ret.description(); return kFailure; } } else { LOG(ERROR) << "Device not available!"; return {ErrorStatus::DEVICE_UNAVAILABLE, {}}; } return result; } std::pair<ErrorStatus, hidl_vec<Extension>> VersionedIDevice::getSupportedExtensions() { const std::pair<ErrorStatus, hidl_vec<Extension>> kFailure = {ErrorStatus::GENERAL_FAILURE, {}}; NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_COMPILATION, "getSupportedExtensions"); if (getDevice<V1_2::IDevice>() != nullptr) { std::pair<ErrorStatus, hidl_vec<Extension>> result; Return<void> ret = recoverable<void, V1_2::IDevice>( __FUNCTION__, [&result](const sp<V1_2::IDevice>& device) { return device->getSupportedExtensions( [&result](ErrorStatus error, const hidl_vec<Extension>& extensions) { result = std::make_pair(error, extensions); }); }); if (!ret.isOk()) { LOG(ERROR) << "getSupportedExtensions failure: " << ret.description(); return kFailure; } return result; } else if (getDevice<V1_0::IDevice>() != nullptr) { return {ErrorStatus::NONE, {/* No extensions. */}}; } else { LOG(ERROR) << "Device not available!"; return {ErrorStatus::DEVICE_UNAVAILABLE, {}}; } } std::pair<ErrorStatus, hidl_vec<bool>> VersionedIDevice::getSupportedOperations( const Model& model, IModelSlicer* slicer) { const std::pair<ErrorStatus, hidl_vec<bool>> kFailure = {ErrorStatus::GENERAL_FAILURE, {}}; std::pair<ErrorStatus, hidl_vec<bool>> result; auto noneSupported = [&model] { hidl_vec<bool> supported(model.operations.size()); std::fill(supported.begin(), supported.end(), false); return std::make_pair(ErrorStatus::NONE, std::move(supported)); }; auto remappedResult = [&model](const std::pair<ErrorStatus, hidl_vec<bool>>& result, const std::function<uint32_t(uint32_t)>& submodelOperationIndexToModelOperationIndex) { const ErrorStatus status = result.first; const hidl_vec<bool>& supported = result.second; hidl_vec<bool> remappedSupported(model.operations.size()); std::fill(remappedSupported.begin(), remappedSupported.end(), false); for (size_t i = 0; i < supported.size(); ++i) { if (supported[i]) { remappedSupported[submodelOperationIndexToModelOperationIndex(i)] = true; } } return std::make_pair(status, std::move(remappedSupported)); }; if (getDevice<V1_2::IDevice>() != nullptr) { NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_COMPILATION, "getSupportedOperations_1_2"); Return<void> ret = recoverable<void, V1_2::IDevice>( __FUNCTION__, [&model, &result](const sp<V1_2::IDevice>& device) { return device->getSupportedOperations_1_2( model, [&result](ErrorStatus error, const hidl_vec<bool>& supported) { result = std::make_pair(error, supported); }); }); if (!ret.isOk()) { LOG(ERROR) << "getSupportedOperations_1_2 failure: " << ret.description(); return kFailure; } return result; } if (getDevice<V1_1::IDevice>() != nullptr) { const bool compliant = compliantWithV1_1(model); if (compliant || slicer) { V1_1::Model model11; std::function<uint32_t(uint32_t)> submodelOperationIndexToModelOperationIndex; if (compliant) { model11 = convertToV1_1(model); } else { const auto slice11 = slicer->getSliceV1_1(); if (!slice11.has_value()) { return noneSupported(); } std::tie(model11, submodelOperationIndexToModelOperationIndex) = *slice11; } NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_COMPILATION, "getSupportedOperations_1_1"); Return<void> ret = recoverable<void, V1_1::IDevice>( __FUNCTION__, [&model11, &result](const sp<V1_1::IDevice>& device) { return device->getSupportedOperations_1_1( model11, [&result](ErrorStatus error, const hidl_vec<bool>& supported) { result = std::make_pair(error, supported); }); }); if (!ret.isOk()) { LOG(ERROR) << "getSupportedOperations_1_1 failure: " << ret.description(); return kFailure; } if (!compliant) { return remappedResult(result, submodelOperationIndexToModelOperationIndex); } } return result; } if (getDevice<V1_0::IDevice>() != nullptr) { const bool compliant = compliantWithV1_0(model); if (compliant || slicer) { V1_0::Model model10; std::function<uint32_t(uint32_t)> submodelOperationIndexToModelOperationIndex; if (compliant) { model10 = convertToV1_0(model); } else { const auto slice10 = slicer->getSliceV1_0(); if (!slice10.has_value()) { return noneSupported(); } std::tie(model10, submodelOperationIndexToModelOperationIndex) = *slice10; } NNTRACE_FULL(NNTRACE_LAYER_IPC, NNTRACE_PHASE_COMPILATION, "getSupportedOperations"); Return<void> ret = recoverable<void, V1_0::IDevice>( __FUNCTION__, [&model10, &result](const sp<V1_0::IDevice>& device) { return device->getSupportedOperations( model10, [&result](ErrorStatus error, const hidl_vec<bool>& supported) { result = std::make_pair(error, supported); }); }); if (!ret.isOk()) { LOG(ERROR) << "getSupportedOperations failure: " << ret.description(); return kFailure; } if (!compliant) { return remappedResult(result, submodelOperationIndexToModelOperationIndex); } } return result; } return kFailure; } std::pair<ErrorStatus, std::shared_ptr<VersionedIPreparedModel>> VersionedIDevice::prepareModel( const Model& model, ExecutionPreference preference, const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, const HidlToken& token) { const std::pair<ErrorStatus, std::shared_ptr<VersionedIPreparedModel>> kFailure = { ErrorStatus::GENERAL_FAILURE, nullptr}; const sp<PreparedModelCallback> callback = new (std::nothrow) PreparedModelCallback(); if (callback == nullptr) { LOG(ERROR) << "prepareModel failed to create callback object"; return kFailure; } // If 1.2 device, try preparing model if (getDevice<V1_2::IDevice>() != nullptr) { const Return<ErrorStatus> ret = recoverable<ErrorStatus, V1_2::IDevice>( __FUNCTION__, [&model, &preference, &modelCache, &dataCache, &token, &callback](const sp<V1_2::IDevice>& device) { return device->prepareModel_1_2(model, preference, modelCache, dataCache, token, callback); }, callback); if (!ret.isOk()) { LOG(ERROR) << "prepareModel_1_2 failure: " << ret.description(); return kFailure; } if (ret != ErrorStatus::NONE) { LOG(ERROR) << "prepareModel_1_2 returned " << toString(static_cast<ErrorStatus>(ret)); return kFailure; } callback->wait(); return {callback->getStatus(), makeVersionedIPreparedModel(callback->getPreparedModel())}; } // If 1.1 device, try preparing model (requires conversion) if (getDevice<V1_1::IDevice>() != nullptr) { bool compliant = false; V1_1::Model model11; { // Attribute time spent in model inspection and conversion to // Runtime, as the time may be substantial (0.03ms for mobilenet, // but could be larger for other models). NNTRACE_FULL_SUBTRACT(NNTRACE_LAYER_RUNTIME, NNTRACE_PHASE_COMPILATION, "VersionedIDevice::prepareModel_1_1"); compliant = compliantWithV1_1(model); if (compliant) { model11 = convertToV1_1(model); // copy is elided } } if (compliant) { const Return<ErrorStatus> ret = recoverable<ErrorStatus, V1_1::IDevice>( __FUNCTION__, [&model11, &preference, &callback](const sp<V1_1::IDevice>& device) { return device->prepareModel_1_1(model11, preference, callback); }, callback); if (!ret.isOk()) { LOG(ERROR) << "prepareModel_1_1 failure: " << ret.description(); return kFailure; } if (ret != ErrorStatus::NONE) { LOG(ERROR) << "prepareModel_1_1 returned " << toString(static_cast<ErrorStatus>(ret)); return kFailure; } callback->wait(); return {callback->getStatus(), makeVersionedIPreparedModel(callback->getPreparedModel())}; } LOG(ERROR) << "Could not handle prepareModel_1_1!"; return kFailure; } // If 1.0 device, try preparing model (requires conversion) if (getDevice<V1_0::IDevice>() != nullptr) { bool compliant = false; V1_0::Model model10; { // Attribute time spent in model inspection and conversion to // Runtime, as the time may be substantial (0.03ms for mobilenet, // but could be larger for other models). NNTRACE_FULL_SUBTRACT(NNTRACE_LAYER_RUNTIME, NNTRACE_PHASE_COMPILATION, "VersionedIDevice::prepareModel"); compliant = compliantWithV1_0(model); if (compliant) { model10 = convertToV1_0(model); // copy is elided } } if (compliant) { const Return<ErrorStatus> ret = recoverable<ErrorStatus, V1_0::IDevice>( __FUNCTION__, [&model10, &callback](const sp<V1_0::IDevice>& device) { return device->prepareModel(model10, callback); }, callback); if (!ret.isOk()) { LOG(ERROR) << "prepareModel failure: " << ret.description(); return kFailure; } if (ret != ErrorStatus::NONE) { LOG(ERROR) << "prepareModel returned " << toString(static_cast<ErrorStatus>(ret)); return kFailure; } callback->wait(); return {callback->getStatus(), makeVersionedIPreparedModel(callback->getPreparedModel())}; } LOG(ERROR) << "Could not handle prepareModel!"; return kFailure; } // Return error because there is no valid device LOG(ERROR) << "prepareModel called with no device"; return kFailure; } std::pair<ErrorStatus, std::shared_ptr<VersionedIPreparedModel>> VersionedIDevice::prepareModelFromCache(const hidl_vec<hidl_handle>& modelCache, const hidl_vec<hidl_handle>& dataCache, const HidlToken& token) { const std::pair<ErrorStatus, std::shared_ptr<VersionedIPreparedModel>> kFailure = { ErrorStatus::GENERAL_FAILURE, nullptr}; const sp<PreparedModelCallback> callback = new (std::nothrow) PreparedModelCallback(); if (callback == nullptr) { LOG(ERROR) << "prepareModelFromCache failed to create callback object"; return kFailure; } if (getDevice<V1_2::IDevice>() != nullptr) { const Return<ErrorStatus> ret = recoverable<ErrorStatus, V1_2::IDevice>( __FUNCTION__, [&modelCache, &dataCache, &token, &callback](const sp<V1_2::IDevice>& device) { return device->prepareModelFromCache(modelCache, dataCache, token, callback); }, callback); if (!ret.isOk()) { LOG(ERROR) << "prepareModelFromCache failure: " << ret.description(); return kFailure; } if (ret != ErrorStatus::NONE) { LOG(ERROR) << "prepareModelFromCache returned " << toString(static_cast<ErrorStatus>(ret)); return kFailure; } callback->wait(); return {callback->getStatus(), makeVersionedIPreparedModel(callback->getPreparedModel())}; } if (getDevice<V1_1::IDevice>() != nullptr || getDevice<V1_0::IDevice>() != nullptr) { LOG(ERROR) << "prepareModelFromCache called on V1_1 or V1_0 device"; return kFailure; } LOG(ERROR) << "prepareModelFromCache called with no device"; return kFailure; } DeviceStatus VersionedIDevice::getStatus() { if (getDevice<V1_0::IDevice>() == nullptr) { LOG(ERROR) << "Device not available!"; return DeviceStatus::UNKNOWN; } Return<DeviceStatus> ret = recoverable<DeviceStatus, V1_0::IDevice>( __FUNCTION__, [](const sp<V1_0::IDevice>& device) { return device->getStatus(); }); if (!ret.isOk()) { LOG(ERROR) << "getStatus failure: " << ret.description(); return DeviceStatus::UNKNOWN; } return static_cast<DeviceStatus>(ret); } int64_t VersionedIDevice::getFeatureLevel() { constexpr int64_t kFailure = -1; if (getDevice<V1_2::IDevice>() != nullptr) { return __ANDROID_API_Q__; } else if (getDevice<V1_1::IDevice>() != nullptr) { return __ANDROID_API_P__; } else if (getDevice<V1_0::IDevice>() != nullptr) { return __ANDROID_API_O_MR1__; } else { LOG(ERROR) << "Device not available!"; return kFailure; } } int32_t VersionedIDevice::getType() const { constexpr int32_t kFailure = -1; std::pair<ErrorStatus, DeviceType> result; if (getDevice<V1_2::IDevice>() != nullptr) { Return<void> ret = recoverable<void, V1_2::IDevice>( __FUNCTION__, [&result](const sp<V1_2::IDevice>& device) { return device->getType([&result](ErrorStatus error, DeviceType deviceType) { result = std::make_pair(error, deviceType); }); }); if (!ret.isOk()) { LOG(ERROR) << "getType failure: " << ret.description(); return kFailure; } return static_cast<int32_t>(result.second); } else { LOG(INFO) << "Unknown NNAPI device type."; return ANEURALNETWORKS_DEVICE_UNKNOWN; } } std::pair<ErrorStatus, hidl_string> VersionedIDevice::getVersionString() { const std::pair<ErrorStatus, hidl_string> kFailure = {ErrorStatus::GENERAL_FAILURE, ""}; std::pair<ErrorStatus, hidl_string> result; if (getDevice<V1_2::IDevice>() != nullptr) { Return<void> ret = recoverable<void, V1_2::IDevice>( __FUNCTION__, [&result](const sp<V1_2::IDevice>& device) { return device->getVersionString( [&result](ErrorStatus error, const hidl_string& version) { result = std::make_pair(error, version); }); }); if (!ret.isOk()) { LOG(ERROR) << "getVersion failure: " << ret.description(); return kFailure; } return result; } else if (getDevice<V1_1::IDevice>() != nullptr || getDevice<V1_0::IDevice>() != nullptr) { return {ErrorStatus::NONE, "UNKNOWN"}; } else { LOG(ERROR) << "Could not handle getVersionString"; return kFailure; } } std::tuple<ErrorStatus, uint32_t, uint32_t> VersionedIDevice::getNumberOfCacheFilesNeeded() { constexpr std::tuple<ErrorStatus, uint32_t, uint32_t> kFailure = {ErrorStatus::GENERAL_FAILURE, 0, 0}; std::tuple<ErrorStatus, uint32_t, uint32_t> result; if (getDevice<V1_2::IDevice>() != nullptr) { Return<void> ret = recoverable<void, V1_2::IDevice>( __FUNCTION__, [&result](const sp<V1_2::IDevice>& device) { return device->getNumberOfCacheFilesNeeded([&result](ErrorStatus error, uint32_t numModelCache, uint32_t numDataCache) { result = {error, numModelCache, numDataCache}; }); }); if (!ret.isOk()) { LOG(ERROR) << "getNumberOfCacheFilesNeeded failure: " << ret.description(); return kFailure; } return result; } else if (getDevice<V1_1::IDevice>() != nullptr || getDevice<V1_0::IDevice>() != nullptr) { return {ErrorStatus::NONE, 0, 0}; } else { LOG(ERROR) << "Could not handle getNumberOfCacheFilesNeeded"; return kFailure; } } bool VersionedIDevice::operator==(nullptr_t) const { return getDevice<V1_0::IDevice>() == nullptr; } bool VersionedIDevice::operator!=(nullptr_t) const { return getDevice<V1_0::IDevice>() != nullptr; } } // namespace nn } // namespace android