/*

  Zerocat Chipflasher --- Flash free firmware, kick the Management Engine.

  Copyright (C) 2015, 2016  kai <kmx@posteo.net>
  Copyright (C) 2016, 2017, 2018, 2020, 2021, 2025  Kai Mertens <kmx@posteo.net>

  This file is part of Zerocat Chipflasher.

  Zerocat Chipflasher 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 3 of the License, or (at your
  option) any later version.

  Zerocat Chipflasher 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 Zerocat Chipflasher.
  If not, see <http://www.gnu.org/licenses/>.


***/


//standard library headers
# include <string.h>
# include <stdlib.h>
# include <stdio.h>
# include <unistd.h>
# include <fcntl.h>
# include <termios.h>
# include <sys/ioctl.h>
# include <sys/types.h>
# include <sys/timerfd.h>
# include <sys/stat.h>
# include <sys/select.h>
# include <signal.h>
# include <errno.h>

//project headers
# include "../../firmware1/src/libcommon/common.h"
# include "../../firmware1/src/libcommon/linespec.h"
# include "../../firmware1/src/libcommon/serial-codes.h"    //include serial control codes
# include "connect.h"

//project source files
# include "../../firmware1/src/libcommon/hexdigit2bin.c"
# include "bin2hexdigit.c"
# include "SREC_addrlen.c"
# include "lineinfo.c"

//globals
struct termios old_stdio;
char * fileToSend = FILE_TO_SEND;
char * fileToCatch = FILE_TO_CATCH;



void errout (
  enum ERRCODE_t errcode
)
{
  /*

    Validate error code.

    TODO: Shouldn't we use stderr for error messages?

    errcode       Error code.

  ***/

  char msg[50];


  //                0         1         2         3         4         5
  switch(errcode) {
    case ERRC__SUCCESS:
      sprintf(msg, "Transmission complete, please verify");
      break;

    case ERRC__LINE_COUNT_MISMATCH:
      sprintf(msg, "Line count mismatch");
      break;

    case ERRC__JOB_CANCELLATION:
      sprintf(msg, "Job cancellation");
      break;

    case ERRC__NO_SREC:
      sprintf(msg, "No Motorola S-Record");
      break;

    case ERRC__BUFFER_OVERRUN:
      sprintf(msg, "Buffer overrun");
      break;

    case ERRC__LINE_TOO_LONG:
      sprintf(msg, "Line too long");
      break;

    case ERRC__NO_HEX_DIGIT:
      sprintf(msg, "No hexadecimal digit received");
      break;

    case ERRC__LINE_ENDING_ERROR:
      sprintf(msg, "Wrong line ending");
      break;

    case ERRC__HEXD_PARSE_ERROR:
      sprintf(msg, "Hex-dump parse error");
      break;

    case ERRC__NO_SUCH_FILE:
      sprintf(msg, "File %s not available", fileToSend);
      break;

    case ERRC__PORT_OPEN_FAILURE:
      sprintf(msg, "Port open failure");
      break;

    case ERRC__INVALID_ADDRESS:
      sprintf(msg, "Invalid address");
      break;

    case ERRC__DEBUG:
      sprintf(msg, "DEBUG!");
      break;

    case ERRC__CHECKSUM_MISMATCH:
      sprintf(msg, "Checksum mismatch");
      break;

    case ERRC__LINE_LENGTH_MISMATCH:
      sprintf(msg, "Line length mismatch");
      break;

    case ERRC__WRONG_CHARACTER:
      sprintf(msg, "Wrong character");
      break;

    case ERRC__LINE_TIMEOUT:
      sprintf(msg, "Line timeout");
      break;
  }

  switch(errcode) {
    case ERRC__SUCCESS:
      printf("%s%s%s%s%s\r\n", MSG_OK, "OK: ", MSG_START, msg, MSG_STOP);
      break;

    case ERRC__JOB_CANCELLATION:
      printf("%s%s%s%s%s\r\n", MSG_WARN, "Warning: ", MSG_START, msg, MSG_STOP);
      break;

    default:
      printf("%s%s%s%s%s\r\n", MSG_ERROR, "Error: ", MSG_START, msg, MSG_STOP);
      break;
  }
}



void set_DTR (
  int fdtty,
  int level
)
{
  /*

    Asserts MARK or SPACE to the DTR line.
    fdtty   File descriptor for serial device Teletype.
    level   i.e. MARK or SPACE.

  ***/


  int serial;

  //get struct info
  ioctl(fdtty, TIOCMGET, &serial);

  //modify struct
  if(level == MARK)   //negative voltage on DTR
    serial &= ~TIOCM_DTR;
  else    //positive voltage on DTR
    serial |= TIOCM_DTR;

  //apply struct info
  ioctl(fdtty, TIOCMSET, &serial);
}



void lock_tty (
  int fdtty
)
{
  /*

    Lock Serial Port

    See `man tty_ioctl`

  ***/


  ioctl(fdtty, TIOCEXCL);
}



void unlock_tty (
  int fdtty
)
{
  /*

    Unlock Serial Port. See `man tty_ioctl`.

  ***/


  ioctl(fdtty, TIOCNXCL);
}



void set_RTS (
  int fdtty,
  int level
)
{
  /*

    Asserts @ref MARK or @ref SPACE to the RTS line.
    fdtty   File descriptor for serial device Teletype.
    level   i.e. @ref MARK or @ref SPACE.

  ***/


  int serial;

  //get struct info
  ioctl(fdtty, TIOCMGET, &serial);

  //modify struct
  if(level == MARK)   //negative voltage on RTS
    serial &= ~TIOCM_RTS;
  else    //positive voltage on RTS
    serial |= TIOCM_RTS;

  //apply struct info
  ioctl(fdtty, TIOCMSET, &serial);
}



