/* * 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. */ #ifndef ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_ #define ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_ #include #include #include #include #include #include #include #include #include #include "chre_host/log.h" #include "hal_client_id.h" using aidl::android::hardware::contexthub::ContextHubMessage; using aidl::android::hardware::contexthub::HostEndpointInfo; using aidl::android::hardware::contexthub::IContextHubCallback; using android::chre::FragmentedLoadTransaction; using HostEndpointId = uint16_t; namespace android::hardware::contexthub::common::implementation { /** * A class managing clients for Context Hub HAL. * * A HAL client is defined as a user calling the IContextHub API. The main * purpose of this class are: * - to assign a unique HalClientId identifying each client; * - to maintain a mapping between client ids and HalClientInfos; * - to maintain a mapping between client ids and their endpoint ids. * * There are two types of ids HalClientManager will track, host endpoint id and * client id. A host endpoint id, which is defined at * hardware/interfaces/contexthub/aidl/android/hardware/contexthub/ContextHubMessage.aidl, * identifies a host app that communicates with a HAL client. A client id * identifies a HAL client, which is the layer beneath the host apps, such as * ContextHubService. Multiple apps with different host endpoint IDs can have * the same client ID. * * For a host endpoint connected to ContextHubService, its endpoint id is kept *in the form below during the communication with CHRE. * * 0 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |0| endpoint_id | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * For vendor host endpoints, the client id is embedded into the endpoint id * before sending a message to CHRE. When that happens, the highest bit is set * to 1 and the endpoint id is mutated to the format below: * * 0 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |1| client_id |endpoint_id| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Note that HalClientManager is not responsible for generating endpoint ids, * which should be managed by HAL clients themselves. */ class HalClientManager { public: HalClientManager(); virtual ~HalClientManager() = default; /** Disable copy constructor and copy assignment to avoid duplicates. */ HalClientManager(HalClientManager &) = delete; void operator=(const HalClientManager &) = delete; /** * Gets the client id allocated to the current HAL client. * * The current HAL client is identified by its process id, which is retrieved * by calling AIBinder_getCallingPid(). If the process doesn't have any client * id assigned, HalClientManager will create one mapped to its process id. * * @return client id assigned to the calling process, or kDefaultHalClientId * if the process id is not found. */ HalClientId getClientId(); /** * Gets the callback for the current HAL client identified by the clientId. * * @return callback previously registered. nullptr is returned if the clientId * is not found. */ std::shared_ptr getCallback(HalClientId clientId); /** * Registers a IContextHubCallback function mapped to the current client's * client id. @p deathRecipient and @p deathRecipientCookie are used to unlink * the previous registered callback for the same client, if any. * * @param callback a function incurred to handle the client death event. * @param deathRecipient a handle on the death notification. * @param deathRecipientCookie the data used by the callback. * * @return true if success, otherwise false. */ bool registerCallback( const std::shared_ptr &callback, const ndk::ScopedAIBinder_DeathRecipient &deathRecipient, void *deathRecipientCookie); /** * Registers a FragmentedLoadTransaction for the current HAL client. * * At this moment only one active transaction, either load or unload, is * supported. * * @return true if success, otherwise false. */ bool registerPendingLoadTransaction( std::unique_ptr transaction); /** * Returns true if the load transaction matches the arguments provided. */ bool isPendingLoadTransactionExpected(HalClientId clientId, uint32_t transactionId, uint32_t currentFragmentId) { const std::lock_guard lock(mLock); return isPendingLoadTransactionMatchedLocked(clientId, transactionId, currentFragmentId); } /** * Clears the pending load transaction. * * This function is called to proactively clear out a pending load transaction * that is not timed out yet. * */ void resetPendingLoadTransaction(); /** * Gets the next FragmentedLoadRequest from PendingLoadTransaction if it's * available. * * @return an optional FragmentedLoadRequest, std::nullopt if unavailable. */ std::optional getNextFragmentedLoadRequest(); /** * Registers the current HAL client as having a pending unload transaction. * * At this moment only one active transaction, either load or unload, is * supported. * * @return true if success, otherwise false. */ bool registerPendingUnloadTransaction(uint32_t transactionId); /** * Clears the pending unload transaction. * * This function is called to proactively clear out a pending unload * transaction that is not timed out yet. @p clientId and @p * transactionId must match the existing pending transaction. * * @param clientId the client id of the caller. * @param transactionId unique id of the transaction. * * @return true if the pending transaction is cleared, otherwise false. */ bool resetPendingUnloadTransaction(HalClientId clientId, uint32_t transactionId); /** * Registers an endpoint id when it is connected to HAL. * * @return true if success, otherwise false. */ bool registerEndpointId(const HostEndpointId &endpointId); /** * Removes an endpoint id when it is disconnected to HAL. * * @return true if success, otherwise false. */ bool removeEndpointId(const HostEndpointId &endpointId); /** * Mutates the endpoint id if the hal client is not the framework service. * * @return true if success, otherwise false. */ bool mutateEndpointIdFromHostIfNeeded(const pid_t &pid, HostEndpointId &endpointId); /** Returns the original endpoint id sent by the host client. */ static HostEndpointId convertToOriginalEndpointId( const HostEndpointId &endpointId); /** * Gets all the connected endpoints for the client identified by the pid. * * @return the pointer to the endpoint id set if the client is identifiable, * otherwise nullptr. */ const std::unordered_set *getAllConnectedEndpoints(pid_t pid); /** Sends a message to every connected endpoints. */ void sendMessageForAllCallbacks( const ContextHubMessage &message, const std::vector &messageParams); std::shared_ptr getCallbackForEndpoint( const HostEndpointId &endpointId); /** * Handles the client death event. * * @param pid of the client that loses the binder connection to the HAL. * @param deathRecipient to be unlinked with the client's callback */ void handleClientDeath( pid_t pid, const ndk::ScopedAIBinder_DeathRecipient &deathRecipient); /** Handles CHRE restart event. */ void handleChreRestart(); protected: static constexpr char kSystemServerName[] = "system_server"; static constexpr char kClientMappingFilePath[] = "/data/vendor/chre/chre_hal_clients.json"; static constexpr char kJsonClientId[] = "ClientId"; static constexpr char kJsonProcessName[] = "ProcessName"; static constexpr int64_t kTransactionTimeoutThresholdMs = 5000; // 5 seconds static constexpr uint8_t kNumOfBitsForEndpointId = 6; static constexpr HostEndpointId kMaxVendorEndpointId = (1 << kNumOfBitsForEndpointId) - 1; // The endpoint id is from a vendor client if the highest bit is set to 1. static constexpr HostEndpointId kVendorEndpointIdBitMask = 0x8000; struct HalClientInfo { explicit HalClientInfo(const std::shared_ptr &callback, void *cookie) { this->callback = callback; this->deathRecipientCookie = cookie; } HalClientInfo() = default; std::shared_ptr callback; // cookie is used by the death recipient's linked callback void *deathRecipientCookie{}; std::unordered_set endpointIds{}; }; struct PendingTransaction { PendingTransaction(HalClientId clientId, uint32_t transactionId, int64_t registeredTimeMs) { this->clientId = clientId; this->transactionId = transactionId; this->registeredTimeMs = registeredTimeMs; } HalClientId clientId; uint32_t transactionId; int64_t registeredTimeMs; }; /** * PendingLoadTransaction tracks ongoing load transactions. */ struct PendingLoadTransaction : public PendingTransaction { PendingLoadTransaction( HalClientId clientId, int64_t registeredTimeMs, uint32_t currentFragmentId, std::unique_ptr transaction) : PendingTransaction(clientId, transaction->getTransactionId(), registeredTimeMs) { this->currentFragmentId = currentFragmentId; this->transaction = std::move(transaction); } uint32_t currentFragmentId; // the fragment id being sent out. std::unique_ptr transaction; [[nodiscard]] std::string toString() const { using android::internal::ToString; return "[Load transaction: client id " + ToString(clientId) + ", Transaction id " + ToString(transaction->getTransactionId()) + ", fragment id " + ToString(currentFragmentId) + "]"; } }; /** * Creates a client id to uniquely identify a HAL client. * * A file is maintained on the device for the mappings between client names * and client ids so that if a client has connected to HAL before the same * client id is always assigned to it. * * mLock must be held when this function is called. * * @param processName the process name of the client */ virtual std::optional createClientIdLocked( const std::string &processName); /** * Returns true if @p clientId and @p transactionId match the * corresponding values in @p transaction. * * mLock must be held when this function is called. */ static bool isPendingTransactionMatchedLocked( HalClientId clientId, uint32_t transactionId, const std::optional &transaction) { return transaction.has_value() && transaction->clientId == clientId && transaction->transactionId == transactionId; } /** * Returns true if the load transaction is expected. * * mLock must be held when this function is called. */ bool isPendingLoadTransactionMatchedLocked(HalClientId clientId, uint32_t transactionId, uint32_t currentFragmentId); /** * Checks if the transaction registration is allowed and clears out any stale * pending transaction if possible. * * This function is called when registering a new transaction. The reason that * we still proceed when there is already a pending transaction is because we * don't want a stale one, for whatever reason, to block future transactions. * However, every transaction is guaranteed to have up to * kTransactionTimeoutThresholdMs to finish. * * mLock must be held when this function is called. * * @param clientId id of the client trying to register the transaction * * @return true if registration is allowed, otherwise false. */ bool isNewTransactionAllowedLocked(HalClientId clientId); /** * Returns true if the clientId is being used. * * mLock must be held when this function is called. */ inline bool isAllocatedClientIdLocked(HalClientId clientId) { return mClientIdsToClientInfo.find(clientId) != mClientIdsToClientInfo.end(); } /** * Returns true if the pid is being used to identify a client. * * mLock must be held when this function is called. */ inline bool isKnownPIdLocked(pid_t pid) { return mPIdsToClientIds.find(pid) != mPIdsToClientIds.end(); } /** Returns true if the endpoint id is within the accepted range. */ [[nodiscard]] inline bool isValidEndpointId( const HalClientId &clientId, const HostEndpointId &endpointId) const { if (clientId != mFrameworkServiceClientId) { return endpointId <= kMaxVendorEndpointId; } return true; } /** * Overrides the old callback registered with the client. * * @return true if success, otherwise false */ bool overrideCallbackLocked( pid_t pid, const std::shared_ptr &callback, const ndk::ScopedAIBinder_DeathRecipient &deathRecipient, void *deathRecipientCookie); /** * Extracts the client id from the endpoint id. * * @param endpointId the endpoint id received from CHRE, before any conversion */ [[nodiscard]] inline HalClientId getClientIdFromEndpointId( const HostEndpointId &endpointId) const { if (endpointId & kVendorEndpointIdBitMask) { return endpointId >> kNumOfBitsForEndpointId & kMaxHalClientId; } return mFrameworkServiceClientId; } std::string getProcessName(pid_t /*pid*/) { // TODO(b/274597758): this is a temporary solution that should be updated // after b/274597758 is resolved. if (mIsFirstClient) { mIsFirstClient = false; return kSystemServerName; } return "the_vendor_client"; } bool mIsFirstClient = true; // next available client id HalClientId mNextClientId = kDefaultHalClientId + 1; // framework service client id HalClientId mFrameworkServiceClientId = kDefaultHalClientId; // The lock guarding the access to clients' states and pending transactions std::mutex mLock; // Map from process name to client id which stays consistent with the file // stored at kClientMappingFilePath std::unordered_map mProcessNamesToClientIds{}; // Map from pids to client ids std::unordered_map mPIdsToClientIds{}; // Map from client ids to ClientInfos std::unordered_map mClientIdsToClientInfo{}; // States tracking pending transactions std::optional mPendingLoadTransaction = std::nullopt; std::optional mPendingUnloadTransaction = std::nullopt; }; } // namespace android::hardware::contexthub::common::implementation #endif // ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_