/* * Copyright (C) 2017 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. */ #define LOG_TAG "ContextHubHal" #define LOG_NDEBUG 0 #include "generic_context_hub.h" #include #include #include #include #include namespace android { namespace hardware { namespace contexthub { namespace V1_0 { namespace implementation { using ::android::chre::getStringFromByteVector; using ::android::hardware::Return; using ::android::hardware::contexthub::V1_0::AsyncEventType; using ::android::hardware::contexthub::V1_0::Result; using ::android::hardware::contexthub::V1_0::TransactionResult; using ::android::chre::HostProtocolHost; using ::flatbuffers::FlatBufferBuilder; // Aliased for consistency with the way these symbols are referenced in // CHRE-side code namespace fbs = ::chre::fbs; namespace { constexpr uint32_t kDefaultHubId = 0; constexpr uint8_t extractChreApiMajorVersion(uint32_t chreVersion) { return static_cast(chreVersion >> 24); } constexpr uint8_t extractChreApiMinorVersion(uint32_t chreVersion) { return static_cast(chreVersion >> 16); } constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) { return static_cast(chreVersion); } /** * @return file descriptor contained in the hidl_handle, or -1 if there is none */ int hidlHandleToFileDescriptor(const hidl_handle& hh) { const native_handle_t *handle = hh.getNativeHandle(); return (handle != nullptr && handle->numFds >= 1) ? handle->data[0] : -1; } } // anonymous namespace GenericContextHub::DeathRecipient::DeathRecipient( sp contexthub) : mGenericContextHub(contexthub){} void GenericContextHub::DeathRecipient::serviceDied( uint64_t cookie, const wp<::android::hidl::base::V1_0::IBase>& /* who */) { uint32_t hubId = static_cast(cookie); mGenericContextHub->handleServiceDeath(hubId); } GenericContextHub::GenericContextHub() { constexpr char kChreSocketName[] = "chre"; mSocketCallbacks = new SocketCallbacks(*this); if (!mClient.connectInBackground(kChreSocketName, mSocketCallbacks)) { ALOGE("Couldn't start socket client"); } mDeathRecipient = new DeathRecipient(this); } Return GenericContextHub::debug( const hidl_handle& hh_fd, const hidl_vec& /*options*/) { // Timeout inside CHRE is typically 5 seconds, grant 500ms extra here to let // the data reach us constexpr auto kDebugDumpTimeout = std::chrono::milliseconds(5500); mDebugFd = hidlHandleToFileDescriptor(hh_fd); if (mDebugFd < 0) { ALOGW("Can't dump debug info to invalid fd"); } else { writeToDebugFile("-- Dumping CHRE/ASH debug info --\n"); ALOGV("Sending debug dump request"); FlatBufferBuilder builder; HostProtocolHost::encodeDebugDumpRequest(builder); std::unique_lock lock(mDebugDumpMutex); mDebugDumpPending = true; if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { ALOGW("Couldn't send debug dump request"); } else { mDebugDumpCond.wait_for(lock, kDebugDumpTimeout, [this]() { return !mDebugDumpPending; }); if (mDebugDumpPending) { ALOGI("Timed out waiting on debug dump data"); mDebugDumpPending = false; } } writeToDebugFile("\n-- End of CHRE/ASH debug info --\n"); mDebugFd = kInvalidFd; ALOGV("Debug dump complete"); } return Void(); } Return GenericContextHub::getHubs(getHubs_cb _hidl_cb) { constexpr auto kHubInfoQueryTimeout = std::chrono::seconds(5); std::vector hubs; ALOGV("%s", __func__); // If we're not connected yet, give it some time // TODO refactor from polling into conditional wait int maxSleepIterations = 250; while (!mHubInfoValid && !mClient.isConnected() && --maxSleepIterations > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } if (!mClient.isConnected()) { ALOGE("Couldn't connect to hub daemon"); } else if (!mHubInfoValid) { // We haven't cached the hub details yet, so send a request and block // waiting on a response std::unique_lock lock(mHubInfoMutex); FlatBufferBuilder builder; HostProtocolHost::encodeHubInfoRequest(builder); ALOGD("Sending hub info request"); if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { ALOGE("Couldn't send hub info request"); } else { mHubInfoCond.wait_for(lock, kHubInfoQueryTimeout, [this]() { return mHubInfoValid; }); } } if (mHubInfoValid) { hubs.push_back(mHubInfo); } else { ALOGE("Unable to get hub info from CHRE"); } _hidl_cb(hubs); return Void(); } Return GenericContextHub::registerCallback( uint32_t hubId, const sp& cb) { Result result; ALOGV("%s", __func__); // TODO: currently we only support 1 hub behind this HAL implementation if (hubId == kDefaultHubId) { std::lock_guard lock(mCallbacksLock); if (cb != nullptr) { if (mCallbacks != nullptr) { ALOGD("Modifying callback for hubId %" PRIu32, hubId); mCallbacks->unlinkToDeath(mDeathRecipient); } Return linkReturn = cb->linkToDeath(mDeathRecipient, hubId); if (!linkReturn.withDefault(false)) { ALOGW("Could not link death recipient to hubId %" PRIu32, hubId); } } mCallbacks = cb; result = Result::OK; } else { result = Result::BAD_PARAMS; } return result; } Return GenericContextHub::sendMessageToHub(uint32_t hubId, const ContextHubMsg& msg) { Result result; ALOGV("%s", __func__); if (hubId != kDefaultHubId) { result = Result::BAD_PARAMS; } else { FlatBufferBuilder builder(1024); HostProtocolHost::encodeNanoappMessage( builder, msg.appName, msg.msgType, msg.hostEndPoint, msg.msg.data(), msg.msg.size()); if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { result = Result::UNKNOWN_FAILURE; } else { result = Result::OK; } } return result; } Return GenericContextHub::loadNanoApp( uint32_t hubId, const NanoAppBinary& appBinary, uint32_t transactionId) { Result result; ALOGV("%s", __func__); if (hubId != kDefaultHubId) { result = Result::BAD_PARAMS; } else { std::lock_guard lock(mPendingLoadTransactionMutex); if (mPendingLoadTransaction.has_value()) { ALOGE("Pending load transaction exists. Overriding pending request"); } uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) | (appBinary.targetChreApiMinorVersion << 16); mPendingLoadTransaction = FragmentedLoadTransaction( transactionId, appBinary.appId, appBinary.appVersion, targetApiVersion, appBinary.customBinary, kLoadFragmentSizeBytes); result = sendFragmentedLoadNanoAppRequest( mPendingLoadTransaction.value()); if (result != Result::OK) { mPendingLoadTransaction.reset(); } } ALOGD("Attempted to send load nanoapp request for app of size %zu with ID " "0x%016" PRIx64 " as transaction ID %" PRIu32 ": result %" PRIu32, appBinary.customBinary.size(), appBinary.appId, transactionId, result); return result; } Result GenericContextHub::sendFragmentedLoadNanoAppRequest( FragmentedLoadTransaction& transaction) { Result result; const FragmentedLoadRequest& request = transaction.getNextRequest(); FlatBufferBuilder builder(128 + request.binary.size()); HostProtocolHost::encodeFragmentedLoadNanoappRequest(builder, request); if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { ALOGE("Failed to send load request message (fragment ID = %zu)", request.fragmentId); result = Result::UNKNOWN_FAILURE; } else { mCurrentFragmentId = request.fragmentId; result = Result::OK; } return result; } Return GenericContextHub::unloadNanoApp( uint32_t hubId, uint64_t appId, uint32_t transactionId) { Result result; ALOGV("%s", __func__); if (hubId != kDefaultHubId) { result = Result::BAD_PARAMS; } else { FlatBufferBuilder builder(64); HostProtocolHost::encodeUnloadNanoappRequest( builder, transactionId, appId, false /* allowSystemNanoappUnload */); if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { result = Result::UNKNOWN_FAILURE; } else { result = Result::OK; } } ALOGD("Attempted to send unload nanoapp request for app ID 0x%016" PRIx64 " as transaction ID %" PRIu32 ": result %" PRIu32, appId, transactionId, result); return result; } Return GenericContextHub::enableNanoApp( uint32_t /* hubId */, uint64_t appId, uint32_t /* transactionId */) { // TODO ALOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported", appId); return Result::TRANSACTION_FAILED; } Return GenericContextHub::disableNanoApp( uint32_t /* hubId */, uint64_t appId, uint32_t /* transactionId */) { // TODO ALOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported", appId); return Result::TRANSACTION_FAILED; } Return GenericContextHub::queryApps(uint32_t hubId) { Result result; ALOGV("%s", __func__); if (hubId != kDefaultHubId) { result = Result::BAD_PARAMS; } else { FlatBufferBuilder builder(64); HostProtocolHost::encodeNanoappListRequest(builder); if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { result = Result::UNKNOWN_FAILURE; } else { result = Result::OK; } } return result; } GenericContextHub::SocketCallbacks::SocketCallbacks(GenericContextHub& parent) : mParent(parent) {} void GenericContextHub::SocketCallbacks::onMessageReceived(const void *data, size_t length) { if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) { ALOGE("Failed to decode message"); } } void GenericContextHub::SocketCallbacks::onConnected() { if (mHaveConnected) { ALOGI("Reconnected to CHRE daemon"); invokeClientCallback([&]() { return mParent.mCallbacks->handleHubEvent(AsyncEventType::RESTARTED); }); } mHaveConnected = true; } void GenericContextHub::SocketCallbacks::onDisconnected() { ALOGW("Lost connection to CHRE daemon"); } void GenericContextHub::SocketCallbacks::handleNanoappMessage( const fbs::NanoappMessageT& message) { ContextHubMsg msg; msg.appName = message.app_id; msg.hostEndPoint = message.host_endpoint; msg.msgType = message.message_type; msg.msg = message.message; invokeClientCallback([&]() { return mParent.mCallbacks->handleClientMsg(msg); }); } void GenericContextHub::SocketCallbacks::handleHubInfoResponse( const fbs::HubInfoResponseT& response) { ALOGD("Got hub info response"); std::lock_guard lock(mParent.mHubInfoMutex); if (mParent.mHubInfoValid) { ALOGI("Ignoring duplicate/unsolicited hub info response"); } else { mParent.mHubInfo.name = getStringFromByteVector(response.name); mParent.mHubInfo.vendor = getStringFromByteVector(response.vendor); mParent.mHubInfo.toolchain = getStringFromByteVector(response.toolchain); mParent.mHubInfo.platformVersion = response.platform_version; mParent.mHubInfo.toolchainVersion = response.toolchain_version; mParent.mHubInfo.hubId = kDefaultHubId; mParent.mHubInfo.peakMips = response.peak_mips; mParent.mHubInfo.stoppedPowerDrawMw = response.stopped_power; mParent.mHubInfo.sleepPowerDrawMw = response.sleep_power; mParent.mHubInfo.peakPowerDrawMw = response.peak_power; mParent.mHubInfo.maxSupportedMsgLen = response.max_msg_len; mParent.mHubInfo.chrePlatformId = response.platform_id; uint32_t version = response.chre_platform_version; mParent.mHubInfo.chreApiMajorVersion = extractChreApiMajorVersion(version); mParent.mHubInfo.chreApiMinorVersion = extractChreApiMinorVersion(version); mParent.mHubInfo.chrePatchVersion = extractChrePatchVersion(version); mParent.mHubInfoValid = true; mParent.mHubInfoCond.notify_all(); } } void GenericContextHub::SocketCallbacks::handleNanoappListResponse( const fbs::NanoappListResponseT& response) { std::vector appInfoList; ALOGV("Got nanoapp list response with %zu apps", response.nanoapps.size()); for (const std::unique_ptr& nanoapp : response.nanoapps) { // TODO: determine if this is really required, and if so, have // HostProtocolHost strip out null entries as part of decode if (nanoapp == nullptr) { continue; } ALOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " enabled %d system %d", nanoapp->app_id, nanoapp->version, nanoapp->enabled, nanoapp->is_system); if (!nanoapp->is_system) { HubAppInfo appInfo; appInfo.appId = nanoapp->app_id; appInfo.version = nanoapp->version; appInfo.enabled = nanoapp->enabled; appInfoList.push_back(appInfo); } } invokeClientCallback([&]() { return mParent.mCallbacks->handleAppsInfo(appInfoList); }); } void GenericContextHub::SocketCallbacks::handleLoadNanoappResponse( const ::chre::fbs::LoadNanoappResponseT& response) { ALOGV("Got load nanoapp response for transaction %" PRIu32 " fragment %" PRIu32 " with result %d", response.transaction_id, response.fragment_id, response.success); std::unique_lock lock(mParent.mPendingLoadTransactionMutex); // TODO: Handle timeout in receiving load response if (!mParent.mPendingLoadTransaction.has_value()) { ALOGE("Dropping unexpected load response (no pending transaction exists)"); } else { FragmentedLoadTransaction& transaction = mParent.mPendingLoadTransaction.value(); if (!mParent.isExpectedLoadResponseLocked(response)) { ALOGE("Dropping unexpected load response, expected transaction %" PRIu32 " fragment %" PRIu32 ", received transaction %" PRIu32 " fragment %" PRIu32, transaction.getTransactionId(), mParent.mCurrentFragmentId, response.transaction_id, response.fragment_id); } else { TransactionResult result; bool continueLoadRequest = false; if (response.success && !transaction.isComplete()) { if (mParent.sendFragmentedLoadNanoAppRequest(transaction) == Result::OK) { continueLoadRequest = true; result = TransactionResult::SUCCESS; } else { result = TransactionResult::FAILURE; } } else { result = (response.success) ? TransactionResult::SUCCESS : TransactionResult::FAILURE; } if (!continueLoadRequest) { mParent.mPendingLoadTransaction.reset(); lock.unlock(); invokeClientCallback([&]() { return mParent.mCallbacks->handleTxnResult( response.transaction_id, result); }); } } } } bool GenericContextHub::isExpectedLoadResponseLocked( const ::chre::fbs::LoadNanoappResponseT& response) { return mPendingLoadTransaction.has_value() && (mPendingLoadTransaction->getTransactionId() == response.transaction_id) && (response.fragment_id == 0 || mCurrentFragmentId == response.fragment_id); } void GenericContextHub::SocketCallbacks::handleUnloadNanoappResponse( const ::chre::fbs::UnloadNanoappResponseT& response) { ALOGV("Got unload nanoapp response for transaction %" PRIu32 " with result " "%d", response.transaction_id, response.success); invokeClientCallback([&]() { TransactionResult result = (response.success) ? TransactionResult::SUCCESS : TransactionResult::FAILURE; return mParent.mCallbacks->handleTxnResult(response.transaction_id, result); }); } void GenericContextHub::SocketCallbacks::handleDebugDumpData( const ::chre::fbs::DebugDumpDataT& data) { ALOGV("Got debug dump data, size %zu", data.debug_str.size()); if (mParent.mDebugFd == kInvalidFd) { ALOGW("Got unexpected debug dump data message"); } else { mParent.writeToDebugFile( reinterpret_cast(data.debug_str.data()), data.debug_str.size()); } } void GenericContextHub::SocketCallbacks::handleDebugDumpResponse( const ::chre::fbs::DebugDumpResponseT& response) { ALOGV("Got debug dump response, success %d, data count %" PRIu32, response.success, response.data_count); std::lock_guard lock(mParent.mDebugDumpMutex); if (!mParent.mDebugDumpPending) { ALOGI("Ignoring duplicate/unsolicited debug dump response"); } else { mParent.mDebugDumpPending = false; mParent.mDebugDumpCond.notify_all(); } } void GenericContextHub::SocketCallbacks::invokeClientCallback( std::function()> callback) { std::lock_guard lock(mParent.mCallbacksLock); if (mParent.mCallbacks != nullptr && !callback().isOk()) { ALOGE("Failed to invoke client callback"); } } void GenericContextHub::writeToDebugFile(const char *str) { writeToDebugFile(str, strlen(str)); } void GenericContextHub::writeToDebugFile(const char *str, size_t len) { ssize_t written = write(mDebugFd, str, len); if (written != (ssize_t) len) { ALOGW("Couldn't write to debug header: returned %zd, expected %zu " "(errno %d)", written, len, errno); } } void GenericContextHub::handleServiceDeath(uint32_t hubId) { std::lock_guard lock(mCallbacksLock); ALOGI("Context hub service died for hubId %" PRIu32, hubId); mCallbacks.clear(); } IContexthub* HIDL_FETCH_IContexthub(const char* /* name */) { return new GenericContextHub(); } } // namespace implementation } // namespace V1_0 } // namespace contexthub } // namespace hardware } // namespace android