/* * Copyright (c) 2023, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * @file * This file implements the OpenThread platform abstraction for the alarm. * */ #include #include #include #include #include #include #include #include #include "common/debug.hpp" #include "common/logging.hpp" #include "alarm.h" #include "platform-efr32.h" #include "utils/code_utils.h" #include "rail.h" #include "sl_core.h" #include "sl_multipan.h" #include "sl_sleeptimer.h" #ifndef TESTING #define STATIC static #else #define STATIC #endif // timer data for handling wrapping typedef struct wrap_timer_data wrap_timer_data_t; struct wrap_timer_data { uint16_t overflow_counter; uint16_t overflow_max; }; // millisecond timer (sleeptimer) static sl_sleeptimer_timer_handle_t sl_handle[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; // microsecond timer (RAIL timer) static RAIL_MultiTimer_t rail_timer[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; // forward declare generic alarm handle struct AlarmHandle; // function pointers for timer operations typedef void (*timerStartFunc)(struct AlarmHandle *, uint32_t); typedef uint32_t (*timerMaxFunc)(void); typedef uint32_t (*timerGetTimeFunc)(void); typedef void (*timerStopFunc)(struct AlarmHandle *); // alarm handle structure typedef struct AlarmHandle AlarmHandle; struct AlarmHandle { otInstance *mThreadInstance; void *mTimerHandle; timerStartFunc mTimerStart; timerMaxFunc mTimerGetMax; timerGetTimeFunc mTimerGetNow; timerStopFunc mTimerStop; wrap_timer_data_t mWrapData; volatile bool mIsRunning; volatile int mFiredCount; }; // callback function for the stack typedef void (*StackAlarmCallback)(otInstance *); // alarm handle instances static AlarmHandle sMsAlarmHandles[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; static AlarmHandle sUsAlarmHandles[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; static uint64_t sPendingTimeMs[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; // millisecond-alarm callback STATIC void msAlarmCallback(sl_sleeptimer_timer_handle_t *aHandle, void *aData); // microsecond-alarm callback STATIC void usAlarmCallback(struct RAIL_MultiTimer *tmr, RAIL_Time_t expectedTimeOfEvent, void *cbArg); // timer specific operations static void msTimerStart(AlarmHandle *aMsAlarm, uint32_t aAlarmDuration); static uint32_t msTimerGetMaxTime(void); static uint32_t msTimerGetNow(void); static void msTimerStop(AlarmHandle *aMsAlarm); static void usTimerStart(AlarmHandle *aUsAlarm, uint32_t aAlarmDuration); static uint32_t usTimerGetMaxTime(void); static uint32_t usTimerGetNow(void); static void usTimerStop(AlarmHandle *aUsAlarm); // overflow utility functions static inline bool isAlarmOverflowInProgress(AlarmHandle *aAlarm); // common timer operations static void FireAlarm(AlarmHandle *aAlarm); static void StartAlarmAt(AlarmHandle *aAlarm, uint32_t aT0, uint32_t aDt); static void StopActiveAlarm(AlarmHandle *aAlarm); static void AlarmCallback(AlarmHandle *aAlarm); // pending time utility functions static inline uint64_t GetPendingTime(otInstance *aInstance); static inline void SetPendingTime(otInstance *aInstance, uint64_t aPendingTime); // alarm handle utility functions static inline AlarmHandle *GetAlarmHandle(AlarmHandle *aHandleList, otInstance *aInstance); static AlarmHandle *GetFirstFiredAlarm(AlarmHandle *aAlarm); static AlarmHandle *GetNextFiredAlarm(AlarmHandle *aAlarm, const AlarmHandle *aAlarmEnd); static inline bool HasAnyAlarmFired(void); static inline uint32_t SetAlarmWrappedDuration(AlarmHandle *aAlarm, uint64_t aRemainingTime); static void msTimerStart(AlarmHandle *aMsAlarm, uint32_t aAlarmDuration) { OT_ASSERT(aMsAlarm != NULL); OT_ASSERT(aMsAlarm->mIsRunning == false); sl_status_t status = sl_sleeptimer_start_timer_ms(aMsAlarm->mTimerHandle, aAlarmDuration, msAlarmCallback, (void *)aMsAlarm, 0, SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG); #if OPENTHREAD_CONFIG_ASSERT_ENABLE OT_ASSERT(status == SL_STATUS_OK); #else OT_UNUSED_VARIABLE(status); #endif } static uint32_t msTimerGetMaxTime(void) { return sl_sleeptimer_get_max_ms32_conversion(); } static uint32_t msTimerGetNow(void) { uint64_t ticks; uint64_t now; sl_status_t status; ticks = sl_sleeptimer_get_tick_count64(); status = sl_sleeptimer_tick64_to_ms(ticks, &now); #if OPENTHREAD_CONFIG_ASSERT_ENABLE OT_ASSERT(status == SL_STATUS_OK); #else OT_UNUSED_VARIABLE(status); #endif return (uint32_t)now; } static void msTimerStop(AlarmHandle *aMsAlarm) { OT_ASSERT(aMsAlarm != NULL); sl_sleeptimer_stop_timer((sl_sleeptimer_timer_handle_t *)aMsAlarm->mTimerHandle); } static void usTimerStart(AlarmHandle *aUsAlarm, uint32_t aAlarmDuration) { OT_ASSERT(aUsAlarm != NULL); OT_ASSERT(aUsAlarm->mIsRunning == false); RAIL_Status_t status = RAIL_SetMultiTimer(aUsAlarm->mTimerHandle, aAlarmDuration, RAIL_TIME_DELAY, usAlarmCallback, (void *)aUsAlarm); #if OPENTHREAD_CONFIG_ASSERT_ENABLE OT_ASSERT(status == RAIL_STATUS_NO_ERROR); #else OT_UNUSED_VARIABLE(status); #endif } static uint32_t usTimerGetMaxTime(void) { return UINT32_MAX; } static uint32_t usTimerGetNow(void) { return RAIL_GetTime(); } static void usTimerStop(AlarmHandle *aUsAlarm) { OT_ASSERT(aUsAlarm != NULL); RAIL_CancelMultiTimer((struct RAIL_MultiTimer *)aUsAlarm->mTimerHandle); } static inline bool isAlarmOverflowInProgress(AlarmHandle *aAlarm) { OT_ASSERT(aAlarm != NULL); return aAlarm->mWrapData.overflow_counter < aAlarm->mWrapData.overflow_max; } static void FireAlarm(AlarmHandle *aAlarm) { OT_ASSERT(aAlarm != NULL); aAlarm->mFiredCount++; StopActiveAlarm(aAlarm); otSysEventSignalPending(); } static void ProcessAlarm(AlarmHandle *aAlarm, StackAlarmCallback aCallback) { OT_ASSERT(aAlarm != NULL); otInstance *instance = aAlarm->mThreadInstance; CORE_DECLARE_IRQ_STATE; CORE_ENTER_ATOMIC(); int numCallbacks = aAlarm->mFiredCount; aAlarm->mFiredCount = 0; CORE_EXIT_ATOMIC(); while (numCallbacks > 0) { numCallbacks--; aCallback(instance); } } static inline uint32_t SetAlarmWrappedDuration(AlarmHandle *aAlarm, uint64_t aRemainingTime) { OT_ASSERT(aAlarm != NULL); uint64_t initial_wrap_time = aRemainingTime; wrap_timer_data_t wrapData = {0}; if (initial_wrap_time > aAlarm->mTimerGetMax()) { initial_wrap_time %= aAlarm->mTimerGetMax(); wrapData.overflow_max = (uint16_t)(aRemainingTime / aAlarm->mTimerGetMax()); wrapData.overflow_counter = 0; } aAlarm->mWrapData = wrapData; return (uint32_t)initial_wrap_time; } static void StartAlarmAt(AlarmHandle *aAlarm, uint32_t aT0, uint32_t aDt) { OT_ASSERT(aAlarm != NULL); otEXPECT(sl_ot_rtos_task_can_access_pal()); StopActiveAlarm(aAlarm); uint64_t requested_time = (uint64_t)aT0 + (uint64_t)aDt; int64_t remaining = (int64_t)requested_time - (int64_t)aAlarm->mTimerGetNow(); if (remaining <= 0) { FireAlarm(aAlarm); } else { aAlarm->mTimerStart(aAlarm, SetAlarmWrappedDuration(aAlarm, (uint64_t)remaining)); aAlarm->mIsRunning = true; } exit: return; } static void StopActiveAlarm(AlarmHandle *aAlarm) { OT_ASSERT(aAlarm != NULL); otEXPECT(aAlarm->mIsRunning); otEXPECT(sl_ot_rtos_task_can_access_pal()); aAlarm->mTimerStop(aAlarm); aAlarm->mIsRunning = false; exit: return; } static void AlarmCallback(AlarmHandle *aAlarm) { OT_ASSERT(aAlarm != NULL); if (isAlarmOverflowInProgress(aAlarm)) { aAlarm->mIsRunning = false; aAlarm->mWrapData.overflow_counter++; aAlarm->mTimerStart(aAlarm, aAlarm->mTimerGetMax()); } else { FireAlarm(aAlarm); } } static inline uint64_t GetPendingTime(otInstance *aInstance) { #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE efr32Iid_t currentIid = (efr32Iid_t)efr32GetIidFromInstance(aInstance); OT_ASSERT(currentIid <= OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); return sPendingTimeMs[currentIid - 1]; #else OT_UNUSED_VARIABLE(aInstance); return sPendingTimeMs[0]; #endif } static inline void SetPendingTime(otInstance *aInstance, uint64_t aPendingTime) { #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE efr32Iid_t currentIid = (efr32Iid_t)efr32GetIidFromInstance(aInstance); OT_ASSERT(currentIid <= OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); sPendingTimeMs[currentIid - 1] = aPendingTime; #else OT_UNUSED_VARIABLE(aInstance); sPendingTimeMs[0] = aPendingTime; #endif } static inline AlarmHandle *GetAlarmHandle(AlarmHandle *aHandleList, otInstance *aInstance) { AlarmHandle *alarmHandle = aHandleList; #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE efr32Iid_t currentIid = (efr32Iid_t)efr32GetIidFromInstance(aInstance); OT_ASSERT(currentIid <= OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); alarmHandle = aHandleList + ((uint8_t)(currentIid - 1)); #else OT_UNUSED_VARIABLE(aInstance); #endif return alarmHandle; } static AlarmHandle *GetFirstFiredAlarm(AlarmHandle *aHandleList) { return GetNextFiredAlarm(aHandleList, aHandleList + OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); } static AlarmHandle *GetNextFiredAlarm(AlarmHandle *aAlarm, const AlarmHandle *aAlarmEnd) { AlarmHandle *nextAlarm = aAlarm; while (nextAlarm && nextAlarm->mFiredCount == 0 && nextAlarm < aAlarmEnd) { nextAlarm++; } if (nextAlarm >= aAlarmEnd) { nextAlarm = NULL; } return nextAlarm; } static inline bool HasAnyAlarmFired(void) { return (GetFirstFiredAlarm(sMsAlarmHandles) != NULL) || (GetFirstFiredAlarm(sUsAlarmHandles) != NULL); } // millisecond-alarm callback SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) STATIC void msAlarmCallback(sl_sleeptimer_timer_handle_t *aHandle, void *aData) { OT_UNUSED_VARIABLE(aHandle); AlarmCallback((AlarmHandle *)aData); } // microsecond-alarm callback SL_CODE_CLASSIFY(SL_CODE_COMPONENT_OT_PLATFORM_ABSTRACTION, SL_CODE_CLASS_TIME_CRITICAL) STATIC void usAlarmCallback(struct RAIL_MultiTimer *tmr, RAIL_Time_t expectedTimeOfEvent, void *cbArg) { OT_UNUSED_VARIABLE(tmr); OT_UNUSED_VARIABLE(expectedTimeOfEvent); AlarmCallback((AlarmHandle *)cbArg); } void efr32AlarmInit(void) { memset(&sl_handle, 0, sizeof sl_handle); memset(&rail_timer, 0, sizeof rail_timer); for (uint8_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++) { sPendingTimeMs[i] = 0; sMsAlarmHandles[i].mThreadInstance = NULL; sMsAlarmHandles[i].mTimerHandle = &sl_handle[i]; sMsAlarmHandles[i].mTimerStart = msTimerStart; sMsAlarmHandles[i].mTimerGetMax = msTimerGetMaxTime; sMsAlarmHandles[i].mTimerGetNow = msTimerGetNow; sMsAlarmHandles[i].mTimerStop = msTimerStop; sMsAlarmHandles[i].mIsRunning = false; sMsAlarmHandles[i].mFiredCount = 0; memset(&sMsAlarmHandles[i].mWrapData, 0, sizeof(wrap_timer_data_t)); sUsAlarmHandles[i].mThreadInstance = NULL; sUsAlarmHandles[i].mTimerHandle = &rail_timer[i]; sUsAlarmHandles[i].mTimerStart = usTimerStart; sUsAlarmHandles[i].mTimerGetMax = usTimerGetMaxTime; sUsAlarmHandles[i].mTimerGetNow = usTimerGetNow; sUsAlarmHandles[i].mTimerStop = usTimerStop; sUsAlarmHandles[i].mIsRunning = false; sUsAlarmHandles[i].mFiredCount = 0; memset(&sUsAlarmHandles[i].mWrapData, 0, sizeof(wrap_timer_data_t)); } } void efr32AlarmProcess(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); otEXPECT(HasAnyAlarmFired()); AlarmHandle *msAlarm = GetFirstFiredAlarm(sMsAlarmHandles); const AlarmHandle *msAlarmEnd = sMsAlarmHandles + OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; StackAlarmCallback alarmCb; while (msAlarm != NULL) { #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otPlatDiagModeGet()) { alarmCb = otPlatDiagAlarmFired; } else #endif { alarmCb = otPlatAlarmMilliFired; } ProcessAlarm(msAlarm, alarmCb); msAlarm = GetNextFiredAlarm(msAlarm, msAlarmEnd); } #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE AlarmHandle *usAlarm = GetFirstFiredAlarm(sUsAlarmHandles); const AlarmHandle *usAlarmEnd = sUsAlarmHandles + OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; while (usAlarm != NULL) { alarmCb = otPlatAlarmMicroFired; ProcessAlarm(usAlarm, alarmCb); usAlarm = GetNextFiredAlarm(usAlarm, usAlarmEnd); } #endif exit: return; } uint64_t efr32AlarmPendingTime(otInstance *aInstance) { uint64_t remaining = GetPendingTime(aInstance); uint32_t now = otPlatAlarmMilliGetNow(); otEXPECT_ACTION(GetAlarmHandle(sMsAlarmHandles, aInstance)->mIsRunning, remaining = 0); if (remaining > now) { remaining -= (uint64_t)now; } exit: return remaining; } bool efr32AlarmIsRunning(otInstance *aInstance) { return (otInstanceIsInitialized(aInstance) ? GetAlarmHandle(sMsAlarmHandles, aInstance)->mIsRunning : false); } #if defined(SL_CATALOG_POWER_MANAGER_PRESENT) // Callback to determine if the system can sleep after an interrupt has fired bool efr32AlarmIsReady(void) { return HasAnyAlarmFired(); } #endif // SL_CATALOG_POWER_MANAGER_PRESENT uint32_t otPlatAlarmMilliGetNow(void) { return sMsAlarmHandles[0].mTimerGetNow(); } uint16_t otPlatTimeGetXtalAccuracy(void) { #if defined(SL_CATALOG_POWER_MANAGER_PRESENT) // For sleepies, we need to account for the low-frequency crystal // accuracy when they go to sleep. Accounting for that as well, // for the worst case. if (efr32AllowSleepCallback()) { return SL_OPENTHREAD_HFXO_ACCURACY + SL_OPENTHREAD_LFXO_ACCURACY; } #endif return SL_OPENTHREAD_HFXO_ACCURACY; } void otPlatAlarmMilliStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) { AlarmHandle *alarm = GetAlarmHandle(sMsAlarmHandles, aInstance); if (alarm->mThreadInstance == NULL) { alarm->mThreadInstance = aInstance; } SetPendingTime(aInstance, (uint64_t)aT0 + (uint64_t)aDt); CORE_ATOMIC_SECTION(StartAlarmAt(alarm, aT0, aDt);) } void otPlatAlarmMilliStop(otInstance *aInstance) { CORE_ATOMIC_SECTION(StopActiveAlarm(GetAlarmHandle(sMsAlarmHandles, aInstance));) } uint32_t otPlatAlarmMicroGetNow(void) { return sUsAlarmHandles[0].mTimerGetNow(); } // Note: This function should be called at least once per wrap // period for the wrap-around logic to work below uint64_t otPlatTimeGet(void) { static uint32_t timerWraps = 0U; static uint32_t prev32TimeUs = 0U; uint32_t now32TimeUs; uint64_t now64TimeUs; CORE_DECLARE_IRQ_STATE; CORE_ENTER_CRITICAL(); now32TimeUs = RAIL_GetTime(); if (now32TimeUs < prev32TimeUs) { timerWraps += 1U; } prev32TimeUs = now32TimeUs; now64TimeUs = ((uint64_t)timerWraps << 32) + now32TimeUs; CORE_EXIT_CRITICAL(); return now64TimeUs; } void otPlatAlarmMicroStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt) { AlarmHandle *alarm = GetAlarmHandle(sUsAlarmHandles, aInstance); if (alarm->mThreadInstance == NULL) { alarm->mThreadInstance = aInstance; } CORE_ATOMIC_SECTION(StartAlarmAt(alarm, aT0, aDt);) } void otPlatAlarmMicroStop(otInstance *aInstance) { CORE_ATOMIC_SECTION(StopActiveAlarm(GetAlarmHandle(sUsAlarmHandles, aInstance));) }