blob: ea1e706b08c1cb7aba76ff8268384b86f5b67d78 [file]
/*
* zyclictest.c: Zephyr cyclictest
*
* Copyright (c) 2025 Andreas Klinger
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zyclictest, LOG_LEVEL_INF);
#include <zephyr/posix/unistd.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/atomic.h>
#ifndef CONFIG_NATIVE_LIBC
extern void getopt_init(void);
#endif
#define ZYC_MAX_HIST 1000
#define ZYC_INT 0
#define ZYC_THR 1
#define MICRO_SEC 1000000ULL
static struct {
uint64_t start_ticks;
int64_t thr_ticks;
int64_t interval_us;
uint64_t start_cycle;
uint64_t irq_cycle;
uint64_t thr_cycle;
uint32_t loops;
int thr_prio;
int max_lat[2];
int cnt_ovl[2];
int lat[2][ZYC_MAX_HIST];
int quiet_mode;
atomic_t cnt_lat;
const struct shell *sh;
} zyc_data;
static struct k_timer zyc_timer;
static atomic_t zyc_running;
static K_KERNEL_STACK_DEFINE(zyc_stack_area, 1024);
static k_tid_t zyc_tid;
static struct k_thread zyc_ctl;
static uint64_t zyc_cyc_sec;
static void zyclictest_thr(void *d0, void *d1, void *d2)
{
int status, i;
uint64_t clat[2];
LOG_DBG("Zyclictest thread started ticks/sec: %d hw-cycles: %llu",
CONFIG_SYS_CLOCK_TICKS_PER_SEC, zyc_cyc_sec);
while (atomic_get(&zyc_running)) {
status = k_timer_status_sync(&zyc_timer);
if (status <= 0) {
k_sleep(K_USEC(zyc_data.interval_us / 4));
continue;
}
zyc_data.thr_cycle = k_cycle_get_64();
clat[ZYC_INT] =
(zyc_data.irq_cycle - zyc_data.start_cycle) * MICRO_SEC / zyc_cyc_sec;
clat[ZYC_THR] =
(zyc_data.thr_cycle - zyc_data.start_cycle) * MICRO_SEC / zyc_cyc_sec;
zyc_data.start_cycle += zyc_cyc_sec * zyc_data.interval_us / MICRO_SEC;
atomic_inc(&zyc_data.cnt_lat);
if (zyc_data.loops && atomic_get(&zyc_data.cnt_lat) >= zyc_data.loops) {
atomic_set(&zyc_running, 0);
}
for (i = 0; i < 2; i++) {
if (clat[i] > zyc_data.max_lat[i]) {
zyc_data.max_lat[i] = clat[i];
}
if (clat[i] < ZYC_MAX_HIST) {
zyc_data.lat[i][clat[i]]++;
} else {
zyc_data.cnt_ovl[i]++;
}
}
LOG_DBG("sta: %d, irq-lat: %llu thr-lat: %llu", status, clat[ZYC_INT],
clat[ZYC_THR]);
}
shell_print(zyc_data.sh, "Finished thread with count: %ld", atomic_get(&zyc_data.cnt_lat));
LOG_DBG("Bye-bye from the zyclictest thread");
}
static void zyclictest_handler(struct k_timer *timer_id)
{
zyc_data.irq_cycle = k_cycle_get_64();
}
static int zyclictest_init(void)
{
int ret;
if (zyc_tid) {
LOG_WRN("zyclictest already running");
return -EBUSY;
}
zyc_cyc_sec = sys_clock_hw_cycles_per_sec();
zyc_data.start_cycle = k_cycle_get_64() + zyc_cyc_sec * zyc_data.interval_us / MICRO_SEC;
k_timer_init(&zyc_timer, zyclictest_handler, NULL);
k_timer_start(&zyc_timer, K_USEC(zyc_data.interval_us), K_USEC(zyc_data.interval_us));
atomic_set(&zyc_running, 1);
zyc_tid =
k_thread_create(&zyc_ctl, zyc_stack_area, K_KERNEL_STACK_SIZEOF(zyc_stack_area),
zyclictest_thr, NULL, NULL, NULL, zyc_data.thr_prio, 0, K_NO_WAIT);
if (zyc_tid != &zyc_ctl) {
LOG_ERR("Error while creating zyclictest thread");
return -ENOMEM;
}
ret = k_thread_name_set(zyc_tid, "zyclictest");
if (ret) {
LOG_ERR("Error while setting zyclictest thread name: %d", ret);
}
return 0;
}
static int zyclictest_exit(void)
{
int ret;
atomic_set(&zyc_running, 0);
ret = k_thread_join(zyc_tid, K_FOREVER);
if (ret) {
LOG_ERR("Error while terminating zyclictest thread: %d", ret);
}
zyc_tid = NULL;
k_timer_stop(&zyc_timer);
return 0;
}
static int zyclictest_print_summary(const struct shell *sh)
{
int max_lat;
int min_int_lat, min_thr_lat;
uint64_t sum_int_lat = 0, sum_thr_lat = 0;
int avg_int_lat, avg_thr_lat;
long cnt;
max_lat = MIN(zyc_data.max_lat[ZYC_THR], ZYC_MAX_HIST - 1);
min_int_lat = min_thr_lat = max_lat;
for (int i = 0; i <= max_lat; i++) {
if (i < min_int_lat && zyc_data.lat[ZYC_INT][i] > 0) {
min_int_lat = i;
}
if (i < min_thr_lat && zyc_data.lat[ZYC_THR][i] > 0) {
min_thr_lat = i;
}
sum_int_lat += zyc_data.lat[ZYC_INT][i] * i;
sum_thr_lat += zyc_data.lat[ZYC_THR][i] * i;
}
cnt = atomic_get(&zyc_data.cnt_lat);
avg_int_lat = (sum_int_lat + cnt / 2) / cnt;
avg_thr_lat = (sum_thr_lat + cnt / 2) / cnt;
shell_print(sh, " \t IRQ\tThread");
shell_print(sh, "Min-Latency:\t%6d\t%6d us", min_int_lat, min_thr_lat);
shell_print(sh, "Avg-Latency:\t%6d\t%6d us", avg_int_lat, avg_thr_lat);
shell_print(sh, "Max-Latency:\t%6d\t%6d us", zyc_data.max_lat[ZYC_INT],
zyc_data.max_lat[ZYC_THR]);
shell_print(sh, "Overflow: \t%6d\t%6d", zyc_data.cnt_ovl[ZYC_INT],
zyc_data.cnt_ovl[ZYC_THR]);
if (!zyc_data.quiet_mode) {
shell_print(sh, "\nHistogram:");
for (int i = 0; i <= max_lat; i++) {
shell_print(sh, "%3d \t%6d\t%6d", i, zyc_data.lat[ZYC_INT][i],
zyc_data.lat[ZYC_THR][i]);
}
}
return 0;
}
static int cmd_zyclictest_start(const struct shell *sh, size_t argc, char **argv)
{
int ret;
int rv;
#ifndef CONFIG_NATIVE_LIBC
getopt_init();
#endif
zyc_data.sh = sh;
zyc_data.thr_prio = -CONFIG_NUM_COOP_PRIORITIES;
zyc_data.interval_us = 1000LL;
atomic_set(&zyc_data.cnt_lat, 0);
zyc_data.loops = 0;
memset(zyc_data.max_lat, 0, sizeof(zyc_data.max_lat));
memset(zyc_data.cnt_ovl, 0, sizeof(zyc_data.cnt_ovl));
memset(zyc_data.lat, 0, sizeof(zyc_data.lat));
while ((rv = getopt(argc, argv, "i:l:p:")) != -1) {
switch (rv) {
case 'i':
ret = 0;
zyc_data.interval_us = shell_strtoull(optarg, 0, &ret);
if (ret) {
shell_error(sh, "invalid interval '%s' ret: %d", optarg, ret);
return -EINVAL;
}
break;
case 'l':
ret = 0;
zyc_data.loops = shell_strtoul(optarg, 0, &ret);
if (ret) {
shell_error(sh, "invalid loop '%s' ret: %d", optarg, ret);
return -EINVAL;
}
break;
case 'p':
ret = 0;
zyc_data.thr_prio = shell_strtol(optarg, 0, &ret);
if ((ret) || (zyc_data.thr_prio < -CONFIG_NUM_COOP_PRIORITIES) ||
(zyc_data.thr_prio > CONFIG_NUM_PREEMPT_PRIORITIES)) {
shell_error(sh, "invalid priority '%s' ret: %d", optarg, ret);
return -EINVAL;
}
break;
default:
return -EINVAL;
}
}
shell_print(sh, "Cycle interval: %llu us", zyc_data.interval_us);
shell_print(sh, "Priority: %d", zyc_data.thr_prio);
ret = zyclictest_init();
if (ret) {
LOG_ERR("Error while initializing zyclictest: %d", ret);
}
return 0;
}
static int cmd_zyclictest_stop(const struct shell *sh, size_t argc, char **argv)
{
int rv;
int cancel = 0;
zyc_data.quiet_mode = 0;
#ifndef CONFIG_NATIVE_LIBC
getopt_init();
#endif
while ((rv = getopt(argc, argv, "cq")) != -1) {
switch (rv) {
case 'c':
cancel = 1;
break;
case 'q':
zyc_data.quiet_mode = 1;
break;
default:
return -EINVAL;
}
}
if (!cancel && zyc_data.loops && atomic_get(&zyc_running)) {
shell_print(sh, "Zyclictest still running: %ld / %u",
atomic_get(&zyc_data.cnt_lat), zyc_data.loops);
return 1;
}
zyclictest_exit();
zyclictest_print_summary(sh);
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(
sub_zyclictest,
SHELL_CMD_ARG(start, NULL, SHELL_HELP("Start Zyclictest",
"[-i <interval-us>] [-l <loops>] [-p <prio>]"),
cmd_zyclictest_start, 1, 6),
SHELL_CMD_ARG(stop, NULL, SHELL_HELP("Stop Zyclictest",
"[-c] [-q]"),
cmd_zyclictest_stop, 1, 2),
SHELL_SUBCMD_SET_END /* Array terminated. */
);
SHELL_CMD_REGISTER(zyclictest, &sub_zyclictest, "Zephyr cyclictest latency measurement", NULL);