blob: 7ff8db0f567e62c344716bced1cacedb1f54ac94 [file] [log] [blame]
#include "pico/test.h"
#include "pico/test/xrand.h"
#include "hardware/sync.h"
#include "hardware/sync/spin_lock.h"
#include "pico/multicore.h"
#include "pico/stdio.h"
#include <stdio.h>
uint counter_local[NUM_CORES][NUM_SPIN_LOCKS];
uint counter_shared[NUM_SPIN_LOCKS];
typedef struct test {
const char *name;
void (*prepare)();
void (*run_per_core)();
// Return true for ok:
bool (*check)();
} test_t;
// Increase until fear turns to boredom:
static const uint ITERATIONS = 0x40000;
void prepare_clear_counters(void) {
__mem_fence_acquire();
for (int i = 0; i < NUM_SPIN_LOCKS; ++i) {
for (int j = 0; j < NUM_CORES; ++j) {
counter_local[j][i] = 0;
}
counter_shared[i] = 0;
}
__mem_fence_release();
}
bool check_counter_sums(void) {
__mem_fence_acquire();
bool all_ok = true;
uint full_sum = 0;
for (int i = 0; i < NUM_SPIN_LOCKS; ++i) {
uint per_lock_sum = 0;
for (int j = 0; j < NUM_CORES; ++j) {
per_lock_sum += counter_local[j][i];
if (counter_local[j][i] > ITERATIONS) {
printf("Impossible local counter value %d on core %d: %08x (max %08x)\n",
i, j, counter_local[j][i], ITERATIONS);
all_ok = false;
}
}
if (per_lock_sum != counter_shared[i]) {
printf("Failed sum check for lock %d: expected %08x, actual %08x\n",
i, per_lock_sum, counter_shared[i]
);
all_ok = false;
}
if (counter_shared[i] > ITERATIONS * NUM_CORES) {
printf("Impossible shared counter value %d: %08x (max %08x)\n",
i, counter_shared[i], ITERATIONS * NUM_CORES);
all_ok = false;
}
full_sum += per_lock_sum;
}
if (full_sum != ITERATIONS * NUM_CORES) {
printf("Incorrect counter total: expected %08x, got %08x\n",
ITERATIONS, full_sum);
all_ok = false;
}
return all_ok;
}
void counter_test_per_core(uint lock_index_mask) {
// Each lock has a global counter. Repeatedly, randomly select a lock and
// write to its counter while holding the lock. Also increment a per-core
// counter for that lock, so we can check at the end that the per-core
// values add up.
xrand_state_t state = XRAND_DEFAULT_INIT;
uint core_num = get_core_num();
for (uint i = 0; i < core_num; ++i) {
xrand_jump(&state);
}
for (uint i = 0; i < ITERATIONS; ++i) {
uint lock_index = xrand_next(&state) & lock_index_mask;
spin_lock_t *lock = spin_lock_instance(lock_index);
uint32_t flags = spin_lock_blocking(lock);
counter_shared[lock_index]++;
spin_unlock(lock, flags);
counter_local[core_num][lock_index]++;
busy_wait_at_least_cycles(xrand_next(&state) & 0xffu);
}
}
void counter_try_test_per_core(uint lock_index_mask) {
// Same as counter_test but use the try_lock variant -- worth testing as
// it may be a different asm implementation altogether.
xrand_state_t state = XRAND_DEFAULT_INIT;
uint core_num = get_core_num();
for (uint i = 0; i < core_num; ++i) {
xrand_jump(&state);
}
for (uint i = 0; i < ITERATIONS; ++i) {
uint lock_index = xrand_next(&state) & lock_index_mask;
spin_lock_t *lock = spin_lock_instance(lock_index);
// Assume this test runs without IRQs active
while (!spin_try_lock_unsafe(lock))
;
counter_shared[lock_index]++;
spin_unlock_unsafe(lock);
counter_local[core_num][lock_index]++;
busy_wait_at_least_cycles(xrand_next(&state) & 0xffu);
}
}
// Test with successively fewer locks to increase contention
void counter_test1(void) {
counter_test_per_core(NUM_SPIN_LOCKS - 1);
}
void counter_test2(void) {
counter_test_per_core((NUM_SPIN_LOCKS - 1) >> 1);
}
void counter_test3(void) {
counter_test_per_core((NUM_SPIN_LOCKS - 1) >> 2);
}
void counter_test4(void) {
counter_test_per_core((NUM_SPIN_LOCKS - 1) >> 3);
}
void counter_test5(void) {
counter_test_per_core((NUM_SPIN_LOCKS - 1) >> 4);
}
void counter_try_test1(void) {
counter_try_test_per_core(NUM_SPIN_LOCKS - 1);
}
void counter_try_test2(void) {
counter_try_test_per_core((NUM_SPIN_LOCKS - 1) >> 4);
}
void counter_test_with_irqs(void) {
}
static const test_t tests[] = {
{
"counter test, all locks\n",
prepare_clear_counters,
counter_test1,
check_counter_sums
},
{
"counter test, half of locks\n",
prepare_clear_counters,
counter_test2,
check_counter_sums
},
{
"counter test, 1/4 of locks\n",
prepare_clear_counters,
counter_test3,
check_counter_sums
},
{
"counter test, 1/8 of locks\n",
prepare_clear_counters,
counter_test4,
check_counter_sums
},
{
"counter test, 1/16 of locks\n",
prepare_clear_counters,
counter_test5,
check_counter_sums
},
{
"counter test with try_lock, all locks\n",
prepare_clear_counters,
counter_try_test1,
check_counter_sums
},
{
"counter test with try_lock, 1/16 of locks\n",
prepare_clear_counters,
counter_try_test2,
check_counter_sums
},
};
void core1_main(void) {
while (true) {
void (*f)() = (void(*)())multicore_fifo_pop_blocking();
f();
multicore_fifo_push_blocking(0);
}
}
int main() {
stdio_init_all();
printf("Hello world\n");
multicore_launch_core1(core1_main);
uint failed = 0;
for (int i = 0; i < count_of(tests); ++i) {
const test_t *t = &tests[i];
printf(">>> Starting test: %s\n", t->name);
spin_locks_reset();
t->prepare();
multicore_fifo_push_blocking((uintptr_t)t->run_per_core);
t->run_per_core();
(void)multicore_fifo_pop_blocking();
printf(">>> Finished test: %s\n", t->name);
if (t->check()) {
printf("OK.\n");
} else {
printf("Failed.\n");
++failed;
}
}
if (failed == 0u) {
printf("All tests passed.\n");
return 0;
} else {
printf("%u tests failed. Review log for details.\n", failed);
return -1;
}
}