/* * Copyright 2024 Google LLC * * 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. */ #pragma once #include "fake_pbl_malloc.h" #include "services/common/new_timer/new_timer.h" #include "util/list.h" #include "drivers/rtc.h" #include "system/passert.h" #include // Structure of a timer typedef struct StubTimer { //! Entry into either the running timers (s_running_timers) list or the idle timers //! (s_idle_timers) ListNode list_node; TimerID id; //list_node); printf(" %p\n", node); if (node == next) { printf("RECURSIVE!!!\n"); return; } node = next; } printf("\n"); } */ static bool prv_id_list_filter(ListNode* node, void* data) { StubTimer* timer = (StubTimer*)node; return timer->id == (uint32_t) data; } static StubTimer* prv_find_timer(TimerID timer_id) { // Look for this timer in either the running or idle list ListNode* node = list_find(s_running_timers, prv_id_list_filter, (void*)(intptr_t)timer_id); if (!node) { node = list_find(s_idle_timers, prv_id_list_filter, (void*)(intptr_t)timer_id); } return (StubTimer *)node; } static int prv_timer_expire_compare_func(void* a, void* b) { return (((StubTimer*)b)->timeout_ms - ((StubTimer*)a)->timeout_ms); } static int stub_new_timer_create(void) { StubTimer *timer = (StubTimer *) kernel_malloc(sizeof(StubTimer)); static int s_next_timer_id = 1; *timer = (StubTimer) { .id = s_next_timer_id++, }; s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node); return timer->id; } //////////////////////////////////// // Stub manipulation: // bool stub_new_timer_start(TimerID timer_id, uint32_t timeout_ms, NewTimerCallback cb, void *cb_data, uint32_t flags) { StubTimer* timer = prv_find_timer(timer_id); // Remove it from its current list if (list_contains(s_running_timers, &timer->list_node)) { list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */); } else { list_remove(&timer->list_node, &s_idle_timers /* &head */, NULL /* &tail */); } // Set timer variables timer->cb = cb; timer->cb_data = cb_data; timer->timeout_ms = timeout_ms; timer->repeating = flags & TIMER_START_FLAG_REPEATING; timer->period_ms = timeout_ms; // Insert into sorted order in the running list s_running_timers = list_sorted_add(s_running_timers, &timer->list_node, prv_timer_expire_compare_func, true); return true; } bool stub_new_timer_stop(TimerID timer_id) { StubTimer* timer = prv_find_timer(timer_id); // Move it to the idle list if it's currently running if (list_contains(s_running_timers, &timer->list_node)) { list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */); s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node); } // Clear the repeating flag so that if they call this method from a callback it won't get // rescheduled timer->repeating = false; timer->timeout_ms = 0; return !timer->executing; } void stub_new_timer_delete(TimerID timer_id) { StubTimer* timer = prv_find_timer(timer_id); // Automatically stop it if it it's not stopped already if (list_contains(s_running_timers, &timer->list_node)) { timer->timeout_ms = 0; list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */); s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node); } timer->repeating = false; // In case it's currently executing, make sure we don't reschedule it if (!timer->executing) { list_remove(&timer->list_node, &s_idle_timers /* &head */, NULL /* &tail */); kernel_free(timer); } else { timer->defer_delete = true; } } bool stub_new_timer_is_scheduled(TimerID timer_id) { StubTimer* timer = prv_find_timer(timer_id); if (timer == NULL) { return false; } return list_contains(s_running_timers, &timer->list_node); } uint32_t stub_new_timer_timeout(TimerID timer_id) { StubTimer* timer = prv_find_timer(timer_id); if (timer == NULL) { return false; } return timer->timeout_ms; } // Mark the timer as executing. This prevents it from getting deleted. In the real implementation, // it would get deleted after it's callback returned void stub_new_timer_set_executing(TimerID timer_id, bool set) { StubTimer* timer = prv_find_timer(timer_id); PBL_ASSERTN(timer != NULL); timer->executing = true; } void * stub_new_timer_callback_data(TimerID timer_id) { StubTimer* timer = prv_find_timer(timer_id); if (timer == NULL) { return false; } return timer->cb_data; } bool stub_new_timer_fire(TimerID timer_id) { StubTimer* timer = prv_find_timer(timer_id); if (timer == NULL) { return false; } if (list_contains(s_running_timers, &timer->list_node)) { list_remove(&timer->list_node, &s_running_timers /* &head */, NULL /* &tail */); s_idle_timers = list_insert_before(s_idle_timers, &timer->list_node); } else { printf("WARNING: Attempted to fire a non-running timer\n"); return false; } timer->timeout_ms = 0; timer->executing = true; timer->cb(timer->cb_data); timer->executing = false; if (timer->defer_delete) { // Timer was deleted from the callback, clean it up now: list_remove(&timer->list_node, &s_idle_timers /* &head */, NULL /* &tail */); kernel_free(timer); return true; } if (timer->repeating && timer->timeout_ms == 0) { stub_new_timer_start(timer_id, timer->period_ms, timer->cb, timer->cb_data, TIMER_START_FLAG_REPEATING); } return true; } void stub_new_timer_cleanup(void) { StubTimer *node = (StubTimer *) s_running_timers; while (node) { StubTimer *next = (StubTimer *) list_get_next(&node->list_node); list_remove(&node->list_node, &s_running_timers, NULL); kernel_free(node); node = next; } PBL_ASSERTN(s_running_timers == NULL); node = (StubTimer *) s_idle_timers; while (node) { StubTimer *next = (StubTimer *) list_get_next(&node->list_node); list_remove(&node->list_node, &s_idle_timers, NULL); kernel_free(node); node = next; } PBL_ASSERTN(s_idle_timers == NULL); } TimerID stub_new_timer_get_next(void) { StubTimer *timer = (StubTimer *) list_get_head(s_running_timers); return timer ? timer->id : TIMER_INVALID_ID; } void stub_new_timer_invoke(int num_to_invoke) { TimerID timer = stub_new_timer_get_next(); while (timer != TIMER_INVALID_ID && num_to_invoke--) { stub_new_timer_fire(timer); timer = stub_new_timer_get_next(); } } // ============================================================================================= // Fakes // Create a new timer TimerID new_timer_create(void) { s_num_new_timer_create_calls++; return stub_new_timer_create(); } // Start a timer bool new_timer_start(TimerID timer_id, uint32_t timeout_ms, NewTimerCallback cb, void *cb_data, uint32_t flags) { s_num_new_timer_start_calls++; s_new_timer_start_param_timer_id = timer_id; s_new_timer_start_param_timeout_ms = timeout_ms; s_new_timer_start_param_cb = cb; s_new_timer_start_param_cb_data = cb_data; return stub_new_timer_start(timer_id, timeout_ms, cb, cb_data, flags); } // Stop a timer bool new_timer_stop(TimerID timer_id) { s_num_new_timer_stop_calls++; return stub_new_timer_stop(timer_id); } // Delete a timer void new_timer_delete(TimerID timer_id) { s_num_new_timer_delete_calls++; stub_new_timer_delete(timer_id); } bool new_timer_scheduled(TimerID timer, uint32_t *expire_ms_p) { s_num_new_timer_schedule_calls++; return stub_new_timer_is_scheduled(timer); }