AKOS  v1.0.0
Documentation
Loading...
Searching...
No Matches
shell.c
1#include "shell.h"
2
3#include <stdbool.h>
4#include <stddef.h>
5#include <stdint.h>
6#include <string.h>
7
8#include "message.h"
9#include "system.h"
10#include "thread.h"
11
12#if (OS_CFG_USE_SHELL != 0u)
13
14#define AKOS_SHELL_SIG_RX_READY 1
15#define AKOS_SHELL_TOP_REFRESH_TICKS 1000u
16#define AKOS_SHELL_TOP_MAX_THREADS UINT8_MAX
17
18extern const akos_shell_cmd_t __start_shell_cmd_desc[] __attribute__((weak));
19extern const akos_shell_cmd_t __stop_shell_cmd_desc[] __attribute__((weak));
20
21static volatile uint8_t shell_rx_head;
22static volatile uint8_t shell_rx_tail;
23static uint8_t shell_rx_buffer[OS_CFG_SHELL_RX_BUFFER_SIZE];
24static volatile bool shell_rx_notified;
25
26static char shell_line_buffer[OS_CFG_SHELL_LINE_BUFFER_SIZE];
27static uint8_t shell_line_length;
28static bool shell_top_active;
29static bool shell_top_has_snapshot;
30static uint32_t shell_top_prev_idle_ticks;
31static uint32_t shell_top_prev_total_ticks;
32static uint32_t shell_top_prev_thread_ticks[AKOS_SHELL_TOP_MAX_THREADS];
33static uint8_t shell_top_thread_prio[AKOS_SHELL_TOP_MAX_THREADS];
34static uint32_t shell_top_thread_load[AKOS_SHELL_TOP_MAX_THREADS];
35static const char *shell_top_thread_name[AKOS_SHELL_TOP_MAX_THREADS];
36
37static void shell_prompt(void);
38static void shell_process_rx(void);
39static void shell_process_byte(uint8_t byte);
40static void shell_execute_line(void);
41static int shell_tokenize(char *line, char **argv, int argv_max);
42static void shell_show_help(void);
43static void shell_show_top(void);
44static void shell_top_enter(void);
45static void shell_top_exit(void);
46static bool shell_match_path(const char *path, int argc, char **argv, int *consumed_argc);
47
48void akos_shell_feature_init(void)
49{
50 shell_rx_head = 0u;
51 shell_rx_tail = 0u;
52 shell_line_length = 0u;
53 shell_rx_notified = false;
54 shell_top_active = false;
55 shell_top_has_snapshot = false;
56 shell_top_prev_idle_ticks = 0u;
57 shell_top_prev_total_ticks = 0u;
58 memset(shell_top_prev_thread_ticks, 0x00, sizeof(shell_top_prev_thread_ticks));
59 memset(shell_top_thread_prio, 0x00, sizeof(shell_top_thread_prio));
60 memset(shell_top_thread_load, 0x00, sizeof(shell_top_thread_load));
61 memset(shell_top_thread_name, 0x00, sizeof(shell_top_thread_name));
62}
63
64void akos_shell_thread(void *p_arg)
65{
66 (void)p_arg;
67
68 SYS_PRINT("\r\n[akos-shell] ready\r\n");
69 shell_prompt();
70
71 for (;;)
72 {
73 msg_t *msg = akos_thread_wait_for_msg(shell_top_active ? AKOS_SHELL_TOP_REFRESH_TICKS : OS_CFG_DELAY_MAX);
74 if (msg == NULL)
75 {
76 if (shell_top_active)
77 {
78 shell_show_top();
79 }
80 continue;
81 }
82
83 if (akos_message_get_pure_data(msg) == AKOS_SHELL_SIG_RX_READY)
84 {
85 shell_process_rx();
86 }
87
89 }
90}
91
92void akos_shell_rx_isr(uint8_t byte)
93{
94 const uint8_t shell_thread_id = akos_thread_get_shell_thread_id();
95 const uint8_t next = (uint8_t)((shell_rx_head + 1u) % OS_CFG_SHELL_RX_BUFFER_SIZE);
96
97 if (next != shell_rx_tail)
98 {
99 shell_rx_buffer[shell_rx_head] = byte;
100 shell_rx_head = next;
101 }
102
103 if ((shell_thread_id == UINT8_MAX) || shell_rx_notified)
104 {
105 return;
106 }
107
108 shell_rx_notified = true;
109 akos_thread_post_msg_pure(shell_thread_id, AKOS_SHELL_SIG_RX_READY);
110}
111
112static void shell_process_rx(void)
113{
114 for (;;)
115 {
116 uint8_t byte;
117
118 if (shell_rx_tail == shell_rx_head)
119 {
120 shell_rx_notified = false;
121 if (shell_rx_tail == shell_rx_head)
122 {
123 break;
124 }
125 shell_rx_notified = true;
126 }
127
128 byte = shell_rx_buffer[shell_rx_tail];
129 shell_rx_tail = (uint8_t)((shell_rx_tail + 1u) % OS_CFG_SHELL_RX_BUFFER_SIZE);
130 shell_process_byte(byte);
131 }
132}
133
134static void shell_process_byte(uint8_t byte)
135{
136 if (shell_top_active)
137 {
138 if ((byte == 'q') || (byte == 'Q') || (byte == 0x03u))
139 {
140 shell_top_exit();
141 }
142 return;
143 }
144
145 if ((byte == '\r') || (byte == '\n'))
146 {
147 SYS_PRINT("\r\n");
148 shell_line_buffer[shell_line_length] = '\0';
149 shell_execute_line();
150 shell_line_length = 0u;
151 if (!shell_top_active)
152 {
153 shell_prompt();
154 }
155 return;
156 }
157
158 if (((byte == '\b') || (byte == 0x7Fu)) && (shell_line_length > 0u))
159 {
160 shell_line_length--;
161 shell_line_buffer[shell_line_length] = '\0';
162 SYS_PRINT("\b \b");
163 return;
164 }
165
166 if ((byte < 0x20u) || (byte > 0x7Eu))
167 {
168 return;
169 }
170
171 if (shell_line_length < (OS_CFG_SHELL_LINE_BUFFER_SIZE - 1u))
172 {
173 shell_line_buffer[shell_line_length++] = (char)byte;
174 shell_line_buffer[shell_line_length] = '\0';
175 SYS_PRINT("%c", byte);
176 }
177}
178
179static void shell_execute_line(void)
180{
181 char *argv[OS_CFG_SHELL_MAX_ARGS];
182 int argc;
183 int consumed_argc;
184 const akos_shell_cmd_t *cmd = __start_shell_cmd_desc;
185
186 if (shell_line_length == 0u)
187 {
188 return;
189 }
190
191 argc = shell_tokenize(shell_line_buffer, argv, (int)OS_CFG_SHELL_MAX_ARGS);
192 if (argc <= 0)
193 {
194 return;
195 }
196
197 if (strcmp(argv[0], "help") == 0)
198 {
199 shell_show_help();
200 return;
201 }
202
203 if (strcmp(argv[0], "clear") == 0)
204 {
205 SYS_PRINT("\033[2J\033[H");
206 return;
207 }
208
209 if (strcmp(argv[0], "top") == 0)
210 {
211 shell_top_enter();
212 return;
213 }
214
215 for (; cmd < __stop_shell_cmd_desc; ++cmd)
216 {
217 if ((cmd->handler != NULL) && shell_match_path(cmd->path, argc, argv, &consumed_argc))
218 {
219 (void)cmd->handler(argc - consumed_argc, &argv[consumed_argc]);
220 return;
221 }
222 }
223
224 SYS_PRINT("Unknown command: %s\r\n", argv[0]);
225}
226
227static int shell_tokenize(char *line, char **argv, int argv_max)
228{
229 int argc = 0;
230 bool in_token = false;
231
232 while ((*line != '\0') && (argc < argv_max))
233 {
234 if ((*line == ' ') || (*line == '\t'))
235 {
236 *line = '\0';
237 in_token = false;
238 }
239 else if (!in_token)
240 {
241 argv[argc++] = line;
242 in_token = true;
243 }
244
245 line++;
246 }
247
248 return argc;
249}
250
251static void shell_show_help(void)
252{
253 const akos_shell_cmd_t *cmd = __start_shell_cmd_desc;
254
255 SYS_PRINT(" %-16s %s\r\n", "help", "Show available commands");
256 SYS_PRINT(" %-16s %s\r\n", "clear", "Clear terminal screen");
257 SYS_PRINT(" %-16s %s\r\n", "top", "Live MCU load screen, press q to exit");
258
259 for (; cmd < __stop_shell_cmd_desc; ++cmd)
260 {
261 SYS_PRINT(" %-16s %s\r\n", cmd->path, cmd->help);
262 }
263}
264
265static void shell_show_top(void)
266{
267 uint32_t idle_ticks_now;
268 uint32_t total_ticks_now;
269 uint32_t idle_delta;
270 uint32_t total_delta;
271 uint8_t cpu_load;
272 uint8_t runtime_count;
273 uint8_t runtime_limit;
274 uint8_t index;
275 uint8_t prio;
276 uint8_t idle_thread_id;
277 uint8_t shell_thread_id;
278 uint8_t timer_thread_id;
279
280 akos_thread_get_runtime_totals(&idle_ticks_now, &total_ticks_now);
281
282 SYS_PRINT("\033[2J\033[H");
283 SYS_PRINT("AKOS top - press q to exit\r\n\r\n");
284
285 if (!shell_top_has_snapshot)
286 {
287 shell_top_prev_idle_ticks = idle_ticks_now;
288 shell_top_prev_total_ticks = total_ticks_now;
289 runtime_count = akos_thread_get_runtime_count();
290 runtime_limit = runtime_count;
291 for (index = 0u; index < runtime_limit; ++index)
292 {
294 akos_thread_get_runtime_snapshot(index, &snapshot);
295 shell_top_prev_thread_ticks[index] = snapshot.run_ticks;
296 }
297 shell_top_has_snapshot = true;
298 SYS_PRINT("cpu load : sampling...\r\n");
299 return;
300 }
301
302 idle_delta = idle_ticks_now - shell_top_prev_idle_ticks;
303 total_delta = total_ticks_now - shell_top_prev_total_ticks;
304
305 shell_top_prev_idle_ticks = idle_ticks_now;
306 shell_top_prev_total_ticks = total_ticks_now;
307
308 if (total_delta == 0u)
309 {
310 cpu_load = 0u;
311 }
312 else
313 {
314 uint32_t busy_delta = (idle_delta >= total_delta) ? 0u : (total_delta - idle_delta);
315 uint32_t load = (busy_delta * 100u) / total_delta;
316 cpu_load = (load > 100u) ? 100u : (uint8_t)load;
317 }
318
319 SYS_PRINT("total cpu load : %u%%\r\n\r\n", (unsigned)cpu_load);
320 SYS_PRINT("%-5s %-20s %s\r\n", "prio", "name", "load");
321
322 runtime_count = akos_thread_get_runtime_count();
323 runtime_limit = runtime_count;
324 idle_thread_id = akos_thread_get_idle_thread_id();
325 shell_thread_id = akos_thread_get_shell_thread_id();
326 timer_thread_id = akos_thread_get_timer_thread_id();
327 for (index = 0u; index < runtime_limit; ++index)
328 {
330
331 akos_thread_get_runtime_snapshot(index, &snapshot);
332 if (snapshot.id == UINT8_MAX)
333 {
334 continue;
335 }
336
337 uint32_t thread_delta = snapshot.run_ticks - shell_top_prev_thread_ticks[index];
338 shell_top_prev_thread_ticks[index] = snapshot.run_ticks;
339 shell_top_thread_prio[index] = snapshot.prio;
340 shell_top_thread_name[index] = snapshot.name;
341 shell_top_thread_load[index] = (total_delta == 0u) ? 0u : ((thread_delta * 100u) / total_delta);
342 }
343
344 for (prio = 0u; prio < OS_CFG_PRIO_MAX; ++prio)
345 {
346 for (index = 0u; index < runtime_limit; ++index)
347 {
349
350 akos_thread_get_runtime_snapshot(index, &snapshot);
351 if ((snapshot.id == UINT8_MAX) ||
352 (snapshot.id == idle_thread_id) ||
353 (snapshot.id == shell_thread_id) ||
354 (snapshot.id == timer_thread_id) ||
355 (snapshot.prio != prio))
356 {
357 continue;
358 }
359
360 SYS_PRINT("%-5u %-20s %lu%%\r\n",
361 (unsigned)shell_top_thread_prio[index],
362 (shell_top_thread_name[index] != NULL) ? shell_top_thread_name[index] : "-",
363 (unsigned long)shell_top_thread_load[index]);
364 }
365 }
366
367 SYS_PRINT("--------------------------------\r\n");
368 for (prio = 0u; prio < OS_CFG_PRIO_MAX; ++prio)
369 {
370 for (index = 0u; index < runtime_limit; ++index)
371 {
373
374 akos_thread_get_runtime_snapshot(index, &snapshot);
375 if ((snapshot.id == UINT8_MAX) ||
376 ((snapshot.id != shell_thread_id) &&
377 (snapshot.id != timer_thread_id) &&
378 (snapshot.id != idle_thread_id)) ||
379 (snapshot.prio != prio))
380 {
381 continue;
382 }
383
384 SYS_PRINT("%-5u %-20s %lu%%\r\n",
385 (unsigned)shell_top_thread_prio[index],
386 (shell_top_thread_name[index] != NULL) ? shell_top_thread_name[index] : "-",
387 (unsigned long)shell_top_thread_load[index]);
388 }
389 }
390}
391
392static void shell_top_enter(void)
393{
394 shell_top_active = true;
395 shell_top_has_snapshot = false;
396 memset(shell_top_prev_thread_ticks, 0x00, sizeof(shell_top_prev_thread_ticks));
397 memset(shell_top_thread_prio, 0x00, sizeof(shell_top_thread_prio));
398 memset(shell_top_thread_load, 0x00, sizeof(shell_top_thread_load));
399 memset(shell_top_thread_name, 0x00, sizeof(shell_top_thread_name));
400 shell_show_top();
401}
402
403static void shell_top_exit(void)
404{
405 shell_top_active = false;
406 shell_top_has_snapshot = false;
407 SYS_PRINT("\033[2J\033[H");
408 shell_prompt();
409}
410
411static void shell_prompt(void)
412{
413 SYS_PRINT(OS_CFG_SHELL_PROMPT_NAME);
414}
415
416static bool shell_match_path(const char *path, int argc, char **argv, int *consumed_argc)
417{
418 int arg_index = 0;
419
420 while (*path != '\0')
421 {
422 const char *token_start;
423 size_t token_len = 0u;
424
425 while (*path == ' ')
426 {
427 path++;
428 }
429
430 if (*path == '\0')
431 {
432 break;
433 }
434
435 if (arg_index >= argc)
436 {
437 return false;
438 }
439
440 token_start = path;
441 while ((path[token_len] != '\0') && (path[token_len] != ' '))
442 {
443 token_len++;
444 }
445
446 if ((strlen(argv[arg_index]) != token_len) ||
447 (strncmp(argv[arg_index], token_start, token_len) != 0))
448 {
449 return false;
450 }
451
452 arg_index++;
453 path += token_len;
454 }
455
456 *consumed_argc = arg_index;
457 return true;
458}
459
460#else
461
462void akos_shell_feature_init(void)
463{
464}
465
466void akos_shell_thread(void *p_arg)
467{
468 (void)p_arg;
469 for (;;)
470 {
471 }
472}
473
474void akos_shell_rx_isr(uint8_t byte)
475{
476 (void)byte;
477}
478
479#endif
#define OS_CFG_SHELL_RX_BUFFER_SIZE
Definition config.h:52
#define OS_CFG_SHELL_PROMPT_NAME
Definition config.h:44
#define OS_CFG_PRIO_MAX
Definition config.h:26
#define OS_CFG_SHELL_MAX_ARGS
Definition config.h:54
#define OS_CFG_SHELL_LINE_BUFFER_SIZE
Definition config.h:53
#define OS_CFG_DELAY_MAX
Definition config.h:27
Message object and message queue APIs.
void akos_message_free(msg_t *p_msg)
Return message to message pool.
Definition message.c:62
int32_t akos_message_get_pure_data(msg_t *p_msg)
Get signal field from pure message.
Definition message.c:303
Message object stored in queues.
Definition message.h:48
Thread scheduling and thread messaging APIs.
msg_t * akos_thread_wait_for_msg(uint32_t time_out)
Wait for message from current thread queue.
Definition thread.c:937
uint8_t akos_thread_get_timer_thread_id(void)
Get the runtime thread ID assigned to the timer thread.
Definition thread.c:86
uint8_t akos_thread_get_runtime_count(void)
Get number of runtime thread slots, including internal threads.
Definition thread.c:702
void akos_thread_get_runtime_totals(uint32_t *p_idle_ticks, uint32_t *p_total_ticks)
Snapshot accumulated runtime counters.
Definition thread.c:665
uint8_t akos_thread_get_shell_thread_id(void)
Get the runtime thread ID assigned to the shell thread.
Definition thread.c:81
uint8_t akos_thread_get_idle_thread_id(void)
Get the runtime thread ID assigned to the idle thread.
Definition thread.c:76
void akos_thread_post_msg_pure(uint8_t des_thread_id, int32_t sig)
Post pure signal message to destination thread.
Definition thread.c:850
void akos_thread_get_runtime_snapshot(uint8_t index, thread_runtime_snapshot_t *p_snapshot)
Snapshot one runtime thread slot by index.
Definition thread.c:707