blob: 92eb1fc82aa6e0326db98613b45de32878ddce87 [file] [log] [blame]
/*
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2025 Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sample_latmon, LOG_LEVEL_DBG);
#include <zephyr/drivers/gpio.h>
#include <zephyr/net/latmon.h>
#include <zephyr/net/socket.h>
#include <zephyr/spinlock.h>
#include <zephyr/sys/atomic.h>
/*
* Blink Control
* DHCP: red
* waiting for connection: blue
* sampling: green
*/
#define LED_WAIT_PERIOD 1000000
#define LED_DHCP_PERIOD 500000
#define LED_RUN_PERIOD 200000
#define BLINK_THREAD_PRIORITY K_IDLE_PRIO
#define BLINK_STACK_SIZE 4096
static K_THREAD_STACK_DEFINE(blink_stack, BLINK_STACK_SIZE);
static const struct gpio_dt_spec pulse =
GPIO_DT_SPEC_GET_OR(DT_PATH(zephyr_user), pulse_gpios, {0});
static const struct gpio_dt_spec ack =
GPIO_DT_SPEC_GET_OR(DT_PATH(zephyr_user), ack_gpios, {0});
static K_SEM_DEFINE(ack_event, 0, 1);
#define DHCP_DONE (atomic_test_bit(&dhcp_done, 0) == true)
#define SET_DHCP_DONE atomic_set_bit(&dhcp_done, 0)
static atomic_val_t dhcp_done;
static struct k_spinlock lock;
static void gpio_ack_handler(const struct device *port,
struct gpio_callback *cb,
gpio_port_pins_t pins)
{
k_sem_give(&ack_event);
}
static int configure_measurement_hardware(void)
{
static struct gpio_callback gpio_cb = { };
int ret = 0;
if (!gpio_is_ready_dt(&pulse) || !gpio_is_ready_dt(&ack)) {
LOG_ERR("GPIO device not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&pulse, GPIO_OUTPUT_HIGH);
if (ret < 0) {
LOG_ERR("failed configuring pulse pin");
return ret;
}
ret = gpio_pin_configure_dt(&ack, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("failed configuring ack pin");
return ret;
}
#if defined(CONFIG_LATMON_LOOPBACK_CALIBRATION)
/*
* Connect GPIO pins in loopback mode for validation (tx to ack)
* On FRDM_K64F, Latmus will show around 3.2 usec of latency.
*
* You can then use these values to adjust the reported latencies (ie,
* subtract the loopback latency from the measured latencies).
*/
ret = gpio_pin_interrupt_configure_dt(&ack, GPIO_INT_EDGE_FALLING);
#else
ret = gpio_pin_interrupt_configure_dt(&ack, GPIO_INT_EDGE_RISING);
#endif
if (ret < 0) {
LOG_ERR("failed configuring ack pin interrupt");
return ret;
}
gpio_init_callback(&gpio_cb, gpio_ack_handler, BIT(ack.pin));
ret = gpio_add_callback_dt(&ack, &gpio_cb);
if (ret < 0) {
LOG_ERR("failed adding ack pin callback");
return ret;
}
return ret;
}
static void blink(void*, void*, void*)
{
const struct gpio_dt_spec led_run =
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios, {0});
const struct gpio_dt_spec led_wait =
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led1), gpios, {0});
const struct gpio_dt_spec led_dhcp =
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led2), gpios, {0});
const struct gpio_dt_spec *led = &led_dhcp, *tmp = NULL;
uint32_t period = LED_DHCP_PERIOD;
if (gpio_is_ready_dt(&led_run)) {
gpio_pin_configure_dt(&led_run, GPIO_OUTPUT_INACTIVE);
}
if (gpio_is_ready_dt(&led_wait)) {
gpio_pin_configure_dt(&led_wait, GPIO_OUTPUT_INACTIVE);
}
if (gpio_is_ready_dt(&led_dhcp)) {
gpio_pin_configure_dt(&led_dhcp, GPIO_OUTPUT_INACTIVE);
}
for (;;) {
k_usleep(period);
if (DHCP_DONE) {
led = net_latmon_running() ? &led_run : &led_wait;
}
if (tmp && led != tmp) {
gpio_pin_set_dt(tmp, 0);
}
if (!gpio_is_ready_dt(led)) {
continue;
}
if (led == &led_wait) {
period = LED_WAIT_PERIOD;
}
if (led == &led_run) {
period = LED_RUN_PERIOD;
}
gpio_pin_toggle_dt(led);
tmp = led;
}
gpio_pin_set_dt(led, 0);
}
static k_tid_t start_led_blinking_thread(struct k_thread *blink_thread,
k_thread_entry_t blink_thread_func)
{
return k_thread_create(blink_thread, blink_stack, BLINK_STACK_SIZE,
(k_thread_entry_t)blink_thread_func,
NULL, NULL, NULL,
BLINK_THREAD_PRIORITY, 0, K_NO_WAIT);
}
/* Raw ticks */
#define CALCULATE_DELTA(ack, pulse) \
((ack) < (pulse) ? \
(~(pulse) + 1 + (ack)) : ((ack) - (pulse)))
static int measure_latency_cycles(uint32_t *delta)
{
k_spinlock_key_t key;
uint32_t tx = 0;
uint32_t rx = 0;
int ret = 0;
/* Remove spurious events */
k_sem_reset(&ack_event);
/* Generate a falling edge pulse to the DUT */
key = k_spin_lock(&lock);
if (gpio_pin_set_dt(&pulse, 0)) {
k_spin_unlock(&lock, key);
LOG_ERR("Failed to set pulse pin");
ret = -1;
goto out;
}
tx = k_cycle_get_32();
k_spin_unlock(&lock, key);
/* Wait for a rising edge from the Latmus controlled DUT */
if (k_sem_take(&ack_event, K_MSEC(1)) == 0) {
rx = k_cycle_get_32();
/* Measure the cycles */
*delta = CALCULATE_DELTA(rx, tx);
} else {
ret = -1;
}
out:
if (gpio_pin_set_dt(&pulse, 1)) {
LOG_ERR("Failed to clear pulse pin");
ret = -1;
}
return ret;
}
int main(void)
{
struct net_if *iface = net_if_get_default();
struct k_thread blink_thread;
static k_tid_t blink_tid;
int client, socket = 0;
int ret = 0;
/* Prepare the instrumentation */
if (configure_measurement_hardware() < 0) {
LOG_ERR("Failed to configure the measurement hardware");
return -1;
}
/* Start visual indicators - dhcp/blue, waiting/red, running/green */
blink_tid = start_led_blinking_thread(&blink_thread, blink);
if (!blink_tid) {
LOG_WRN("Failed to start led blinking thread");
}
/* Get a valid ip */
LOG_INF("DHCPv4: binding...");
net_dhcpv4_start(iface);
for (;;) {
ret = net_mgmt_event_wait(NET_EVENT_IPV4_DHCP_BOUND, NULL,
NULL, NULL, NULL, K_SECONDS(10));
if (ret == -ETIMEDOUT) {
LOG_WRN("DHCPv4: binding timed out, retrying...");
continue;
}
if (ret < 0) {
LOG_ERR("DHCPv4: binding failed, aborting...");
goto out;
}
break;
}
SET_DHCP_DONE;
/* Get a socket to the Latmus port */
socket = net_latmon_get_socket(NULL);
if (socket < 0) {
LOG_ERR("Failed to get a socket to latmon (errno %d)", socket);
ret = -1;
goto out;
}
for (;;) {
/* Wait for Latmus to connect */
client = net_latmon_connect(socket,
&iface->config.dhcpv4.requested_ip);
if (client < 0) {
if (client == -EAGAIN) {
continue;
}
LOG_ERR("Failed to connect to latmon");
ret = -1;
goto out;
}
/* Provide latency data until Latmus closes the connection */
net_latmon_start(client, measure_latency_cycles);
}
out:
k_thread_abort(blink_tid);
close(socket);
return ret;
}