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