1 /*
2  *
3  * SPDX-License-Identifier: Apache-2.0
4  *
5  * Copyright (c) 2025 Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
6  */
7 
8 #include <zephyr/logging/log.h>
9 LOG_MODULE_REGISTER(sample_latmon, LOG_LEVEL_DBG);
10 
11 #include <zephyr/drivers/gpio.h>
12 #include <zephyr/net/latmon.h>
13 #include <zephyr/net/socket.h>
14 #include <zephyr/spinlock.h>
15 #include <zephyr/sys/atomic.h>
16 
17 /*
18  * Blink Control
19  * DHCP: red
20  * waiting for connection: blue
21  * sampling: green
22  */
23 #define LED_WAIT_PERIOD 1000000
24 #define LED_DHCP_PERIOD 500000
25 #define LED_RUN_PERIOD  200000
26 
27 #define BLINK_THREAD_PRIORITY K_IDLE_PRIO
28 #define BLINK_STACK_SIZE 4096
29 static K_THREAD_STACK_DEFINE(blink_stack, BLINK_STACK_SIZE);
30 
31 static const struct gpio_dt_spec pulse =
32 	GPIO_DT_SPEC_GET_OR(DT_PATH(zephyr_user), pulse_gpios, {0});
33 static const struct gpio_dt_spec ack =
34 	GPIO_DT_SPEC_GET_OR(DT_PATH(zephyr_user), ack_gpios, {0});
35 
36 static K_SEM_DEFINE(ack_event, 0, 1);
37 
38 #define DHCP_DONE		(atomic_test_bit(&dhcp_done, 0) == true)
39 #define SET_DHCP_DONE		atomic_set_bit(&dhcp_done, 0)
40 static atomic_val_t dhcp_done;
41 
42 static struct k_spinlock lock;
43 
gpio_ack_handler(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)44 static void gpio_ack_handler(const struct device *port,
45 			     struct gpio_callback *cb,
46 			     gpio_port_pins_t pins)
47 {
48 	k_sem_give(&ack_event);
49 }
50 
configure_measurement_hardware(void)51 static int configure_measurement_hardware(void)
52 {
53 	static struct gpio_callback gpio_cb = { };
54 	int ret = 0;
55 
56 	if (!gpio_is_ready_dt(&pulse) || !gpio_is_ready_dt(&ack)) {
57 		LOG_ERR("GPIO device not ready");
58 		return -ENODEV;
59 	}
60 
61 	ret = gpio_pin_configure_dt(&pulse, GPIO_OUTPUT_HIGH);
62 	if (ret < 0) {
63 		LOG_ERR("failed configuring pulse pin");
64 		return ret;
65 	}
66 
67 	ret = gpio_pin_configure_dt(&ack, GPIO_INPUT);
68 	if (ret < 0) {
69 		LOG_ERR("failed configuring ack pin");
70 		return ret;
71 	}
72 
73 #if defined(CONFIG_LATMON_LOOPBACK_CALIBRATION)
74 	/*
75 	 * Connect GPIO pins in loopback mode for validation (tx to ack)
76 	 * On FRDM_K64F, Latmus will show around 3.2 usec of latency.
77 	 *
78 	 * You can then use these values to adjust the reported latencies (ie,
79 	 * subtract the loopback latency from the measured latencies).
80 	 */
81 	ret = gpio_pin_interrupt_configure_dt(&ack, GPIO_INT_EDGE_FALLING);
82 #else
83 	ret = gpio_pin_interrupt_configure_dt(&ack, GPIO_INT_EDGE_RISING);
84 #endif
85 	if (ret < 0) {
86 		LOG_ERR("failed configuring ack pin interrupt");
87 		return ret;
88 	}
89 
90 	gpio_init_callback(&gpio_cb, gpio_ack_handler, BIT(ack.pin));
91 
92 	ret = gpio_add_callback_dt(&ack, &gpio_cb);
93 	if (ret < 0) {
94 		LOG_ERR("failed adding ack pin callback");
95 		return ret;
96 	}
97 
98 	return ret;
99 }
100 
blink(void *,void *,void *)101 static void blink(void*, void*, void*)
102 {
103 	const struct gpio_dt_spec led_run =
104 		GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios, {0});
105 	const struct gpio_dt_spec led_wait =
106 		GPIO_DT_SPEC_GET_OR(DT_ALIAS(led1), gpios, {0});
107 	const struct gpio_dt_spec led_dhcp =
108 		GPIO_DT_SPEC_GET_OR(DT_ALIAS(led2), gpios, {0});
109 	const struct gpio_dt_spec *led = &led_dhcp, *tmp = NULL;
110 	uint32_t period = LED_DHCP_PERIOD;
111 
112 	if (gpio_is_ready_dt(&led_run)) {
113 		gpio_pin_configure_dt(&led_run, GPIO_OUTPUT_INACTIVE);
114 	}
115 
116 	if (gpio_is_ready_dt(&led_wait)) {
117 		gpio_pin_configure_dt(&led_wait, GPIO_OUTPUT_INACTIVE);
118 	}
119 
120 	if (gpio_is_ready_dt(&led_dhcp)) {
121 		gpio_pin_configure_dt(&led_dhcp, GPIO_OUTPUT_INACTIVE);
122 	}
123 
124 	for (;;) {
125 		k_usleep(period);
126 		if (DHCP_DONE) {
127 			led = net_latmon_running() ? &led_run : &led_wait;
128 		}
129 
130 		if (tmp && led != tmp) {
131 			gpio_pin_set_dt(tmp, 0);
132 		}
133 
134 		if (!gpio_is_ready_dt(led)) {
135 			continue;
136 		}
137 
138 		if (led == &led_wait) {
139 			period = LED_WAIT_PERIOD;
140 		}
141 
142 		if (led == &led_run) {
143 			period = LED_RUN_PERIOD;
144 		}
145 
146 		gpio_pin_toggle_dt(led);
147 		tmp = led;
148 	}
149 	gpio_pin_set_dt(led, 0);
150 }
151 
start_led_blinking_thread(struct k_thread * blink_thread,k_thread_entry_t blink_thread_func)152 static k_tid_t start_led_blinking_thread(struct k_thread *blink_thread,
153 					k_thread_entry_t blink_thread_func)
154 {
155 	return k_thread_create(blink_thread, blink_stack, BLINK_STACK_SIZE,
156 				(k_thread_entry_t)blink_thread_func,
157 				NULL, NULL, NULL,
158 				BLINK_THREAD_PRIORITY, 0, K_NO_WAIT);
159 }
160 
161 /* Raw ticks */
162 #define CALCULATE_DELTA(ack, pulse) \
163 ((ack) < (pulse) ? \
164 (~(pulse) + 1 + (ack)) : ((ack) - (pulse)))
165 
measure_latency_cycles(uint32_t * delta)166 static int measure_latency_cycles(uint32_t *delta)
167 {
168 	k_spinlock_key_t key;
169 	uint32_t tx = 0;
170 	uint32_t rx = 0;
171 	int ret = 0;
172 
173 	/* Remove spurious events */
174 	k_sem_reset(&ack_event);
175 
176 	/* Generate a falling edge pulse to the DUT */
177 	key = k_spin_lock(&lock);
178 	if (gpio_pin_set_dt(&pulse, 0)) {
179 		k_spin_unlock(&lock, key);
180 		LOG_ERR("Failed to set pulse pin");
181 		ret = -1;
182 		goto out;
183 	}
184 	tx = k_cycle_get_32();
185 	k_spin_unlock(&lock, key);
186 
187 	/* Wait for a rising edge from the Latmus controlled DUT */
188 	if (k_sem_take(&ack_event, K_MSEC(1)) == 0) {
189 		rx = k_cycle_get_32();
190 		/* Measure the cycles */
191 		*delta = CALCULATE_DELTA(rx, tx);
192 	} else {
193 		ret = -1;
194 	}
195 out:
196 	if (gpio_pin_set_dt(&pulse, 1)) {
197 		LOG_ERR("Failed to clear pulse pin");
198 		ret = -1;
199 	}
200 
201 	return ret;
202 }
203 
main(void)204 int main(void)
205 {
206 	struct net_if *iface = net_if_get_default();
207 	struct k_thread blink_thread;
208 	static k_tid_t blink_tid;
209 	int client, socket = 0;
210 	int ret = 0;
211 
212 	/* Prepare the instrumentation */
213 	if (configure_measurement_hardware() < 0) {
214 		LOG_ERR("Failed to configure the measurement hardware");
215 		return -1;
216 	}
217 
218 	/* Start visual indicators - dhcp/blue, waiting/red, running/green */
219 	blink_tid = start_led_blinking_thread(&blink_thread, blink);
220 	if (!blink_tid) {
221 		LOG_WRN("Failed to start led blinking thread");
222 	}
223 
224 	/* Get a valid ip */
225 	LOG_INF("DHCPv4: binding...");
226 	net_dhcpv4_start(iface);
227 	for (;;) {
228 		ret = net_mgmt_event_wait(NET_EVENT_IPV4_DHCP_BOUND, NULL,
229 					  NULL, NULL, NULL, K_SECONDS(10));
230 		if (ret == -ETIMEDOUT) {
231 			LOG_WRN("DHCPv4: binding timed out, retrying...");
232 			continue;
233 		}
234 		if (ret < 0) {
235 			LOG_ERR("DHCPv4: binding failed, aborting...");
236 			goto out;
237 		}
238 		break;
239 	}
240 
241 	SET_DHCP_DONE;
242 
243 	/* Get a socket to the Latmus port */
244 	socket = net_latmon_get_socket(NULL);
245 	if (socket < 0) {
246 		LOG_ERR("Failed to get a socket to latmon (errno %d)", socket);
247 		ret = -1;
248 		goto out;
249 	}
250 
251 	for (;;) {
252 		/* Wait for Latmus to connect */
253 		client = net_latmon_connect(socket,
254 					    &iface->config.dhcpv4.requested_ip);
255 		if (client < 0) {
256 			if (client == -EAGAIN) {
257 				continue;
258 			}
259 			LOG_ERR("Failed to connect to latmon");
260 			ret = -1;
261 			goto out;
262 		}
263 
264 		/* Provide latency data until Latmus closes the connection */
265 		net_latmon_start(client, measure_latency_cycles);
266 	}
267 out:
268 	k_thread_abort(blink_tid);
269 	zsock_close(socket);
270 
271 	return ret;
272 }
273