-
Notifications
You must be signed in to change notification settings - Fork 75
/
memfault_demo_shell.c
221 lines (189 loc) · 6.46 KB
/
memfault_demo_shell.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
//! @file
//!
//! Copyright (c) Memfault, Inc.
//! See LICENSE for details
//!
//! @brief
//! Minimal shell/console implementation for platforms that do not include one.
//! NOTE: For simplicity, ANSI escape sequences are not dealt with!
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "memfault/config.h"
#include "memfault/core/compiler.h"
#include "memfault/demo/shell.h"
#include "memfault/demo/shell_commands.h"
#define MEMFAULT_SHELL_MAX_ARGS (16)
#define MEMFAULT_SHELL_PROMPT "mflt> "
#if defined(MEMFAULT_DEMO_SHELL_COMMAND_EXTENSIONS)
// When the extension list is enabled, iterate over both the core commands and
// the extension commands. This construct, despite being pretty intricate,
// saves about ~28 bytes of code space over running the iteration twice in a
// row, and keeps the iterator in one macro, instead of two.
#define MEMFAULT_SHELL_FOR_EACH_COMMAND(command) \
const sMemfaultShellCommand *command = g_memfault_shell_commands; \
for (size_t i = 0; i < g_memfault_num_shell_commands + s_mflt_shell.num_extension_commands; \
++i, command = (i < g_memfault_num_shell_commands) ? \
&g_memfault_shell_commands[i] : \
(s_mflt_shell.extension_commands ? \
&s_mflt_shell.extension_commands[i - g_memfault_num_shell_commands] : \
NULL))
#else
#define MEMFAULT_SHELL_FOR_EACH_COMMAND(command) \
for (const sMemfaultShellCommand *command = g_memfault_shell_commands; \
command < &g_memfault_shell_commands[g_memfault_num_shell_commands]; ++command)
#endif
static struct MemfaultShellContext {
int (*send_char)(char c);
size_t rx_size;
// the char we will ignore when received end-of-line sequences
char eol_ignore_char;
char rx_buffer[MEMFAULT_DEMO_SHELL_RX_BUFFER_SIZE];
#if defined(MEMFAULT_DEMO_SHELL_COMMAND_EXTENSIONS)
const sMemfaultShellCommand *extension_commands;
size_t num_extension_commands;
#endif
} s_mflt_shell;
static bool prv_booted(void) {
return s_mflt_shell.send_char != NULL;
}
static void prv_send_char(char c) {
if (!prv_booted()) {
return;
}
s_mflt_shell.send_char(c);
}
static void prv_echo(char c) {
if (c == '\n') {
prv_send_char('\r');
prv_send_char('\n');
} else if ('\b' == c) {
prv_send_char('\b');
prv_send_char(' ');
prv_send_char('\b');
} else {
prv_send_char(c);
}
}
static char prv_last_char(void) {
return s_mflt_shell.rx_buffer[s_mflt_shell.rx_size - 1];
}
static bool prv_is_rx_buffer_full(void) {
return s_mflt_shell.rx_size >= MEMFAULT_DEMO_SHELL_RX_BUFFER_SIZE;
}
static void prv_reset_rx_buffer(void) {
memset(s_mflt_shell.rx_buffer, 0, sizeof(s_mflt_shell.rx_buffer));
s_mflt_shell.rx_size = 0;
}
static void prv_echo_str(const char *str) {
for (const char *c = str; *c != '\0'; ++c) {
prv_echo(*c);
}
}
static void prv_send_prompt(void) {
prv_echo_str(MEMFAULT_SHELL_PROMPT);
}
static const sMemfaultShellCommand *prv_find_command(const char *name) {
MEMFAULT_SHELL_FOR_EACH_COMMAND (command) {
if (strcmp(command->command, name) == 0) {
return command;
}
}
return NULL;
}
static void prv_process(void) {
if (prv_last_char() != '\n' && !prv_is_rx_buffer_full()) {
return;
}
char *argv[MEMFAULT_SHELL_MAX_ARGS] = { 0 };
int argc = 0;
char *next_arg = NULL;
for (size_t i = 0; i < s_mflt_shell.rx_size && argc < MEMFAULT_SHELL_MAX_ARGS; ++i) {
char *const c = &s_mflt_shell.rx_buffer[i];
if (*c == ' ' || *c == '\n' || i == s_mflt_shell.rx_size - 1) {
*c = '\0';
if (next_arg) {
argv[argc++] = next_arg;
next_arg = NULL;
}
} else if (!next_arg) {
next_arg = c;
}
}
if (s_mflt_shell.rx_size == MEMFAULT_DEMO_SHELL_RX_BUFFER_SIZE) {
prv_echo('\n');
}
if (argc >= 1) {
const sMemfaultShellCommand *command = prv_find_command(argv[0]);
if (!command) {
prv_echo_str("Unknown command: ");
prv_echo_str(argv[0]);
prv_echo('\n');
prv_echo_str("Type 'help' to list all commands\n");
} else {
command->handler(argc, argv);
}
}
prv_reset_rx_buffer();
prv_send_prompt();
}
void memfault_demo_shell_boot(const sMemfaultShellImpl *impl) {
s_mflt_shell.eol_ignore_char = 0;
s_mflt_shell.send_char = impl->send_char;
prv_reset_rx_buffer();
prv_echo_str("\n" MEMFAULT_SHELL_PROMPT);
}
#if defined(MEMFAULT_DEMO_SHELL_COMMAND_EXTENSIONS)
void memfault_shell_command_set_extensions(const sMemfaultShellCommand *const commands,
size_t num_commands) {
s_mflt_shell.extension_commands = commands;
s_mflt_shell.num_extension_commands = num_commands;
}
#endif
//! Logic to deal with CR, LF, CRLF, or LFCR end-of-line (EOL) sequences
//! @return true if the character should be ignored, false otherwise
static bool prv_should_ignore_eol_char(char c) {
if (s_mflt_shell.eol_ignore_char != 0) {
return (c == s_mflt_shell.eol_ignore_char);
}
//
// Check to see if we have encountered our first newline character since the shell was booted
// (either a CR ('\r') or LF ('\n')). Once found, we will use this character as our EOL delimiter
// and ignore the opposite character if we see it in the future.
//
if (c == '\r') {
s_mflt_shell.eol_ignore_char = '\n';
} else if (c == '\n') {
s_mflt_shell.eol_ignore_char = '\r';
}
return false;
}
void memfault_demo_shell_receive_char(char c) {
if (prv_should_ignore_eol_char(c) || prv_is_rx_buffer_full() || !prv_booted()) {
return;
}
const bool is_backspace = (c == '\b');
if (is_backspace && s_mflt_shell.rx_size == 0) {
return; // nothing left to delete so don't echo the backspace
}
// CR are our EOL delimiter. Remap as a LF here since that's what internal handling logic expects
if (c == '\r') {
c = '\n';
}
prv_echo(c);
if (is_backspace) {
s_mflt_shell.rx_buffer[--s_mflt_shell.rx_size] = '\0';
return;
}
s_mflt_shell.rx_buffer[s_mflt_shell.rx_size++] = c;
prv_process();
}
int memfault_shell_help_handler(MEMFAULT_UNUSED int argc, MEMFAULT_UNUSED char *argv[]) {
MEMFAULT_SHELL_FOR_EACH_COMMAND (command) {
prv_echo_str(command->command);
prv_echo_str(": ");
prv_echo_str(command->help);
prv_echo('\n');
}
return 0;
}