int get_DTR (
  int fdtty
)
{
  /*

    Helps to get the current status of the DTR line. This code had
    been picked from the Linux Programmer's Manual.

    NOTE: Please refer to `$ man tty_ioctl`, section 'modem control'.

    fdtty   File descriptor for serial device Teletype.
            Returns zero if DTR is set, otherwise 1.

  ***/


  int serial;

  ioctl(fdtty, TIOCMGET, &serial);

  if(serial & TIOCM_DTR)
    puts(MSG_START"TIOCM_DTR is not set.\r"MSG_STOP);
  else
    puts(MSG_START"TIOCM_DTR is set.\r"MSG_STOP);

  return(serial & TIOCM_DTR);
}



void headline (
  char c,
  char * pstr
)
{
  for(int n = MENUWIDTH; n > 0; n--)
    putchar(c);
  puts("\r");
  for(int n = MENUWIDTH - (strlen(pstr)); n > 0; n--)
    putchar(' ');
  printf("%s\r\n\n", pstr);
}



void greeting (
  const char * port,
  const char * rate
)
{
  //Put a greeting with some basic info on screen.

  printf("%s\r\n", MSG_TOP);
  headline('=', PROJECT_HEADER);
  printf(HEADER_PROGRAM);
  printf(HEADER_LICENSE);

  printf("%s\r\n", MSG_START);
  headline('~', HEADER_CONNECT);
  printf("This is `connect', the project’s host utility.\r\n");
  printf("\r\n");
  printf("settings:\r\n");
  printf("    TTY port:  %s\t\tbaudrate:  %s\r\n", port, rate);
  printf("\r\n");
  printf("probe chip:\r\n");
  printf("    Your first action should be a probe, and a check of registers.\r\n");
  printf("    Be careful when changing register bits! Read datasheets, first.\r\n");
  printf("\r\n");
  printf("read chip:\r\n");
  printf("    File in use for “chip -> file” operation:\r\t\t\t\t\t\t`%s'\r\n", fileToCatch);
  printf("    Attention, this file will be replaced without prompt!\r\n");
  printf("    Per default, data is stored in Motorola S-Record format.\r\n");
  printf("    Use `srec_cat chip2file.txt -o file.bin -raw' to turn it into a binary.\r\n");
  printf("\r\n");
  printf("write to chip:\r\n");
  printf("    File in use for “file -> chip” operation:\r\t\t\t\t\t\t`%s'\r\n", fileToSend);
  printf("    Use `srec_cat file.bin -raw -o file2chip.txt -obs 0x40' to create it from binary.\r\n");
  printf("\r\n");
  printf("Waiting for the firmware, `kick' or `kick2' ...\r\n");
  printf("... if terminal freezes, use reset goals in different terminal -- good luck!");

  printf("%s\r\n", MSG_STOP);
}



void goodbye (
  void
)
{
  printf("%s\r\n", MSG_START);
  printf("Good bye.\r\n");
  printf("%s\r\n", MSG_RESET);
}



/*
 * time_elapsed -- print job time
 */
void time_elapsed(struct timespec *ts_start, struct timespec *ts_stop)
{
  unsigned long tdelta;
  unsigned long tsec;

  tdelta = (ts_stop->tv_sec - ts_start->tv_sec);

  printf("%sTime:%s This procedure took ", FG_CYAN, MSG_START);
  if(tdelta < 60) {
    tdelta *= 1000000000;
    tdelta += ts_stop->tv_nsec;
    tdelta -= ts_start->tv_nsec;
    printf("%lu.%lu seconds%s\r\n", tdelta / 1000000000, tdelta % 1000000000, MSG_STOP);
  }
  else {
    tsec = tdelta % 60;
    if(tdelta < 120) {
      if(tsec == 1)
        printf("1 minute and 1 second%s\r\n", MSG_STOP);
      else
        printf("1 minute and %lu seconds%s\r\n", tsec, MSG_STOP);
    }
    else {
      if(tsec == 1)
        printf("%lu minutes and 1 second%s\r\n", tdelta / 60, MSG_STOP);
      else
        printf("%lu minutes and %lu seconds%s\r\n", tdelta / 60, tsec, MSG_STOP);
    }
  }
}



/******************************************************************//**
 * @brief Endless Loop of Stream Monitoring.
 *
 * The following streams are monitored/accessed as necessary:
 * - stdin  Standard Input
 * - fdtty  Serial Port /dev/ttyS0
 * - fdrd   File, used for file to chip transmission
 * - fdwr   File, used for chip to file transmission
 * - stdout Standard Output
 * - stderr Standard Error Output
 *
 * In general, everything that is received from fdtty is send to stdout,
 * and everything that is received at stdin is send to fdtty. But not
 * everything, there are some control chars that trigger some more
 * actions...
 *
 * @param fdtty   /dev/ttyS0 File Descriptor
 * @return        Returns status of kick after exiting.
 *
 */
