1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef LOG_TAG
18 #define LOG_TAG "CHRE.HAL.CLIENT"
19 #endif
20 
21 #include "chre_host/hal_client.h"
22 #include "chre_host/log.h"
23 
24 #include <android-base/properties.h>
25 #include <android_chre_flags.h>
26 #include <utils/SystemClock.h>
27 
28 #include <cinttypes>
29 #include <thread>
30 
31 namespace android::chre {
32 
33 using ::aidl::android::hardware::contexthub::IContextHub;
34 using ::aidl::android::hardware::contexthub::IContextHubCallback;
35 using ::android::base::GetBoolProperty;
36 using ::ndk::ScopedAStatus;
37 
38 namespace {
39 constexpr char kHalEnabledProperty[]{"vendor.chre.multiclient_hal.enabled"};
40 
41 // Multiclient HAL needs getUuid() added since V3 to identify each client.
42 constexpr int kMinHalInterfaceVersion = 3;
43 }  // namespace
44 
isServiceAvailable()45 bool HalClient::isServiceAvailable() {
46   return GetBoolProperty(kHalEnabledProperty, /* default_value= */ false);
47 }
48 
reduceLockHolding()49 bool HalClient::reduceLockHolding() {
50   return flags::bug_fix_reduce_lock_holding_period();
51 }
52 
create(const std::shared_ptr<IContextHubCallback> & callback,int32_t contextHubId)53 std::unique_ptr<HalClient> HalClient::create(
54     const std::shared_ptr<IContextHubCallback> &callback,
55     int32_t contextHubId) {
56   if (callback == nullptr) {
57     LOGE("Callback function must not be null");
58     return nullptr;
59   }
60 
61   if (!isServiceAvailable()) {
62     LOGE("CHRE Multiclient HAL is not enabled on this device");
63     return nullptr;
64   }
65 
66   if (callback->version < kMinHalInterfaceVersion) {
67     LOGE("Callback interface version is %" PRIi32 ". It must be >= %" PRIi32,
68          callback->version, kMinHalInterfaceVersion);
69     return nullptr;
70   }
71 
72   return std::unique_ptr<HalClient>(new HalClient(callback, contextHubId));
73 }
74 
initConnection()75 HalError HalClient::initConnection() {
76   std::lock_guard<std::shared_mutex> lockGuard{mConnectionLock};
77 
78   if (mContextHub != nullptr) {
79     LOGW("%s is already connected to CHRE HAL", mClientName.c_str());
80     return HalError::SUCCESS;
81   }
82 
83   // Wait to connect to the service. Note that we don't do local retries
84   // because we're relying on the internal retries in
85   // AServiceManager_waitForService(). If HAL service has just restarted, it
86   // can take a few seconds to connect.
87   ndk::SpAIBinder binder{
88       AServiceManager_waitForService(kAidlServiceName.c_str())};
89   if (binder.get() == nullptr) {
90     return HalError::BINDER_CONNECTION_FAILED;
91   }
92 
93   // Link the death recipient to handle the binder disconnection event.
94   if (AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), this) !=
95       STATUS_OK) {
96     LOGE("Failed to link the binder death recipient");
97     return HalError::LINK_DEATH_RECIPIENT_FAILED;
98   }
99 
100   // Retrieve a handle of context hub service.
101   mContextHub = IContextHub::fromBinder(binder);
102   if (mContextHub == nullptr) {
103     LOGE("Got null context hub from the binder connection");
104     return HalError::NULL_CONTEXT_HUB_FROM_BINDER;
105   }
106 
107   // Enforce the required interface version for the service.
108   int32_t version = 0;
109   mContextHub->getInterfaceVersion(&version);
110   if (version < kMinHalInterfaceVersion) {
111     LOGE("CHRE multiclient HAL interface version is %" PRIi32
112          ". It must be >= %" PRIi32,
113          version, kMinHalInterfaceVersion);
114     mContextHub = nullptr;
115     return HalError::VERSION_TOO_LOW;
116   }
117 
118   // Register an IContextHubCallback.
119   ScopedAStatus status =
120       mContextHub->registerCallback(kDefaultContextHubId, mCallback);
121   if (!status.isOk()) {
122     LOGE("Unable to register callback: %s", status.getDescription().c_str());
123     // At this moment it's guaranteed that mCallback is not null and
124     // kDefaultContextHubId is valid. So if the registerCallback() still fails
125     // it's a hard failure and CHRE HAL is treated as disconnected.
126     mContextHub = nullptr;
127     return HalError::CALLBACK_REGISTRATION_FAILED;
128   }
129   LOGI("%s is successfully (re)connected to CHRE HAL", mClientName.c_str());
130   return HalError::SUCCESS;
131 }
132 
onHalDisconnected(void * cookie)133 void HalClient::onHalDisconnected(void *cookie) {
134   int64_t startTime = ::android::elapsedRealtime();
135   auto *halClient = static_cast<HalClient *>(cookie);
136   {
137     std::lock_guard<std::shared_mutex> lockGuard(halClient->mConnectionLock);
138     halClient->mContextHub = nullptr;
139   }
140   LOGW("%s is disconnected from CHRE HAL. Reconnecting...",
141        halClient->mClientName.c_str());
142 
143   HalError result = halClient->initConnection();
144   uint64_t duration = ::android::elapsedRealtime() - startTime;
145   if (result != HalError::SUCCESS) {
146     LOGE("Failed to fully reconnect to CHRE HAL after %" PRIu64
147          "ms, HalErrorCode: %" PRIi32,
148          duration, result);
149     return;
150   }
151   tryReconnectEndpoints(halClient);
152   LOGI("%s is reconnected to CHRE HAL after %" PRIu64 "ms",
153        halClient->mClientName.c_str(), duration);
154 }
155 
connectEndpoint(const HostEndpointInfo & hostEndpointInfo)156 ScopedAStatus HalClient::connectEndpoint(
157     const HostEndpointInfo &hostEndpointInfo) {
158   HostEndpointId endpointId = hostEndpointInfo.hostEndpointId;
159   if (isEndpointConnected(endpointId)) {
160     // Connecting the endpoint again even though it is already connected to let
161     // HAL and/or CHRE be the single place to control the behavior.
162     LOGW("Endpoint id %" PRIu16 " of %s is already connected", endpointId,
163          mClientName.c_str());
164   }
165   ScopedAStatus result = callIfConnected(
166       [&hostEndpointInfo](const std::shared_ptr<IContextHub> &hub) {
167         return hub->onHostEndpointConnected(hostEndpointInfo);
168       });
169   if (result.isOk()) {
170     insertConnectedEndpoint(hostEndpointInfo);
171   } else {
172     LOGE("Failed to connect endpoint id %" PRIu16 " of %s",
173          hostEndpointInfo.hostEndpointId, mClientName.c_str());
174   }
175   return result;
176 }
177 
disconnectEndpoint(HostEndpointId hostEndpointId)178 ScopedAStatus HalClient::disconnectEndpoint(HostEndpointId hostEndpointId) {
179   if (!isEndpointConnected(hostEndpointId)) {
180     // Disconnecting the endpoint again even though it is already disconnected
181     // to let HAL and/or CHRE be the single place to control the behavior.
182     LOGW("Endpoint id %" PRIu16 " of %s is already disconnected",
183          hostEndpointId, mClientName.c_str());
184   }
185   ScopedAStatus result = callIfConnected(
186       [&hostEndpointId](const std::shared_ptr<IContextHub> &hub) {
187         return hub->onHostEndpointDisconnected(hostEndpointId);
188       });
189   if (result.isOk()) {
190     removeConnectedEndpoint(hostEndpointId);
191   } else {
192     LOGE("Failed to disconnect the endpoint id %" PRIu16 " of %s",
193          hostEndpointId, mClientName.c_str());
194   }
195   return result;
196 }
197 
sendMessage(const ContextHubMessage & message)198 ScopedAStatus HalClient::sendMessage(const ContextHubMessage &message) {
199   uint16_t hostEndpointId = message.hostEndPoint;
200   if (!isEndpointConnected(hostEndpointId)) {
201     // This is still allowed now but in the future an error will be returned.
202     LOGW("Endpoint id %" PRIu16
203          " of %s is unknown or disconnected. Message sending will be skipped "
204          "in the future",
205          hostEndpointId, mClientName.c_str());
206   }
207   return callIfConnected([&](const std::shared_ptr<IContextHub> &hub) {
208     return hub->sendMessageToHub(mContextHubId, message);
209   });
210 }
211 
tryReconnectEndpoints(HalClient * halClient)212 void HalClient::tryReconnectEndpoints(HalClient *halClient) {
213   LOGW("CHRE has restarted. Reconnecting endpoints of %s",
214        halClient->mClientName.c_str());
215   std::lock_guard<std::shared_mutex> lockGuard(
216       halClient->mConnectedEndpointsLock);
217   for (const auto &[endpointId, endpointInfo] :
218        halClient->mConnectedEndpoints) {
219     if (!halClient
220              ->callIfConnected(
221                  [&endpointInfo](const std::shared_ptr<IContextHub> &hub) {
222                    return hub->onHostEndpointConnected(endpointInfo);
223                  })
224              .isOk()) {
225       LOGE("Failed to set up the connected state for endpoint %" PRIu16
226            " of %s after HAL restarts.",
227            endpointId, halClient->mClientName.c_str());
228       halClient->mConnectedEndpoints.erase(endpointId);
229     } else {
230       LOGI("Reconnected endpoint %" PRIu16 " of %s to CHRE HAL", endpointId,
231            halClient->mClientName.c_str());
232     }
233   }
234 }
235 
~HalClient()236 HalClient::~HalClient() {
237   std::lock_guard<std::mutex> lock(mBackgroundConnectionFuturesLock);
238   for (const auto &future : mBackgroundConnectionFutures) {
239     // Calling std::thread.join() has chance to hang if the background thread
240     // being joined is still waiting for connecting to the service. Therefore
241     // waiting for the thread to finish here instead and logging the timeout
242     // every second until system kills the process to report the abnormality.
243     while (future.wait_for(std::chrono::seconds(1)) !=
244            std::future_status::ready) {
245       LOGE(
246           "Failed to finish a background connection in time when HalClient is "
247           "being destructed. Waiting...");
248     }
249   }
250 }
251 }  // namespace android::chre