blob: 55295823e904a59c108690fdf2a5ce8a5d7d62b8 [file] [log] [blame]
/*
* Copyright (C) 2020 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 <cinttypes>
#include "chre_host/st_hal_lpma_handler.h"
namespace android {
namespace chre {
namespace {
constexpr char kChreWakeLockName[] = "chre_daemon";
void acquireWakeLock() {
int rc;
if ((rc = acquire_wake_lock(PARTIAL_WAKE_LOCK, kChreWakeLockName)) != 0) {
LOGE("Failed to acquire wakelock (err %d)", rc);
}
}
void releaseWakeLock() {
int rc;
static bool wakeLockInitialRelease = true;
// It's expected to get an error when we first try to release the
// wakelock
// as it won't exist unless it was leaked previously - don't output a
// false warning for this case
if (((rc = release_wake_lock(kChreWakeLockName)) != 0) &&
!wakeLockInitialRelease) {
LOGE("Failed to release wakelock (err %d)", rc);
}
wakeLockInitialRelease = false;
}
} // anonymous namespace
StHalLpmaHandler::StHalLpmaHandler(bool allowed) : mIsLpmaAllowed(allowed) {
auto cb = [&]() { onStHalServiceDeath(); };
mDeathRecipient = new StHalDeathRecipient(cb);
}
void StHalLpmaHandler::init() {
if (mIsLpmaAllowed) {
mThread = std::thread(&StHalLpmaHandler::StHalLpmaHandlerThreadEntry, this);
}
}
void StHalLpmaHandler::enable(bool enabled) {
if (mIsLpmaAllowed) {
std::lock_guard<std::mutex> lock(mMutex);
mTargetLpmaEnabled = enabled;
mCondVarPredicate = true;
mCondVar.notify_one();
} else {
LOGE("Trying to modify LPMA state when LPMA is disabled");
}
}
bool StHalLpmaHandler::load(SoundModelHandle &lpmaHandle) {
constexpr uint8_t kUuidNode[] = {0x2E, 0x95, 0xA2, 0x31, 0x3A, 0xEE};
LOGV("Loading LPMA");
ISoundTriggerHw::SoundModel soundModel;
soundModel.type = SoundModelType::GENERIC;
soundModel.vendorUuid.timeLow = 0x57CADDB1;
soundModel.vendorUuid.timeMid = 0xACDB;
soundModel.vendorUuid.versionAndTimeHigh = 0x4DCE;
soundModel.vendorUuid.variantAndClockSeqHigh = 0x8CB0;
memcpy(&soundModel.vendorUuid.node[0], kUuidNode, sizeof(kUuidNode));
soundModel.data.resize(1); // Insert a dummy byte to bypass HAL NULL checks.
bool loaded = false;
checkConnectionToStHalServiceLocked();
int32_t loadResult;
Return<void> hidlResult = mStHalService->loadSoundModel(
soundModel, nullptr /* callback */, 0 /* cookie */,
[&](int32_t retval, SoundModelHandle handle) {
loadResult = retval;
lpmaHandle = handle;
});
if (hidlResult.isOk()) {
if (loadResult == 0) {
LOGD("Loaded LPMA");
loaded = true;
} else {
LOGE("Failed to load LPMA with %" PRId32, loadResult);
}
} else {
LOGE("Failed to load LPMA due to hidl error %s",
hidlResult.description().c_str());
}
return loaded;
}
void StHalLpmaHandler::unload(SoundModelHandle lpmaHandle) {
checkConnectionToStHalServiceLocked();
Return<int32_t> hidlResult = mStHalService->unloadSoundModel(lpmaHandle);
if (hidlResult.isOk()) {
if (hidlResult != 0) {
LOGE("Failed to unload LPMA with %" PRId32, int32_t(hidlResult));
}
} else {
LOGE("Failed to unload LPMA due to hidl error %s",
hidlResult.description().c_str());
}
}
void StHalLpmaHandler::checkConnectionToStHalServiceLocked() {
if (mStHalService == nullptr) {
mStHalService = ISoundTriggerHw::getService();
if (mStHalService != nullptr) {
LOGI("Connected to ST HAL service");
mStHalService->linkToDeath(mDeathRecipient, 0 /* flags */);
}
}
}
bool StHalLpmaHandler::waitOnStHalRequestAndProcess() {
SoundModelHandle lpmaHandle = 0;
bool noDelayNeeded = true;
std::unique_lock<std::mutex> lock(mMutex);
if (mCurrentLpmaEnabled == mTargetLpmaEnabled) {
mRetryDelay = 0;
mRetryCount = 0;
releaseWakeLock(); // Allow the system to suspend while waiting.
mCondVar.wait(lock, [this] { return mCondVarPredicate; });
mCondVarPredicate = false;
acquireWakeLock(); // Ensure the system stays up while retrying.
} else if (mTargetLpmaEnabled && load(lpmaHandle)) {
mCurrentLpmaEnabled = mTargetLpmaEnabled;
} else if (!mTargetLpmaEnabled) {
// Regardless of whether the use case fails to unload, set the
// currentLpmaEnabled to the targetLpmaEnabled. This will allow the next
// enable request to proceed. After a failure to unload occurs, the
// supplied handle is invalid and should not be unloaded again.
unload(lpmaHandle);
mCurrentLpmaEnabled = mTargetLpmaEnabled;
} else {
noDelayNeeded = false;
}
return noDelayNeeded;
}
void StHalLpmaHandler::delay() {
constexpr useconds_t kInitialRetryDelayUs = 500000;
constexpr int kRetryGrowthFactor = 2;
constexpr int kRetryGrowthLimit = 5; // Terminates at 8s retry interval.
constexpr int kRetryWakeLockLimit = 10; // Retry with a wakelock 10 times.
if (mRetryDelay == 0) {
mRetryDelay = kInitialRetryDelayUs;
} else if (mRetryCount < kRetryGrowthLimit) {
mRetryDelay *= kRetryGrowthFactor;
}
usleep(mRetryDelay);
mRetryCount++;
if (mRetryCount > kRetryWakeLockLimit) {
releaseWakeLock();
}
}
void StHalLpmaHandler::StHalLpmaHandlerThreadEntry() {
LOGD("Starting LPMA thread");
while (true) {
if (!waitOnStHalRequestAndProcess()) {
// processing an LPMA state update failed, retry after a little while
delay();
}
}
}
void StHalLpmaHandler::onStHalServiceDeath() {
LOGE("ST HAL Service Died");
std::lock_guard<std::mutex> lock(mMutex);
mStHalService = nullptr;
if (mTargetLpmaEnabled) {
// ST HAL has died, so assume that the sound model is no longer active,
// and trigger a reload of the sound model.
mCurrentLpmaEnabled = false;
mCondVarPredicate = true;
mCondVar.notify_one();
}
}
} // namespace chre
} // namespace android