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