pebble/tests/fakes/fake_new_timer.h
2025-01-27 11:38:16 -08:00

320 lines
9.5 KiB
C

/*
* 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 <stdio.h>
// 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; //<! ID assigned to this timer
//! client provided callback function and argument
NewTimerCallback cb;
void* cb_data;
//! The tick value when this timer will expire (in milliseconds). If the timer isn't currently
//! running (scheduled) this value will be zero.
uint32_t timeout_ms;
//! True if this timer should automatically be rescheduled for period_ms from now
bool repeating;
uint32_t period_ms;
//! True if this timer is currently having its callback executed.
bool executing;
//! Set by the delete function of client tries to delete a timer currently executing it's callback
bool defer_delete;
} StubTimer;
// =============================================================================================
// Stubs
static ListNode *s_running_timers = NULL;
static ListNode *s_idle_timers = NULL;
// Call counters
static int s_num_new_timer_create_calls = 0;
static int s_num_new_timer_start_calls = 0;
static int s_num_new_timer_stop_calls = 0;
static int s_num_new_timer_delete_calls = 0;
static int s_num_new_timer_schedule_calls = 0;
// Last parameters
static TimerID s_new_timer_start_param_timer_id;
static uint32_t s_new_timer_start_param_timeout_ms;
static NewTimerCallback s_new_timer_start_param_cb;
static void * s_new_timer_start_param_cb_data;
// Debug utility
/*
static void prv_print_idle_list(char* title) {
printf("%s IDLE LIST:\n", title);
StubTimer *node = (StubTimer *) s_idle_timers;
while (node) {
StubTimer *next = (StubTimer *) list_get_next(&node->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);
}