char polling (
  int fdtty
)
{
  /*

    Endless Loop of Stream Monitoring.

    The following streams are monitored/accessed as necessary:
    - stdin  Standard Input
    - fdtty  Serial Port /dev/ttyS0
    - fdrd   File, used for file to chip transmission
    - fdwr   File, used for chip to file transmission
    - stdout Standard Output
    - stderr Standard Error Output

    In general, everything that is received from fdtty is send to stdout,
    and everything that is received at stdin is send to fdtty. But not
    everything, there are some control chars that trigger some more
    actions...

    fdtty   /dev/ttyS0 File Descriptor
            Returns status of kick after exiting.

  ***/


  //time
  clockid_t clk = CLOCK_REALTIME;
  struct timespec ts_start;
  struct timespec ts_stop;

  //tty exit sequence
  enum STEP_EXIT_t {
    CHECK_A = 0,
    CHECK_B,
    EXIT_C
  } step_exit = CHECK_A;

  //SOH data
  char file_format;
  char file_command;
  enum SOH_STEP_t {
    SOH_GET_COMMAND = 0,
    SOH_GET_FORMAT
  } SOH_step;
  int usec_byte = 500;

  //send file to chip
  char a = 0;
  char b = 0;
  enum ERRCODE_t errcode;
  enum CONNECT_FILESTEP_t tx;
  int wait_feedback = OFF;
  int wait_nofeedback = OFF;
  int flag_stx;
  int fdrd = -1;        //used to switch appropriate functionality on/off
  int ispayload;        //color flag, will be initialized in SOH

  //send chip to file
  char c = 0;
  char * pc;
  int fdwr = -1;    //used to switch appropriate functionality on/off
  char spinwheel[] = {'\\', '-', '/', '|'};       //read backwards!
  char* pspinwheel = spinwheel + sizeof(spinwheel) - 1;
  int flag_nodata;
  int dry_line = OFF;

  //protocol control
  char stx = STX;
  char etx = ETX;
  char eot = EOT;
  char ack = ACK;
  char can = CAN;
  int flag_can = OFF;
  char nak = NAK;
  char clim = CLIM_HEXD;

  //stream supervision
  fd_set rdfds;
  fd_set wrfds;
  int nfds;       //maximal file descriptor, see macro MAX()
  int r, rr;      //return values for pselect(), read(), write(), ... and usleep()!
  const struct timespec tspec = { TIMEOUT_SEC, TIMEOUT_NSEC };

  //explicit stream switches and flags
  int read_stdin = OFF;
  const int read_tty = ON;
  int write_stdout = OFF;
  int write_tty = OFF;
  int write_file = OFF;
  int read_SOH = OFF;
  int screen_output;

  //tty read buffer
  unsigned char buf_rdtty[SIZE_BUF_RDTTY];
  int pwr_rdtty = 0;
  int prd_rdtty = 0;

  //stream buffer and pointers, stdout
  unsigned char buf_stdout[SIZE_BUF_STDOUT];
  int pwr_stdout = 0;
  int prd_stdout = 0;

  //stream buffer and pointers, file
  unsigned char buf_file[SIZE_BUF_FILE];
  int pwr_file = 0;
  int prd_file = 0;

  //HEXD
  struct CONNECT_LINEHEXD_t linehexd = {
    OFF, OFF, '-'
  };
  //SREC
  struct CONNECT_LINESREC_t linesrec;
  //chip to file
  int iline_chip2file = SCANNER_OFF;
  //file to chip
  int iline_file2chip = SCANNER_OFF;
  int ispayload_file2chip = 0;
  int isbyte_file;  //will be initialized at STX of line

  //setup trigger
  write_tty = ON;
  pc = &ack;

  while(ON) {
    //reset pointers
    if(prd_rdtty == pwr_rdtty)
      prd_rdtty = pwr_rdtty = 0;
    if(prd_stdout == pwr_stdout)
      prd_stdout = pwr_stdout = 0;
    if(prd_file == pwr_file)
      prd_file = pwr_file = 0;

    //is the read buffer empty?
    //do we need to write?
    if(prd_rdtty == pwr_rdtty || write_stdout || write_tty || write_file) {
      //clear
      nfds = 0;
      FD_ZERO(&rdfds);
      FD_ZERO(&wrfds);

      //write to stdout?
      if(write_stdout == ON) {
        FD_SET(STDOUT_FILENO, &wrfds);
        nfds = MAX(nfds, STDOUT_FILENO);
        write_stdout = OFF;   //automatic reset
      }

      //write to file?
      if(write_file == ON) {
        FD_SET(fdwr, &wrfds);
        nfds = MAX(nfds, fdwr);
        write_file = OFF;    //automatic reset
      }

      //write to tty?
      if(write_tty == ON) {
        FD_SET(fdtty, &wrfds);
        nfds = MAX(nfds, fdtty);
        write_tty = OFF;     //automatic reset
      }

      //read from tty?
      if((read_tty == ON) || (read_SOH == ON)) {
        FD_SET(fdtty, &rdfds);
        nfds = MAX(nfds, fdtty);
      }

      //read stdin?
      if(read_stdin == ON) {
        FD_SET(STDIN_FILENO, &rdfds);
        nfds = MAX(nfds, STDIN_FILENO);
      }

      //read from file?
      if(fdrd > -1) {
        if((wait_feedback == OFF) && (wait_nofeedback == OFF)) {
          FD_SET(fdrd, &rdfds);
          nfds = MAX(nfds, fdrd);
        }
      }

      //===============================================================
      //wait for pselect()
      /*

        NOTE:
        If all chars have been read into cbuf so far, we should proceed
        reading the cbuf buffer instead of waiting for more chars on tty.
        Because no char will be present, the following pselect() will
        time out.

      ***/
      r = pselect(nfds + 1, &rdfds, &wrfds, NULL, &tspec, NULL);
      //check pselect() timeout
      if(r == 0) {
        if(fdwr > -1 || fdrd > -1) {
          /*

            Transmission Error (Timeout)

            NOTE:
            This timeout occurs most likely due to a fifo buffer overrun.

            The firmware 'kick' has sent ETX which 'connect' missed to
            catch and which it keeps waiting for. In turn, 'kick'
            is waiting for an ACK or NAK now... that's the trap.

            As 'kick' is using getChar(), it cannot monitor a timeout
            like 'connect' has already done.

            In this situation, sending an ENQ to request the line once
            more gets ourselfs out of the trap.

          ***/
          if(!errcode)  //don't override a previous error
            errcode = ERRC__LINE_TIMEOUT;
          goto FAKE_ETX;
        }
        continue;
      }
      //check pselect() ready
      else if(r > 0) {
        //write to stdout
        if(STDOUT_FILENO > -1) {
          if(FD_ISSET(STDOUT_FILENO, &wrfds)) {
            r = write(STDOUT_FILENO, buf_stdout + prd_stdout, pwr_stdout - prd_stdout);
            if(r > 0) {
              prd_stdout += r;
            }
            else if(r < 0) {
              if(errno != EAGAIN && errno != EINTR)
                EXITFAIL("write()");
            }
          }
        }

        //write to file
        if(fdwr > -1) {
          if(FD_ISSET(fdwr, &wrfds)) {
            r = write(fdwr, buf_file + prd_file, pwr_file - prd_file);
            if(r > 0) {
              prd_file += r;
            }
            else if (r < 0) {
              if(errno != EAGAIN && errno != EINTR)
                EXITFAIL("write()");
            }
          }
        }

        //write to tty
        if(fdtty > -1) {
          if(FD_ISSET(fdtty, &wrfds)) {
            do {
              r = write(fdtty, pc, 1);
              if(r > 0) {
                //wait_nofeedback?
                if(wait_nofeedback) {
                  do {
                    rr = usleep(wait_nofeedback);         // grant controller some time to process byte
                  } while (rr && errno == EINTR);
                  wait_nofeedback = OFF;
                }
              }
              else if(r < 0) {
                if(errno != EAGAIN && errno != EINTR)
                  EXITFAIL("write()");
              }
            } while (r < 1);
          }
        }

        //read from stdin: any keystroke?
        if(STDIN_FILENO > -1) {
          if(FD_ISSET(STDIN_FILENO, &rdfds)) {
            r = read(STDIN_FILENO, &c, 1);
            if(r < 0) {
              if(errno != EAGAIN && errno != EINTR)
                EXITFAIL("read()");
            }
            else if(r == 1) {
              if(read_stdin) {
                //file to chip
                if(fdrd > -1) {
                  switch(c) {
                    case 'q':
                      flag_can = ON;
                      break;
                  }
                }
                //chip to file
                else if(fdwr > -1) {
                  switch(c) {
                    case 'q':
                      flag_can = ON;
                      break;
                  }
                }
                //idle
                else {
                  write_tty = ON;
                  continue;   //skip the rest
                }
              }
            }
          }
        }

        //read from tty: any char from kick?
        if(fdtty > -1) {
          if(FD_ISSET(fdtty, &rdfds)) {
            r = read(fdtty, buf_rdtty + pwr_rdtty, SIZE_BUF_RDTTY - pwr_rdtty);
            if(r == 0) {
              /*
                NOTE: This is essential, otherwise we will exit
                if SIZE_BUF_RDTTY is bigger than 1 but still small
              */
              continue;
            }
            else if(r > 0) {
              pwr_rdtty += r;
            }
            else {
              if(errno != EAGAIN && errno != EINTR)
                EXITFAIL("read() into buf_rdtty");
              continue;
            }
          }
        }

        //read from file: any file data to be processed?
        if(fdrd > -1) {
          if(FD_ISSET(fdrd, &rdfds)) {
            switch(tx) {
              case START_FILE_READOUT:
                a = 0;
                flag_stx = ON;

              case BYTE_PREVIEW:
                b = a;
                r = read(fdrd, &a, 1);
                if(r == 0) {
                  a = EM;
                  if(b == EM) {
                    tx = SEND_EOT;
                    continue;
                  }
                }
                else if(r < 0) {
                  if(errno != EAGAIN && errno != EINTR)
                    EXITFAIL ("read()");
                }

              case CHECK_STX:
                if(flag_stx == ON) {
                  flag_stx = OFF;
                  errcode = ERRC__SUCCESS;  //reset for this line
                  pc = &stx;
                  write_tty = ON;
                  tx = CHECK_CRNL;
                  wait_feedback = ON;
                  if(file_format == LINETYPE_SREC) {
                    iline_file2chip = SCANNER_INI;
                    isbyte_file = 0;  //b is zero, a is pointing to 'S'
                  }
                  continue;
                }

              case CHECK_CRNL:
                if(b == CARR_RET) {
                  if(a != NEW_LINE) {
                    if(file_format == LINETYPE_HEXD)
                      tx = SEND_CLIM;
                    else
                      tx = SEND_ETX_OR_CAN;
                  }
                  else
                    tx = BYTE_PREVIEW;
                }
                else if(b == NEW_LINE) {
                  if(file_format == LINETYPE_HEXD)
                    tx = SEND_CLIM;
                  else
                    tx = SEND_ETX_OR_CAN;
                }
                else
                  tx = SEND_BYTE;
                continue;

              case SEND_BYTE:
                tx = BYTE_PREVIEW;
                if(b) {
                  /*
                   * Enable this code block for binary S-Record payloads:
                   */
                  if((ispayload_file2chip = lineinfo(file_format, &iline_file2chip, b, NULL, &linesrec, PAYLOAD_HEX))) {
                    if((isbyte_file ^= 1)) {
                      b = (hexdigit2bin(b) << 4) | hexdigit2bin(a);   // Attn: No hex digit error check here
                      if(screen_output == ON) {
                        if(ispayload == 0) {
                          ispayload = 1;
                          WRITE_BUF_STDOUT__DATA_BIN;
                        }
                        WRITE_BUF_STDOUT(bin2hexdigit((b >> 4) & 0x0f, CASE_MODE));
                        WRITE_BUF_STDOUT(bin2hexdigit(b & 0x0f, CASE_MODE));
                      }
                      pc = &b;
                      write_tty = ON;
                      wait_nofeedback = usec_byte;    // FIXME: if too small, communication will hang-up
                    }
                  }
                  else {
                    if(screen_output == ON) {
                      if(ispayload) {   //check for -1 or 1
                        ispayload = 0;
                        WRITE_BUF_STDOUT__DATA_HEX;
                      }
                      WRITE_BUF_STDOUT(b);
                      write_stdout = read_stdin;
                    }
                    pc = &b;
                    write_tty = ON;
                    wait_nofeedback = usec_byte;      // FIXME: if too small, communication will hang-up
                  }

                  /*
                   * Enable this code block for hexadecimal payloads:
                   */
                  //'~ if(screen_output == ON) {
                    //'~ if(ispayload) {   //check for -1 or 1
                      //'~ ispayload = 0;
                      //'~ WRITE_BUF_STDOUT__DATA_HEX;
                    //'~ }
                    //'~ WRITE_BUF_STDOUT(b);
                    //'~ write_stdout = read_stdin;
                  //'~ }
                  //'~ pc = &b;
                  //'~ write_tty = ON;
                  //'~ wait_nofeedback = usec_byte;       // FIXME: if too small, communication will hang-up
                }
                continue;

              case SEND_CLIM:
                pc = &clim;
                write_tty = ON;
                wait_nofeedback = usec_byte;
                tx = SEND_ETX_OR_CAN;
                continue;

              case SEND_ETX_OR_CAN:
                if(flag_can) {
                  flag_can = OFF;
                  pc = &can;
                }
                else
                  pc = &etx;
                write_tty = ON;
                tx = WAIT_CHAR;
                iline_file2chip = SCANNER_OFF;
                continue;

              case WAIT_CHAR:
              case RECEIVE_CAN:
              case RECEIVE_NAK:
                continue;

              case RECEIVE_ACK:
                WRITE_BUF_STDOUT('\r');
                if(screen_output)
                  WRITE_BUF_STDOUT('\n');
                write_stdout = ON;
                flag_stx = ON;
                tx = BYTE_PREVIEW;
                continue;

              case SEND_EOT:  //do we need wait_feedback = ON here?
                pc = &eot;
                write_tty = ON;
                tx = FLUSH_BUFFERS;
                continue;

              case FLUSH_BUFFERS:
                tx = CLOSE_TX;
                if(errcode != ERRC__SUCCESS) {
                  WRITE_BUF_STDOUT('\n');
                  write_stdout = screen_output;  //will be served first, thus flushing the buffer
                  WRITE_BUF_FILE('\n');
                  write_file = ON;    //will be served second
                }
                continue;

              case CLOSE_TX:
                do {
                  r = close(fdrd);
                  if(r < 0) {
                    if(errno != EAGAIN && errno != EINTR)
                      EXITFAIL("close()");
                  }
                } while (r < 0);
                fdrd = -1;
                printf(" \r");  // clear remaining character of spinwheel (if any), then carriage return
                clock_gettime(clk, &ts_stop);
                errout(errcode);
                time_elapsed(&ts_start, &ts_stop);
                //reset buffer pointers
                //TODO: why is that necessary?
                prd_stdout = pwr_stdout = 0;
                prd_file = pwr_file = 0;
                continue;
            };
          }
        }
      }
      //check pselect() error
      else {
        if(errno != EAGAIN && errno != EINTR)
          EXITFAIL("pselect()");
      }
    }
    //is data in tty buffer?
    else if(prd_rdtty < pwr_rdtty) {
      //read from buf_rdtty
      c = buf_rdtty[prd_rdtty++];

      //catch error status
      if(tx == RECEIVE_NAK) {
        errcode = c;
        tx = SEND_EOT;
      }
      else {
        /*

          scan for propeller-load exit sequence
          don't scan when receiving random data
          don't scan when receiving SREC’s binary data

        ***/
        if(!dry_line && (iline_chip2file < SCANNER_INI && iline_file2chip < SCANNER_INI)) {
          switch(step_exit) {
            case CHECK_A:
              if((unsigned char)c == EXIT_CHAR_A) {
                step_exit++;
                continue;
              }
              break;

            case CHECK_B:
              if(c == EXIT_CHAR_B) {
                step_exit++;
                continue;
              }
              break;

            case EXIT_C:
              return(c);
          }
          step_exit = CHECK_A;    //reset
        }

        //file to chip (data and control chars)
        if(fdrd > -1) {
          if(c == *pc)
            wait_feedback = OFF;

          switch(c) {
            case STX:
            case ENQ:
            case SOH:
            case EOT:
            case ETX:
              break;

            case ACK:
              if(tx == WAIT_CHAR) {
                tx = RECEIVE_ACK;
                if(!screen_output) {    //provide a spinning wheel if no output on screen available
                  WRITE_BUF_STDOUT(*pspinwheel);
                  if(pspinwheel == spinwheel)
                    pspinwheel = spinwheel + sizeof(spinwheel) - 1;
                  else
                    pspinwheel--;
                  WRITE_BUF_STDOUT('\b');
                  write_stdout = ON;
                }
              }
              break;

            case NAK:
              if(tx == WAIT_CHAR)
                tx = RECEIVE_NAK;
              break;

            case CAN:
              if(tx == WAIT_CHAR)
                tx = RECEIVE_CAN;
              break;

            default:    // process feedback of line endings
              if(c != CLIM_CR && c != CLIM_NL)
                errcode = ERRC__WRONG_CHARACTER;

              if(errcode) {
                iline_file2chip = SCANNER_OFF;   //switch line scanner off
                break;
              }

              if(screen_output == ON) {
                WRITE_BUF_STDOUT(c);
                write_stdout = read_stdin;
                if(c == CLIM_NL)
                  write_stdout = ON;
              }
          }
          /*

            Now, wait_feedback should be zero.
            If not, proceed with wrong character and create timeout error.

          ***/
        }
        //chip to file (data and control chars)
        else if(fdwr > -1) {
          //binary data transmission (payload only, no control chars)
          if(lineinfo(file_format, &iline_chip2file, c, &linehexd, &linesrec, PAYLOAD_BIN)) {
            unsigned char a, b;
            a = bin2hexdigit((c >> 4) & 0x0f, CASE_MODE);
            b = bin2hexdigit(c & 0x0f, CASE_MODE);
            if(screen_output == ON) {
              WRITE_BUF_STDOUT(a);
              WRITE_BUF_STDOUT(b);
            }
            WRITE_BUF_FILE(a);
            WRITE_BUF_FILE(b);
          }
          //hexadecimal data transmission (control chars as well)
          else {
            if(!dry_line) {   //don't scan for control characters when receiving random data
              switch(c) {
                case STX:
                  if(iline_chip2file == SCANNER_OFF) {
                    flag_nodata = 1;  //will be cleared with first valid data character
                    /*

                      switch lineinfo() scanner on,
                      will be switched off in case of ETX,
                      will be switched off in case of timeout error

                    ***/
                    iline_chip2file = SCANNER_INI;
                    break;
                  }

                case EOT:
                  if(iline_chip2file == SCANNER_OFF) {
                    do {
                      r = close(fdwr);
                      if(r < 0) {
                        if(errno != EAGAIN && errno != EINTR)
                          EXITFAIL("close()");
                      }
                    } while (r < 0);
                    fdwr = -1;
                    write_file = OFF;
                    if(!errcode && (!screen_output || flag_nodata))
                      printf(" \r");  //clear remaining character of spinwheel
                    clock_gettime(clk, &ts_stop);
                    errout(errcode);
                    time_elapsed(&ts_start, &ts_stop);
                    break;
                  }

                case ETX:
                  if(flag_can) {
                    flag_can = OFF;
                    errcode = ERRC__JOB_CANCELLATION;
                  }
                  else if(!errcode && !flag_nodata) {
                    switch(file_format) {
                      case LINETYPE_SREC:
                        if(linesrec.checksum != linesrec.chksum_rx)
                          errcode = ERRC__CHECKSUM_MISMATCH;
                        else if(iline_chip2file != linesrec.size - 1)
                          errcode = ERRC__LINE_LENGTH_MISMATCH;
                        break;
                      case LINETYPE_HEXD:
                        if(!linehexd.is_info && (iline_chip2file) != HEXD_LINELEN)
                          errcode = ERRC__LINE_LENGTH_MISMATCH;
                        break;
                    }
                  }
FAKE_ETX:
                  if(errcode <= ERRC__JOB_CANCELLATION && !flag_nodata) {
                    //start Write CLIM_NL
                    c = CLIM_NL;    //temporary use of c
                    WRITE_BUF_FILE(c);
                    if(c == CLIM_NL)
                      write_file = ON;
                    //end Write CLIM_NL
                  }
                  iline_chip2file = SCANNER_OFF;   //switch line scanner off
                  switch(errcode) {
                    case ERRC__SUCCESS:
                      pc = &ack;
                      if(!screen_output || flag_nodata) {    //provide a spinning wheel if no screen output
                        WRITE_BUF_STDOUT__MSG_START;
                        WRITE_BUF_STDOUT(*pspinwheel);
                        if(pspinwheel == spinwheel)
                          pspinwheel = spinwheel + sizeof(spinwheel) - 1;
                        else
                          pspinwheel--;
                        WRITE_BUF_STDOUT('\b');
                        WRITE_BUF_STDOUT__MSG_STOP;
                        write_stdout = ON;
                      }
                      break;

                    case ERRC__JOB_CANCELLATION:
                      pc = &can;
                      break;

                    case ERRC__CHECKSUM_MISMATCH:
                    case ERRC__LINE_LENGTH_MISMATCH:
                    case ERRC__WRONG_CHARACTER:
                    case ERRC__LINE_TIMEOUT:
                      prd_stdout = pwr_stdout = 0;
                      prd_file = pwr_file = 0;
                      prd_rdtty = pwr_rdtty = 0;
                      dry_line = OFF;
                      pc = &nak;
                      break;

                    default:  //any other error?
                      pc = &nak;
                      break;
                  }
                  write_tty = ON;
                  break;

                default:
                  //check charset
                  switch(file_format) {
                    case LINETYPE_SREC:
                      if((c != 'S')
                          && !isxdigit(c)
                            && (c != CLIM_CR)
                              && (c != CLIM_NL))
                        errcode = ERRC__WRONG_CHARACTER;
                      break;

                    case LINETYPE_HEXD:
                      if(linehexd.is_print) {
                        if(!isprint(c) && (c != CLIM_CR) && (c != CLIM_NL))
                          errcode = ERRC__WRONG_CHARACTER;
                      }
                      else {
                        if(((c != '#') && (c != ':') && (c != ' ') && !isxdigit(c)))
                          errcode = ERRC__WRONG_CHARACTER;
                      }
                      break;
                  }

                  if(errcode) {
                    iline_chip2file = SCANNER_OFF;   //switch line scanner off
                    dry_line = ON;
                    break;
                  }

                  if(screen_output == ON) {
                    WRITE_BUF_STDOUT(c);
                    write_stdout = read_stdin;
                    if(c == CLIM_NL)
                      write_stdout = ON;
                  }


                  /*

                    - Discard CLIM_NL here, write after ETX instead!

                        This allows us to overwrite a broken line in case
                        of line length mismatch.

                    - Discard CLIM_CR here

                        Obtain UNIX style line endings.

                  ***/
                  if((c != CLIM_NL) && (c != CLIM_CR)) {
                    WRITE_BUF_FILE(c);
                    //'~ if(c == CLIM_NL)
                      //'~ write_file = ON;
                  }

                  flag_nodata = 0;
                  break;
              }
            }
          }
        }
        //control commands: menu is active
        else {
          //SOH data
          if(read_SOH == ON) {
            switch(SOH_step) {
              case SOH_GET_COMMAND:
                file_command = c;
                SOH_step = SOH_GET_FORMAT;
                break;

              case SOH_GET_FORMAT:
                file_format = c;
                read_SOH = OFF;
                errcode = ERRC__SUCCESS;    //reset
                flag_can = OFF;             //reset
                ispayload = -1;             //start condition for color flag
                switch(file_format) {
                  case LINETYPE_HEXD:
                    usec_byte = USEC_BYTE_HEXD;
                    break;

                  default:
                    usec_byte = USEC_BYTE_SREC;
                    break;
                }
                screen_output = ON;
                switch(file_command) {
                  case FILE_TO_CHIP_NOSCREEN:
                    screen_output = OFF;

                  case FILE_TO_CHIP:
                    //initiate transmission, stream will go active
                    fdrd = open(fileToSend, O_RDONLY);
                    if(fdrd == -1) {
                      //issue error message
                      errcode = ERRC__NO_SUCH_FILE;
                      errout(errcode);
                      //send EOT
                      pc = &eot;
                      write_tty = ON;
                      continue;
                    }
                    else {
                      tx = START_FILE_READOUT;
                      wait_feedback = OFF;
                      if(screen_output)   //remove host color setting if spinning wheel is not present
                        printf("%sReading %s, sending data ...%s\r\n", MSG_START, fileToSend, MSG_STOP);
                      else
                        printf("%sReading %s, sending data ...\r\n", MSG_START, fileToSend);
                      clock_gettime(clk, &ts_start);
                    }
                    break;

                  case CHIP_TO_FILE_NOSCREEN:
                    screen_output = OFF;

                  case CHIP_TO_FILE:
                    fdwr = open(
                      fileToCatch,
                      O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
                      );
                    if(fdwr == -1) {
                       EXITFAIL("open()");
                      /*

                        FIXME: This error handling is very basic.
                        It will leave the chipflasher board with its SPI-port active!
                        It would be better if kick would now expect an ACK or NAK before it proceeds.

                      ***/
                    }
                    if(screen_output)   //remove host color setting if spinning wheel is not present
                      printf("%sReceiving data, writing %s ...%s\r\n", MSG_START, fileToCatch, MSG_STOP);
                    else
                      printf("%sReceiving data, writing %s ...\r\n", MSG_START, fileToCatch);
                    clock_gettime(clk, &ts_start);
                    break;
                }
                break;
            }
          }
          //normal menu
          else {
            switch(c) {
              case ACK:
              case NAK:
              case EOT:
              case CAN:
                continue;

              case STX:
                continue;

              case ETX:
                continue;

              case INPUT_START:
                read_stdin = ON;
                continue;

              case INPUT_STOP:
                read_stdin = OFF;
                continue;

              case ENQ:
                pc = &ack;
                write_tty = ON;
                continue;

              case SOH:   //start of heading for tx, rx
                SOH_step = SOH_GET_COMMAND;
                read_SOH = ON;
                continue;

              default:  //all other chars
                pc = &c;
                if(isprint(c) || isspace(c)) {
                  WRITE_BUF_STDOUT(c);
                  write_stdout = ON;
                }
                continue;
            }
          }
        }
      }
    }
  }

  //this line should never be reached, but maybe buffers are too small?
  EXITFAIL("polling()");
}



