One more time sans HTML On Fri, Jan 8, 2016 at 3:13 AM, Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> wrote: > From: Jan Luebbe <jlu@xxxxxxxxxxxxxx> > > This contains the host tool for barebox remote control. It is written in > Phython with its own implementation of the RATP protocol. Currently this > is a very simple tool which needs more work, but the code can also be > used as a library. > > Example output: > > console: '. ' > console: '.. ' > console: 'dev ' > console: 'env ' > console: 'mnt ' > console: '\n' > Result: BBPacketCommandReturn(exit_code=0) I have the following fix-ups for this patch: -----------------------------8<------------------- >From 6c89443301b0064aa424501da242dc7dfd29f3bb Mon Sep 17 00:00:00 2001 From: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> Date: Sun, 17 Jan 2016 15:58:03 -0800 Subject: [PATCH] fixup! host side for barebox remote control remote needs to have __init__.py file in order to be valid python module Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> --- scripts/remote/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scripts/remote/__init__.py diff --git a/scripts/remote/__init__.py b/scripts/remote/__init__.py new file mode 100644 index 0000000..e69de29 -- 2.5.0 ----------------------------->8------------------- -----------------------------8<------------------- >From 61f5d56ea0ec9eb2bc87defb15751c51173ea003 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> Date: Sun, 17 Jan 2016 16:54:41 -0800 Subject: [PATCH] fixup! host side for barebox remote control Add a call to os.path.expanduser so that paths starting with '~' would be handled correctly Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> --- scripts/remote/ratpfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/remote/ratpfs.py b/scripts/remote/ratpfs.py index 0333bf7..91ca044 100644 --- a/scripts/remote/ratpfs.py +++ b/scripts/remote/ratpfs.py @@ -59,7 +59,7 @@ class RatpFSServer(object): def __init__(self, path=None): self.path = path if path: - self.path = os.path.abspath(path) + self.path = os.path.abspath(os.path.expanduser(path)) self.next_handle = 1 # 0 is invalid self.files = {} self.mounted = False -- 2.5.0 ----------------------------->8------------------- -----------------------------8<------------------- >From b7a202a5cb53bdc52eb54fcdf808919519467001 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> Date: Sun, 17 Jan 2016 16:03:28 -0800 Subject: [PATCH] fixup! host side for barebox remote control 'event' variable is not really used anywhere Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> --- scripts/remote/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/remote/main.py b/scripts/remote/main.py index 9350151..bd30472 100644 --- a/scripts/remote/main.py +++ b/scripts/remote/main.py @@ -95,8 +95,7 @@ def handle_console(args): cons.start() try: while True: - event = queue.get(block=True) - src, data = event + src, data = queue.get(block=True) if src == cons: if data is None: # shutdown cons.join() -- 2.5.0 ----------------------------->8------------------- > > Signed-off-by: Jan Lübbe <j.luebbe@xxxxxxxxxxxxxx> > Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> With fixes above, Tested-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> > --- > scripts/bbremote | 3 + > scripts/remote/controller.py | 173 ++++++++++ > scripts/remote/main.py | 169 +++++++++ > scripts/remote/messages.py | 154 +++++++++ > scripts/remote/missing.py | 28 ++ > scripts/remote/ratp.py | 773 ++++++++++++++++++++++++++++++++++++++++++ > scripts/remote/ratpfs.py | 189 +++++++++++ > scripts/remote/threadstdio.py | 47 +++ > 8 files changed, 1536 insertions(+) > create mode 100755 scripts/bbremote > create mode 100644 scripts/remote/controller.py > create mode 100644 scripts/remote/main.py > create mode 100644 scripts/remote/messages.py > create mode 100644 scripts/remote/missing.py > create mode 100644 scripts/remote/ratp.py > create mode 100644 scripts/remote/ratpfs.py > create mode 100644 scripts/remote/threadstdio.py > > diff --git a/scripts/bbremote b/scripts/bbremote > new file mode 100755 > index 0000000..bc5351d > --- /dev/null > +++ b/scripts/bbremote > @@ -0,0 +1,3 @@ > +#!/usr/bin/env python2 > + > +import remote.main > diff --git a/scripts/remote/controller.py b/scripts/remote/controller.py > new file mode 100644 > index 0000000..a7257ec > --- /dev/null > +++ b/scripts/remote/controller.py > @@ -0,0 +1,173 @@ > +#!/usr/bin/env python2 > +# -*- coding: utf-8 -*- > + > +from __future__ import absolute_import, division, print_function > + > +import struct > +import logging > +import sys > +import os > +from threading import Thread > +from Queue import Queue, Empty > +from .ratpfs import RatpFSServer > +from .messages import * > +from .ratp import RatpError > + > +try: > + from time import monotonic > +except: > + from .missing import monotonic > + > + > +def unpack(data): > + p_type, = struct.unpack("!H", data[:2]) > + logging.debug("unpack: %r data=%r", p_type, repr(data)) > + if p_type == BBType.command: > + logging.debug("received: command") > + return BBPacketCommand(raw=data) > + elif p_type == BBType.command_return: > + logging.debug("received: command_return") > + return BBPacketCommandReturn(raw=data) > + elif p_type == BBType.consolemsg: > + logging.debug("received: consolemsg") > + return BBPacketConsoleMsg(raw=data) > + elif p_type == BBType.ping: > + logging.debug("received: ping") > + return BBPacketPing(raw=data) > + elif p_type == BBType.pong: > + logging.debug("received: pong") > + return BBPacketPong(raw=data) > + elif p_type == BBType.getenv_return: > + logging.debug("received: getenv_return") > + return BBPacketGetenvReturn(raw=data) > + elif p_type == BBType.fs: > + logging.debug("received: fs") > + return BBPacketFS(raw=data) > + elif p_type == BBType.fs_return: > + logging.debug("received: fs_return") > + return BBPacketFSReturn(raw=data) > + else: > + logging.debug("received: UNKNOWN") > + return BBPacket(raw=data) > + > + > +class Controller(Thread): > + def __init__(self, conn): > + Thread.__init__(self) > + self.daemon = True > + self.conn = conn > + self.fsserver = None > + self.rxq = None > + self.conn.connect(timeout=5.0) > + self._txq = Queue() > + self._stop = False > + self.fsserver = RatpFSServer() > + > + def _send(self, bbpkt): > + self.conn.send(bbpkt.pack()) > + > + def _handle(self, bbpkt): > + if isinstance(bbpkt, BBPacketConsoleMsg): > + os.write(sys.stdout.fileno(), bbpkt.text) > + elif isinstance(bbpkt, BBPacketPong): > + print("pong",) > + elif isinstance(bbpkt, BBPacketFS): > + if self.fsserver != None: > + self._send(self.fsserver.handle(bbpkt)) > + > + def _expect(self, bbtype, timeout=1.0): > + if timeout is not None: > + limit = monotonic()+timeout > + while timeout is None or limit > monotonic(): > + pkt = self.conn.recv(0.1) > + if not pkt: > + continue > + bbpkt = unpack(pkt) > + if isinstance(bbpkt, bbtype): > + return bbpkt > + else: > + self._handle(bbpkt) > + > + def export(self, path): > + self.fsserver = RatpFSServer(path) > + > + def ping(self): > + self._send(BBPacketPing()) > + r = self._expect(BBPacketPong) > + logging.info("Ping: %r", r) > + if not r: > + return 1 > + else: > + print("pong") > + return 0 > + > + def command(self, cmd): > + self._send(BBPacketCommand(cmd=cmd)) > + r = self._expect(BBPacketCommandReturn, timeout=None) > + logging.info("Command: %r", r) > + return r.exit_code > + > + def getenv(self, varname): > + self._send(BBPacketGetenv(varname=varname)) > + r = self._expect(BBPacketGetenvReturn) > + return r.text > + > + def close(self): > + self.conn.close() > + > + def run(self): > + assert self.rxq is not None > + try: > + while not self._stop: > + # receive > + pkt = self.conn.recv() > + if pkt: > + bbpkt = unpack(pkt) > + if isinstance(bbpkt, BBPacketConsoleMsg): > + self.rxq.put((self, bbpkt.text)) > + else: > + self._handle(bbpkt) > + # send > + try: > + pkt = self._txq.get(block=False) > + except Empty: > + pkt = None > + if pkt: > + self._send(pkt) > + except RatpError as detail: > + print("Ratp error:", detail, file=sys.stderr); > + self.rxq.put((self, None)) > + return > + > + def start(self, queue): > + assert self.rxq is None > + self.rxq = queue > + Thread.start(self) > + > + def stop(self): > + self._stop = True > + self.join() > + self._stop = False > + self.rxq = None > + > + def send_async(self, pkt): > + self._txq.put(pkt) > + > + def send_async_console(self, text): > + self._txq.put(BBPacketConsoleMsg(text=text)) > + > + def send_async_ping(self): > + self._txq.put(BBPacketPing()) > + > + > +def main(): > + import serial > + from .ratp import SerialRatpConnection > + url = "rfc2217://192.168.23.176:3002" > + port = serial.serial_for_url(url, 115200) > + conn = SerialRatpConnection(port) > + ctrl = Controller(conn) > + return ctrl > + > +if __name__ == "__main__": > + C = main() > diff --git a/scripts/remote/main.py b/scripts/remote/main.py > new file mode 100644 > index 0000000..9350151 > --- /dev/null > +++ b/scripts/remote/main.py > @@ -0,0 +1,169 @@ > +#!/usr/bin/env python2 > + > +from __future__ import absolute_import, division, print_function > + > +import sys > +import os > +import argparse > +import logging > +from Queue import Queue > +from .ratp import RatpError > + > +try: > + import serial > +except: > + print("error: No python-serial package found", file=sys.stderr) > + exit(2) > + > + > +def versiontuple(v): > + return tuple(map(int, (v.split(".")))) > + > +if versiontuple(serial.VERSION) < (2, 7): > + print("warning: python-serial package is buggy in RFC2217 mode,", > + "consider updating to at least 2.7", file=sys.stderr) > + > +from .ratp import SerialRatpConnection > +from .controller import Controller > +from .threadstdio import ConsoleInput > + > + > +def get_controller(args): > + port = serial.serial_for_url(args.port, args.baudrate) > + conn = SerialRatpConnection(port) > + > + while True: > + try: > + ctrl = Controller(conn) > + break > + except (RatpError): > + if args.wait == True: > + pass > + else: > + raise > + > + return ctrl > + > + > +def handle_run(args): > + ctrl = get_controller(args) > + ctrl.export(args.export) > + res = ctrl.command(' '.join(args.arg)) > + if res: > + res = 1 > + ctrl.close() > + return res > + > + > +def handle_ping(args): > + ctrl = get_controller(args) > + res = ctrl.ping() > + if res: > + res = 1 > + ctrl.close() > + return res > + > + > +def handle_getenv(args): > + ctrl = get_controller(args) > + value = ctrl.getenv(' '.join(args.arg)) > + if not value: > + res = 1 > + else: > + print(value) > + res = 0 > + ctrl.close() > + return res > + > + > +def handle_listen(args): > + port = serial.serial_for_url(args.port, args.baudrate) > + conn = SerialRatpConnection(port) > + conn.listen() > + while True: > + conn.wait(None) > + conn.close() > + > + > +def handle_console(args): > + queue = Queue() > + ctrl = get_controller(args) > + ctrl.export(args.export) > + ctrl.start(queue) > + ctrl.send_async_console('\r') > + cons = ConsoleInput(queue, exit='\x14') # CTRL-T > + cons.start() > + try: > + while True: > + event = queue.get(block=True) > + src, data = event > + if src == cons: > + if data is None: # shutdown > + cons.join() > + break > + elif data == '\x10': # CTRL-P > + ctrl.send_async_ping() > + else: > + ctrl.send_async_console(data) > + elif src == ctrl: > + if data is None: # shutdown > + sys.exit(1) > + break > + else: > + os.write(sys.stdout.fileno(), data) > + ctrl.stop() > + ctrl.close() > + finally: > + print() > + print("total retransmits=%i crc-errors=%i" % ( > + ctrl.conn.total_retransmits, > + ctrl.conn.total_crc_errors)) > + > +VERBOSITY = { > + 0: logging.WARN, > + 1: logging.INFO, > + 2: logging.DEBUG, > + } > + > +parser = argparse.ArgumentParser(prog='bbremote') > +parser.add_argument('-v', '--verbose', action='count', default=0) > +parser.add_argument('--port', type=str, default=os.environ.get('BBREMOTE_PORT', None)) > +parser.add_argument('--baudrate', type=int, default=os.environ.get('BBREMOTE_BAUDRATE', 115200)) > +parser.add_argument('--export', type=str, default=os.environ.get('BBREMOTE_EXPORT', None)) > +parser.add_argument('-w', '--wait', action='count', default=0) > +subparsers = parser.add_subparsers(help='sub-command help') > + > +parser_run = subparsers.add_parser('run', help="run a barebox command") > +parser_run.add_argument('arg', nargs='+', help="barebox command to run") > +parser_run.set_defaults(func=handle_run) > + > +parser_ping = subparsers.add_parser('ping', help="test connection") > +parser_ping.set_defaults(func=handle_ping) > + > +parser_ping = subparsers.add_parser('getenv', help="get a barebox environment variable") > +parser_ping.add_argument('arg', nargs='+', help="variable name") > +parser_ping.set_defaults(func=handle_getenv) > + > +parser_run = subparsers.add_parser('listen', help="listen for an incoming connection") > +parser_run.set_defaults(func=handle_listen) > + > +parser_run = subparsers.add_parser('console', help="connect to the console") > +parser_run.set_defaults(func=handle_console) > + > +args = parser.parse_args() > +logging.basicConfig(level=VERBOSITY[args.verbose], > + format='%(levelname)-8s %(module)-8s %(funcName)-16s %(message)s') > +try: > + res = args.func(args) > + exit(res) > +except RatpError as detail: > + print("Ratp error:", detail, file=sys.stderr); > + exit(127) > +except KeyboardInterrupt: > + print("\nInterrupted", file=sys.stderr); > + exit(1) > +#try: > +# res = args.func(args) > +#except Exception as e: > +# print("error: failed to establish connection: %s" % e, file=sys.stderr) > +# exit(2) > diff --git a/scripts/remote/messages.py b/scripts/remote/messages.py > new file mode 100644 > index 0000000..8e8495b > --- /dev/null > +++ b/scripts/remote/messages.py > @@ -0,0 +1,154 @@ > +#!/usr/bin/env python2 > +# -*- coding: utf-8 -*- > + > +from __future__ import absolute_import, division, print_function > + > +import struct > + > + > +class BBType(object): > + command = 1 > + command_return = 2 > + consolemsg = 3 > + ping = 4 > + pong = 5 > + getenv = 6 > + getenv_return = 7 > + fs = 8 > + fs_return = 9 > + > + > +class BBPacket(object): > + def __init__(self, p_type=0, p_flags=0, payload="", raw=None): > + self.p_type = p_type > + self.p_flags = p_flags > + if raw is not None: > + self.unpack(raw) > + else: > + self.payload = payload > + > + def __repr__(self): > + return "BBPacket(%i, %i)" % (self.p_type, self.p_flags) > + > + def _unpack_payload(self, data): > + self.payload = data > + > + def _pack_payload(self): > + return self.payload > + > + def unpack(self, data): > + self.p_type, self.p_flags = struct.unpack("!HH", data[:4]) > + self._unpack_payload(data[4:]) > + > + def pack(self): > + return struct.pack("!HH", self.p_type, self.p_flags) + \ > + self._pack_payload() > + > + > +class BBPacketCommand(BBPacket): > + def __init__(self, raw=None, cmd=None): > + self.cmd = cmd > + super(BBPacketCommand, self).__init__(BBType.command, raw=raw) > + > + def __repr__(self): > + return "BBPacketCommand(cmd=%r)" % self.cmd > + > + def _unpack_payload(self, payload): > + self.cmd = payload > + > + def _pack_payload(self): > + return self.cmd > + > + > +class BBPacketCommandReturn(BBPacket): > + def __init__(self, raw=None, exit_code=None): > + self.exit_code = exit_code > + super(BBPacketCommandReturn, self).__init__(BBType.command_return, > + raw=raw) > + > + def __repr__(self): > + return "BBPacketCommandReturn(exit_code=%i)" % self.exit_code > + > + def _unpack_payload(self, data): > + self.exit_code, = struct.unpack("!L", data[:4]) > + > + def _pack_payload(self): > + return struct.pack("!L", self.exit_code) > + > + > +class BBPacketConsoleMsg(BBPacket): > + def __init__(self, raw=None, text=None): > + self.text = text > + super(BBPacketConsoleMsg, self).__init__(BBType.consolemsg, raw=raw) > + > + def __repr__(self): > + return "BBPacketConsoleMsg(text=%r)" % self.text > + > + def _unpack_payload(self, payload): > + self.text = payload > + > + def _pack_payload(self): > + return self.text > + > + > +class BBPacketPing(BBPacket): > + def __init__(self, raw=None): > + super(BBPacketPing, self).__init__(BBType.ping, raw=raw) > + > + def __repr__(self): > + return "BBPacketPing()" > + > + > +class BBPacketPong(BBPacket): > + def __init__(self, raw=None): > + super(BBPacketPong, self).__init__(BBType.pong, raw=raw) > + > + def __repr__(self): > + return "BBPacketPong()" > + > + > +class BBPacketGetenv(BBPacket): > + def __init__(self, raw=None, varname=None): > + self.varname = varname > + super(BBPacketGetenv, self).__init__(BBType.getenv, raw=raw) > + > + def __repr__(self): > + return "BBPacketGetenv(varname=%r)" % self.varname > + > + def _unpack_payload(self, payload): > + self.varname = payload > + > + def _pack_payload(self): > + return self.varname > + > + > +class BBPacketGetenvReturn(BBPacket): > + def __init__(self, raw=None, text=None): > + self.text = text > + super(BBPacketGetenvReturn, self).__init__(BBType.getenv_return, > + raw=raw) > + > + def __repr__(self): > + return "BBPacketGetenvReturn(varvalue=%s)" % self.text > + > + def _unpack_payload(self, payload): > + self.text = payload > + > + def _pack_payload(self): > + return self.text > + > + > +class BBPacketFS(BBPacket): > + def __init__(self, raw=None, payload=None): > + super(BBPacketFS, self).__init__(BBType.fs, payload=payload, raw=raw) > + > + def __repr__(self): > + return "BBPacketFS(payload=%r)" % self.payload > + > + > +class BBPacketFSReturn(BBPacket): > + def __init__(self, raw=None, payload=None): > + super(BBPacketFSReturn, self).__init__(BBType.fs_return, payload=payload, raw=raw) > + > + def __repr__(self): > + return "BBPacketFSReturn(payload=%r)" % self.payload > diff --git a/scripts/remote/missing.py b/scripts/remote/missing.py > new file mode 100644 > index 0000000..67c2dfa > --- /dev/null > +++ b/scripts/remote/missing.py > @@ -0,0 +1,28 @@ > +#!/usr/bin/env python > + > +import ctypes > +import os > + > +CLOCK_MONOTONIC_RAW = 4 # from <linux/time.h> > + > + > +class timespec(ctypes.Structure): > + _fields_ = [ > + ('tv_sec', ctypes.c_long), > + ('tv_nsec', ctypes.c_long) > + ] > + > +librt = ctypes.CDLL('librt.so.1', use_errno=True) > +clock_gettime = librt.clock_gettime > +clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)] > + > + > +def monotonic(): > + t = timespec() > + if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(t)) != 0: > + errno_ = ctypes.get_errno() > + raise OSError(errno_, os.strerror(errno_)) > + return t.tv_sec + t.tv_nsec * 1e-9 > + > +if __name__ == "__main__": > + print monotonic() > diff --git a/scripts/remote/ratp.py b/scripts/remote/ratp.py > new file mode 100644 > index 0000000..079fb87 > --- /dev/null > +++ b/scripts/remote/ratp.py > @@ -0,0 +1,773 @@ > +#!/usr/bin/env python2 > +# -*- coding: utf-8 -*- > + > +from __future__ import absolute_import, division, print_function > + > +import crcmod > +import logging > +import struct > +from enum import Enum > +from time import sleep > + > +try: > + from time import monotonic > +except: > + from .missing import monotonic > + > +csum_func = crcmod.predefined.mkCrcFun('xmodem') > + > + > +class RatpState(Enum): > + listen = "listen" # 1 > + syn_sent = "syn-sent" # 2 > + syn_received = "syn-received" # 3 > + established = "established" # 4 > + fin_wait = "fin-wait" # 5 > + last_ack = "last-ack" # 6 > + closing = "closing" # 7 > + time_wait = "time-wait" # 8 > + closed = "closed" # 9 > + > + > +class RatpInvalidHeader(ValueError): > + pass > + > + > +class RatpInvalidPayload(ValueError): > + pass > + > + > +class RatpError(ValueError): > + pass > + > + > +class RatpPacket(object): > + > + def __init__(self, data=None, flags=''): > + self.payload = None > + self.synch = 0x01 > + self._control = 0 > + self.length = 0 > + self.csum = 0 > + self.c_syn = False > + self.c_ack = False > + self.c_fin = False > + self.c_rst = False > + self.c_sn = 0 > + self.c_an = 0 > + self.c_eor = False > + self.c_so = False > + if data: > + (self.synch, self._control, self.length, self.csum) = \ > + struct.unpack('!BBBB', data) > + if self.synch != 0x01: > + raise RatpInvalidHeader("invalid synch octet (%x != %x)" % > + (self.synch, 0x01)) > + csum = (self._control + self.length + self.csum) & 0xff > + if csum != 0xff: > + raise RatpInvalidHeader("invalid csum octet (%x != %x)" % > + (csum, 0xff)) > + self._unpack_control() > + elif flags: > + if 'S' in flags: > + self.c_syn = True > + if 'A' in flags: > + self.c_ack = True > + if 'F' in flags: > + self.c_fin = True > + if 'R' in flags: > + self.c_rst = True > + if 'E' in flags: > + self.c_eor = True > + > + def __repr__(self): > + s = "RatpPacket(" > + if self.c_syn: > + s += "SYN," > + if self.c_ack: > + s += "ACK," > + if self.c_fin: > + s += "FIN," > + if self.c_rst: > + s += "RST," > + s += "SN=%i,AN=%i," % (self.c_sn, self.c_an) > + if self.c_eor: > + s += "EOR," > + if self.c_so: > + s += "SO,DATA=%i)" % self.length > + else: > + s += "DATA=%i)" % self.length > + return s > + > + def _pack_control(self): > + self._control = 0 | \ > + self.c_syn << 7 | \ > + self.c_ack << 6 | \ > + self.c_fin << 5 | \ > + self.c_rst << 4 | \ > + self.c_sn << 3 | \ > + self.c_an << 2 | \ > + self.c_eor << 1 | \ > + self.c_so << 0 > + > + def _unpack_control(self): > + self.c_syn = bool(self._control & 1 << 7) > + self.c_ack = bool(self._control & 1 << 6) > + self.c_fin = bool(self._control & 1 << 5) > + self.c_rst = bool(self._control & 1 << 4) > + self.c_sn = bool(self._control & 1 << 3) > + self.c_an = bool(self._control & 1 << 2) > + self.c_eor = bool(self._control & 1 << 1) > + self.c_so = bool(self._control & 1 << 0) > + > + def pack(self): > + self._pack_control() > + self.csum = 0 > + self.csum = (self._control + self.length + self.csum) > + self.csum = (self.csum & 0xff) ^ 0xff > + return struct.pack('!BBBB', self.synch, self._control, self.length, > + self.csum) > + > + def unpack_payload(self, payload): > + (c_recv,) = struct.unpack('!H', payload[-2:]) > + c_calc = csum_func(payload[:-2]) > + if c_recv != c_calc: > + raise RatpInvalidPayload("bad checksum (%04x != %04x)" % > + (c_recv, c_calc)) > + self.payload = payload[:-2] > + > + def pack_payload(self): > + c_calc = csum_func(self.payload) > + return self.payload+struct.pack('!H', c_calc) > + > + > +class RatpConnection(object): > + def __init__(self): > + self._state = RatpState.closed > + self._passive = True > + self._input = b'' > + self._s_sn = 0 > + self._r_sn = 0 > + self._retrans = None > + self._retrans_counter = None > + self._retrans_deadline = None > + self._r_mdl = None > + self._s_mdl = 0xff > + self._rx_buf = [] # reassembly buffer > + self._rx_queue = [] > + self._tx_queue = [] > + self._rtt_alpha = 0.8 > + self._rtt_beta = 2.0 > + self._srtt = 0.2 > + self._rto_min, self._rto_max = 0.2, 1 > + self._tx_timestamp = None > + self.total_retransmits = 0 > + self.total_crc_errors = 0 > + > + def _update_srtt(self, rtt): > + self._srtt = (self._rtt_alpha * self._srtt) + \ > + ((1.0 - self._rtt_alpha) * rtt) > + logging.info("SRTT: %r", self._srtt) > + > + def _get_rto(self): > + return min(self._rto_max, > + max(self._rto_min, self._rtt_beta * self._srtt)) > + > + def _write(self, pkt): > + > + if pkt.payload or pkt.c_so or pkt.c_syn or pkt.c_rst or pkt.c_fin: > + self._s_sn = pkt.c_sn > + if not self._retrans: > + self._retrans = pkt > + self._retrans_counter = 0 > + else: > + self.total_retransmits += 1 > + self._retrans_counter += 1 > + if self._retrans_counter > 10: > + raise RatpError("Maximum retransmit count exceeded") > + self._retrans_deadline = monotonic()+self._get_rto() > + > + logging.info("Write: %r", pkt) > + > + self._write_raw(pkt.pack()) > + if pkt.payload: > + self._write_raw(pkt.pack_payload()) > + self._tx_timestamp = monotonic() > + > + def _check_rto(self): > + if self._retrans is None: > + return > + > + if self._retrans_deadline < monotonic(): > + logging.debug("Retransmit...") > + self._write(self._retrans) > + > + def _check_time_wait(self): > + if not self._state == RatpState.time_wait: > + return > + > + remaining = self._time_wait_deadline - monotonic() > + if remaining < 0: > + self._state = RatpState.closed > + else: > + logging.debug("Time-Wait: %.2f remaining" % remaining) > + sleep(min(remaining, 0.1)) > + > + def _read(self): > + if len(self._input) < 4: > + self._input += self._read_raw(4-len(self._input)) > + if len(self._input) < 4: > + return > + > + try: > + pkt = RatpPacket(data=self._input[:4]) > + except RatpInvalidHeader as e: > + logging.info("%r", e) > + self._input = self._input[1:] > + return > + > + self._input = self._input[4:] > + > + logging.info("Read: %r", pkt) > + > + if pkt.c_syn or pkt.c_rst or pkt.c_so or pkt.c_fin: > + return pkt > + > + if pkt.length == 0: > + return pkt > + > + while len(self._input) < pkt.length+2: > + self._input += self._read_raw() > + > + try: > + pkt.unpack_payload(self._input[:pkt.length+2]) > + except RatpInvalidPayload as e: > + self.total_crc_errors += 1 > + return > + finally: > + self._input = self._input[pkt.length+2:] > + > + return pkt > + > + def _close(self): > + pass > + > + def _a(self, r): > + logging.info("A") > + > + if r.c_rst: > + return True > + > + if r.c_ack: > + s = RatpPacket(flags='R') > + s.c_sn = r.c_an > + self._write(s) > + return False > + > + if r.c_syn: > + self._r_mdl = r.length > + > + s = RatpPacket(flags='SA') > + s.c_sn = 0 > + s.c_an = (r.c_sn + 1) % 2 > + s.length = self._s_mdl > + self._write(s) > + self._state = RatpState.syn_received > + return False > + > + return False > + > + def _b(self, r): > + logging.info("B") > + > + if r.c_ack and r.c_an != (self._s_sn + 1) % 2: > + if r.c_rst: > + return False > + else: > + s = RatpPacket(flags='R') > + s.c_sn = r.c_an > + self._write(s) > + return False > + > + if r.c_rst: > + if r.c_ack: > + self._retrans = None > + # FIXME: delete the TCB > + self._state = RatpState.closed > + return False > + else: > + return False > + > + if r.c_syn: > + if r.c_ack: > + self._r_mdl = r.length > + self._retrans = None > + self._r_sn = r.c_sn > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._state = RatpState.established > + return False > + else: > + self._retrans = None > + s = RatpPacket(flags='SA') > + s.c_sn = 0 > + s.c_an = (r.c_sn + 1) % 2 > + s.length = self._s_mdl > + self._write(s) > + self._state = RatpState.syn_received > + return False > + > + return False > + > + def _c1(self, r): > + logging.info("C1") > + > + if r.c_sn != self._r_sn: > + return True > + > + if r.c_rst or r.c_fin: > + return False > + > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + return False > + > + def _c2(self, r): > + logging.info("C2") > + > + if r.length == 0 and r.c_so == 0: > + return True > + > + if r.c_sn != self._r_sn: > + return True > + > + if r.c_rst or r.c_fin: > + return False > + > + if r.c_syn: > + s = RatpPacket(flags='RA') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._retrans = None > + # FIXME: inform the user "Error: Connection reset" > + self._state = RatpState.closed > + return False > + > + # FIXME: only ack duplicate data packages? > + # This is not documented in RFC 916 > + if r.length or r.c_so: > + logging.info("C2: duplicate data packet, dropping") > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + > + return False > + > + def _d1(self, r): > + logging.info("D1") > + > + if not r.c_rst: > + return True > + > + if self._passive: > + self._retrans = None > + self._state = RatpState.listen > + return False > + else: > + self._retrans = None > + > + self._state = RatpState.closed > + raise RatpError("Connection refused") > + > + def _d2(self, r): > + logging.info("D2") > + > + if not r.c_rst: > + return True > + > + self._retrans = None > + > + self._state = RatpState.closed > + > + raise RatpError("Connection reset") > + > + def _d3(self, r): > + logging.info("C3") > + > + if not r.c_rst: > + return True > + > + self._state = RatpState.closed > + return False > + > + def _e(self, r): > + logging.info("E") > + > + if not r.c_syn: > + return True > + > + self._retrans = None > + s = RatpPacket(flags='R') > + if r.c_ack: > + s.c_sn = r.c_an > + else: > + s.c_sn = 0 > + self._write(s) > + self._state = RatpState.closed > + raise RatpError("Connection reset") > + > + def _f1(self, r): > + logging.info("F1") > + > + if not r.c_ack: > + return False > + > + if r.c_an == (self._s_sn + 1) % 2: > + return True > + > + if self._passive: > + self._retrans = None > + s = RatpPacket(flags='R') > + s.c_sn = r.c_an > + self._write(s) > + self._state = RatpState.listen > + return False > + else: > + self._retrans = None > + s = RatpPacket(flags='R') > + s.c_sn = r.c_an > + self._write(s) > + self._state = RatpState.closed > + raise RatpError("Connection refused") > + > + def _f2(self, r): > + logging.info("F2") > + > + if not r.c_ack: > + return False > + > + if r.c_an == (self._s_sn + 1) % 2: > + if self._retrans: > + self._retrans = None > + self._update_srtt(monotonic()-self._tx_timestamp) > + # FIXME: inform the user with an "Ok" if a buffer has been > + # entirely acknowledged. Another packet containing data may > + # now be sent. > + return True > + > + return True > + > + def _f3(self, r): > + logging.info("F3") > + > + if not r.c_ack: > + return False > + > + if r.c_an == (self._s_sn + 1) % 2: > + return True > + > + return True > + > + def _g(self, r): > + logging.info("G") > + > + if not r.c_rst: > + return False > + > + self._retrans = None > + if r.c_ack: > + s = RatpPacket(flags='R') > + s.c_sn = r.c_an > + self._write(s) > + else: > + s = RatpPacket(flags='RA') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + > + return False > + > + def _h1(self, r): > + logging.info("H1") > + > + # FIXME: initial data? > + self._state = RatpState.established > + self._r_sn = r.c_sn > + > + return False > + > + def _h2(self, r): > + logging.info("H2") > + > + if not r.c_fin: > + return True > + > + if self._retrans is not None: > + # FIXME: inform the user "Warning: Data left unsent.", "Connection closing." > + self._retrans = None > + s = RatpPacket(flags='FA') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._state = RatpState.last_ack > + raise RatpError("Connection closed by remote") > + > + def _h3(self, r): > + logging.info("H3") > + > + if not r.c_fin: > + # Our fin was lost, rely on retransmission > + return False > + > + if r.length or r.c_so: > + self._retrans = None > + s = RatpPacket(flags='RA') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._state = RatpState.closed > + raise RatpError("Connection reset") > + > + if r.c_an == (self._s_sn + 1) % 2: > + self._retrans = None > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._time_wait_deadline = monotonic() + self._get_rto() > + self._state = RatpState.time_wait > + return False > + else: > + self._retrans = None > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._state = RatpState.closing > + return False > + > + def _h4(self, r): > + logging.info("H4") > + > + if r.c_an == (self._s_sn + 1) % 2: > + self._retrans = None > + self._time_wait_deadline = monotonic() + self._get_rto() > + self._state = RatpState.time_wait > + return False > + > + return False > + > + def _h5(self, r): > + logging.info("H5") > + > + if r.c_an == (self._s_sn + 1) % 2: > + self._time_wait_deadline = monotonic() + self._get_rto() > + self._state = RatpState.time_wait > + return False > + > + return False > + > + def _h6(self, r): > + logging.info("H6") > + > + if not r.c_ack: > + return False > + > + if not r.c_fin: > + return False > + > + self._retrans = None > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + self._time_wait_deadline = monotonic() + self._get_rto() > + return False > + > + def _i1(self, r): > + logging.info("I1") > + > + if r.c_so: > + self._r_sn = r.c_sn > + self._rx_buf.append(chr(r.length)) > + elif r.length: > + self._r_sn = r.c_sn > + self._rx_buf.append(r.payload) > + else: > + return False > + > + # reassemble > + if r.c_eor: > + logging.info("Reassembling %i frames", len(self._rx_buf)) > + self._rx_queue.append(''.join(self._rx_buf)) > + self._rx_buf = [] > + > + s = RatpPacket(flags='A') > + s.c_sn = r.c_an > + s.c_an = (r.c_sn + 1) % 2 > + self._write(s) > + return False > + > + def _machine(self, pkt): > + logging.info("State: %r", self._state) > + if self._state == RatpState.listen: > + self._a(pkt) > + elif self._state == RatpState.syn_sent: > + self._b(pkt) > + elif self._state == RatpState.syn_received: > + self._c1(pkt) and \ > + self._d1(pkt) and \ > + self._e(pkt) and \ > + self._f1(pkt) and \ > + self._h1(pkt) > + elif self._state == RatpState.established: > + self._c2(pkt) and \ > + self._d2(pkt) and \ > + self._e(pkt) and \ > + self._f2(pkt) and \ > + self._h2(pkt) and \ > + self._i1(pkt) > + elif self._state == RatpState.fin_wait: > + self._c2(pkt) and \ > + self._d2(pkt) and \ > + self._e(pkt) and \ > + self._f3(pkt) and \ > + self._h3(pkt) > + elif self._state == RatpState.last_ack: > + self._c2(pkt) and \ > + self._d3(pkt) and \ > + self._e(pkt) and \ > + self._f3(pkt) and \ > + self._h4(pkt) > + elif self._state == RatpState.closing: > + self._c2(pkt) and \ > + self._d3(pkt) and \ > + self._e(pkt) and \ > + self._f3(pkt) and \ > + self._h5(pkt) > + elif self._state == RatpState.time_wait: > + self._d3(pkt) and \ > + self._e(pkt) and \ > + self._f3(pkt) and \ > + self._h6(pkt) > + elif self._state == RatpState.closed: > + self._g(pkt) > + > + def wait(self, deadline): > + while deadline is None or deadline > monotonic(): > + pkt = self._read() > + if pkt: > + self._machine(pkt) > + else: > + self._check_rto() > + self._check_time_wait() > + if not self._retrans or self._rx_queue: > + return > + > + def wait1(self, deadline): > + while deadline is None or deadline > monotonic(): > + pkt = self._read() > + if pkt: > + self._machine(pkt) > + else: > + self._check_rto() > + self._check_time_wait() > + if not self._retrans: > + return > + > + def listen(self): > + logging.info("LISTEN") > + self._state = RatpState.listen > + > + def connect(self, timeout=5.0): > + deadline = monotonic() + timeout > + logging.info("CONNECT") > + self._retrans = None > + syn = RatpPacket(flags='S') > + syn.length = self._s_mdl > + self._write(syn) > + self._state = RatpState.syn_sent > + self.wait(deadline) > + > + def send_one(self, data, eor=True, timeout=1.0): > + deadline = monotonic() + timeout > + logging.info("SEND_ONE (len=%i, eor=%r)", len(data), eor) > + assert self._state == RatpState.established > + assert self._retrans is None > + snd = RatpPacket(flags='A') > + snd.c_eor = eor > + snd.c_sn = (self._s_sn + 1) % 2 > + snd.c_an = (self._r_sn + 1) % 2 > + snd.length = len(data) > + snd.payload = data > + self._write(snd) > + self.wait1(deadline=None) > + > + def send(self, data, timeout=1.0): > + logging.info("SEND (len=%i)", len(data)) > + while len(data) > 255: > + self.send_one(data[:255], eor=False, timeout=timeout) > + data = data[255:] > + self.send_one(data, eor=True, timeout=timeout) > + > + def recv(self, timeout=1.0): > + deadline = monotonic() + timeout > + > + assert self._state == RatpState.established > + if self._rx_queue: > + return self._rx_queue.pop(0) > + self.wait(deadline) > + if self._rx_queue: > + return self._rx_queue.pop(0) > + > + def close(self, timeout=1.0): > + deadline = monotonic() + timeout > + logging.info("CLOSE") > + if self._state == RatpState.established: > + fin = RatpPacket(flags='FA') # FIXME: only F? > + fin.c_sn = (self._s_sn + 1) % 2 > + fin.c_an = (self._r_sn + 1) % 2 > + self._write(fin) > + self._state = RatpState.fin_wait > + while deadline > monotonic() and not self._state == RatpState.time_wait: > + self.wait(deadline) > + while self._state == RatpState.time_wait: > + self.wait(None) > + if self._state == RatpState.closed: > + logging.info("CLOSE: success") > + else: > + logging.info("CLOSE: failure") > + > + > + def abort(self): > + logging.info("ABORT") > + > + def status(self): > + logging.info("STATUS") > + return self._state > + > + > +class SerialRatpConnection(RatpConnection): > + def __init__(self, port): > + super(SerialRatpConnection, self).__init__() > + self.__port = port > + self.__port.timeout = 0.01 > + self.__port.writeTimeout = None > + self.__port.flushInput() > + > + def _write_raw(self, data): > + if data: > + logging.debug("-> %r", bytearray(data)) > + return self.__port.write(data) > + > + def _read_raw(self, size=1): > + data = self.__port.read(size) > + if data: > + logging.debug("<- %r", bytearray(data)) > + return data > diff --git a/scripts/remote/ratpfs.py b/scripts/remote/ratpfs.py > new file mode 100644 > index 0000000..0333bf7 > --- /dev/null > +++ b/scripts/remote/ratpfs.py > @@ -0,0 +1,189 @@ > +#!/usr/bin/env python2 > +# -*- coding: utf-8 -*- > + > +from __future__ import absolute_import, division, print_function > + > +import logging > +import os > +import stat > +import struct > +from enum import IntEnum > + > +from .messages import BBPacketFS, BBPacketFSReturn > + > +class RatpFSType(IntEnum): > + invalid = 0 > + mount_call = 1 > + mount_return = 2 > + readdir_call = 3 > + readdir_return = 4 > + stat_call = 5 > + stat_return = 6 > + open_call = 7 > + open_return = 8 > + read_call = 9 > + read_return = 10 > + write_call = 11 > + write_return = 12 > + close_call = 13 > + close_return = 14 > + truncate_call = 15 > + truncate_return = 16 > + > + > +class RatpFSError(ValueError): > + pass > + > + > +class RatpFSPacket(object): > + def __init__(self, type=RatpFSType.invalid, payload="", raw=None): > + if raw is not None: > + type, = struct.unpack('!B', raw[:1]) > + self.type = RatpFSType(type) > + self.payload = raw[1:] > + else: > + self.type = type > + self.payload = payload > + > + def __repr__(self): > + s = "%s(" % self.__class__.__name__ > + s += "TYPE=%i," % self.type > + s += "PAYLOAD=%s)" % repr(self.payload) > + return s > + > + def pack(self): > + return struct.pack('!B', int(self.type))+self.payload > + > + > +class RatpFSServer(object): > + def __init__(self, path=None): > + self.path = path > + if path: > + self.path = os.path.abspath(path) > + self.next_handle = 1 # 0 is invalid > + self.files = {} > + self.mounted = False > + logging.info("exporting: %s", self.path) > + > + def _alloc_handle(self): > + handle = self.next_handle > + self.next_handle += 1 > + return handle > + > + def _resolve(self, path): > + components = path.split('/') > + components = [x for x in components if x and x != '..'] > + return os.path.join(self.path, *components) > + > + def handle_stat(self, path): > + > + try: > + logging.info("path: %r", path) > + path = self._resolve(path) > + logging.info("path1: %r", path) > + s = os.stat(path) > + except OSError as e: > + return struct.pack('!BI', 0, e.errno) > + if stat.S_ISREG(s.st_mode): > + return struct.pack('!BI', 1, s.st_size) > + elif stat.S_ISDIR(s.st_mode): > + return struct.pack('!BI', 2, s.st_size) > + else: > + return struct.pack('!BI', 0, 0) > + > + def handle_open(self, params): > + flags, = struct.unpack('!I', params[:4]) > + flags = flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR | os.O_CREAT | > + os.O_TRUNC) > + path = params[4:] > + try: > + f = os.open(self._resolve(path), flags, 0666) > + except OSError as e: > + return struct.pack('!II', 0, e.errno) > + h = self._alloc_handle() > + self.files[h] = f > + size = os.lseek(f, 0, os.SEEK_END) > + return struct.pack('!II', h, size) > + > + def handle_read(self, params): > + h, pos, size = struct.unpack('!III', params) > + f = self.files[h] > + os.lseek(f, pos, os.SEEK_SET) > + size = min(size, 4096) > + return os.read(f, size) > + > + def handle_write(self, params): > + h, pos = struct.unpack('!II', params[:8]) > + payload = params[8:] > + f = self.files[h] > + pos = os.lseek(f, pos, os.SEEK_SET) > + assert os.write(f, payload) == len(payload) > + return "" > + > + def handle_readdir(self, path): > + res = "" > + for x in os.listdir(self._resolve(path)): > + res += x+'\0' > + return res > + > + def handle_close(self, params): > + h, = struct.unpack('!I', params[:4]) > + os.close(self.files.pop(h)) > + return "" > + > + def handle_truncate(self, params): > + h, size = struct.unpack('!II', params) > + f = self.files[h] > + os.ftruncate(f, size) > + return "" > + > + def handle(self, bbcall): > + assert isinstance(bbcall, BBPacketFS) > + logging.debug("bb-call: %s", bbcall) > + fscall = RatpFSPacket(raw=bbcall.payload) > + logging.info("fs-call: %s", fscall) > + > + if not self.path: > + logging.warning("no filesystem exported") > + fsreturn = RatpFSPacket(type=RatpFSType.invalid) > + elif fscall.type == RatpFSType.mount_call: > + self.mounted = True > + fsreturn = RatpFSPacket(type=RatpFSType.mount_return) > + elif not self.mounted: > + logging.warning("filesystem not mounted") > + fsreturn = RatpFSPacket(type=RatpFSType.invalid) > + elif fscall.type == RatpFSType.readdir_call: > + payload = self.handle_readdir(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.readdir_return, > + payload=payload) > + elif fscall.type == RatpFSType.stat_call: > + payload = self.handle_stat(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.stat_return, > + payload=payload) > + elif fscall.type == RatpFSType.open_call: > + payload = self.handle_open(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.open_return, > + payload=payload) > + elif fscall.type == RatpFSType.read_call: > + payload = self.handle_read(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.read_return, > + payload=payload) > + elif fscall.type == RatpFSType.write_call: > + payload = self.handle_write(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.write_return, > + payload=payload) > + elif fscall.type == RatpFSType.close_call: > + payload = self.handle_close(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.close_return, > + payload=payload) > + elif fscall.type == RatpFSType.truncate_call: > + payload = self.handle_truncate(fscall.payload) > + fsreturn = RatpFSPacket(type=RatpFSType.truncate_return, > + payload=payload) > + else: > + raise RatpFSError() > + > + logging.info("fs-return: %s", fsreturn) > + bbreturn = BBPacketFSReturn(payload=fsreturn.pack()) > + logging.debug("bb-return: %s", bbreturn) > + return bbreturn > diff --git a/scripts/remote/threadstdio.py b/scripts/remote/threadstdio.py > new file mode 100644 > index 0000000..db24989 > --- /dev/null > +++ b/scripts/remote/threadstdio.py > @@ -0,0 +1,47 @@ > +#!/usr/bin/python2 > + > +import os > +import sys > +import termios > +import atexit > +from threading import Thread > +from Queue import Queue, Empty > + > +class ConsoleInput(Thread): > + def __init__(self, queue, exit='\x14'): > + Thread.__init__(self) > + self.daemon = True > + self.q = queue > + self._exit = exit > + self.fd = sys.stdin.fileno() > + old = termios.tcgetattr(self.fd) > + new = termios.tcgetattr(self.fd) > + new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG > + new[6][termios.VMIN] = 1 > + new[6][termios.VTIME] = 0 > + termios.tcsetattr(self.fd, termios.TCSANOW, new) > + > + def cleanup(): > + termios.tcsetattr(self.fd, termios.TCSAFLUSH, old) > + atexit.register(cleanup) > + > + def run(self): > + while True: > + c = os.read(self.fd, 1) > + if c == self._exit: > + self.q.put((self, None)) > + return > + else: > + self.q.put((self, c)) > + > +if __name__ == "__main__": > + q = Queue() > + i = ConsoleInput(q) > + i.start() > + while True: > + event = q.get(block=True) > + src, c = event > + if c == '\x04': > + break > + os.write(sys.stdout.fileno(), c.upper()) > + > -- > 2.6.4 > > > _______________________________________________ > barebox mailing list > barebox@xxxxxxxxxxxxxxxxxxx > http://lists.infradead.org/mailman/listinfo/barebox _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox