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
create(const std::shared_ptr<IContextHubCallback> & callback,int32_t contextHubId)49 std::unique_ptr<HalClient> HalClient::create(
50 const std::shared_ptr<IContextHubCallback> &callback,
51 int32_t contextHubId) {
52 if (callback == nullptr) {
53 LOGE("Callback function must not be null");
54 return nullptr;
55 }
56
57 if (!isServiceAvailable()) {
58 LOGE("CHRE Multiclient HAL is not enabled on this device");
59 return nullptr;
60 }
61
62 if (callback->version < kMinHalInterfaceVersion) {
63 LOGE("Callback interface version is %" PRIi32 ". It must be >= %" PRIi32,
64 callback->version, kMinHalInterfaceVersion);
65 return nullptr;
66 }
67
68 return std::unique_ptr<HalClient>(new HalClient(callback, contextHubId));
69 }
70
initConnection()71 HalError HalClient::initConnection() {
72 std::lock_guard<std::shared_mutex> lockGuard{mConnectionLock};
73
74 if (mContextHub != nullptr) {
75 LOGW("%s is already connected to CHRE HAL", mClientName.c_str());
76 return HalError::SUCCESS;
77 }
78
79 // Wait to connect to the service. Note that we don't do local retries
80 // because we're relying on the internal retries in
81 // AServiceManager_waitForService(). If HAL service has just restarted, it
82 // can take a few seconds to connect.
83 ndk::SpAIBinder binder{
84 AServiceManager_waitForService(kAidlServiceName.c_str())};
85 if (binder.get() == nullptr) {
86 return HalError::BINDER_CONNECTION_FAILED;
87 }
88
89 // Link the death recipient to handle the binder disconnection event.
90 if (AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), this) !=
91 STATUS_OK) {
92 LOGE("Failed to link the binder death recipient");
93 return HalError::LINK_DEATH_RECIPIENT_FAILED;
94 }
95
96 // Retrieve a handle of context hub service.
97 mContextHub = IContextHub::fromBinder(binder);
98 if (mContextHub == nullptr) {
99 LOGE("Got null context hub from the binder connection");
100 return HalError::NULL_CONTEXT_HUB_FROM_BINDER;
101 }
102
103 // Enforce the required interface version for the service.
104 int32_t version = 0;
105 mContextHub->getInterfaceVersion(&version);
106 if (version < kMinHalInterfaceVersion) {
107 LOGE("CHRE multiclient HAL interface version is %" PRIi32
108 ". It must be >= %" PRIi32,
109 version, kMinHalInterfaceVersion);
110 mContextHub = nullptr;
111 return HalError::VERSION_TOO_LOW;
112 }
113
114 // Register an IContextHubCallback.
115 ScopedAStatus status =
116 mContextHub->registerCallback(kDefaultContextHubId, mCallback);
117 if (!status.isOk()) {
118 LOGE("Unable to register callback: %s", status.getDescription().c_str());
119 // At this moment it's guaranteed that mCallback is not null and
120 // kDefaultContextHubId is valid. So if the registerCallback() still fails
121 // it's a hard failure and CHRE HAL is treated as disconnected.
122 mContextHub = nullptr;
123 return HalError::CALLBACK_REGISTRATION_FAILED;
124 }
125 mIsHalConnected = true;
126 LOGI("%s is successfully (re)connected to CHRE HAL", mClientName.c_str());
127 return HalError::SUCCESS;
128 }
129
onHalDisconnected(void * cookie)130 void HalClient::onHalDisconnected(void *cookie) {
131 int64_t startTime = ::android::elapsedRealtime();
132 auto *halClient = static_cast<HalClient *>(cookie);
133 {
134 std::lock_guard<std::shared_mutex> lockGuard(halClient->mConnectionLock);
135 halClient->mContextHub = nullptr;
136 halClient->mIsHalConnected = false;
137 }
138 LOGW("%s is disconnected from CHRE HAL. Reconnecting...",
139 halClient->mClientName.c_str());
140
141 HalError result = halClient->initConnection();
142 uint64_t duration = ::android::elapsedRealtime() - startTime;
143 if (result != HalError::SUCCESS) {
144 LOGE("Failed to fully reconnect to CHRE HAL after %" PRIu64
145 "ms, HalErrorCode: %" PRIi32,
146 duration, result);
147 return;
148 }
149 tryReconnectEndpoints(halClient);
150 LOGI("%s is reconnected to CHRE HAL after %" PRIu64 "ms",
151 halClient->mClientName.c_str(), duration);
152 }
153
connectEndpoint(const HostEndpointInfo & hostEndpointInfo)154 ScopedAStatus HalClient::connectEndpoint(
155 const HostEndpointInfo &hostEndpointInfo) {
156 HostEndpointId endpointId = hostEndpointInfo.hostEndpointId;
157 if (isEndpointConnected(endpointId)) {
158 // Connecting the endpoint again even though it is already connected to let
159 // HAL and/or CHRE be the single place to control the behavior.
160 LOGW("Endpoint id %" PRIu16 " of %s is already connected", endpointId,
161 mClientName.c_str());
162 }
163 ScopedAStatus result = callIfConnected(
164 [&hostEndpointInfo](const std::shared_ptr<IContextHub> &hub) {
165 return hub->onHostEndpointConnected(hostEndpointInfo);
166 });
167 if (result.isOk()) {
168 insertConnectedEndpoint(hostEndpointInfo);
169 } else {
170 LOGE("Failed to connect endpoint id %" PRIu16 " of %s",
171 hostEndpointInfo.hostEndpointId, mClientName.c_str());
172 }
173 return result;
174 }
175
disconnectEndpoint(HostEndpointId hostEndpointId)176 ScopedAStatus HalClient::disconnectEndpoint(HostEndpointId hostEndpointId) {
177 if (!isEndpointConnected(hostEndpointId)) {
178 // Disconnecting the endpoint again even though it is already disconnected
179 // to let HAL and/or CHRE be the single place to control the behavior.
180 LOGW("Endpoint id %" PRIu16 " of %s is already disconnected",
181 hostEndpointId, mClientName.c_str());
182 }
183 ScopedAStatus result = callIfConnected(
184 [&hostEndpointId](const std::shared_ptr<IContextHub> &hub) {
185 return hub->onHostEndpointDisconnected(hostEndpointId);
186 });
187 if (result.isOk()) {
188 removeConnectedEndpoint(hostEndpointId);
189 } else {
190 LOGE("Failed to disconnect the endpoint id %" PRIu16 " of %s",
191 hostEndpointId, mClientName.c_str());
192 }
193 return result;
194 }
195
sendMessage(const ContextHubMessage & message)196 ScopedAStatus HalClient::sendMessage(const ContextHubMessage &message) {
197 uint16_t hostEndpointId = message.hostEndPoint;
198 if (!isEndpointConnected(hostEndpointId)) {
199 // This is still allowed now but in the future an error will be returned.
200 LOGW("Endpoint id %" PRIu16
201 " of %s is unknown or disconnected. Message sending will be skipped "
202 "in the future",
203 hostEndpointId, mClientName.c_str());
204 }
205 return callIfConnected([&](const std::shared_ptr<IContextHub> &hub) {
206 return hub->sendMessageToHub(mContextHubId, message);
207 });
208 }
209
tryReconnectEndpoints(HalClient * halClient)210 void HalClient::tryReconnectEndpoints(HalClient *halClient) {
211 LOGW("CHRE has restarted. Reconnecting endpoints of %s",
212 halClient->mClientName.c_str());
213 std::lock_guard<std::shared_mutex> lockGuard(
214 halClient->mConnectedEndpointsLock);
215 for (const auto &[endpointId, endpointInfo] :
216 halClient->mConnectedEndpoints) {
217 if (!halClient
218 ->callIfConnected(
219 [&endpointInfo](const std::shared_ptr<IContextHub> &hub) {
220 return hub->onHostEndpointConnected(endpointInfo);
221 })
222 .isOk()) {
223 LOGE("Failed to set up the connected state for endpoint %" PRIu16
224 " of %s after HAL restarts.",
225 endpointId, halClient->mClientName.c_str());
226 halClient->mConnectedEndpoints.erase(endpointId);
227 } else {
228 LOGI("Reconnected endpoint %" PRIu16 " of %s to CHRE HAL", endpointId,
229 halClient->mClientName.c_str());
230 }
231 }
232 }
233
~HalClient()234 HalClient::~HalClient() {
235 std::lock_guard<std::mutex> lock(mBackgroundConnectionFuturesLock);
236 for (const auto &future : mBackgroundConnectionFutures) {
237 // Calling std::thread.join() has chance to hang if the background thread
238 // being joined is still waiting for connecting to the service. Therefore
239 // waiting for the thread to finish here instead and logging the timeout
240 // every second until system kills the process to report the abnormality.
241 while (future.wait_for(std::chrono::seconds(1)) !=
242 std::future_status::ready) {
243 LOGE(
244 "Failed to finish a background connection in time when HalClient is "
245 "being destructed. Waiting...");
246 }
247 }
248 }
249 } // namespace android::chre