blob: f109ce24fd4ea01e230c77d123ec8c61a32df379 [file] [log] [blame]
/*
*
* Copyright (c) 2025 Project CHIP Authors
*
* 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.
*/
/**
* @file
* This file implements System::Layer dedicated to Zephyr RTOS, which does not use select().
*/
#include <lib/support/CodeUtils.h>
#include <platform/LockTracker.h>
#include <system/PlatformEventSupport.h>
#include <system/SystemFaultInjection.h>
#include <system/SystemLayer.h>
#include <system/SystemLayerImplZephyr.h>
namespace chip {
namespace System {
LayerImplZephyr::LayerImplZephyr() {}
CHIP_ERROR LayerImplZephyr::Init()
{
VerifyOrReturnError(mLayerState.SetInitializing(), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mLayerState.SetInitialized(), CHIP_ERROR_INCORRECT_STATE);
return CHIP_NO_ERROR;
}
void LayerImplZephyr::Shutdown()
{
mLayerState.ResetFromInitialized();
}
CHIP_ERROR LayerImplZephyr::StartTimer(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
CHIP_SYSTEM_FAULT_INJECT(FaultInjection::kFault_TimeoutImmediate, delay = System::Clock::kZero);
CancelTimer(onComplete, appState);
TimerList::Node * timer = mTimerPool.Create(*this, SystemClock().GetMonotonicTimestamp() + delay, onComplete, appState);
VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
if (mTimerList.Add(timer) == timer)
{
// The new timer is the earliest, so the time until the next event has probably changed.
TriggerPlatformTimerUpdate();
}
return CHIP_NO_ERROR;
}
CHIP_ERROR LayerImplZephyr::ExtendTimerTo(Clock::Timeout delay, TimerCompleteCallback onComplete, void * appState)
{
VerifyOrReturnError(delay.count() > 0, CHIP_ERROR_INVALID_ARGUMENT);
assertChipStackLockedByCurrentThread();
Clock::Timeout remainingTime = mTimerList.GetRemainingTime(onComplete, appState);
if (remainingTime.count() < delay.count())
{
return StartTimer(delay, onComplete, appState);
}
return CHIP_NO_ERROR;
}
bool LayerImplZephyr::IsTimerActive(TimerCompleteCallback onComplete, void * appState)
{
bool timerIsActive = (mTimerList.GetRemainingTime(onComplete, appState) > Clock::kZero);
if (!timerIsActive)
{
// check if the timer is in the mExpiredTimers list about to be fired.
for (TimerList::Node * timer = mExpiredTimers.Earliest(); timer != nullptr; timer = timer->mNextTimer)
{
if (timer->GetCallback().GetOnComplete() == onComplete && timer->GetCallback().GetAppState() == appState)
{
return true;
}
}
}
return timerIsActive;
}
Clock::Timeout LayerImplZephyr::GetRemainingTime(TimerCompleteCallback onComplete, void * appState)
{
return mTimerList.GetRemainingTime(onComplete, appState);
}
void LayerImplZephyr::CancelTimer(TimerCompleteCallback onComplete, void * appState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturn(mLayerState.IsInitialized());
TimerList::Node * timer = mTimerList.Remove(onComplete, appState);
if (timer == nullptr)
{
// The timer was not in our "will fire in the future" list, but it might
// be in the "we're about to fire these" chunk we already grabbed from
// that list. Check for it there too, and if found there we still want
// to cancel it.
timer = mExpiredTimers.Remove(onComplete, appState);
}
VerifyOrReturn(timer != nullptr);
mTimerPool.Release(timer);
}
CHIP_ERROR LayerImplZephyr::ScheduleWork(TimerCompleteCallback onComplete, void * appState)
{
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mLayerState.IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
// Ideally we would not use a timer here at all, but if we try to just
// ScheduleLambda the lambda needs to capture the following:
// 1) onComplete
// 2) appState
// 3) The `this` pointer, because onComplete needs to be passed a pointer to
// the System::Layer.
//
// On a 64-bit system that's 24 bytes, but lambdas passed to ScheduleLambda
// are capped at CHIP_CONFIG_LAMBDA_EVENT_SIZE which is 16 bytes.
//
// So for now use a timer as a poor-man's closure that captures `this` and
// onComplete and appState in a single pointer, so we fit inside the size
// limit.
//
// TODO: We could do something here where we compile-time condition on the
// sizes of things and use a direct ScheduleLambda if it would fit and this
// setup otherwise.
//
// TODO: But also, unit tests seem to do SystemLayer::ScheduleWork without
// actually running a useful event loop (in the PlatformManager sense),
// which breaks if we use ScheduleLambda here, since that does rely on the
// PlatformManager event loop. So for now, keep scheduling an expires-ASAP
// timer, but just make sure we don't cancel existing timers with the same
// callback and appState, so ScheduleWork invocations don't stomp on each
// other.
TimerList::Node * timer = mTimerPool.Create(*this, SystemClock().GetMonotonicTimestamp(), onComplete, appState);
VerifyOrReturnError(timer != nullptr, CHIP_ERROR_NO_MEMORY);
if (mTimerList.Add(timer) == timer)
{
// The new timer is the earliest, so the time until the next event has probably changed.
TriggerPlatformTimerUpdate();
}
return CHIP_NO_ERROR;
}
void LayerImplZephyr::TriggerPlatformTimerUpdate()
{
CHIP_ERROR status = PlatformEventing::StartTimer(*this, Clock::kZero);
if (status != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Error starting platform timer: %" CHIP_ERROR_FORMAT, status.Format());
}
}
/**
* Get the next awaken time from the timer list.
*
* @return The next awaken time or nullopt if there are no timers in the list.
*/
std::optional<Clock::Timestamp> LayerImplZephyr::GetNextAwakenTime()
{
TimerList::Node * timer = mTimerList.Earliest();
if (timer)
{
return timer->AwakenTime();
}
return std::nullopt;
}
/**
* Handle the platform timer expiration event. Completes any timers that have expired.
*
* A static API that gets called when the platform timer expires. Any expired timers are completed and removed from the list
* of active timers in the layer object.
*
* It is assumed that this API is called only while on the thread which owns the CHIP System Layer object.
*
* @note
* It's harmless if this API gets called and there are no expired timers.
*
* @return CHIP_NO_ERROR on success, error code otherwise.
*
*/
CHIP_ERROR LayerImplZephyr::HandlePlatformTimer()
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
// Obtain the list of currently expired timers. Any new timers added by timer callback are NOT handled on this pass,
// since that could result in infinite handling of new timers blocking any other progress.
VerifyOrDieWithMsg(mExpiredTimers.Empty(), DeviceLayer, "Re-entry into HandleEvents from a timer callback?");
Clock::Timestamp now = SystemClock().GetMonotonicTimestamp();
mExpiredTimers = mTimerList.ExtractEarlier(Clock::Timeout(1) + now);
TimerList::Node * timer = nullptr;
while ((timer = mExpiredTimers.PopEarliest()) != nullptr)
{
mTimerPool.Invoke(timer);
}
return CHIP_NO_ERROR;
}
} // namespace System
} // namespace chip