/*
* Copyright (C) 2016 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 "chre/core/event_loop.h"
#include "chre/core/event_loop_manager.h"
#include "chre/core/timer_pool.h"
#include "chre/platform/fatal_error.h"
#include "chre/platform/system_time.h"
#include "chre/util/lock_guard.h"
namespace chre {
TimerPool::TimerPool() {
if (!mSystemTimer.init()) {
FATAL_ERROR("Failed to initialize a system timer for the TimerPool");
}
}
TimerHandle TimerPool::setSystemTimer(
Nanoseconds duration, SystemCallbackFunction *callback,
SystemCallbackType callbackType, const void *cookie) {
TimerHandle timerHandle = setTimer(
kSystemInstanceId, duration, callback,
static_cast<uint16_t>(callbackType), cookie, true /* isOneShot */);
if (timerHandle == CHRE_TIMER_INVALID) {
FATAL_ERROR("Failed to set system timer");
}
return timerHandle;
}
TimerHandle TimerPool::setTimer(
uint32_t instanceId, Nanoseconds duration,
SystemCallbackFunction *callback, uint16_t eventType,
const void *cookie, bool isOneShot) {
LockGuard<Mutex> lock(mMutex);
TimerRequest timerRequest;
timerRequest.instanceId = instanceId;
timerRequest.timerHandle = generateTimerHandleLocked();
timerRequest.expirationTime = SystemTime::getMonotonicTime() + duration;
timerRequest.duration = duration;
timerRequest.isOneShot = isOneShot;
timerRequest.callback = callback;
timerRequest.eventType = eventType;
timerRequest.cookie = cookie;
bool newTimerExpiresEarliest =
(!mTimerRequests.empty() && mTimerRequests.top() > timerRequest);
bool success = insertTimerRequestLocked(timerRequest);
if (success) {
if (newTimerExpiresEarliest) {
mSystemTimer.set(handleSystemTimerCallback, this, duration);
} else if (mTimerRequests.size() == 1) {
// If this timer request was the first, schedule it.
handleExpiredTimersAndScheduleNextLocked();
}
}
return success ? timerRequest.timerHandle : CHRE_TIMER_INVALID;
}
bool TimerPool::cancelTimer(
uint32_t instanceId, TimerHandle timerHandle) {
LockGuard<Mutex> lock(mMutex);
size_t index;
bool success = false;
TimerRequest *timerRequest = getTimerRequestByTimerHandleLocked(timerHandle,
&index);
if (timerRequest == nullptr) {
LOGW("Failed to cancel timer ID %" PRIu32 ": not found", timerHandle);
} else if (timerRequest->instanceId != instanceId) {
LOGW("Failed to cancel timer ID %" PRIu32 ": permission denied",
timerHandle);
} else {
removeTimerRequestLocked(index);
if (index == 0) {
mSystemTimer.cancel();
handleExpiredTimersAndScheduleNextLocked();
}
success = true;
}
return success;
}
TimerPool::TimerRequest *TimerPool::getTimerRequestByTimerHandleLocked(
TimerHandle timerHandle, size_t *index) {
for (size_t i = 0; i < mTimerRequests.size(); i++) {
if (mTimerRequests[i].timerHandle == timerHandle) {
if (index != nullptr) {
*index = i;
}
return &mTimerRequests[i];
}
}
return nullptr;
}
bool TimerPool::TimerRequest::operator>(const TimerRequest& request) const {
return (expirationTime > request.expirationTime);
}
TimerHandle TimerPool::generateTimerHandleLocked() {
TimerHandle timerHandle;
if (mGenerateTimerHandleMustCheckUniqueness) {
timerHandle = generateUniqueTimerHandleLocked();
} else {
timerHandle = mLastTimerHandle + 1;
if (timerHandle == CHRE_TIMER_INVALID) {
// TODO: Consider that uniqueness checking can be reset when the number of
// timer requests reaches zero.
mGenerateTimerHandleMustCheckUniqueness = true;
timerHandle = generateUniqueTimerHandleLocked();
}
}
mLastTimerHandle = timerHandle;
return timerHandle;
}
TimerHandle TimerPool::generateUniqueTimerHandleLocked() {
TimerHandle timerHandle = mLastTimerHandle;
while (1) {
timerHandle++;
if (timerHandle != CHRE_TIMER_INVALID) {
TimerRequest *timerRequest =
getTimerRequestByTimerHandleLocked(timerHandle);
if (timerRequest == nullptr) {
return timerHandle;
}
}
}
}
bool TimerPool::isNewTimerAllowedLocked(bool isNanoappTimer) const {
static_assert(kMaxNanoappTimers <= kMaxTimerRequests,
"Max number of nanoapp timers is too big");
static_assert(kNumReservedNanoappTimers <= kMaxTimerRequests,
"Number of reserved nanoapp timers is too big");
bool allowed;
if (isNanoappTimer) {
allowed = (mNumNanoappTimers < kMaxNanoappTimers);
} else { // System timer
// We must not allow more system timers than the required amount of reserved
// timers for nanoapps.
constexpr size_t kMaxSystemTimers =
kMaxTimerRequests - kNumReservedNanoappTimers;
size_t numSystemTimers = mTimerRequests.size() - mNumNanoappTimers;
allowed = (numSystemTimers < kMaxSystemTimers);
}
return allowed;
}
bool TimerPool::insertTimerRequestLocked(const TimerRequest& timerRequest) {
bool isNanoappTimer = (timerRequest.instanceId != kSystemInstanceId);
bool success = isNewTimerAllowedLocked(isNanoappTimer) &&
mTimerRequests.push(timerRequest);
if (!success) {
LOG_OOM();
} else if (isNanoappTimer) {
mNumNanoappTimers++;
}
return success;
}
void TimerPool::popTimerRequestLocked() {
CHRE_ASSERT(!mTimerRequests.empty());
if (!mTimerRequests.empty()) {
bool isNanoappTimer =
(mTimerRequests.top().instanceId != kSystemInstanceId);
mTimerRequests.pop();
if (isNanoappTimer) {
mNumNanoappTimers--;
}
}
}
void TimerPool::removeTimerRequestLocked(size_t index) {
CHRE_ASSERT(index < mTimerRequests.size());
if (index < mTimerRequests.size()) {
bool isNanoappTimer =
(mTimerRequests[index].instanceId != kSystemInstanceId);
mTimerRequests.remove(index);
if (isNanoappTimer) {
mNumNanoappTimers--;
}
}
}
bool TimerPool::handleExpiredTimersAndScheduleNext() {
LockGuard<Mutex> lock(mMutex);
return handleExpiredTimersAndScheduleNextLocked();
}
bool TimerPool::handleExpiredTimersAndScheduleNextLocked() {
bool success = false;
while (!mTimerRequests.empty()) {
Nanoseconds currentTime = SystemTime::getMonotonicTime();
TimerRequest& currentTimerRequest = mTimerRequests.top();
if (currentTime >= currentTimerRequest.expirationTime) {
// Post an event for an expired timer.
success = EventLoopManagerSingleton::get()->getEventLoop().postEvent(
currentTimerRequest.eventType,
const_cast<void *>(currentTimerRequest.cookie),
currentTimerRequest.callback, kSystemInstanceId,
currentTimerRequest.instanceId);
// Reschedule the timer if needed, and release the current request.
if (!currentTimerRequest.isOneShot) {
// Important: we need to make a copy of currentTimerRequest here,
// because it's a reference to memory that may get moved during the
// insert operation (thereby invalidating it).
TimerRequest cyclicTimerRequest = currentTimerRequest;
cyclicTimerRequest.expirationTime = currentTime
+ currentTimerRequest.duration;
popTimerRequestLocked();
CHRE_ASSERT(insertTimerRequestLocked(cyclicTimerRequest));
} else {
popTimerRequestLocked();
}
} else {
Nanoseconds duration = currentTimerRequest.expirationTime - currentTime;
mSystemTimer.set(handleSystemTimerCallback, this, duration);
// Assign success to true here to handle timers that tick before their
// expiration time. This should be rarely required, but for systems where
// a timer may tick earlier than requested the request is rescheduled with
// the remaining time as computed above.
success = true;
break;
}
}
return success;
}
void TimerPool::handleSystemTimerCallback(void *timerPoolPtr) {
auto callback = [](uint16_t /* eventType */, void *eventData) {
auto *timerPool = static_cast<TimerPool *>(eventData);
if (!timerPool->handleExpiredTimersAndScheduleNext()) {
LOGE("Timer callback invoked with no outstanding timers");
}
};
EventLoopManagerSingleton::get()->deferCallback(
SystemCallbackType::TimerPoolTick, timerPoolPtr, callback);
}
} // namespace chre