librfn
An ad-hoc utility library
console.c
Go to the documentation of this file.
1 /*
2  * console.c
3  *
4  * Part of librfn (a general utility library from redfelineninja.org.uk)
5  *
6  * Copyright (C) 2014 Daniel Thompson <daniel@redfelineninja.org.uk>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  */
13 
14 #include <librfn/console.h>
15 
16 #include <ctype.h>
17 #include <stdbool.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include <librfn/util.h>
23 
24 static pt_state_t console_echo(console_t *c)
25 {
26  for (int i=1; i<c->argc; i++)
27  fprintf(c->out, " %s", c->argv[i]);
28  fprintf(c->out, "\n");
29  return PT_EXITED;
30 }
31 static const console_cmd_t cmd_echo =
32  CONSOLE_CMD_VAR_INIT("echo", console_echo);
33 
34 static pt_state_t console_help(console_t *c);
35 static const console_cmd_t cmd_help =
36  CONSOLE_CMD_VAR_INIT("help", console_help);
37 
38 static pt_state_t console_unknown(console_t *c)
39 {
40  if (c->argv[0][0])
41  fprintf(c->out, "Unknown/bad command\n");
42  return PT_EXITED;
43 }
44 static const console_cmd_t cmd_unknown =
45  CONSOLE_CMD_VAR_INIT(NULL, console_unknown);
46 
47 static const console_cmd_t *cmd_table[32] = {
48  &cmd_echo,
49  &cmd_help,
50  &cmd_unknown
51 };
52 
53 static pt_state_t console_help(console_t *c)
54 {
55  PT_BEGIN(&c->pt);
56 
57  fprintf(c->out, "Available commands:\n");
58  PT_YIELD();
59 
60  static bool locked;
61  PT_WAIT_UNTIL(!locked);
62  locked = true;
63 
64  static const console_cmd_t **cmd;
65 
66  for (cmd=cmd_table; (*cmd)->name; cmd++) {
67  fprintf(c->out, " %s\n", (*cmd)->name);
68  PT_YIELD();
69  }
70 
71  locked = false;
72  PT_END();
73 }
74 
75 static void do_tokenize(console_t *c)
76 {
77  c->argc = 1;
78  c->argv[0] = c->scratch.buf;
79 
80  /* do the actual tokenization */
81  size_t len = strlen(c->scratch.buf);
82  char quote = '\0';
83  for (size_t i = 1; i < len; i++) {
84  if (isspace((int) c->scratch.buf[i]) && !quote) {
85  c->scratch.buf[i] = '\0';
86  continue;
87  }
88 
89  if (c->scratch.buf[i] == quote) {
90  quote = '\0';
91  c->scratch.buf[i] = '\0';
92  continue;
93  }
94 
95  if (c->scratch.buf[i - 1] == '\0') {
96  if (c->scratch.buf[i] == '\'' ||
97  c->scratch.buf[i] == '"') {
98  quote = c->scratch.buf[i];
99  c->scratch.buf[i] = '\0';
100  } else {
101  c->argv[c->argc] = c->scratch.buf + i;
102  if (++c->argc >= (int) lengthof(c->argv))
103  break;
104  }
105  }
106  }
107 
108  /* ensure all arguments are NULL terminated so we can
109  * avoid error checking in the commands themselves
110  */
111  for (size_t i = c->argc; i < lengthof(c->argv); i++)
112  c->argv[i] = c->scratch.buf + len;
113 }
114 
115 static void find_command(console_t *c)
116 {
117  const console_cmd_t **cmd = cmd_table;
118  for (cmd = cmd_table; (*cmd)->name; cmd++) {
119  if (0 == strcmp(c->argv[0], (*cmd)->name))
120  break;
121  }
122  c->cmd = *cmd;
123 }
124 
125 static void do_prompt(console_t *c)
126 {
127  /* get ready to read the command */
128  memset(c->scratch.buf, 0, sizeof(c->scratch));
129  c->bufp = c->scratch.buf;
130 
131  /* show the prompt */
132  fprintf(c->out, "> ");
133  fflush(c->out);
134 }
135 
136 void console_init(console_t *c, FILE *f)
137 {
138  memset(c, 0, sizeof(console_t));
139 
140  c->out = f;
141  ringbuf_init(&c->ring, c->ringbuf, sizeof(c->ringbuf));
142 
143  console_hwinit(c);
144 
145 #ifndef CONFIG_NO_FIBRE
147  fibre_run(&c->fibre);
148 #endif
149 }
150 
152 {
153  size_t i, j;
154 
155  /* fail if the last command slot is already full */
156  if (cmd_table[lengthof(cmd_table)-1])
157  return -1;
158 
159  for (i = 0; i < lengthof(cmd_table); i++)
160  if (NULL == cmd_table[i]->name ||
161  strcmp(cmd_table[i]->name, cmd->name) > 0)
162  break;
163 
164  for (j = lengthof(cmd_table) - 1; j > i; j--)
165  cmd_table[j] = cmd_table[j-1];
166 
167  cmd_table[i] = cmd;
168 
169  return 0;
170 }
171 
173 {
174  return ringbuf_get(&c->ring);
175 }
176 
177 void console_putchar(console_t *c, char d)
178 {
179  /* this deliberately does not use ringbuf_putchar() because in most
180  * practical systems console_putchar() will be called from an ISR
181  * (which ringbuf_putchar() doesn't like)
182  */
183  ringbuf_put(&c->ring, d);
184 #ifndef CONFIG_NO_FIBRE
185  fibre_run_atomic(&c->fibre);
186 #endif
187 }
188 
189 pt_state_t console_eval(pt_t *pt, console_t *c, const char *cmd)
190 {
191  uint16_t *i = &c->scratch.u16[sizeof(c->scratch.u16)-1];
192 
193  PT_BEGIN(pt);
194 
195  for (*i=0; cmd[*i]; ) {
196  if (ringbuf_put(&c->ring, cmd[*i])) {
197  *i += 1;
198  } else {
199 #ifndef CONFIG_NO_FIBRE
200  fibre_run(&c->fibre);
201 #endif
202  PT_YIELD();
203  }
204  }
205 
206 #ifndef CONFIG_NO_FIBRE
207  fibre_run(&c->fibre);
208 #endif
209  PT_END();
210 }
211 
212 /* This function must remain safe to cast to fibre_entrypoint_t in order to
213  * inter-operate with the fibre scheduler. Currently pt_state_t is a enum (and
214  * therefore is ABI compatible with int) and fibre is the first member of the
215  * console_t structure.
216  */
218 {
219  PT_BEGIN_FIBRE(&c->fibre);
220 
221  if (!c->argc)
222  do_prompt(c);
223  else
224  c->bufp = c->scratch.buf;
225 
226  while (1) {
227  int ch;
228  PT_WAIT_UNTIL((ch = console_getch(c)) != -1);
229 
230  if (ch == '\n' || c->bufp >= &c->scratch.buf[79]) {
231  do_tokenize(c);
232  find_command(c);
233  PT_SPAWN(&c->pt, c->cmd->fn(c));
234  do_prompt(c);
235  } else if (ch == '\b') {
236  if (c->bufp > c->scratch.buf) {
237  c->bufp--;
238  fprintf(c->out, " \b");
239  } else {
240  fprintf(c->out, " ");
241  }
242  fflush(c->out);
243  } else if (ch == 3) { /* Ctrl-C */
244  fprintf(c->out, "\n");
245  do_prompt(c);
246  } else if (ch != '\n') {
247  *c->bufp++ = ch;
248  }
249  }
250 
251  PT_END();
252 }
253 
254 void console_process(console_t *c, char d)
255 {
256  pt_state_t s;
257 
258  /* do *not* use console_putchar() we don't want the fibre to be run */
259  ringbuf_put(&c->ring, d);
260 
261  do {
262  s = console_run(c);
263  } while (s == PT_YIELDED);
264 }
265 
struct charlie c
#define lengthof(x)
Definition: util.h:44
void fibre_init(fibre_t *f, fibre_entrypoint_t *fn)
Dynamic initializer for a fibre descriptor.
Definition: fibre.c:164
Console descriptor.
Definition: console.h:66
const char * name
Definition: console.h:48
void console_hwinit(console_t *c)
Platform dependant function that will be called during console_init().
void console_putchar(console_t *c, char d)
Asynchronously send a character to the command processor.
Definition: console.c:177
#define PT_SPAWN(child, thread)
Call a child thread.
Definition: protothreads.h:149
#define PT_WAIT_UNTIL(c)
Definition: protothreads.h:116
#define CONSOLE_CMD_VAR_INIT(n, f)
Definition: console.h:52
int ringbuf_get(ringbuf_t *rb)
Extract a byte from the ring buffer.
Definition: ringbuf.c:31
ringbuf_t ring
Definition: console.h:72
#define PT_BEGIN(pt)
Definition: protothreads.h:93
#define PT_YIELD()
Definition: protothreads.h:124
uint16_t pt_t
Definition: protothreads.h:86
#define PT_END()
Definition: protothreads.h:100
int fibre_entrypoint_t(struct fibre *)
Definition: fibre.h:59
const console_cmd_t * cmd
Definition: console.h:93
int argc
Definition: console.h:90
void console_process(console_t *c, char d)
Synchronous console function for use in threaded environments.
Definition: console.c:254
char * bufp
Definition: console.h:88
union console::@0 scratch
Console command descriptor.
Definition: console.h:47
FILE * out
Definition: console.h:69
bool fibre_run_atomic(fibre_t *f)
Definition: fibre.c:183
fibre_t fibre
Definition: console.h:67
char * argv[4]
Definition: console.h:91
int console_register(const console_cmd_t *cmd)
Register a new command.
Definition: console.c:151
char buf[SCRATCH_SIZE]
Definition: console.h:82
pt_state_t
Definition: protothreads.h:80
pt_state_t console_run(console_t *c)
Console protothread entrypoint.
Definition: console.c:217
pt_t pt
Definition: console.h:94
int console_getch(console_t *c)
Fetch a character from the command processors queue.
Definition: console.c:172
char ringbuf[16]
Definition: console.h:71
#define PT_BEGIN_FIBRE(f)
Fibre aware alternative to PT_BEGIN().
Definition: fibre.h:103
pt_state_t console_eval(pt_t *pt, console_t *c, const char *cmd)
Proto-thread to inject a string into the command parser.
Definition: console.c:189
bool ringbuf_put(ringbuf_t *rb, uint8_t d)
Insert a byte into the ring buffer.
Definition: ringbuf.c:58
void fibre_run(fibre_t *f)
Definition: fibre.c:173
uint16_t u16[SCRATCH_SIZE/2]
Definition: console.h:84
pt_state_t(* fn)(struct console *c)
Definition: console.h:49
void ringbuf_init(ringbuf_t *rb, void *bufp, size_t buf_len)
Runtime initializer for a ring buffer descriptor.
Definition: ringbuf.c:22
void console_init(console_t *c, FILE *f)
Initialized the console handler.
Definition: console.c:136