From 8004e38403746552c5b371208daadb90beb686be Mon Sep 17 00:00:00 2001 From: Joerg Wunsch Date: Mon, 22 Nov 2021 21:35:26 +0000 Subject: [PATCH] patch #8719: Support Over-the-Air bootloading with XBeeBoot Submitted by David Sainty: * xbee.c: New programmer * xbee.h: (Dito.) * pgm_type.c: Add xbee.h * avrdude.conf.in (xbee): New programmer * Makefile.am (libavrdude_a_SOURCES): add xbee.c, xbee.h * avrdude.1: document the new programmer * doc/avrdude.texi: (Dito.) git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@1477 81a1dc3b-b13d-400b-aceb-764788c761c2 --- AUTHORS | 1 + ChangeLog | 12 + Makefile.am | 4 +- NEWS | 2 + avrdude.1 | 35 +- avrdude.conf.in | 8 + doc/avrdude.texi | 35 +- pgm_type.c | 2 + xbee.c | 1783 ++++++++++++++++++++++++++++++++++++++++++++++ xbee.h | 27 + 10 files changed, 1905 insertions(+), 4 deletions(-) create mode 100644 xbee.c create mode 100644 xbee.h diff --git a/AUTHORS b/AUTHORS index 8ed4870b..45a4de79 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,6 +25,7 @@ Contributors: Jim Paris Jan Egil Ruud David Mosberger-Tang + David Sainty For minor contributions, please see the ChangeLog files. diff --git a/ChangeLog b/ChangeLog index 2c86747b..55dfad1d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2021-11-22 Joerg Wunsch + + Submitted by David Sainty: + patch #8719: Support Over-the-Air bootloading with XBeeBoot + * xbee.c: New programmer + * xbee.h: (Dito.) + * pgm_type.c: Add xbee.h + * avrdude.conf.in (xbee): New programmer + * Makefile.am (libavrdude_a_SOURCES): add xbee.c, xbee.h + * avrdude.1: document the new programmer + * doc/avrdude.texi: (Dito.) + 2021-11-14 Joerg Wunsch Submitted by Ivan Frederiks: diff --git a/Makefile.am b/Makefile.am index b9929897..2b812d17 100644 --- a/Makefile.am +++ b/Makefile.am @@ -190,7 +190,9 @@ libavrdude_a_SOURCES = \ usbtiny.c \ update.c \ wiring.h \ - wiring.c + wiring.c \ + xbee.h \ + xbee.c libavrdude_la_SOURCES = $(libavrdude_a_SOURCES) libavrdude_la_LDFLAGS = -version-info 1:0 diff --git a/NEWS b/NEWS index b54282c6..727af752 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,7 @@ Current: - linuxspi (direct SPI bus e.g. on Raspberry Pi devices) - PICkit4, Snap, PKoB - iseavrprog + - XBeeBoot * Bugfixes: bug #47550: Linux GPIO broken @@ -110,6 +111,7 @@ Current: patch #9079: Fix ftdi_syncbb teardown (supersedes #9893) patch #9122: Fixed MISO sampling in ftdi_syncbb patch #9123: ftdi_syncbb: use FT245R_CYCLES in ft245r_set_bitclock() + patch #8719: Support Over-the-Air bootloading with XBeeBoot * Internals: - New avrdude.conf keyword "family_id", used to verify SIB attributes diff --git a/avrdude.1 b/avrdude.1 index d6c43f96..01e7870e 100644 --- a/avrdude.1 +++ b/avrdude.1 @@ -18,7 +18,7 @@ .\" .\" $Id$ .\" -.Dd DATE September 19, 2020 +.Dd DATE November 22, 2021 .Os .Dt AVRDUDE 1 .Sh NAME @@ -531,6 +531,22 @@ location in the USB hierarchy. See the the respective .Em Troubleshooting entry in the detailed documentation for examples. .Pp +For the XBee programmer the target MCU is to be programmed wirelessly over a +ZigBee mesh using the XBeeBoot bootloader. The ZigBee 64-bit address for the +target MCU's own XBee device must be supplied as a 16-character hexadecimal +value as a +.Ar port +prefix, followed by the +.Ql @ +character, and the serial device to connect to a second directly contactable +XBee device associated with the same mesh (with a default baud rate of 9600). +This may look similar to: +.Pa 0013a20000000001@/dev/tty.serial . +.Pp +For diagnostic purposes, if the target MCU with an XBeeBoot bootloader is +connected directly to the serial port, the 64-bit address field can be +omitted. In this mode the default baud rate will be 19200. +.Pp For programmers that attach to a serial port using some kind of higher level protocol (as opposed to bit-bang style programmers), .Ar port @@ -1100,6 +1116,23 @@ Programmer will erase configuration section with option (chip erase), rather than entire chip. Only applicable to TPI devices (ATtiny 4/5/9/10/20/40). .El +.It Ar xbee +Extended parameters: +.Bl -tag -offset indent -width indent +.It Ar xbeeresetpin=<1..7> +Select the XBee pin DIO<1..7> that is connected to the MCU's +.Ql /RESET +line. The programmer needs to know which DIO pin to use to reset into the +bootloader. The default (3) is the DIO3 pin (XBee pin 17), but some +commercial products use a different XBee pin. +.Pp +The remaining two necessary XBee-to-MCU connections are not selectable - the +XBee DOUT pin (pin 2) must be connected to the MCU's +.Ql RXD +line, and the XBee DIN pin (pin 3) must be connected to the MCU's +.Ql TXD +line. +.El .El .Sh FILES .Bl -tag -offset indent -width /dev/ppi0XXX diff --git a/avrdude.conf.in b/avrdude.conf.in index cd7fa405..d8bd8a28 100644 --- a/avrdude.conf.in +++ b/avrdude.conf.in @@ -363,6 +363,14 @@ programmer type = "arduino"; connection_type = serial; ; + +programmer + id = "xbee"; + desc = "XBee Series 2 Over-The-Air (XBeeBoot)"; + type = "xbee"; + connection_type = serial; +; + # this will interface with the chips on these programmers: # # http://real.kiev.ua/old/avreal/en/adapters diff --git a/doc/avrdude.texi b/doc/avrdude.texi index eaa81b31..72858727 100644 --- a/doc/avrdude.texi +++ b/doc/avrdude.texi @@ -30,7 +30,7 @@ For avrdude version @value{VERSION}, @value{UPDATED}. Copyright @copyright{} 2003, 2005 Brian Dean -Copyright @copyright{} 2006 - 2016 J@"org Wunsch +Copyright @copyright{} 2006 - 2021 J@"org Wunsch Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice @@ -584,7 +584,22 @@ and Mac OS but not on Windows. For more information about AVR-Doper see For the USBtinyISP, which is a simplistic device not implementing serial numbers, multiple devices can be distinguished by their location in the USB hierarchy. -@xref{Troubleshooting}, for examples. +See the respective +@xref{Troubleshooting} entry for examples. + +For the XBee programmer the target MCU is to be programmed wirelessly +over a ZigBee mesh using the XBeeBoot bootloader. The ZigBee 64-bit +address for the target MCU's own XBee device must be supplied as a +16-character hexadecimal value as a port prefix, followed by the +@code{@@} character, and the serial device to connect to a second +directly contactable XBee device associated with the same mesh (with +a default baud rate of 9600). This may look similar to: +@code{0013a20000000001@/dev/tty.serial}. + +For diagnostic purposes, if the target MCU with an XBeeBoot +bootloader is connected directly to the serial port, the +64-bit address field can be omitted. In this mode the +default baud rate will be 19200. For programmers that attach to a serial port using some kind of higher level protocol (as opposed to bit-bang style programmers), @@ -959,6 +974,22 @@ rather than entire chip. Only applicable to TPI devices (ATtiny 4/5/9/10/20/40). @end table +@item xbee +Extended parameters: +@table @code +@item @samp{xbeeresetpin=@var{1..7}} +Select the XBee pin @code{DIO<1..7>} that is connected to the MCU's +‘/RESET’ line. The programmer needs to know which DIO pin to use to +reset into the bootloader. The default (3) is the @code{DIO3} pin +(XBee pin 17), but some commercial products use a different XBee +pin. + +The remaining two necessary XBee-to-MCU connections are not selectable +- the XBee @code{DOUT} pin (pin 2) must be connected to the MCU's +‘RXD’ line, and the XBee @code{DIN} pin (pin 3) must be connected to +the MCU's ‘TXD’ line. +@end table + @end table @page diff --git a/pgm_type.c b/pgm_type.c index 4297f4b9..9f28f441 100644 --- a/pgm_type.c +++ b/pgm_type.c @@ -51,6 +51,7 @@ #include "usbasp.h" #include "usbtiny.h" #include "wiring.h" +#include "xbee.h" const PROGRAMMER_TYPE programmers_types[] = { @@ -97,6 +98,7 @@ const PROGRAMMER_TYPE programmers_types[] = { {"usbasp", usbasp_initpgm, usbasp_desc}, {"usbtiny", usbtiny_initpgm, usbtiny_desc}, {"wiring", wiring_initpgm, wiring_desc}, + {"xbee", xbee_initpgm, xbee_desc}, }; const PROGRAMMER_TYPE * locate_programmer_type(const char * id) diff --git a/xbee.c b/xbee.c new file mode 100644 index 00000000..454dc6de --- /dev/null +++ b/xbee.c @@ -0,0 +1,1783 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2015-2020 David Sainty + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* $Id$ */ + +/* + * avrdude interface for AVR devices Over-The-Air programmable via an + * XBee Series 2 device. + * + * The XBee programmer is STK500v1 (optiboot) encapsulated in the XBee + * API protocol. The bootloader supporting this protocol is available at: + * + * https://github.com/davidsainty/xbeeboot + */ + +#include "ac_cfg.h" + +#include /* gettimeofday() */ + +#include /* sscanf() */ +#include /* malloc() */ +#include /* memmove() etc. */ +#include /* usleep() */ + +#include "avrdude.h" +#include "libavrdude.h" +#include "stk500_private.h" +#include "stk500.h" +#include "xbee.h" + +/* + * For non-direct mode (Over-The-Air) we need to issue XBee commands + * to the remote XBee in order to reset the AVR CPU and initiate the + * XBeeBoot bootloader. + * + * XBee IO port 3 is a somewhat-arbitrarily chosen pin that can be + * connected directly to the AVR reset pin. + * + * Note that port 7 was not used because it is the only pin that can + * be used as a CTS flow control output. Port 6 is the only pin that + * can be used as an RTS flow control input. + * + * Some off-the-shelf Arduino shields select a different pin. For + * example this one uses XBee IO port 7. + * + * https://wiki.dfrobot.com/Xbee_Shield_For_Arduino__no_Xbee___SKU_DFR0015_ + */ +#ifndef XBEE_DEFAULT_RESET_PIN +#define XBEE_DEFAULT_RESET_PIN 3 +#endif + +/* + * After eight seconds the AVR bootloader watchdog will kick in. But + * to allow for the possibility of eight seconds upstream and another + * eight seconds downstream, allow for 16 retries (of roughly one + * second each). + */ +#ifndef XBEE_MAX_RETRIES +#define XBEE_MAX_RETRIES 16 +#endif + +/* + * Maximum chunk size, which is the maximum encapsulated payload to be + * delivered to the remote CPU. + * + * There is an additional overhead of 3 bytes encapsulation, one + * "REQUEST" byte, one sequence number byte, and one + * "FIRMWARE_DELIVER" request type. + * + * The ZigBee maximum (unfragmented) payload is 84 bytes. Source + * routing decreases that by two bytes overhead, plus two bytes per + * hop. Maximum hop support is for 11 or 25 hops depending on + * firmware. + * + * Network layer encryption decreases the maximum payload by 18 bytes. + * APS end-to-end encryption decreases the maximum payload by 9 bytes. + * Both these layers are available in concert, as seen in the section + * "Network and APS layer encryption", decreasing our maximum payload + * by both 18 bytes and 9 bytes. + * + * Our maximum payload size should therefore ideally be 84 - 18 - 9 = + * 57 bytes, and therefore a chunk size of 54 bytes for zero hops. + * + * Source: XBee X2C manual: "Maximum RF payload size" section for most + * details; "Network layer encryption and decryption" section for the + * reference to 18 bytes of overhead; and "Enable APS encryption" for + * the reference to 9 bytes of overhead. + */ +#ifndef XBEEBOOT_MAX_CHUNK +#define XBEEBOOT_MAX_CHUNK 54 +#endif + +/* + * Maximum source route intermediate hops. This is described in the + * documentation variously as 40 hops (routing table); OR 25 hops + * (firmware 4x58 or later); OR 11 hops (firmware earlier than 4x58). + * + * What isn't described is how to know if a given source route length + * is actually supported by the mesh for our target device. + */ +#ifndef XBEE_MAX_INTERMEDIATE_HOPS +#define XBEE_MAX_INTERMEDIATE_HOPS 40 +#endif + +/* Protocol */ +#define XBEEBOOT_PACKET_TYPE_ACK 0 +#define XBEEBOOT_PACKET_TYPE_REQUEST 1 + +/* + * Read signature bytes - Direct copy of the Arduino behaviour to + * satisfy Optiboot. + */ +static int xbee_read_sig_bytes(PROGRAMMER *pgm, AVRPART *p, AVRMEM *m) +{ + unsigned char buf[32]; + + /* Signature byte reads are always 3 bytes. */ + + if (m->size < 3) { + avrdude_message(MSG_INFO, "%s: memsize too small for sig byte read\n", + progname); + return -1; + } + + buf[0] = Cmnd_STK_READ_SIGN; + buf[1] = Sync_CRC_EOP; + + serial_send(&pgm->fd, buf, 2); + + if (serial_recv(&pgm->fd, buf, 5) < 0) + return -1; + if (buf[0] == Resp_STK_NOSYNC) { + avrdude_message(MSG_INFO, "%s: stk500_cmd(): programmer is out of sync\n", + progname); + return -1; + } else if (buf[0] != Resp_STK_INSYNC) { + avrdude_message(MSG_INFO, + "\n%s: xbee_read_sig_bytes(): (a) protocol error, " + "expect=0x%02x, resp=0x%02x\n", + progname, Resp_STK_INSYNC, buf[0]); + return -2; + } + if (buf[4] != Resp_STK_OK) { + avrdude_message(MSG_INFO, + "\n%s: xbee_read_sig_bytes(): (a) protocol error, " + "expect=0x%02x, resp=0x%02x\n", + progname, Resp_STK_OK, buf[4]); + return -3; + } + + m->buf[0] = buf[1]; + m->buf[1] = buf[2]; + m->buf[2] = buf[3]; + + return 3; +} + +struct XBeeSequenceStatistics { + struct timeval sendTime; +}; + +struct XBeeStaticticsSummary { + struct timeval minimum; + struct timeval maximum; + struct timeval sum; + unsigned long samples; +}; + +#define XBEE_STATS_GROUPS 4 +#define XBEE_STATS_FRAME_LOCAL 0 +#define XBEE_STATS_FRAME_REMOTE 1 +#define XBEE_STATS_TRANSMIT 2 +#define XBEE_STATS_RECEIVE 3 + +static const char* groupNames[] = + { + "FRAME_LOCAL", + "FRAME_REMOTE", + "TRANSMIT", + "RECEIVE" + }; + +struct XBeeBootSession { + struct serial_device *serialDevice; + union filedescriptor serialDescriptor; + + unsigned char xbee_address[10]; + int directMode; + unsigned char outSequence; + unsigned char inSequence; + + /* + * XBee API frame sequence number. + */ + unsigned char txSequence; + + /* + * Set to non-zero if the transport is broken to the point it is + * considered unusable. + */ + int transportUnusable; + + int xbeeResetPin; + + size_t inInIndex; + size_t inOutIndex; + unsigned char inBuffer[256]; + + int sourceRouteHops; /* -1 if unset */ + int sourceRouteChanged; + + /* + * The source route is an array of intermediate 16 bit addresses, + * starting with the address nearest to the target address, and + * finishing with the address closest to our local device. + */ + unsigned char sourceRoute[2 * XBEE_MAX_INTERMEDIATE_HOPS]; + + struct XBeeSequenceStatistics sequenceStatistics[256 * XBEE_STATS_GROUPS]; + struct XBeeStaticticsSummary groupSummary[XBEE_STATS_GROUPS]; +}; + +static void xbeeStatsReset(struct XBeeStaticticsSummary *summary) +{ + summary->minimum.tv_sec = 0; + summary->minimum.tv_usec = 0; + summary->maximum.tv_sec = 0; + summary->maximum.tv_usec = 0; + summary->sum.tv_sec = 0; + summary->sum.tv_usec = 0; + summary->samples = 0; +} + +static void xbeeStatsAdd(struct XBeeStaticticsSummary *summary, + struct timeval const *sample) +{ + summary->sum.tv_usec += sample->tv_usec; + if (summary->sum.tv_usec > 1000000) { + summary->sum.tv_usec -= 1000000; + summary->sum.tv_sec++; + } + summary->sum.tv_sec += sample->tv_sec; + + if (summary->samples == 0 || + summary->minimum.tv_sec > sample->tv_sec || + (summary->minimum.tv_sec == sample->tv_sec && + summary->minimum.tv_usec > sample->tv_usec)) { + summary->minimum = *sample; + } + + if (summary->maximum.tv_sec < sample->tv_sec || + (summary->maximum.tv_sec == sample->tv_sec && + summary->maximum.tv_usec < sample->tv_usec)) { + summary->maximum = *sample; + } + + summary->samples++; +} + +static void xbeeStatsSummarise(struct XBeeStaticticsSummary const *summary) +{ + avrdude_message(MSG_NOTICE, "%s: Minimum response time: %lu.%06lu\n", + progname, summary->minimum.tv_sec, summary->minimum.tv_usec); + avrdude_message(MSG_NOTICE, "%s: Maximum response time: %lu.%06lu\n", + progname, summary->maximum.tv_sec, summary->maximum.tv_usec); + + struct timeval average; + + const unsigned long samples = summary->samples; + average.tv_sec = summary->sum.tv_sec / samples; + + unsigned long long usecs = summary->sum.tv_usec; + usecs += (summary->sum.tv_sec % samples) * 1000000; + usecs = usecs / samples; + average.tv_sec += usecs / 1000000; + average.tv_usec = usecs % 1000000; + + avrdude_message(MSG_NOTICE, "%s: Average response time: %lu.%06lu\n", + progname, average.tv_sec, average.tv_usec); +} + +static void XBeeBootSessionInit(struct XBeeBootSession *xbs) { + xbs->serialDevice = &serial_serdev; + xbs->directMode = 1; + xbs->xbeeResetPin = XBEE_DEFAULT_RESET_PIN; + xbs->outSequence = 0; + xbs->inSequence = 0; + xbs->txSequence = 0; + xbs->transportUnusable = 0; + xbs->inInIndex = 0; + xbs->inOutIndex = 0; + xbs->sourceRouteHops = -1; + xbs->sourceRouteChanged = 0; + + int group; + for (group = 0; group < 3; group++) { + int index; + for (index = 0; index < 256; index++) + xbs->sequenceStatistics[group * 256 + index].sendTime.tv_sec = (time_t)0; + xbeeStatsReset(&xbs->groupSummary[group]); + } +} + +#define xbeebootsession(fdp) (struct XBeeBootSession*)((fdp)->pfd) + +static void xbeedev_setresetpin(union filedescriptor *fdp, int xbeeResetPin) +{ + struct XBeeBootSession *xbs = xbeebootsession(fdp); + xbs->xbeeResetPin = xbeeResetPin; +} + +enum xbee_stat_is_retry_enum {XBEE_STATS_NOT_RETRY, XBEE_STATS_IS_RETRY}; +typedef enum xbee_stat_is_retry_enum xbee_stat_is_retry; + +static void xbeedev_stats_send(struct XBeeBootSession *xbs, + char const *detail, + int detailSequence, + unsigned int group, unsigned char sequence, + xbee_stat_is_retry retry, + struct timeval const *sendTime) +{ + struct XBeeSequenceStatistics *stats = + &xbs->sequenceStatistics[group * 256 + sequence]; + + if (retry == XBEE_STATS_NOT_RETRY) + stats->sendTime = *sendTime; + + if (detailSequence >= 0) { + avrdude_message(MSG_NOTICE2, + "%s: Stats: Send Group %s Sequence %u : " + "Send %lu.%06lu %s Sequence %d\n", + progname, groupNames[group], + (unsigned int)sequence, + (unsigned long)sendTime->tv_sec, + (unsigned long)sendTime->tv_usec, + detail, detailSequence); + } else { + avrdude_message(MSG_NOTICE2, + "%s: Stats: Send Group %s Sequence %u : " + "Send %lu.%06lu %s\n", + progname, groupNames[group], + (unsigned int)sequence, + (unsigned long)sendTime->tv_sec, + (unsigned long)sendTime->tv_usec, + detail); + } +} + +static void xbeedev_stats_receive(struct XBeeBootSession *xbs, + char const *detail, + unsigned int group, unsigned char sequence, + struct timeval const *receiveTime) +{ + struct XBeeSequenceStatistics *stats = + &xbs->sequenceStatistics[group * 256 + sequence]; + struct timeval delay; + time_t secs; + long usecs; + + secs = receiveTime->tv_sec - stats->sendTime.tv_sec; + usecs = receiveTime->tv_usec - stats->sendTime.tv_usec; + + if (usecs < 0) { + usecs += 1000000; + secs--; + } + + delay.tv_sec = secs; + delay.tv_usec = usecs; + + avrdude_message(MSG_NOTICE2, + "%s: Stats: Receive Group %s Sequence %u : " + "Send %lu.%06lu Receive %lu.%06lu Delay %lu.%06lu %s\n", + progname, groupNames[group], + (unsigned int)sequence, + (unsigned long)stats->sendTime.tv_sec, + (unsigned long)stats->sendTime.tv_usec, + (unsigned long)receiveTime->tv_sec, + (unsigned long)receiveTime->tv_usec, + (unsigned long)secs, + (unsigned long)usecs, + detail); + + xbeeStatsAdd(&xbs->groupSummary[group], &delay); +} + +static int sendAPIRequest(struct XBeeBootSession *xbs, + unsigned char apiType, + int txSequence, + int apiOption, + int prePayload1, + int prePayload2, + int packetType, + int sequence, + int appType, + char const *detail, + int detailSequence, + unsigned int frameGroup, + xbee_stat_is_retry retry, + unsigned int dataLength, + const unsigned char *data) +{ + unsigned char frame[256]; + + unsigned char *fp = &frame[5]; + unsigned char *dataStart = fp; + unsigned char checksum = 0xff; + unsigned char length = 0; + struct timeval time; + + gettimeofday(&time, NULL); + + avrdude_message(MSG_NOTICE2, + "%s: sendAPIRequest(): %lu.%06lu %d, %d, %d, %d %s\n", + progname, (unsigned long)time.tv_sec, + (unsigned long)time.tv_usec, + (int)packetType, (int)sequence, appType, + data == NULL ? -1 : (int)*data, detail); + +#define fpput(x) \ + do { \ + const unsigned char v = (x); \ + if (v == 0x7d || v == 0x7e || v == 0x11 || v == 0x13) { \ + *fp++ = 0x7d; \ + *fp++ = v ^ 0x20; \ + } else { \ + *fp++ = v; \ + } \ + checksum -= v; \ + length++; \ + } while (0) + + fpput(apiType); /* ZigBee Receive Packet or ZigBee Transmit Request */ + + if (apiOption >= 0) + fpput(apiOption); /* Receive options (RX) */ + + if (txSequence >= 0) { + fpput(txSequence); /* Delivery sequence (TX/AT) */ + + /* + * Record the frame send time. Note that frame sequences are + * never retries. + */ + xbeedev_stats_send(xbs, detail, detailSequence, + frameGroup, txSequence, 0, &time); + } + + if (apiType != 0x08) { + /* Automatically inhibit addressing for local AT command requests. */ + size_t index; + for (index = 0; index < 10; index++) { + const unsigned char val = xbs->xbee_address[index]; + fpput(val); + } + + /* + * If this is an API call with remote address, but is not a Create + * Source Route request, consider prefixing it with source routing + * instructions. + */ + if (apiType != 0x21 && xbs->sourceRouteChanged) { + avrdude_message(MSG_NOTICE2, "%s: sendAPIRequest(): " + "Issuing Create Source Route request with %d hops\n", + progname, xbs->sourceRouteHops); + + int rc = sendAPIRequest(xbs, 0x21, /* Create Source Route */ + 0, -1, 0, xbs->sourceRouteHops, + -1, -1, -1, + "Create Source Route for FRAME_REMOTE", + txSequence, + XBEE_STATS_FRAME_LOCAL, /* Local, no response */ + 0, /* Not a retry */ + xbs->sourceRouteHops * 2, + xbs->sourceRoute); + if (rc != 0) + return rc; + + xbs->sourceRouteChanged = 0; + } + } + + if (prePayload1 >= 0) + fpput(prePayload1); /* Transmit broadcast radius */ + + if (prePayload2 >= 0) + fpput(prePayload2); /* Transmit options */ + + if (packetType >= 0) + fpput(packetType); /* XBEEBOOT_PACKET_TYPE_{ACK,REQUEST} */ + + if (sequence >= 0) { + fpput(sequence); + + /* Record the send time */ + if (packetType == XBEEBOOT_PACKET_TYPE_REQUEST) + xbeedev_stats_send(xbs, detail, sequence, XBEE_STATS_TRANSMIT, + sequence, retry, &time); + } + + if (appType >= 0) + fpput(appType); /* FIRMWARE_DELIVER */ + + { + size_t index; + for (index = 0; index < dataLength; index++) + fpput(data[index]); + } + + /* Length BEFORE checksum byte */ + const unsigned char unescapedLength = length; + + fpput(checksum); + + /* Length AFTER checksum byte */ + const unsigned int finalLength = fp - dataStart; + + frame[0] = 0x7e; + fp = &frame[1]; + fpput(0); + fpput(unescapedLength); + const unsigned int prefixLength = fp - frame; + unsigned char *frameStart = dataStart - prefixLength; + memmove(frameStart, frame, prefixLength); + + return xbs->serialDevice->send(&xbs->serialDescriptor, + frameStart, finalLength + prefixLength); +} + +static int sendPacket(struct XBeeBootSession *xbs, + const char *detail, + unsigned char packetType, + unsigned char sequence, + xbee_stat_is_retry retry, + int appType, + unsigned int dataLength, + const unsigned char *data) +{ + unsigned char apiType; + int prePayload1; + int prePayload2; + + if (xbs->directMode) { + /* + * In direct mode we are pretending to be an XBee device + * forwarding on data received from the transmitting XBee. We + * therefore format the data as a remote XBee would, encapsulated + * in a 0x90 packet. + */ + apiType = 0x90; /* ZigBee Receive Packet */ + prePayload1 = -1; + prePayload2 = -1; + } else { + /* + * In normal mode we are requesting a payload delivery, + * encapsulated in a 0x10 packet. + */ + apiType = 0x10; /* ZigBee Transmit Request */ + prePayload1 = 0; + prePayload2 = 0; + } + + while ((++xbs->txSequence & 0xff) == 0); + return sendAPIRequest(xbs, apiType, xbs->txSequence, -1, + prePayload1, prePayload2, packetType, + sequence, appType, + detail, sequence, + XBEE_STATS_FRAME_REMOTE, retry, + dataLength, data); +} + +#define XBEE_LENGTH_LEN 2 +#define XBEE_CHECKSUM_LEN 1 +#define XBEE_APITYPE_LEN 1 +#define XBEE_APISEQUENCE_LEN 1 +#define XBEE_ADDRESS_64BIT_LEN 8 +#define XBEE_ADDRESS_16BIT_LEN 2 +#define XBEE_RADIUS_LEN 1 +#define XBEE_TXOPTIONS_LEN 1 +#define XBEE_RXOPTIONS_LEN 1 + +static void xbeedev_record16Bit(struct XBeeBootSession *xbs, + const unsigned char *rx16Bit) +{ + /* + * We don't start out knowing what the 16-bit device address is, but + * we should receive it on the return packets, and re-use it from + * that point on. + */ + unsigned char * const tx16Bit = + &xbs->xbee_address[XBEE_ADDRESS_64BIT_LEN]; + if (memcmp(rx16Bit, tx16Bit, XBEE_ADDRESS_16BIT_LEN) != 0) { + avrdude_message(MSG_NOTICE2, "%s: xbeedev_record16Bit(): " + "New 16-bit address: %02x%02x\n", + progname, + (unsigned int)rx16Bit[0], + (unsigned int)rx16Bit[1]); + memcpy(tx16Bit, rx16Bit, XBEE_ADDRESS_16BIT_LEN); + } +} + +/* + * Return 0 on success. + * Return -1 on generic error (normally serial timeout). + * Return -512 + XBee AT Response code + */ +#define XBEE_AT_RETURN_CODE(x) (((x) >= -512 && (x) <= -256) ? (x) + 512 : -1) +static int xbeedev_poll(struct XBeeBootSession *xbs, + unsigned char **buf, size_t *buflen, + int waitForAck, + int waitForSequence) +{ + for (;;) { + unsigned char byte; + unsigned char frame[256]; + unsigned int frameSize; + + before_frame: + do { + const int rc = xbs->serialDevice->recv(&xbs->serialDescriptor, &byte, 1); + if (rc < 0) + return rc; + } while (byte != 0x7e); + + start_of_frame: + { + size_t index = 0; + int escaped = 0; + frameSize = XBEE_LENGTH_LEN; + do { + const int rc = xbs->serialDevice->recv(&xbs->serialDescriptor, + &byte, 1); + if (rc < 0) + return rc; + + if (byte == 0x7e) + /* + * No matter when we receive a frame start byte, we should + * abort parsing and start a fresh frame. + */ + goto start_of_frame; + + if (escaped) { + byte ^= 0x20; + escaped = 0; + } else if (byte == 0x7d) { + escaped = 1; + continue; + } + + if (index >= sizeof(frame)) + goto before_frame; + + frame[index++] = byte; + + if (index == XBEE_LENGTH_LEN) { + /* Length plus the two length bytes, plus the checksum byte */ + frameSize = (frame[0] << 8 | frame[1]) + + XBEE_LENGTH_LEN + XBEE_CHECKSUM_LEN; + + if (frameSize >= sizeof(frame)) + /* Too long - immediately give up on this frame */ + goto before_frame; + } + } while (index < frameSize); + + /* End of frame */ + unsigned char checksum = 1; + size_t cIndex; + for (cIndex = 2; cIndex < index; cIndex++) { + checksum += frame[cIndex]; + } + + if (checksum) { + /* Checksum didn't match */ + avrdude_message(MSG_NOTICE2, + "%s: xbeedev_poll(): Bad checksum %d\n", + progname, (int)checksum); + continue; + } + } + + const unsigned char frameType = frame[2]; + + struct timeval receiveTime; + gettimeofday(&receiveTime, NULL); + + avrdude_message(MSG_NOTICE2, + "%s: xbeedev_poll(): %lu.%06lu Received frame type %x\n", + progname, (unsigned long)receiveTime.tv_sec, + (unsigned long)receiveTime.tv_usec, + (unsigned int)frameType); + + if (frameType == 0x97 && frameSize > 16) { + /* Remote command response */ + unsigned char txSequence = frame[3]; + unsigned char resultCode = frame[16]; + + xbeedev_stats_receive(xbs, "Remote AT command response", + XBEE_STATS_FRAME_REMOTE, txSequence, &receiveTime); + + avrdude_message(MSG_NOTICE, + "%s: xbeedev_poll(): Remote command %d result code %d\n", + progname, (int)txSequence, (int)resultCode); + + if (waitForSequence >= 0 && waitForSequence == frame[3]) + /* Received result for our sequence numbered request */ + return -512 + resultCode; + } else if (frameType == 0x88 && frameSize > 6) { + /* Local command response */ + unsigned char txSequence = frame[3]; + + xbeedev_stats_receive(xbs, "Local AT command response", + XBEE_STATS_FRAME_LOCAL, txSequence, &receiveTime); + + avrdude_message(MSG_NOTICE, + "%s: xbeedev_poll(): Local command %c%c result code %d\n", + progname, frame[4], frame[5], (int)frame[6]); + + if (waitForSequence >= 0 && waitForSequence == txSequence) + /* Received result for our sequence numbered request */ + return 0; + } else if (frameType == 0x8b && frameSize > 7) { + /* Transmit status */ + unsigned char txSequence = frame[3]; + + xbeedev_stats_receive(xbs, "Transmit status", XBEE_STATS_FRAME_REMOTE, + txSequence, &receiveTime); + + avrdude_message(MSG_NOTICE2, + "%s: xbeedev_poll(): Transmit status %d result code %d\n", + progname, (int)frame[3], (int)frame[7]); + } else if (frameType == 0xa1 && + frameSize >= XBEE_LENGTH_LEN + XBEE_APITYPE_LEN + + XBEE_ADDRESS_64BIT_LEN + + XBEE_ADDRESS_16BIT_LEN + 2 + XBEE_CHECKSUM_LEN) { + /* Route Record Indicator */ + if (memcmp(&frame[XBEE_LENGTH_LEN + XBEE_APITYPE_LEN], + xbs->xbee_address, XBEE_ADDRESS_64BIT_LEN) != 0) { + /* Not from our target device */ + avrdude_message(MSG_NOTICE2, "%s: xbeedev_poll(): " + "Route Record Indicator from other XBee\n"); + continue; + } + + /* + * We don't start out knowing what the 16-bit device address is, + * but we should receive it on the return packets, and re-use it + * from that point on. + */ + { + const unsigned char *rx16Bit = + &frame[XBEE_LENGTH_LEN + XBEE_APITYPE_LEN + + XBEE_ADDRESS_64BIT_LEN]; + xbeedev_record16Bit(xbs, rx16Bit); + } + + const unsigned int header = XBEE_LENGTH_LEN + XBEE_APITYPE_LEN + + XBEE_ADDRESS_64BIT_LEN + + XBEE_ADDRESS_16BIT_LEN; + + const unsigned char receiveOptions = frame[header]; + const unsigned char hops = frame[header + 1]; + + avrdude_message(MSG_NOTICE2, "%s: xbeedev_poll(): " + "Route Record Indicator from target XBee: " + "hops=%d options=%d\n", + progname, (int)hops, (int)receiveOptions); + + if (frameSize < header + 2 + hops * 2 + XBEE_CHECKSUM_LEN) + /* Bounds check: Frame is too small */ + continue; + + const unsigned int tableOffset = header + 2; + + unsigned char index; + for (index = 0; index < hops; index++) { + avrdude_message(MSG_NOTICE2, "%s: xbeedev_poll(): " + "Route Intermediate Hop %d : %02x%02x\n", + progname, (int)index, + (int)frame[tableOffset + index * 2], + (int)frame[tableOffset + index * 2 + 1]); + } + + if (hops <= XBEE_MAX_INTERMEDIATE_HOPS) { + if (xbs->sourceRouteHops != (int)hops || + memcmp(&frame[tableOffset], xbs->sourceRoute, hops * 2) != 0) { + memcpy(xbs->sourceRoute, &frame[tableOffset], hops * 2); + xbs->sourceRouteHops = hops; + xbs->sourceRouteChanged = 1; + + avrdude_message(MSG_NOTICE2, "%s: xbeedev_poll(): " + "Route has changed\n", + progname); + } + } + } else if (frameType == 0x10 || frameType == 0x90) { + unsigned char *dataStart; + unsigned int dataLength; + + if (frameType == 0x10) { + /* Direct mode frame */ + const unsigned int header = XBEE_LENGTH_LEN + + XBEE_APITYPE_LEN + XBEE_APISEQUENCE_LEN + + XBEE_ADDRESS_64BIT_LEN + XBEE_ADDRESS_16BIT_LEN + + XBEE_RADIUS_LEN + XBEE_TXOPTIONS_LEN; + + if (frameSize <= header + XBEE_CHECKSUM_LEN) + /* Bounds check: Frame is too small */ + continue; + + dataLength = frameSize - header - XBEE_CHECKSUM_LEN; + dataStart = &frame[header]; + } else { + /* Remote reply frame */ + const unsigned int header = XBEE_LENGTH_LEN + + XBEE_APITYPE_LEN + XBEE_ADDRESS_64BIT_LEN + XBEE_ADDRESS_16BIT_LEN + + XBEE_RXOPTIONS_LEN; + + if (frameSize <= header + XBEE_CHECKSUM_LEN) + /* Bounds check: Frame is too small */ + continue; + + dataLength = frameSize - header - XBEE_CHECKSUM_LEN; + dataStart = &frame[header]; + + if (memcmp(&frame[XBEE_LENGTH_LEN + XBEE_APITYPE_LEN], + xbs->xbee_address, XBEE_ADDRESS_64BIT_LEN) != 0) { + /* + * This packet is not from our target device. Unlikely + * to ever happen, but if it does we have to ignore + * it. + */ + continue; + } + + /* + * We don't start out knowing what the 16-bit device address + * is, but we should receive it on the return packets, and + * re-use it from that point on. + */ + { + const unsigned char *rx16Bit = + &frame[XBEE_LENGTH_LEN + XBEE_APITYPE_LEN + + XBEE_ADDRESS_64BIT_LEN]; + xbeedev_record16Bit(xbs, rx16Bit); + } + } + + if (dataLength >= 2) { + const unsigned char protocolType = dataStart[0]; + const unsigned char sequence = dataStart[1]; + + avrdude_message(MSG_NOTICE2, "%s: xbeedev_poll(): " + "%lu.%06lu Packet %d #%d\n", + progname, (unsigned long)receiveTime.tv_sec, + (unsigned long)receiveTime.tv_usec, + (int)protocolType, (int)sequence); + + if (protocolType == XBEEBOOT_PACKET_TYPE_ACK) { + /* ACK */ + xbeedev_stats_receive(xbs, "XBeeBoot ACK", + XBEE_STATS_TRANSMIT, sequence, + &receiveTime); + + /* + * We can't update outSequence here, we already do that + * somewhere else. + */ + if (waitForAck >= 0 && waitForAck == sequence) + return 0; + } else if (protocolType == XBEEBOOT_PACKET_TYPE_REQUEST && + dataLength >= 4 && dataStart[2] == 24) { + /* REQUEST FRAME_REPLY */ + xbeedev_stats_receive(xbs, "XBeeBoot Receive", XBEE_STATS_RECEIVE, + sequence, &receiveTime); + + unsigned char nextSequence = xbs->inSequence; + while ((++nextSequence & 0xff) == 0); + if (sequence == nextSequence) { + xbs->inSequence = nextSequence; + + const size_t textLength = dataLength - 3; + size_t index; + for (index = 0; index < textLength; index++) { + const unsigned char data = dataStart[3 + index]; + if (buflen != NULL && *buflen > 0) { + /* If we are receiving right now, and have a buffer... */ + *(*buf)++ = data; + (*buflen)--; + } else { + xbs->inBuffer[xbs->inInIndex++] = data; + if (xbs->inInIndex == sizeof(xbs->inBuffer)) + xbs->inInIndex = 0; + if (xbs->inInIndex == xbs->inOutIndex) { + /* Should be impossible */ + avrdude_message(MSG_INFO, "%s: Buffer overrun\n", progname); + xbs->transportUnusable = 1; + return -1; + } + } + } + + /*avrdude_message(MSG_INFO, "ACK %x\n", (unsigned int)sequence);*/ + sendPacket(xbs, "Transmit Request ACK for RECEIVE", + XBEEBOOT_PACKET_TYPE_ACK, sequence, + XBEE_STATS_NOT_RETRY, + -1, 0, NULL); + + if (buf != NULL && *buflen == 0) + /* Input buffer has been filled */ + return 0; + + /* + * Input buffer has NOT been filled, we are still in a + * receive. Not a retry, this is the first point we know + * for sure for this sequence number. + */ + while ((++nextSequence & 0xff) == 0); + xbeedev_stats_send(xbs, "poll() implies pending RECEIVE", + nextSequence, + XBEE_STATS_RECEIVE, + nextSequence, XBEE_STATS_NOT_RETRY, + &receiveTime); + } + } + } + } + } +} + +/* + * @return + * 0 on success, a negative value on failure, or a positive + * value indicating the sequence number associated with the + * request. + */ +static int localAsyncAT(struct XBeeBootSession *xbs, char const *detail, + unsigned char at1, unsigned char at2, int value) +{ + if (xbs->directMode) + /* + * Remote XBee AT commands make no sense in direct mode - there is + * no XBee device to communicate with. + * + * Return success, no sequence number. + */ + return 0; + + while ((++xbs->txSequence & 0xff) == 0); + const unsigned char sequence = xbs->txSequence; + + unsigned char buf[3]; + size_t length = 0; + + buf[length++] = at1; + buf[length++] = at2; + + if (value >= 0) + buf[length++] = (unsigned char)value; + + avrdude_message(MSG_NOTICE, "%s: Local AT command: %c%c\n", + progname, at1, at2); + + /* Local AT command 0x08 */ + int rc = sendAPIRequest(xbs, 0x08, sequence, -1, -1, -1, -1, -1, -1, + detail, -1, XBEE_STATS_FRAME_LOCAL, + XBEE_STATS_NOT_RETRY, + length, buf); + if (rc < 0) + /* Failed */ + return rc; + + /* Success, positive sequence number */ + return (int)sequence; +} + +static int localAT(struct XBeeBootSession *xbs, char const *detail, + unsigned char at1, unsigned char at2, int value) +{ + int result = localAsyncAT(xbs, detail, at1, at2, value); + + if (result <= 0) + /* Failure, or success without a sequence number */ + return result; + + unsigned char sequence = (unsigned char)result; + + int retries; + for (retries = 0; retries < 5; retries++) { + const int rc = xbeedev_poll(xbs, NULL, NULL, -1, sequence); + if (rc == 0) + return 0; + } + + return -1; +} + +/* + * Return 0 on success. + * Return -1 on generic error (normally serial timeout). + * Return -512 + XBee AT Response code + */ +static int sendAT(struct XBeeBootSession *xbs, char const *detail, + unsigned char at1, unsigned char at2, int value) +{ + if (xbs->directMode) + /* + * Remote XBee AT commands make no sense in direct mode - there is + * no XBee device to communicate with. + */ + return 0; + + while ((++xbs->txSequence & 0xff) == 0); + const unsigned char sequence = xbs->txSequence; + + unsigned char buf[3]; + size_t length = 0; + + buf[length++] = at1; + buf[length++] = at2; + + if (value >= 0) + buf[length++] = (unsigned char)value; + + avrdude_message(MSG_NOTICE, + "%s: Remote AT command: %c%c\n", progname, at1, at2); + + /* Remote AT command 0x17 with Apply Changes 0x02 */ + sendAPIRequest(xbs, 0x17, sequence, -1, + -1, -1, -1, + 0x02, -1, + detail, -1, XBEE_STATS_FRAME_REMOTE, + XBEE_STATS_NOT_RETRY, + length, buf); + + int retries; + for (retries = 0; retries < 30; retries++) { + const int rc = xbeedev_poll(xbs, NULL, NULL, -1, sequence); + const int xbeeRc = XBEE_AT_RETURN_CODE(rc); + if (xbeeRc == 0) + /* Translate to normal success code */ + return 0; + if (rc != -1) + return rc; + } + + return -1; +} + +/* + * Return 0 on no error recognised, 1 if error was detected and + * reported. + */ +static int xbeeATError(int rc) { + const int xbeeRc = XBEE_AT_RETURN_CODE(rc); + if (xbeeRc < 0) + return 0; + + if (xbeeRc == 1) { + avrdude_message(MSG_INFO, "%s: Error communicating with Remote XBee\n", + progname); + } else if (xbeeRc == 2) { + avrdude_message(MSG_INFO, "%s: Remote XBee command error: " + "Invalid command\n", + progname); + } else if (xbeeRc == 3) { + avrdude_message(MSG_INFO, "%s: Remote XBee command error: " + "Invalid parameter\n", + progname); + } else if (xbeeRc == 4) { + avrdude_message(MSG_INFO, "%s: Remote XBee error: " + "Transmission failure\n", + progname); + } else { + avrdude_message(MSG_INFO, "%s: Unrecognised remote XBee error code %d\n", + progname, xbeeRc); + } + return 1; +} + +static void xbeedev_free(struct XBeeBootSession *xbs) +{ + xbs->serialDevice->close(&xbs->serialDescriptor); + free(xbs); +} + +static void xbeedev_close(union filedescriptor *fdp) +{ + struct XBeeBootSession *xbs = xbeebootsession(fdp); + xbeedev_free(xbs); +} + +static int xbeedev_open(char *port, union pinfo pinfo, + union filedescriptor *fdp) +{ + /* + * The syntax for XBee devices is defined as: + * + * -P @[serialdevice] + * + * ... or ... + * + * -P @[serialdevice] + * + * ... for a direct connection. + */ + char *ttySeparator = strchr(port, '@'); + if (ttySeparator == NULL) { + avrdude_message(MSG_INFO, + "%s: XBee: Bad port syntax: " + "require \"@\"\n", + progname); + return -1; + } + + struct XBeeBootSession *xbs = malloc(sizeof(struct XBeeBootSession)); + if (xbs == NULL) { + avrdude_message(MSG_INFO, "%s: xbeedev_open(): out of memory\n", + progname); + return -1; + } + + XBeeBootSessionInit(xbs); + + char *tty = &ttySeparator[1]; + + if (ttySeparator == port) { + /* Direct connection */ + memset(xbs->xbee_address, 0, 8); + xbs->directMode = 1; + } else { + size_t addrIndex = 0; + int nybble = -1; + char const *address = port; + while (address != ttySeparator) { + char hex = *address++; + unsigned int val; + if (hex >= '0' && hex <= '9') { + val = hex - '0'; + } else if (hex >= 'A' && hex <= 'F') { + val = hex - 'A' + 10; + } else if (hex >= 'a' && hex <= 'f') { + val = hex - 'a' + 10; + } else { + break; + } + if (nybble == -1) { + nybble = val; + } else { + xbs->xbee_address[addrIndex++] = (nybble * 16) | val; + nybble = -1; + if (addrIndex == 8) + break; + } + } + + if (addrIndex != 8 || address != ttySeparator || nybble != -1) { + avrdude_message(MSG_INFO, + "%s: XBee: Bad XBee address: " + "require 16-character hexadecimal address\"\n", + progname); + free(xbs); + return -1; + } + + xbs->directMode = 0; + } + + /* Unknown 16 bit address */ + xbs->xbee_address[8] = 0xff; + xbs->xbee_address[9] = 0xfe; + + avrdude_message(MSG_TRACE, + "%s: XBee address: %02x%02x%02x%02x%02x%02x%02x%02x\n", + progname, + (unsigned int)xbs->xbee_address[0], + (unsigned int)xbs->xbee_address[1], + (unsigned int)xbs->xbee_address[2], + (unsigned int)xbs->xbee_address[3], + (unsigned int)xbs->xbee_address[4], + (unsigned int)xbs->xbee_address[5], + (unsigned int)xbs->xbee_address[6], + (unsigned int)xbs->xbee_address[7]); + + if (pinfo.baud) { + /* + * User supplied the correct baud rate. + */ + } else if (xbs->directMode) { + /* + * In direct mode, default to 19200. + * + * Why? + * + * In this mode, we are NOT talking to an XBee, we are talking + * directly to an AVR device that thinks it is talking to an XBee + * itself. + * + * Because, an XBee is a 3.3V device defaulting to 9600baud, and + * the Atmel328P is only rated at a maximum clock rate of 8MHz + * with a 3.3V supply, so there's a high likelihood a remote + * Atmel328P will be clocked at 8MHz. + * + * With a direct connection, there's a good chance we're talking + * to an Arduino clocked at 16MHz with an XBee-enabled chip + * plugged in. The doubled clock rate means a doubled serial + * rate. Double 9600 baud == 19200 baud. + */ + pinfo.baud = 19200; + } else { + /* + * In normal mode, default to 9600. + * + * Why? + * + * XBee devices default to 9600 baud. In this mode we are talking + * to the XBee device, not the far-end device, so it's the local + * XBee baud rate we should select. The baud rate of the AVR + * device is irrelevant. + */ + pinfo.baud = 9600; + } + + avrdude_message(MSG_NOTICE, "%s: Baud %ld\n", progname, (long)pinfo.baud); + + { + const int rc = xbs->serialDevice->open(tty, pinfo, + &xbs->serialDescriptor); + if (rc < 0) { + free(xbs); + return rc; + } + } + + if (!xbs->directMode) { + /* Attempt to ensure the local XBee is in API mode 2 */ + { + const int rc = localAT(xbs, "AT AP=2", 'A', 'P', 2); + if (rc < 0) { + avrdude_message(MSG_INFO, "%s: Local XBee is not responding.\n", + progname); + xbeedev_free(xbs); + return rc; + } + } + + /* + * At this point we want to set the remote XBee parameters as + * required for talking to XBeeBoot. Ideally we would start with + * an "FR" full reset, but because that causes the XBee to + * disappear off the mesh for a significant period and become + * unresponsive, we don't do that. + */ + + /* + * Issue an "Aggregate Routing Notification" to enable many-to-one + * routing to this device. This has two effects: + * + * - Establishes a route from the remote XBee attached to the CPU + * being programmed back to the local XBee. + * + * - Enables the 0xa1 Route frames so that we can make use of + * Source Routing to deliver packets directly to the remote + * XBee. + * + * Under "RF packet routing" subsection "Many-to-One routing", the + * XBee S2C manual states "Applications that require multiple data + * collectors can also use many-to-one routing. If more than one + * data collector device sends a many-to-one broadcast, devices + * create one reverse routing table entry for each collector." + * + * Under "RF packet routing" subsection "Source routing", the XBee + * S2C manual states "To use source routing, a device must use the + * API mode, and it must send periodic many-to-one route request + * broadcasts (AR command) to create a many-to-one route to it on + * all devices". + */ + { + const int rc = localAT(xbs, "AT AR=0", 'A', 'R', 0); + if (rc < 0) { + avrdude_message(MSG_INFO, "%s: Local XBee is not responding.\n", + progname); + xbeedev_free(xbs); + return rc; + } + } + + /* + * Disable RTS input on the remote XBee, just in case it is + * enabled by default. XBeeBoot doesn't attempt to support flow + * control, and so it may not correctly drive this pin if RTS mode + * is the default configuration. + * + * XBee IO port 6 is the only pin that supports RTS mode, so there + * is no need to support any alternative pin. + */ + const int rc = sendAT(xbs, "AT D6=0", 'D', '6', 0); + if (rc < 0) { + xbeedev_free(xbs); + + if (xbeeATError(rc)) + return -1; + + avrdude_message(MSG_INFO, "%s: Remote XBee is not responding.\n", + progname); + return rc; + } + } + + fdp->pfd = xbs; + + return 0; +} + +static int xbeedev_send(union filedescriptor *fdp, + const unsigned char *buf, size_t buflen) +{ + struct XBeeBootSession *xbs = xbeebootsession(fdp); + + if (xbs->transportUnusable) + /* Don't attempt to continue on an unusable transport layer */ + return -1; + + while (buflen > 0) { + unsigned char sequence = xbs->outSequence; + while ((++sequence & 0xff) == 0); + xbs->outSequence = sequence; + + /* + * We are about to send some data, and that might lead potentially + * to received data before we see the ACK for this transmission. + * As this might be the trigger seen before the next "recv" + * operation, record that we have delivered this potential + * trigger. + */ + { + unsigned char nextSequence = xbs->inSequence; + while ((++nextSequence & 0xff) == 0); + + struct timeval sendTime; + gettimeofday(&sendTime, NULL); + + /* + * Optimistic records should never be treated as retries, + * because they might simply be guessing too optimistically. + */ + xbeedev_stats_send(xbs, "send() hints possible triggered RECEIVE", + nextSequence, + XBEE_STATS_RECEIVE, + nextSequence, 0, &sendTime); + } + + /* + * Chunk the data into chunks of up to XBEEBOOT_MAX_CHUNK bytes. + */ + unsigned char maximum_chunk = XBEEBOOT_MAX_CHUNK; + + /* + * Source routing incurs a two byte fixed overhead, plus a two + * byte additional cost per intermediate hop. + * + * We are attempting to avoid fragmentation here, so resize our + * maximum size to anticipate the overhead of the current number + * of hops. If our maximum chunk would be less than one, just + * give up and hope fragmentation will somehow save us. + */ + const int hops = xbs->sourceRouteHops; + if (hops > 0 && (hops * 2 + 2) < XBEEBOOT_MAX_CHUNK) + maximum_chunk -= hops * 2 + 2; + + const unsigned char blockLength = + (buflen > maximum_chunk) ? maximum_chunk : buflen; + + int pollRc = 0; + + /* Repeatedly send whilst timing out waiting for ACK responses. */ + int retries; + for (retries = 0; retries < XBEE_MAX_RETRIES; retries++) { + int sendRc = + sendPacket(xbs, + "Transmit Request Data, expect ACK for TRANSMIT", + XBEEBOOT_PACKET_TYPE_REQUEST, sequence, + retries > 0 ? XBEE_STATS_IS_RETRY : XBEE_STATS_NOT_RETRY, + 23 /* FIRMWARE_DELIVER */, + blockLength, buf); + if (sendRc < 0) { + /* There is no way to recover from a failure mid-send */ + xbs->transportUnusable = 1; + return sendRc; + } + + pollRc = xbeedev_poll(xbs, NULL, NULL, sequence, -1); + if (pollRc == 0) { + /* Send was ACK'd */ + buflen -= blockLength; + buf += blockLength; + break; + } + + /* + * Test the connection to the local XBee by repeatedly + * requesting local configuration details. This functionally + * has no effect, but will allow us to measure any reliability + * issues on this link. + */ + localAsyncAT(xbs, "Local XBee ping [send]", 'A', 'P', -1); + + /* + * If we don't receive an ACK it might be because the chip + * missed an ACK from us. Resend that too after a timeout, + * unless it's zero which is an illegal sequence number. + */ + if (xbs->inSequence != 0) { + int ackRc = sendPacket(xbs, + "Transmit Request ACK [Retry in send] " + "for RECEIVE", + XBEEBOOT_PACKET_TYPE_ACK, + xbs->inSequence, + XBEE_STATS_IS_RETRY, + -1, 0, NULL); + if (ackRc < 0) { + /* There is no way to recover from a failure mid-send */ + xbs->transportUnusable = 1; + return ackRc; + } + } + } + + if (pollRc < 0) { + /* There is no way to recover from a failure mid-send */ + xbs->transportUnusable = 1; + return pollRc; + } + } + + return 0; +} + +static int xbeedev_recv(union filedescriptor *fdp, + unsigned char *buf, size_t buflen) +{ + struct XBeeBootSession *xbs = xbeebootsession(fdp); + + /* + * First de-buffer anything previously received in a chunk that + * couldn't be immediately delievered. + */ + while (xbs->inInIndex != xbs->inOutIndex) { + *buf++ = xbs->inBuffer[xbs->inOutIndex++]; + if (xbs->inOutIndex == sizeof(xbs->inBuffer)) + xbs->inOutIndex = 0; + if (--buflen == 0) + return 0; + } + + if (xbs->transportUnusable) + /* Don't attempt to continue on an unusable transport layer */ + return -1; + + /* + * When we expect to receive data, that is the time to start the + * clock. + */ + { + unsigned char nextSequence = xbs->inSequence; + while ((++nextSequence & 0xff) == 0); + + struct timeval sendTime; + gettimeofday(&sendTime, NULL); + + /* + * Not a retry - in fact this is the first stage we know for sure + * a RECEIVE is due. + */ + xbeedev_stats_send(xbs, "recv() implies pending RECEIVE", + nextSequence, + XBEE_STATS_RECEIVE, + nextSequence, + XBEE_STATS_NOT_RETRY, + &sendTime); + } + + int retries; + for (retries = 0; retries < XBEE_MAX_RETRIES; retries++) { + const int rc = xbeedev_poll(xbs, &buf, &buflen, -1, -1); + if (rc == 0) + return 0; + + if (xbs->transportUnusable) + /* Don't attempt to continue on an unusable transport layer */ + return -1; + + /* + * Test the connection to the local XBee by repeatedly + * requesting local configuration details. This functionally + * has no effect, but will allow us to measure any reliability + * issues on this link. + */ + localAsyncAT(xbs, "Local XBee ping [recv]", 'A', 'P', -1); + + /* + * The chip may have missed an ACK from us. Resend after a + * timeout. + */ + if (xbs->inSequence != 0) + sendPacket(xbs, "Transmit Request ACK [Retry in recv] for RECEIVE", + XBEEBOOT_PACKET_TYPE_ACK, xbs->inSequence, + XBEE_STATS_IS_RETRY, + -1, 0, NULL); + } + return -1; +} + +static int xbeedev_drain(union filedescriptor *fdp, int display) +{ + struct XBeeBootSession *xbs = xbeebootsession(fdp); + + if (xbs->transportUnusable) + /* Don't attempt to continue on an unusable transport layer */ + return -1; + + /* + * Flushing the local serial buffer is unhelpful under this + * protocol. + */ + do { + xbs->inOutIndex = xbs->inInIndex = 0; + } while (xbeedev_poll(xbs, NULL, NULL, -1, -1) == 0); + + return 0; +} + +static int xbeedev_set_dtr_rts(union filedescriptor *fdp, int is_on) +{ + struct XBeeBootSession *xbs = xbeebootsession(fdp); + + if (xbs->directMode) + /* Correct for direct mode */ + return xbs->serialDevice->set_dtr_rts(&xbs->serialDescriptor, is_on); + + /* + * For non-direct mode (Over-The-Air) we need to issue XBee commands + * to the remote XBee in order to reset the AVR CPU and initiate the + * XBeeBoot bootloader. + */ + const int rc = sendAT(xbs, is_on ? "AT [DTR]=low" : "AT [DTR]=high", + 'D', '0' + xbs->xbeeResetPin, is_on ? 5 : 4); + if (rc < 0) { + if (xbeeATError(rc)) + return -1; + + avrdude_message(MSG_INFO, + "%s: Remote XBee is not responding.\n", progname); + return rc; + } + + return 0; +} + +/* + * Device descriptor for XBee framing. + */ +static struct serial_device xbee_serdev_frame = { + .open = xbeedev_open, + .close = xbeedev_close, + .send = xbeedev_send, + .recv = xbeedev_recv, + .drain = xbeedev_drain, + .set_dtr_rts = xbeedev_set_dtr_rts, + .flags = SERDEV_FL_NONE, +}; + +static int xbee_getsync(PROGRAMMER *pgm) +{ + unsigned char buf[2], resp[2]; + + /* + * Issue sync request as per STK500. Unlike stk500_getsync(), don't + * retry here - the underlying protocol will deal with retries for + * us in xbeedev_send() and should be reliable. + */ + buf[0] = Cmnd_STK_GET_SYNC; + buf[1] = Sync_CRC_EOP; + + int sendRc = serial_send(&pgm->fd, buf, 2); + if (sendRc < 0) { + avrdude_message(MSG_INFO, + "%s: xbee_getsync(): failed to deliver STK_GET_SYNC " + "to the remote XBeeBoot bootloader\n", + progname); + return sendRc; + } + + /* + * The same is true of the receive - it will retry on timeouts until + * the response buffer is full. + */ + int recvRc = serial_recv(&pgm->fd, resp, 2); + if (recvRc < 0) { + avrdude_message(MSG_INFO, + "%s: xbee_getsync(): no response to STK_GET_SYNC " + "from the remote XBeeBoot bootloader\n", + progname); + return recvRc; + } + + if (resp[0] != Resp_STK_INSYNC) { + avrdude_message(MSG_INFO, "%s: xbee_getsync(): not in sync: resp=0x%02x\n", + progname, (unsigned int)resp[0]); + return -1; + } + + if (resp[1] != Resp_STK_OK) { + avrdude_message(MSG_INFO, "%s: xbee_getsync(): in sync, not OK: " + "resp=0x%02x\n", + progname, (unsigned int)resp[1]); + return -1; + } + + return 0; +} + +static int xbee_open(PROGRAMMER *pgm, char *port) +{ + union pinfo pinfo; + strcpy(pgm->port, port); + pinfo.baud = pgm->baudrate; + + /* Wireless is lossier than normal serial */ + serial_recv_timeout = 1000; + + serdev = &xbee_serdev_frame; + + if (serial_open(port, pinfo, &pgm->fd) == -1) { + return -1; + } + + /* + * NB: Because we are making use of the STK500 programmer + * implementation, we can't readily use pgm->cookie ourselves. We + * can use the private "flag" field in the PROGRAMMER though, as + * it's unused by stk500.c. + */ + xbeedev_setresetpin(&pgm->fd, pgm->flag); + + /* Clear DTR and RTS */ + serial_set_dtr_rts(&pgm->fd, 0); + usleep(250*1000); + + /* Set DTR and RTS back to high */ + serial_set_dtr_rts(&pgm->fd, 1); + usleep(50*1000); + + /* + * At this point stk500_drain() and stk500_getsync() calls would + * normally be made. But given that we have a transport layer over + * the serial command stream, the drain and repeated STK_GET_SYNC + * requests are not very helpful. Instead, skip the draining + * entirely, and issue the STK_GET_SYNC ourselves. + */ + if (xbee_getsync(pgm) < 0) + return -1; + + return 0; +} + +static void xbee_close(PROGRAMMER *pgm) +{ + struct XBeeBootSession *xbs = xbeebootsession(&pgm->fd); + + /* + * NB: This request is for the target device, not the locally + * connected serial device. + */ + serial_set_dtr_rts(&pgm->fd, 0); + + /* + * We have tweaked a few settings on the XBee, including the RTS + * mode and the reset pin's configuration. Do a soft full reset, + * restoring the device to its normal power-on settings. + * + * Note that this DOES mean that the remote XBee will be + * uncontactable until it has restarted and re-established + * communications on the mesh. + */ + if (!xbs->directMode) { + const int rc = sendAT(xbs, "AT FR", 'F', 'R', -1); + xbeeATError(rc); + } + + avrdude_message(MSG_NOTICE, "%s: Statistics for FRAME_LOCAL requests - %s->XBee(local)\n", progname, progname); + xbeeStatsSummarise(&xbs->groupSummary[XBEE_STATS_FRAME_LOCAL]); + + avrdude_message(MSG_NOTICE, "%s: Statistics for FRAME_REMOTE requests - %s->XBee(local)->XBee(target)\n", progname, progname); + xbeeStatsSummarise(&xbs->groupSummary[XBEE_STATS_FRAME_REMOTE]); + + avrdude_message(MSG_NOTICE, "%s: Statistics for TRANSMIT requests - %s->XBee(local)->XBee(target)->XBeeBoot\n", progname, progname); + xbeeStatsSummarise(&xbs->groupSummary[XBEE_STATS_TRANSMIT]); + + avrdude_message(MSG_NOTICE, "%s: Statistics for RECEIVE requests - XBeeBoot->XBee(target)->XBee(local)->%s\n", progname, progname); + xbeeStatsSummarise(&xbs->groupSummary[XBEE_STATS_RECEIVE]); + + xbeedev_free(xbs); + + pgm->fd.pfd = NULL; +} + +static int xbee_parseextparms(PROGRAMMER *pgm, LISTID extparms) +{ + LNODEID ln; + const char *extended_param; + int rc = 0; + + for (ln = lfirst(extparms); ln; ln = lnext(ln)) { + extended_param = ldata(ln); + + if (strncmp(extended_param, + "xbeeresetpin=", 13 /*strlen("xbeeresetpin=")*/) == 0) { + int resetpin; + if (sscanf(extended_param, "xbeeresetpin=%i", &resetpin) != 1 || + resetpin <= 0 || resetpin > 7) { + avrdude_message(MSG_INFO, "%s: xbee_parseextparms(): " + "invalid xbeeresetpin '%s'\n", + progname, extended_param); + rc = -1; + continue; + } + + pgm->flag = resetpin; + continue; + } + + avrdude_message(MSG_INFO, "%s: xbee_parseextparms(): " + "invalid extended parameter '%s'\n", + progname, extended_param); + rc = -1; + } + + return rc; +} + +const char xbee_desc[] = "XBee Series 2 Over-The-Air (XBeeBoot)"; + +void xbee_initpgm(PROGRAMMER *pgm) +{ + /* + * This behaves like an Arduino, but with packet encapsulation of + * the serial streams, XBee device management, and XBee GPIO for the + * Auto-Reset feature. + */ + stk500_initpgm(pgm); + + strncpy(pgm->type, "XBee", sizeof(pgm->type)); + pgm->read_sig_bytes = xbee_read_sig_bytes; + pgm->open = xbee_open; + pgm->close = xbee_close; + + /* + * NB: Because we are making use of the STK500 programmer + * implementation, we can't readily use pgm->cookie ourselves, nor + * can we override setup() and teardown(). We can use the private + * "flag" field in the PROGRAMMER though, as it's unused by + * stk500.c. + */ + pgm->parseextparams = xbee_parseextparms; + pgm->flag = XBEE_DEFAULT_RESET_PIN; +} diff --git a/xbee.h b/xbee.h new file mode 100644 index 00000000..9830d853 --- /dev/null +++ b/xbee.h @@ -0,0 +1,27 @@ +/* + * avrdude - A Downloader/Uploader for AVR device programmers + * Copyright (C) 2015-2020 David Sainty + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* $Id$ */ + +#ifndef xbee_h__ +#define xbee_h__ + +extern const char xbee_desc[]; +void xbee_initpgm (PROGRAMMER * pgm); + +#endif