| /* |
| * Copyright (C) 2022 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 "hal_client_manager.h" |
| #include <aidl/android/hardware/contexthub/AsyncEventType.h> |
| #include <android-base/strings.h> |
| #include <json/json.h> |
| #include <utils/SystemClock.h> |
| #include <fstream> |
| |
| namespace android::hardware::contexthub::common::implementation { |
| |
| using aidl::android::hardware::contexthub::AsyncEventType; |
| using aidl::android::hardware::contexthub::ContextHubMessage; |
| using aidl::android::hardware::contexthub::HostEndpointInfo; |
| using aidl::android::hardware::contexthub::IContextHubCallback; |
| using HalClient = HalClientManager::HalClient; |
| |
| namespace { |
| bool getClientMappingsFromFile(const std::string &filePath, |
| Json::Value &mappings) { |
| std::fstream file(filePath); |
| Json::CharReaderBuilder builder; |
| return file.good() && |
| Json::parseFromStream(builder, file, &mappings, /* errs= */ nullptr); |
| } |
| } // namespace |
| |
| HalClient *HalClientManager::getClientByField( |
| const std::function<bool(const HalClient &client)> &fieldMatcher) { |
| for (HalClient &client : mClients) { |
| if (fieldMatcher(client)) { |
| return &client; |
| } |
| } |
| return nullptr; |
| } |
| |
| HalClient *HalClientManager::getClientByClientIdLocked(HalClientId clientId) { |
| return getClientByField([&clientId](const HalClient &client) { |
| return client.clientId == clientId; |
| }); |
| } |
| |
| HalClient *HalClientManager::getClientByUuidLocked(const std::string &uuid) { |
| return getClientByField( |
| [&uuid](const HalClient &client) { return client.uuid == uuid; }); |
| } |
| |
| HalClient *HalClientManager::getClientByProcessIdLocked(pid_t pid) { |
| return getClientByField( |
| [&pid](const HalClient &client) { return client.pid == pid; }); |
| } |
| |
| bool HalClientManager::createClientLocked( |
| const std::string &uuid, pid_t pid, |
| const std::shared_ptr<IContextHubCallback> &callback, |
| void *deathRecipientCookie) { |
| if (mClients.size() > kMaxNumOfHalClients || |
| mNextClientId > kMaxHalClientId) { |
| LOGE("Too many HAL clients registered which should never happen."); |
| return false; |
| } |
| mClients.emplace_back(uuid, mNextClientId, pid, callback, |
| deathRecipientCookie); |
| // Update the json list with the new mapping |
| Json::Value mappings; |
| for (const auto &client : mClients) { |
| Json::Value mapping; |
| mapping[kJsonUuid] = client.uuid; |
| mapping[kJsonClientId] = client.clientId; |
| mappings.append(mapping); |
| } |
| // write to the file; Create the file if it doesn't exist |
| Json::StreamWriterBuilder factory; |
| std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter()); |
| std::ofstream fileStream(mClientMappingFilePath); |
| writer->write(mappings, &fileStream); |
| fileStream << std::endl; |
| mNextClientId++; |
| return true; |
| } |
| |
| HalClientId HalClientManager::getClientId(pid_t pid) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| const HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE("Failed to find the client id for pid %d", pid); |
| return ::chre::kHostClientIdUnspecified; |
| } |
| return client->clientId; |
| } |
| |
| std::shared_ptr<IContextHubCallback> HalClientManager::getCallback( |
| HalClientId clientId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| const HalClient *client = getClientByClientIdLocked(clientId); |
| if (client == nullptr) { |
| LOGE("Failed to find the callback for the client id %" PRIu16, clientId); |
| return nullptr; |
| } |
| return client->callback; |
| } |
| |
| bool HalClientManager::registerCallback( |
| pid_t pid, const std::shared_ptr<IContextHubCallback> &callback, |
| void *deathRecipientCookie) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| HalClient *client = getClientByProcessIdLocked(pid); |
| if (client != nullptr) { |
| LOGW("The pid %d has already registered. Overriding its callback.", pid); |
| if (!mDeadClientUnlinker(client->callback, client->deathRecipientCookie)) { |
| LOGE("Unable to unlink the old callback for pid %d", pid); |
| return false; |
| } |
| client->callback.reset(); |
| client->callback = callback; |
| client->deathRecipientCookie = deathRecipientCookie; |
| return true; |
| } |
| std::string uuid = getUuidLocked(); |
| client = getClientByUuidLocked(uuid); |
| if (client != nullptr) { |
| if (client->pid != HalClient::PID_UNSET) { |
| // A client is trying to connect to HAL from a different process. But the |
| // previous connection is still active because otherwise the pid will be |
| // cleared in handleClientDeath(). |
| LOGE("Client (uuid=%s) already has a connection to HAL.", uuid.c_str()); |
| return false; |
| } |
| // For a known client the previous assigned clientId will be reused. |
| client->reset(/* processId= */ pid, |
| /* contextHubCallback= */ callback, |
| /* cookie= */ deathRecipientCookie); |
| return true; |
| } |
| return createClientLocked(uuid, pid, callback, deathRecipientCookie); |
| } |
| |
| void HalClientManager::handleClientDeath(pid_t pid) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE("Failed to locate the dead pid %d", pid); |
| return; |
| } |
| |
| if (!mDeadClientUnlinker(client->callback, client->deathRecipientCookie)) { |
| LOGE("Unable to unlink the old callback for pid %d in death handler", pid); |
| } |
| client->reset(/* processId= */ HalClient::PID_UNSET, |
| /* contextHubCallback= */ nullptr, /* cookie= */ nullptr); |
| |
| if (mPendingLoadTransaction.has_value() && |
| mPendingLoadTransaction->clientId == client->clientId) { |
| mPendingLoadTransaction.reset(); |
| } |
| if (mPendingUnloadTransaction.has_value() && |
| mPendingUnloadTransaction->clientId == client->clientId) { |
| mPendingLoadTransaction.reset(); |
| } |
| LOGI("Process %" PRIu32 " is disconnected from HAL.", pid); |
| } |
| |
| bool HalClientManager::registerPendingLoadTransaction( |
| pid_t pid, std::unique_ptr<chre::FragmentedLoadTransaction> transaction) { |
| if (transaction->isComplete()) { |
| LOGW("No need to register a completed load transaction."); |
| return false; |
| } |
| |
| const std::lock_guard<std::mutex> lock(mLock); |
| const HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE("Unknown HAL client when registering its pending load transaction."); |
| return false; |
| } |
| if (!isNewTransactionAllowedLocked(client->clientId)) { |
| return false; |
| } |
| mPendingLoadTransaction.emplace( |
| client->clientId, /* registeredTimeMs= */ android::elapsedRealtime(), |
| /* currentFragmentId= */ 0, std::move(transaction)); |
| return true; |
| } |
| |
| std::optional<chre::FragmentedLoadRequest> |
| HalClientManager::getNextFragmentedLoadRequest() { |
| const std::lock_guard<std::mutex> lock(mLock); |
| if (mPendingLoadTransaction->transaction->isComplete()) { |
| LOGI("Pending load transaction %" PRIu32 |
| " is finished with client %" PRIu16, |
| mPendingLoadTransaction->transaction->getTransactionId(), |
| mPendingLoadTransaction->clientId); |
| mPendingLoadTransaction.reset(); |
| return std::nullopt; |
| } |
| auto request = mPendingLoadTransaction->transaction->getNextRequest(); |
| mPendingLoadTransaction->currentFragmentId = request.fragmentId; |
| LOGD("Client %" PRIu16 " has fragment #%zu ready", |
| mPendingLoadTransaction->clientId, request.fragmentId); |
| return request; |
| } |
| |
| bool HalClientManager::registerPendingUnloadTransaction( |
| pid_t pid, uint32_t transactionId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| const HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE("Unknown HAL client when registering its pending unload transaction."); |
| return false; |
| } |
| if (!isNewTransactionAllowedLocked(client->clientId)) { |
| return false; |
| } |
| mPendingUnloadTransaction.emplace( |
| client->clientId, transactionId, |
| /* registeredTimeMs= */ android::elapsedRealtime()); |
| return true; |
| } |
| |
| bool HalClientManager::isNewTransactionAllowedLocked(HalClientId clientId) { |
| if (mPendingLoadTransaction.has_value()) { |
| auto timeElapsedMs = |
| android::elapsedRealtime() - mPendingLoadTransaction->registeredTimeMs; |
| if (timeElapsedMs < kTransactionTimeoutThresholdMs) { |
| LOGE("Rejects client %" PRIu16 |
| "'s transaction because an active load " |
| "transaction %" PRIu32 " with current fragment id %" PRIu32 |
| " from client %" PRIu16 " exists. Try again later.", |
| clientId, mPendingLoadTransaction->transaction->getTransactionId(), |
| mPendingLoadTransaction->currentFragmentId, |
| mPendingLoadTransaction->clientId); |
| return false; |
| } |
| LOGE("Client %" PRIu16 "'s pending load transaction %" PRIu32 |
| " with current fragment id %" PRIu32 |
| " is overridden by client %" PRIu16 |
| " after holding the slot for %" PRIu64 " ms", |
| mPendingLoadTransaction->clientId, |
| mPendingLoadTransaction->transaction->getTransactionId(), |
| mPendingLoadTransaction->currentFragmentId, clientId, timeElapsedMs); |
| mPendingLoadTransaction.reset(); |
| return true; |
| } |
| if (mPendingUnloadTransaction.has_value()) { |
| auto timeElapsedMs = android::elapsedRealtime() - |
| mPendingUnloadTransaction->registeredTimeMs; |
| if (timeElapsedMs < kTransactionTimeoutThresholdMs) { |
| LOGE("Rejects client %" PRIu16 |
| "'s transaction because an active unload " |
| "transaction %" PRIu32 " from client %" PRIu16 |
| " exists. Try again later.", |
| clientId, mPendingUnloadTransaction->transactionId, |
| mPendingUnloadTransaction->clientId); |
| return false; |
| } |
| LOGE("A pending unload transaction %" PRIu32 |
| " registered by client %" PRIu16 |
| " is overridden by a new transaction from client %" PRIu16 |
| " after holding the slot for %" PRIu64 "ms", |
| mPendingUnloadTransaction->transactionId, |
| mPendingUnloadTransaction->clientId, clientId, timeElapsedMs); |
| mPendingUnloadTransaction.reset(); |
| return true; |
| } |
| return true; |
| } |
| |
| bool HalClientManager::registerEndpointId(pid_t pid, |
| const HostEndpointId &endpointId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE( |
| "Unknown HAL client (pid %d). Register the callback before registering " |
| "an endpoint.", |
| pid); |
| return false; |
| } |
| if (!isValidEndpointId(client, endpointId)) { |
| LOGE("Endpoint id %" PRIu16 " from process %d is out of range.", endpointId, |
| pid); |
| return false; |
| } |
| if (client->endpointIds.find(endpointId) != client->endpointIds.end()) { |
| LOGW("The endpoint %" PRIu16 " is already connected.", endpointId); |
| return false; |
| } |
| client->endpointIds.insert(endpointId); |
| LOGI("Endpoint id %" PRIu16 " is connected to client %" PRIu16, endpointId, |
| client->clientId); |
| return true; |
| } |
| |
| bool HalClientManager::removeEndpointId(pid_t pid, |
| const HostEndpointId &endpointId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE( |
| "Unknown HAL client (pid %d). A callback should have been registered " |
| "before removing an endpoint.", |
| pid); |
| return false; |
| } |
| if (!isValidEndpointId(client, endpointId)) { |
| LOGE("Endpoint id %" PRIu16 " from process %d is out of range.", endpointId, |
| pid); |
| return false; |
| } |
| if (client->endpointIds.find(endpointId) == client->endpointIds.end()) { |
| LOGW("The endpoint %" PRIu16 " is not connected.", endpointId); |
| return false; |
| } |
| client->endpointIds.erase(endpointId); |
| LOGI("Endpoint id %" PRIu16 " is removed from client %" PRIu16, endpointId, |
| client->clientId); |
| return true; |
| } |
| |
| std::shared_ptr<IContextHubCallback> HalClientManager::getCallbackForEndpoint( |
| const HostEndpointId mutatedEndpointId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| HalClient *client; |
| if (mutatedEndpointId & kVendorEndpointIdBitMask) { |
| HalClientId clientId = |
| mutatedEndpointId >> kNumOfBitsForEndpointId & kMaxHalClientId; |
| client = getClientByClientIdLocked(clientId); |
| } else { |
| client = getClientByUuidLocked(kSystemServerUuid); |
| } |
| if (client == nullptr) { |
| LOGE("Unknown endpoint id %" PRIu16 ". Please register the callback first.", |
| mutatedEndpointId); |
| return nullptr; |
| } |
| return client->callback; |
| } |
| |
| void HalClientManager::sendMessageForAllCallbacks( |
| const ContextHubMessage &message, |
| const std::vector<std::string> &messageParams) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| for (const auto &client : mClients) { |
| if (client.callback != nullptr) { |
| client.callback->handleContextHubMessage(message, messageParams); |
| } |
| } |
| } |
| |
| const std::unordered_set<HostEndpointId> |
| *HalClientManager::getAllConnectedEndpoints(pid_t pid) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| const HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE("Unknown HAL client with pid %d", pid); |
| return nullptr; |
| } |
| return &(client->endpointIds); |
| } |
| |
| bool HalClientManager::mutateEndpointIdFromHostIfNeeded( |
| pid_t pid, HostEndpointId &endpointId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| const HalClient *client = getClientByProcessIdLocked(pid); |
| if (client == nullptr) { |
| LOGE("Unknown HAL client with pid %d", pid); |
| return false; |
| } |
| // no need to mutate client id for framework service |
| if (client->uuid != kSystemServerUuid) { |
| endpointId = kVendorEndpointIdBitMask | |
| client->clientId << kNumOfBitsForEndpointId | endpointId; |
| } |
| return true; |
| } |
| |
| HostEndpointId HalClientManager::convertToOriginalEndpointId( |
| const HostEndpointId &endpointId) { |
| if (endpointId & kVendorEndpointIdBitMask) { |
| return endpointId & kMaxVendorEndpointId; |
| } |
| return endpointId; |
| } |
| |
| HalClientManager::HalClientManager(DeadClientUnlinker deadClientUnlinker, |
| const std::string &clientIdMappingFilePath) { |
| mDeadClientUnlinker = std::move(deadClientUnlinker); |
| mClientMappingFilePath = clientIdMappingFilePath; |
| // Parses the file to construct a mapping from process names to client ids. |
| Json::Value mappings; |
| if (!getClientMappingsFromFile(mClientMappingFilePath, mappings)) { |
| // TODO(b/247124878): When the device was firstly booted up the file doesn't |
| // exist which is expected. Consider to create a default file to avoid |
| // confusions. |
| LOGW("Unable to find and read %s.", mClientMappingFilePath.c_str()); |
| return; |
| } |
| for (int i = 0; i < mappings.size(); i++) { |
| Json::Value mapping = mappings[i]; |
| if (!mapping.isMember(kJsonClientId) || !mapping.isMember(kJsonUuid)) { |
| LOGE("Unable to find expected key name for the entry %d", i); |
| continue; |
| } |
| std::string uuid = mapping[kJsonUuid].asString(); |
| auto clientId = static_cast<HalClientId>(mapping[kJsonClientId].asUInt()); |
| mClients.emplace_back(uuid, clientId); |
| // mNextClientId should always hold the next available client id |
| if (mNextClientId <= clientId) { |
| mNextClientId = clientId + 1; |
| } |
| } |
| } |
| |
| bool HalClientManager::isPendingLoadTransactionMatchedLocked( |
| HalClientId clientId, uint32_t transactionId, uint32_t currentFragmentId) { |
| bool success = |
| isPendingTransactionMatchedLocked(clientId, transactionId, |
| mPendingLoadTransaction) && |
| mPendingLoadTransaction->currentFragmentId == currentFragmentId; |
| if (!success) { |
| if (mPendingLoadTransaction.has_value()) { |
| LOGE("Transaction of client %" PRIu16 " transaction %" PRIu32 |
| " fragment %" PRIu32 |
| " doesn't match the current pending transaction (client %" PRIu16 |
| " transaction %" PRIu32 " fragment %" PRIu32 ").", |
| clientId, transactionId, currentFragmentId, |
| mPendingLoadTransaction->clientId, |
| mPendingLoadTransaction->transactionId, |
| mPendingLoadTransaction->currentFragmentId); |
| } else { |
| LOGE("Transaction of client %" PRIu16 " transaction %" PRIu32 |
| " fragment %" PRIu32 " doesn't match any pending transaction.", |
| clientId, transactionId, currentFragmentId); |
| } |
| } |
| return success; |
| } |
| |
| void HalClientManager::resetPendingLoadTransaction() { |
| const std::lock_guard<std::mutex> lock(mLock); |
| mPendingLoadTransaction.reset(); |
| } |
| |
| bool HalClientManager::resetPendingUnloadTransaction(HalClientId clientId, |
| uint32_t transactionId) { |
| const std::lock_guard<std::mutex> lock(mLock); |
| // Only clear a pending transaction when the client id and the transaction id |
| // are both matched |
| if (isPendingTransactionMatchedLocked(clientId, transactionId, |
| mPendingUnloadTransaction)) { |
| LOGI("Clears out the pending unload transaction: client id %" PRIu16 |
| ", transaction id %" PRIu32, |
| clientId, transactionId); |
| mPendingUnloadTransaction.reset(); |
| return true; |
| } |
| LOGW("Client %" PRIu16 " doesn't have a pending unload transaction %" PRIu32 |
| ". Skip resetting", |
| clientId, transactionId); |
| return false; |
| } |
| |
| void HalClientManager::handleChreRestart() { |
| { |
| const std::lock_guard<std::mutex> lock(mLock); |
| mPendingLoadTransaction.reset(); |
| mPendingUnloadTransaction.reset(); |
| for (HalClient &client : mClients) { |
| client.endpointIds.clear(); |
| } |
| } |
| // Incurs callbacks without holding the lock to avoid deadlocks. |
| for (const HalClient &client : mClients) { |
| if (client.callback != nullptr) { |
| client.callback->handleContextHubAsyncEvent(AsyncEventType::RESTARTED); |
| } |
| } |
| } |
| } // namespace android::hardware::contexthub::common::implementation |