From b4ef7a88a1cf445b6648b781507cf0b303035553 Mon Sep 17 00:00:00 2001 From: Sam Kumar Date: Mon, 30 Oct 2017 17:06:44 -0700 Subject: [PATCH 1/2] REthos code, rebased manually on hamilton-combined-v11.0 --- dist/tools/rethos/.gitignore | 1 + dist/tools/rethos/LICENSE | 339 +++++++ dist/tools/rethos/Makefile | 10 + dist/tools/rethos/README.md | 15 + dist/tools/rethos/rethos.c | 1155 ++++++++++++++++++++++++ dist/tools/rethos/start_network.sh | 50 + drivers/include/rethos.h | 283 ++++++ drivers/rethos/Makefile | 1 + drivers/rethos/rethos.c | 650 +++++++++++++ sys/auto_init/auto_init.c | 5 + sys/auto_init/netif/auto_init_rethos.c | 72 ++ 11 files changed, 2581 insertions(+) create mode 100644 dist/tools/rethos/.gitignore create mode 100644 dist/tools/rethos/LICENSE create mode 100644 dist/tools/rethos/Makefile create mode 100644 dist/tools/rethos/README.md create mode 100644 dist/tools/rethos/rethos.c create mode 100644 dist/tools/rethos/start_network.sh create mode 100644 drivers/include/rethos.h create mode 100644 drivers/rethos/Makefile create mode 100644 drivers/rethos/rethos.c create mode 100644 sys/auto_init/netif/auto_init_rethos.c diff --git a/dist/tools/rethos/.gitignore b/dist/tools/rethos/.gitignore new file mode 100644 index 000000000000..7be83cade597 --- /dev/null +++ b/dist/tools/rethos/.gitignore @@ -0,0 +1 @@ +rethos diff --git a/dist/tools/rethos/LICENSE b/dist/tools/rethos/LICENSE new file mode 100644 index 000000000000..d159169d1050 --- /dev/null +++ b/dist/tools/rethos/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/dist/tools/rethos/Makefile b/dist/tools/rethos/Makefile new file mode 100644 index 000000000000..c5ef7a624e7a --- /dev/null +++ b/dist/tools/rethos/Makefile @@ -0,0 +1,10 @@ +all: rethos + +rethos: rethos.o + $(CC) -O3 -Wall rethos.o -lrt -o rethos + +rethos.o: rethos.c + $(CC) -O3 -Wall -c rethos.c -o rethos.o + +clean: + rm -f rethos rethos.o diff --git a/dist/tools/rethos/README.md b/dist/tools/rethos/README.md new file mode 100644 index 000000000000..2131251daec8 --- /dev/null +++ b/dist/tools/rethos/README.md @@ -0,0 +1,15 @@ +## Requirements + +- currently, the host side only compiles on Linux + +## Usage + +To use, add + + # + GNRC_NETIF_NUMOF := 2 + USEMODULE += ethos gnrc_netdev2 + CFLAGS += '-DETHOS_UART=UART_DEV(0)' -DETHOS_BAUDRATE=115200 -DUSE_ETHOS_FOR_STDIO + +to app Makefile, "make clean all flash", then run this tool so follows: + # sudo ./ethos diff --git a/dist/tools/rethos/rethos.c b/dist/tools/rethos/rethos.c new file mode 100644 index 000000000000..60c4cfd83b48 --- /dev/null +++ b/dist/tools/rethos/rethos.c @@ -0,0 +1,1155 @@ +/* + * Copyright (C) 2016 Sam Kumar + * + * This is a re-implementation of ethos (originally by Kaspar Schleiser) + * that creates a reliable multi-channel duplex link over serial. + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define MTU 9000 + +#define TRACE(x) +#define TTY_TIMEOUT_MS (500) + +#define case_baudrate(val) \ + case val: \ + *baudrate = B ## val; \ + break + +#define BAUDRATE_DEFAULT B115200 + +#define RESERVED_CHANNEL 0 +#define STDIN_CHANNEL 1 +#define TUNTAP_CHANNEL 2 +#define NUM_CHANNELS 256 + +#define TCP_DEV "tcp:" +#define IOTLAB_TCP_PORT "20000" + +bool have_stdin = true; + +typedef struct { + uint64_t serial_received; + uint64_t domain_forwarded; + + uint64_t domain_received; + uint64_t serial_forwarded; + + uint64_t lost_frames; + uint64_t bad_frames; + + uint64_t drop_notconnected; +} __attribute__((packed)) global_stats_t; + +typedef struct { + uint64_t serial_received; + uint64_t domain_forwarded; + uint64_t drop_notconnected; + + uint64_t domain_received; + uint64_t serial_forwarded; +} __attribute__((packed)) channel_stats_t; + +struct { + global_stats_t global; + channel_stats_t channel[NUM_CHANNELS]; +} __attribute__((packed)) stats = {0}; + + +/* Code to handle timers. + * There are two timers, a periodic stats timer, and a retransmission timer. + */ +#define STATS_TIMER_TYPE 0 +#define REXMIT_TIMER_TYPE 1 + +/* Stats timeout is 15 seconds. Type is struct timespec. */ +#define STATS_TIMEOUT {(time_t) 5, 0L} + +/* Retransmission timeout is 100 ms. Type is struct timespec. */ +#define REXMIT_TIMEOUT {(time_t) 0, 100000000L} + +const struct itimerspec stats_timer_spec = { STATS_TIMEOUT, STATS_TIMEOUT }; +const struct itimerspec rexmit_timer_spec = { REXMIT_TIMEOUT, REXMIT_TIMEOUT }; +const struct itimerspec cancel_timer_spec = { {0, 0L}, {0, 0L} }; + +timer_t stats_timer; +timer_t rexmit_timer; + +volatile sig_atomic_t stats_fired = 0; +volatile sig_atomic_t rexmit_fired = 0; + +/* This function executes in the SIGUSR1 signal handler. */ +static void timer_handler(int signum, siginfo_t* info, void* context) { + (void) signum; + (void) context; + + int timer_type = info->si_value.sival_int; + switch (timer_type) { + case STATS_TIMER_TYPE: + stats_fired = 1; + break; + case REXMIT_TIMER_TYPE: + rexmit_fired = 1; + break; + } +} + +void check_fatal_error(const char* msg) +{ + assert(errno); + perror(msg); + exit(1); +} + +static void usage(void) +{ + fprintf(stderr, "Usage: rethos [baudrate]\n"); + fprintf(stderr, " rethos tcp: [port]\n"); +} + +static void checked_write(int fd, const void* buffer, size_t size) +{ + const char* buf = buffer; + size_t written = 0; + while (written < size) { + ssize_t rv = write(fd, &buf[written], size - written); + if (rv == -1) { + char errbuf[50]; + snprintf(errbuf, sizeof(errbuf), "write to fd %d failed", fd); + check_fatal_error(errbuf); + } + written += rv; + } +} + +static void write_message(int handle, const void *buffer, int nbyte) +{ + /* Write 4-byte size in network byte order. */ + uint32_t size = (uint32_t) nbyte; + size = htonl(size); + checked_write(handle, &size, sizeof(size)); + + /* Write the actual data. */ + checked_write(handle, buffer, (size_t) nbyte); +} + +size_t checked_read(int fd, void* buffer, size_t toread) +{ + char* buf = buffer; + size_t consumed = 0; + while (consumed < toread) { + ssize_t rv = read(fd, &buf[consumed], toread - consumed); + if (rv == -1) { + char errbuf[50]; + snprintf(errbuf, sizeof(errbuf), "read from fd %d failed", fd); + check_fatal_error(errbuf); + } else if (rv == 0) { + break; + } + consumed += rv; + } + return consumed; +} + +#define READ_SUCCESS 0 +#define READ_EOF 1 +#define READ_PARTIAL 2 +#define READ_OVERFLOW 3 +static uint32_t read_message(int* status, int handle, void* buffer, int buffer_size) +{ + uint32_t message_size; + uint32_t bytes_read = checked_read(handle, &message_size, sizeof(message_size)); + if (bytes_read != sizeof(message_size)) { + if (bytes_read == 0) { + *status = READ_EOF; + } else { + *status = READ_PARTIAL; + } + return 0; + } + message_size = ntohl(message_size); + + uint32_t bytes_to_read = message_size; + if (bytes_to_read > (uint32_t) buffer_size) { + bytes_to_read = (uint32_t) buffer_size; + } + + bytes_read = checked_read(handle, buffer, bytes_to_read); + if (bytes_read != bytes_to_read) { + *status = READ_PARTIAL; + } + + if (message_size > (uint32_t) buffer_size) { + *status = READ_OVERFLOW; + size_t remaining = message_size - (uint32_t) buffer_size; + char sbuf; + while (remaining != 0) { + checked_read(handle, &sbuf, 1); + remaining--; + } + } else { + *status = READ_SUCCESS; + } + + return message_size; +} + +int set_serial_attribs (int fd, int speed, int parity) +{ + struct termios tty; + memset (&tty, 0, sizeof tty); + if (tcgetattr (fd, &tty) != 0) + { + perror ("error in tcgetattr"); + return -1; + } + + cfsetospeed (&tty, speed); + cfsetispeed (&tty, speed); + + tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; /* 8-bit chars*/ + /* disable IGNBRK for mismatched speed + * tests; otherwise receive break*/ + /* as \000 chars*/ + tty.c_iflag &= ~IGNBRK; /* disable break processing*/ + tty.c_lflag = 0; /* no signaling chars, no echo,*/ + /* no canonical processing*/ + tty.c_oflag = 0; /* no remapping, no delays*/ + tty.c_cc[VMIN] = 0; /* read doesn't block*/ + tty.c_cc[VTIME] = TTY_TIMEOUT_MS / 100; /* 0.5 seconds read timeout*/ + /* in tenths of a second*/ + + tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* shut off xon/xoff ctrl*/ + + tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls,*/ + /* enable reading*/ + tty.c_cflag &= ~(PARENB | PARODD); /* shut off parity*/ + tty.c_cflag |= parity; + tty.c_cflag &= ~CSTOPB; + tty.c_cflag &= ~CRTSCTS; + cfmakeraw(&tty); + + if (tcsetattr (fd, TCSANOW, &tty) != 0) + { + perror("error from tcsetattr"); + return -1; + } + return 0; +} + +void set_blocking (int fd, int should_block) +{ + struct termios tty; + memset (&tty, 0, sizeof tty); + if (tcgetattr (fd, &tty) != 0) + { + perror("error from tggetattr"); + return; + } + + tty.c_cc[VMIN] = should_block ? 1 : 0; + tty.c_cc[VTIME] = TTY_TIMEOUT_MS / 100; /* 0.5 seconds read timeout*/ + /* in tenths of a second*/ + + if (tcsetattr (fd, TCSANOW, &tty) != 0) + perror("error setting term attributes"); +} + +/** + * @name Escape char definitions + * @{ + */ +#define RETHOS_ESC_CHAR (0xBE) +/* This means that a stream of ESC_CHAR still keeps us inside the escape state */ +#define RETHOS_LITERAL_ESC (0x55) +#define RETHOS_FRAME_START (0xEF) +#define RETHOS_FRAME_END (0xE5) + +#define RETHOS_FRAME_TYPE_DATA (0x1) + +#define RETHOS_FRAME_TYPE_HB (0x2) +#define RETHOS_FRAME_TYPE_HB_REPLY (0x3) + +#define RETHOS_FRAME_TYPE_ACK (0x4) +#define RETHOS_FRAME_TYPE_NACK (0x5) + +/** @} */ + +static const uint8_t _esc_esc[] = {RETHOS_ESC_CHAR, RETHOS_LITERAL_ESC}; +static const uint8_t _start_frame[] = {RETHOS_ESC_CHAR, RETHOS_FRAME_START}; +static const uint8_t _end_frame[] = {RETHOS_ESC_CHAR, RETHOS_FRAME_END}; + +typedef enum { + WAIT_FRAMESTART, + WAIT_FRAMETYPE, + WAIT_SEQNO_1, + WAIT_SEQNO_2, + WAIT_CHANNEL, + IN_FRAME, + WAIT_CHECKSUM_1, + WAIT_CHECKSUM_2 +} line_state_t; + +typedef struct { + int fd; + + /* State for storing partial checksum. */ + uint16_t sum1; + uint16_t sum2; + + /* State for reading data. */ + line_state_t state; + uint8_t frametype; + uint16_t in_seqno; + uint8_t channel; + size_t numbytes; + char frame[MTU]; + uint16_t checksum; + + bool in_escape; + + /* State for writing data. */ + uint16_t out_seqno; + + /* Last data frame sent, used for retransmissions. */ + // rexmit_frametype is always RETHOS_FRAME_TYPE_DATA + uint16_t rexmit_seqno; + uint8_t rexmit_channel; + size_t rexmit_numbytes; + uint8_t rexmit_frame[MTU]; + bool rexmit_acked; + + /* Keeps track of whether anything has been sent yet. */ + bool received_data_frame; + + /* Last received sequence number, used to detect losses. */ + uint16_t last_rcvd_seqno; +} serial_t; + +static void fletcher16_add(const uint8_t *data, size_t bytes, uint16_t *sum1i, uint16_t *sum2i) +{ + uint16_t sum1 = *sum1i, sum2 = *sum2i; + + while (bytes) { + size_t tlen = bytes > 20 ? 20 : bytes; + bytes -= tlen; + do { + sum2 += sum1 += *data++; + } while (--tlen); + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + } + *sum1i = sum1; + *sum2i = sum2; +} + +static uint16_t fletcher16_fin(uint16_t sum1, uint16_t sum2) +{ + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + return (sum2 << 8) | sum1; +} + +typedef enum { + NO_EVENT, + FRAME_READY, + FRAME_DROPPED +} serial_event_t; + +static serial_event_t _serial_handle_byte(serial_t *serial, char c) +{ + serial_event_t event = NO_EVENT; + + if (c == RETHOS_ESC_CHAR) { + serial->in_escape = true; + return event; + } + + if (serial->in_escape) { + if (c == RETHOS_LITERAL_ESC) { + c = RETHOS_ESC_CHAR; + serial->in_escape = false; + } else if (c == RETHOS_FRAME_START) { + /* If we receive the start sequence, then drop the current frame and start receiving. */ + if (serial->state != WAIT_FRAMESTART) { + fprintf(stderr, "Got unexpected start-of-frame sequence: dropping current frame\n"); + } + serial->sum1 = 0xFF; + serial->sum2 = 0xFF; + serial->state = WAIT_FRAMETYPE; + goto done_char; + } else if (c == RETHOS_FRAME_END) { + if (serial->state != IN_FRAME) { + fprintf(stderr, "Got unexpected end-of-frame sequence: dropping current frame\n"); + goto handle_corrupt_frame; + } + + serial->state = WAIT_CHECKSUM_1; + goto done_char; + } else { + fprintf(stderr, "Got unexpected escape sequence 0xBE%X: dropping current frame\n", (uint8_t) c); + goto handle_corrupt_frame; + } + } + + switch (serial->state) { + case WAIT_FRAMESTART: + fprintf(stderr, "Got stray byte %c\n", c); + break; + case WAIT_FRAMETYPE: + serial->frametype = (uint8_t) c; + serial->state = WAIT_SEQNO_1; + break; + case WAIT_SEQNO_1: + serial->in_seqno = ((uint8_t) c); + serial->state = WAIT_SEQNO_2; + break; + case WAIT_SEQNO_2: + serial->in_seqno |= (((uint16_t) c) << 8); + serial->state = WAIT_CHANNEL; + break; + case WAIT_CHANNEL: + serial->channel = (uint8_t) c; + serial->state = IN_FRAME; + serial->numbytes = 0; + break; + case IN_FRAME: + if (serial->numbytes >= MTU) { + fprintf(stderr, "Dropping runaway frame\n"); + goto handle_corrupt_frame; + } + serial->frame[serial->numbytes] = c; + serial->numbytes++; + break; + case WAIT_CHECKSUM_1: + serial->checksum = (uint8_t) c; + serial->state = WAIT_CHECKSUM_2; + goto done_char; + case WAIT_CHECKSUM_2: + serial->checksum |= (((uint16_t) c) << 8); + + /* Check the checksum. */ + if (serial->checksum != fletcher16_fin(serial->sum1, serial->sum2)) { + goto handle_corrupt_frame; + } + + /* Set "ready" so the frame is delivered to the handler. */ + event = FRAME_READY; + + goto drop_frame_state; + } + + /* Update checksum. */ + fletcher16_add((uint8_t*) &c, 1, &serial->sum1, &serial->sum2); + + goto done_char; + +handle_corrupt_frame: + /* It's the caller's responsibility to send a NACK if this happens. */ + event = FRAME_DROPPED; + +drop_frame_state: + /* Start listening for a frame at the beginning. */ + serial->state = WAIT_FRAMESTART; + +done_char: + /* Finished handling this character. */ + serial->in_escape = false; + return event; +} + +/************************************************************************** + * tun_alloc: allocates or reconnects to a tun/tap device. The caller * + * needs to reserve enough space in *dev. * + **************************************************************************/ +int tun_alloc(char *dev, int flags) { + + struct ifreq ifr; + int fd, err; + + if( (fd = open("/dev/net/tun", O_RDWR)) < 0 ) { + perror("Opening /dev/net/tun"); + return fd; + } + + memset(&ifr, 0, sizeof(ifr)); + + ifr.ifr_flags = flags; + + if (*dev) { + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + } + + if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) { + perror("ioctl(TUNSETIFF)"); + close(fd); + return err; + } + + strcpy(dev, ifr.ifr_name); + + return fd; +} + +static void _write_escaped(int fd, const uint8_t* buf, ssize_t n) +{ + for (ssize_t i = 0; i < n; i++) { + char c = (char) buf[i]; + if (c == RETHOS_ESC_CHAR) { + checked_write(fd, _esc_esc, sizeof(_esc_esc)); + } else { + checked_write(fd, &buf[i], 1); + } + } +} + +void _send_frame(serial_t* serial, const uint8_t *data, size_t thislen, uint8_t channel, uint16_t seqno, uint8_t frame_type) +{ + uint8_t preamble_buffer[4]; + uint8_t postamble_buffer[2]; + + uint16_t flsum1 = 0xFF; + uint16_t flsum2 = 0xFF; + + //This is where the checksum starts + preamble_buffer[0] = frame_type; + preamble_buffer[1] = seqno & 0xFF; //Little endian cos im a rebel + preamble_buffer[2] = seqno >> 8; + preamble_buffer[3] = channel; + + checked_write(serial->fd, _start_frame, sizeof(_start_frame)); + + fletcher16_add(preamble_buffer, sizeof(preamble_buffer), &flsum1, &flsum2); + _write_escaped(serial->fd, preamble_buffer, sizeof(preamble_buffer)); + + fletcher16_add(data, thislen, &flsum1, &flsum2); + _write_escaped(serial->fd, data, thislen); + + checked_write(serial->fd, _end_frame, sizeof(_end_frame)); + + uint16_t cksum = fletcher16_fin(flsum1, flsum2); + postamble_buffer[0] = (uint8_t) cksum; + postamble_buffer[1] = (uint8_t) (cksum >> 8); + + _write_escaped(serial->fd, postamble_buffer, sizeof(postamble_buffer)); +} + +void rethos_send_data_frame(serial_t* serial, const uint8_t* data, size_t datalen, uint8_t channel) { + uint16_t seqno = ++serial->out_seqno; + + /* Store this data, in case we need to retransmit it. */ + serial->rexmit_seqno = seqno; + serial->rexmit_channel = channel; + serial->rexmit_numbytes = datalen; + memcpy(serial->rexmit_frame, data, datalen); + serial->rexmit_acked = false; + + _send_frame(serial, data, datalen, channel, seqno, RETHOS_FRAME_TYPE_DATA); + + /* Set the rexmit timer. */ + if (timer_settime(rexmit_timer, 0, &rexmit_timer_spec, NULL) == -1) { + check_fatal_error("Could not set rexmit timer"); + } +} + +void rethos_rexmit_data_frame(serial_t* serial) { + _send_frame(serial, serial->rexmit_frame, serial->rexmit_numbytes, serial->rexmit_channel, serial->rexmit_seqno, RETHOS_FRAME_TYPE_DATA); +} + +void rethos_send_ack_frame(serial_t* serial, uint16_t seqno) { + _send_frame(serial, NULL, 0, RESERVED_CHANNEL, seqno, RETHOS_FRAME_TYPE_ACK); +} + +void rethos_send_nack_frame(serial_t* serial) { + _send_frame(serial, NULL, 0, RESERVED_CHANNEL, 0, RETHOS_FRAME_TYPE_NACK); +} + +/*static void _clear_neighbor_cache(const char *ifname) +{ + char tmp[20 + IFNAMSIZ]; + snprintf(tmp, sizeof(tmp), "ip neigh flush dev %s", ifname); + if (system(tmp) < 0) { + fprintf(stderr, "error while flushing device neighbor cache\n"); + } +}*/ + +static int _parse_baudrate(const char *arg, unsigned *baudrate) +{ + if (arg == NULL) { + *baudrate = BAUDRATE_DEFAULT; + return 0; + } + + switch(strtol(arg, (char**)NULL, 10)) { + case 9600: + *baudrate = B9600; + break; + case 19200: + *baudrate = B19200; + break; + case 38400: + *baudrate = B38400; + break; + case 57600: + *baudrate = B57600; + break; + case 115200: + *baudrate = B115200; + break; + /* the following baudrates might not be available on all platforms */ + #ifdef B234000 + case_baudrate(230400); + #endif + #ifdef B460800 + case_baudrate(460800); + #endif + #ifdef B500000 + case_baudrate(500000); + #endif + #ifdef B576000 + case_baudrate(576000); + #endif + #ifdef B921600 + case_baudrate(921600); + #endif + #ifdef B1000000 + case_baudrate(1000000); + #endif + #ifdef B1152000 + case_baudrate(1152000); + #endif + #ifdef B1500000 + case_baudrate(1500000); + #endif + #ifdef B2000000 + case_baudrate(2000000); + #endif + #ifdef B2500000 + case_baudrate(2500000); + #endif + #ifdef B3000000 + case_baudrate(3000000); + #endif + #ifdef B3500000 + case_baudrate(3500000); + #endif + #ifdef B4000000 + case_baudrate(4000000); + #endif + default: + return -1; + } + + return 0; +} + +int _parse_tcp_arg(char *name, char *port_arg, char **host, char **port) +{ + /* Remove 'tcp:' */ + name = &name[sizeof(TCP_DEV) - 1]; + + /* Set default if NULL */ + if (!port_arg) { + port_arg = IOTLAB_TCP_PORT; + } + + *host = name; + *port = port_arg; + + return 0; +} + +/* Adapted from 'getaddrinfo' manpage example */ +int _tcp_connect(char *host, char *port) +{ + int sfd = -1; + struct addrinfo hints, *result, *rp; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + int s = getaddrinfo(host, port, &hints, &result); + if (s) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + return -1; + } + + /* getaddrinfo() returns a list of address structures. + Try each address until we successfully connect(2). + If socket(2) (or connect(2)) fails, we (close the socket + and) try the next address. */ + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) + continue; + + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; + + close(sfd); + } + + freeaddrinfo(result); + + if (rp == NULL) { + fprintf(stderr, "Could not connect to '%s:%s'\n", host, port); + return -1; + } + + return sfd; +} + +int _set_socket_timeout(int sfd) +{ + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = TTY_TIMEOUT_MS * 1000, + }; + + if (setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, + (char *)&timeout, sizeof(timeout)) == -1) { + perror("setsockopt failed\n"); + return 1; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_SNDTIMEO, + (char *)&timeout, sizeof(timeout)) == -1) { + perror("setsockopt failed\n"); + return 1; + } + return 0; +} + +int _open_tcp_connection(char *name, char *port_arg) +{ + char *host; + char *port; + + int ret = _parse_tcp_arg(name, port_arg, &host, &port); + if (ret) { + fprintf(stderr, "Error while parsing tcp arguments\n"); + return -1; + } + + int sfd = _tcp_connect(host, port); + if (_set_socket_timeout(sfd)) { + fprintf(stderr, "Error while setting socket options\n"); + return -1; + } + return sfd; +} + +int _open_serial_connection(char *name, char *baudrate_arg) +{ + unsigned baudrate = 0; + if (_parse_baudrate(baudrate_arg, &baudrate) == -1) { + fprintf(stderr, "Invalid baudrate specified: %s\n", baudrate_arg); + return 1; + } + + int serial_fd = open(name, O_RDWR | O_NOCTTY | O_SYNC); + + if (serial_fd < 0) { + fprintf(stderr, "Error opening serial device %s\n", name); + return -1; + } + + set_serial_attribs(serial_fd, baudrate, 0); + set_blocking(serial_fd, 1); + + return serial_fd; +} + +int _open_connection(char *name, char* option) +{ + if (strncmp(name, TCP_DEV, strlen(TCP_DEV)) == 0) { + return _open_tcp_connection(name, option); + } else { + return _open_serial_connection(name, option); + } +} + +typedef struct { + int server_socket; + int client_socket; +} channel_t; + +void channel_listen(channel_t* chan, int channel_number) { + int dsock; + int flags; + struct sockaddr_un bound_name; + size_t total_size; + memset(&bound_name, 0, sizeof(bound_name)); + bound_name.sun_family = AF_UNIX; + snprintf(&bound_name.sun_path[1], sizeof(bound_name.sun_path) - 1, "rethos/%d", channel_number); + total_size = sizeof(bound_name.sun_family) + 1 + strlen(&bound_name.sun_path[1]); + dsock = socket(AF_UNIX, SOCK_STREAM, 0); + if (dsock == -1) { + check_fatal_error("Could not create domain socket"); + } + flags = fcntl(dsock, F_GETFL); + if (flags == -1) { + check_fatal_error("Could not get socket flags"); + } + flags = fcntl(dsock, F_SETFL, flags | O_NONBLOCK); + if (flags == -1) { + check_fatal_error("Could not set socket flags"); + } + if (bind(dsock, (struct sockaddr*) &bound_name, total_size) == -1) { + check_fatal_error("Could not bind domain socket"); + } + if (listen(dsock, 0) == -1) { + check_fatal_error("Could not listen on domain socket"); + } + + chan->server_socket = dsock; + chan->client_socket = -1; +} + +int main(int argc, char *argv[]) +{ + uint8_t inbuf[MTU]; + char *serial_option = NULL; + + /* Block the SIGUSR1 signal. */ + sigset_t oldmask; + sigset_t toblock; + if (sigemptyset(&toblock) == -1) { + check_fatal_error("Could not create empty signal set"); + } + if (sigaddset(&toblock, SIGUSR1) == -1) { + check_fatal_error("Could not add signal to signal set"); + } + if (sigprocmask(SIG_BLOCK, &toblock, &oldmask) == -1) { + check_fatal_error("Could not block signal"); + } + + struct sigaction act; + memset(&act, 0x00, sizeof(struct sigaction)); + act.sa_sigaction = timer_handler; + act.sa_flags = SA_SIGINFO; + if (sigaction(SIGUSR1, &act, NULL) == -1) { + check_fatal_error("Could not set up signal handler for SIGUSR1"); + } + + serial_t serial = {0}; + serial.rexmit_acked = true; // The retransmit buffer contains garbage, so don't transmit that + + if (argc < 3) { + usage(); + return 1; + } + + if (argc >= 4) { + serial_option = argv[3]; + } + + char ifname[IFNAMSIZ]; + strncpy(ifname, argv[1], IFNAMSIZ); + int tap_fd = tun_alloc(ifname, IFF_TAP | IFF_NO_PI); + + if (tap_fd < 0) { + return 1; + } + + + int serial_fd = _open_connection(argv[2], serial_option); + if (serial_fd < 0) { + fprintf(stderr, "Error opening serial device %s\n", argv[2]); + return 1; + } + + serial.fd = serial_fd; + + fd_set readfds; + + channel_t domain_sockets[NUM_CHANNELS]; + int i; + for (i = 0; i < NUM_CHANNELS; i++) { + channel_listen(&domain_sockets[i], i); + } + + struct sigevent sev; + memset(&sev, 0x00, sizeof(struct sigevent)); + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = SIGUSR1; + sev.sigev_value.sival_int = STATS_TIMER_TYPE; + if (timer_create(CLOCK_MONOTONIC, &sev, &stats_timer) == -1) { + check_fatal_error("Could not create rexmit timer"); + } + + sev.sigev_value.sival_int = REXMIT_TIMER_TYPE; + if (timer_create(CLOCK_MONOTONIC, &sev, &rexmit_timer) == -1) { + check_fatal_error("Could not create rexmit timer"); + } + + if (timer_settime(stats_timer, 0, &stats_timer_spec, NULL) == -1) { + check_fatal_error("Could not set stats timer"); + } + + while (true) { + int activity; + int max_fd = 0; + #define UPDATE_MAX_FD(fd) max_fd = ((fd) > max_fd ? (fd) : max_fd); + FD_ZERO(&readfds); + if (have_stdin) { + FD_SET(STDIN_FILENO, &readfds); + UPDATE_MAX_FD(STDIN_FILENO); + } + FD_SET(tap_fd, &readfds); + UPDATE_MAX_FD(tap_fd); + FD_SET(serial_fd, &readfds); + UPDATE_MAX_FD(serial_fd); + for (i = 0; i < NUM_CHANNELS; i++) { + if (domain_sockets[i].client_socket == -1) { + FD_SET(domain_sockets[i].server_socket, &readfds); + UPDATE_MAX_FD(domain_sockets[i].server_socket); + } else { + FD_SET(domain_sockets[i].client_socket, &readfds); + UPDATE_MAX_FD(domain_sockets[i].client_socket); + } + } + + activity = pselect(max_fd + 1, &readfds, NULL, NULL, NULL, &oldmask); + + if (activity == -1 && (errno != EINTR || (stats_fired == 0 && rexmit_fired == 0))) { + check_fatal_error("Could not wait for event"); + } + + if (stats_fired == 1) { + stats_fired = 0; + + printf("================================================================================\n"); + printf("Received %" PRIu64 " frames on serial link; forwarded %" PRIu64 " on domain sockets\n", stats.global.serial_received, stats.global.domain_forwarded); + printf("Received %" PRIu64 " frames on domain sockets; forwarded %" PRIu64 " on serial link\n", stats.global.domain_received, stats.global.serial_forwarded); + printf("Lost %" PRIu64 " frames, %" PRIu64 " of which were detected on the serial link\n", stats.global.lost_frames, stats.global.bad_frames); + printf("An additional %" PRIu64 " frames were dropped, due to lack of a listening process\n", stats.global.drop_notconnected); + + if (domain_sockets[RESERVED_CHANNEL].client_socket != -1) { + write_message(domain_sockets[RESERVED_CHANNEL].client_socket, &stats, sizeof(stats)); + } + } + + if (rexmit_fired == 1) { + rexmit_fired = 0; + + if (!serial.rexmit_acked) { + rethos_rexmit_data_frame(&serial); + } + } + + if (activity == -1) { + /* The file descriptor sets were unmodified. The only reason pselect + * returned was because of the signal. So readfds just contains + * what we originally put there, and there are really no file + * descriptors that are ready. + */ + continue; + } + + if (FD_ISSET(serial_fd, &readfds)) { + ssize_t n = read(serial_fd, (char*) inbuf, sizeof(inbuf)); + if (n > 0) { + char* ptr = (char*) inbuf; + for (i = 0; i != n; i++) { + serial_event_t event = _serial_handle_byte(&serial, *ptr++); + if (event == FRAME_READY) { + if (serial.channel >= NUM_CHANNELS) { + continue; + } + + stats.channel[serial.channel].serial_received++; + stats.global.serial_received++; + + /* Use the sequence number and + * message type for reliable delivery. */ + if (serial.channel == RESERVED_CHANNEL) { + if (serial.frametype == RETHOS_FRAME_TYPE_NACK) { + /* If we got a NACK for something that was already ACKed, it's + * probably because we sent a NACK that got corrupted, or an ACK + * that got corrupted. + * + * Sending a NACK could cause a NACK storm, so instead just ACK + * the last packet we received. + */ + if (serial.rexmit_acked) { + if (serial.received_data_frame) + { + rethos_send_ack_frame(&serial, serial.last_rcvd_seqno); + } + } else { + /* Retransmit the last frame that was sent. */ + rethos_rexmit_data_frame(&serial); + } + } else if (serial.frametype == RETHOS_FRAME_TYPE_ACK) { + if (serial.in_seqno == serial.rexmit_seqno) { + /* Mark the frame to be retransmitted as having been ACKed so we don't retransmit it on timeout. */ + serial.rexmit_acked = true; + + /* Cancel the pending rexmit timer. */ + if (timer_settime(rexmit_timer, 0, &cancel_timer_spec, NULL) == -1) { + check_fatal_error("Could not cancel rexmit timer"); + } + } + } else { + printf("Got frame of type %d on control channel\n", serial.frametype); + } + goto serial_done; + } + + /* ACK the frame we just received. */ + rethos_send_ack_frame(&serial, serial.in_seqno); + + /* If it's a duplicate, just drop the frame. */ + if (serial.received_data_frame && (serial.in_seqno == serial.last_rcvd_seqno)) { + printf("Got a duplicate frame on channel %d\n", serial.channel); + goto serial_done; + } + + serial.received_data_frame = true; + + /* Record the number of lost frames. */ + stats.global.lost_frames += (serial.in_seqno - serial.last_rcvd_seqno - 1); + serial.last_rcvd_seqno = serial.in_seqno; + + if (serial.numbytes == 0) { + printf("Got an empty frame on channel %d: dropping frame\n", serial.channel); + goto serial_done; + } else { + printf("Got a frame on channel %d\n", serial.channel); + } + + if (serial.channel == STDIN_CHANNEL) { + checked_write(STDOUT_FILENO, serial.frame, serial.numbytes); + } else if (serial.channel == TUNTAP_CHANNEL) { + write_message(tap_fd, serial.frame, serial.numbytes); + } + + if (domain_sockets[serial.channel].client_socket != -1) { + write_message(domain_sockets[serial.channel].client_socket, serial.frame, serial.numbytes); + stats.channel[serial.channel].domain_forwarded++; + stats.global.domain_forwarded++; + } else { + fprintf(stderr, "Got message on channel %d, which is not connected: dropping message\n", serial.channel); + stats.channel[serial.channel].drop_notconnected++; + if (serial.channel != STDIN_CHANNEL && serial.channel != TUNTAP_CHANNEL) { + stats.global.drop_notconnected++; + } + } + } else if (event == FRAME_DROPPED) { + stats.global.bad_frames++; + stats.global.lost_frames++; + + /* Send a NACK. */ + rethos_send_nack_frame(&serial); + } + } + } + else { + fprintf(stderr, "lost serial connection.\n"); + exit(1); + } + } + serial_done: + + if (FD_ISSET(tap_fd, &readfds)) { + ssize_t res = read(tap_fd, inbuf, sizeof(inbuf)); + if (res <= 0) { + fprintf(stderr, "error reading from tap device. res=%zi\n", res); + continue; + } + rethos_send_data_frame(&serial, inbuf, res, TUNTAP_CHANNEL); + } + + if (FD_ISSET(STDIN_FILENO, &readfds)) { + ssize_t res = read(STDIN_FILENO, inbuf, sizeof(inbuf)); + if (res <= 0) { + fprintf(stderr, "Error reading from stdio. res=%zi. Disabling stdin functionality...\n", res); + have_stdin = false; + continue; + } + rethos_send_data_frame(&serial, inbuf, res, STDIN_CHANNEL); + } + + for (i = 0; i < NUM_CHANNELS; i++) { + int dsock; + if (domain_sockets[i].client_socket == -1) { + dsock = domain_sockets[i].server_socket; + if (FD_ISSET(dsock, &readfds)) { + struct sockaddr_un client_addr; + socklen_t client_addr_len = sizeof(client_addr); + int client_socket = accept(dsock, (struct sockaddr*) &client_addr, &client_addr_len); + if (client_socket == -1) { + check_fatal_error("accept connection on domain socket"); + } + printf("Accepted client process on channel %d\n", i); + domain_sockets[i].client_socket = client_socket; + + /* Stop listening on this channel. It only makes sense to have one entity listening and writing. */ + close(domain_sockets[i].server_socket); + domain_sockets[i].server_socket = -1; + } + } else { + dsock = domain_sockets[i].client_socket; + if (FD_ISSET(dsock, &readfds)) { + int status; + uint32_t message_size = read_message(&status, dsock, inbuf, sizeof(inbuf)); + + switch (status) { + case READ_SUCCESS: + stats.channel[i].domain_received++; + stats.global.domain_received++; + rethos_send_data_frame(&serial, inbuf, message_size, i); + stats.channel[i].serial_forwarded++; + stats.global.serial_forwarded++; + break; + case READ_OVERFLOW: + fprintf(stderr, "frame too big; skipping\n"); + break; + case READ_PARTIAL: + fprintf(stderr, "read from domain socket (fd %d) failed: closing\n", domain_sockets[i].client_socket); + /* fallthrough intentional */ + case READ_EOF: + close(dsock); + domain_sockets[i].client_socket = -1; + channel_listen(&domain_sockets[i], i); + printf("Client process on channel %d disconnected\n", i); + break; + } + } + } + } + } + + return 0; +} diff --git a/dist/tools/rethos/start_network.sh b/dist/tools/rethos/start_network.sh new file mode 100644 index 000000000000..c69a6c6c6624 --- /dev/null +++ b/dist/tools/rethos/start_network.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +RETHOS_DIR="$(dirname $(readlink -f $0))" + +create_tap() { + ip tuntap add ${TAP} mode tap user ${USER} + sysctl -w net.ipv6.conf.${TAP}.forwarding=1 + sysctl -w net.ipv6.conf.${TAP}.accept_ra=0 + ip link set ${TAP} up + ip a a fe80::1/64 dev ${TAP} + ip a a fd00:dead:beef::1/128 dev lo + ip route add ${PREFIX} via fe80::2 dev ${TAP} +} + +remove_tap() { + ip tuntap del ${TAP} mode tap +} + +cleanup() { + echo "Cleaning up..." + remove_tap + ip a d fd00:dead:beef::1/128 dev lo + kill ${UHCPD_PID} + trap "" INT QUIT TERM EXIT +} + +start_uhcpd() { + ${UHCPD} ${TAP} ${PREFIX} > /dev/null & + UHCPD_PID=$! +} + +PORT=$1 +TAP=$2 +PREFIX=$3 +BAUDRATE=115200 +UHCPD="$(readlink -f "${RETHOS_DIR}/../uhcpd/bin")/uhcpd" + +[ -z "${PORT}" -o -z "${TAP}" -o -z "${PREFIX}" ] && { + echo "usage: $0 [baudrate]" + exit 1 +} + +[ ! -z $4 ] && { + BAUDRATE=$4 +} + +trap "cleanup" INT QUIT TERM EXIT + + +create_tap && start_uhcpd && "${RETHOS_DIR}/rethos" ${TAP} ${PORT} ${BAUDRATE} diff --git a/drivers/include/rethos.h b/drivers/include/rethos.h new file mode 100644 index 000000000000..0ea264ed5981 --- /dev/null +++ b/drivers/include/rethos.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2016 Michael Andersen + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @defgroup drivers_ethos rethos + * @ingroup drivers_netdev + * @brief Driver for the Really EveryTHing over-serial module + * @{ + * + * @file + * @brief Interface definition for the rethos module + * + * @author Michael Andersen + */ + +#ifndef RETHOS_H +#define RETHOS_H + +#include "kernel_types.h" +#include "periph/uart.h" +#include "net/netdev.h" +#include "tsrb.h" +#include "mutex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* rethos is a drop in replacement for ethos, so shares some of its + symbols. The difference between an ETHOS and RETHOS frame is as follows: + +A RETHOS frame looks like: + .. frame .. + +a RETHOS implementation may drop frames whose types it does not recognize, or +frames on channels that have no registered listener + +Note that it is illegal for any character in the preamble to be 0xBE (the escape) +so that implies that even the preamble needs to be escaped when written +*/ + +/* if using ethos + stdio, use UART_STDIO values unless overridden */ +#ifdef USE_ETHOS_FOR_STDIO +#include "uart_stdio.h" +#ifndef ETHOS_UART +#define ETHOS_UART UART_STDIO_DEV +#endif +#ifndef ETHOS_BAUDRATE +#define ETHOS_BAUDRATE UART_STDIO_BAUDRATE +#endif +#endif + +#ifndef RETHOS_TX_BUF_SZ +#define RETHOS_TX_BUF_SZ 2048 +#endif + +#ifndef RETHOS_RX_BUF_SZ +#define RETHOS_RX_BUF_SZ 2048 +#endif + +/** + * @name Escape char definitions + * @{ + */ +#define RETHOS_ESC_CHAR (0xBE) +/* This means that a stream of ESC_CHAR still keeps us inside the escape state */ +#define RETHOS_LITERAL_ESC (0x55) +#define RETHOS_FRAME_START (0xEF) +#define RETHOS_FRAME_END (0xE5) + +#define RETHOS_FRAME_TYPE_DATA (0x1) + +#define RETHOS_FRAME_TYPE_HB (0x2) +#define RETHOS_FRAME_TYPE_HB_REPLY (0x3) + +/* Sam: I am going to remove this because I don't use it at all. +#define RETHOS_FRAME_TYPE_SETMAC (0x4) +*/ + +#define RETHOS_FRAME_TYPE_ACK (0x4) +#define RETHOS_FRAME_TYPE_NACK (0x5) + +#define RETHOS_CHANNEL_CONTROL 0x00 +#define RETHOS_CHANNEL_NETDEV 0x01 +#define RETHOS_CHANNEL_STDIO 0x02 + + +/* Retransmit interval in microseconds. */ +#define RETHOS_REXMIT_MICROS 100000L + +/** @} */ + +/** + * @brief enum describing line state + */ +typedef enum { + SM_WAIT_FRAMESTART, + SM_WAIT_TYPE, + SM_WAIT_SEQ0, + SM_WAIT_SEQ1, + SM_WAIT_CHANNEL, + SM_IN_FRAME, + SM_WAIT_CKSUM1, + SM_WAIT_CKSUM2, + SM_IN_ESCAPE +} line_state_t; + +struct _rethos_handler; + +typedef struct _rethos_handler rethos_handler_t; + + +struct rethos_recv_ctx { + uint8_t rx_buffer [RETHOS_RX_BUF_SZ]; + size_t rx_buffer_index; + uint8_t rx_frame_type; + uint16_t rx_seqno; + uint8_t rx_channel; + uint16_t rx_cksum1; + uint16_t rx_cksum2; + uint16_t rx_actual_cksum; //The data + uint16_t rx_expected_cksum; //The header +}; + +/** + * @brief ethos netdev device + * @extends netdev_t + */ +typedef struct { + netdev_t netdev; /**< extended netdev structure */ + uart_t uart; /**< UART device the to use */ + + line_state_t state; /**< Line status variable */ + line_state_t fromstate; /**< what you go back to after escape */ + // size_t framesize; /**< size of currently incoming frame */ + // unsigned frametype; /**< type of currently incoming frame */ + // size_t last_framesize; /**< size of last completed frame */ + mutex_t out_mutex; /**< mutex used for locking concurrent sends */ + + int recv_ctx_index; + struct rethos_recv_ctx recv_ctx[2]; /* One to store received frame for processing, another to store state while receiving frames. */ + + rethos_handler_t *handlers; + + uint32_t stats_rx_cksum_fail; + uint32_t stats_rx_bytes; + uint32_t stats_rx_frames; + + uint32_t stats_tx_bytes; + uint32_t stats_tx_frames; + uint32_t stats_tx_retries; + + uint16_t txseq; + uint16_t flsum1; + uint16_t flsum2; + + /* State for retransmissions. */ + uint16_t rexmit_seqno; + uint8_t rexmit_channel; + size_t rexmit_numbytes; + uint8_t rexmit_frame[RETHOS_TX_BUF_SZ]; + bool rexmit_acked; + + bool nack_ready; + bool rexmit_ready; + bool rx_ready; + + bool received_data; + uint16_t last_rcvd_seqno; +} ethos_t; + +struct _rethos_handler { + struct _rethos_handler *_next; + void (*cb)(ethos_t *dev, uint8_t channel, uint8_t *data, uint16_t length); + uint8_t channel; +}; + +/** + * @brief Struct containing the needed configuration + */ +typedef struct { + uart_t uart; /**< UART device to use */ + uint32_t baudrate; /**< baudrate to UART device */ + uint8_t *buf; /**< buffer for incoming packets */ + size_t bufsize; /**< size of ethos_params_t::buf */ +} ethos_params_t; + +/** + * @brief Setup an ethos based device state. + * + * The supplied buffer *must* have a power-of-two size, and it *must* be large + * enough for the largest expected packet + enough buffer space to buffer + * bytes that arrive while one packet is being handled. + * note that rethos needs a bigger buffer than ethos because it buffers up to TWO frames + * + * E.g., if 1536b ethernet frames are expected, 4096 is probably a good size for @p buf. + * + * @param[out] dev handle of the device to initialize + * @param[in] params parameters for device initialization + */ +void rethos_setup(ethos_t *dev, const ethos_params_t *params); + +void rethos_rexmit_callback(void* arg); + +/** + * @brief send frame over serial port using ethos' framing + * + * This is used by e.g., stdio over ethos to send text frames. + * + * @param[in] dev handle of the device to initialize + * @param[in] data ptr to data to be sent + * @param[in] len nr of bytes to send + * @param[in] frame_type frame channel to use + */ +void ethos_send_frame(ethos_t *dev, const uint8_t *data, size_t len, unsigned channel); + +/** + * @brief send frame over serial port using ethos' framing + * + * This is used by e.g., stdio over ethos to send text frames. + * + * @param[in] dev handle of the device to initialize + * @param[in] data ptr to data to be sent + * @param[in] len nr of bytes to send + * @param[in] frame_type frame channel to use + */ +void rethos_send_frame(ethos_t *dev, const uint8_t *data, size_t len, uint8_t channel, uint8_t frame_type); + +void rethos_rexmit_data_frame(ethos_t* dev); + +void rethos_send_ack_frame(ethos_t* dev, uint16_t seqno); + +void rethos_send_nack_frame(ethos_t* dev); + +/** + * @brief send frame over serial port using ethos' framing + * + * This is used by e.g., stdio over ethos to send text frames. + * + * @param[in] dev handle of the device to initialize + * @param[in] data ptr to data to be sent + * @param[in] thislen nr of bytes to send on this invocation + * @param[in] frame_type frame type to use + */ +void rethos_start_frame(ethos_t *dev, const uint8_t *data, size_t thislen, uint8_t channel, uint8_t frame_type); + +/** + * @brief send frame over serial port using ethos' framing + * + * This is used by e.g., stdio over ethos to send text frames. + * + * @param[in] dev handle of the device to initialize + * @param[in] data ptr to data to be sent + * @param[in] thislen nr of bytes to send on this invocation + */ +void rethos_continue_frame(ethos_t *dev, const uint8_t *data, size_t thislen); + +/** + * @brief send frame over serial port using ethos' framing + * + * This is used by e.g., stdio over ethos to send text frames. + * + * @param[in] dev handle of the device to initialize + * @param[in] data ptr to data to be sent + * @param[in] thislen nr of bytes to send on this invocation + * @param[in] frame_type frame type to use + */ +void rethos_end_frame(ethos_t *dev); + + + +void rethos_register_handler(ethos_t *dev, rethos_handler_t *handler); + +#ifdef __cplusplus +} +#endif +#endif /* RETHOS_H */ +/** @} */ diff --git a/drivers/rethos/Makefile b/drivers/rethos/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/drivers/rethos/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/rethos/rethos.c b/drivers/rethos/rethos.c new file mode 100644 index 000000000000..c66d9b444f88 --- /dev/null +++ b/drivers/rethos/rethos.c @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2016 Michael Andersen + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup driver_ethos + * @{ + * + * @file + * @brief A re-implementation of ethos (originally by Kaspar Schleiser) + * that creates a reliable multi-channel duplex link over serial + * + * @author Michael Andersen + * + * @} + */ + +#include +#include +#include + +#include "random.h" +#include "rethos.h" +#include "periph/uart.h" +#include "tsrb.h" +#include "irq.h" + +#include "net/netdev.h" +#include "net/netdev/eth.h" +#include "net/eui64.h" +#include "net/ethernet.h" + +#include + +#ifdef USE_ETHOS_FOR_STDIO +#include "uart_stdio.h" +#include "isrpipe.h" +extern isrpipe_t uart_stdio_isrpipe; +#endif + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static void ethos_isr(void *arg, uint8_t c); +static const netdev_driver_t netdev_driver_ethos; + +static const uint8_t _esc_esc[] = {RETHOS_ESC_CHAR, RETHOS_LITERAL_ESC}; +static const uint8_t _start_frame[] = {RETHOS_ESC_CHAR, RETHOS_FRAME_START}; +static const uint8_t _end_frame[] = {RETHOS_ESC_CHAR, RETHOS_FRAME_END}; + +xtimer_t rexmit_timer; + +void rethos_send_frame_seqno_norexmit(ethos_t *dev, const uint8_t *data, size_t len, uint8_t channel, uint16_t seqno, uint8_t frame_type); +void rethos_start_frame_seqno_norexmit(ethos_t* dev, const uint8_t* data, size_t thislen, uint8_t channel, uint16_t seqno, uint8_t frame_type); + +static void fletcher16_add(const uint8_t *data, size_t bytes, uint16_t *sum1i, uint16_t *sum2i) +{ + uint16_t sum1 = *sum1i, sum2 = *sum2i; + + while (bytes) { + size_t tlen = bytes > 20 ? 20 : bytes; + bytes -= tlen; + do { + sum2 += sum1 += *data++; + } while (--tlen); + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + } + *sum1i = sum1; + *sum2i = sum2; +} + +static uint16_t fletcher16_fin(uint16_t sum1, uint16_t sum2) +{ + sum1 = (sum1 & 0xff) + (sum1 >> 8); + sum2 = (sum2 & 0xff) + (sum2 >> 8); + return (sum2 << 8) | sum1; +} + +void rethos_setup(ethos_t *dev, const ethos_params_t *params) +{ + dev->netdev.driver = &netdev_driver_ethos; + dev->uart = params->uart; + dev->state = SM_WAIT_FRAMESTART; + dev->recv_ctx_index = 0; + dev->recv_ctx[0].rx_buffer_index = 0; + dev->recv_ctx[1].rx_buffer_index = 0; + dev->handlers = NULL; + dev->txseq = 0; + dev->stats_tx_frames = 0; + dev->stats_tx_retries = 0; + dev->stats_tx_bytes = 0; + dev->stats_rx_frames = 0; + dev->stats_rx_cksum_fail = 0; + dev->stats_rx_bytes = 0; + + dev->nack_ready = false; + dev->rx_ready = false; + dev->rexmit_ready = false; + dev->rexmit_acked = true; + dev->received_data = false; + + mutex_init(&dev->out_mutex); + + rexmit_timer.callback = rethos_rexmit_callback; + + uart_init(params->uart, params->baudrate, ethos_isr, (void*)dev); + + //TODO send mac address + // + // uint8_t frame_delim = ETHOS_FRAME_DELIMITER; + // uart_write(dev->uart, &frame_delim, 1); + // ethos_send_frame(dev, dev->mac_addr, 6, ETHOS_FRAME_TYPE_HELLO); +} + +static void sm_invalidate(ethos_t *dev) +{ + struct rethos_recv_ctx* r = &dev->recv_ctx[dev->recv_ctx_index]; + dev->state = SM_WAIT_FRAMESTART; + r->rx_buffer_index = 0; +} +static void process_frame(ethos_t *dev) +{ + /* We want to process the frame in the buffer that isn't being used to + * receive new data right now... + */ + struct rethos_recv_ctx* r = &dev->recv_ctx[1 - dev->recv_ctx_index]; + /* Sam: Michael, I have no idea what you're doing here. + if (dev->rx_frame_type == RETHOS_FRAME_TYPE_SETMAC) + { + memcpy(&dev->remote_mac_addr, dev->rx_buffer, 6); + rethos_send_frame(dev, dev->mac_addr, 6, RETHOS_CHANNEL_CONTROL, RETHOS_FRAME_TYPE_SETMAC); + } + */ + if (r->rx_frame_type != RETHOS_FRAME_TYPE_DATA) + { + /* All ACKs and NACKs happen on the RETHOS-reserved channel. */ + if (r->rx_channel == RETHOS_CHANNEL_CONTROL) + { + if (r->rx_frame_type == RETHOS_FRAME_TYPE_ACK) + { + if (r->rx_seqno == dev->rexmit_seqno) + { + dev->rexmit_acked = true; + xtimer_remove(&rexmit_timer); + } + } + else if (r->rx_frame_type == RETHOS_FRAME_TYPE_NACK) + { + if (dev->rexmit_acked) + { + /* They've already ACKed the last thing we sent, so either one of NACKs got + * corrupted or one of our ACKs got corrupted. + * + * Sending a NACK here could cause a NACK storm, so instead just ACK the last + * thing we received. + */ + if (dev->received_data) + { + rethos_send_ack_frame(dev, r->rx_seqno); + } + } + else + { + /* Retransmit the last data frame we sent. */ + rethos_rexmit_data_frame(dev); + } + } + } + + return; //Other types are internal to rethos + } + + dev->received_data = true; + + if (dev->last_rcvd_seqno == r->rx_seqno) { + return; + } + dev->last_rcvd_seqno = r->rx_seqno; + + /* ACK the frame we just received. */ + rethos_send_ack_frame(dev, r->rx_seqno); + + //Handle the special channels + switch(r->rx_channel) { + /* Sam: I'm not using REthos as a netdev, and this fails when I enable rtt_stdio... */ + /* + case RETHOS_CHANNEL_NETDEV: + tsrb_add(&dev->netdev_inbuf, (char*) dev->rx_buffer, dev->rx_buffer_index); + dev->netdev_packetsz = dev->rx_buffer_index; + dev->netdev.event_callback((netdev_t*) dev, NETDEV_EVENT_ISR); + break; + */ + /* Sam: this fails to compile if we disable -DUSE_ETHOS_FOR_STDIO, so I'm removing it. */ + /* + case RETHOS_CHANNEL_STDIO: + for (size_t i = 0; i < dev->rx_buffer_index; i++) + { + //uart_stdio_rx_cb(NULL, dev->rx_buffer[i]); + isrpipe_write_one(&uart_stdio_isrpipe, dev->rx_buffer[i]); + } + break; + */ + default: + break; + } + //And all registered handlers + rethos_handler_t *h = dev->handlers; + while (h != NULL) + { + if (h->channel == r->rx_channel) { + h->cb(dev, r->rx_channel, r->rx_buffer, r->rx_buffer_index); + } + h = h->_next; + } +} + +static void sm_char(ethos_t *dev, uint8_t c) +{ + struct rethos_recv_ctx* r = &dev->recv_ctx[dev->recv_ctx_index]; + switch (dev->state) + { + case SM_WAIT_TYPE: + r->rx_frame_type = c; + fletcher16_add(&c, 1, &r->rx_cksum1, &r->rx_cksum2); + dev->state = SM_WAIT_SEQ0; + return; + case SM_WAIT_SEQ0: + r->rx_seqno = c; + fletcher16_add(&c, 1, &r->rx_cksum1, &r->rx_cksum2); + dev->state = SM_WAIT_SEQ1; + return; + case SM_WAIT_SEQ1: + r->rx_seqno |= (((uint16_t)c)<<8); + fletcher16_add(&c, 1, &r->rx_cksum1, &r->rx_cksum2); + dev->state = SM_WAIT_CHANNEL; + return; + case SM_WAIT_CHANNEL: + r->rx_channel = c; + fletcher16_add(&c, 1, &r->rx_cksum1, &r->rx_cksum2); + dev->state = SM_IN_FRAME; + return; + case SM_IN_FRAME: + r->rx_buffer[r->rx_buffer_index] = c; + fletcher16_add(&c, 1, &r->rx_cksum1, &r->rx_cksum2); + if ((++r->rx_buffer_index) >= RETHOS_RX_BUF_SZ) { + sm_invalidate(dev); + } + return; + case SM_WAIT_CKSUM1: + r->rx_expected_cksum = c; + dev->state = SM_WAIT_CKSUM2; + return; + case SM_WAIT_CKSUM2: + r->rx_expected_cksum |= (((uint16_t)c)<<8); + if (r->rx_expected_cksum != r->rx_actual_cksum) + { + dev->stats_rx_cksum_fail++; + //SAM: do nack or something + dev->nack_ready = true; + if (dev->netdev.event_callback != NULL) { + dev->netdev.event_callback((netdev_t*) dev, NETDEV_EVENT_ISR); + } + } else { + dev->stats_rx_frames++; + dev->stats_rx_bytes += r->rx_buffer_index; + + /* Switch the active receive context so the next frame doesn't trample over this one (while it's being processed). */ + dev->recv_ctx_index = 1 - dev->recv_ctx_index; + + /* Schedule processing of received frame in a thread. */ + dev->rx_ready = true; + if (dev->netdev.event_callback != NULL) { + dev->netdev.event_callback((netdev_t*) dev, NETDEV_EVENT_ISR); + } + } + sm_invalidate(dev); + return; + default: + return; + } +} +static void sm_frame_start(ethos_t *dev) +{ + struct rethos_recv_ctx* r = &dev->recv_ctx[dev->recv_ctx_index]; + //Drop everything, we are beginning a new frame reception + dev->state = SM_WAIT_TYPE; + r->rx_buffer_index = 0; + r->rx_cksum1 = 0xFF; + r->rx_cksum2 = 0xFF; +} +//This is not quite the real end of the frame, we still expect the checksum +static void sm_frame_end(ethos_t *dev) +{ + struct rethos_recv_ctx* r = &dev->recv_ctx[dev->recv_ctx_index]; + uint16_t cksum = fletcher16_fin(r->rx_cksum1, r->rx_cksum2); + r->rx_actual_cksum = cksum; + dev->state = SM_WAIT_CKSUM1; +} + +static void ethos_isr(void *arg, uint8_t c) +{ + ethos_t *dev = (ethos_t *) arg; + + if (dev->state == SM_IN_ESCAPE) { + switch (c) { + case RETHOS_LITERAL_ESC: + dev->state = dev->fromstate; + sm_char(dev, RETHOS_ESC_CHAR); + return; + case RETHOS_FRAME_START: + sm_frame_start(dev); + return; + case RETHOS_FRAME_END: + sm_frame_end(dev); + return; + default: + //any other character is invalid + sm_invalidate(dev); + return; + } + } else { + switch(c) { + case RETHOS_ESC_CHAR: + dev->fromstate = dev->state; + dev->state = SM_IN_ESCAPE; + return; + default: + sm_char(dev, c); + return; + } + } +} + +//This gets called by netdev +static void _isr(netdev_t *netdev) +{ + ethos_t *dev = (ethos_t *) netdev; + + //This is what we would do if we were acting like a normal netdev... + //dev->netdev.event_callback((netdev_t*) dev, NETDEV_EVENT_RX_COMPLETE); + + if (dev->nack_ready) { + dev->nack_ready = false; + rethos_send_nack_frame(dev); + } + if (dev->rx_ready) { + dev->rx_ready = false; + process_frame(dev); + } + if (dev->rexmit_ready) { + dev->rexmit_ready = false; + rethos_rexmit_data_frame(dev); + xtimer_set(&rexmit_timer, (uint32_t) RETHOS_REXMIT_MICROS); + } +} + +static int _init(netdev_t *encdev) +{ + ethos_t *dev = (ethos_t *) encdev; + (void)dev; + return 0; +} + +static size_t iovec_count_total(const struct iovec *vector, int count) +{ + size_t result = 0; + while(count--) { + result += vector->iov_len; + vector++; + } + return result; +} + +static void _write_escaped(uart_t uart, uint8_t c) +{ + uint8_t *out; + int n; + + switch(c) { + case RETHOS_ESC_CHAR: + out = (uint8_t*)_esc_esc; + n = 2; + break; + default: + out = &c; + n = 1; + } + + uart_write(uart, out, n); +} + +void rethos_rexmit_callback(void* arg) +{ + ethos_t* dev = (ethos_t*) arg; + dev->rexmit_ready = true; + if (dev->netdev.event_callback != NULL) { + dev->netdev.event_callback((netdev_t*) dev, NETDEV_EVENT_ISR); + } +} + +void _start_frame_seqno(ethos_t* dev, const uint8_t* data, size_t thislen, uint8_t channel, uint16_t seqno, uint8_t frame_type) +{ + uint8_t preamble_buffer[6]; + + dev->flsum1 = 0xFF; + dev->flsum2 = 0xFF; + + preamble_buffer[0] = RETHOS_ESC_CHAR; + preamble_buffer[1] = RETHOS_FRAME_START; + //This is where the checksum starts + preamble_buffer[2] = frame_type; + preamble_buffer[3] = seqno & 0xFF; //Little endian cos im a rebel + preamble_buffer[4] = seqno >> 8; + preamble_buffer[5] = channel; + + dev->stats_tx_bytes += 4 + thislen; + + fletcher16_add(&preamble_buffer[2], 4, &dev->flsum1, &dev->flsum2); + + uart_write(dev->uart, preamble_buffer, 2); + for (size_t i = 0; i < 4; i++) + { + _write_escaped(dev->uart, preamble_buffer[2+i]); + } + + if (thislen > 0) + { + fletcher16_add(data, thislen, &dev->flsum1, &dev->flsum2); + //todo replace with a little bit of chunking + for (size_t i = 0; iuart, data[i]); + } + } +} + +void rethos_start_frame_seqno_norexmit(ethos_t* dev, const uint8_t* data, size_t thislen, uint8_t channel, uint16_t seqno, uint8_t frame_type) +{ + if (!irq_is_in()) { + mutex_lock(&dev->out_mutex); + } + + _start_frame_seqno(dev, data, thislen, channel, seqno, frame_type); +} + +void rethos_start_frame_seqno(ethos_t* dev, const uint8_t* data, size_t thislen, uint8_t channel, uint16_t seqno, uint8_t frame_type) +{ + if (!irq_is_in()) { + mutex_lock(&dev->out_mutex); + } + + /* Store this data, in case we need to retransmit it. */ + dev->rexmit_seqno = seqno; + dev->rexmit_channel = (uint8_t) channel; + dev->rexmit_numbytes = thislen; + memcpy(dev->rexmit_frame, data, thislen); + dev->rexmit_acked = true; // We have a partial frame, so don't retransmit it on a NACK + + _start_frame_seqno(dev, data, thislen, channel, seqno, frame_type); +} + +void ethos_send_frame(ethos_t *dev, const uint8_t *data, size_t len, unsigned channel) +{ + rethos_send_frame(dev, data, len, channel, RETHOS_FRAME_TYPE_DATA); +} + +void rethos_send_frame(ethos_t *dev, const uint8_t *data, size_t len, uint8_t channel, uint8_t frame_type) +{ + rethos_start_frame(dev, data, len, channel, frame_type); + rethos_end_frame(dev); +} + +/* We need to copy this because, apparently, both rethos_send_frame and rethos_start_frame are public... */ +void rethos_send_frame_seqno(ethos_t *dev, const uint8_t *data, size_t len, uint8_t channel, uint16_t seqno, uint8_t frame_type) +{ + rethos_start_frame_seqno(dev, data, len, channel, seqno, frame_type); + rethos_end_frame(dev); +} + +void rethos_send_frame_seqno_norexmit(ethos_t *dev, const uint8_t *data, size_t len, uint8_t channel, uint16_t seqno, uint8_t frame_type) +{ + rethos_start_frame_seqno_norexmit(dev, data, len, channel, seqno, frame_type); + rethos_end_frame(dev); +} + +void rethos_rexmit_data_frame(ethos_t* dev) +{ + rethos_send_frame_seqno(dev, dev->rexmit_frame, dev->rexmit_numbytes, dev->rexmit_channel, dev->rexmit_seqno, RETHOS_FRAME_TYPE_DATA); +} + +void rethos_send_ack_frame(ethos_t* dev, uint16_t seqno) +{ + rethos_send_frame_seqno_norexmit(dev, NULL, 0, RETHOS_CHANNEL_CONTROL, seqno, RETHOS_FRAME_TYPE_ACK); +} + +void rethos_send_nack_frame(ethos_t* dev) +{ + rethos_send_frame_seqno_norexmit(dev, NULL, 0, RETHOS_CHANNEL_CONTROL, 0, RETHOS_FRAME_TYPE_NACK); +} + +void rethos_start_frame(ethos_t *dev, const uint8_t *data, size_t thislen, uint8_t channel, uint8_t frame_type) +{ + uint16_t seqno = ++(dev->txseq); + rethos_start_frame_seqno(dev, data, thislen, channel, seqno, frame_type); +} + +void rethos_continue_frame(ethos_t *dev, const uint8_t *data, size_t thislen) +{ + fletcher16_add(data, thislen, &dev->flsum1, &dev->flsum2); + + /* Check if we're going to overflow the rexmit buffer. */ + if (thislen + dev->rexmit_numbytes > RETHOS_TX_BUF_SZ) + { + /* Just stop transmitting data. The checksum should be corrupt anyway, so + * the other side won't think this was valid. We just need to make sure we + * never retransmit. + */ + dev->rexmit_numbytes = RETHOS_TX_BUF_SZ + 1; + return; + } + + dev->stats_tx_bytes += thislen; + //todo replace with a little bit of chunking + for (size_t i = 0; iuart, data[i]); + } +} + +void rethos_end_frame(ethos_t *dev) +{ + uint16_t cksum = fletcher16_fin(dev->flsum1, dev->flsum2); + uart_write(dev->uart, _end_frame, 2); + _write_escaped(dev->uart, cksum & 0xFF); + _write_escaped(dev->uart, cksum >> 8); + dev->stats_tx_frames += 1; + + /* Enable retransmission and set the rexmit timer */ + if (dev->rexmit_numbytes <= RETHOS_TX_BUF_SZ) + { + dev->rexmit_acked = false; + rexmit_timer.arg = dev; + xtimer_set(&rexmit_timer, (uint32_t) RETHOS_REXMIT_MICROS); + } + + if (!irq_is_in()) + { + mutex_unlock(&dev->out_mutex); + } +} + +static int _send(netdev_t *netdev, const struct iovec *vector, unsigned count) +{ + ethos_t * dev = (ethos_t *) netdev; + + rethos_start_frame(dev, NULL, 0, RETHOS_CHANNEL_NETDEV, RETHOS_FRAME_TYPE_DATA); + + /* count total packet length */ + size_t pktlen = iovec_count_total(vector, count); + + while(count--) { + size_t n = vector->iov_len; + uint8_t *ptr = vector->iov_base; + rethos_continue_frame(dev, ptr, n); + vector++; + } + + rethos_end_frame(dev); + + return pktlen; +} + +static int _recv(netdev_t *netdev, void *buf, size_t len, void* info) +{ + /* Sam: I'm not using REthos as a netdev... */ + return 0; +/* + (void) info; + ethos_t * dev = (ethos_t *) netdev; + + if (buf) { + if (len < (int)dev->netdev_packetsz) { + DEBUG("ethos _recv(): receive buffer too small.\n"); + return -1; + } + + len = dev->netdev_packetsz; + + + if ((tsrb_get(&dev->netdev_inbuf, buf, len) != len)) { + DEBUG("ethos _recv(): inbuf doesn't contain enough bytes.\n"); + return -1; + } + + return (int)len; + } + else { + return dev->netdev_packetsz; + } +*/ +} + +static int _get(netdev_t *dev, netopt_t opt, void *value, size_t max_len) +{ + int res = 0; + + switch (opt) { + case NETOPT_ADDRESS: + if (max_len < ETHERNET_ADDR_LEN) { + res = -EINVAL; + } + else { + //_get_mac_addr(dev, (uint8_t*)value); + res = ETHERNET_ADDR_LEN; + } + break; + default: + res = netdev_eth_get(dev, opt, value, max_len); + break; + } + + return res; +} + +void rethos_register_handler(ethos_t *dev, rethos_handler_t *handler) +{ + rethos_handler_t *h = dev->handlers; + handler->_next = NULL; + if (h == NULL) { + dev->handlers = handler; + } else { + while (h->_next != NULL) { + h = h->_next; + } + h->_next = handler; + } +} + +/* netdev interface */ +static const netdev_driver_t netdev_driver_ethos = { + .send = _send, + .recv = _recv, + .init = _init, + .isr = _isr, + .get = _get, + .set = netdev_eth_set +}; diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index d1fe65e2ac7e..726acba77074 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -200,6 +200,11 @@ void auto_init(void) auto_init_ethos(); #endif +#ifdef MODULE_RETHOS + extern void auto_init_rethos(void); + auto_init_rethos(); +#endif + #ifdef MODULE_SLIPDEV extern void auto_init_slipdev(void); auto_init_slipdev(); diff --git a/sys/auto_init/netif/auto_init_rethos.c b/sys/auto_init/netif/auto_init_rethos.c new file mode 100644 index 000000000000..45fd9ed7c4b2 --- /dev/null +++ b/sys/auto_init/netif/auto_init_rethos.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + */ + +/** + * @ingroup auto_init_gnrc_netif + * @{ + * + * @file + * @brief Auto initialization for ethernet-over-serial module + * + * @author Kaspar Schleiser + */ + +#if defined(MODULE_RETHOS) + +#include "log.h" +#include "debug.h" +#include "rethos.h" +#include "periph/uart.h" +#include "net/gnrc/netdev.h" +#include "net/gnrc/netdev/eth.h" + +/** + * @brief global ethos object, used by uart_stdio + */ +ethos_t rethos; + +/** + * @brief Define stack parameters for the MAC layer thread + * @{ + */ +#define RETHOS_MAC_STACKSIZE (THREAD_STACKSIZE_DEFAULT + DEBUG_EXTRA_STACKSIZE) +#ifndef RETHOS_MAC_PRIO +#define RETHOS_MAC_PRIO (GNRC_NETDEV_MAC_PRIO) +#endif + +/** + * @brief Stacks for the MAC layer threads + */ +static char _rethos_stack[RETHOS_MAC_STACKSIZE]; +static gnrc_netdev_t _gnrc_rethos; + +void auto_init_rethos(void) +{ + LOG_DEBUG("[auto_init_netif] initializing rethos #0\n"); + + /* setup netdev device */ + ethos_params_t p; + p.uart = RETHOS_UART; + p.baudrate = RETHOS_BAUDRATE; + p.buf = NULL; + p.bufsize = 0; + rethos_setup(&rethos, &p); + + /* initialize netdev<->gnrc adapter state */ + gnrc_netdev_eth_init(&_gnrc_rethos, (netdev_t*) &rethos); + + /* start gnrc netdev thread */ + gnrc_netdev_init(_rethos_stack, RETHOS_MAC_STACKSIZE, RETHOS_MAC_PRIO, + "gnrc_rethos", &_gnrc_rethos); +} + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_ETHOS */ +/** @} */ From 635c7da68112fa4f6bd22dc9b2d68a7db666d32b Mon Sep 17 00:00:00 2001 From: Sam Kumar Date: Mon, 30 Oct 2017 17:15:20 -0700 Subject: [PATCH 2/2] Toggle LEDs on Hamilton border router when frames are sent and received --- drivers/at86rf2xx/at86rf2xx_netdev.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/at86rf2xx/at86rf2xx_netdev.c b/drivers/at86rf2xx/at86rf2xx_netdev.c index b95e6a1bd0c0..e9f185c4aea1 100644 --- a/drivers/at86rf2xx/at86rf2xx_netdev.c +++ b/drivers/at86rf2xx/at86rf2xx_netdev.c @@ -35,6 +35,9 @@ #include "at86rf2xx_internal.h" #include "at86rf2xx_registers.h" +#define TX_TOGGLE (PORT->Group[0].OUTTGL.reg = (1<<27)) +#define RX_TOGGLE (PORT->Group[1].OUTTGL.reg = (1<<23)) + #define ENABLE_DEBUG (0) #include "debug.h" @@ -120,6 +123,10 @@ static int _send(netdev_t *netdev, const struct iovec *vector, unsigned count) if (!(dev->netdev.flags & AT86RF2XX_OPT_PRELOADING)) { at86rf2xx_tx_exec(dev); } + + /* toggle the "TX" LED on Hamilton border router */ + TX_TOGGLE; + /* return the number of bytes that were actually send out */ return (int)len; } @@ -145,6 +152,10 @@ static int _recv(netdev_t *netdev, void *buf, size_t len, void *info) at86rf2xx_fb_stop(dev); return pkt_len; } + + /* toggle the "RX" LED on Hamilton border router */ + RX_TOGGLE; + /* not enough space in buf */ if (pkt_len > len) { at86rf2xx_fb_stop(dev); @@ -592,7 +603,7 @@ static void _isr(netdev_t *netdev) #if LEAF_NODE /* Wake up for a while when receiving an ACK with pending bit */ if (trac_status == AT86RF2XX_TRX_STATE__TRAC_SUCCESS_DATA_PENDING) { - dev->idle_state = AT86RF2XX_STATE_RX_AACK_ON; + dev->idle_state = AT86RF2XX_STATE_RX_AACK_ON; } #endif #endif