altos: Support negative decimal values on command line
[fw/altos] / src / kernel / ao_cmd.c
1 /*
2  * Copyright © 2009 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 #include "ao.h"
20 #include "ao_task.h"
21
22 char    ao_cmd_lex_c;
23 enum ao_cmd_status ao_cmd_status;
24
25 #ifndef AO_CMD_LEN
26 #if AO_PYRO_NUM
27 #define AO_CMD_LEN 128
28 #else
29 #define AO_CMD_LEN      48
30 #endif
31 #endif
32
33 static char     cmd_line[AO_CMD_LEN];
34 static uint8_t  cmd_len;
35 static uint8_t  cmd_i;
36
37 static const char backspace[] = "\010 \010";
38
39 void
40 ao_put_string(const char *s)
41 {
42         char    c;
43         while ((c = *s++))
44                 putchar(c);
45 }
46
47 void
48 ao_cmd_readline(const char *prompt)
49 {
50         char c;
51         if (ao_echo())
52                 ao_put_string(prompt);
53         cmd_len = 0;
54         for (;;) {
55                 flush();
56                 c = getchar();
57                 /* backspace/delete */
58                 if (c == '\010' || c == '\177') {
59                         if (cmd_len != 0) {
60                                 if (ao_echo())
61                                         ao_put_string(backspace);
62                                 --cmd_len;
63                         }
64                         continue;
65                 }
66
67                 /* ^U */
68                 if (c == '\025') {
69                         while (cmd_len != 0) {
70                                 if (ao_echo())
71                                         ao_put_string(backspace);
72                                 --cmd_len;
73                         }
74                         continue;
75                 }
76
77                 /* map CR to NL */
78                 if (c == '\r')
79                         c = '\n';
80
81                 if (c == '\n') {
82                         if (ao_echo())
83                                 putchar('\n');
84                         break;
85                 }
86
87                 if (cmd_len >= AO_CMD_LEN - 2)
88                         continue;
89                 cmd_line[cmd_len++] = c;
90                 if (ao_echo())
91                         putchar(c);
92         }
93         cmd_line[cmd_len++] = '\n';
94         cmd_line[cmd_len++] = '\0';
95         cmd_i = 0;
96 }
97
98 char
99 ao_cmd_lex(void)
100 {
101         ao_cmd_lex_c = '\n';
102         if (cmd_i < cmd_len)
103                 ao_cmd_lex_c = cmd_line[cmd_i++];
104         return ao_cmd_lex_c;
105 }
106
107 static void
108 putnibble(uint8_t v)
109 {
110         if (v < 10)
111                 putchar(v + '0');
112         else
113                 putchar(v + ('a' - 10));
114 }
115
116 uint8_t
117 ao_getnibble(void)
118 {
119         char    c;
120
121         c = getchar();
122         if ('0' <= c && c <= '9')
123                 return c - '0';
124         if ('a' <= c && c <= 'f')
125                 return c - ('a' - 10);
126         if ('A' <= c && c <= 'F')
127                 return c - ('A' - 10);
128         ao_cmd_status = ao_cmd_lex_error;
129         return 0;
130 }
131
132 void
133 ao_cmd_put16(uint16_t v)
134 {
135         ao_cmd_put8(v >> 8);
136         ao_cmd_put8(v);
137 }
138
139 void
140 ao_cmd_put8(uint8_t v)
141 {
142         putnibble((v >> 4) & 0xf);
143         putnibble(v & 0xf);
144 }
145
146 uint8_t
147 ao_cmd_is_white(void)
148 {
149         return ao_cmd_lex_c == ' ' || ao_cmd_lex_c == '\t';
150 }
151
152 void
153 ao_cmd_white(void)
154 {
155         while (ao_cmd_is_white())
156                 ao_cmd_lex();
157 }
158
159 int8_t
160 ao_cmd_hexchar(char c)
161 {
162         if ('0' <= c && c <= '9')
163                 return (c - '0');
164         if ('a' <= c && c <= 'f')
165                 return (c - 'a' + 10);
166         if ('A' <= c && c <= 'F')
167                 return (c - 'A' + 10);
168         return -1;
169 }
170
171 static uint32_t
172 get_hex(uint8_t lim)
173 {
174         uint32_t result = 0;
175         uint8_t i;
176
177         ao_cmd_white();
178         for (i = 0; i < lim; i++) {
179                 int8_t n = ao_cmd_hexchar(ao_cmd_lex_c);
180                 if (n < 0) {
181                         if (i == 0 || lim != 0xff)
182                                 ao_cmd_status = ao_cmd_lex_error;
183                         break;
184                 }
185                 result = (result << 4) | n;
186                 ao_cmd_lex();
187         }
188         return result;
189 }
190
191 uint8_t
192 ao_cmd_hexbyte(void)
193 {
194         return get_hex(2);
195 }
196
197 uint32_t
198 ao_cmd_hex(void)
199 {
200         return get_hex(0xff);
201 }
202
203 uint32_t
204 ao_cmd_decimal(void) 
205 {
206         uint32_t result = 0;
207         uint8_t r = ao_cmd_lex_error;
208         bool negative = false;
209
210         ao_cmd_white();
211         if (ao_cmd_lex_c == '-') {
212                 negative = true;
213                 ao_cmd_lex();
214         }
215         for(;;) {
216                 if ('0' <= ao_cmd_lex_c && ao_cmd_lex_c <= '9')
217                         result = result * 10 + (ao_cmd_lex_c - '0');
218                 else
219                         break;
220                 r = ao_cmd_success;
221                 ao_cmd_lex();
222         }
223         if (r != ao_cmd_success)
224                 ao_cmd_status = r;
225         if (negative)
226                 result = -result;
227         return result;
228 }
229
230 uint8_t
231 ao_match_word(const char *word)
232 {
233         while (*word) {
234                 if (ao_cmd_lex_c != *word) {
235                         ao_cmd_status = ao_cmd_syntax_error;
236                         return 0;
237                 }
238                 word++;
239                 ao_cmd_lex();
240         }
241         return 1;
242 }
243
244 static void
245 echo(void)
246 {
247         uint32_t v = ao_cmd_hex();
248         if (ao_cmd_status == ao_cmd_success)
249                 ao_stdios[ao_cur_stdio].echo = v != 0;
250 }
251
252 static void
253 ao_reboot(void)
254 {
255         ao_cmd_white();
256         if (!ao_match_word("eboot"))
257                 return;
258         /* Delay waiting for the packet master to be turned off
259          * so that we don't end up back in idle mode because we
260          * received a packet after boot.
261          */
262         flush();
263         ao_delay(AO_SEC_TO_TICKS(1));
264         ao_arch_reboot();
265         ao_panic(AO_PANIC_REBOOT);
266 }
267
268 #ifndef HAS_VERSION
269 #define HAS_VERSION 1
270 #endif
271
272 #if HAS_VERSION
273 static void
274 version(void)
275 {
276         printf("manufacturer     %s\n"
277                "product          %s\n"
278                "serial-number    %u\n"
279 #if HAS_LOG && (HAS_FLIGHT || HAS_TRACKER)
280                "current-flight   %u\n"
281 #endif
282 #if HAS_LOG
283                "log-format       %u\n"
284 #if !DISABLE_LOG_SPACE
285                "log-space        %lu\n"
286 #endif
287 #endif
288 #if defined(AO_BOOT_APPLICATION_BASE) && defined(AO_BOOT_APPLICATION_BOUND)
289                "program-space    %u\n"
290 #endif
291 #if AO_VALUE_32
292                "altitude-32      1\n"
293 #endif
294                , ao_manufacturer
295                , ao_product
296                , ao_serial_number
297 #if HAS_LOG && (HAS_FLIGHT || HAS_TRACKER)
298                , ao_flight_number
299 #endif
300 #if HAS_LOG
301                , AO_LOG_FORMAT
302 #if !DISABLE_LOG_SPACE
303                , (unsigned long) ao_storage_log_max
304 #endif
305 #endif
306 #if defined(AO_BOOT_APPLICATION_BASE) && defined(AO_BOOT_APPLICATION_BOUND)
307                , (unsigned) ((uint32_t) AO_BOOT_APPLICATION_BOUND - (uint32_t) AO_BOOT_APPLICATION_BASE)
308 #endif
309                 );
310         printf("software-version %s\n", ao_version);
311 }
312 #endif
313
314 #ifndef NUM_CMDS
315 #define NUM_CMDS        11
316 #endif
317
318 static const struct ao_cmds     *(ao_cmds[NUM_CMDS]);
319 static uint8_t          ao_ncmds;
320
321 static void
322 help(void)
323 {
324         uint8_t cmds;
325         uint8_t cmd;
326         const struct ao_cmds * cs;
327         const char *h;
328         uint8_t e;
329
330         for (cmds = 0; cmds < ao_ncmds; cmds++) {
331                 cs = ao_cmds[cmds];
332                 for (cmd = 0; cs[cmd].func; cmd++) {
333                         h = cs[cmd].help;
334                         ao_put_string(h);
335                         e = strlen(h);
336                         h += e + 1;
337                         e = 45 - e;
338                         while (e--)
339                                 putchar(' ');
340                         ao_put_string(h);
341                         putchar('\n');
342                 }
343         }
344 }
345
346 static void
347 report(void)
348 {
349         switch(ao_cmd_status) {
350         case ao_cmd_lex_error:
351         case ao_cmd_syntax_error:
352                 ao_put_string("Syntax error\n");
353                 ao_cmd_status = 0;
354         default:
355                 break;
356         }
357 }
358
359 void
360 ao_cmd_register(const struct ao_cmds *cmds)
361 {
362         if (ao_ncmds >= NUM_CMDS)
363                 ao_panic(AO_PANIC_CMD);
364         ao_cmds[ao_ncmds++] = cmds;
365 }
366
367 void
368 ao_cmd(void)
369 {
370         char    c;
371         uint8_t cmd, cmds;
372         const struct ao_cmds * cs;
373         void (*func)(void);
374
375         for (;;) {
376                 ao_cmd_readline("> ");
377                 ao_cmd_lex();
378                 ao_cmd_white();
379                 c = ao_cmd_lex_c;
380                 ao_cmd_lex();
381                 if (c == '\r' || c == '\n')
382                         continue;
383                 func = (void (*)(void)) NULL;
384                 for (cmds = 0; cmds < ao_ncmds; cmds++) {
385                         cs = ao_cmds[cmds];
386                         for (cmd = 0; cs[cmd].func; cmd++)
387                                 if (cs[cmd].help[0] == c) {
388                                         func = cs[cmd].func;
389                                         break;
390                                 }
391                         if (func)
392                                 break;
393                 }
394 #if HAS_MONITOR
395                 ao_mutex_get(&ao_monitoring_mutex);
396 #endif
397                 if (func)
398                         (*func)();
399                 else
400                         ao_cmd_status = ao_cmd_syntax_error;
401                 report();
402 #if HAS_MONITOR
403                 ao_mutex_put(&ao_monitoring_mutex);
404 #endif
405         }
406 }
407
408 #if HAS_BOOT_LOADER
409
410 #include <ao_boot.h>
411
412 static void
413 ao_loader(void)
414 {
415         flush();
416         ao_boot_loader();
417 }
418 #endif
419
420 #if HAS_TASK
421 struct ao_task ao_cmd_task;
422 #endif
423
424 const struct ao_cmds    ao_base_cmds[] = {
425         { help,         "?\0Help" },
426 #if HAS_TASK_INFO && HAS_TASK
427         { ao_task_info, "T\0Tasks" },
428 #endif
429         { echo,         "E <0 off, 1 on>\0Echo" },
430         { ao_reboot,    "r eboot\0Reboot" },
431 #if HAS_VERSION
432         { version,      "v\0Version" },
433 #endif
434 #if HAS_BOOT_LOADER
435         { ao_loader,    "X\0Switch to boot loader" },
436 #endif
437         { 0,    NULL },
438 };
439
440 void
441 ao_cmd_init(void)
442 {
443         ao_cmd_register(&ao_base_cmds[0]);
444 #if HAS_TASK
445         ao_add_task(&ao_cmd_task, ao_cmd, "cmd");
446 #endif
447 }