void restore_stdout (
  void
)
{
  tcsetattr(STDOUT_FILENO, TCSANOW, &old_stdio);
  tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old_stdio);
}



int connect (
  char * dev,
  const speed_t baudrate,
  unsigned int bps
)
{
  int fdtty;
  struct termios tio;
  struct termios stdio;
  int r;

  //initialize memory
  memset(&stdio, 0, sizeof(stdio));
  memset(&tio, 0, sizeof(tio));

  //save current stdout terminal settings and restore later
  tcgetattr(STDOUT_FILENO, &old_stdio);

  //set exit function
  {
    int r;
    r = atexit(restore_stdout);
    if(r != 0) {
       fprintf(stderr, "%s%s%s\r\n", MSG_START, "cannot set exit function", MSG_STOP);
       exit(EXIT_FAILURE);
    }
  }

  //configure ttyS0
  tio.c_iflag = 0;                  //specify input modes
  tio.c_oflag = 0;                  //specify output modes
  tio.c_cflag = CS8|CREAD|CLOCAL;   //flag constants (8n1, see termios.h for more information)
  tio.c_lflag = 0;                  //local modes
  tio.c_cc[VMIN] = 0;               //minimum of input chars
  tio.c_cc[VTIME] = 0;              //input timeout in deciseconds

  /*
   * FIXME:
   * 1) Open() seems to initialize DTR and RTS to SPACE thus triggering
   *    a reset even if we don't want to. See Notes below.
   * 2) Open() seems to corrupt an existing locked connection.
   */
  do {
    r = open(dev, O_RDWR | O_NONBLOCK);          //open port
  } while (r < 0 && (errno == EAGAIN || errno == EINTR));
  if (r < 0) {
    //return status
    return EXIT_FAILURE;
  }
  else {
    fdtty = r;

    //'~ lock_tty(fdtty);                          //if locked, tty cannot be reset
    cfsetospeed(&tio, baudrate);              //specify output speed (propeller default)
    cfsetispeed(&tio, baudrate);              //specify input speed (propeller default)
    tcsetattr(fdtty, TCSANOW, &tio);          //apply changes now

    /* Notes:
     *
     * The DTR signal is generated by your workstation and tells the computer or device on the
     * other end that you are ready (a space voltage) or not-ready (a mark voltage). DTR is
     * usually enabled automatically whenever you open the serial interface on the workstation.
     *
     * The RTS signal is set to the space voltage by your workstation to indicate that more data
     * is ready to be sent. Like CTS, RTS helps to regulate the flow of data between your
     * workstation and the computer or device on the other end of the serial cable. Most
     * workstations leave this signal set to the space voltage all the time.
     *
     * Source: https://www.cmrr.umn.edu/~strupp/serial.html#2_5_2
     */

    //configure stdout terminal
    stdio.c_iflag = 0;
    stdio.c_oflag = 0;
    stdio.c_cflag = 0;
    stdio.c_lflag = 0;
    stdio.c_cc[VMIN] = 1;
    stdio.c_cc[VTIME] = 0;
    tcsetattr(STDOUT_FILENO, TCSANOW, &stdio);
    tcsetattr(STDOUT_FILENO, TCSAFLUSH, &stdio);
    fcntl(STDOUT_FILENO, F_SETFL, O_NONBLOCK);  //make the writes non-blocking
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);  //make the reads non-blocking

    //disable board reset and trigger the propeller
    do {
      r = usleep(RESET_DURATION);
    } while (r && errno == EINTR);
    set_DTR(fdtty, MARK);
    set_RTS(fdtty, MARK);

    //call polling
    polling(fdtty);

    //close '/dev/ttyS0'
    unlock_tty(fdtty);

    do {
      r = close(fdtty);
      if(r < 0) {
        if(errno != EAGAIN && errno != EINTR)
          return EXIT_FAILURE;
      }
    } while (r < 0);

    //end successfully
    return EXIT_SUCCESS;
  }
}



