/* * 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/timer_pool.h" #include "chre/core/event.h" #include "chre/core/event_loop.h" #include "chre/core/event_loop_common.h" #include "chre/core/event_loop_manager.h" #include "chre/platform/fatal_error.h" #include "chre/platform/log.h" #include "chre/platform/system_time.h" #include "chre/util/lock_guard.h" #include "chre/util/nested_data_ptr.h" #include namespace chre { namespace { constexpr uint64_t kTimerAlreadyFiredExpiration = UINT64_MAX; } // anonymous namespace TimerPool::TimerPool() { if (!mSystemTimer.init()) { FATAL_ERROR("Failed to initialize a system timer for the TimerPool"); } } TimerHandle TimerPool::setSystemTimer(Nanoseconds duration, SystemEventCallbackFunction *callback, SystemCallbackType callbackType, void *data) { CHRE_ASSERT(callback != nullptr); TimerHandle timerHandle = setTimer(kSystemInstanceId, duration, data, callback, callbackType, true /* isOneShot */); if (timerHandle == CHRE_TIMER_INVALID) { FATAL_ERROR("Failed to set system timer"); } return timerHandle; } uint32_t TimerPool::cancelAllNanoappTimers(const Nanoapp *nanoapp) { CHRE_ASSERT(nanoapp != nullptr); LockGuard lock(mMutex); uint32_t numTimersCancelled = 0; // Iterate backward as we remove requests from the list. for (int i = static_cast(mTimerRequests.size()) - 1; i >= 0; i--) { size_t iAsSize = static_cast(i); const TimerRequest &request = mTimerRequests[iAsSize]; if (request.instanceId == nanoapp->getInstanceId()) { numTimersCancelled++; removeTimerRequestLocked(iAsSize); } } return numTimersCancelled; } TimerHandle TimerPool::setTimer(uint16_t instanceId, Nanoseconds duration, const void *cookie, SystemEventCallbackFunction *systemCallback, SystemCallbackType callbackType, bool isOneShot) { LockGuard lock(mMutex); TimerRequest timerRequest; timerRequest.instanceId = instanceId; timerRequest.timerHandle = generateTimerHandleLocked(); timerRequest.expirationTime = SystemTime::getMonotonicTime() + duration; timerRequest.duration = duration; timerRequest.cookie = cookie; timerRequest.systemCallback = systemCallback; timerRequest.callbackType = callbackType; timerRequest.isOneShot = isOneShot; bool success = insertTimerRequestLocked(timerRequest); if (success) { if (mTimerRequests.size() == 1) { // If this timer request was the first, schedule it. handleExpiredTimersAndScheduleNextLocked(); } else { // If there was already a timer pending before this, and we just inserted // to the top of the queue, just update the system timer. This is slightly // more efficient than calling into // handleExpiredTimersAndScheduleNextLocked(). bool newRequestExpiresFirst = timerRequest.timerHandle == mTimerRequests.top().timerHandle; if (newRequestExpiresFirst) { mSystemTimer.set(handleSystemTimerCallback, this, duration); } } } return success ? timerRequest.timerHandle : CHRE_TIMER_INVALID; } bool TimerPool::cancelTimer(uint16_t instanceId, TimerHandle timerHandle) { LockGuard 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); 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--; } if (index == 0) { mSystemTimer.cancel(); handleExpiredTimersAndScheduleNextLocked(); } } } bool TimerPool::handleExpiredTimersAndScheduleNext() { LockGuard lock(mMutex); return handleExpiredTimersAndScheduleNextLocked(); } bool TimerPool::handleExpiredTimersAndScheduleNextLocked() { bool handledExpiredTimer = false; while (!mTimerRequests.empty()) { Nanoseconds currentTime = SystemTime::getMonotonicTime(); TimerRequest ¤tTimerRequest = mTimerRequests.top(); if (currentTime >= currentTimerRequest.expirationTime) { handledExpiredTimer = true; // This timer has expired, so post an event if it is a nanoapp timer, or // submit a deferred callback if it's a system timer. bool success; if (currentTimerRequest.instanceId == kSystemInstanceId) { success = EventLoopManagerSingleton::get()->deferCallback( currentTimerRequest.callbackType, const_cast(currentTimerRequest.cookie), currentTimerRequest.systemCallback); } else { success = EventLoopManagerSingleton::get()->deferCallback( SystemCallbackType::TimerPoolTimerExpired, NestedDataPtr(currentTimerRequest.timerHandle), TimerPool::handleTimerExpiredCallback, this); } if (!success) { LOGW("Failed to defer timer callback"); } rescheduleAndRemoveExpiredTimersLocked(currentTimerRequest); } else { if (currentTimerRequest.expirationTime.toRawNanoseconds() < kTimerAlreadyFiredExpiration) { // Update the system timer to reflect the duration until the closest // expiry (mTimerRequests is sorted by expiry, so we just do this for // the first timer found which has not expired yet) Nanoseconds duration = currentTimerRequest.expirationTime - currentTime; mSystemTimer.set(handleSystemTimerCallback, this, duration); } break; } } return handledExpiredTimer; } void TimerPool::rescheduleAndRemoveExpiredTimersLocked( const TimerRequest &request) { if (request.isOneShot && request.instanceId == kSystemInstanceId) { popTimerRequestLocked(); } else { TimerRequest copyRequest = request; copyRequest.expirationTime = request.isOneShot ? Nanoseconds(kTimerAlreadyFiredExpiration) : request.expirationTime + request.duration; popTimerRequestLocked(); CHRE_ASSERT(insertTimerRequestLocked(copyRequest)); } } bool TimerPool::hasNanoappTimers(uint16_t instanceId) { LockGuard lock(mMutex); for (size_t i = 0; i < mTimerRequests.size(); i++) { const TimerRequest &request = mTimerRequests[i]; if (request.instanceId == instanceId) { return true; } } return false; } void TimerPool::handleSystemTimerCallback(void *timerPoolPtr) { auto callback = [](uint16_t /* type */, void *data, void * /* extraData */) { auto *timerPool = static_cast(data); if (!timerPool->handleExpiredTimersAndScheduleNext()) { // Means that the system timer invoked our callback before the next // timer expired. Possible in rare race conditions with time removal, // but could indicate a faulty SystemTimer implementation if this // happens often. Not a major problem - we'll just reset the timer to // the next expiration. LOGW("Timer callback invoked prior to expiry"); } }; EventLoopManagerSingleton::get()->deferCallback( SystemCallbackType::TimerPoolTick, timerPoolPtr, callback); } void TimerPool::handleTimerExpiredCallback(uint16_t /* type */, void *data, void *extraData) { NestedDataPtr timerHandle(data); TimerPool* timerPool = static_cast(extraData); size_t index; TimerRequest currentTimerRequest; { LockGuard lock(timerPool->mMutex); TimerRequest* timerRequest = timerPool->getTimerRequestByTimerHandleLocked( timerHandle, &index); if (timerRequest == nullptr) { return; } currentTimerRequest = *timerRequest; if (currentTimerRequest.isOneShot) { timerPool->removeTimerRequestLocked(index); } } if (!EventLoopManagerSingleton::get()->getEventLoop().distributeEventSync( CHRE_EVENT_TIMER, const_cast(currentTimerRequest.cookie), currentTimerRequest.instanceId)) { LOGW("Failed to deliver timer event"); } } } // namespace chre