212 lines
8.6 KiB
C
212 lines
8.6 KiB
C
/**
|
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <hardware/sync.h>
|
|
#include "pico/stdlib.h"
|
|
#include "pico/test.h"
|
|
#include <inttypes.h>
|
|
PICOTEST_MODULE_NAME("pico_time_test", "pico_time test harness");
|
|
|
|
#define NUM_TIMEOUTS 500
|
|
#define MAX_TIMERS_PER_POOL 250
|
|
static_assert(PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS >= MAX_TIMERS_PER_POOL, "");
|
|
#define TEST_LENGTH_US 2000000
|
|
|
|
#define NUM_REPEATING_TIMERS 50
|
|
static struct repeating_timer repeating_timers[NUM_REPEATING_TIMERS];
|
|
static uint repeating_timer_callback_count[NUM_REPEATING_TIMERS];
|
|
|
|
static struct timeout {
|
|
alarm_id_t alarm_id;
|
|
absolute_time_t target;
|
|
absolute_time_t fired_at;
|
|
uint pool;
|
|
uint fired_count;
|
|
bool cancelled;
|
|
bool not_cancelled; // tried to cancel but it was done
|
|
} timeouts[NUM_TIMEOUTS];
|
|
|
|
int64_t timer_callback1(alarm_id_t id, void *user_data) {
|
|
struct timeout *timeout = (struct timeout *)user_data;
|
|
assert(timeout >= timeouts && timeout < (timeouts + NUM_TIMEOUTS));
|
|
timeout->fired_at = get_absolute_time();
|
|
timeout->fired_count++;
|
|
// printf("%d %d %ld\n", timeout->pool, id, to_us_since_boot(timeout->target));
|
|
return 0;
|
|
}
|
|
|
|
int sort_by_target(const void *a, const void *b) {
|
|
const struct timeout *ta = (const struct timeout *)a;
|
|
const struct timeout *tb = (const struct timeout *)b;
|
|
int64_t delta = absolute_time_diff_us(tb->target, ta->target);
|
|
if (delta < 0) return -1;
|
|
else if (delta > 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static bool repeating_timer_callback(struct repeating_timer *t) {
|
|
// check we get the right timer structure
|
|
uint i = (uintptr_t)t->user_data;
|
|
hard_assert(i == (t - repeating_timers));
|
|
repeating_timer_callback_count[i]++;
|
|
return true;
|
|
}
|
|
|
|
#ifndef PICO_HARDWARE_TIMER_RESOLUTION_US
|
|
#define RESOLUTION_ALLOWANCE 0
|
|
#else
|
|
#define RESOLUTION_ALLOWANCE PICO_HARDWARE_TIMER_RESOLUTION_US
|
|
#endif
|
|
|
|
int main() {
|
|
setup_default_uart();
|
|
alarm_pool_init_default();
|
|
|
|
PICOTEST_START();
|
|
|
|
struct alarm_pool *pools[NUM_TIMERS];
|
|
for(uint i=0; i<NUM_TIMERS; i++) {
|
|
if (i == alarm_pool_hardware_alarm_num(alarm_pool_get_default())) {
|
|
pools[i] = alarm_pool_get_default();
|
|
} else {
|
|
pools[i] = alarm_pool_create(i, MAX_TIMERS_PER_POOL);
|
|
}
|
|
PICOTEST_CHECK_AND_ABORT(pools[i], "failed to create timer pool");
|
|
}
|
|
|
|
// Check default config has valid data in it
|
|
PICOTEST_START_SECTION("Alarm ordering test");
|
|
|
|
absolute_time_t time_base = get_absolute_time();
|
|
uint32_t init_ms = 1000;
|
|
for(uint i = 0; i < NUM_TIMEOUTS; i++) {
|
|
// printf("%d %p\n", i);
|
|
absolute_time_t target;
|
|
uint pool;
|
|
if (1 == (i&127u)) {
|
|
// want occasional duplicate time
|
|
target = timeouts[i-1].target;
|
|
pool = timeouts[i-1].pool;
|
|
} else {
|
|
target = delayed_by_us(time_base, init_ms + (rand() % TEST_LENGTH_US));
|
|
pool = rand() % 4;
|
|
}
|
|
timeouts[i].target = target;
|
|
timeouts[i].pool = pool;
|
|
alarm_id_t id = alarm_pool_add_alarm_at(pools[pool], target, timer_callback1, timeouts + i, true);
|
|
PICOTEST_CHECK_AND_ABORT(id >=0, "Failed to add timer");
|
|
}
|
|
PICOTEST_CHECK(absolute_time_diff_us(time_base, get_absolute_time()) < init_ms * 1000, "This is a flaky test :-(");
|
|
|
|
uint64_t last_fired_at[NUM_TIMERS];
|
|
uint64_t last_target[NUM_TIMERS];
|
|
memset(&last_fired_at, 0, sizeof(last_fired_at));
|
|
printf("Sleeping...\n");
|
|
sleep_us(TEST_LENGTH_US + 250000);
|
|
printf(" ...done\n");
|
|
|
|
qsort(timeouts, NUM_TIMEOUTS, sizeof(struct timeout), sort_by_target);
|
|
|
|
uint64_t max_jitter = 0;
|
|
for(uint i = 0; i < NUM_TIMEOUTS; i++) {
|
|
printf("%d %d %"PRIi64" : %"PRIi64"\n", timeouts[i].pool, timeouts[i].fired_count, to_us_since_boot(timeouts[i].fired_at), to_us_since_boot(timeouts[i].target));
|
|
PICOTEST_CHECK(timeouts[i].fired_count, "Timer should have fired");
|
|
PICOTEST_CHECK(timeouts[i].fired_count < 2, "Timer should only have fired once");
|
|
uint64_t fired_at = to_us_since_boot(timeouts[i].fired_at);
|
|
PICOTEST_CHECK(timeouts[i].fired_count != 1 || fired_at >= MAX(RESOLUTION_ALLOWANCE,
|
|
to_us_since_boot(timeouts[i].target)) - RESOLUTION_ALLOWANCE, "Timer fired early");
|
|
// we need to be in order unless the targets are the same in which case order is arbitrary
|
|
PICOTEST_CHECK(timeouts[i].fired_count != 1 || fired_at > MAX(RESOLUTION_ALLOWANCE, last_fired_at[timeouts[i].pool]) - RESOLUTION_ALLOWANCE ||
|
|
to_us_since_boot(timeouts[i].target) == last_target[timeouts[i].pool], "Timer fired out of order");
|
|
last_fired_at[timeouts[i].pool] = fired_at;
|
|
last_target[timeouts[i].pool] = to_us_since_boot(timeouts[i].target);
|
|
if (timeouts[i].fired_count == 1) {
|
|
uint64_t jitter = absolute_time_diff_us(timeouts[i].target, timeouts[i].fired_at);
|
|
if (jitter > max_jitter) {
|
|
max_jitter = jitter;
|
|
}
|
|
}
|
|
}
|
|
printf("MAX JITTER: %dus\n", (uint)max_jitter);
|
|
|
|
PICOTEST_END_SECTION();
|
|
|
|
PICOTEST_START_SECTION("Alarm completion or canceled");
|
|
memset(timeouts, 0, sizeof(timeouts));
|
|
|
|
absolute_time_t time_base = get_absolute_time();
|
|
// this runs concurrently with the firing, so some are in the past
|
|
uint approx_past_timeouts = 0;
|
|
// uint32_t save = save_and_disable_interrupts();
|
|
for(uint i = 0; i < NUM_TIMEOUTS; i++) {
|
|
// printf("%d %p\n", i);
|
|
absolute_time_t target = delayed_by_us(time_base, (rand() % TEST_LENGTH_US));
|
|
if (absolute_time_diff_us(target, get_absolute_time()) >= 0) {
|
|
approx_past_timeouts++;
|
|
}
|
|
uint pool = rand() % 4;
|
|
timeouts[i].target = target;
|
|
timeouts[i].pool = pool;
|
|
alarm_id_t id = alarm_pool_add_alarm_at(pools[pool], target, timer_callback1, timeouts + i, true);
|
|
timeouts[i].alarm_id = id;
|
|
PICOTEST_CHECK_AND_ABORT(id >=0, "Failed to add timer");
|
|
if (id && !(rand() & 6)) {
|
|
uint j = rand() % (i + 1);
|
|
if (timeouts[j].alarm_id && !timeouts[j].cancelled && !timeouts[j].not_cancelled) {
|
|
// alarm_pool_dump(pools[pool]);
|
|
// printf("removing %d\n", timeouts[j].alarm_id);
|
|
if (alarm_pool_cancel_alarm(pools[timeouts[j].pool], timeouts[j].alarm_id)) {
|
|
timeouts[j].cancelled = true;
|
|
} else {
|
|
timeouts[j].not_cancelled = true;
|
|
}
|
|
// printf("removed %d\n", timeouts[j].alarm_id);
|
|
// alarm_pool_dump(pools[pool]);
|
|
}
|
|
}
|
|
busy_wait_us_32(2000); // we want to overlap with the firing
|
|
}
|
|
printf("approx past timeouts %d/%d\n", approx_past_timeouts, NUM_TIMEOUTS);
|
|
sleep_us(TEST_LENGTH_US - 2000 * NUM_TIMEOUTS / 4 + 250000);
|
|
for(uint i = 0; i < NUM_TIMEOUTS/4; i++) {
|
|
printf("%d %d %d/%d/%d %"PRIi64" : %"PRIi64"\n", timeouts[i].pool, (int)timeouts[i].alarm_id, timeouts[i].fired_count, timeouts[i].cancelled,
|
|
timeouts[i].not_cancelled, to_us_since_boot(timeouts[i].fired_at), to_us_since_boot(timeouts[i].target));
|
|
uint total = timeouts[i].fired_count + timeouts[i].cancelled;
|
|
PICOTEST_CHECK( timeouts[i].not_cancelled ? timeouts[i].fired_count : true, "Timer that failed to cancel should have fired");
|
|
PICOTEST_CHECK(total == 1, "Timer should have fired or been cancelled");
|
|
}
|
|
|
|
PICOTEST_END_SECTION();
|
|
|
|
PICOTEST_START_SECTION("Repeating timertest");
|
|
for(uint i=0;i<NUM_REPEATING_TIMERS;i++) {
|
|
|
|
add_repeating_timer_us(500+ (rand() & 1023), repeating_timer_callback, (void *)(uintptr_t)i, repeating_timers + i);
|
|
}
|
|
|
|
sleep_ms(3000);
|
|
uint callbacks = 0;
|
|
for(uint i=0;i<NUM_REPEATING_TIMERS;i++) {
|
|
PICOTEST_CHECK(cancel_repeating_timer(repeating_timers + i), "Cancelling repeating timer should succeed");
|
|
PICOTEST_CHECK(repeating_timer_callback_count[i] > 1, "Each repeating timer should have been called back multiple times");
|
|
callbacks += repeating_timer_callback_count[i];
|
|
}
|
|
uint callbacks2 = 0;
|
|
for(uint i=0;i<NUM_REPEATING_TIMERS;i++) {
|
|
PICOTEST_CHECK(!cancel_repeating_timer(repeating_timers + i), "Re-cancelling repeating timer should fail");
|
|
callbacks2 += repeating_timer_callback_count[i];
|
|
}
|
|
PICOTEST_CHECK(callbacks == callbacks2, "No repeating timers should have been called back after being cancelled")
|
|
|
|
PICOTEST_END_SECTION();
|
|
|
|
PICOTEST_END_TEST();
|
|
}
|
|
|