summaryrefslogtreecommitdiff
path: root/sys/src/cmd/cec
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/cec
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/cec')
-rwxr-xr-xsys/src/cmd/cec/LICENSE31
-rwxr-xr-xsys/src/cmd/cec/Protocol93
-rwxr-xr-xsys/src/cmd/cec/cec.c558
-rwxr-xr-xsys/src/cmd/cec/cec.h38
-rwxr-xr-xsys/src/cmd/cec/mkfile14
-rwxr-xr-xsys/src/cmd/cec/mux.c110
-rwxr-xr-xsys/src/cmd/cec/plan9.c89
-rwxr-xr-xsys/src/cmd/cec/utils.c52
8 files changed, 985 insertions, 0 deletions
diff --git a/sys/src/cmd/cec/LICENSE b/sys/src/cmd/cec/LICENSE
new file mode 100755
index 000000000..40f3d2c68
--- /dev/null
+++ b/sys/src/cmd/cec/LICENSE
@@ -0,0 +1,31 @@
+The FreeBSD License
+
+Copyright © 2006-8 Coraid, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+This software is provided by Coraid “as is” and any express or
+implied warranties, including, but not limited to, the implied
+warranties of merchantability and fitness for a particular purpose are
+disclaimed. In no event shall Coraid or contributors be liable for
+any direct, indirect, incidental, special, exemplary, or consequential
+damages (including, but not limited to, procurement of substitute
+goods or services; loss of use, data, or profits; or business
+interruption) however caused and on any theory of liability, whether
+in contract, strict liability, or tort (including negligence or
+otherwise) arising in any way out of the use of this software, even if
+advised of the possibility of such damage.
+
+The views and conclusions contained in the software and documentation
+are those of the authors and should not be interpreted as representing
+official policies, either expressed or implied, of Coraid.
diff --git a/sys/src/cmd/cec/Protocol b/sys/src/cmd/cec/Protocol
new file mode 100755
index 000000000..196bc5db4
--- /dev/null
+++ b/sys/src/cmd/cec/Protocol
@@ -0,0 +1,93 @@
+Coraid Ethernet Console (cec)
+
+Abstract
+
+The Coraid Ethernet Console (cec) implements a bidirectional conversation
+over raw ethernet frames with provisions for retransmission and discovery.
+Like serial consoles, each machine has a single shared interface per service
+type, but any number connections can be made from any machine connected to
+the same physical network. The communications from the various connections
+will be interleaved. The first implementation of cec is for console communications
+to Coraid appliances.
+
+Outline
+
+1. Packet format
+2. The Tdiscover packet and Toffer reply.
+3. Initializing a connection. Tinit[abc]
+4. The connection. Tdata and Tack
+5. Closing the connection. Treset
+
+1. Packet Format
+
+All cec packets are follow the layout implied by the following structure
+
+ struct Pkt {
+ uchar dst[6]; // destination ethernet address
+ uchar src[6]; // source ethernet address
+ uchar etype[2]; // service type
+ uchar type; // type of packet.
+ uchar conn; // unique connection id.
+ uchar seq; // packet sequence number
+ uchar len; // data length.
+ uchar data[1500];
+ };
+
+The packet types are as follows:
+
+ enum {
+ Tinita,
+ Tinitb,
+ Tinitc,
+ Tdata,
+ Tack,
+ Tdiscover,
+ Toffer,
+ Treset,
+ };
+
+2. The Tdiscover packet and Toffer reply.
+
+The Tdiscover packet is used to discover the avaliable cec devices on the local
+network. The packet sent is of the form
+
+ Tdiscover = {
+ [dest] 0xffffffff,
+ [src] mac of local interface,
+ [etype] service type,
+ [type] Tdiscover,
+ [seq] 0,
+ [len] 0,
+ [data] 0,
+ }
+
+The reply to the Tdiscover packet is of type Toffer which differes from
+Tdiscover in that data and len may be set. The contents of data is application
+specific.
+
+3. Initializing a connection. Tinit[abc]
+
+A connection is initialized by the following conversation: In addition
+to the fields set for the Tdiscover packet, the client sends a packet
+of type Tinita with the conntag of its choice. The server responds to
+Tinita with a packet of type Tinitb. And finally the client sents a
+Tinitc packet back to the server, completing the connection.
+
+4. The connection. Tdata and Tack
+
+Data is sent from the client to the console server with the Tdata packet.
+The seq field is incremented for each data packet sent. Thus data packets
+may be transmitted if lost. The data is whatever data the client has to
+send to the server, up to 255 bytes. Typically, however, each keystroke
+is sent in a seperate packet. The len field is the length of the
+data.
+
+The server responds to a Tdata message with a Tack message with the
+same seq sequence number.
+
+5. Closing the connection. Treset
+
+Either the server of the client may send a Treset message to close the
+connection. There is no response to a Treset message, however any
+information associated with that connection (conntag) is discarded
+when the Treset message is recieved.
diff --git a/sys/src/cmd/cec/cec.c b/sys/src/cmd/cec/cec.c
new file mode 100755
index 000000000..f962a037e
--- /dev/null
+++ b/sys/src/cmd/cec/cec.c
@@ -0,0 +1,558 @@
+/*
+ * cec — coraid ethernet console
+ * Copyright © Coraid, Inc. 2006-2008.
+ * All Rights Reserved.
+ */
+#include <u.h>
+#include <libc.h>
+#include <ip.h> /* really! */
+#include <ctype.h>
+#include "cec.h"
+
+enum {
+ Tinita = 0,
+ Tinitb,
+ Tinitc,
+ Tdata,
+ Tack,
+ Tdiscover,
+ Toffer,
+ Treset,
+
+ Hdrsz = 18,
+ Eaddrlen = 6,
+};
+
+typedef struct{
+ uchar ea[Eaddrlen];
+ int major;
+ char name[28];
+} Shelf;
+
+int conn(int);
+void gettingkilled(int);
+int pickone(void);
+void probe(void);
+void sethdr(Pkt *, int);
+int shelfidx(void);
+
+Shelf *con;
+Shelf tab[1000];
+
+char *host;
+char *srv;
+char *svc;
+
+char pflag;
+
+int ntab;
+int shelf = -1;
+
+uchar bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+uchar contag;
+uchar esc = '';
+uchar ea[Eaddrlen];
+uchar unsetea[Eaddrlen];
+
+extern int fd; /* set in netopen */
+
+void
+post(char *srv, int fd)
+{
+ char buf[32];
+ int f;
+
+ if((f = create(srv, OWRITE, 0666)) == -1)
+ sysfatal("create %s: %r", srv);
+ snprint(buf, sizeof buf, "%d", fd);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ sysfatal("write %s: %r", srv);
+ close(f);
+}
+
+void
+dosrv(char *s)
+{
+ int p[2];
+
+ if(pipe(p) < 0)
+ sysfatal("pipe: %r");
+ if (srv[0] != '/')
+ svc = smprint("/srv/%s", s);
+ else
+ svc = smprint("%s", s);
+ post(svc, p[0]);
+ close(p[0]);
+ dup(p[1], 0);
+ dup(p[1], 1);
+
+ switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
+ case -1:
+ sysfatal("fork: %r");
+ case 0:
+ break;
+ default:
+ exits("");
+ }
+ close(2);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: cec [-dp] [-c esc] [-e ea] [-h host] [-s shelf] "
+ "[-S srv] interface\n");
+ exits0("usage");
+}
+
+void
+catch(void*, char *note)
+{
+ if(strcmp(note, "alarm") == 0)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+int
+nilea(uchar *ea)
+{
+ return memcmp(ea, unsetea, Eaddrlen) == 0;
+}
+
+void
+main(int argc, char **argv)
+{
+ int r, n;
+
+ ARGBEGIN{
+ case 'S':
+ srv = EARGF(usage());
+ break;
+ case 'c':
+ esc = tolower(*(EARGF(usage()))) - 'a' + 1;
+ if(esc == 0 || esc >= ' ')
+ usage();
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'e':
+ if(parseether(ea, EARGF(usage())) == -1)
+ usage();
+ pflag = 1;
+ break;
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 's':
+ shelf = atoi(EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+ if(argc == 0)
+ *argv = "/net/ether0";
+ else if(argc != 1)
+ usage();
+
+ fmtinstall('E', eipfmt);
+ if(srv != nil)
+ dosrv(srv);
+ r = netopen(*argv);
+ if(r == -1){
+ fprint(2, "cec: can't netopen %s\n", *argv);
+ exits0("open");
+ }
+ notify(catch);
+ probe();
+ for(;;){
+ n = 0;
+ if(shelf == -1 && host == 0 && nilea(ea))
+ n = pickone();
+ rawon();
+ conn(n);
+ rawoff();
+ if(pflag == 0){
+ if(shelf != -1)
+ exits0("shelf not found");
+ if(host)
+ exits0("host not found");
+ if(!nilea(ea))
+ exits0("ea not found");
+ } else if(shelf != -1 || host || !nilea(ea))
+ exits0("");
+ }
+}
+
+void
+timewait(int ms)
+{
+ alarm(ms);
+}
+
+int
+didtimeout(void)
+{
+ char buf[ERRMAX];
+
+ rerrstr(buf, sizeof buf);
+ if(strcmp(buf, "interrupted") == 0){
+ werrstr(buf, 0);
+ return 1;
+ }
+ return 0;
+}
+
+ushort
+htons(ushort h)
+{
+ ushort n;
+ uchar *p;
+
+ p = (uchar*)&n;
+ p[0] = h >> 8;
+ p[1] = h;
+ return n;
+}
+
+ushort
+ntohs(int h)
+{
+ ushort n;
+ uchar *p;
+
+ n = h;
+ p = (uchar*)&n;
+ return p[0] << 8 | p[1];
+}
+
+int
+tcmp(void *a, void *b)
+{
+ Shelf *s, *t;
+ int d;
+
+ s = a;
+ t = b;
+ d = s->major - t->major;
+ if(d == 0)
+ d = strcmp(s->name, t->name);
+ if(d == 0)
+ d = memcmp(s->ea, t->ea, Eaddrlen);
+ return d;
+}
+
+void
+probe(void)
+{
+ char *sh, *other;
+ int n;
+ Pkt q;
+ Shelf *p;
+
+ do {
+ ntab = 0;
+ memset(q.dst, 0xff, Eaddrlen);
+ memset(q.src, 0, Eaddrlen);
+ q.etype = htons(Etype);
+ q.type = Tdiscover;
+ q.len = 0;
+ q.conn = 0;
+ q.seq = 0;
+ netsend(&q, 60);
+ timewait(Iowait);
+ while((n = netget(&q, sizeof q)) >= 0){
+ if((n <= 0 && didtimeout()) || ntab == nelem(tab))
+ break;
+ if(n < 60 || q.len == 0 || q.type != Toffer)
+ continue;
+ q.data[q.len] = 0;
+ sh = strtok((char *)q.data, " \t");
+ if(sh == nil)
+ continue;
+ if(!nilea(ea) && memcmp(ea, q.src, Eaddrlen) != 0)
+ continue;
+ if(shelf != -1 && atoi(sh) != shelf)
+ continue;
+ other = strtok(nil, "\x1");
+ if(other == 0)
+ other = "";
+ if(host && strcmp(host, other) != 0)
+ continue;
+ p = tab + ntab++;
+ memcpy(p->ea, q.src, Eaddrlen);
+ p->major = atoi(sh);
+ p->name[0] = 0;
+ if(p->name)
+ snprint(p->name, sizeof p->name, "%s", other);
+ }
+ alarm(0);
+ } while (ntab == 0 && pflag);
+ if(ntab == 0){
+ fprint(2, "none found.\n");
+ exits0("none found");
+ }
+ qsort(tab, ntab, sizeof tab[0], tcmp);
+}
+
+void
+showtable(void)
+{
+ int i;
+
+ for(i = 0; i < ntab; i++)
+ print("%2d %5d %E %s\n", i, tab[i].major, tab[i].ea, tab[i].name);
+}
+
+int
+pickone(void)
+{
+ char buf[80];
+ int n, i;
+
+ for(;;){
+ showtable();
+ print("[#qp]: ");
+ switch(n = read(0, buf, sizeof buf)){
+ case 1:
+ if(buf[0] == '\n')
+ continue;
+ /* fall through */
+ case 2:
+ if(buf[0] == 'p'){
+ probe();
+ break;
+ }
+ if(buf[0] == 'q')
+ /* fall through */
+ case 0:
+ case -1:
+ exits0(0);
+ break;
+ }
+ if(isdigit(buf[0])){
+ buf[n] = 0;
+ i = atoi(buf);
+ if(i >= 0 && i < ntab)
+ break;
+ }
+ }
+ return i;
+}
+
+void
+sethdr(Pkt *pp, int type)
+{
+ memmove(pp->dst, con->ea, Eaddrlen);
+ memset(pp->src, 0, Eaddrlen);
+ pp->etype = htons(Etype);
+ pp->type = type;
+ pp->len = 0;
+ pp->conn = contag;
+}
+
+void
+ethclose(void)
+{
+ static Pkt msg;
+
+ sethdr(&msg, Treset);
+ timewait(Iowait);
+ netsend(&msg, 60);
+ alarm(0);
+ con = 0;
+}
+
+int
+ethopen(void)
+{
+ Pkt tpk, rpk;
+ int i, n;
+
+ contag = (getpid() >> 8) ^ (getpid() & 0xff);
+ sethdr(&tpk, Tinita);
+ sethdr(&rpk, 0);
+ for(i = 0; i < 3 && rpk.type != Tinitb; i++){
+ netsend(&tpk, 60);
+ timewait(Iowait);
+ n = netget(&rpk, 1000);
+ alarm(0);
+ if(n < 0)
+ return -1;
+ }
+ if(rpk.type != Tinitb)
+ return -1;
+ sethdr(&tpk, Tinitc);
+ netsend(&tpk, 60);
+ return 0;
+}
+
+char
+escape(void)
+{
+ char buf[64];
+ int r;
+
+ for(;;){
+ fprint(2, ">>> ");
+ buf[0] = '.';
+ rawoff();
+ r = read(0, buf, sizeof buf - 1);
+ rawon();
+ if(r == -1)
+ exits0("kbd: %r");
+ switch(buf[0]){
+ case 'i':
+ case 'q':
+ case '.':
+ return buf[0];
+ }
+ fprint(2, " (q)uit, (i)nterrupt, (.)continue\n");
+ }
+}
+
+/*
+ * this is a bit too aggressive. it really needs to replace only \n\r with \n.
+ */
+static uchar crbuf[256];
+
+void
+nocrwrite(int fd, uchar *buf, int n)
+{
+ int i, j, c;
+
+ j = 0;
+ for(i = 0; i < n; i++)
+ if((c = buf[i]) != '\r')
+ crbuf[j++] = c;
+ write(fd, crbuf, j);
+}
+
+int
+doloop(void)
+{
+ int unacked, retries, set[2];
+ uchar c, tseq, rseq;
+ uchar ea[Eaddrlen];
+ Pkt tpk, spk;
+ Mux *m;
+
+ memmove(ea, con->ea, Eaddrlen);
+ retries = 0;
+ unacked = 0;
+ tseq = 0;
+ rseq = -1;
+ set[0] = 0;
+ set[1] = fd;
+top:
+ if ((m = mux(set)) == 0)
+ exits0("mux: %r");
+ for (; ; )
+ switch (muxread(m, &spk)) {
+ case -1:
+ if (unacked == 0)
+ break;
+ if (retries-- == 0) {
+ fprint(2, "Connection timed out\n");
+ muxfree(m);
+ return 0;
+ }
+ netsend(&tpk, Hdrsz + unacked);
+ break;
+ case Fkbd:
+ c = spk.data[0];
+ if (c == esc) {
+ muxfree(m);
+ switch (escape()) {
+ case 'q':
+ tpk.len = 0;
+ tpk.type = Treset;
+ netsend(&tpk, 60);
+ return 0;
+ case '.':
+ goto top;
+ case 'i':
+ if ((m = mux(set)) == 0)
+ exits0("mux: %r");
+ break;
+ }
+ }
+ sethdr(&tpk, Tdata);
+ memcpy(tpk.data, spk.data, spk.len);
+ tpk.len = spk.len;
+ tpk.seq = ++tseq;
+ unacked = spk.len;
+ retries = 2;
+ netsend(&tpk, Hdrsz + spk.len);
+ break;
+ case Fcec:
+ if (memcmp(spk.src, ea, Eaddrlen) != 0 ||
+ ntohs(spk.etype) != Etype)
+ continue;
+ if (spk.type == Toffer &&
+ memcmp(spk.dst, bcast, Eaddrlen) != 0) {
+ muxfree(m);
+ return 1;
+ }
+ if (spk.conn != contag)
+ continue;
+ switch (spk.type) {
+ case Tdata:
+ if (spk.seq == rseq)
+ break;
+ nocrwrite(1, spk.data, spk.len);
+ memmove(spk.dst, spk.src, Eaddrlen);
+ memset(spk.src, 0, Eaddrlen);
+ spk.type = Tack;
+ spk.len = 0;
+ rseq = spk.seq;
+ netsend(&spk, 60);
+ break;
+ case Tack:
+ if (spk.seq == tseq)
+ unacked = 0;
+ break;
+ case Treset:
+ muxfree(m);
+ return 1;
+ }
+ break;
+ case Ffatal:
+ muxfree(m);
+ fprint(2, "kbd read error\n");
+ exits0("fatal");
+ }
+}
+
+int
+conn(int n)
+{
+ int r;
+
+ for(;;){
+ if(con)
+ ethclose();
+ con = tab + n;
+ if(ethopen() < 0){
+ fprint(2, "connection failed\n");
+ return 0;
+ }
+ r = doloop();
+ if(r <= 0)
+ return r;
+ }
+}
+
+void
+exits0(char *s)
+{
+ if(con != nil)
+ ethclose();
+ rawoff();
+ if(svc != nil)
+ remove(svc);
+ exits(s);
+}
diff --git a/sys/src/cmd/cec/cec.h b/sys/src/cmd/cec/cec.h
new file mode 100755
index 000000000..c5b388d93
--- /dev/null
+++ b/sys/src/cmd/cec/cec.h
@@ -0,0 +1,38 @@
+typedef struct {
+ uchar dst[6];
+ uchar src[6];
+ ushort etype;
+ uchar type;
+ uchar conn;
+ uchar seq;
+ uchar len;
+ uchar data[1500];
+} Pkt;
+
+enum {
+ Fkbd,
+ Fcec,
+ Ffatal,
+};
+
+typedef struct Mux Mux;
+#pragma incomplete Mux;
+
+enum{
+ Iowait = 2000,
+ Etype = 0xbcbc,
+};
+int debug;
+
+Mux *mux(int fd[2]);
+void muxfree(Mux*);
+int muxread(Mux*, Pkt*);
+
+int netget(void *, int);
+int netopen(char *name);
+int netsend(void *, int);
+
+void dump(uchar*, int);
+void exits0(char*);
+void rawoff(void);
+void rawon(void);
diff --git a/sys/src/cmd/cec/mkfile b/sys/src/cmd/cec/mkfile
new file mode 100755
index 000000000..0181f5f34
--- /dev/null
+++ b/sys/src/cmd/cec/mkfile
@@ -0,0 +1,14 @@
+</$objtype/mkfile
+
+TARG=cec
+
+OFILES=\
+ cec.$O\
+ plan9.$O\
+ mux.$O\
+ utils.$O\
+
+HFILES=cec.h
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
diff --git a/sys/src/cmd/cec/mux.c b/sys/src/cmd/cec/mux.c
new file mode 100755
index 000000000..66f4fa6df
--- /dev/null
+++ b/sys/src/cmd/cec/mux.c
@@ -0,0 +1,110 @@
+#include <u.h>
+#include <libc.h>
+#include "cec.h"
+
+typedef struct {
+ char type;
+ char pad[3];
+ Pkt p;
+} Muxmsg;
+
+typedef struct {
+ int fd;
+ int type;
+ int pid;
+} Muxproc;
+
+struct Mux {
+ Muxmsg m;
+ Muxproc p[2];
+ int pfd[2];
+ int inuse;
+};
+
+static Mux smux = {
+.inuse = -1,
+};
+
+void
+muxcec(int, int cfd)
+{
+ Muxmsg m;
+ int l;
+
+ m.type = Fcec;
+ while((l = netget(&m.p, sizeof m.p)) > 0)
+ if(write(cfd, &m, l+4) != l+4)
+ break;
+ exits("");
+}
+
+void
+muxkbd(int kfd, int cfd)
+{
+ Muxmsg m;
+
+ m.type = Fkbd;
+ while((m.p.len = read(kfd, m.p.data, sizeof m.p.data)) > 0)
+ if(write(cfd, &m, m.p.len+22) != m.p.len+22)
+ break;
+ m.type = Ffatal;
+ write(cfd, &m, 4);
+ exits("");
+}
+
+int
+muxproc(Mux *m, Muxproc *p, int fd, void (*f)(int, int), int type)
+{
+ memset(p, 0, sizeof p);
+ p->type = -1;
+ switch(p->pid = rfork(RFPROC|RFFDG)){
+ case -1:
+ return -1;
+ case 0:
+ close(m->pfd[0]);
+ f(fd, m->pfd[1]);
+ default:
+ p->fd = fd;
+ p->type = type;
+ return p->pid;
+ }
+}
+
+void
+muxfree(Mux *m)
+{
+ close(m->pfd[0]);
+ close(m->pfd[1]);
+ postnote(PNPROC, m->p[0].pid, "this note goes to 11");
+ postnote(PNPROC, m->p[1].pid, "this note goes to 11");
+ waitpid();
+ waitpid();
+ memset(m, 0, sizeof *m);
+ m->inuse = -1;
+}
+
+Mux*
+mux(int fd[2])
+{
+ Mux *m;
+
+ if(smux.inuse != -1)
+ sysfatal("mux in use");
+ m = &smux;
+ m->inuse = 1;
+ if(pipe(m->pfd) == -1)
+ sysfatal("pipe: %r");
+ muxproc(m, m->p+0, fd[0], muxkbd, Fkbd);
+ muxproc(m, m->p+1, fd[1], muxcec, Fcec);
+ close(m->pfd[1]);
+ return m;
+}
+
+int
+muxread(Mux *m, Pkt *p)
+{
+ if(read(m->pfd[0], &m->m, sizeof m->m) == -1)
+ return -1;
+ memcpy(p, &m->m.p, sizeof *p);
+ return m->m.type;
+}
diff --git a/sys/src/cmd/cec/plan9.c b/sys/src/cmd/cec/plan9.c
new file mode 100755
index 000000000..62079e297
--- /dev/null
+++ b/sys/src/cmd/cec/plan9.c
@@ -0,0 +1,89 @@
+/* Copyright © Coraid, Inc. 2006. All rights reserved. */
+#include <u.h>
+#include <libc.h>
+#include "cec.h"
+
+int fd = -1;
+int cfd = -1;
+int efd = -1;
+
+int
+netopen0(char *e)
+{
+ char buf[128], ctl[13];
+ int n;
+
+ snprint(buf, sizeof buf, "%s/clone", e);
+ if((efd = open(buf, ORDWR)) == -1)
+ return -1;
+ memset(ctl, 0, sizeof ctl);
+ if(read(efd, ctl, sizeof ctl) < 0)
+ return -1;
+ n = atoi(ctl);
+ snprint(buf, sizeof buf, "connect %d", Etype);
+ if(write(efd, buf, strlen(buf)) != strlen(buf))
+ return -1;
+ snprint(buf, sizeof buf, "%s/%d/ctl", e, n);
+ if((cfd = open(buf, ORDWR)) < 0)
+ return -1;
+ snprint(buf, sizeof buf, "nonblocking");
+ if(write(cfd, buf, strlen(buf)) != strlen(buf))
+ return -1;
+ snprint(buf, sizeof buf, "%s/%d/data", e, n);
+ fd = open(buf, ORDWR);
+ return fd;
+}
+
+void
+netclose(void)
+{
+ close(efd);
+ close(cfd);
+ close(fd);
+ efd = -1;
+ cfd = -1;
+ fd = -1;
+}
+
+int
+netopen(char *e)
+{
+ int r;
+
+ if((r = netopen0(e)) >= 0)
+ return r;
+ perror("netopen");
+ netclose();
+ return -1;
+}
+
+/* what if len < netlen? */
+int
+netget(void *v, int len)
+{
+ int l;
+
+ l = read(fd, v, len);
+ if(debug && l > 0){
+ fprint(2, "read %d bytes\n", l);
+ dump((uchar*)v, l);
+ }
+ if (l <= 0)
+ return 0;
+ return l;
+}
+
+int
+netsend(void *v, int len)
+{
+ uchar *p;
+
+ p = v;
+ if (debug) {
+ fprint(2, "sending %d bytes\n", len);
+ dump(p, len);
+ }
+ if (len < 60)
+ len = 60; /* mintu */
+ return write(fd, p, len);
+}
diff --git a/sys/src/cmd/cec/utils.c b/sys/src/cmd/cec/utils.c
new file mode 100755
index 000000000..483b41d32
--- /dev/null
+++ b/sys/src/cmd/cec/utils.c
@@ -0,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include "cec.h"
+
+static int fd = -1;
+extern char *svc;
+
+void
+rawon(void)
+{
+ if(svc)
+ return;
+ if((fd = open("/dev/consctl", OWRITE)) == -1 ||
+ write(fd, "rawon", 5) != 5)
+ fprint(2, "Can't make console raw\n");
+}
+
+void
+rawoff(void)
+{
+ if(svc)
+ return;
+ close(fd);
+}
+
+enum {
+ Perline = 16,
+ Perch = 3,
+};
+
+char line[Perch*Perline+1];
+
+static void
+format(uchar *buf, int n, int t)
+{
+ int i, r;
+
+ for(i = 0; i < n; i++){
+ r = (i + t) % Perline;
+ if(r == 0 && i + t > 0)
+ fprint(2, "%s\n", line);
+ sprint(line + r*Perch, "%.2x ", buf[i]);
+ }
+}
+
+void
+dump(uchar *p, int n)
+{
+ format(p, n, 0);
+ if(n % 16 > 0)
+ print("%s\n", line);
+}