| /* |
| * Copyright (C) 2023 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. |
| */ |
| |
| #ifndef LOG_TAG |
| #define LOG_TAG "CHRE.HAL.CLIENT" |
| #endif |
| |
| #include "chre_host/hal_client.h" |
| #include "chre_host/log.h" |
| |
| #include <android-base/properties.h> |
| #include <android_chre_flags.h> |
| #include <utils/SystemClock.h> |
| |
| #include <cinttypes> |
| #include <thread> |
| |
| namespace android::chre { |
| |
| using ::aidl::android::hardware::contexthub::IContextHub; |
| using ::aidl::android::hardware::contexthub::IContextHubCallback; |
| using ::android::base::GetBoolProperty; |
| using ::ndk::ScopedAStatus; |
| |
| namespace { |
| constexpr char kHalEnabledProperty[]{"vendor.chre.multiclient_hal.enabled"}; |
| |
| // Multiclient HAL needs getUuid() added since V3 to identify each client. |
| constexpr int kMinHalInterfaceVersion = 3; |
| } // namespace |
| |
| bool HalClient::isServiceAvailable() { |
| return GetBoolProperty(kHalEnabledProperty, /* default_value= */ false); |
| } |
| |
| bool HalClient::reduceLockHolding() { |
| return flags::bug_fix_reduce_lock_holding_period(); |
| } |
| |
| std::unique_ptr<HalClient> HalClient::create( |
| const std::shared_ptr<IContextHubCallback> &callback, |
| int32_t contextHubId) { |
| if (callback == nullptr) { |
| LOGE("Callback function must not be null"); |
| return nullptr; |
| } |
| |
| if (!isServiceAvailable()) { |
| LOGE("CHRE Multiclient HAL is not enabled on this device"); |
| return nullptr; |
| } |
| |
| if (callback->version < kMinHalInterfaceVersion) { |
| LOGE("Callback interface version is %" PRIi32 ". It must be >= %" PRIi32, |
| callback->version, kMinHalInterfaceVersion); |
| return nullptr; |
| } |
| |
| return std::unique_ptr<HalClient>(new HalClient(callback, contextHubId)); |
| } |
| |
| HalError HalClient::initConnection() { |
| std::lock_guard<std::shared_mutex> lockGuard{mConnectionLock}; |
| |
| if (mContextHub != nullptr) { |
| LOGW("%s is already connected to CHRE HAL", mClientName.c_str()); |
| return HalError::SUCCESS; |
| } |
| |
| // Wait to connect to the service. Note that we don't do local retries |
| // because we're relying on the internal retries in |
| // AServiceManager_waitForService(). If HAL service has just restarted, it |
| // can take a few seconds to connect. |
| ndk::SpAIBinder binder{ |
| AServiceManager_waitForService(kAidlServiceName.c_str())}; |
| if (binder.get() == nullptr) { |
| return HalError::BINDER_CONNECTION_FAILED; |
| } |
| |
| // Link the death recipient to handle the binder disconnection event. |
| if (AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), this) != |
| STATUS_OK) { |
| LOGE("Failed to link the binder death recipient"); |
| return HalError::LINK_DEATH_RECIPIENT_FAILED; |
| } |
| |
| // Retrieve a handle of context hub service. |
| mContextHub = IContextHub::fromBinder(binder); |
| if (mContextHub == nullptr) { |
| LOGE("Got null context hub from the binder connection"); |
| return HalError::NULL_CONTEXT_HUB_FROM_BINDER; |
| } |
| |
| // Enforce the required interface version for the service. |
| int32_t version = 0; |
| mContextHub->getInterfaceVersion(&version); |
| if (version < kMinHalInterfaceVersion) { |
| LOGE("CHRE multiclient HAL interface version is %" PRIi32 |
| ". It must be >= %" PRIi32, |
| version, kMinHalInterfaceVersion); |
| mContextHub = nullptr; |
| return HalError::VERSION_TOO_LOW; |
| } |
| |
| // Register an IContextHubCallback. |
| ScopedAStatus status = |
| mContextHub->registerCallback(kDefaultContextHubId, mCallback); |
| if (!status.isOk()) { |
| LOGE("Unable to register callback: %s", status.getDescription().c_str()); |
| // At this moment it's guaranteed that mCallback is not null and |
| // kDefaultContextHubId is valid. So if the registerCallback() still fails |
| // it's a hard failure and CHRE HAL is treated as disconnected. |
| mContextHub = nullptr; |
| return HalError::CALLBACK_REGISTRATION_FAILED; |
| } |
| LOGI("%s is successfully (re)connected to CHRE HAL", mClientName.c_str()); |
| return HalError::SUCCESS; |
| } |
| |
| void HalClient::onHalDisconnected(void *cookie) { |
| int64_t startTime = ::android::elapsedRealtime(); |
| auto *halClient = static_cast<HalClient *>(cookie); |
| { |
| std::lock_guard<std::shared_mutex> lockGuard(halClient->mConnectionLock); |
| halClient->mContextHub = nullptr; |
| } |
| LOGW("%s is disconnected from CHRE HAL. Reconnecting...", |
| halClient->mClientName.c_str()); |
| |
| HalError result = halClient->initConnection(); |
| uint64_t duration = ::android::elapsedRealtime() - startTime; |
| if (result != HalError::SUCCESS) { |
| LOGE("Failed to fully reconnect to CHRE HAL after %" PRIu64 |
| "ms, HalErrorCode: %" PRIi32, |
| duration, result); |
| return; |
| } |
| tryReconnectEndpoints(halClient); |
| LOGI("%s is reconnected to CHRE HAL after %" PRIu64 "ms", |
| halClient->mClientName.c_str(), duration); |
| } |
| |
| ScopedAStatus HalClient::connectEndpoint( |
| const HostEndpointInfo &hostEndpointInfo) { |
| HostEndpointId endpointId = hostEndpointInfo.hostEndpointId; |
| if (isEndpointConnected(endpointId)) { |
| // Connecting the endpoint again even though it is already connected to let |
| // HAL and/or CHRE be the single place to control the behavior. |
| LOGW("Endpoint id %" PRIu16 " of %s is already connected", endpointId, |
| mClientName.c_str()); |
| } |
| ScopedAStatus result = callIfConnected( |
| [&hostEndpointInfo](const std::shared_ptr<IContextHub> &hub) { |
| return hub->onHostEndpointConnected(hostEndpointInfo); |
| }); |
| if (result.isOk()) { |
| insertConnectedEndpoint(hostEndpointInfo); |
| } else { |
| LOGE("Failed to connect endpoint id %" PRIu16 " of %s", |
| hostEndpointInfo.hostEndpointId, mClientName.c_str()); |
| } |
| return result; |
| } |
| |
| ScopedAStatus HalClient::disconnectEndpoint(HostEndpointId hostEndpointId) { |
| if (!isEndpointConnected(hostEndpointId)) { |
| // Disconnecting the endpoint again even though it is already disconnected |
| // to let HAL and/or CHRE be the single place to control the behavior. |
| LOGW("Endpoint id %" PRIu16 " of %s is already disconnected", |
| hostEndpointId, mClientName.c_str()); |
| } |
| ScopedAStatus result = callIfConnected( |
| [&hostEndpointId](const std::shared_ptr<IContextHub> &hub) { |
| return hub->onHostEndpointDisconnected(hostEndpointId); |
| }); |
| if (result.isOk()) { |
| removeConnectedEndpoint(hostEndpointId); |
| } else { |
| LOGE("Failed to disconnect the endpoint id %" PRIu16 " of %s", |
| hostEndpointId, mClientName.c_str()); |
| } |
| return result; |
| } |
| |
| ScopedAStatus HalClient::sendMessage(const ContextHubMessage &message) { |
| uint16_t hostEndpointId = message.hostEndPoint; |
| if (!isEndpointConnected(hostEndpointId)) { |
| // This is still allowed now but in the future an error will be returned. |
| LOGW("Endpoint id %" PRIu16 |
| " of %s is unknown or disconnected. Message sending will be skipped " |
| "in the future", |
| hostEndpointId, mClientName.c_str()); |
| } |
| return callIfConnected([&](const std::shared_ptr<IContextHub> &hub) { |
| return hub->sendMessageToHub(mContextHubId, message); |
| }); |
| } |
| |
| void HalClient::tryReconnectEndpoints(HalClient *halClient) { |
| LOGW("CHRE has restarted. Reconnecting endpoints of %s", |
| halClient->mClientName.c_str()); |
| std::lock_guard<std::shared_mutex> lockGuard( |
| halClient->mConnectedEndpointsLock); |
| for (const auto &[endpointId, endpointInfo] : |
| halClient->mConnectedEndpoints) { |
| if (!halClient |
| ->callIfConnected( |
| [&endpointInfo](const std::shared_ptr<IContextHub> &hub) { |
| return hub->onHostEndpointConnected(endpointInfo); |
| }) |
| .isOk()) { |
| LOGE("Failed to set up the connected state for endpoint %" PRIu16 |
| " of %s after HAL restarts.", |
| endpointId, halClient->mClientName.c_str()); |
| halClient->mConnectedEndpoints.erase(endpointId); |
| } else { |
| LOGI("Reconnected endpoint %" PRIu16 " of %s to CHRE HAL", endpointId, |
| halClient->mClientName.c_str()); |
| } |
| } |
| } |
| |
| HalClient::~HalClient() { |
| std::lock_guard<std::mutex> lock(mBackgroundConnectionFuturesLock); |
| for (const auto &future : mBackgroundConnectionFutures) { |
| // Calling std::thread.join() has chance to hang if the background thread |
| // being joined is still waiting for connecting to the service. Therefore |
| // waiting for the thread to finish here instead and logging the timeout |
| // every second until system kills the process to report the abnormality. |
| while (future.wait_for(std::chrono::seconds(1)) != |
| std::future_status::ready) { |
| LOGE( |
| "Failed to finish a background connection in time when HalClient is " |
| "being destructed. Waiting..."); |
| } |
| } |
| } |
| } // namespace android::chre |