From bbdbda0a7442545ab9022f2ec78e45f1d6750f38 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 3 Jun 2015 00:38:09 +0200 Subject: Add Reliable Asynchronous Transfer Protocol This patch adds support for Reliable Asynchronous Transfer Protocol (RATP) as described in RFC916. Communication over RS232 is often unreliable as characters are lost or misinterpreted. This protocol allows for a reliable packet based communication over serial lines. The implementation simply follows the state machine described in the RFC text with one exception. RFC916 uses a plain checksum for the transferred data. We decided to use CRC16 for greater robustness. Since this is the only RFC916 implementation we currently know of interoperability with other implementations should not matter. Signed-off-by: Sascha Hauer Tested-by: Andrey Smirnov --- include/ratp.h | 22 + lib/Kconfig | 8 + lib/Makefile | 1 + lib/ratp.c | 1834 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1865 insertions(+) create mode 100644 include/ratp.h create mode 100644 lib/ratp.c diff --git a/include/ratp.h b/include/ratp.h new file mode 100644 index 0000000000..b91d305a22 --- /dev/null +++ b/include/ratp.h @@ -0,0 +1,22 @@ +#ifndef __RATP_H +#define __RATP_H + +struct ratp { + struct ratp_internal *internal; + int (*send)(struct ratp *, void *pkt, int len); + int (*recv)(struct ratp *, uint8_t *data); +}; + +int ratp_establish(struct ratp *ratp, bool active, int timeout_ms); +void ratp_close(struct ratp *ratp); +int ratp_recv(struct ratp *ratp, void **data, size_t *len); +int ratp_send(struct ratp *ratp, const void *data, size_t len); +int ratp_send_complete(struct ratp *ratp, const void *data, size_t len, + void (*complete)(void *ctx, int status), void *complete_ctx); +int ratp_poll(struct ratp *ratp); +bool ratp_closed(struct ratp *ratp); +bool ratp_busy(struct ratp *ratp); + +void ratp_run_command(void); + +#endif /* __RATP_H */ diff --git a/lib/Kconfig b/lib/Kconfig index fbf9f0f348..a7e067e571 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -55,6 +55,14 @@ config LIBMTD config STMP_DEVICE bool +config RATP + select CRC16 + bool + help + Reliable Asynchronous Transfer Protocol (RATP) is a protocol for reliably + transferring packets over serial links described in RFC916. This implementation + is used for controlling barebox over serial ports. + source lib/gui/Kconfig source lib/fonts/Kconfig diff --git a/lib/Makefile b/lib/Makefile index abb34cfbdb..0694721e9e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -56,3 +56,4 @@ obj-y += gcd.o obj-y += hexdump.o obj-$(CONFIG_FONTS) += fonts/ obj-$(CONFIG_BAREBOX_LOGO) += logo/ +obj-$(CONFIG_RATP) += ratp.o diff --git a/lib/ratp.c b/lib/ratp.c new file mode 100644 index 0000000000..d596a0e8b2 --- /dev/null +++ b/lib/ratp.c @@ -0,0 +1,1834 @@ +/* + * barebox RATP implementation. + * This is the barebox implementation for the Reliable Asynchronous + * Transfer Protocol (RATP) as described in RFC916. + * + * Copyright (C) 2015 Pengutronix, Sascha Hauer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "ratp: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* + * RATP packet format: + * + * Byte No. + * + * +-------------------------------+ + * | | + * 1 | Synch Leader | Hex 01 + * | | + * +-------------------------------+ + * | S | A | F | R | S | A | E | S | + * 2 | Y | C | I | S | N | N | O | O | Control + * | N | K | N | T | | | R | | + * +-------------------------------+ + * | | + * 3 | Data length (0-255) | + * | | + * +-------------------------------+ + * | | + * 4 | Header Checksum | + * | | + * +-------------------------------+ + * + */ + +struct ratp_header { + uint8_t synch; + uint8_t control; + uint8_t data_length; + uint8_t cksum; +}; + +#define RATP_CONTROL_SO (1 << 0) +#define RATP_CONTROL_EOR (1 << 1) +#define RATP_CONTROL_AN (1 << 2) +#define RATP_CONTROL_SN (1 << 3) +#define RATP_CONTROL_RST (1 << 4) +#define RATP_CONTROL_FIN (1 << 5) +#define RATP_CONTROL_ACK (1 << 6) +#define RATP_CONTROL_SYN (1 << 7) + +enum ratp_state { + RATP_STATE_LISTEN, + RATP_STATE_SYN_SENT, + RATP_STATE_SYN_RECEIVED, + RATP_STATE_ESTABLISHED, + RATP_STATE_FIN_WAIT, + RATP_STATE_LAST_ACK, + RATP_STATE_CLOSING, + RATP_STATE_TIME_WAIT, + RATP_STATE_CLOSED, +}; + +struct ratp_message { + void *buf; + size_t len; + struct list_head list; + void (*complete)(void *ctx, int status); + void *complete_ctx; + int eor; +}; + +static char *ratp_state_str[] = { + [RATP_STATE_LISTEN] = "LISTEN", + [RATP_STATE_SYN_SENT] = "SYN_SENT", + [RATP_STATE_SYN_RECEIVED] = "SYN_RECEIVED", + [RATP_STATE_ESTABLISHED] = "ESTABLISHED", + [RATP_STATE_FIN_WAIT] = "FIN_WAIT", + [RATP_STATE_LAST_ACK] = "LAST_ACK", + [RATP_STATE_CLOSING] = "CLOSING", + [RATP_STATE_TIME_WAIT] = "TIME_WAIT", + [RATP_STATE_CLOSED] = "CLOSED", +}; + +struct ratp_internal { + struct ratp *ratp; + + enum ratp_state state; + int sn_sent; + int sn_received; + int active; + + void *recvbuf; + void *sendbuf; + int sendbuf_len; + + struct list_head recvmsg; + struct list_head sendmsg; + + struct ratp_message *sendmsg_current; + + uint64_t timewait_timer_start; + uint64_t retransmission_timer_start; + int max_retransmission; + int retransmission_count; + int srtt; + int rto; + + int status; + + int in_ratp; +}; + +static bool ratp_sn(struct ratp_header *hdr) +{ + return hdr->control & RATP_CONTROL_SN ? 1 : 0; +} + +static bool ratp_an(struct ratp_header *hdr) +{ + return hdr->control & RATP_CONTROL_AN ? 1 : 0; +} + +#define ratp_set_sn(sn) (((sn) % 2) ? RATP_CONTROL_SN : 0) +#define ratp_set_an(an) (((an) % 2) ? RATP_CONTROL_AN : 0) + +static inline int ratp_header_ok(struct ratp_internal *ri, struct ratp_header *h) +{ + uint8_t cksum; + int ret; + + cksum = h->control; + cksum += h->data_length; + cksum += h->cksum; + + ret = cksum == 0xff ? 1 : 0; + + if (ret) + pr_vdebug("Header ok\n"); + else + pr_vdebug("Header cksum failed: %02x\n", cksum); + + return ret; +} + +static bool ratp_has_data(struct ratp_header *hdr) +{ + if (hdr->control & RATP_CONTROL_SO) + return 1; + if (hdr->data_length) + return 1; + return 0; +} + +static void ratp_print_header(struct ratp_internal *ri, struct ratp_header *hdr, + const char *prefix) +{ + uint8_t control = hdr->control; + + pr_debug("%s>%s %s %s %s %s %s %s %s< len: %-3d\n", + prefix, + control & RATP_CONTROL_SO ? "so" : "--", + control & RATP_CONTROL_EOR ? "eor" : "---", + control & RATP_CONTROL_AN ? "an" : "--", + control & RATP_CONTROL_SN ? "sn" : "--", + control & RATP_CONTROL_RST ? "rst" : "---", + control & RATP_CONTROL_FIN ? "fin" : "---", + control & RATP_CONTROL_ACK ? "ack" : "---", + control & RATP_CONTROL_SYN ? "syn" : "---", + hdr->data_length); + +#ifdef VERBOSE_DEBUG + if (hdr->data_length) + memory_display(hdr + 1, 0, hdr->data_length, 1, 0); +#endif +} + +static void ratp_create_packet(struct ratp_internal *ri, struct ratp_header *hdr, + uint8_t control, uint8_t length) +{ + hdr->synch = 0x1; + hdr->control = control; + hdr->data_length = length; + hdr->cksum = (control + length) ^ 0xff; +} + +static void ratp_state_change(struct ratp_internal *ri, enum ratp_state state) +{ + pr_debug("state %-10s -> %-10s\n", ratp_state_str[ri->state], + ratp_state_str[state]); + + ri->state = state; +} + +#define RATP_CONTROL_SO (1 << 0) +#define RATP_CONTROL_EOR (1 << 1) +#define RATP_CONTROL_AN (1 << 2) +#define RATP_CONTROL_SN (1 << 3) +#define RATP_CONTROL_RST (1 << 4) +#define RATP_CONTROL_FIN (1 << 5) +#define RATP_CONTROL_ACK (1 << 6) +#define RATP_CONTROL_SYN (1 << 7) + +static int ratp_send_pkt(struct ratp_internal *ri, void *pkt, int length) +{ + struct ratp_header *hdr = (void *)pkt; + + ratp_print_header(ri, hdr, "send"); + + if (ratp_has_data(hdr) || + (hdr->control & (RATP_CONTROL_SYN | RATP_CONTROL_RST | RATP_CONTROL_FIN))) { + memcpy(ri->sendbuf, pkt, length); + ri->sn_sent = ratp_sn(hdr); + ri->sendbuf_len = length; + ri->retransmission_timer_start = get_time_ns(); + ri->retransmission_count = 0; + } + + return ri->ratp->send(ri->ratp, pkt, length); +} + +static int ratp_send_hdr(struct ratp_internal *ri, uint8_t control) +{ + struct ratp_header hdr = {}; + + ratp_create_packet(ri, &hdr, control, 0); + + return ratp_send_pkt(ri, &hdr, sizeof(hdr)); +} + +static int ratp_recv_char(struct ratp_internal *ri, uint8_t *data, int poll_timeout_ms) +{ + uint64_t start; + int ret; + + start = get_time_ns(); + + while (1) { + ret = ri->ratp->recv(ri->ratp, data); + if (ret < 0 && ret != -EAGAIN) + return ret; + + if (ret == 0) + return 0; + + if (is_timeout(start, poll_timeout_ms * MSECOND)) + return -EAGAIN; + } +} + +static int ratp_recv_pkt_header(struct ratp_internal *ri, struct ratp_header *hdr, + int poll_timeout_ms) +{ + int ret; + uint8_t buf; + + do { + ret = ratp_recv_char(ri, &buf, 0); + if (ret < 0) + return ret; + hdr->synch = buf; + } while (hdr->synch != 1); + ret = ratp_recv_char(ri, &buf, poll_timeout_ms); + if (ret < 0) + return ret; + + hdr->control = buf; + ret = ratp_recv_char(ri, &buf, poll_timeout_ms); + if (ret < 0) + return ret; + + hdr->data_length = buf; + + ret = ratp_recv_char(ri, &buf, poll_timeout_ms); + if (ret < 0) + return ret; + + hdr->cksum = buf; + + if (!ratp_header_ok(ri, hdr)) + return -EAGAIN; + + return 0; +} + +static int ratp_recv_pkt_data(struct ratp_internal *ri, void *data, uint8_t len, + int poll_timeout_ms) +{ + uint16_t crc_expect, crc_read; + int ret, i; + + for (i = 0; i < len + 2; i++) { + ret = ratp_recv_char(ri, data + i, poll_timeout_ms); + if (ret < 0) + return ret; + } + + crc_expect = cyg_crc16(data, len); + + crc_read = get_unaligned_be16(data + len); + + if (crc_expect != crc_read) { + pr_vdebug("Wrong CRC: expected: 0x%04x, got 0x%04x\n", + crc_expect, crc_read); + return -EBADMSG; + } else { + pr_vdebug("correct CRC: 0x%04x\n", crc_expect); + } + + return 0; +} + +static int ratp_recv_pkt(struct ratp_internal *ri, void *pkt, int poll_timeout_ms) +{ + struct ratp_header *hdr = pkt; + void *data = pkt + sizeof(struct ratp_header); + int ret; + + ret = ratp_recv_pkt_header(ri, hdr, poll_timeout_ms); + if (ret < 0) + return ret; + + if (hdr->control & (RATP_CONTROL_SO | RATP_CONTROL_RST | RATP_CONTROL_SYN | + RATP_CONTROL_FIN)) + return 0; + + if (hdr->data_length) { + ret = ratp_recv_pkt_data(ri, data, hdr->data_length, + poll_timeout_ms); + if (ret) + return ret; + } + + return 0; +} + +static bool ratp_an_expected(struct ratp_internal *ri, struct ratp_header *hdr) +{ + return ratp_an(hdr) == (ri->sn_sent + 1) % 2; +} + +static bool ratp_sn_expected(struct ratp_internal *ri, struct ratp_header *hdr) +{ + return ratp_sn(hdr) != ri->sn_received; +} + +static int ratp_send_ack(struct ratp_internal *ri, struct ratp_header *hdr) +{ + uint8_t control = RATP_CONTROL_ACK; + int ret; + + if (hdr->control & RATP_CONTROL_SN) + control |= RATP_CONTROL_AN; + else + control |= 0; + + ret = ratp_send_hdr(ri, control); + if (ret) + return ret; + + return 0; +} + +static int ratp_send_next_data(struct ratp_internal *ri) +{ + uint16_t crc; + uint8_t control = RATP_CONTROL_ACK; + struct ratp_header *hdr; + int pktlen; + struct ratp_message *msg; + int len; + + if (ri->sendmsg_current) { + pr_err("%s: busy\n", __func__); + return -EBUSY; + } + + if (list_empty(&ri->sendmsg)) + return 0; + + msg = list_first_entry(&ri->sendmsg, struct ratp_message, list); + + ri->sendmsg_current = msg; + + list_del(&msg->list); + + len = msg->len; + + control = ratp_set_sn(ri->sn_sent + 1) | + ratp_set_an(ri->sn_received + 1) | + RATP_CONTROL_ACK; + + hdr = msg->buf; + + if (msg->eor) + control |= RATP_CONTROL_EOR; + + if (len > 1) { + void *data = hdr + 1; + pktlen = sizeof(*hdr) + len + 2; + crc = cyg_crc16(data, len); + put_unaligned_be16(crc, data + len); + } else { + pktlen = sizeof(struct ratp_header); + control |= RATP_CONTROL_SO; + len = 0; + } + + ratp_create_packet(ri, hdr, control, len); + + ri->retransmission_count = 0; + + ratp_send_pkt(ri, msg->buf, pktlen); + + return 0; +} + +static void ratp_start_time_wait_timer(struct ratp_internal *ri) +{ + ri->timewait_timer_start = get_time_ns(); +} + +static void ratp_msg_done(struct ratp_internal *ri, struct ratp_message *msg, int status) +{ + int alpha, beta, rtt; + + if (!status) { + rtt = (unsigned long)(get_time_ns() - ri->retransmission_timer_start) / MSECOND; + + alpha = 8; + beta = 15; + + ri->srtt = (alpha * ri->srtt + (10 - alpha) * rtt) / 10; + ri->rto = max(200, beta * ri->srtt / 10); + + pr_debug("%s: done. SRTT: %dms RTO: %dms status: %d\n", + __func__, ri->srtt, ri->rto, ri->status); + } + + if (msg->complete) + msg->complete(msg->complete_ctx, status); + + free(msg->buf); + free(msg); +} + +/* + * This procedure details the behavior of the LISTEN state. First + * check the packet for the RST flag. If it is set then packet is + * discarded and ignored, return and continue the processing + * associated with this state. + * + * We assume now that the RST flag was not set. Check the packet + * for the ACK flag. If it is set we have an illegal condition + * since no connection has yet been opened. Send a RST packet + * with the correct response SN value: + * + * + * + * Return to the current state without any further processing. + * + * We assume now that neither the RST nor the ACK flags were set. + * Check the packet for a SYN flag. If it is set then an attempt + * is being made to open a connection. Create a TCB for this + * connection. The sender has placed its MDL in the LENGTH field, + * also specified is the sender's initial SN value. Retrieve and + * place them into the TCB. Note that the presence of the SO flag + * is ignored since it has no meaning when either of the SYN, RST, + * or FIN flags are set. + * + * Send a SYN packet which acknowledges the SYN received. Choose + * the initial SN value and the MDL for this end of the + * connection: + * + * + * + * and go to the RATP_STATE_SYN_RECEIVED state without any further + * processing. + * + * Any packet not satisfying the above tests is discarded and + * ignored. Return to the current state without any further + * processing. + */ +static void ratp_behaviour_a(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (hdr->control & RATP_CONTROL_RST) + return; + + if (hdr->control & RATP_CONTROL_ACK) { + uint8_t control = RATP_CONTROL_RST; + + if (hdr->control & RATP_CONTROL_AN) + control |= RATP_CONTROL_SN; + + ratp_send_hdr(ri, control); + + return; + } + + if (hdr->control & RATP_CONTROL_SYN) { + struct ratp_header synack = {}; + uint8_t control = RATP_CONTROL_SYN | RATP_CONTROL_ACK; + + if (!(hdr->control & RATP_CONTROL_SN)) + control |= RATP_CONTROL_AN; + + ratp_create_packet(ri, &synack, control, 255); + ratp_send_pkt(ri, &synack, sizeof(synack)); + + ratp_state_change(ri, RATP_STATE_SYN_RECEIVED); + } +} + +/* + * This procedure represents the behavior of the SYN-SENT state + * and is entered when this end of the connection decides to + * execute an active OPEN. + * + * First, check the packet for the ACK flag. If the ACK flag is + * set then check to see if the AN value was as expected. If it + * was continue below. Otherwise the AN value was unexpected. If + * the RST flag was set then discard the packet and return to the + * current state without any further processing, else send a + * reset: + * + * + * + * Discard the packet and return to the current state without any + * further processing. + * + * At this point either the ACK flag was set and the AN value was + * as expected or ACK was not set. Second, check the RST flag. + * If the RST flag is set there are two cases: + * + * . If the ACK flag is set then discard the packet, flush the + * retransmission queue, inform the user "Error: Connection + * refused", delete the TCB, and go to the CLOSED state without + * any further processing. + * + * 2. If the ACK flag was not set then discard the packet and + * return to this state without any further processing. + * + * At this point we assume the packet contained an ACK which was + * Ok, or there was no ACK, and there was no RST. Now check the + * packet for the SYN flag. If the ACK flag was set then our SYN + * has been acknowledged. Store MDL received in the TCB. At this + * point we are technically in the ESTABLISHED state. Send an + * acknowledgment packet and any initial data which is queued to + * send: + * + * + * + * Go to the ESTABLISHED state without any further processing. + * + * If the SYN flag was set but the ACK was not set then the other + * end of the connection has executed an active open also. + * Acknowledge the SYN, choose your MDL, and send: + * + * + * + * Go to the SYN-RECEIVED state without any further processing. + * + * Any packet not satisfying the above tests is discarded and + * ignored. Return to the current state without any further + * processing. + */ +static void ratp_behaviour_b(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if ((hdr->control & RATP_CONTROL_ACK) && !ratp_an_expected(ri, hdr)) { + if (!(hdr->control & RATP_CONTROL_RST)) { + uint8_t control = RATP_CONTROL_RST; + + control = RATP_CONTROL_RST | + ratp_set_sn(ratp_an(hdr)); + + ratp_send_hdr(ri, control); + } + return; + } + + if (hdr->control & RATP_CONTROL_RST) { + if (hdr->control & RATP_CONTROL_ACK) { + ri->status = -ECONNREFUSED; + + pr_debug("Connection refused\n"); + + ratp_state_change(ri, RATP_STATE_CLOSED); + + } + return; + } + + if (hdr->control & RATP_CONTROL_SYN) { + uint8_t control; + + if (hdr->control & RATP_CONTROL_ACK) { + control = ratp_set_sn(ratp_an(hdr)) | + ratp_set_an(!ratp_sn(hdr)) | + RATP_CONTROL_ACK; + } else { + control = ratp_set_an(!ratp_sn(hdr)) | + RATP_CONTROL_SYN | + RATP_CONTROL_ACK; + + } + + ri->sn_received = ratp_sn(hdr); + + ratp_send_hdr(ri, control); + ratp_state_change(ri, RATP_STATE_ESTABLISHED); + } +} + +/* + * Examine the received SN field value. If the SN value was + * expected then return and continue the processing associated + * with this state. + * + * We now assume the SN value was not what was expected. + * + * If either RST or FIN were set discard the packet and return to + * the current state without any further processing. + * + * If neither RST nor FIN flags were set it is assumed that this + * packet is a duplicate of one already received. Send an ACK + * back: + * + * + * + * Discard the duplicate packet and return to the current state + * without any further processing. + */ +static int ratp_behaviour_c1(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + int ret; + + pr_debug("%s\n", __func__); + + if (ratp_sn_expected(ri, hdr)) { + pr_vdebug("%s: sn is expected\n", __func__); + return 0; + } + + if (!(hdr->control & RATP_CONTROL_RST) && + !(hdr->control & RATP_CONTROL_FIN)) { + ret = ratp_send_ack(ri, hdr); + if (ret) + return ret; + } + + return 1; + +} + +/* + * Examine the received SN field value. If the SN value was + * expected then return and continue the processing associated + * with this state. + * + * We now assume the SN value was not what was expected. + * + * If either RST or FIN were set discard the packet and return to + * the current state without any further processing. + * + * If SYN was set we assume that the other end crashed and has + * attempted to open a new connection. We respond by sending a + * legal reset: + * + * + * + * This will cause the other end, currently in the SYN-SENT state, + * to close. Flush the retransmission queue, inform the user + * "Error: Connection reset", discard the packet, delete the TCB, + * and go to the CLOSED state without any further processing. + * + * If neither RST, FIN, nor SYN flags were set it is assumed that + * this packet is a duplicate of one already received. Send an + * ACK back: + * + * + * + * Discard the duplicate packet and return to the current state + * without any further processing. + */ +static int ratp_behaviour_c2(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + int ret; + + pr_debug("%s\n", __func__); + + if (!ratp_has_data(hdr)) + return 0; + + if (ratp_sn_expected(ri, hdr)) + return 0; + + if ((hdr->control & RATP_CONTROL_RST) || + (hdr->control & RATP_CONTROL_FIN)) + return 1; + + if (hdr->control & RATP_CONTROL_SYN) { + ri->status = -ECONNRESET; + pr_debug("Error: Connection reset\n"); + ratp_state_change(ri, RATP_STATE_CLOSED); + return 1; + } + + if (!ratp_has_data(hdr)) + return 1; + + pr_debug("Sending ack for duplicate message\n"); + ret = ratp_send_ack(ri, hdr); + if (ret) + return ret; + + return 1; +} + +/* + * The packet is examined for a RST flag. If RST is not set then + * return and continue the processing associated with this state. + * + * RST is now assumed to have been set. If the connection was + * originally initiated from the LISTEN state (it was passively + * opened) then flush the retransmission queue, discard the + * packet, and go to the LISTEN state without any further + * processing. + * + * If instead the connection was initiated actively (came from the + * SYN-SENT state) then flush the retransmission queue, inform the + * user "Error: Connection refused", discard the packet, delete + * the TCB, and go to the CLOSED state without any further + * processing. + */ +static int ratp_behaviour_d1(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_RST)) + return 0; + + if (!(ri->active)) { + ratp_state_change(ri, RATP_STATE_LISTEN); + return 1; + } + + ri->status = -ECONNREFUSED; + + pr_debug("Error: connection refused\n"); + + ratp_state_change(ri, RATP_STATE_CLOSED); + + return 1; +} + +/* + * The packet is examined for a RST flag. If RST is not set then + * return and continue the processing associated with this state. + * + * RST is now assumed to have been set. Any data remaining to be + * sent is flushed. The retransmission queue is flushed, the user + * is informed "Error: Connection reset.", discard the packet, + * delete the TCB, and go to the CLOSED state without any further + * processing. + */ +static int ratp_behaviour_d2(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_RST)) + return 0; + + ri->status = -ECONNRESET; + + pr_debug("connection reset\n"); + + return 0; +} + +/* + * The packet is examined for a RST flag. If RST is not set then + * return and continue the processing associated with this state. + * + * RST is now assumed to have been set. Discard the packet, + * delete the TCB, and go to the CLOSED state without any further + * processing. + */ +static int ratp_behaviour_d3(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_RST)) + return 0; + + ratp_state_change(ri, RATP_STATE_CLOSED); + + return 1; +} + +/* + * Check the presence of the SYN flag. If the SYN flag is not set + * then return and continue the processing associated with this + * state. + * + * We now assume that the SYN flag was set. The presence of a SYN + * here is an error. Flush the retransmission queue, send a legal + * RST packet. + * + * If the ACK flag was set then send: + * + * + * + * If the ACK flag was not set then send: + * + * + * + * The user should receive the message "Error: Connection reset.", + * then delete the TCB and go to the CLOSED state without any + * further processing. + */ +static int ratp_behaviour_e(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_SYN)) + return 0; + + ri->status = -ECONNRESET; + + control = RATP_CONTROL_RST; + + if (hdr->control & RATP_CONTROL_ACK) + control |= ratp_set_sn(ratp_an(hdr)); + + ratp_send_hdr(ri, control); + + pr_debug("connection reset\n"); + + ratp_state_change(ri, RATP_STATE_CLOSED); + + return 1; +} + +/* + * Check the presence of the ACK flag. If ACK is not set then + * discard the packet and return without any further processing. + * + * We now assume that the ACK flag was set. If the AN field value + * was as expected then return and continue the processing + * associated with this state. + * + * We now assume that the ACK flag was set and that the AN field + * value was unexpected. If the connection was originally + * initiated from the LISTEN state (it was passively opened) then + * flush the retransmission queue, discard the packet, and send a + * legal RST packet: + * + * + * + * Then delete the TCB and go to the LISTEN state without any + * further processing. + * + * Otherwise the connection was initiated actively (came from the + * SYN-SENT state) then inform the user "Error: Connection + * refused", flush the retransmission queue, discard the packet, + * and send a legal RST packet: + * + * + * + * Then delete the TCB and go to the CLOSED state without any + * further processing. + */ +static int ratp_behaviour_f1(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_ACK)) + return 1; + + if (ratp_an_expected(ri, hdr)) + return 0; + + control = RATP_CONTROL_RST | ratp_set_sn(ratp_an(hdr)); + ratp_send_hdr(ri, control); + + if (ri->active) { + ratp_state_change(ri, RATP_STATE_CLOSED); + ri->status = -ECONNREFUSED; + + pr_debug("connection refused\n"); + } else { + ratp_state_change(ri, RATP_STATE_LISTEN); + } + + return 1; +} + +/* + * Check the presence of the ACK flag. If ACK is not set then + * discard the packet and return without any further processing. + * + * We now assume that the ACK flag was set. If the AN field value + * was as expected then flush the retransmission queue and inform + * the user with an "Ok" if a buffer has been entirely + * acknowledged. Another packet containing data may now be sent. + * Return and continue the processing associated with this state. + * + * We now assume that the ACK flag was set and that the AN field + * value was unexpected. This is assumed to indicate a duplicate + * acknowledgment. It is ignored, return and continue the + * processing associated with this state. + */ +static int ratp_behaviour_f2(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_ACK)) + return 1; + + if (ratp_an_expected(ri, hdr)) { + pr_debug("Data succesfully sent\n"); + if (ri->sendmsg_current) + ratp_msg_done(ri, ri->sendmsg_current, 0); + ri->sendmsg_current = NULL; + return 0; + } else { + pr_vdebug("%s: an not expected\n", __func__); + } + + return 0; +} + +/* + * Check the presence of the ACK flag. If ACK is not set then + * discard the packet and return without any further processing. + * + * We now assume that the ACK flag was set. If the AN field value + * was as expected then continue the processing associated with + * this state. + * + * We now assume that the ACK flag was set and that the AN field + * value was unexpected. This is ignored, return and continue + * with the processing associated with this state. + */ +static int ratp_behaviour_f3(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_ACK)) + return 1; + + return 0; +} + +/* + * This procedure represents the behavior of the CLOSED state of a + * connection. All incoming packets are discarded. If the packet + * had the RST flag set take no action. Otherwise it is necessary + * to build a RST packet. Since this end is closed the other end + * of the connection has incorrect data about the state of the + * connection and should be so informed. + * + * If the ACK flag was set then send: + * + * + * + * If the ACK flag was not set then send: + * + * + * + * After sending the reset packet return to the current state + * without any further processing. + */ +static int ratp_behaviour_g(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control; + + pr_debug("%s\n", __func__); + + control = RATP_CONTROL_RST; + + if (hdr->control & RATP_CONTROL_ACK) + control |= ratp_set_sn(ratp_an(hdr)); + else + control = ratp_set_an(ratp_sn(hdr) + 1) | RATP_CONTROL_ACK; + + ratp_send_hdr(ri, control); + + return 0; +} + +/* + * Our SYN has been acknowledged. At this point we are + * technically in the ESTABLISHED state. Send any initial data + * which is queued to send: + * + * + * + * Go to the ESTABLISHED state and execute procedure I1 to process + * any data which might be in this packet. + * + * Any packet not satisfying the above tests is discarded and + * ignored. Return to the current state without any further + * processing. + */ +static int ratp_behaviour_h1(struct ratp_internal *ri, void *pkt) +{ + pr_debug("%s\n", __func__); + + ratp_state_change(ri, RATP_STATE_ESTABLISHED); + + return 0; +} + +/* + * Check the presence of the FIN flag. If FIN is not set then + * continue the processing associated with this state. + * + * We now assume that the FIN flag was set. This means the other + * end has decided to close the connection. Flush the + * retransmission queue. If any data remains to be sent then + * inform the user "Warning: Data left unsent." The user must + * also be informed "Connection closing." An acknowledgment for + * the FIN must be sent which also indicates this end is closing: + * + * + * + * Go to the LAST-ACK state without any further processing. + */ +static int ratp_behaviour_h2(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_FIN)) + return 0; + + ri->status = -ENETDOWN; + + control = ratp_set_sn(ratp_an(hdr)) | + ratp_set_an(ratp_sn(hdr) + 1) | + RATP_CONTROL_FIN | + RATP_CONTROL_ACK; + + ratp_send_hdr(ri, control); + + ratp_state_change(ri, RATP_STATE_LAST_ACK); + + return 1; +} + +/* + * This state represents the final behavior of the FIN-WAIT state. + * + * If the packet did not contain a FIN we assume this packet is a + * duplicate and that the other end of the connection has not seen + * the FIN packet we sent earlier. Rely upon retransmission of + * our earlier FIN packet to inform the other end of our desire to + * close. Discard the packet and return without any further + * processing. + * + * At this point we have a packet which should contain a FIN. By + * the rules of this protocol an ACK of a FIN requires a FIN, ACK + * in response and no data. If the packet contains data we have + * detected an illegal condition. Send a reset: + * + * + * Discard the packet, flush the retransmission queue, inform the + * ser "Error: Connection reset.", delete the TCB, and go to the + * CLOSED state without any further processing. + * + * We now assume that the FIN flag was set and no data was + * contained in the packet. If the AN field value was expected + * then this packet acknowledges a previously sent FIN packet. + * The other end of the connection is then also assumed to be + * closing and expects an acknowledgment. Send an acknowledgment + * of the FIN: + * + * + * + * Start the 2*SRTT timer associated with the TIME-WAIT state, + * discard the packet, and go to the TIME-WAIT state without any + * further processing. + * + * Otherwise the AN field value was unexpected. This indicates a + * simultaneous closing by both sides of the connection. Send an + * acknowledgment of the FIN: + * + * + * + * Discard the packet, and go to the CLOSING state without any + * further processing. + */ +static int ratp_behaviour_h3(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control; + int expected; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_FIN)) + return 1; + + if (ratp_has_data(hdr)) { + control = ratp_set_sn(ratp_an(hdr)) | + ratp_set_an(ratp_sn(hdr) + 1) | + RATP_CONTROL_RST | + RATP_CONTROL_ACK; + ratp_send_hdr(ri, control); + ri->status = -ECONNRESET; + pr_debug("Error: Connection reset\n"); + ratp_state_change(ri, RATP_STATE_CLOSED); + return 1; + } + + control = ratp_set_sn(ratp_an(hdr)) | + ratp_set_an(ratp_sn(hdr) + 1) | + RATP_CONTROL_ACK; + + expected = ratp_an_expected(ri, hdr); + + ratp_send_hdr(ri, control); + + if (expected) { + ratp_state_change(ri, RATP_STATE_TIME_WAIT); + ratp_start_time_wait_timer(ri); + } else { + ratp_state_change(ri, RATP_STATE_CLOSING); + } + + return 1; +} + +/* + * This state represents the final behavior of the LAST-ACK state. + * + * If the AN field value is expected then this ACK is in response + * to the FIN, ACK packet recently sent. This is the final + * acknowledging message indicating both side's agreement to close + * the connection. Discard the packet, flush all queues, delete + * the TCB, and go to the CLOSED state without any further + * processing. + * + * Otherwise the AN field value was unexpected. Discard the + * packet and remain in the current state without any further + * processing. + */ +static int ratp_behaviour_h4(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (ratp_an_expected(ri, hdr)) + ratp_state_change(ri, RATP_STATE_CLOSED); + + return 1; +} + +/* + * This state represents the final behavior of the CLOSING state. + * + * If the AN field value was expected then this packet + * acknowledges the FIN packet recently sent. This is the final + * acknowledging message indicating both side's agreement to close + * the connection. Start the 2*SRTT timer associated with the + * TIME-WAIT state, discard the packet, and go to the TIME-WAIT + * state without any further processing. + * + * Otherwise the AN field value was unexpected. Discard the + * packet and remain in the current state without any further + * processing. + */ +static int ratp_behaviour_h5(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + + pr_debug("%s\n", __func__); + + if (ratp_an_expected(ri, hdr)) { + ratp_state_change(ri, RATP_STATE_TIME_WAIT); + ratp_start_time_wait_timer(ri); + } + + return 0; +} + +/* + * This state represents the behavior of the TIME-WAIT state. + * Check the presence of the ACK flag. If ACK is not set then + * discard the packet and return without any further processing. + * + * Check the presence of the FIN flag. If FIN is not set then + * discard the packet and return without any further processing. + * + * We now assume that the FIN flag was set. This situation + * indicates that the last acknowledgment of the FIN packet sent + * by the other end of the connection did not arrive. Resend the + * acknowledgment: + * + * + * + * Restart the 2*SRTT timer, discard the packet, and remain in the + * current state without any further processing. + */ +static int ratp_behaviour_h6(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control; + + pr_debug("%s\n", __func__); + + if (!(hdr->control & RATP_CONTROL_ACK)) + return 1; + + if (!(hdr->control & RATP_CONTROL_FIN)) + return 1; + + control = ratp_set_sn(ratp_an(hdr) + 1) | RATP_CONTROL_ACK; + + ratp_send_hdr(ri, control); + + ratp_start_time_wait_timer(ri); + + return 0; +} + +static int msg_recv(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + struct ratp_message *msg; + + pr_debug("%s: Put msg in receive queue\n", __func__); + + msg = xzalloc(sizeof(*msg)); + if (hdr->data_length) { + msg->len = hdr->data_length; + msg->buf = xzalloc(msg->len); + memcpy(msg->buf, pkt + sizeof(struct ratp_header), msg->len); + } else { + msg->len = 1; + msg->buf = xzalloc(1); + *(uint8_t *)msg->buf = hdr->data_length; + } + + if (hdr->control & RATP_CONTROL_EOR) + msg->eor = 1; + + list_add_tail(&msg->list, &ri->recvmsg); + + return 0; +} + +/* + * This represents that stage of processing in the ESTABLISHED + * state in which all the flag bits have been processed and only + * data may remain. The packet is examined to see if it contains + * data. If not the packet is now discarded, return to the + * current state without any further processing. + * + * We assume the packet contained data, that either the SO flag + * was set or LENGTH is positive. That data is placed into the + * user's receive buffers. As these become full the user should + * be informed "Receive buffer full." An acknowledgment is sent: + * + * + * + * If data is queued to send then it is most efficient to + * 'piggyback' this acknowledgment on that data packet. + * + * The packet is now discarded, return to the ESTABLISHED state + * without any further processing. + */ +static int ratp_behaviour_i1(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + uint8_t control = 0; + + if (!hdr->data_length && !(hdr->control & RATP_CONTROL_SO)) + return 1; + + pr_vdebug("%s **received** %d\n", __func__, hdr->data_length); + + ri->sn_received = ratp_sn(hdr); + + msg_recv(ri, pkt); + + if (list_empty(&ri->sendmsg) || ri->sendmsg_current) { + control = ratp_set_sn(!ri->sn_sent) | + ratp_set_an(ri->sn_received + 1) | + RATP_CONTROL_ACK; + + ratp_send_hdr(ri, control); + } else { + ratp_send_next_data(ri); + } + + return 0; +} + +/* + * State machine as desribed in RFC916 + * + * STATE BEHAVIOR + * =============+======================== + * LISTEN | A + * -------------+------------------------ + * SYN-SENT | B + * -------------+------------------------ + * SYN-RECEIVED | C1 D1 E F1 H1 + * -------------+------------------------ + * ESTABLISHED | C2 D2 E F2 H2 I1 + * -------------+------------------------ + * FIN-WAIT | C2 D2 E F3 H3 + * -------------+------------------------ + * LAST-ACK | C2 D3 E F3 H4 + * -------------+------------------------ + * CLOSING | C2 D3 E F3 H5 + * -------------+------------------------ + * TIME-WAIT | D3 E F3 H6 + * -------------+------------------------ + * CLOSED | G + * -------------+------------------------ + */ + +static int ratp_state_machine(struct ratp_internal *ri, void *pkt) +{ + struct ratp_header *hdr = pkt; + int ret; + + ratp_print_header(ri, hdr, " recv"); + pr_debug(" state %s\n", ratp_state_str[ri->state]); + + switch (ri->state) { + case RATP_STATE_LISTEN: + ratp_behaviour_a(ri, pkt); + break; + case RATP_STATE_SYN_SENT: + ratp_behaviour_b(ri, pkt); + break; + case RATP_STATE_SYN_RECEIVED: + ret = ratp_behaviour_c1(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_d1(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_e(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_f1(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_h1(ri, pkt); + if (ret) + return ret; + break; + case RATP_STATE_ESTABLISHED: + ret = ratp_behaviour_c2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_d2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_e(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_f2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_h2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_i1(ri, pkt); + if (ret) + return ret; + break; + case RATP_STATE_FIN_WAIT: + ret = ratp_behaviour_c2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_d2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_e(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_f3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_h3(ri, pkt); + if (ret) + return ret; + break; + case RATP_STATE_LAST_ACK: + ret = ratp_behaviour_c2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_d3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_e(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_f3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_h4(ri, pkt); + if (ret) + return ret; + break; + case RATP_STATE_CLOSING: + ret = ratp_behaviour_c2(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_d3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_e(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_f3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_h5(ri, pkt); + if (ret) + return ret; + break; + case RATP_STATE_TIME_WAIT: + ret = ratp_behaviour_d3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_e(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_f3(ri, pkt); + if (ret) + return ret; + ret = ratp_behaviour_h6(ri, pkt); + if (ret) + return ret; + break; + case RATP_STATE_CLOSED: + ratp_behaviour_g(ri, pkt); + break; + }; + + return 0; +} + +/** + * ratp_closed() - Check if a connection is closed + * + * Return: true if a connection is closed, false otherwise + */ +bool ratp_closed(struct ratp *ratp) +{ + struct ratp_internal *ri = ratp->internal; + + if (!ri) + return true; + + return ri->state == RATP_STATE_CLOSED; +} + +/** + * ratp_busy() - Check if we are inside the RATP code + * + * Needed for RATP debugging. The RATP console uses this to determine + * if it is called from inside the RATP code. + * + * Return: true if we are inside the RATP code, false otherwise + */ +bool ratp_busy(struct ratp *ratp) +{ + struct ratp_internal *ri = ratp->internal; + + if (!ri) + return false; + + return ri->in_ratp != 0; +} + +/** + * ratp_poll() - Execute RATP state machine + * @ratp: The RATP link + * + * This function should be executed periodically to keep the RATP state + * machine going. + * + * Return: 0 if successful, a negative error code otherwise. + */ +int ratp_poll(struct ratp *ratp) +{ + struct ratp_internal *ri = ratp->internal; + int ret; + + if (!ri) + return -ENETDOWN; + + ri->in_ratp++; + + ret = ratp_recv_pkt(ri, ri->recvbuf, 100); + if (ret == 0) { + + if (ri->state == RATP_STATE_TIME_WAIT && + is_timeout(ri->timewait_timer_start, ri->srtt * 2 * MSECOND)) { + pr_debug("2*SRTT timer timed out\n"); + ret = -ECONNRESET; + goto out; + } + + ret = ratp_state_machine(ri, ri->recvbuf); + if (ret < 0) + goto out; + + if (ri->status < 0) { + ret = ri->status; + goto out; + } + } + + if (ri->sendmsg_current && is_timeout(ri->retransmission_timer_start, + ri->rto * MSECOND)) { + + ri->retransmission_count++; + if (ri->retransmission_count == ri->max_retransmission) { + ri->status = ret = -ETIMEDOUT; + ri->state = RATP_STATE_CLOSED; + goto out; + } + + pr_debug("%s: retransmit\n", __func__); + + ratp_print_header(ri, ri->sendbuf, "resend"); + + ri->retransmission_timer_start = get_time_ns(); + + ret = ri->ratp->send(ratp, ri->sendbuf, ri->sendbuf_len); + if (ret) + goto out; + } + + if (!ri->sendmsg_current && !list_empty(&ri->sendmsg)) + ratp_send_next_data(ri); + + ret = 0; +out: + ri->in_ratp--; + + return ret; +} + +/** + * ratp_establish(): Establish a RATP link + * @ratp: The RATP link + * @active: if true actively create a connection + * @timeout_ms: Timeout in ms to wait until a connection is established. If + * 0 wait forever. + * + * This function establishes a link with the remote end. It expects the + * send and receive functions to be set, all other struct ratp_internal members can + * be left uninitialized. + * + * Return: 0 if successful, a negative error code otherwise. + */ +int ratp_establish(struct ratp *ratp, bool active, int timeout_ms) +{ + struct ratp_internal *ri; + int ret; + uint64_t start; + + ri = xzalloc(sizeof(*ri)); + ri->ratp = ratp; + ratp->internal = ri; + + ri->recvbuf = xmalloc(512); + ri->sendbuf = xmalloc(512); + INIT_LIST_HEAD(&ri->recvmsg); + INIT_LIST_HEAD(&ri->sendmsg); + ri->max_retransmission = 100; + ri->srtt = 100; + ri->rto = 100; + ri->active = active; + + ri->in_ratp++; + + if (ri->active) { + ratp_send_hdr(ri, RATP_CONTROL_SYN); + + ratp_state_change(ri, RATP_STATE_SYN_SENT); + } + + start = get_time_ns(); + + while (1) { + ret = ratp_poll(ri->ratp); + if (ret < 0) + goto out; + + if (ri->state == RATP_STATE_ESTABLISHED) { + ret = 0; + goto out; + } + + if (timeout_ms && is_timeout(start, MSECOND * timeout_ms)) { + ret = -ETIMEDOUT; + goto out; + } + } + +out: + if (ret) { + free(ri->recvbuf); + free(ri->sendbuf); + free(ri); + ratp->internal = NULL; + } + + ri->in_ratp--; + + return ret; +} + +void ratp_close(struct ratp *ratp) +{ + struct ratp_internal *ri = ratp->internal; + struct ratp_message *msg, *tmp; + struct ratp_header fin = {}; + + if (!ri) + return; + + if (ri->state == RATP_STATE_ESTABLISHED) { + uint64_t start; + u8 control; + + pr_debug("Closing...\n"); + + ratp_state_change(ri, RATP_STATE_FIN_WAIT); + + control = ratp_set_sn(!ri->sn_sent) | + ratp_set_an(ri->sn_received + 1) | + RATP_CONTROL_FIN | RATP_CONTROL_ACK; + + ratp_create_packet(ri, &fin, control, 0); + + ratp_send_pkt(ri, &fin, sizeof(fin)); + + start = get_time_ns(); + + while (!is_timeout(start, ri->srtt * MSECOND * 2)) + ratp_poll(ratp); + } + + list_for_each_entry_safe(msg, tmp, &ri->sendmsg, list) + ratp_msg_done(ri, msg, -ECONNRESET); + + free(ri); + ratp->internal = NULL; + + pr_info("Closed\n"); +} + +/** + * ratp_send_complete(): Send data over a RATP link + * @ratp: The RATP link + * @data: The data buffer + * @len: The length of the message to send + * @complete: The completion callback for the message + * @complete_ctx: context pointer for the completion callback + * + * Queue a RATP message for transmission. This only queues the message, + * ratp_poll has to be called to actually transfer the message. + * @complete will be called upon completion of the message. + * + * Return: 0 if successful, a negative error code otherwise. + */ +int ratp_send_complete(struct ratp *ratp, const void *data, size_t len, + void (*complete)(void *ctx, int status), void *complete_ctx) +{ + struct ratp_internal *ri = ratp->internal; + struct ratp_message *msg; + + if (!ri || ri->state != RATP_STATE_ESTABLISHED) + return -ENETDOWN; + + if (!len) + return -EINVAL; + + ri->in_ratp++; + + while (len) { + int now = min((int)len, 255); + + msg = xzalloc(sizeof(*msg)); + msg->buf = xzalloc(sizeof(struct ratp_header) + now + 2); + msg->len = now; + memcpy(msg->buf + sizeof(struct ratp_header), data, now); + + list_add_tail(&msg->list, &ri->sendmsg); + + len -= now; + } + + msg->eor = 1; + msg->complete = complete; + msg->complete_ctx = complete_ctx; + + ri->in_ratp--; + + return 0; +} + +/** + * ratp_send(): Send data over a RATP link + * @ratp: The RATP link + * @data: The data buffer + * @len: The length of the message to send + * + * Queue a RATP message for transmission. This only queues the message, + * ratp_poll has to be called to actually transfer the message. + * + * Return: 0 if successful, a negative error code otherwise. + */ +int ratp_send(struct ratp *ratp, const void *data, size_t len) +{ + return ratp_send_complete(ratp, data, len, NULL, NULL); +} + +/** + * ratp_recv() - Receive data from a RATP link + * @ratp: The RATP link + * @data: Pointer to data + * @len: The length of the data in bytes + * + * If a message is available it fills @data with a pointer to the data. + * This function does not wait for new messages. If no data is available + * -EAGAIN is returned. If data is received @data has to be freed by the + * caller. + * + * Return: 0 if successful, a negative error code otherwise. + */ +int ratp_recv(struct ratp *ratp, void **data, size_t *len) +{ + struct ratp_internal *ri = ratp->internal; + struct ratp_message *msg, *tmp; + void *pos; + int num = 0; + + *len = 0; + + if (!ri || ri->state != RATP_STATE_ESTABLISHED) + return -ENETDOWN; + + if (list_empty(&ri->recvmsg)) + return -EAGAIN; + + list_for_each_entry(msg, &ri->recvmsg, list) { + *len += msg->len; + num++; + if (msg->eor) + goto eor; + } + + return -EAGAIN; + +eor: + *data = malloc(*len); + if (!*data) + return -ENOMEM; + + pos = *data; + + list_for_each_entry_safe(msg, tmp, &ri->recvmsg, list) { + memcpy(pos, msg->buf, msg->len); + pos += msg->len; + + list_del(&msg->list); + + free(msg->buf); + free(msg); + } + + return 0; +} \ No newline at end of file -- cgit v1.2.3 From 266057337402f45adf707ef59beebd2a6821a749 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 26 Nov 2015 09:47:23 +0100 Subject: barebox remote control This adds the ability to control barebox over serial lines. The regular console is designed for human input and is unsuitable for controlling barebox from scripts since characters can be lost on both ends, the data stream contains escape sequences and the prompt cannot be easily matched upon. This approach is based on the RATP protocol. RATP packages start with a binary 0x01 which does not occur in normal console data. Whenever a 0x01 character is detected in the console barebox goes into RATP mode. The RATP packets contain a simple structure with a command/respone type and data for that type. Currently defined types are: BB_RATP_TYPE_COMMAND (host->barebox): Execute a command in the shell BB_RATP_TYPE_COMMAND_RETURN (barebox->host) Sends return value of the command back to the host, also means barebox is ready for the next command BB_RATP_TYPE_CONSOLEMSG (barebox->host) Console message from barebox Planned but not yet implemented are: BB_RATP_TYPE_PING (host->barebox) BB_RATP_TYPE_PONG (barebox->host) For testing purposes BB_RATP_TYPE_GETENV (host->barebox) BB_RATP_TYPE_GETENV_RETURN (barebox->host) Get values of environment variables Signed-off-by: Sascha Hauer Tested-by: Andrey Smirnov --- common/Kconfig | 10 ++ common/Makefile | 2 + common/console.c | 25 ++- common/ratp.c | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ crypto/Kconfig | 1 + fs/Makefile | 1 + include/ratp.h | 2 +- include/ratp_bb.h | 15 ++ lib/readline.c | 8 + 9 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 common/ratp.c create mode 100644 include/ratp_bb.h diff --git a/common/Kconfig b/common/Kconfig index 8e7950968c..2b5943be8b 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -611,6 +611,16 @@ config PBL_CONSOLE must be running at the address it's linked at and bss must be cleared. On ARM that would be after setup_c(). +config CONSOLE_RATP + bool + select RATP + prompt "RATP console support" + help + This option adds support for remote controlling barebox via serial + port. The regular console is designed for human interaction whereas + this option adds a machine readable interface for controlling barebox. + Say yes here if you want to control barebox from a remote host. + config PARTITION bool prompt "Enable Partitions" diff --git a/common/Makefile b/common/Makefile index 56e6becec0..5eb3c96f45 100644 --- a/common/Makefile +++ b/common/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_RESET_SOURCE) += reset_source.o obj-$(CONFIG_SHELL_HUSH) += hush.o obj-$(CONFIG_SHELL_SIMPLE) += parser.o obj-$(CONFIG_STATE) += state.o +obj-$(CONFIG_RATP) += ratp.o obj-$(CONFIG_UIMAGE) += image.o uimage.o obj-$(CONFIG_MENUTREE) += menutree.o obj-$(CONFIG_EFI_GUID) += efi-guid.o @@ -54,6 +55,7 @@ obj-$(CONFIG_IMD) += imd.o obj-$(CONFIG_FILE_LIST) += file-list.o obj-$(CONFIG_FIRMWARE) += firmware.o obj-$(CONFIG_BAREBOX_UPDATE_IMX_NAND_FCB) += imx-bbu-nand-fcb.o +obj-$(CONFIG_CONSOLE_RATP) += ratp.o quiet_cmd_pwd_h = PWDH $@ ifdef CONFIG_PASSWORD diff --git a/common/console.c b/common/console.c index 4a1d2576d0..a541892583 100644 --- a/common/console.c +++ b/common/console.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -313,8 +314,16 @@ static int getc_raw(void) if (!(cdev->f_active & CONSOLE_STDIN)) continue; active = 1; - if (cdev->tstc(cdev)) - return cdev->getc(cdev); + if (cdev->tstc(cdev)) { + int ch = cdev->getc(cdev); + + if (IS_ENABLED(CONFIG_RATP) && ch == 0x01) { + barebox_ratp(cdev); + return -1; + } + + return ch; + } } if (!active) /* no active console found. bail out */ @@ -349,16 +358,26 @@ int getc(void) start = get_time_ns(); while (1) { if (tstc_raw()) { - kfifo_putc(console_input_fifo, getc_raw()); + int c = getc_raw(); + + if (c < 0) + break; + + kfifo_putc(console_input_fifo, c); start = get_time_ns(); } + if (is_timeout(start, 100 * USECOND) && kfifo_len(console_input_fifo)) break; } + if (!kfifo_len(console_input_fifo)) + return -1; + kfifo_getc(console_input_fifo, &ch); + return ch; } EXPORT_SYMBOL(getc); diff --git a/common/ratp.c b/common/ratp.c new file mode 100644 index 0000000000..2fef3cc0fe --- /dev/null +++ b/common/ratp.c @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2015 Sascha Hauer , Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "barebox-ratp: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BB_RATP_TYPE_COMMAND 1 +#define BB_RATP_TYPE_COMMAND_RETURN 2 +#define BB_RATP_TYPE_CONSOLEMSG 3 +#define BB_RATP_TYPE_PING 4 +#define BB_RATP_TYPE_PONG 5 +#define BB_RATP_TYPE_GETENV 6 +#define BB_RATP_TYPE_GETENV_RETURN 7 +#define BB_RATP_TYPE_FS 8 +#define BB_RATP_TYPE_FS_RETURN 9 + +struct ratp_bb { + uint16_t type; + uint16_t flags; + uint8_t data[]; +}; + +struct ratp_bb_command_return { + uint32_t errno; +}; + +struct ratp_ctx { + struct console_device *cdev; + struct ratp ratp; + int ratp_status; + struct console_device ratp_console; + int have_synch; + int in_ratp_console; + + u8 sendbuf[256]; + u8 sendbuf_len; + + int old_active; + + struct kfifo *console_recv_fifo; + struct kfifo *console_transmit_fifo; + + struct ratp_bb_pkt *fs_rx; + + struct poller_struct poller; +}; + +static int console_recv(struct ratp *r, uint8_t *data) +{ + struct ratp_ctx *ctx = container_of(r, struct ratp_ctx, ratp); + struct console_device *cdev = ctx->cdev; + + if (ctx->have_synch) { + ctx->have_synch = 0; + *data = 0x01; + return 0; + } + + if (!cdev->tstc(cdev)) + return -EAGAIN; + + *data = cdev->getc(cdev); + + return 0; +} + +static int console_send(struct ratp *r, void *pkt, int len) +{ + struct ratp_ctx *ctx = container_of(r, struct ratp_ctx, ratp); + struct console_device *cdev = ctx->cdev; + const uint8_t *buf = pkt; + int i; + + for (i = 0; i < len; i++) + cdev->putc(cdev, buf[i]); + + return 0; +} + +static void *xmemdup_add_zero(const void *buf, int len) +{ + void *ret; + + ret = xzalloc(len + 1); + *(uint8_t *)(ret + len) = 0; + memcpy(ret, buf, len); + + return ret; +} + +static void ratp_queue_console_tx(struct ratp_ctx *ctx) +{ + u8 buf[255]; + struct ratp_bb *rbb = (void *)buf; + unsigned int now, maxlen = 255 - sizeof(*rbb); + int ret; + + rbb->type = cpu_to_be16(BB_RATP_TYPE_CONSOLEMSG); + + while (1) { + now = min(maxlen, kfifo_len(ctx->console_transmit_fifo)); + if (!now) + break; + + kfifo_get(ctx->console_transmit_fifo, rbb->data, now); + + ret = ratp_send(&ctx->ratp, rbb, now + sizeof(*rbb)); + if (ret) + return; + } +} + +static int ratp_bb_send_command_return(struct ratp_ctx *ctx, uint32_t errno) +{ + void *buf; + struct ratp_bb *rbb; + struct ratp_bb_command_return *rbb_ret; + int len = sizeof(*rbb) + sizeof(*rbb_ret); + int ret; + + ratp_queue_console_tx(ctx); + + buf = xzalloc(len); + rbb = buf; + rbb_ret = buf + sizeof(*rbb); + + rbb->type = cpu_to_be16(BB_RATP_TYPE_COMMAND_RETURN); + rbb_ret->errno = cpu_to_be32(errno); + + ret = ratp_send(&ctx->ratp, buf, len); + + free(buf); + + return ret; +} + +static int ratp_bb_send_pong(struct ratp_ctx *ctx) +{ + void *buf; + struct ratp_bb *rbb; + int len = sizeof(*rbb); + int ret; + + buf = xzalloc(len); + rbb = buf; + + rbb->type = cpu_to_be16(BB_RATP_TYPE_PONG); + + ret = ratp_send(&ctx->ratp, buf, len); + + free(buf); + + return ret; +} + +static int ratp_bb_send_getenv_return(struct ratp_ctx *ctx, const char *val) +{ + void *buf; + struct ratp_bb *rbb; + int len, ret; + + if (!val) + val = ""; + + len = sizeof(*rbb) + strlen(val); + buf = xzalloc(len); + rbb = buf; + strcpy(rbb->data, val); + + rbb->type = cpu_to_be16(BB_RATP_TYPE_GETENV_RETURN); + + ret = ratp_send(&ctx->ratp, buf, len); + + free(buf); + + return ret; +} + +static char *ratp_command; +static struct ratp_ctx *ratp_command_ctx; + +static int ratp_bb_dispatch(struct ratp_ctx *ctx, const void *buf, int len) +{ + const struct ratp_bb *rbb = buf; + struct ratp_bb_pkt *pkt; + int dlen = len - sizeof(struct ratp_bb); + char *varname; + int ret = 0; + + switch (be16_to_cpu(rbb->type)) { + case BB_RATP_TYPE_COMMAND: + if (ratp_command) + return 0; + + ratp_command = xmemdup_add_zero(&rbb->data, dlen); + ratp_command_ctx = ctx; + pr_debug("got command: %s\n", ratp_command); + + break; + + case BB_RATP_TYPE_COMMAND_RETURN: + case BB_RATP_TYPE_PONG: + break; + + case BB_RATP_TYPE_CONSOLEMSG: + + kfifo_put(ctx->console_recv_fifo, rbb->data, dlen); + break; + + case BB_RATP_TYPE_PING: + ret = ratp_bb_send_pong(ctx); + break; + + case BB_RATP_TYPE_GETENV: + varname = xmemdup_add_zero(&rbb->data, dlen); + + ret = ratp_bb_send_getenv_return(ctx, getenv(varname)); + break; + + case BB_RATP_TYPE_FS_RETURN: + pkt = xzalloc(sizeof(*pkt) + dlen); + pkt->len = dlen; + memcpy(pkt->data, &rbb->data, dlen); + ctx->fs_rx = pkt; + break; + default: + printf("%s: unhandled packet type 0x%04x\n", __func__, be16_to_cpu(rbb->type)); + break; + } + + return ret; +} + +static int ratp_console_getc(struct console_device *cdev) +{ + struct ratp_ctx *ctx = container_of(cdev, struct ratp_ctx, ratp_console); + unsigned char c; + + if (!kfifo_len(ctx->console_recv_fifo)) + return -1; + + kfifo_getc(ctx->console_recv_fifo, &c); + + return c; +} + +static int ratp_console_tstc(struct console_device *cdev) +{ + struct ratp_ctx *ctx = container_of(cdev, struct ratp_ctx, ratp_console); + + return kfifo_len(ctx->console_recv_fifo) ? 1 : 0; +} + +static int ratp_console_puts(struct console_device *cdev, const char *s) +{ + struct ratp_ctx *ctx = container_of(cdev, struct ratp_ctx, ratp_console); + int len = 0; + + len = strlen(s); + + if (ratp_busy(&ctx->ratp)) + return len; + + kfifo_put(ctx->console_transmit_fifo, s, len); + + return len; +} + +static void ratp_console_putc(struct console_device *cdev, char c) +{ + struct ratp_ctx *ctx = container_of(cdev, struct ratp_ctx, ratp_console); + + if (ratp_busy(&ctx->ratp)) + return; + + kfifo_putc(ctx->console_transmit_fifo, c); +} + +static int ratp_console_register(struct ratp_ctx *ctx) +{ + int ret; + + ctx->ratp_console.tstc = ratp_console_tstc; + ctx->ratp_console.puts = ratp_console_puts; + ctx->ratp_console.putc = ratp_console_putc; + ctx->ratp_console.getc = ratp_console_getc; + ctx->ratp_console.devname = "ratpconsole"; + ctx->ratp_console.devid = DEVICE_ID_SINGLE; + + ret = console_register(&ctx->ratp_console); + if (ret) { + pr_err("registering failed with %s\n", strerror(-ret)); + return ret; + } + + return 0; +} + +void ratp_run_command(void) +{ + int ret; + + if (!ratp_command) + return; + + pr_debug("running command: %s\n", ratp_command); + + ret = run_command(ratp_command); + + free(ratp_command); + ratp_command = NULL; + + ratp_bb_send_command_return(ratp_command_ctx, ret); +} + +static const char *ratpfs_mount_path; + +int barebox_ratp_fs_mount(const char *path) +{ + if (path && ratpfs_mount_path) + return -EBUSY; + + ratpfs_mount_path = path; + + return 0; +} + +static void ratp_console_unregister(struct ratp_ctx *ctx) +{ + int ret; + + console_set_active(&ctx->ratp_console, 0); + poller_unregister(&ctx->poller); + ratp_close(&ctx->ratp); + console_set_active(ctx->cdev, ctx->old_active); + ctx->cdev = NULL; + + if (ratpfs_mount_path) { + ret = umount(ratpfs_mount_path); + if (!ret) + ratpfs_mount_path = NULL; + } +} + +static void ratp_poller(struct poller_struct *poller) +{ + struct ratp_ctx *ctx = container_of(poller, struct ratp_ctx, poller); + int ret; + size_t len; + void *buf; + + ratp_queue_console_tx(ctx); + + ret = ratp_poll(&ctx->ratp); + if (ret == -EINTR) + goto out; + if (ratp_closed(&ctx->ratp)) + goto out; + + ret = ratp_recv(&ctx->ratp, &buf, &len); + if (ret < 0) + return; + + ratp_bb_dispatch(ctx, buf, len); + + free(buf); + + return; + +out: + ratp_console_unregister(ctx); +} + +static int do_ratp_close(int argc, char *argv[]) +{ + if (ratp_command_ctx && ratp_command_ctx->cdev) + ratp_console_unregister(ratp_command_ctx); + else + printf("ratp is not active\n"); + + return 0; +} + +BAREBOX_CMD_START(ratp_close) + .cmd = do_ratp_close, +}; + +int barebox_ratp_fs_call(struct ratp_bb_pkt *tx, struct ratp_bb_pkt **rx) +{ + struct ratp_ctx *ctx = ratp_command_ctx; + struct ratp_bb *rbb; + int len; + u64 start; + + if (!ctx) + return -EINVAL; + + ctx->fs_rx = NULL; + + len = sizeof(*rbb) + tx->len; + rbb = xzalloc(len); + rbb->type = cpu_to_be16(BB_RATP_TYPE_FS); + memcpy(rbb->data, tx->data, tx->len); + + if (ratp_send(&ctx->ratp, rbb, len) != 0) + pr_debug("failed to send port pkt\n"); + + free(rbb); + + start = get_time_ns(); + + while (!ctx->fs_rx) { + poller_call(); + if (ratp_closed(&ctx->ratp)) + return -EIO; + if (is_timeout(start, 10 * SECOND)) + return -ETIMEDOUT; + } + + *rx = ctx->fs_rx; + + pr_debug("%s: len %i\n", __func__, ctx->fs_rx->len); + + return 0; +} + +int barebox_ratp(struct console_device *cdev) +{ + int ret; + struct ratp_ctx *ctx; + struct ratp *ratp; + + if (ratp_command_ctx) { + ctx = ratp_command_ctx; + } else { + ctx = xzalloc(sizeof(*ctx)); + ratp_command_ctx = ctx; + ctx->ratp.send = console_send; + ctx->ratp.recv = console_recv; + ctx->console_recv_fifo = kfifo_alloc(512); + ctx->console_transmit_fifo = kfifo_alloc(SZ_128K); + ctx->poller.func = ratp_poller; + ratp_console_register(ctx); + } + + if (ctx->cdev) + return -EBUSY; + + ratp = &ctx->ratp; + + ctx->old_active = console_get_active(cdev); + console_set_active(cdev, 0); + + ctx->cdev = cdev; + ctx->have_synch = 1; + + ret = ratp_establish(ratp, false, 100); + if (ret < 0) + goto out; + + ret = poller_register(&ctx->poller); + if (ret) + goto out1; + + console_set_active(&ctx->ratp_console, CONSOLE_STDOUT | CONSOLE_STDERR | + CONSOLE_STDIN); + + return 0; + +out1: + ratp_close(ratp); +out: + console_set_active(ctx->cdev, ctx->old_active); + ctx->cdev = NULL; + + return ret; +} + +static void barebox_ratp_close(void) +{ + if (ratp_command_ctx && ratp_command_ctx->cdev) + ratp_console_unregister(ratp_command_ctx); +} +predevshutdown_exitcall(barebox_ratp_close); \ No newline at end of file diff --git a/crypto/Kconfig b/crypto/Kconfig index 41145a312d..fcf92c9f84 100644 --- a/crypto/Kconfig +++ b/crypto/Kconfig @@ -4,6 +4,7 @@ config CRC32 bool config CRC16 + default y bool config CRC7 diff --git a/fs/Makefile b/fs/Makefile index 46932057c1..befbdf27f4 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_FS_UIMAGEFS) += uimagefs.o obj-$(CONFIG_FS_EFI) += efi.o obj-$(CONFIG_FS_EFIVARFS) += efivarfs.o obj-$(CONFIG_FS_SMHFS) += smhfs.o +obj-$(CONFIG_RATP) += ratpfs.o diff --git a/include/ratp.h b/include/ratp.h index b91d305a22..94fd004f45 100644 --- a/include/ratp.h +++ b/include/ratp.h @@ -19,4 +19,4 @@ bool ratp_busy(struct ratp *ratp); void ratp_run_command(void); -#endif /* __RATP_H */ +#endif /* __RATP_H */ \ No newline at end of file diff --git a/include/ratp_bb.h b/include/ratp_bb.h new file mode 100644 index 0000000000..52ecaff374 --- /dev/null +++ b/include/ratp_bb.h @@ -0,0 +1,15 @@ +#ifndef __RATP_BB_H +#define __RATP_BB_H + +struct ratp_bb_pkt { + struct list_head list; + + unsigned int len; + uint8_t data[]; +}; + +int barebox_ratp(struct console_device *cdev); +int barebox_ratp_fs_call(struct ratp_bb_pkt *tx, struct ratp_bb_pkt **rx); +int barebox_ratp_fs_mount(const char *path); + +#endif /* __RATP_BB_H */ diff --git a/lib/readline.c b/lib/readline.c index c007e10f50..a5dac08af1 100644 --- a/lib/readline.c +++ b/lib/readline.c @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -197,6 +199,12 @@ int readline(const char *prompt, char *buf, int len) puts (prompt); while (1) { + while (!tstc()) { + poller_call(); + if (IS_ENABLED(CONFIG_RATP)) + ratp_run_command(); + } + ichar = read_key(); if ((ichar == '\n') || (ichar == '\r')) { -- cgit v1.2.3 From ec95d547e721d2762c3cb66572528028f2a56483 Mon Sep 17 00:00:00 2001 From: Jan Luebbe Date: Thu, 7 Jan 2016 12:39:12 +0100 Subject: fs: Add RATP fs support This adds file transfer support over RATP. The host can export a directory using the bbremote tool which can then be mounted under barebox as a filesystem. Signed-off-by: Jan Luebbe Signed-off-by: Sascha Hauer Tested-by: Andrey Smirnov --- fs/Kconfig | 8 + fs/Makefile | 2 +- fs/ratpfs.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 fs/ratpfs.c diff --git a/fs/Kconfig b/fs/Kconfig index 9217bc81ea..9cfeb376a2 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -89,4 +89,12 @@ config FS_SMHFS located on a debugging host connected to the target running Barebox +config FS_RATP + bool + depends on CONSOLE_RATP + prompt "RATP filesystem support" + help + This enables support for transferring files over RATP. A host can + export a directory which can then be mounted under barebox. + endmenu diff --git a/fs/Makefile b/fs/Makefile index befbdf27f4..714acb4473 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -14,4 +14,4 @@ obj-$(CONFIG_FS_UIMAGEFS) += uimagefs.o obj-$(CONFIG_FS_EFI) += efi.o obj-$(CONFIG_FS_EFIVARFS) += efivarfs.o obj-$(CONFIG_FS_SMHFS) += smhfs.o -obj-$(CONFIG_RATP) += ratpfs.o +obj-$(CONFIG_FS_RATP) += ratpfs.o diff --git a/fs/ratpfs.c b/fs/ratpfs.c new file mode 100644 index 0000000000..902289bab1 --- /dev/null +++ b/fs/ratpfs.c @@ -0,0 +1,476 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#define pr_fmt(fmt) "barebox-ratpfs: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RATPFS_TYPE_MOUNT_CALL 1 +#define RATPFS_TYPE_MOUNT_RETURN 2 +#define RATPFS_TYPE_READDIR_CALL 3 +#define RATPFS_TYPE_READDIR_RETURN 4 +#define RATPFS_TYPE_STAT_CALL 5 +#define RATPFS_TYPE_STAT_RETURN 6 +#define RATPFS_TYPE_OPEN_CALL 7 +#define RATPFS_TYPE_OPEN_RETURN 8 +#define RATPFS_TYPE_READ_CALL 9 +#define RATPFS_TYPE_READ_RETURN 10 +#define RATPFS_TYPE_WRITE_CALL 11 +#define RATPFS_TYPE_WRITE_RETURN 12 +#define RATPFS_TYPE_CLOSE_CALL 13 +#define RATPFS_TYPE_CLOSE_RETURN 14 +#define RATPFS_TYPE_TRUNCATE_CALL 15 +#define RATPFS_TYPE_TRUNCATE_RETURN 16 + +struct ratpfs_file { + uint32_t handle; +}; + +struct ratpfs_dir { + char *entries; + int len, off; + DIR dir; +}; + +static int ratpfs_create(struct device_d __always_unused *dev, + const char __always_unused *pathname, + mode_t __always_unused mode) +{ + pr_debug("%s\n", __func__); + + return 0; +} + +static int ratpfs_mkdir(struct device_d __always_unused *dev, + const char __always_unused *pathname) +{ + pr_debug("%s\n", __func__); + + return -ENOSYS; +} + +static int ratpfs_rm(struct device_d __always_unused *dev, + const char *pathname) +{ + pr_debug("%s\n", __func__); + + /* Get rid of leading '/' */ + pathname = &pathname[1]; + + return 0; +} + +static int ratpfs_truncate(struct device_d __always_unused *dev, + FILE *f, ulong size) +{ + int len_tx = 1 /* type */ + + 4 /* handle */ + + 4 /* size */; + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx)+len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + struct ratpfs_file *rfile = f->priv; + int ret; + + pr_debug("%s: len_tx=%i handle=%i size=%i\n", __func__, + len_tx, rfile->handle, (int)size); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_TRUNCATE_CALL; + put_unaligned_be32(rfile->handle, &pkt_tx->data[1]); + put_unaligned_be32(size, &pkt_tx->data[5]); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) { + ret = -EIO; + goto out; + } + + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_TRUNCATE_RETURN) { + pr_err("invalid truncate response\n"); + ret = -EIO; + goto out; + } + +out: + free(pkt_rx); + return ret; +} + +static int ratpfs_open(struct device_d __always_unused *dev, + FILE *file, const char *filename) +{ + int len_name = strlen(filename); + int len_tx = 1 /* type */ + + 4 /* flags */ + + len_name /* path */; + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + struct ratpfs_file *rfile = xzalloc(sizeof(*rfile)); + int ret; + + pr_debug("%s: len_tx=%i filename='%s'\n", __func__, len_tx, filename); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_OPEN_CALL; + put_unaligned_be32(file->flags, &pkt_tx->data[1]); + memcpy(&pkt_tx->data[5], filename, len_name); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) { + ret = -EIO; + goto err; + } + + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_OPEN_RETURN) { + pr_err("invalid open response\n"); + ret = -EIO; + goto err; + } + rfile->handle = get_unaligned_be32(&pkt_rx->data[1]); + if (rfile->handle == 0) { + ret = -get_unaligned_be32(&pkt_rx->data[5]); /* errno */ + goto err; + } + file->priv = rfile; + file->size = get_unaligned_be32(&pkt_rx->data[5]); + + goto out; + +err: + file->priv = NULL; + free(rfile); +out: + free(pkt_rx); + return ret; +} + +static int ratpfs_close(struct device_d __always_unused *dev, + FILE *f) +{ + int len_tx = 1 /* type */ + + 4 /* handle */; + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + struct ratpfs_file *rfile = f->priv; + int ret; + + pr_debug("%s: len_tx=%i handle=%i\n", __func__, + len_tx, rfile->handle); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_CLOSE_CALL; + put_unaligned_be32(rfile->handle, &pkt_tx->data[1]); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) { + ret = -EIO; + goto out; + } + + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_CLOSE_RETURN) { + pr_err("invalid close response\n"); + goto out; + } + +out: + free(pkt_rx); + return ret; +} + +static int ratpfs_write(struct device_d __always_unused *dev, + FILE *f, const void *buf, size_t orig_size) +{ + int size = min((int)orig_size, 4096); + int len_tx = 1 /* type */ + + 4 /* handle */ + + 4 /* pos */ + + size /* data */; + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + struct ratpfs_file *rfile = f->priv; + int ret; + + pr_debug("%s: len_tx=%i handle=%i pos=%i size=%i\n", __func__, + len_tx, rfile->handle, (int)f->pos, size); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_WRITE_CALL; + put_unaligned_be32(rfile->handle, &pkt_tx->data[1]); + put_unaligned_be32(f->pos, &pkt_tx->data[5]); + memcpy(&pkt_tx->data[9], buf, size); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) { + ret = -EIO; + goto out; + } + + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_WRITE_RETURN) { + pr_err("invalid write response\n"); + ret = -EIO; + goto out; + } + + ret = size; +out: + free(pkt_rx); + + return ret; +} + +static int ratpfs_read(struct device_d __always_unused *dev, + FILE *f, void *buf, size_t orig_size) +{ + int size = min((int)orig_size, 4096); + int len_tx = 1 /* type */ + + 4 /* handle */ + + 4 /* pos */ + + 4 /* size */; + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + struct ratpfs_file *rfile = f->priv; + int ret; + + pr_debug("%s: len_tx=%i handle=%i pos=%i size=%i\n", __func__, + len_tx, rfile->handle, (int)f->pos, size); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_READ_CALL; + put_unaligned_be32(rfile->handle, &pkt_tx->data[1]); + put_unaligned_be32(f->pos, &pkt_tx->data[5]); + put_unaligned_be32(size, &pkt_tx->data[9]); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) { + ret = -EIO; + goto out; + } + + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_READ_RETURN) { + pr_err("invalid read response\n"); + ret = -EIO; + goto out; + } + size = pkt_rx->len - 1; + memcpy(buf, &pkt_rx->data[1], size); + ret = size; + +out: + free(pkt_rx); + return ret; +} + +static loff_t ratpfs_lseek(struct device_d __always_unused *dev, + FILE *f, loff_t pos) +{ + pr_debug("%s\n", __func__); + f->pos = pos; + return f->pos; +} + +static DIR* ratpfs_opendir(struct device_d __always_unused *dev, + const char *pathname) +{ + int len_name = strlen(pathname); + int len_tx = 1 /* type */ + + len_name /* path */; + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx)+len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + struct ratpfs_dir *rdir = xzalloc(sizeof(*rdir)); + int ret; + + pr_debug("%s: len_tx=%i pathname='%s'\n", __func__, len_tx, pathname); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_READDIR_CALL; + memcpy(&pkt_tx->data[1], pathname, len_name); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (!ret) { + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_READDIR_RETURN) { + pr_err("invalid readdir response\n"); + free(pkt_rx); + return NULL; + } + rdir->len = pkt_rx->len - 1; + rdir->entries = xmemdup(&pkt_rx->data[1], rdir->len); + free(pkt_rx); + return &rdir->dir; + } else { + return NULL; + } +} + +static struct dirent *ratpfs_readdir(struct device_d *dev, DIR *dir) +{ + struct ratpfs_dir *rdir = container_of(dir, struct ratpfs_dir, dir); + int i; + + pr_debug("%s\n", __func__); + + if (rdir->len <= rdir->off) + return NULL; + + for (i = 0; rdir->off < rdir->len; rdir->off++, i++) { + dir->d.d_name[i] = rdir->entries[rdir->off]; + if (dir->d.d_name[i] == 0) + break; + } + rdir->off++; + + return &dir->d; +} + +static int ratpfs_closedir(struct device_d *dev, DIR *dir) +{ + struct ratpfs_dir *rdir = container_of(dir, struct ratpfs_dir, dir); + + pr_debug("%s\n", __func__); + + free(rdir->entries); + free(rdir); + + return 0; +} + +static int ratpfs_stat(struct device_d __always_unused *dev, + const char *filename, struct stat *s) +{ + int len_name = strlen(filename); + int len_tx = 1 /* type */ + + len_name; /* path */ + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + int ret; + + pr_debug("%s: len_tx=%i filename='%s'\n", __func__, len_tx, filename); + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_STAT_CALL; + memcpy(&pkt_tx->data[1], filename, len_name); + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) { + ret = -EIO; + goto out; + } + + pr_debug("%s: len_rx=%i\n", __func__, pkt_rx->len); + if (pkt_rx->len < 6 || pkt_rx->data[0] != RATPFS_TYPE_STAT_RETURN) { + pr_err("invalid stat response\n"); + goto out; + } + switch (pkt_rx->data[1]) { + case 0: + ret = -ENOENT; + break; + case 1: + s->st_mode = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; + break; + case 2: + s->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; + break; + } + s->st_size = get_unaligned_be32(&pkt_rx->data[2]); + +out: + free(pkt_rx); + return ret; +} + +static int ratpfs_probe(struct device_d *dev) +{ + int len_tx = 1; /* type */ + struct ratp_bb_pkt *pkt_tx = xzalloc(sizeof(*pkt_tx) + len_tx); + struct ratp_bb_pkt *pkt_rx = NULL; + int ret; + struct fs_device_d *fsdev = dev_to_fs_device(dev); + + pr_debug("%s\n", __func__); + + ret = barebox_ratp_fs_mount(fsdev->path); + if (ret) + return ret; + + pkt_tx->len = len_tx; + pkt_tx->data[0] = RATPFS_TYPE_MOUNT_CALL; + + ret = barebox_ratp_fs_call(pkt_tx, &pkt_rx); + if (ret) + goto out; + + if (pkt_rx->len < 1 || pkt_rx->data[0] != RATPFS_TYPE_MOUNT_RETURN) { + pr_err("invalid mount response\n"); + ret = -EINVAL; + goto out; + } + +out: + free(pkt_rx); + + if (ret) + barebox_ratp_fs_mount(NULL); + + return ret; +} + +static void ratpfs_remove(struct device_d __always_unused *dev) +{ + pr_debug("%s\n", __func__); + + barebox_ratp_fs_mount(NULL); +} + +static struct fs_driver_d ratpfs_driver = { + .open = ratpfs_open, + .close = ratpfs_close, + .read = ratpfs_read, + .lseek = ratpfs_lseek, + .opendir = ratpfs_opendir, + .readdir = ratpfs_readdir, + .closedir = ratpfs_closedir, + .stat = ratpfs_stat, + .create = ratpfs_create, + .unlink = ratpfs_rm, + .mkdir = ratpfs_mkdir, + .rmdir = ratpfs_rm, + .write = ratpfs_write, + .truncate = ratpfs_truncate, + .flags = FS_DRIVER_NO_DEV, + .drv = { + .probe = ratpfs_probe, + .remove = ratpfs_remove, + .name = "ratpfs", + } +}; + +static int ratpfs_init(void) +{ + return register_fs_driver(&ratpfs_driver); +} +coredevice_initcall(ratpfs_init); \ No newline at end of file -- cgit v1.2.3 From 80caf8ac4300c36f21479f38699ac665082d9b39 Mon Sep 17 00:00:00 2001 From: Jan Luebbe Date: Mon, 8 Jun 2015 12:09:34 +0200 Subject: include pyserial trunk The current pyserial is broken, this version contains the fix for: http://sourceforge.net/p/pyserial/bugs/166/ Signed-off-by: Jan Luebbe --- .gitignore | 1 + scripts/serial/__init__.py | 79 ++ scripts/serial/rfc2217.py | 1327 +++++++++++++++++++++++++ scripts/serial/serialcli.py | 284 ++++++ scripts/serial/serialposix.py | 730 ++++++++++++++ scripts/serial/serialutil.py | 572 +++++++++++ scripts/serial/tools/__init__.py | 0 scripts/serial/tools/list_ports.py | 103 ++ scripts/serial/tools/list_ports_linux.py | 152 +++ scripts/serial/urlhandler/__init__.py | 0 scripts/serial/urlhandler/protocol_hwgrep.py | 45 + scripts/serial/urlhandler/protocol_loop.py | 279 ++++++ scripts/serial/urlhandler/protocol_rfc2217.py | 11 + scripts/serial/urlhandler/protocol_socket.py | 291 ++++++ 14 files changed, 3874 insertions(+) create mode 100644 scripts/serial/__init__.py create mode 100644 scripts/serial/rfc2217.py create mode 100644 scripts/serial/serialcli.py create mode 100644 scripts/serial/serialposix.py create mode 100644 scripts/serial/serialutil.py create mode 100644 scripts/serial/tools/__init__.py create mode 100644 scripts/serial/tools/list_ports.py create mode 100644 scripts/serial/tools/list_ports_linux.py create mode 100644 scripts/serial/urlhandler/__init__.py create mode 100644 scripts/serial/urlhandler/protocol_hwgrep.py create mode 100644 scripts/serial/urlhandler/protocol_loop.py create mode 100644 scripts/serial/urlhandler/protocol_rfc2217.py create mode 100644 scripts/serial/urlhandler/protocol_socket.py diff --git a/.gitignore b/.gitignore index ce2be8ac04..bbcfa22447 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ *.symtypes *.elf *.patch +*.pyc *.mcp *.bct *.dcd diff --git a/scripts/serial/__init__.py b/scripts/serial/__init__.py new file mode 100644 index 0000000000..33ae52ec11 --- /dev/null +++ b/scripts/serial/__init__.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +# portable serial port access with python +# this is a wrapper module for different platform implementations +# +# (C) 2001-2010 Chris Liechti +# this is distributed under a free software license, see license.txt + +VERSION = '2.7' + +import sys + +if sys.platform == 'cli': + from serial.serialcli import * +else: + import os + # chose an implementation, depending on os + if os.name == 'nt': #sys.platform == 'win32': + from serial.serialwin32 import * + elif os.name == 'posix': + from serial.serialposix import * + elif os.name == 'java': + from serial.serialjava import * + else: + raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,)) + + +protocol_handler_packages = [ + 'serial.urlhandler', + ] + +def serial_for_url(url, *args, **kwargs): + """\ + Get an instance of the Serial class, depending on port/url. The port is not + opened when the keyword parameter 'do_not_open' is true, by default it + is. All other parameters are directly passed to the __init__ method when + the port is instantiated. + + The list of package names that is searched for protocol handlers is kept in + ``protocol_handler_packages``. + + e.g. we want to support a URL ``foobar://``. A module + ``my_handlers.protocol_foobar`` is provided by the user. Then + ``protocol_handler_packages.append("my_handlers")`` would extend the search + path so that ``serial_for_url("foobar://"))`` would work. + """ + # check remove extra parameter to not confuse the Serial class + do_open = 'do_not_open' not in kwargs or not kwargs['do_not_open'] + if 'do_not_open' in kwargs: del kwargs['do_not_open'] + # the default is to use the native version + klass = Serial # 'native' implementation + # check port type and get class + try: + url_nocase = url.lower() + except AttributeError: + # it's not a string, use default + pass + else: + if '://' in url_nocase: + protocol = url_nocase.split('://', 1)[0] + for package_name in protocol_handler_packages: + module_name = '%s.protocol_%s' % (package_name, protocol,) + try: + handler_module = __import__(module_name) + except ImportError: + pass + else: + klass = sys.modules[module_name].Serial + break + else: + raise ValueError('invalid URL, protocol %r not known' % (protocol,)) + else: + klass = Serial # 'native' implementation + # instantiate and open when desired + instance = klass(None, *args, **kwargs) + instance.port = url + if do_open: + instance.open() + return instance diff --git a/scripts/serial/rfc2217.py b/scripts/serial/rfc2217.py new file mode 100644 index 0000000000..4fe1a72f19 --- /dev/null +++ b/scripts/serial/rfc2217.py @@ -0,0 +1,1327 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a RFC2217 compatible client. RF2217 descibes a +# protocol to access serial ports over TCP/IP and allows setting the baud rate, +# modem control lines etc. +# +# (C) 2001-2013 Chris Liechti +# this is distributed under a free software license, see license.txt + +# TODO: +# - setting control line -> answer is not checked (had problems with one of the +# severs). consider implementing a compatibility mode flag to make check +# conditional +# - write timeout not implemented at all + +############################################################################## +# observations and issues with servers +#============================================================================= +# sredird V2.2.1 +# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz +# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding +# [105 1] instead of the actual value. +# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger +# numbers than 2**32? +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done +#============================================================================= +# telnetcpcd (untested) +# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz +# - To get the signature [COM_PORT_OPTION] w/o data has to be sent. +#============================================================================= +# ser2net +# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least +# acknowledges that the client activates these options +# - The configuration may be that the server prints a banner. As this client +# implementation does a flushInput on connect, this banner is hidden from +# the user application. +# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one +# second. +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: run ser2net daemon, in /etc/ser2net.conf: +# 2000:telnet:0:/dev/ttyS0:9600 remctl banner +############################################################################## + +# How to identify ports? pySerial might want to support other protocols in the +# future, so lets use an URL scheme. +# for RFC2217 compliant servers we will use this: +# rfc2217://:[/option[/option...]] +# +# options: +# - "debug" print diagnostic messages +# - "ign_set_control": do not look at the answers to SET_CONTROL +# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read. +# Without this option it expects that the server sends notifications +# automatically on change (which most servers do and is according to the +# RFC). +# the order of the options is not relevant + +from serial.serialutil import * +import time +import struct +import socket +import threading +import Queue +import logging + +# port string is expected to be something like this: +# rfc2217://host:port +# host may be an IP or including domain, whatever. +# port is 0...65535 + +# map log level names to constants. used in fromURL() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + } + + +# telnet protocol characters +IAC = to_bytes([255]) # Interpret As Command +DONT = to_bytes([254]) +DO = to_bytes([253]) +WONT = to_bytes([252]) +WILL = to_bytes([251]) +IAC_DOUBLED = to_bytes([IAC, IAC]) + +SE = to_bytes([240]) # Subnegotiation End +NOP = to_bytes([241]) # No Operation +DM = to_bytes([242]) # Data Mark +BRK = to_bytes([243]) # Break +IP = to_bytes([244]) # Interrupt process +AO = to_bytes([245]) # Abort output +AYT = to_bytes([246]) # Are You There +EC = to_bytes([247]) # Erase Character +EL = to_bytes([248]) # Erase Line +GA = to_bytes([249]) # Go Ahead +SB = to_bytes([250]) # Subnegotiation Begin + +# selected telnet options +BINARY = to_bytes([0]) # 8-bit data path +ECHO = to_bytes([1]) # echo +SGA = to_bytes([3]) # suppress go ahead + +# RFC2217 +COM_PORT_OPTION = to_bytes([44]) + +# Client to Access Server +SET_BAUDRATE = to_bytes([1]) +SET_DATASIZE = to_bytes([2]) +SET_PARITY = to_bytes([3]) +SET_STOPSIZE = to_bytes([4]) +SET_CONTROL = to_bytes([5]) +NOTIFY_LINESTATE = to_bytes([6]) +NOTIFY_MODEMSTATE = to_bytes([7]) +FLOWCONTROL_SUSPEND = to_bytes([8]) +FLOWCONTROL_RESUME = to_bytes([9]) +SET_LINESTATE_MASK = to_bytes([10]) +SET_MODEMSTATE_MASK = to_bytes([11]) +PURGE_DATA = to_bytes([12]) + +SERVER_SET_BAUDRATE = to_bytes([101]) +SERVER_SET_DATASIZE = to_bytes([102]) +SERVER_SET_PARITY = to_bytes([103]) +SERVER_SET_STOPSIZE = to_bytes([104]) +SERVER_SET_CONTROL = to_bytes([105]) +SERVER_NOTIFY_LINESTATE = to_bytes([106]) +SERVER_NOTIFY_MODEMSTATE = to_bytes([107]) +SERVER_FLOWCONTROL_SUSPEND = to_bytes([108]) +SERVER_FLOWCONTROL_RESUME = to_bytes([109]) +SERVER_SET_LINESTATE_MASK = to_bytes([110]) +SERVER_SET_MODEMSTATE_MASK = to_bytes([111]) +SERVER_PURGE_DATA = to_bytes([112]) + +RFC2217_ANSWER_MAP = { + SET_BAUDRATE: SERVER_SET_BAUDRATE, + SET_DATASIZE: SERVER_SET_DATASIZE, + SET_PARITY: SERVER_SET_PARITY, + SET_STOPSIZE: SERVER_SET_STOPSIZE, + SET_CONTROL: SERVER_SET_CONTROL, + NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE, + NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE, + FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND, + FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME, + SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK, + SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK, + PURGE_DATA: SERVER_PURGE_DATA, +} + +SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both) +SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both) +SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both) +SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both) +SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State +SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON +SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF +SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State +SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON +SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF +SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State +SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON +SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF +SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound) +SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound) +SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound) +SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound) +SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both) +SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound) +SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both) + +LINESTATE_MASK_TIMEOUT = 128 # Time-out Error +LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty +LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty +LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error +LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error +LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error +LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error +LINESTATE_MASK_DATA_READY = 1 # Data Ready + +MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect) +MODEMSTATE_MASK_RI = 64 # Ring Indicator +MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State +MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State +MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect +MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector +MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready +MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send + +PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer +PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer +PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer + + +RFC2217_PARITY_MAP = { + PARITY_NONE: 1, + PARITY_ODD: 2, + PARITY_EVEN: 3, + PARITY_MARK: 4, + PARITY_SPACE: 5, +} +RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items()) + +RFC2217_STOPBIT_MAP = { + STOPBITS_ONE: 1, + STOPBITS_ONE_POINT_FIVE: 3, + STOPBITS_TWO: 2, +} +RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items()) + +# Telnet filter states +M_NORMAL = 0 +M_IAC_SEEN = 1 +M_NEGOTIATE = 2 + +# TelnetOption and TelnetSubnegotiation states +REQUESTED = 'REQUESTED' +ACTIVE = 'ACTIVE' +INACTIVE = 'INACTIVE' +REALLY_INACTIVE = 'REALLY_INACTIVE' + +class TelnetOption(object): + """Manage a single telnet option, keeps track of DO/DONT WILL/WONT.""" + + def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None): + """\ + Initialize option. + :param connection: connection used to transmit answers + :param name: a readable name for debug outputs + :param send_yes: what to send when option is to be enabled. + :param send_no: what to send when option is to be disabled. + :param ack_yes: what to expect when remote agrees on option. + :param ack_no: what to expect when remote disagrees on option. + :param initial_state: options initialized with REQUESTED are tried to + be enabled on startup. use INACTIVE for all others. + """ + self.connection = connection + self.name = name + self.option = option + self.send_yes = send_yes + self.send_no = send_no + self.ack_yes = ack_yes + self.ack_no = ack_no + self.state = initial_state + self.active = False + self.activation_callback = activation_callback + + def __repr__(self): + """String for debug outputs""" + return "%s:%s(%s)" % (self.name, self.active, self.state) + + def process_incoming(self, command): + """\ + A DO/DONT/WILL/WONT was received for this option, update state and + answer when needed. + """ + if command == self.ack_yes: + if self.state is REQUESTED: + self.state = ACTIVE + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is ACTIVE: + pass + elif self.state is INACTIVE: + self.state = ACTIVE + self.connection.telnetSendOption(self.send_yes, self.option) + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is REALLY_INACTIVE: + self.connection.telnetSendOption(self.send_no, self.option) + else: + raise ValueError('option in illegal state %r' % self) + elif command == self.ack_no: + if self.state is REQUESTED: + self.state = INACTIVE + self.active = False + elif self.state is ACTIVE: + self.state = INACTIVE + self.connection.telnetSendOption(self.send_no, self.option) + self.active = False + elif self.state is INACTIVE: + pass + elif self.state is REALLY_INACTIVE: + pass + else: + raise ValueError('option in illegal state %r' % self) + + +class TelnetSubnegotiation(object): + """\ + A object to handle subnegotiation of options. In this case actually + sub-sub options for RFC 2217. It is used to track com port options. + """ + + def __init__(self, connection, name, option, ack_option=None): + if ack_option is None: ack_option = option + self.connection = connection + self.name = name + self.option = option + self.value = None + self.ack_option = ack_option + self.state = INACTIVE + + def __repr__(self): + """String for debug outputs.""" + return "%s:%s" % (self.name, self.state) + + def set(self, value): + """\ + Request a change of the value. a request is sent to the server. if + the client needs to know if the change is performed he has to check the + state of this object. + """ + self.value = value + self.state = REQUESTED + self.connection.rfc2217SendSubnegotiation(self.option, self.value) + if self.connection.logger: + self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value)) + + def isReady(self): + """\ + Check if answer from server has been received. when server rejects + the change, raise a ValueError. + """ + if self.state == REALLY_INACTIVE: + raise ValueError("remote rejected value for option %r" % (self.name)) + return self.state == ACTIVE + # add property to have a similar interface as TelnetOption + active = property(isReady) + + def wait(self, timeout=3): + """\ + Wait until the subnegotiation has been acknowledged or timeout. It + can also throw a value error when the answer from the server does not + match the value sent. + """ + timeout_time = time.time() + timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + if self.isReady(): + break + else: + raise SerialException("timeout while waiting for option %r" % (self.name)) + + def checkAnswer(self, suboption): + """\ + Check an incoming subnegotiation block. The parameter already has + cut off the header like sub option number and com port option value. + """ + if self.value == suboption[:len(self.value)]: + self.state = ACTIVE + else: + # error propagation done in isReady + self.state = REALLY_INACTIVE + if self.connection.logger: + self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state)) + + +class RFC2217Serial(SerialBase): + """Serial port implementation for RFC 2217 remote serial ports.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + self.logger = None + self._ignore_set_control_answer = False + self._poll_modem_state = False + self._network_timeout = 3 + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self._isOpen: + raise SerialException("Port is already open.") + try: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect(self.fromURL(self.portstr)) + self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception, msg: + self._socket = None + raise SerialException("Could not open port %s: %s" % (self.portstr, msg)) + + self._socket.settimeout(5) # XXX good value? + + # use a thread save queue as buffer. it also simplifies implementing + # the read timeout + self._read_buffer = Queue.Queue() + # to ensure that user writes does not interfere with internal + # telnet/rfc2217 options establish a lock + self._write_lock = threading.Lock() + # name the following separately so that, below, a check can be easily done + mandadory_options = [ + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED), + ] + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED), + ] + mandadory_options + # RFC 2217 specific states + # COM port settings + self._rfc2217_port_settings = { + 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE), + 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE), + 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY), + 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE), + } + # There are more subnegotiation objects, combine all in one dictionary + # for easy access + self._rfc2217_options = { + 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA), + 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL), + } + self._rfc2217_options.update(self._rfc2217_port_settings) + # cache for line and modem states that the server sends to us + self._linestate = 0 + self._modemstate = None + self._modemstate_expires = 0 + # RFC 2217 flow control between server and client + self._remote_suspend_flow = False + + self._thread = threading.Thread(target=self._telnetReadLoop) + self._thread.setDaemon(True) + self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,)) + self._thread.start() + + # negotiate Telnet/RFC 2217 -> send initial requests + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnetSendOption(option.send_yes, option.option) + # now wait until important options are negotiated + timeout_time = time.time() + self._network_timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options): + break + else: + raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options) + if self.logger: + self.logger.info("Negotiated options: %s" % self._telnet_options) + + # fine, go on, set RFC 2271 specific things + self._reconfigurePort() + # all things set up get, now a clean start + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if self._socket is None: + raise SerialException("Can only operate on open ports") + + # if self._timeout != 0 and self._interCharTimeout is not None: + # XXX + + if self._writeTimeout is not None: + raise NotImplementedError('writeTimeout is currently not supported') + # XXX + + # Setup the connection + # to get good performance, all parameter changes are sent first... + if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32: + raise ValueError("invalid baudrate: %r" % (self._baudrate)) + self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate)) + self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize)) + self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity])) + self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits])) + + # and now wait until parameters are active + items = self._rfc2217_port_settings.values() + if self.logger: + self.logger.debug("Negotiating settings: %s" % (items,)) + timeout_time = time.time() + self._network_timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in items) == len(items): + break + else: + raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items) + if self.logger: + self.logger.info("Negotiated settings: %s" % (items,)) + + if self._rtscts and self._xonxoff: + raise ValueError('xonxoff and rtscts together are not supported') + elif self._rtscts: + self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL) + elif self._xonxoff: + self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL) + else: + self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL) + + def close(self): + """Close port""" + if self._isOpen: + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + # ignore errors. + pass + self._socket = None + if self._thread: + self._thread.join() + self._isOpen = False + # in case of quick reconnects, give the server some time + time.sleep(0.3) + + def makeDeviceName(self, port): + raise SerialException("there is no sensible way to turn numbers into URLs") + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("rfc2217://"): url = url[10:] + try: + # is there a "path" (our options)? + if '/' in url: + # cut away options + url, options = url.split('/', 1) + # process options now, directly altering self + for option in options.split('/'): + if '=' in option: + option, value = option.split('=', 1) + else: + value = None + if option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.rfc2217') + self.logger.setLevel(LOGGER_LEVELS[value]) + self.logger.debug('enabled logging') + elif option == 'ign_set_control': + self._ignore_set_control_answer = True + elif option == 'poll_modem': + self._poll_modem_state = True + elif option == 'timeout': + self._network_timeout = float(value) + else: + raise ValueError('unknown option: %r' % (option,)) + # get host and port + host, port = url.split(':', 1) # may raise ValueError because of unpacking + port = int(port) # and this if it's not a number + if not 0 <= port < 65536: raise ValueError("port not in range 0...65535") + except ValueError, e: + raise SerialException('expected a string in the form "[rfc2217://]:[/option[/option...]]": %s' % e) + return (host, port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._isOpen: raise portNotOpenError + return self._read_buffer.qsize() + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self._isOpen: raise portNotOpenError + data = bytearray() + try: + while len(data) < size: + if self._thread is None: + raise SerialException('connection failed (reader thread died)') + data.append(self._read_buffer.get(True, self._timeout)) + except Queue.Empty: # -> timeout + pass + return bytes(data) + + def write(self, data): + """\ + Output the given string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed. + """ + if not self._isOpen: raise portNotOpenError + self._write_lock.acquire() + try: + try: + self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED)) + except socket.error, e: + raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails + finally: + self._write_lock.release() + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER) + # empty read buffer + while self._read_buffer.qsize(): + self._read_buffer.get(False) + + def flushOutput(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self._isOpen: raise portNotOpenError + self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER) + + def sendBreak(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self._isOpen: raise portNotOpenError + self.setBreak(True) + time.sleep(duration) + self.setBreak(False) + + def setBreak(self, level=True): + """\ + Set break: Controls TXD. When active, to transmitting is + possible. + """ + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)]) + if level: + self.rfc2217SetControl(SET_CONTROL_BREAK_ON) + else: + self.rfc2217SetControl(SET_CONTROL_BREAK_OFF) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)]) + if level: + self.rfc2217SetControl(SET_CONTROL_RTS_ON) + else: + self.rfc2217SetControl(SET_CONTROL_RTS_OFF) + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)]) + if level: + self.rfc2217SetControl(SET_CONTROL_DTR_ON) + else: + self.rfc2217SetControl(SET_CONTROL_DTR_OFF) + + def getCTS(self): + """Read terminal status line: Clear To Send.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_CTS) + + def getDSR(self): + """Read terminal status line: Data Set Ready.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_DSR) + + def getRI(self): + """Read terminal status line: Ring Indicator.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_RI) + + def getCD(self): + """Read terminal status line: Carrier Detect.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_CD) + + # - - - platform specific - - - + # None so far + + # - - - RFC2217 specific - - - + + def _telnetReadLoop(self): + """Read loop for the socket.""" + mode = M_NORMAL + suboption = None + try: + while self._socket is not None: + try: + data = self._socket.recv(1024) + except socket.timeout: + # just need to get out of recv form time to time to check if + # still alive + continue + except socket.error, e: + # connection fails -> terminate loop + if self.logger: + self.logger.debug("socket error in reader thread: %s" % (e,)) + break + if not data: break # lost connection + for byte in data: + if mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + mode = M_IAC_SEEN + else: + # store data in read buffer or sub option buffer + # depending on state + if suboption is not None: + suboption.append(byte) + else: + self._read_buffer.put(byte) + elif mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if suboption is not None: + suboption.append(IAC) + else: + self._read_buffer.put(IAC) + mode = M_NORMAL + elif byte == SB: + # sub option start + suboption = bytearray() + mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnetProcessSubnegotiation(bytes(suboption)) + suboption = None + mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + telnet_command = byte + mode = M_NEGOTIATE + else: + # other telnet commands + self._telnetProcessCommand(byte) + mode = M_NORMAL + elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnetNegotiateOption(telnet_command, byte) + mode = M_NORMAL + finally: + self._thread = None + if self.logger: + self.logger.debug("read thread terminated") + + # - incoming telnet commands and options + + def _telnetProcessCommand(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: %r" % (command,)) + + def _telnetNegotiateOption(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnetSendOption((command == WILL and DONT or WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: %r" % (option,)) + + + def _telnetProcessSubnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3: + self._linestate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate) + elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3: + self._modemstate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate) + # update time when we think that a poll would make sense + self._modemstate_expires = time.time() + 0.3 + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + self._remote_suspend_flow = False + else: + for item in self._rfc2217_options.values(): + if item.ack_option == suboption[1:2]: + #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:]) + item.checkAnswer(bytes(suboption[2:])) + break + else: + if self.logger: + self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,)) + else: + if self.logger: + self.logger.warning("ignoring subnegotiation: %r" % (suboption,)) + + # - outgoing telnet commands and options + + def _internal_raw_write(self, data): + """internal socket write with no data escaping. used to send telnet stuff.""" + self._write_lock.acquire() + try: + self._socket.sendall(data) + finally: + self._write_lock.release() + + def telnetSendOption(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self._internal_raw_write(to_bytes([IAC, action, option])) + + def rfc2217SendSubnegotiation(self, option, value=''): + """Subnegotiation of RFC2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE])) + + def rfc2217SendPurge(self, value): + item = self._rfc2217_options['purge'] + item.set(value) # transmit desired purge type + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217SetControl(self, value): + item = self._rfc2217_options['control'] + item.set(value) # transmit desired control type + if self._ignore_set_control_answer: + # answers are ignored when option is set. compatibility mode for + # servers that answer, but not the expected one... (or no answer + # at all) i.e. sredird + time.sleep(0.1) # this helps getting the unit tests passed + else: + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217FlowServerReady(self): + """\ + check if server is ready to receive data. block for some time when + not. + """ + #~ if self._remote_suspend_flow: + #~ wait--- + + def getModemState(self): + """\ + get last modem state (cached value. If value is "old", request a new + one. This cache helps that we don't issue to many requests when e.g. all + status lines, one after the other is queried by the user (getCTS, getDSR + etc.) + """ + # active modem state polling enabled? is the value fresh enough? + if self._poll_modem_state and self._modemstate_expires < time.time(): + if self.logger: + self.logger.debug('polling modem state') + # when it is older, request an update + self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE) + timeout_time = time.time() + self._network_timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + # when expiration time is updated, it means that there is a new + # value + if self._modemstate_expires > time.time(): + if self.logger: + self.logger.warning('poll for modem state failed') + break + # even when there is a timeout, do not generate an error just + # return the last known value. this way we can support buggy + # servers that do not respond to polls, but send automatic + # updates. + if self._modemstate is not None: + if self.logger: + self.logger.debug('using cached modem state') + return self._modemstate + else: + # never received a notification from the server + raise SerialException("remote sends no NOTIFY_MODEMSTATE") + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(RFC2217Serial, FileLike): + pass +else: + # io library present + class Serial(RFC2217Serial, io.RawIOBase): + pass + + +############################################################################# +# The following is code that helps implementing an RFC 2217 server. + +class PortManager(object): + """\ + This class manages the state of Telnet and RFC 2217. It needs a serial + instance and a connection to work with. Connection is expected to implement + a (thread safe) write function, that writes the string to the network. + """ + + def __init__(self, serial_port, connection, logger=None): + self.serial = serial_port + self.connection = connection + self.logger = logger + self._client_is_rfc2217 = False + + # filter state machine + self.mode = M_NORMAL + self.suboption = None + self.telnet_command = None + + # states for modem/line control events + self.modemstate_mask = 255 + self.last_modemstate = None + self.linstate_mask = 0 + + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok), + ] + + # negotiate Telnet/RFC2217 -> send initial requests + if self.logger: + self.logger.debug("requesting initial Telnet/RFC 2217 options") + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnetSendOption(option.send_yes, option.option) + # issue 1st modem state notification + + def _client_ok(self): + """\ + callback of telnet option. It gets called when option is activated. + This one here is used to detect when the client agrees on RFC 2217. A + flag is set so that other functions like check_modem_lines know if the + client is OK. + """ + # The callback is used for we and they so if one party agrees, we're + # already happy. it seems not all servers do the negotiation correctly + # and i guess there are incorrect clients too.. so be happy if client + # answers one or the other positively. + self._client_is_rfc2217 = True + if self.logger: + self.logger.info("client accepts RFC 2217") + # this is to ensure that the client gets a notification, even if there + # was no change + self.check_modem_lines(force_notification=True) + + # - outgoing telnet commands and options + + def telnetSendOption(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self.connection.write(to_bytes([IAC, action, option])) + + def rfc2217SendSubnegotiation(self, option, value=''): + """Subnegotiation of RFC 2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE])) + + # - check modem lines, needs to be called periodically from user to + # establish polling + + def check_modem_lines(self, force_notification=False): + modemstate = ( + (self.serial.getCTS() and MODEMSTATE_MASK_CTS) | + (self.serial.getDSR() and MODEMSTATE_MASK_DSR) | + (self.serial.getRI() and MODEMSTATE_MASK_RI) | + (self.serial.getCD() and MODEMSTATE_MASK_CD) + ) + # check what has changed + deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0 + if deltas & MODEMSTATE_MASK_CTS: + modemstate |= MODEMSTATE_MASK_CTS_CHANGE + if deltas & MODEMSTATE_MASK_DSR: + modemstate |= MODEMSTATE_MASK_DSR_CHANGE + if deltas & MODEMSTATE_MASK_RI: + modemstate |= MODEMSTATE_MASK_RI_CHANGE + if deltas & MODEMSTATE_MASK_CD: + modemstate |= MODEMSTATE_MASK_CD_CHANGE + # if new state is different and the mask allows this change, send + # notification. suppress notifications when client is not rfc2217 + if modemstate != self.last_modemstate or force_notification: + if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification: + self.rfc2217SendSubnegotiation( + SERVER_NOTIFY_MODEMSTATE, + to_bytes([modemstate & self.modemstate_mask]) + ) + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,)) + # save last state, but forget about deltas. + # otherwise it would also notify about changing deltas which is + # probably not very useful + self.last_modemstate = modemstate & 0xf0 + + # - outgoing data escaping + + def escape(self, data): + """\ + This generator function is for the user. All outgoing data has to be + properly escaped, so that no IAC character in the data stream messes up + the Telnet state machine in the server. + + socket.sendall(escape(data)) + """ + for byte in data: + if byte == IAC: + yield IAC + yield IAC + else: + yield byte + + # - incoming data filter + + def filter(self, data): + """\ + Handle a bunch of incoming bytes. This is a generator. It will yield + all characters not of interest for Telnet/RFC 2217. + + The idea is that the reader thread pushes data from the socket through + this filter: + + for byte in filter(socket.recv(1024)): + # do things like CR/LF conversion/whatever + # and write data to the serial port + serial.write(byte) + + (socket error handling code left as exercise for the reader) + """ + for byte in data: + if self.mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + self.mode = M_IAC_SEEN + else: + # store data in sub option buffer or pass it to our + # consumer depending on state + if self.suboption is not None: + self.suboption.append(byte) + else: + yield byte + elif self.mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if self.suboption is not None: + self.suboption.append(byte) + else: + yield byte + self.mode = M_NORMAL + elif byte == SB: + # sub option start + self.suboption = bytearray() + self.mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnetProcessSubnegotiation(bytes(self.suboption)) + self.suboption = None + self.mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + self.telnet_command = byte + self.mode = M_NEGOTIATE + else: + # other telnet commands + self._telnetProcessCommand(byte) + self.mode = M_NORMAL + elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnetNegotiateOption(self.telnet_command, byte) + self.mode = M_NORMAL + + # - incoming telnet commands and options + + def _telnetProcessCommand(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: %r" % (command,)) + + def _telnetNegotiateOption(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnetSendOption((command == WILL and DONT or WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: %r" % (option,)) + + + def _telnetProcessSubnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if self.logger: + self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,)) + if suboption[1:2] == SET_BAUDRATE: + backup = self.serial.baudrate + try: + (baudrate,) = struct.unpack("!I", suboption[2:6]) + if baudrate != 0: + self.serial.baudrate = baudrate + except ValueError, e: + if self.logger: + self.logger.error("failed to set baud rate: %s" % (e,)) + self.serial.baudrate = backup + else: + if self.logger: + self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate)) + self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate)) + elif suboption[1:2] == SET_DATASIZE: + backup = self.serial.bytesize + try: + (datasize,) = struct.unpack("!B", suboption[2:3]) + if datasize != 0: + self.serial.bytesize = datasize + except ValueError, e: + if self.logger: + self.logger.error("failed to set data size: %s" % (e,)) + self.serial.bytesize = backup + else: + if self.logger: + self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize)) + self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize)) + elif suboption[1:2] == SET_PARITY: + backup = self.serial.parity + try: + parity = struct.unpack("!B", suboption[2:3])[0] + if parity != 0: + self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity] + except ValueError, e: + if self.logger: + self.logger.error("failed to set parity: %s" % (e,)) + self.serial.parity = backup + else: + if self.logger: + self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity)) + self.rfc2217SendSubnegotiation( + SERVER_SET_PARITY, + struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity]) + ) + elif suboption[1:2] == SET_STOPSIZE: + backup = self.serial.stopbits + try: + stopbits = struct.unpack("!B", suboption[2:3])[0] + if stopbits != 0: + self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits] + except ValueError, e: + if self.logger: + self.logger.error("failed to set stop bits: %s" % (e,)) + self.serial.stopbits = backup + else: + if self.logger: + self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits)) + self.rfc2217SendSubnegotiation( + SERVER_SET_STOPSIZE, + struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]) + ) + elif suboption[1:2] == SET_CONTROL: + if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING: + if self.serial.xonxoff: + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif self.serial.rtscts: + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + else: + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL: + self.serial.xonxoff = False + self.serial.rtscts = False + if self.logger: + self.logger.info("changed flow control to None") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL: + self.serial.xonxoff = True + if self.logger: + self.logger.info("changed flow control to XON/XOFF") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL: + self.serial.rtscts = True + if self.logger: + self.logger.info("changed flow control to RTS/CTS") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE: + if self.logger: + self.logger.warning("requested break state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_BREAK_ON: + self.serial.setBreak(True) + if self.logger: + self.logger.info("changed BREAK to active") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON) + elif suboption[2:3] == SET_CONTROL_BREAK_OFF: + self.serial.setBreak(False) + if self.logger: + self.logger.info("changed BREAK to inactive") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_DTR: + if self.logger: + self.logger.warning("requested DTR state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_DTR_ON: + self.serial.setDTR(True) + if self.logger: + self.logger.info("changed DTR to active") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON) + elif suboption[2:3] == SET_CONTROL_DTR_OFF: + self.serial.setDTR(False) + if self.logger: + self.logger.info("changed DTR to inactive") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_RTS: + if self.logger: + self.logger.warning("requested RTS state - not implemented") + pass # XXX needs cached value + #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_ON: + self.serial.setRTS(True) + if self.logger: + self.logger.info("changed RTS to active") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_OFF: + self.serial.setRTS(False) + if self.logger: + self.logger.info("changed RTS to inactive") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF) + #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL: + elif suboption[1:2] == NOTIFY_LINESTATE: + # client polls for current state + self.rfc2217SendSubnegotiation( + SERVER_NOTIFY_LINESTATE, + to_bytes([0]) # sorry, nothing like that implemented + ) + elif suboption[1:2] == NOTIFY_MODEMSTATE: + if self.logger: + self.logger.info("request for modem state") + # client polls for current state + self.check_modem_lines(force_notification=True) + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + if self.logger: + self.logger.info("suspend") + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + if self.logger: + self.logger.info("resume") + self._remote_suspend_flow = False + elif suboption[1:2] == SET_LINESTATE_MASK: + self.linstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,)) + elif suboption[1:2] == SET_MODEMSTATE_MASK: + self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,)) + elif suboption[1:2] == PURGE_DATA: + if suboption[2:3] == PURGE_RECEIVE_BUFFER: + self.serial.flushInput() + if self.logger: + self.logger.info("purge in") + self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER) + elif suboption[2:3] == PURGE_TRANSMIT_BUFFER: + self.serial.flushOutput() + if self.logger: + self.logger.info("purge out") + self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER) + elif suboption[2:3] == PURGE_BOTH_BUFFERS: + self.serial.flushInput() + self.serial.flushOutput() + if self.logger: + self.logger.info("purge both") + self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS) + else: + if self.logger: + self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:])) + else: + if self.logger: + self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:])) + else: + if self.logger: + self.logger.warning("unknown subnegotiation: %r" % (suboption,)) + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('rfc2217://localhost:7000', 115200) + sys.stdout.write('%s\n' % s) + + #~ s.baudrate = 1898 + + sys.stdout.write("write...\n") + s.write("hello\n") + s.flush() + sys.stdout.write("read: %s\n" % s.read(5)) + + #~ s.baudrate = 19200 + #~ s.databits = 7 + s.close() diff --git a/scripts/serial/serialcli.py b/scripts/serial/serialcli.py new file mode 100644 index 0000000000..9ab3876206 --- /dev/null +++ b/scripts/serial/serialcli.py @@ -0,0 +1,284 @@ +#! python +# Python Serial Port Extension for Win32, Linux, BSD, Jython and .NET/Mono +# serial driver for .NET/Mono (IronPython), .NET >= 2 +# see __init__.py +# +# (C) 2008 Chris Liechti +# this is distributed under a free software license, see license.txt + +import clr +import System +import System.IO.Ports +from serial.serialutil import * + + +def device(portnum): + """Turn a port number into a device name""" + return System.IO.Ports.SerialPort.GetPortNames()[portnum] + + +# must invoke function with byte array, make a helper to convert strings +# to byte arrays +sab = System.Array[System.Byte] +def as_byte_array(string): + return sab([ord(x) for x in string]) # XXX will require adaption when run with a 3.x compatible IronPython + +class IronSerial(SerialBase): + """Serial port implementation for .NET/Mono.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self._isOpen: + raise SerialException("Port is already open.") + try: + self._port_handle = System.IO.Ports.SerialPort(self.portstr) + except Exception, msg: + self._port_handle = None + raise SerialException("could not open port %s: %s" % (self.portstr, msg)) + + self._reconfigurePort() + self._port_handle.Open() + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if not self._port_handle: + raise SerialException("Can only operate on a valid port handle") + + #~ self._port_handle.ReceivedBytesThreshold = 1 + + if self._timeout is None: + self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout + else: + self._port_handle.ReadTimeout = int(self._timeout*1000) + + # if self._timeout != 0 and self._interCharTimeout is not None: + # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:] + + if self._writeTimeout is None: + self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout + else: + self._port_handle.WriteTimeout = int(self._writeTimeout*1000) + + + # Setup the connection info. + try: + self._port_handle.BaudRate = self._baudrate + except IOError, e: + # catch errors from illegal baudrate settings + raise ValueError(str(e)) + + if self._bytesize == FIVEBITS: + self._port_handle.DataBits = 5 + elif self._bytesize == SIXBITS: + self._port_handle.DataBits = 6 + elif self._bytesize == SEVENBITS: + self._port_handle.DataBits = 7 + elif self._bytesize == EIGHTBITS: + self._port_handle.DataBits = 8 + else: + raise ValueError("Unsupported number of data bits: %r" % self._bytesize) + + if self._parity == PARITY_NONE: + self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k + elif self._parity == PARITY_EVEN: + self._port_handle.Parity = System.IO.Ports.Parity.Even + elif self._parity == PARITY_ODD: + self._port_handle.Parity = System.IO.Ports.Parity.Odd + elif self._parity == PARITY_MARK: + self._port_handle.Parity = System.IO.Ports.Parity.Mark + elif self._parity == PARITY_SPACE: + self._port_handle.Parity = System.IO.Ports.Parity.Space + else: + raise ValueError("Unsupported parity mode: %r" % self._parity) + + if self._stopbits == STOPBITS_ONE: + self._port_handle.StopBits = System.IO.Ports.StopBits.One + elif self._stopbits == STOPBITS_ONE_POINT_FIVE: + self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive + elif self._stopbits == STOPBITS_TWO: + self._port_handle.StopBits = System.IO.Ports.StopBits.Two + else: + raise ValueError("Unsupported number of stop bits: %r" % self._stopbits) + + if self._rtscts and self._xonxoff: + self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff + elif self._rtscts: + self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend + elif self._xonxoff: + self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff + else: + self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None') # reserved keyword in Py3k + + #~ def __del__(self): + #~ self.close() + + def close(self): + """Close port""" + if self._isOpen: + if self._port_handle: + try: + self._port_handle.Close() + except System.IO.Ports.InvalidOperationException: + # ignore errors. can happen for unplugged USB serial devices + pass + self._port_handle = None + self._isOpen = False + + def makeDeviceName(self, port): + try: + return device(port) + except TypeError, e: + raise SerialException(str(e)) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._port_handle: raise portNotOpenError + return self._port_handle.BytesToRead + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self._port_handle: raise portNotOpenError + # must use single byte reads as this is the only way to read + # without applying encodings + data = bytearray() + while size: + try: + data.append(self._port_handle.ReadByte()) + except System.TimeoutException, e: + break + else: + size -= 1 + return bytes(data) + + def write(self, data): + """Output the given string over the serial port.""" + if not self._port_handle: raise portNotOpenError + #~ if not isinstance(data, (bytes, bytearray)): + #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data))) + try: + # must call overloaded method with byte array argument + # as this is the only one not applying encodings + self._port_handle.Write(as_byte_array(data), 0, len(data)) + except System.TimeoutException, e: + raise writeTimeoutError + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._port_handle: raise portNotOpenError + self._port_handle.DiscardInBuffer() + + def flushOutput(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self._port_handle: raise portNotOpenError + self._port_handle.DiscardOutBuffer() + + def sendBreak(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self._port_handle: raise portNotOpenError + import time + self._port_handle.BreakState = True + time.sleep(duration) + self._port_handle.BreakState = False + + def setBreak(self, level=True): + """ + Set break: Controls TXD. When active, to transmitting is possible. + """ + if not self._port_handle: raise portNotOpenError + self._port_handle.BreakState = bool(level) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send""" + if not self._port_handle: raise portNotOpenError + self._port_handle.RtsEnable = bool(level) + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready""" + if not self._port_handle: raise portNotOpenError + self._port_handle.DtrEnable = bool(level) + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if not self._port_handle: raise portNotOpenError + return self._port_handle.CtsHolding + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if not self._port_handle: raise portNotOpenError + return self._port_handle.DsrHolding + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if not self._port_handle: raise portNotOpenError + #~ return self._port_handle.XXX + return False #XXX an error would be better + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if not self._port_handle: raise portNotOpenError + return self._port_handle.CDHolding + + # - - platform specific - - - - + # none + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(IronSerial, FileLike): + pass +else: + # io library present + class Serial(IronSerial, io.RawIOBase): + pass + + +# Nur Testfunktion!! +if __name__ == '__main__': + import sys + + s = Serial(0) + sys.stdio.write('%s\n' % s) + + s = Serial() + sys.stdio.write('%s\n' % s) + + + s.baudrate = 19200 + s.databits = 7 + s.close() + s.port = 0 + s.open() + sys.stdio.write('%s\n' % s) + diff --git a/scripts/serial/serialposix.py b/scripts/serial/serialposix.py new file mode 100644 index 0000000000..359ad1b877 --- /dev/null +++ b/scripts/serial/serialposix.py @@ -0,0 +1,730 @@ +#!/usr/bin/env python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# module for serial IO for POSIX compatible systems, like Linux +# see __init__.py +# +# (C) 2001-2010 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# parts based on code from Grant B. Edwards : +# ftp://ftp.visi.com/users/grante/python/PosixSerial.py +# +# references: http://www.easysw.com/~mike/serial/serial.html + +import sys, os, fcntl, termios, struct, select, errno, time +from serial.serialutil import * + +# Do check the Python version as some constants have moved. +if (sys.hexversion < 0x020100f0): + import TERMIOS +else: + TERMIOS = termios + +if (sys.hexversion < 0x020200f0): + import FCNTL +else: + FCNTL = fcntl + +# try to detect the OS so that a device can be selected... +# this code block should supply a device() and set_special_baudrate() function +# for the platform +plat = sys.platform.lower() + +if plat[:5] == 'linux': # Linux (confirmed) + + def device(port): + return '/dev/ttyS%d' % port + + TCGETS2 = 0x802C542A + TCSETS2 = 0x402C542B + BOTHER = 0o010000 + + def set_special_baudrate(port, baudrate): + # right size is 44 on x86_64, allow for some growth + import array + buf = array.array('i', [0] * 64) + + try: + # get serial_struct + FCNTL.ioctl(port.fd, TCGETS2, buf) + # set custom speed + buf[2] &= ~TERMIOS.CBAUD + buf[2] |= BOTHER + buf[9] = buf[10] = baudrate + + # set serial_struct + res = FCNTL.ioctl(port.fd, TCSETS2, buf) + except IOError, e: + raise ValueError('Failed to set custom baud rate (%s): %s' % (baudrate, e)) + + baudrate_constants = { + 0: 0000000, # hang up + 50: 0000001, + 75: 0000002, + 110: 0000003, + 134: 0000004, + 150: 0000005, + 200: 0000006, + 300: 0000007, + 600: 0000010, + 1200: 0000011, + 1800: 0000012, + 2400: 0000013, + 4800: 0000014, + 9600: 0000015, + 19200: 0000016, + 38400: 0000017, + 57600: 0010001, + 115200: 0010002, + 230400: 0010003, + 460800: 0010004, + 500000: 0010005, + 576000: 0010006, + 921600: 0010007, + 1000000: 0010010, + 1152000: 0010011, + 1500000: 0010012, + 2000000: 0010013, + 2500000: 0010014, + 3000000: 0010015, + 3500000: 0010016, + 4000000: 0010017 + } + +elif plat == 'cygwin': # cygwin/win32 (confirmed) + + def device(port): + return '/dev/com%d' % (port + 1) + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = { + 128000: 0x01003, + 256000: 0x01005, + 500000: 0x01007, + 576000: 0x01008, + 921600: 0x01009, + 1000000: 0x0100a, + 1152000: 0x0100b, + 1500000: 0x0100c, + 2000000: 0x0100d, + 2500000: 0x0100e, + 3000000: 0x0100f + } + +elif plat[:7] == 'openbsd': # OpenBSD + + def device(port): + return '/dev/cua%02d' % port + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +elif plat[:3] == 'bsd' or \ + plat[:7] == 'freebsd': + + def device(port): + return '/dev/cuad%d' % port + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +elif plat[:6] == 'darwin': # OS X + + version = os.uname()[2].split('.') + # Tiger or above can support arbitrary serial speeds + if int(version[0]) >= 8: + def set_special_baudrate(port, baudrate): + # use IOKit-specific call to set up high speeds + import array, fcntl + buf = array.array('i', [baudrate]) + IOSSIOSPEED = 0x80045402 #_IOW('T', 2, speed_t) + fcntl.ioctl(port.fd, IOSSIOSPEED, buf, 1) + else: # version < 8 + def set_special_baudrate(port, baudrate): + raise ValueError("baud rate not supported") + + def device(port): + return '/dev/cuad%d' % port + + baudrate_constants = {} + + +elif plat[:6] == 'netbsd': # NetBSD 1.6 testing by Erk + + def device(port): + return '/dev/dty%02d' % port + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +elif plat[:4] == 'irix': # IRIX (partially tested) + + def device(port): + return '/dev/ttyf%d' % (port+1) #XXX different device names depending on flow control + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +elif plat[:2] == 'hp': # HP-UX (not tested) + + def device(port): + return '/dev/tty%dp0' % (port+1) + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +elif plat[:5] == 'sunos': # Solaris/SunOS (confirmed) + + def device(port): + return '/dev/tty%c' % (ord('a')+port) + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +elif plat[:3] == 'aix': # AIX + + def device(port): + return '/dev/tty%d' % (port) + + def set_special_baudrate(port, baudrate): + raise ValueError("sorry don't know how to handle non standard baud rate on this platform") + + baudrate_constants = {} + +else: + # platform detection has failed... + sys.stderr.write("""\ +don't know how to number ttys on this system. +! Use an explicit path (eg /dev/ttyS1) or send this information to +! the author of this module: + +sys.platform = %r +os.name = %r +serialposix.py version = %s + +also add the device name of the serial port and where the +counting starts for the first serial port. +e.g. 'first serial port: /dev/ttyS0' +and with a bit luck you can get this module running... +""" % (sys.platform, os.name, VERSION)) + # no exception, just continue with a brave attempt to build a device name + # even if the device name is not correct for the platform it has chances + # to work using a string with the real device name as port parameter. + def device(portum): + return '/dev/ttyS%d' % portnum + def set_special_baudrate(port, baudrate): + raise SerialException("sorry don't know how to handle non standard baud rate on this platform") + baudrate_constants = {} + #~ raise Exception, "this module does not run on this platform, sorry." + +# whats up with "aix", "beos", .... +# they should work, just need to know the device names. + + +# load some constants for later use. +# try to use values from TERMIOS, use defaults from linux otherwise +TIOCMGET = hasattr(TERMIOS, 'TIOCMGET') and TERMIOS.TIOCMGET or 0x5415 +TIOCMBIS = hasattr(TERMIOS, 'TIOCMBIS') and TERMIOS.TIOCMBIS or 0x5416 +TIOCMBIC = hasattr(TERMIOS, 'TIOCMBIC') and TERMIOS.TIOCMBIC or 0x5417 +TIOCMSET = hasattr(TERMIOS, 'TIOCMSET') and TERMIOS.TIOCMSET or 0x5418 + +#TIOCM_LE = hasattr(TERMIOS, 'TIOCM_LE') and TERMIOS.TIOCM_LE or 0x001 +TIOCM_DTR = hasattr(TERMIOS, 'TIOCM_DTR') and TERMIOS.TIOCM_DTR or 0x002 +TIOCM_RTS = hasattr(TERMIOS, 'TIOCM_RTS') and TERMIOS.TIOCM_RTS or 0x004 +#TIOCM_ST = hasattr(TERMIOS, 'TIOCM_ST') and TERMIOS.TIOCM_ST or 0x008 +#TIOCM_SR = hasattr(TERMIOS, 'TIOCM_SR') and TERMIOS.TIOCM_SR or 0x010 + +TIOCM_CTS = hasattr(TERMIOS, 'TIOCM_CTS') and TERMIOS.TIOCM_CTS or 0x020 +TIOCM_CAR = hasattr(TERMIOS, 'TIOCM_CAR') and TERMIOS.TIOCM_CAR or 0x040 +TIOCM_RNG = hasattr(TERMIOS, 'TIOCM_RNG') and TERMIOS.TIOCM_RNG or 0x080 +TIOCM_DSR = hasattr(TERMIOS, 'TIOCM_DSR') and TERMIOS.TIOCM_DSR or 0x100 +TIOCM_CD = hasattr(TERMIOS, 'TIOCM_CD') and TERMIOS.TIOCM_CD or TIOCM_CAR +TIOCM_RI = hasattr(TERMIOS, 'TIOCM_RI') and TERMIOS.TIOCM_RI or TIOCM_RNG +#TIOCM_OUT1 = hasattr(TERMIOS, 'TIOCM_OUT1') and TERMIOS.TIOCM_OUT1 or 0x2000 +#TIOCM_OUT2 = hasattr(TERMIOS, 'TIOCM_OUT2') and TERMIOS.TIOCM_OUT2 or 0x4000 +if hasattr(TERMIOS, 'TIOCINQ'): + TIOCINQ = TERMIOS.TIOCINQ +else: + TIOCINQ = hasattr(TERMIOS, 'FIONREAD') and TERMIOS.FIONREAD or 0x541B +TIOCOUTQ = hasattr(TERMIOS, 'TIOCOUTQ') and TERMIOS.TIOCOUTQ or 0x5411 + +TIOCM_zero_str = struct.pack('I', 0) +TIOCM_RTS_str = struct.pack('I', TIOCM_RTS) +TIOCM_DTR_str = struct.pack('I', TIOCM_DTR) + +TIOCSBRK = hasattr(TERMIOS, 'TIOCSBRK') and TERMIOS.TIOCSBRK or 0x5427 +TIOCCBRK = hasattr(TERMIOS, 'TIOCCBRK') and TERMIOS.TIOCCBRK or 0x5428 + +CMSPAR = 010000000000 # Use "stick" (mark/space) parity + + +class PosixSerial(SerialBase): + """\ + Serial port class POSIX implementation. Serial port configuration is + done with termios and fcntl. Runs on Linux and many other Un*x like + systems. + """ + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened.""" + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self._isOpen: + raise SerialException("Port is already open.") + self.fd = None + # open + try: + self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK) + except OSError, msg: + self.fd = None + raise SerialException(msg.errno, "could not open port %s: %s" % (self._port, msg)) + #~ fcntl.fcntl(self.fd, FCNTL.F_SETFL, 0) # set blocking + + try: + self._reconfigurePort() + except: + try: + os.close(self.fd) + except: + # ignore any exception when closing the port + # also to keep original exception that happened when setting up + pass + self.fd = None + raise + else: + self._isOpen = True + self.flushInput() + + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if self.fd is None: + raise SerialException("Can only operate on a valid file descriptor") + custom_baud = None + + vmin = vtime = 0 # timeout is done via select + if self._interCharTimeout is not None: + vmin = 1 + vtime = int(self._interCharTimeout * 10) + try: + orig_attr = termios.tcgetattr(self.fd) + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr + except termios.error, msg: # if a port is nonexistent but has a /dev file, it'll fail here + raise SerialException("Could not configure port: %s" % msg) + # set up raw mode / no echo / binary + cflag |= (TERMIOS.CLOCAL|TERMIOS.CREAD) + lflag &= ~(TERMIOS.ICANON|TERMIOS.ECHO|TERMIOS.ECHOE|TERMIOS.ECHOK|TERMIOS.ECHONL| + TERMIOS.ISIG|TERMIOS.IEXTEN) #|TERMIOS.ECHOPRT + for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk + if hasattr(TERMIOS, flag): + lflag &= ~getattr(TERMIOS, flag) + + oflag &= ~(TERMIOS.OPOST) + iflag &= ~(TERMIOS.INLCR|TERMIOS.IGNCR|TERMIOS.ICRNL|TERMIOS.IGNBRK) + if hasattr(TERMIOS, 'IUCLC'): + iflag &= ~TERMIOS.IUCLC + if hasattr(TERMIOS, 'PARMRK'): + iflag &= ~TERMIOS.PARMRK + + # setup baud rate + try: + ispeed = ospeed = getattr(TERMIOS, 'B%s' % (self._baudrate)) + except AttributeError: + try: + ispeed = ospeed = baudrate_constants[self._baudrate] + except KeyError: + #~ raise ValueError('Invalid baud rate: %r' % self._baudrate) + # may need custom baud rate, it isn't in our list. + ispeed = ospeed = getattr(TERMIOS, 'B38400') + try: + custom_baud = int(self._baudrate) # store for later + except ValueError: + raise ValueError('Invalid baud rate: %r' % self._baudrate) + else: + if custom_baud < 0: + raise ValueError('Invalid baud rate: %r' % self._baudrate) + + # setup char len + cflag &= ~TERMIOS.CSIZE + if self._bytesize == 8: + cflag |= TERMIOS.CS8 + elif self._bytesize == 7: + cflag |= TERMIOS.CS7 + elif self._bytesize == 6: + cflag |= TERMIOS.CS6 + elif self._bytesize == 5: + cflag |= TERMIOS.CS5 + else: + raise ValueError('Invalid char len: %r' % self._bytesize) + # setup stop bits + if self._stopbits == STOPBITS_ONE: + cflag &= ~(TERMIOS.CSTOPB) + elif self._stopbits == STOPBITS_ONE_POINT_FIVE: + cflag |= (TERMIOS.CSTOPB) # XXX same as TWO.. there is no POSIX support for 1.5 + elif self._stopbits == STOPBITS_TWO: + cflag |= (TERMIOS.CSTOPB) + else: + raise ValueError('Invalid stop bit specification: %r' % self._stopbits) + # setup parity + iflag &= ~(TERMIOS.INPCK|TERMIOS.ISTRIP) + if self._parity == PARITY_NONE: + cflag &= ~(TERMIOS.PARENB|TERMIOS.PARODD) + elif self._parity == PARITY_EVEN: + cflag &= ~(TERMIOS.PARODD) + cflag |= (TERMIOS.PARENB) + elif self._parity == PARITY_ODD: + cflag |= (TERMIOS.PARENB|TERMIOS.PARODD) + elif self._parity == PARITY_MARK and plat[:5] == 'linux': + cflag |= (TERMIOS.PARENB|CMSPAR|TERMIOS.PARODD) + elif self._parity == PARITY_SPACE and plat[:5] == 'linux': + cflag |= (TERMIOS.PARENB|CMSPAR) + cflag &= ~(TERMIOS.PARODD) + else: + raise ValueError('Invalid parity: %r' % self._parity) + # setup flow control + # xonxoff + if hasattr(TERMIOS, 'IXANY'): + if self._xonxoff: + iflag |= (TERMIOS.IXON|TERMIOS.IXOFF) #|TERMIOS.IXANY) + else: + iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF|TERMIOS.IXANY) + else: + if self._xonxoff: + iflag |= (TERMIOS.IXON|TERMIOS.IXOFF) + else: + iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF) + # rtscts + if hasattr(TERMIOS, 'CRTSCTS'): + if self._rtscts: + cflag |= (TERMIOS.CRTSCTS) + else: + cflag &= ~(TERMIOS.CRTSCTS) + elif hasattr(TERMIOS, 'CNEW_RTSCTS'): # try it with alternate constant name + if self._rtscts: + cflag |= (TERMIOS.CNEW_RTSCTS) + else: + cflag &= ~(TERMIOS.CNEW_RTSCTS) + # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails?? + + # buffer + # vmin "minimal number of characters to be read. 0 for non blocking" + if vmin < 0 or vmin > 255: + raise ValueError('Invalid vmin: %r ' % vmin) + cc[TERMIOS.VMIN] = vmin + # vtime + if vtime < 0 or vtime > 255: + raise ValueError('Invalid vtime: %r' % vtime) + cc[TERMIOS.VTIME] = vtime + # activate settings + if [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr: + termios.tcsetattr(self.fd, TERMIOS.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) + + # apply custom baud rate, if any + if custom_baud is not None: + set_special_baudrate(self, custom_baud) + + def close(self): + """Close port""" + if self._isOpen: + if self.fd is not None: + os.close(self.fd) + self.fd = None + self._isOpen = False + + def makeDeviceName(self, port): + return device(port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) + return struct.unpack('I',s)[0] + + # select based implementation, proved to work on many systems + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self._isOpen: raise portNotOpenError + read = bytearray() + while len(read) < size: + try: + ready,_,_ = select.select([self.fd],[],[], self._timeout) + # If select was used with a timeout, and the timeout occurs, it + # returns with empty lists -> thus abort read operation. + # For timeout == 0 (non-blocking operation) also abort when there + # is nothing to read. + if not ready: + break # timeout + buf = os.read(self.fd, size-len(read)) + # read should always return some data as select reported it was + # ready to read when we get to this point. + if not buf: + # Disconnected devices, at least on Linux, show the + # behavior that they are always ready to read immediately + # but reading returns nothing. + raise SerialException('device reports readiness to read but returned no data (device disconnected or multiple access on port?)') + read.extend(buf) + except OSError, e: + # this is for Python 3.x where select.error is a subclass of OSError + # ignore EAGAIN errors. all other errors are shown + if e.errno != errno.EAGAIN: + raise SerialException('read failed: %s' % (e,)) + except select.error, e: + # this is for Python 2.x + # ignore EAGAIN errors. all other errors are shown + # see also http://www.python.org/dev/peps/pep-3151/#select + if e[0] != errno.EAGAIN: + raise SerialException('read failed: %s' % (e,)) + return bytes(read) + + def write(self, data): + """Output the given string over the serial port.""" + if not self._isOpen: raise portNotOpenError + d = to_bytes(data) + tx_len = len(d) + if self._writeTimeout is not None and self._writeTimeout > 0: + timeout = time.time() + self._writeTimeout + else: + timeout = None + while tx_len > 0: + try: + n = os.write(self.fd, d) + if timeout: + # when timeout is set, use select to wait for being ready + # with the time left as timeout + timeleft = timeout - time.time() + if timeleft < 0: + raise writeTimeoutError + _, ready, _ = select.select([], [self.fd], [], timeleft) + if not ready: + raise writeTimeoutError + else: + # wait for write operation + _, ready, _ = select.select([], [self.fd], [], None) + if not ready: + raise SerialException('write failed (select)') + d = d[n:] + tx_len -= n + except OSError, v: + if v.errno != errno.EAGAIN: + raise SerialException('write failed: %s' % (v,)) + return len(data) + + def flush(self): + """\ + Flush of file like objects. In this case, wait until all data + is written. + """ + self.drainOutput() + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + termios.tcflush(self.fd, TERMIOS.TCIFLUSH) + + def flushOutput(self): + """\ + Clear output buffer, aborting the current output and discarding all + that is in the buffer. + """ + if not self._isOpen: raise portNotOpenError + termios.tcflush(self.fd, TERMIOS.TCOFLUSH) + + def sendBreak(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self._isOpen: raise portNotOpenError + termios.tcsendbreak(self.fd, int(duration/0.25)) + + def setBreak(self, level=1): + """\ + Set break: Controls TXD. When active, no transmitting is possible. + """ + if self.fd is None: raise portNotOpenError + if level: + fcntl.ioctl(self.fd, TIOCSBRK) + else: + fcntl.ioctl(self.fd, TIOCCBRK) + + def setRTS(self, level=1): + """Set terminal status line: Request To Send""" + if not self._isOpen: raise portNotOpenError + if level: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str) + + def setDTR(self, level=1): + """Set terminal status line: Data Terminal Ready""" + if not self._isOpen: raise portNotOpenError + if level: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str) + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if not self._isOpen: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_CTS != 0 + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if not self._isOpen: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_DSR != 0 + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if not self._isOpen: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_RI != 0 + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if not self._isOpen: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_CD != 0 + + # - - platform specific - - - - + + def outWaiting(self): + """Return the number of characters currently in the output buffer.""" + #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str) + return struct.unpack('I',s)[0] + + def drainOutput(self): + """internal - not portable!""" + if not self._isOpen: raise portNotOpenError + termios.tcdrain(self.fd) + + def nonblocking(self): + """internal - not portable!""" + if not self._isOpen: raise portNotOpenError + fcntl.fcntl(self.fd, FCNTL.F_SETFL, os.O_NONBLOCK) + + def fileno(self): + """\ + For easier use of the serial port instance with select. + WARNING: this function is not portable to different platforms! + """ + if not self._isOpen: raise portNotOpenError + return self.fd + + def setXON(self, level=True): + """\ + Manually control flow - when software flow control is enabled. + This will send XON (true) and XOFF (false) to the other device. + WARNING: this function is not portable to different platforms! + """ + if not self.hComPort: raise portNotOpenError + if enable: + termios.tcflow(self.fd, TERMIOS.TCION) + else: + termios.tcflow(self.fd, TERMIOS.TCIOFF) + + def flowControlOut(self, enable): + """\ + Manually control flow of outgoing data - when hardware or software flow + control is enabled. + WARNING: this function is not portable to different platforms! + """ + if not self._isOpen: raise portNotOpenError + if enable: + termios.tcflow(self.fd, TERMIOS.TCOON) + else: + termios.tcflow(self.fd, TERMIOS.TCOOFF) + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(PosixSerial, FileLike): + pass +else: + # io library present + class Serial(PosixSerial, io.RawIOBase): + pass + +class PosixPollSerial(Serial): + """\ + Poll based read implementation. Not all systems support poll properly. + However this one has better handling of errors, such as a device + disconnecting while it's in use (e.g. USB-serial unplugged). + """ + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if self.fd is None: raise portNotOpenError + read = bytearray() + poll = select.poll() + poll.register(self.fd, select.POLLIN|select.POLLERR|select.POLLHUP|select.POLLNVAL) + if size > 0: + while len(read) < size: + # print "\tread(): size",size, "have", len(read) #debug + # wait until device becomes ready to read (or something fails) + for fd, event in poll.poll(self._timeout*1000): + if event & (select.POLLERR|select.POLLHUP|select.POLLNVAL): + raise SerialException('device reports error (poll)') + # we don't care if it is select.POLLIN or timeout, that's + # handled below + buf = os.read(self.fd, size - len(read)) + read.extend(buf) + if ((self._timeout is not None and self._timeout >= 0) or + (self._interCharTimeout is not None and self._interCharTimeout > 0)) and not buf: + break # early abort on timeout + return bytes(read) + + +if __name__ == '__main__': + s = Serial(0, + baudrate=19200, # baud rate + bytesize=EIGHTBITS, # number of data bits + parity=PARITY_EVEN, # enable parity checking + stopbits=STOPBITS_ONE, # number of stop bits + timeout=3, # set a timeout value, None for waiting forever + xonxoff=0, # enable software flow control + rtscts=0, # enable RTS/CTS flow control + ) + s.setRTS(1) + s.setDTR(1) + s.flushInput() + s.flushOutput() + s.write('hello') + sys.stdout.write('%r\n' % s.read(5)) + sys.stdout.write('%s\n' % s.inWaiting()) + del s + diff --git a/scripts/serial/serialutil.py b/scripts/serial/serialutil.py new file mode 100644 index 0000000000..af0d2f6402 --- /dev/null +++ b/scripts/serial/serialutil.py @@ -0,0 +1,572 @@ +#! python +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# (C) 2001-2010 Chris Liechti +# this is distributed under a free software license, see license.txt + +# compatibility for older Python < 2.6 +try: + bytes + bytearray +except (NameError, AttributeError): + # Python older than 2.6 do not have these types. Like for Python 2.6 they + # should behave like str. For Python older than 3.0 we want to work with + # strings anyway, only later versions have a true bytes type. + bytes = str + # bytearray is a mutable type that is easily turned into an instance of + # bytes + class bytearray(list): + # for bytes(bytearray()) usage + def __str__(self): return ''.join(self) + def __repr__(self): return 'bytearray(%r)' % ''.join(self) + # append automatically converts integers to characters + def append(self, item): + if isinstance(item, str): + list.append(self, item) + else: + list.append(self, chr(item)) + # += + def __iadd__(self, other): + for byte in other: + self.append(byte) + return self + + def __getslice__(self, i, j): + return bytearray(list.__getslice__(self, i, j)) + + def __getitem__(self, item): + if isinstance(item, slice): + return bytearray(list.__getitem__(self, item)) + else: + return ord(list.__getitem__(self, item)) + + def __eq__(self, other): + if isinstance(other, basestring): + other = bytearray(other) + return list.__eq__(self, other) + +# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)`` +# isn't returning the contents (very unfortunate). Therefore we need special +# cases and test for it. Ensure that there is a ``memoryview`` object for older +# Python versions. This is easier than making every test dependent on its +# existence. +try: + memoryview +except (NameError, AttributeError): + # implementation does not matter as we do not realy use it. + # it just must not inherit from something else we might care for. + class memoryview: + pass + + +# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11' +# so a simple ``bytes(sequence)`` doesn't work for all versions +def to_bytes(seq): + """convert a sequence to a bytes type""" + if isinstance(seq, bytes): + return seq + elif isinstance(seq, bytearray): + return bytes(seq) + elif isinstance(seq, memoryview): + return seq.tobytes() + else: + b = bytearray() + for item in seq: + b.append(item) # this one handles int and str for our emulation and ints for Python 3.x + return bytes(b) + +# create control bytes +XON = to_bytes([17]) +XOFF = to_bytes([19]) + +CR = to_bytes([13]) +LF = to_bytes([10]) + + +PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S' +STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2) +FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8) + +PARITY_NAMES = { + PARITY_NONE: 'None', + PARITY_EVEN: 'Even', + PARITY_ODD: 'Odd', + PARITY_MARK: 'Mark', + PARITY_SPACE: 'Space', +} + + +class SerialException(IOError): + """Base class for serial port related exceptions.""" + + +class SerialTimeoutException(SerialException): + """Write timeouts give an exception""" + + +writeTimeoutError = SerialTimeoutException('Write timeout') +portNotOpenError = SerialException('Attempting to use a port that is not open') + + +class FileLike(object): + """\ + An abstract file like class. + + This class implements readline and readlines based on read and + writelines based on write. + This class is used to provide the above functions for to Serial + port objects. + + Note that when the serial port was opened with _NO_ timeout that + readline blocks until it sees a newline (or the specified size is + reached) and that readlines would never return and therefore + refuses to work (it raises an exception in this case)! + """ + + def __init__(self): + self.closed = True + + def close(self): + self.closed = True + + # so that ports are closed when objects are discarded + def __del__(self): + """Destructor. Calls close().""" + # The try/except block is in case this is called at program + # exit time, when it's possible that globals have already been + # deleted, and then the close() call might fail. Since + # there's nothing we can do about such failures and they annoy + # the end users, we suppress the traceback. + try: + self.close() + except: + pass + + def writelines(self, sequence): + for line in sequence: + self.write(line) + + def flush(self): + """flush of file like objects""" + pass + + # iterator for e.g. "for line in Serial(0): ..." usage + def next(self): + line = self.readline() + if not line: raise StopIteration + return line + + def __iter__(self): + return self + + def readline(self, size=None, eol=LF): + """\ + Read a line which is terminated with end-of-line (eol) character + ('\n' by default) or until timeout. + """ + leneol = len(eol) + line = bytearray() + while True: + c = self.read(1) + if c: + line += c + if line[-leneol:] == eol: + break + if size is not None and len(line) >= size: + break + else: + break + return bytes(line) + + def readlines(self, sizehint=None, eol=LF): + """\ + Read a list of lines, until timeout. + sizehint is ignored. + """ + if self.timeout is None: + raise ValueError("Serial port MUST have enabled timeout for this function!") + leneol = len(eol) + lines = [] + while True: + line = self.readline(eol=eol) + if line: + lines.append(line) + if line[-leneol:] != eol: # was the line received with a timeout? + break + else: + break + return lines + + def xreadlines(self, sizehint=None): + """\ + Read lines, implemented as generator. It will raise StopIteration on + timeout (empty read). sizehint is ignored. + """ + while True: + line = self.readline() + if not line: break + yield line + + # other functions of file-likes - not used by pySerial + + #~ readinto(b) + + def seek(self, pos, whence=0): + raise IOError("file is not seekable") + + def tell(self): + raise IOError("file is not seekable") + + def truncate(self, n=None): + raise IOError("file is not seekable") + + def isatty(self): + return False + + +class SerialBase(object): + """\ + Serial port base class. Provides __init__ function and properties to + get/set port settings. + """ + + # default values, may be overridden in subclasses that do not support all values + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, + 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, + 3000000, 3500000, 4000000) + BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS) + PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE) + STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO) + + def __init__(self, + port = None, # number of device, numbering starts at + # zero. if everything fails, the user + # can specify a device string, note + # that this isn't portable anymore + # port will be opened if one is specified + baudrate=9600, # baud rate + bytesize=EIGHTBITS, # number of data bits + parity=PARITY_NONE, # enable parity checking + stopbits=STOPBITS_ONE, # number of stop bits + timeout=None, # set a timeout value, None to wait forever + xonxoff=False, # enable software flow control + rtscts=False, # enable RTS/CTS flow control + writeTimeout=None, # set a timeout for writes + dsrdtr=False, # None: use rtscts setting, dsrdtr override if True or False + interCharTimeout=None # Inter-character timeout, None to disable + ): + """\ + Initialize comm port object. If a port is given, then the port will be + opened immediately. Otherwise a Serial port object in closed state + is returned. + """ + + self._isOpen = False + self._port = None # correct value is assigned below through properties + self._baudrate = None # correct value is assigned below through properties + self._bytesize = None # correct value is assigned below through properties + self._parity = None # correct value is assigned below through properties + self._stopbits = None # correct value is assigned below through properties + self._timeout = None # correct value is assigned below through properties + self._writeTimeout = None # correct value is assigned below through properties + self._xonxoff = None # correct value is assigned below through properties + self._rtscts = None # correct value is assigned below through properties + self._dsrdtr = None # correct value is assigned below through properties + self._interCharTimeout = None # correct value is assigned below through properties + + # assign values using get/set methods using the properties feature + self.port = port + self.baudrate = baudrate + self.bytesize = bytesize + self.parity = parity + self.stopbits = stopbits + self.timeout = timeout + self.writeTimeout = writeTimeout + self.xonxoff = xonxoff + self.rtscts = rtscts + self.dsrdtr = dsrdtr + self.interCharTimeout = interCharTimeout + + if port is not None: + self.open() + + def isOpen(self): + """Check if the port is opened.""" + return self._isOpen + + # - - - - - - - - - - - - - - - - - - - - - - - - + + # TODO: these are not really needed as the is the BAUDRATES etc. attribute... + # maybe i remove them before the final release... + + def getSupportedBaudrates(self): + return [(str(b), b) for b in self.BAUDRATES] + + def getSupportedByteSizes(self): + return [(str(b), b) for b in self.BYTESIZES] + + def getSupportedStopbits(self): + return [(str(b), b) for b in self.STOPBITS] + + def getSupportedParities(self): + return [(PARITY_NAMES[b], b) for b in self.PARITIES] + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def setPort(self, port): + """\ + Change the port. The attribute portstr is set to a string that + contains the name of the port. + """ + + was_open = self._isOpen + if was_open: self.close() + if port is not None: + if isinstance(port, basestring): + self.portstr = port + else: + self.portstr = self.makeDeviceName(port) + else: + self.portstr = None + self._port = port + self.name = self.portstr + if was_open: self.open() + + def getPort(self): + """\ + Get the current port setting. The value that was passed on init or using + setPort() is passed back. See also the attribute portstr which contains + the name of the port as a string. + """ + return self._port + + port = property(getPort, setPort, doc="Port setting") + + + def setBaudrate(self, baudrate): + """\ + Change baud rate. It raises a ValueError if the port is open and the + baud rate is not possible. If the port is closed, then the value is + accepted and the exception is raised when the port is opened. + """ + try: + b = int(baudrate) + except TypeError: + raise ValueError("Not a valid baudrate: %r" % (baudrate,)) + else: + if b <= 0: + raise ValueError("Not a valid baudrate: %r" % (baudrate,)) + self._baudrate = b + if self._isOpen: self._reconfigurePort() + + def getBaudrate(self): + """Get the current baud rate setting.""" + return self._baudrate + + baudrate = property(getBaudrate, setBaudrate, doc="Baud rate setting") + + + def setByteSize(self, bytesize): + """Change byte size.""" + if bytesize not in self.BYTESIZES: raise ValueError("Not a valid byte size: %r" % (bytesize,)) + self._bytesize = bytesize + if self._isOpen: self._reconfigurePort() + + def getByteSize(self): + """Get the current byte size setting.""" + return self._bytesize + + bytesize = property(getByteSize, setByteSize, doc="Byte size setting") + + + def setParity(self, parity): + """Change parity setting.""" + if parity not in self.PARITIES: raise ValueError("Not a valid parity: %r" % (parity,)) + self._parity = parity + if self._isOpen: self._reconfigurePort() + + def getParity(self): + """Get the current parity setting.""" + return self._parity + + parity = property(getParity, setParity, doc="Parity setting") + + + def setStopbits(self, stopbits): + """Change stop bits size.""" + if stopbits not in self.STOPBITS: raise ValueError("Not a valid stop bit size: %r" % (stopbits,)) + self._stopbits = stopbits + if self._isOpen: self._reconfigurePort() + + def getStopbits(self): + """Get the current stop bits setting.""" + return self._stopbits + + stopbits = property(getStopbits, setStopbits, doc="Stop bits setting") + + + def setTimeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + try: + timeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: %r" % (timeout,)) + if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,)) + self._timeout = timeout + if self._isOpen: self._reconfigurePort() + + def getTimeout(self): + """Get the current timeout setting.""" + return self._timeout + + timeout = property(getTimeout, setTimeout, doc="Timeout setting for read()") + + + def setWriteTimeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,)) + try: + timeout + 1 #test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: %r" % timeout) + + self._writeTimeout = timeout + if self._isOpen: self._reconfigurePort() + + def getWriteTimeout(self): + """Get the current timeout setting.""" + return self._writeTimeout + + writeTimeout = property(getWriteTimeout, setWriteTimeout, doc="Timeout setting for write()") + + + def setXonXoff(self, xonxoff): + """Change XON/XOFF setting.""" + self._xonxoff = xonxoff + if self._isOpen: self._reconfigurePort() + + def getXonXoff(self): + """Get the current XON/XOFF setting.""" + return self._xonxoff + + xonxoff = property(getXonXoff, setXonXoff, doc="XON/XOFF setting") + + def setRtsCts(self, rtscts): + """Change RTS/CTS flow control setting.""" + self._rtscts = rtscts + if self._isOpen: self._reconfigurePort() + + def getRtsCts(self): + """Get the current RTS/CTS flow control setting.""" + return self._rtscts + + rtscts = property(getRtsCts, setRtsCts, doc="RTS/CTS flow control setting") + + def setDsrDtr(self, dsrdtr=None): + """Change DsrDtr flow control setting.""" + if dsrdtr is None: + # if not set, keep backwards compatibility and follow rtscts setting + self._dsrdtr = self._rtscts + else: + # if defined independently, follow its value + self._dsrdtr = dsrdtr + if self._isOpen: self._reconfigurePort() + + def getDsrDtr(self): + """Get the current DSR/DTR flow control setting.""" + return self._dsrdtr + + dsrdtr = property(getDsrDtr, setDsrDtr, "DSR/DTR flow control setting") + + def setInterCharTimeout(self, interCharTimeout): + """Change inter-character timeout setting.""" + if interCharTimeout is not None: + if interCharTimeout < 0: raise ValueError("Not a valid timeout: %r" % interCharTimeout) + try: + interCharTimeout + 1 # test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: %r" % interCharTimeout) + + self._interCharTimeout = interCharTimeout + if self._isOpen: self._reconfigurePort() + + def getInterCharTimeout(self): + """Get the current inter-character timeout setting.""" + return self._interCharTimeout + + interCharTimeout = property(getInterCharTimeout, setInterCharTimeout, doc="Inter-character timeout setting for read()") + + # - - - - - - - - - - - - - - - - - - - - - - - - + + _SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff', + 'dsrdtr', 'rtscts', 'timeout', 'writeTimeout', 'interCharTimeout') + + def getSettingsDict(self): + """\ + Get current port settings as a dictionary. For use with + applySettingsDict. + """ + return dict([(key, getattr(self, '_'+key)) for key in self._SETTINGS]) + + def applySettingsDict(self, d): + """\ + apply stored settings from a dictionary returned from + getSettingsDict. it's allowed to delete keys from the dictionary. these + values will simply left unchanged. + """ + for key in self._SETTINGS: + if d[key] != getattr(self, '_'+key): # check against internal "_" value + setattr(self, key, d[key]) # set non "_" value to use properties write function + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def __repr__(self): + """String representation of the current port settings and its state.""" + return "%s(port=%r, baudrate=%r, bytesize=%r, parity=%r, stopbits=%r, timeout=%r, xonxoff=%r, rtscts=%r, dsrdtr=%r)" % ( + self.__class__.__name__, + id(self), + self._isOpen, + self.portstr, + self.baudrate, + self.bytesize, + self.parity, + self.stopbits, + self.timeout, + self.xonxoff, + self.rtscts, + self.dsrdtr, + ) + + + # - - - - - - - - - - - - - - - - - - - - - - - - + # compatibility with io library + + def readable(self): return True + def writable(self): return True + def seekable(self): return False + def readinto(self, b): + data = self.read(len(b)) + n = len(data) + try: + b[:n] = data + except TypeError, err: + import array + if not isinstance(b, array.array): + raise err + b[:n] = array.array('b', data) + return n + + +if __name__ == '__main__': + import sys + s = SerialBase() + sys.stdout.write('port name: %s\n' % s.portstr) + sys.stdout.write('baud rates: %s\n' % s.getSupportedBaudrates()) + sys.stdout.write('byte sizes: %s\n' % s.getSupportedByteSizes()) + sys.stdout.write('parities: %s\n' % s.getSupportedParities()) + sys.stdout.write('stop bits: %s\n' % s.getSupportedStopbits()) + sys.stdout.write('%s\n' % s) diff --git a/scripts/serial/tools/__init__.py b/scripts/serial/tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/serial/tools/list_ports.py b/scripts/serial/tools/list_ports.py new file mode 100644 index 0000000000..d373a5566d --- /dev/null +++ b/scripts/serial/tools/list_ports.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +# portable serial port access with python +# this is a wrapper module for different platform implementations of the +# port enumeration feature +# +# (C) 2011-2013 Chris Liechti +# this is distributed under a free software license, see license.txt + +"""\ +This module will provide a function called comports that returns an +iterable (generator or list) that will enumerate available com ports. Note that +on some systems non-existent ports may be listed. + +Additionally a grep function is supplied that can be used to search for ports +based on their descriptions or hardware ID. +""" + +import sys, os, re + +# chose an implementation, depending on os +#~ if sys.platform == 'cli': +#~ else: +import os +# chose an implementation, depending on os +if os.name == 'nt': #sys.platform == 'win32': + from serial.tools.list_ports_windows import * +elif os.name == 'posix': + from serial.tools.list_ports_posix import * +#~ elif os.name == 'java': +else: + raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,)) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def grep(regexp): + """\ + Search for ports using a regular expression. Port name, description and + hardware ID are searched. The function returns an iterable that returns the + same tuples as comport() would do. + """ + r = re.compile(regexp, re.I) + for port, desc, hwid in comports(): + if r.search(port) or r.search(desc) or r.search(hwid): + yield port, desc, hwid + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def main(): + import optparse + + parser = optparse.OptionParser( + usage = "%prog [options] []", + description = "Miniterm - A simple terminal program for the serial port." + ) + + parser.add_option("--debug", + help="print debug messages and tracebacks (development mode)", + dest="debug", + default=False, + action='store_true') + + parser.add_option("-v", "--verbose", + help="show more messages (can be given multiple times)", + dest="verbose", + default=1, + action='count') + + parser.add_option("-q", "--quiet", + help="suppress all messages", + dest="verbose", + action='store_const', + const=0) + + (options, args) = parser.parse_args() + + + hits = 0 + # get iteraror w/ or w/o filter + if args: + if len(args) > 1: + parser.error('more than one regexp not supported') + print "Filtered list with regexp: %r" % (args[0],) + iterator = sorted(grep(args[0])) + else: + iterator = sorted(comports()) + # list them + for port, desc, hwid in iterator: + print("%-20s" % (port,)) + if options.verbose > 1: + print(" desc: %s" % (desc,)) + print(" hwid: %s" % (hwid,)) + hits += 1 + if options.verbose: + if hits: + print("%d ports found" % (hits,)) + else: + print("no ports found") + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + main() diff --git a/scripts/serial/tools/list_ports_linux.py b/scripts/serial/tools/list_ports_linux.py new file mode 100644 index 0000000000..955761eaa4 --- /dev/null +++ b/scripts/serial/tools/list_ports_linux.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python + +# portable serial port access with python +# +# This is a module that gathers a list of serial ports including details on +# GNU/Linux systems +# +# (C) 2011-2013 Chris Liechti +# this is distributed under a free software license, see license.txt + +import glob +import sys +import os +import re + +try: + import subprocess +except ImportError: + def popen(argv): + try: + si, so = os.popen4(' '.join(argv)) + return so.read().strip() + except: + raise IOError('lsusb failed') +else: + def popen(argv): + try: + return subprocess.check_output(argv, stderr=subprocess.STDOUT).strip() + except: + raise IOError('lsusb failed') + + +# The comports function is expected to return an iterable that yields tuples of +# 3 strings: port name, human readable description and a hardware ID. +# +# as currently no method is known to get the second two strings easily, they +# are currently just identical to the port name. + +# try to detect the OS so that a device can be selected... +plat = sys.platform.lower() + +def read_line(filename): + """\ + Helper function to read a single line from a file. + Returns None on errors.. + """ + try: + f = open(filename) + line = f.readline().strip() + f.close() + return line + except IOError: + return None + +def re_group(regexp, text): + """search for regexp in text, return 1st group on match""" + if sys.version < '3': + m = re.search(regexp, text) + else: + # text is bytes-like + m = re.search(regexp, text.decode('ascii', 'replace')) + if m: return m.group(1) + + +# try to extract descriptions from sysfs. this was done by experimenting, +# no guarantee that it works for all devices or in the future... + +def usb_sysfs_hw_string(sysfs_path): + """given a path to a usb device in sysfs, return a string describing it""" + bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-') + snr = read_line(sysfs_path+'/serial') + if snr: + snr_txt = ' SNR=%s' % (snr,) + else: + snr_txt = '' + return 'USB VID:PID=%s:%s%s' % ( + read_line(sysfs_path+'/idVendor'), + read_line(sysfs_path+'/idProduct'), + snr_txt + ) + +def usb_lsusb_string(sysfs_path): + base = os.path.basename(os.path.realpath(sysfs_path)) + bus = base.split('-')[0] + try: + dev = int(read_line(os.path.join(sysfs_path, 'devnum'))) + desc = popen(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)]) + # descriptions from device + iManufacturer = re_group('iManufacturer\s+\w+ (.+)', desc) + iProduct = re_group('iProduct\s+\w+ (.+)', desc) + iSerial = re_group('iSerial\s+\w+ (.+)', desc) or '' + # descriptions from kernel + idVendor = re_group('idVendor\s+0x\w+ (.+)', desc) + idProduct = re_group('idProduct\s+0x\w+ (.+)', desc) + # create descriptions. prefer text from device, fall back to the others + return '%s %s %s' % (iManufacturer or idVendor, iProduct or idProduct, iSerial) + except IOError: + return base + +def describe(device): + """\ + Get a human readable description. + For USB-Serial devices try to run lsusb to get a human readable description. + For USB-CDC devices read the description from sysfs. + """ + base = os.path.basename(device) + # USB-Serial devices + sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base) + if os.path.exists(sys_dev_path): + sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path))) + return usb_lsusb_string(sys_usb) + # USB-CDC devices + sys_dev_path = '/sys/class/tty/%s/device/interface' % (base,) + if os.path.exists(sys_dev_path): + return read_line(sys_dev_path) + # USB Product Information + sys_dev_path = '/sys/class/tty/%s/device' % (base,) + if os.path.exists(sys_dev_path): + product_name_file = os.path.dirname(os.path.realpath(sys_dev_path)) + "/product" + if os.path.exists(product_name_file): + return read_line(product_name_file) + return base + +def hwinfo(device): + """Try to get a HW identification using sysfs""" + base = os.path.basename(device) + if os.path.exists('/sys/class/tty/%s/device' % (base,)): + # PCI based devices + sys_id_path = '/sys/class/tty/%s/device/id' % (base,) + if os.path.exists(sys_id_path): + return read_line(sys_id_path) + # USB-Serial devices + sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base) + if os.path.exists(sys_dev_path): + sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path))) + return usb_sysfs_hw_string(sys_usb) + # USB-CDC devices + if base.startswith('ttyACM'): + sys_dev_path = '/sys/class/tty/%s/device' % (base,) + if os.path.exists(sys_dev_path): + return usb_sysfs_hw_string(sys_dev_path + '/..') + return 'n/a' # XXX directly remove these from the list? + +def comports(): + devices = glob.glob('/dev/ttyS*') + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') + return [(d, describe(d), hwinfo(d)) for d in devices] + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# test +if __name__ == '__main__': + for port, desc, hwid in sorted(comports()): + print "%s: %s [%s]" % (port, desc, hwid) diff --git a/scripts/serial/urlhandler/__init__.py b/scripts/serial/urlhandler/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/serial/urlhandler/protocol_hwgrep.py b/scripts/serial/urlhandler/protocol_hwgrep.py new file mode 100644 index 0000000000..62cda43aa7 --- /dev/null +++ b/scripts/serial/urlhandler/protocol_hwgrep.py @@ -0,0 +1,45 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a special URL handler that uses the port listing to +# find ports by searching the string descriptions. +# +# (C) 2011 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# URL format: hwgrep://regexp + +import serial +import serial.tools.list_ports + +class Serial(serial.Serial): + """Just inherit the native Serial port implementation and patch the open function.""" + + def setPort(self, value): + """translate port name before storing it""" + if isinstance(value, basestring) and value.startswith('hwgrep://'): + serial.Serial.setPort(self, self.fromURL(value)) + else: + serial.Serial.setPort(self, value) + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("hwgrep://"): url = url[9:] + # use a for loop to get the 1st element from the generator + for port, desc, hwid in serial.tools.list_ports.grep(url): + return port + else: + raise serial.SerialException('no ports found matching regexp %r' % (url,)) + + # override property + port = property(serial.Serial.getPort, setPort, doc="Port setting") + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == '__main__': + #~ s = Serial('hwgrep://ttyS0') + s = Serial(None) + s.port = 'hwgrep://ttyS0' + print s + diff --git a/scripts/serial/urlhandler/protocol_loop.py b/scripts/serial/urlhandler/protocol_loop.py new file mode 100644 index 0000000000..a414839761 --- /dev/null +++ b/scripts/serial/urlhandler/protocol_loop.py @@ -0,0 +1,279 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a loop back connection receiving itself what it sent. +# +# The purpose of this module is.. well... You can run the unit tests with it. +# and it was so easy to implement ;-) +# +# (C) 2001-2011 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# URL format: loop://[option[/option...]] +# options: +# - "debug" print diagnostic messages + +from serial.serialutil import * +import threading +import time +import logging + +# map log level names to constants. used in fromURL() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + } + + +class LoopbackSerial(SerialBase): + """Serial port implementation that simulates a loop back connection in plain software.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + if self._isOpen: + raise SerialException("Port is already open.") + self.logger = None + self.buffer_lock = threading.Lock() + self.loop_buffer = bytearray() + self.cts = False + self.dsr = False + + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + # not that there is anything to open, but the function applies the + # options found in the URL + self.fromURL(self.port) + + # not that there anything to configure... + self._reconfigurePort() + # all things set up get, now a clean start + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """\ + Set communication parameters on opened port. For the loop:// + protocol all settings are ignored! + """ + # not that's it of any real use, but it helps in the unit tests + if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32: + raise ValueError("invalid baudrate: %r" % (self._baudrate)) + if self.logger: + self.logger.info('_reconfigurePort()') + + def close(self): + """Close port""" + if self._isOpen: + self._isOpen = False + # in case of quick reconnects, give the server some time + time.sleep(0.3) + + def makeDeviceName(self, port): + raise SerialException("there is no sensible way to turn numbers into URLs") + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("loop://"): url = url[7:] + try: + # process options now, directly altering self + for option in url.split('/'): + if '=' in option: + option, value = option.split('=', 1) + else: + value = None + if not option: + pass + elif option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.loop') + self.logger.setLevel(LOGGER_LEVELS[value]) + self.logger.debug('enabled logging') + else: + raise ValueError('unknown option: %r' % (option,)) + except ValueError, e: + raise SerialException('expected a string in the form "[loop://][option[/option...]]": %s' % e) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + # attention the logged value can differ from return value in + # threaded environments... + self.logger.debug('inWaiting() -> %d' % (len(self.loop_buffer),)) + return len(self.loop_buffer) + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self._isOpen: raise portNotOpenError + if self._timeout is not None: + timeout = time.time() + self._timeout + else: + timeout = None + data = bytearray() + while size > 0: + self.buffer_lock.acquire() + try: + block = to_bytes(self.loop_buffer[:size]) + del self.loop_buffer[:size] + finally: + self.buffer_lock.release() + data += block + size -= len(block) + # check for timeout now, after data has been read. + # useful for timeout = 0 (non blocking) read + if timeout and time.time() > timeout: + break + return bytes(data) + + def write(self, data): + """\ + Output the given string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed. + """ + if not self._isOpen: raise portNotOpenError + # ensure we're working with bytes + data = to_bytes(data) + # calculate aprox time that would be used to send the data + time_used_to_send = 10.0*len(data) / self._baudrate + # when a write timeout is configured check if we would be successful + # (not sending anything, not even the part that would have time) + if self._writeTimeout is not None and time_used_to_send > self._writeTimeout: + time.sleep(self._writeTimeout) # must wait so that unit test succeeds + raise writeTimeoutError + self.buffer_lock.acquire() + try: + self.loop_buffer += data + finally: + self.buffer_lock.release() + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('flushInput()') + self.buffer_lock.acquire() + try: + del self.loop_buffer[:] + finally: + self.buffer_lock.release() + + def flushOutput(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('flushOutput()') + + def sendBreak(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self._isOpen: raise portNotOpenError + + def setBreak(self, level=True): + """\ + Set break: Controls TXD. When active, to transmitting is + possible. + """ + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('setBreak(%r)' % (level,)) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('setRTS(%r) -> state of CTS' % (level,)) + self.cts = level + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('setDTR(%r) -> state of DSR' % (level,)) + self.dsr = level + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('getCTS() -> state of RTS (%r)' % (self.cts,)) + return self.cts + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('getDSR() -> state of DTR (%r)' % (self.dsr,)) + return self.dsr + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getRI()') + return False + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getCD()') + return True + + # - - - platform specific - - - + # None so far + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(LoopbackSerial, FileLike): + pass +else: + # io library present + class Serial(LoopbackSerial, io.RawIOBase): + pass + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('loop://') + sys.stdout.write('%s\n' % s) + + sys.stdout.write("write...\n") + s.write("hello\n") + s.flush() + sys.stdout.write("read: %s\n" % s.read(5)) + + s.close() diff --git a/scripts/serial/urlhandler/protocol_rfc2217.py b/scripts/serial/urlhandler/protocol_rfc2217.py new file mode 100644 index 0000000000..981ba45fea --- /dev/null +++ b/scripts/serial/urlhandler/protocol_rfc2217.py @@ -0,0 +1,11 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see ../__init__.py +# +# This is a thin wrapper to load the rfc2271 implementation. +# +# (C) 2011 Chris Liechti +# this is distributed under a free software license, see license.txt + +from serial.rfc2217 import Serial diff --git a/scripts/serial/urlhandler/protocol_socket.py b/scripts/serial/urlhandler/protocol_socket.py new file mode 100644 index 0000000000..dc5992342c --- /dev/null +++ b/scripts/serial/urlhandler/protocol_socket.py @@ -0,0 +1,291 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a simple socket based client. +# It does not support changing any port parameters and will silently ignore any +# requests to do so. +# +# The purpose of this module is that applications using pySerial can connect to +# TCP/IP to serial port converters that do not support RFC 2217. +# +# (C) 2001-2011 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# URL format: socket://:[/option[/option...]] +# options: +# - "debug" print diagnostic messages + +from serial.serialutil import * +import time +import socket +import select +import logging + +# map log level names to constants. used in fromURL() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + } + +POLL_TIMEOUT = 2 + +class SocketSerial(SerialBase): + """Serial port implementation for plain sockets.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + self.logger = None + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self._isOpen: + raise SerialException("Port is already open.") + try: + # XXX in future replace with create_connection (py >=2.6) + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect(self.fromURL(self.portstr)) + except Exception, msg: + self._socket = None + raise SerialException("Could not open port %s: %s" % (self.portstr, msg)) + + self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/ + + # not that there anything to configure... + self._reconfigurePort() + # all things set up get, now a clean start + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """\ + Set communication parameters on opened port. For the socket:// + protocol all settings are ignored! + """ + if self._socket is None: + raise SerialException("Can only operate on open ports") + if self.logger: + self.logger.info('ignored port configuration change') + + def close(self): + """Close port""" + if self._isOpen: + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + # ignore errors. + pass + self._socket = None + self._isOpen = False + # in case of quick reconnects, give the server some time + time.sleep(0.3) + + def makeDeviceName(self, port): + raise SerialException("there is no sensible way to turn numbers into URLs") + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("socket://"): url = url[9:] + try: + # is there a "path" (our options)? + if '/' in url: + # cut away options + url, options = url.split('/', 1) + # process options now, directly altering self + for option in options.split('/'): + if '=' in option: + option, value = option.split('=', 1) + else: + value = None + if option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.socket') + self.logger.setLevel(LOGGER_LEVELS[value]) + self.logger.debug('enabled logging') + else: + raise ValueError('unknown option: %r' % (option,)) + # get host and port + host, port = url.split(':', 1) # may raise ValueError because of unpacking + port = int(port) # and this if it's not a number + if not 0 <= port < 65536: raise ValueError("port not in range 0...65535") + except ValueError, e: + raise SerialException('expected a string in the form "[rfc2217://]:[/option[/option...]]": %s' % e) + return (host, port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._isOpen: raise portNotOpenError + # Poll the socket to see if it is ready for reading. + # If ready, at least one byte will be to read. + lr, lw, lx = select.select([self._socket], [], [], 0) + return len(lr) + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self._isOpen: raise portNotOpenError + data = bytearray() + if self._timeout is not None: + timeout = time.time() + self._timeout + else: + timeout = None + while len(data) < size and (timeout is None or time.time() < timeout): + try: + # an implementation with internal buffer would be better + # performing... + t = time.time() + block = self._socket.recv(size - len(data)) + duration = time.time() - t + if block: + data.extend(block) + else: + # no data -> EOF (connection probably closed) + break + except socket.timeout: + # just need to get out of recv from time to time to check if + # still alive + continue + except socket.error, e: + # connection fails -> terminate loop + raise SerialException('connection failed (%s)' % e) + return bytes(data) + + def write(self, data): + """\ + Output the given string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed. + """ + if not self._isOpen: raise portNotOpenError + try: + self._socket.sendall(to_bytes(data)) + except socket.error, e: + # XXX what exception if socket connection fails + raise SerialException("socket connection failed: %s" % e) + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored flushInput') + + def flushOutput(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored flushOutput') + + def sendBreak(self, duration=0.25): + """\ + Send break condition. Timed, returns to idle state after given + duration. + """ + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored sendBreak(%r)' % (duration,)) + + def setBreak(self, level=True): + """Set break: Controls TXD. When active, to transmitting is + possible.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored setBreak(%r)' % (level,)) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored setRTS(%r)' % (level,)) + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored setDTR(%r)' % (level,)) + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getCTS()') + return True + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getDSR()') + return True + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getRI()') + return False + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getCD()') + return True + + # - - - platform specific - - - + + # works on Linux and probably all the other POSIX systems + def fileno(self): + """Get the file handle of the underlying socket for use with select""" + return self._socket.fileno() + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(SocketSerial, FileLike): + pass +else: + # io library present + class Serial(SocketSerial, io.RawIOBase): + pass + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('socket://localhost:7000') + sys.stdout.write('%s\n' % s) + + sys.stdout.write("write...\n") + s.write("hello\n") + s.flush() + sys.stdout.write("read: %s\n" % s.read(5)) + + s.close() -- cgit v1.2.3 From 06fc3557c94c2b9d43bcfab72f4a024c7860be64 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 16 Dec 2015 16:49:18 +0100 Subject: pyserial: decrease timeouts pyserial has very generous timeouts which introduces quite big latencies at least when used on rfc2217 ports. Decrease timeouts to make it more reactive. Signed-off-by: Sascha Hauer --- scripts/serial/rfc2217.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/serial/rfc2217.py b/scripts/serial/rfc2217.py index 4fe1a72f19..b65edd55c8 100644 --- a/scripts/serial/rfc2217.py +++ b/scripts/serial/rfc2217.py @@ -341,9 +341,9 @@ class TelnetSubnegotiation(object): """ timeout_time = time.time() + timeout while time.time() < timeout_time: - time.sleep(0.05) # prevent 100% CPU load if self.isReady(): break + time.sleep(0.001) # prevent 100% CPU load else: raise SerialException("timeout while waiting for option %r" % (self.name)) @@ -443,9 +443,9 @@ class RFC2217Serial(SerialBase): # now wait until important options are negotiated timeout_time = time.time() + self._network_timeout while time.time() < timeout_time: - time.sleep(0.05) # prevent 100% CPU load if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options): break + time.sleep(0.001) # prevent 100% CPU load else: raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options) if self.logger: @@ -488,9 +488,9 @@ class RFC2217Serial(SerialBase): self.logger.debug("Negotiating settings: %s" % (items,)) timeout_time = time.time() + self._network_timeout while time.time() < timeout_time: - time.sleep(0.05) # prevent 100% CPU load if sum(o.active for o in items) == len(items): break + time.sleep(0.001) # prevent 100% CPU load else: raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items) if self.logger: @@ -865,13 +865,13 @@ class RFC2217Serial(SerialBase): self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE) timeout_time = time.time() + self._network_timeout while time.time() < timeout_time: - time.sleep(0.05) # prevent 100% CPU load # when expiration time is updated, it means that there is a new # value if self._modemstate_expires > time.time(): if self.logger: self.logger.warning('poll for modem state failed') break + time.sleep(0.001) # prevent 100% CPU load # even when there is a timeout, do not generate an error just # return the last known value. this way we can support buggy # servers that do not respond to polls, but send automatic -- cgit v1.2.3 From be6c6e653683e86d4c7aeb2b67c62ec0895befa3 Mon Sep 17 00:00:00 2001 From: Jan Luebbe Date: Fri, 5 Jun 2015 09:18:56 +0200 Subject: host side for barebox remote control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Signed-off-by: Jan Lübbe Signed-off-by: Sascha Hauer Signed-off-by: Andrey Smirnov Tested-by: Andrey Smirnov --- scripts/bbremote | 3 + scripts/remote/__init__.py | 0 scripts/remote/controller.py | 173 ++++++++++ scripts/remote/main.py | 168 +++++++++ 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 +++ 9 files changed, 1535 insertions(+) create mode 100755 scripts/bbremote create mode 100644 scripts/remote/__init__.py 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 0000000000..bc5351dbae --- /dev/null +++ b/scripts/bbremote @@ -0,0 +1,3 @@ +#!/usr/bin/env python2 + +import remote.main diff --git a/scripts/remote/__init__.py b/scripts/remote/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/remote/controller.py b/scripts/remote/controller.py new file mode 100644 index 0000000000..a7257ecc97 --- /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 0000000000..bd304723bb --- /dev/null +++ b/scripts/remote/main.py @@ -0,0 +1,168 @@ +#!/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: + src, data = queue.get(block=True) + 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 0000000000..8e8495b12e --- /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 0000000000..67c2dfa8c0 --- /dev/null +++ b/scripts/remote/missing.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import ctypes +import os + +CLOCK_MONOTONIC_RAW = 4 # from + + +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 0000000000..079fb871a3 --- /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 0000000000..91ca044540 --- /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(os.path.expanduser(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 0000000000..db249892ac --- /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()) + -- cgit v1.2.3 From a73734c3c03f2754ca4a56d7d95e4ae0fb325bd3 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 17 Dec 2015 11:26:06 +0100 Subject: defaultenv2: Add automount for RATPFS Signed-off-by: Sascha Hauer --- defaultenv/defaultenv-2-base/init/automount-ratp | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 defaultenv/defaultenv-2-base/init/automount-ratp diff --git a/defaultenv/defaultenv-2-base/init/automount-ratp b/defaultenv/defaultenv-2-base/init/automount-ratp new file mode 100644 index 0000000000..6474df8348 --- /dev/null +++ b/defaultenv/defaultenv-2-base/init/automount-ratp @@ -0,0 +1,6 @@ +#!/bin/sh + +# automount ratp filesystem + +mkdir -p /mnt/ratp +automount /mnt/ratp 'mount -t ratpfs none /mnt/ratp' \ No newline at end of file -- cgit v1.2.3 From 36d5eea256ff81ddb108180f655dd6921fe70e8d Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 8 Jan 2016 12:06:38 +0100 Subject: barebox remote control: Documentation Signed-off-by: Sascha Hauer --- Documentation/user/remote-control.rst | 134 ++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 Documentation/user/remote-control.rst diff --git a/Documentation/user/remote-control.rst b/Documentation/user/remote-control.rst new file mode 100644 index 0000000000..b9275cea80 --- /dev/null +++ b/Documentation/user/remote-control.rst @@ -0,0 +1,134 @@ +barebox remote control +====================== + +barebox remote control is for controlling barebox from a remote host via +scripts. The barebox console is designed for human interaction, +controlling it from a script is very error prone since UARTs do not +offer reliable communication. Usually a tool like 'expect' is used for +this purpose which uses its own language to communicate with the remote +partner. The barebox remote control offers an alternative. barebox +commands can be integrated into regular shell scripts running on the +host: + +.. code-block:: sh + + bbremote --port /dev/ttyUSB0 run "ls" + +Additionally files can be transferred from/to barebox and a regular +console offers interactive access to barebox on flawy serial +connections. + +Enabling remote control support +------------------------------- + +To get remote control support barebox has to be compiled with +CONFIG_RATP and CONFIG_CONSOLE_RATP enabled. Optionally CONFIG_FS_RATP +can also be enabled. + +Running the bbremote tool +------------------------- + +The bbremote host tool is written in python. To run it python2 has to be +installed with the following additional packages: + ++----------------+---------------------+ +| python package | Debian package name | ++================+=====================+ +| crcmod | python-crcmod | ++----------------+---------------------+ +| enum | python-enum | ++----------------+---------------------+ +| enum34 | python-enum34 | ++----------------+---------------------+ + +If your distribution does not provide aforementioned packages, you can +use 'pip' in order to install the dependencies localy to your user +account via: + +.. code-block:: sh + + pip install --user crcmod enum enum34 + +configuring bbremote +^^^^^^^^^^^^^^^^^^^^ + +bbremote needs the port and possibly the baudrate to access the remote +barebox. The port can be configured with the ``--baudrate`` option or +with the ``BBREMOTE_PORT`` environment variable. The port can either be +the device special file if it's a local port or if it's a remote port a +string of the form: ``rfc2217://host:port``. The baudrate can be given +with the ``--baudrate`` option or the ``BBREMOTE_BAUDRATE`` environment +variable. For the rest of this document it is assumed that ``bbremote`` +has been configured using environment variables. + +running commands on the target +------------------------------ + +``bbremote`` can be used to run arbitrary commands on the remote +barebox: + +.. code-block:: sh + + bbremote run "echo huhu" + huhu + +The bbremote exit status will be 0 if the remote command exited +successfully, 1 if the remote command failed and 127 if there was a +communication error. + +**NOTE** It is possible to put the output into a shell variable for +further processing, like ``RESULT=$(bbremote run "echo huhu")``. +However, this string may contain unexpected messages from drivers and +the like because currently we cannot filter out driver messages and +messages to stderr. + +ping +---- + +This is a simple ping test. + +.. code-block:: sh + + bbremote ping + pong + +getenv +------ + +.. code-block:: sh + + bbremote getenv global.version + 2015.12.0-00150-g81cd49f + +interactive console +------------------- + +The bbremote tool also offers a regular interactive console to barebox. +This is especially useful for flawy serial connections. + +.. code-block:: sh + + bbremote console + barebox@Phytec phyFLEX-i.MX6 Quad Carrier-Board:/ ls + . .. dev env mnt + +*NOTE** To terminate resulting Barebox console session press 'Ctrl-T' + +**NOTE** You can also send 'ping' request to the target without + closing console session by pressint 'Ctrl-P' + +transferring files +------------------ + +With the bbremote tool it's possible to transfer files both from the +host to barebox and from barebox to the host. Using the ``--export`` +option to bbremote a directory can be specified to export to barebox. +This can be mounted on barebox using the regular mount command using +``-t ratpfs`` as filesystem type. + +.. code-block:: sh + + bbremote --export=somedir console + mkdir -p /ratpfs; mount -t ratpfs none /ratpfs + ls /ratpfs + -- cgit v1.2.3