int main (
  int argc,
  char ** argv
)
{
  /*

    This is main() of `connect`, the host utility.

    The program `connect` has dedicated features, in contrast to a
    standard terminal, set up with `propeller-load`.

    argc    Number of arguments.
    **argv  Pointer to argument buffer.

    Returns status as reported by the Propeller board.
    Remember, the three byte exit sequence used by the Propeller is:
    EXIT_CHAR_A, EXIT_CHAR_B, status.

  ***/


  char *port[] = {
    "/dev/ttyS0",
    "/dev/ttyS1",
    "/dev/ttyS2",
    "/dev/ttyS3",
    "/dev/ttyUSB0",
    "/dev/ttyUSB1"
  };

  struct tag_rate {
    const speed_t macro;
    const char * s;
    const unsigned int bps;   //should be 4 bytes
  } rate[] = {
    { B921600, "B921600", 921600 },
    { B576000, "B576000", 576000 },
    { B500000, "B500000", 500000 },
    { B460800, "B460800", 460800 },
    { B230400, "B230400", 230400 },
    { B115200, "B115200", 115200 },
    { B57600,  "B57600",   57600 },
    { B38400,  "B38400",   38400 }
  };

  int status;
  struct tag_rate * pRate;
  char * pPort;

  //set defaults
  pPort = port[PORT_DEFAULT];
  pRate = &(rate[RATE_DEFAULT]);

  switch(argc) {
    case 5:
      {
        int n;

        //filenames
        fileToCatch = argv[1];
        fileToSend = argv[2];

        //port
        n = SIZE_PORT;
        while(n--) {
          if(!strcmp(argv[3], port[n])) {
            pPort = port[n];
            break;
          }
        }

        //baudrate
        n = SIZE_RATE;
        while(n--) {
          if(!strcmp(argv[4], rate[n].s)) {
            pRate = &(rate[n]);
            break;
          }
        }
      }

    case 1:
      //standard action
      greeting(pPort, pRate->s);
      status = connect(pPort, pRate->macro, pRate->bps);
      if(status != EXIT_SUCCESS) {
        restore_stdout();
        errout(ERRC__PORT_OPEN_FAILURE);
      }
      goodbye();
      return status;

    default:
      //usage
      printf( "%s:\r\n", argv[0]);
      printf( "   version:\r\t\t" VERSION "\r\n");
      printf( "   usage:\r\t\t%s [chip2file file2chip port baudrate]\r\n\n", argv[0]);
      printf( "   chip2file:" \
              "\r\t\tA text file that will be used to store chip readouts\n" \
              "\r\t\tas lines of Motorola S-Record or hexdump.\n" \
              "\r\t\tDefaults to '" FILE_TO_CATCH "'.\r\n");
      printf( "   file2chip:" \
              "\r\t\tA text file that contains data to be flashed as lines\n" \
              "\r\t\tof hexdump or Motorola S-Record.\n" \
              "\r\t\tDefaults to '" FILE_TO_SEND "'.\r\n");
      printf( "   port:" \
              "\r\t\t/dev/ttyS0 (default) | /dev/ttyS1 | /dev/ttyS2 | /dev/ttyS3 |\n" \
              "\r\t\t/dev/ttyUSB0 | /dev/ttyUSB1\r\n");
      printf( "   baudrate:" \
              "\r\t\tB38400 | B57600 | B115200 (default) | B230400 | ... | B921600 \r\n");
      return 0; //should we flag an error?
  }
}
