commit 06abcce2337cc853a09635fa70ff3583b5dc80ef
parent 71c38bc3d65907d2997c23b62fdd13f193b62cee
Author: Ambroise Vincent <ambroise.vincent@arm.com>
Date: Wed, 3 Apr 2019 15:05:16 +0100
[dev] Add line editing to console driver
The functionalities are:
- Echo
- Convert NL to CR NL
- Backspace
- ^W erase last word
- ^U erase full line
- ^L repaint line
Change-Id: I18d23a44ec7ad1e45f5f2b1e2293c6e9e7f1a8de
Signed-off-by: Ambroise Vincent <ambroise.vincent@arm.com>
Diffstat:
4 files changed, 211 insertions(+), 33 deletions(-)
diff --git a/drivers/devcons.c b/drivers/devcons.c
@@ -4,15 +4,18 @@
#include <ctype.h>
#include <errno.h>
#include <libk.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <rcode/dev.h>
+#include "dev.h"
#define CONSOUT 2
#define NAMESIZE 15
#define CONSSTATUS 128
+#define CTRL(x) ((x) & 0x1f)
+
enum Orootqid {
Qconsfs,
Qraw,
@@ -33,6 +36,10 @@ struct cons {
static struct cons *cons;
+static char buffer[LINELEN];
+static int head;
+static int tail;
+
static int
conswalk(Chan *c, const char *name)
{
@@ -67,8 +74,15 @@ consstatus(void *buf, Chan *c, int n)
static int
consaddin(void *buf, int n)
{
- if (cons->in || n >= NAMESIZE)
+ if (n >= NAMESIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (cons->in) {
+ errno = ENOMEM;
return -1;
+ }
cons->in = namec(buf, O_READ);
memcpy(cons->inname, buf, n);
@@ -80,8 +94,10 @@ consaddout(void *buf, int n)
{
int i;
- if (n >= NAMESIZE)
+ if (n >= NAMESIZE) {
+ errno = ENAMETOOLONG;
return -1;
+ }
for (i = 0; i < CONSOUT; i++) {
if (!cons->out[i]) {
@@ -90,14 +106,22 @@ consaddout(void *buf, int n)
return 0;
}
}
+ errno = ENOMEM;
return -1;
}
static int
consdelin(void *buf, int n)
{
- if (!cons->in || memcmp(buf, cons->inname, n))
+ if (n >= NAMESIZE) {
+ errno = EINVAL;
return -1;
+ }
+
+ if (!cons->in || memcmp(buf, cons->inname, n)) {
+ errno = ENOENT;
+ return -1;
+ }
chanclose(cons->in);
cons->in = NULL;
@@ -110,6 +134,11 @@ consdelout(void *buf, int n)
{
int i;
+ if (n >= NAMESIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+
for (i = 0; i < CONSOUT; i++) {
if (cons->out[i] && !memcmp(buf, cons->outname[i], n)) {
chanclose(cons->out[i]);
@@ -118,16 +147,49 @@ consdelout(void *buf, int n)
return 0;
}
}
+ errno = ENOENT;
return -1;
}
static int
+conswriteraw(char *buf, int n)
+{
+ int i, idx, r;
+ char wrtbuf[LINELEN];
+ Chan **pp;
+
+ idx = 0;
+ for (i = 0; i < n; i++) {
+ if (idx + 2 > sizeof(wrtbuf))
+ break;
+ if (buf[i] == '\n')
+ wrtbuf[idx++] = '\r';
+ wrtbuf[idx++] = buf[i];
+ }
+
+ r = 0;
+ for (pp = cons->out; pp < &cons->out[CONSOUT]; pp++) {
+ int w;
+ Chan *p;
+
+ if ((p = *pp) == NULL)
+ continue;
+ w = devtab[p->type]->write(p, wrtbuf, idx);
+ if (w < 0) {
+ r = -1;
+ } else if (w != idx) {
+ errno = EIO;
+ r = -1;
+ }
+ }
+ return r < 0 ? r : idx;
+}
+
+static int
conswrite(Chan *c, void *buf, int n)
{
char *tokens[2];
- int written, i;
- Chan *p;
- Chan **pp;
+ int i;
int (*func)(void *, int);
struct conscmds {
@@ -144,42 +206,169 @@ conswrite(Chan *c, void *buf, int n)
switch (c->qid & ~CHDIR) {
case Qconsfs:
+ errno = EISDIR;
return -1;
case Qraw:
- written = -1;
- for (pp = cons->out; pp < &cons->out[CONSOUT]; pp++) {
- if ((p = *pp) == NULL)
- continue;
- written = devtab[p->type]->write(p, buf, n);
- }
- return written;
+ return conswriteraw(buf, n);
case Qctl:
- if (tokenize(buf, n, tokens, 2) != 2)
+ if (tokenize(buf, n, tokens, 2) != 2) {
+ errno = EINVAL;
return -1;
+ }
for (i = 0; i < NELEM(conscmds); i++) {
if (!strcmp(tokens[0], conscmds[i].name)) {
func = conscmds[i].func;
return (*func)(tokens[1], strlen(tokens[1]));
}
}
+ errno = EINVAL;
return -1;
default:
panic("conswrite");
}
}
+static void
+conserase(Chan *c, int n)
+{
+ int i;
+ char editingchar;
+
+ if (head + 1 < n)
+ n = head + 1;
+
+ editingchar = '\b';
+ for (i = 0; i < n; i++)
+ conswrite(c, &editingchar, 1);
+ editingchar = ' ';
+ for (i = 0; i < n; i++)
+ conswrite(c, &editingchar, 1);
+ editingchar = '\b';
+ for (i = 0; i < n; i++)
+ conswrite(c, &editingchar, 1);
+}
+
+static int
+consreadediting(Chan *c)
+{
+ int i;
+ char editingchar;
+
+ /*
+ * XXX: it relies on the fact that head is incremented in the
+ * calling function after exiting this one. For example, head is
+ * briefly equal to -1 when backspace is hit on an empty line.
+ */
+ switch (buffer[head]) {
+ case '\0':
+ return -1;
+ case '\b':
+ case 127: /* DEL */
+ head--;
+ conserase(c, 1);
+ head -= head >= 0;
+ return 0;
+ case CTRL('L'):
+ editingchar = '\r';
+ conswrite(c, &editingchar, 1);
+ conswrite(c, buffer, head);
+ head--;
+ return 0;
+ case CTRL('U'):
+ editingchar = '\r';
+ conswrite(c, &editingchar, 1);
+ editingchar = ' ';
+ for (i = 0; i < head; i++)
+ conswrite(c, &editingchar, 1);
+ editingchar = '\r';
+ conswrite(c, &editingchar, 1);
+ head = -1;
+ return 0;
+ case '\r':
+ buffer[head] = '\n';
+ break;
+ case CTRL('W'):
+ i = 0;
+ head--;
+ while (head - i >= 0 && buffer[head - i] == ' ')
+ i++;
+ while (head - i >= 0 && buffer[head - i] != ' ')
+ i++;
+ conserase(c, i);
+ head -= i;
+ return 0;
+ default:
+ break;
+ }
+
+ conswrite(c, &buffer[head], 1);
+
+ return 0;
+}
+
+static char
+consreadone(void)
+{
+ char ch;
+ Chan *in = cons->in;
+
+ return devtab[in->type]->read(in, &ch, 1) < 0 ? EOF : ch;
+}
+
+static int
+fillbuffer(Chan *c)
+{
+ char ch;
+
+ while (head + 1 < sizeof(buffer) && (ch = consreadone()) != EOF) {
+ buffer[head] = ch;
+ consreadediting(c);
+ if (buffer[head++] == '\n')
+ break;
+ }
+ if (ch == EOF && head == 0) {
+ errno = EIO;
+ return -1;
+ }
+ buffer[head] = '\0';
+
+ return 0;
+}
+
+static int
+readqueue(void *buf, int n)
+{
+ int size = head - tail;
+
+ if (n < size) {
+ memcpy(buf, &buffer[tail], n);
+ tail += n;
+ return n;
+ } else {
+ memcpy(buf, &buffer[tail], size);
+ head = 0;
+ tail = 0;
+ return size;
+ }
+}
+
static int
consread(Chan *c, void *buf, int n)
{
switch (c->qid & ~CHDIR) {
case Qconsfs:
return dirread(c, buf, n, dirtab, NELEM(dirtab), devgen);
- case Qraw: {
- Chan *in = cons->in;
- if (cons->in)
- return devtab[in->type]->read(in, buf, n);
- return -1;
- }
+ case Qraw:
+ if (!cons->in) {
+ errno = ENOENT;
+ return -1;
+ }
+ if (head == 0) {
+ if (fillbuffer(c) < 0)
+ return -1;
+ }
+
+ return readqueue(buf, n);
case Qctl:
return consstatus(buf, c, n);
default:
diff --git a/include/rcode/rcode.h b/include/rcode/rcode.h
@@ -14,6 +14,7 @@
#define NELEM(tab) (sizeof(tab) / sizeof((tab)[0]))
+#define LINELEN 80
#define PAGESIZE 4096
#define IENABLE 1
#define IDISABLE 0
diff --git a/src/romfw/dlang.c b/src/romfw/dlang.c
@@ -15,7 +15,6 @@
#define PREFIX "> "
#define NR_ARGC_MAX 5
-#define LINELEN 80
unsigned in_debug;
jmp_buf dbgrecover;
diff --git a/target/native/rom.c b/target/native/rom.c
@@ -73,16 +73,6 @@ imach(Mach *mp, void *stackp)
}
static void
-itty(void)
-{
- static char lnm[] = "\x1b[20h";
- static char srm[] = "\x1b[12l";
-
- write(1, lnm, sizeof(lnm));
- write(1, srm, sizeof(srm));
-}
-
-static void
info(Mach *mp)
{
dbg("romfw: version %s\n"
@@ -148,7 +138,6 @@ main(void *stackp)
intr(IENABLE);
barrier(ISB);
namespace();
- itty();
info(&mach);
debug();
swtch(framep);