| // Copyright 2017 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include "tests.h" |
| |
| #include <err.h> |
| #include <inttypes.h> |
| #include <malloc.h> |
| #include <platform.h> |
| #include <stdio.h> |
| |
| #include <kernel/event.h> |
| #include <kernel/thread.h> |
| #include <kernel/timer.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/atomic.h> |
| #include <zircon/types.h> |
| |
| static void timer_cb(timer_t* timer, zx_time_t now, void* arg) { |
| event_t* event = (event_t*)arg; |
| event_signal(event, true); |
| } |
| |
| static int timer_do_one_thread(void* arg) { |
| event_t event; |
| timer_t timer; |
| |
| event_init(&event, false, 0); |
| timer_init(&timer); |
| |
| timer_set(&timer, current_time() + ZX_MSEC(10), TIMER_SLACK_CENTER, 0, timer_cb, &event); |
| event_wait(&event); |
| |
| printf("got timer on cpu %u\n", arch_curr_cpu_num()); |
| |
| event_destroy(&event); |
| |
| return 0; |
| } |
| |
| static void timer_test_all_cpus(void) { |
| thread_t* timer_threads[SMP_MAX_CPUS]; |
| uint max = arch_max_num_cpus(); |
| |
| uint i; |
| for (i = 0; i < max; i++) { |
| char name[16]; |
| snprintf(name, sizeof(name), "timer %u\n", i); |
| |
| timer_threads[i] = thread_create_etc( |
| NULL, name, timer_do_one_thread, NULL, |
| DEFAULT_PRIORITY, NULL, NULL, DEFAULT_STACK_SIZE, NULL); |
| if (timer_threads[i] == NULL) { |
| printf("failed to create thread for cpu %u\n", i); |
| return; |
| } |
| thread_set_cpu_affinity(timer_threads[i], cpu_num_to_mask(i)); |
| thread_resume(timer_threads[i]); |
| } |
| uint joined = 0; |
| for (i = 0; i < max; i++) { |
| if (thread_join(timer_threads[i], NULL, ZX_SEC(1)) == 0) { |
| joined += 1; |
| } |
| } |
| printf("%u threads created, %u threads joined\n", max, joined); |
| } |
| |
| static void timer_cb2(timer_t* timer, zx_time_t now, void* arg) { |
| auto timer_count = static_cast<fbl::atomic<size_t>*>(arg); |
| timer_count->fetch_add(1); |
| thread_preempt_set_pending(); |
| } |
| |
| static void timer_test_coalescing(enum slack_mode mode, uint64_t slack, |
| const zx_time_t* deadline, const int64_t* expected_adj, size_t count) { |
| printf("testing coalsecing mode %d\n", mode); |
| |
| fbl::atomic<size_t> timer_count(0); |
| |
| timer_t* timer = (timer_t*)malloc(sizeof(timer_t) * count); |
| |
| printf(" orig new adjustment\n"); |
| for (size_t ix = 0; ix != count; ++ix) { |
| timer_init(&timer[ix]); |
| zx_time_t dl = deadline[ix]; |
| timer_set(&timer[ix], dl, mode, slack, timer_cb2, &timer_count); |
| printf("[%zu] %" PRIu64 " -> %" PRIu64 ", %" PRIi64 "\n", |
| ix, dl, timer[ix].scheduled_time, timer[ix].slack); |
| |
| if (timer[ix].slack != expected_adj[ix]) { |
| printf("\n!! unexpected adjustment! expected %" PRIi64 "\n", expected_adj[ix]); |
| } |
| } |
| |
| // Wait for the timers to fire. |
| while (timer_count.load() != count) { |
| thread_sleep(current_time() + ZX_MSEC(5)); |
| } |
| |
| free(timer); |
| } |
| |
| static void timer_test_coalescing_center(void) { |
| zx_time_t when = current_time() + ZX_MSEC(1); |
| zx_duration_t off = ZX_USEC(10); |
| zx_duration_t slack = 2u * off; |
| |
| const zx_time_t deadline[] = { |
| when + (6u * off), // non-coalesced, adjustment = 0 |
| when, // non-coalesced, adjustment = 0 |
| when - off, // coalesced with [1], adjustment = 10u |
| when - (3u * off), // non-coalesced, adjustment = 0 |
| when + off, // coalesced with [1], adjustment = -10u |
| when + (3u * off), // non-coalesced, adjustment = 0 |
| when + (5u * off), // coalesced with [0], adjustment = 10u |
| when - (3u * off), // non-coalesced, same as [3], adjustment = 0 |
| }; |
| |
| const int64_t expected_adj[fbl::count_of(deadline)] = { |
| 0, 0, ZX_USEC(10), 0, -(int64_t)ZX_USEC(10), 0, ZX_USEC(10), 0}; |
| |
| timer_test_coalescing( |
| TIMER_SLACK_CENTER, slack, deadline, expected_adj, fbl::count_of(deadline)); |
| } |
| |
| static void timer_test_coalescing_late(void) { |
| zx_time_t when = current_time() + ZX_MSEC(1); |
| zx_duration_t off = ZX_USEC(10); |
| zx_duration_t slack = 3u * off; |
| |
| const zx_time_t deadline[] = { |
| when + off, // non-coalesced, adjustment = 0 |
| when + (2u * off), // non-coalesced, adjustment = 0 |
| when - off, // coalesced with [0], adjustment = 20u |
| when - (3u * off), // non-coalesced, adjustment = 0 |
| when + (3u * off), // non-coalesced, adjustment = 0 |
| when + (2u * off), // non-coalesced, same as [1] |
| when - (4u * off), // coalesced with [3], adjustment = 10u |
| }; |
| |
| const int64_t expected_adj[fbl::count_of(deadline)] = { |
| 0, 0, ZX_USEC(20), 0, 0, 0, ZX_USEC(10)}; |
| |
| timer_test_coalescing( |
| TIMER_SLACK_LATE, slack, deadline, expected_adj, fbl::count_of(deadline)); |
| } |
| |
| static void timer_test_coalescing_early(void) { |
| zx_time_t when = current_time() + ZX_MSEC(1); |
| zx_duration_t off = ZX_USEC(10); |
| zx_duration_t slack = 3u * off; |
| |
| const zx_time_t deadline[] = { |
| when, // non-coalesced, adjustment = 0 |
| when + (2u * off), // coalesced with [0], adjustment = -20u |
| when - off, // non-coalesced, adjustment = 0 |
| when - (3u * off), // non-coalesced, adjustment = 0 |
| when + (4u * off), // non-coalesced, adjustment = 0 |
| when + (5u * off), // coalesced with [4], adjustment = -10u |
| when - (2u * off), // coalesced with [3], adjustment = -10u |
| }; |
| |
| const int64_t expected_adj[fbl::count_of(deadline)] = { |
| 0, -(int64_t)ZX_USEC(20), 0, 0, 0, -(int64_t)ZX_USEC(10), -(int64_t)ZX_USEC(10)}; |
| |
| timer_test_coalescing( |
| TIMER_SLACK_EARLY, slack, deadline, expected_adj, fbl::count_of(deadline)); |
| } |
| |
| static void timer_far_deadline(void) { |
| event_t event; |
| timer_t timer; |
| |
| event_init(&event, false, 0); |
| timer_init(&timer); |
| |
| timer_set(&timer, UINT64_MAX - 5, TIMER_SLACK_CENTER, 0, timer_cb, &event); |
| zx_status_t st = event_wait_deadline(&event, current_time() + ZX_MSEC(100), false); |
| if (st != ZX_ERR_TIMED_OUT) { |
| printf("error: unexpected timer fired!\n"); |
| } else { |
| timer_cancel(&timer); |
| } |
| |
| event_destroy(&event); |
| } |
| |
| void timer_tests(void) { |
| timer_test_coalescing_center(); |
| timer_test_coalescing_late(); |
| timer_test_coalescing_early(); |
| timer_test_all_cpus(); |
| timer_far_deadline(); |
| } |