/*

  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, 2026  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/>.


***/


// compiler switches
# define USE_SRECORD_BINARY_PAYLOAD
//# define ENABLE_FATAL_TIMEOUT
//# define TEST_ERRC__FATAL
//# define TEST_FAIL_OPEN_OUTFILE
//# define TEST_FAIL_OPEN_INFILE
//# define TEST_FAIL_CLOSE_OUTFILE
//# define TEST_FAIL_CLOSE_INFILE
//# define TEST_FAIL_PSELECT
//# define TEST_PSELECT_TIMEOUT
//# define TEST_FAIL_WRITE_FDWR
//# define TEST_FAIL_WRITE_FDTTY
//# define TEST_FAIL_WRITE_STDOUT
//# define TEST_FAIL_READ_FDRD
//# define TEST_FAIL_READ_FDTTY
//# define TEST_FAIL_READ_STDIN

// 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>
# include <ctype.h>

// project headers
# include "../../firmware1/src/libcommon/protocol.h"
# include "./identifier.h"      // generated from template!
# include "./connect.h"

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

// compiler messages
# ifdef USE_SRECORD_BINARY_PAYLOAD
#   pragma message FG_BLUE"*** S-Record lines use binary payload ***"FG_DEFAULT
# endif
# ifdef ENABLE_FATAL_TIMEOUT
#   pragma message FG_BLUE"*** fatal timeout counter in use ***"FG_DEFAULT
# endif



// globals
int fdserial = -1;
struct termios stdio_save;
static sig_atomic_t signal_in_progress = 0;     // global, static track variable
int fw_handshake = -1;
int fw_SPI_is_powered = -1;
int fw_menu_level = -1;
int fw_menu_keyid = -1;
int usec_byte = 0xff;                           // initial value, maximum



// functions
void fwquit (
  void
)
{
  char q = 'q';
  char n = 'n';
  int r;

  if (fdserial != -1) {
    switch (fw_handshake) {
      /* send a mix of `n' and `q' characters */
      case THIS_IS_KICK_CONNECT:

        // quit LON
        if (fw_menu_level == 0xff) {
          fprintf(stderr, "Stop active job ...\r\n");
          do {
            r = write(fdserial, &q, 1);
          } while (r < 1 && (errno == EINTR || errno == EAGAIN));
          do {
            r = usleep(500000);     // 0.5s
          } while (r && (errno == EINTR || errno == EAGAIN));
        }

        // quit L3
        if (fw_menu_level == 3) {
          fprintf(stderr, "Quit job level L3 ...\r\n");
          do {
            r = write(fdserial, &n, 1);
          } while (r < 1 && (errno == EINTR || errno == EAGAIN));
          do {
            r = usleep(500000);     // 0.5s
          } while (r && (errno == EINTR || errno == EAGAIN));
        }

        // quit L2
        if (fw_menu_level == 2) {
          fprintf(stderr, "Quit job level L2 ...\r\n");
          do {
            r = write(fdserial, &n, 1);
          } while (r < 1 && (errno == EINTR || errno == EAGAIN));
          do {
            r = usleep(500000);     // 0.5s
          } while (r && (errno == EINTR || errno == EAGAIN));
          do {
            r = write(fdserial, &n, 1);
          } while (r < 1 && (errno == EINTR || errno == EAGAIN));
          do {
            r = usleep(500000);     // 0.5s
          } while (r && (errno == EINTR || errno == EAGAIN));
        }

        // quit L3, LON (0xff)
        if (fw_menu_level > 1) {
          fprintf(stderr, "Quit menu ...\r\n");
          do {
            r = write(fdserial, &q, 1);
          } while (r < 1 && (errno == EINTR || errno == EAGAIN));
          do {
            r = usleep(500000);     // 0.5s
          } while (r && (errno == EINTR || errno == EAGAIN));
        }

        // quit LOFF, L1
        fprintf(stderr, "Quit firmware ...\r\n");
        do {
          r = write(fdserial, &q, 1);
        } while (r < 1 && (errno == EINTR || errno == EAGAIN));
        do {
          r = usleep(500000);     // 0.5s
        } while (r && (errno == EINTR || errno == EAGAIN));

        break;

      /* send three `q' characters (cancel/SPIoff/quit) */
      case THIS_IS_KICK2_CONNECT:
        if (fw_menu_level > -1) {
          // job KEYID_I active??
          if ((fw_menu_level & 0x04) && (fw_menu_keyid == 0x11)) {    // KEYID_I

            fprintf(stderr, "Wait %ds for suspend cycle (if any) ...\r\n", 5);
            do {
              // wait 5s for start up suspend (if any)
              r = usleep(5000000);
            } while (r && (errno == EINTR || errno == EAGAIN));

            fprintf(stderr, "Add %ds to create line timeout ...\r\n", SEC_WATCHDOG);
            do {
              // wait for 3s to create line timeout
              r = usleep(SEC_WATCHDOG * 1000000);
            } while (r && (errno == EINTR || errno == EAGAIN));

            fprintf(stderr, "Add %dms to let menu reappear ...\r\n", 500);
            do {
              // wait for 0.5s to let menu reappear
              r = usleep(500000);
            } while (r && (errno == EINTR || errno == EAGAIN));
          }

          // other job in progress or active?
          else if (fw_menu_level & 0x07) {
            fprintf(stderr, "Reset job level ...\r\n");
            do {
              r = write(fdserial, &q, 1);
            } while (r < 1 && (errno == EINTR || errno == EAGAIN));
            do {
              r = usleep(500000);     // 0.5s
            } while (r && (errno == EINTR || errno == EAGAIN));
          }

          if (fw_SPI_is_powered > 0) {
            fprintf(stderr, "Switch SPI off ...\r\n");
            do {
              r = write(fdserial, &q, 1);
            } while (r < 1 && (errno == EINTR || errno == EAGAIN));
            do {
              r = usleep(500000);     // 0.5s
            } while (r && (errno == EINTR || errno == EAGAIN));
          }

          fprintf(stderr, "Quit firmware ...\r\n");
          do {
            r = write(fdserial, &q, 1);
          } while (r < 1 && (errno == EINTR || errno == EAGAIN));
          do {
            r = usleep(500000);     // 0.5s
          } while (r && (errno == EINTR || errno == EAGAIN));
        }
        break;

      default:
        break;
    }
  }
  return;
}


void dump (
  const char * msg_color
)
{
  /* dump global variables */

  fprintf(stderr, "Dump of globals:\r\n\n");
  fprintf(stderr, "* int fdserial:\r\t\t\t\t0x%02x\r\n", fdserial);
  fprintf(stderr, "* int fw_handshake:\r\t\t\t\t0x%02x\r\n", fw_handshake);
  fprintf(stderr, "* int fw_menu_keyid:\r\t\t\t\t0x%02x\r\n", fw_menu_keyid);
  fprintf(stderr, "* int fw_menu_level:\r\t\t\t\t0x%02x\r\n", fw_menu_level);
  fprintf(stderr, "* int fw_SPI_is_powered:\r\t\t\t\t0x%02x\r\n", fw_SPI_is_powered);
  fprintf(stderr, "* unsigned char usec_byte:\r\t\t\t\t0x%02x\r\n", usec_byte);
  fprintf(stderr, "%s\r\n", msg_color);

  return;
}


void termination (
  const int sig
)
{
  int r;
  char * msg_signal;
  char * msg_reset;
  char * msg_stop;

  /* raise signal if already in progress */
  if(signal_in_progress) {
    raise(sig);
  }
  else {
    signal_in_progress = 1;
  }

  /* set color to restore */
  if ((fw_menu_level & 0x04) && (fw_menu_keyid == 0x11)) {    // KEYID_I
    msg_signal = "";
    msg_reset = "";
    msg_stop = "";
  }
  else {
    msg_signal = MSG_SIGNAL;
    msg_reset = MSG_RESET;
    msg_stop = MSG_STOP;
  }

  /* check signal type */
  switch (sig) {
    case SIGTERM:
      fprintf(stderr, "\r\n\n%sSignal:%s SIGTERM\r\n\n", MSG_SIGNAL, MSG_RESET);
      break;    // no need to restore color

    case SIGINT:
      fprintf(stderr, "\r\n\n%sSignal:%s SIGINT\r\n\n", MSG_SIGNAL, MSG_RESET);
      break;    // no need to restore color

    case SIGQUIT:
      fprintf(stderr, "\r\n\n%sSignal:%s SIGQUIT\r\n\n", MSG_SIGNAL, MSG_RESET);
      dump(MSG_RESET);
      break;    // no need to restore color

    case SIGUSR1:
      fprintf(stderr, "\r\n\n%sSignal:%s SIGUSR1\r\n\n", msg_signal, msg_reset);
      dump(msg_stop);
      signal_in_progress = 0;
      return;

    default:
      fprintf(stderr, "\r\n\n%sSignal:%s unknown%s\r\n\n", msg_signal, msg_reset, msg_stop);
      signal_in_progress = 0;
      return;
  }

  /* quit firmware program */
  fwquit();

  /* close serial port */
  if (fdserial != -1) {
    fprintf(stderr, "Close serial port ... ");
    r = serial_close(fdserial);
    if (r != 0) {
      fprintf(stderr, "\r\n%sFailure:%s Error in serial_close()\r\n", MSG_ERROR, MSG_RESET);
    }
    else {
      fprintf(stderr, "ok\r\n");
    }
  }

  /* restore terminal settings */
  fprintf(stderr, "Restore terminal settings ... ");
  r = terminal_restore(STDOUT_FILENO, STDIN_FILENO);
  if (r != 0) {
    fprintf(stderr, "\r\n%sFailure:%s Error in terminal_restore()\r\n", MSG_ERROR, MSG_RESET);
  }
  else {
    fprintf(stderr, "ok\r\n");
  }

  /* terminate connect program */
  fprintf(stderr, "Terminate program ...\r\n\n");
  signal(sig, SIG_DFL);   // establish default back again
  raise(sig);             // raise signal, thus aborting program
}



void errout (
  const enum ERRCODE_t errcode,
  const char * infile
)
{
  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", infile);
      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;

    case ERRC__POWER_FAILURE:
      sprintf(msg, "Power failure");
      break;

    case ERRC__FATAL:
      sprintf(msg, "Device does not respond. Exiting.");
      break;

    default:    // dummy
      break;
  }

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

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

    case ERRC__FATAL:
      fprintf(stderr, "\r\n%s%s%s%s\r\n\n", FG_MAGENTA, "Fatal: ", FG_DEFAULT, msg);
      break;

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



int get_DTR (
  const 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);
}



int set_DTR (
  const int fdtty,
  const 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;
  int r;

  // get struct info
  r = ioctl(fdtty, TIOCMGET, &serial);
  if (r != 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in ioctl()");
    return 1;
  }

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

  // apply struct info
  r = ioctl(fdtty, TIOCMSET, &serial);
  if (r != 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in ioctl()");
    return 1;
  }

  // success
  return 0;
}



int set_RTS (
  const int fdtty,
  const 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;
  int r;

  // get struct info
  r = ioctl(fdtty, TIOCMGET, &serial);
  if (r != 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in ioctl()");
    return 1;
  }

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

  // apply struct info
  r = ioctl(fdtty, TIOCMSET, &serial);
  if (r != 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in ioctl()");
    return 1;
  }

  // success
  return 0;
}



int lock_tty (
  const int fdtty
)
{
  /*
    Lock Serial Port
    See `man tty_ioctl`
  ***/

  int r;

  r = ioctl(fdtty, TIOCEXCL);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in ioctl()");
    return 1;
  }

  return 0;
}



int unlock_tty (
  const int fdtty
)
{
  /*
    Unlock Serial Port. See `man tty_ioctl`.
  ***/

  int r;

  r = ioctl(fdtty, TIOCNXCL);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in ioctl()");
    return 1;
  }

  return 0;
}



void headline (
  const char c,
  const 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 * pport,
  const struct rate_t * prate,
  const struct RST_t * prst,
  const char * poutfile,
  const char * poutfile_secarr0,
  const char * poutfile_secarr1,
  const char * poutfile_secarr2,
  const char * poutfile_secarr3,
  const char * poutfile_sfdp,
  const char * pinfile,
  const char * pinfile_secarr0,
  const char * pinfile_secarr1,
  const char * pinfile_secarr2,
  const char * pinfile_secarr3
)
{
  // Put a greeting with some basic info on screen.

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

  printf("%s\r\n", MSG_START);
  headline('~', IDENTIFIER_CONNECT);
  printf("This is `./connect', the project's host utility, talking to `%s' at %s bps.\r\n", pport, prate->s + 1);
  printf("\r\n");
  printf("    RST signal line: %s\r\n", prst->s);
  printf("\r\n");
  printf("    probe chip\r\n");
  printf("        Your first action should be a probe, and a check of registers.\r\n");
  printf("        Adjust PCB settings until your probe is reliable!\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\t`%s'\r\n", poutfile);
  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 %s -o file.bin -raw' to turn it into a binary.\r\n", poutfile);
  printf("\r\n");
  printf("    write to chip\r\n");
  printf("        File in use for `file->chip' operation:\r\t\t\t\t\t\t\t`%s'\r\n", pinfile);
  printf("        Per default, data is parsed as Motorola S-Record format.\r\n");
  printf("        Use `srec_cat file.bin -raw -o %s' to create it from binary.\r\n", pinfile);
  printf("\r\n");
  printf("Waiting for the firmware, `kick' or `kick2'.\r\n");
  printf("If program freezes, type Ctrl-C or Ctrl-\\ to abort ...%s\r\n", MSG_STOP);
}



void goodbye (
  void
)
{
  printf("\r\n%sGod b'ye.%s\r\n\n", MSG_START, MSG_RESET);
}



/*
 * time_elapsed -- print job time
 */
void time_elapsed (
  const struct timespec *ts_start,
  const 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.
 *
 */
int polling (
  const int fdtty,
  const char * infile,
  const char * outfile,
  const char * pinfile_secarr0,
  const char * poutfile_secarr0,
  const char * pinfile_secarr1,
  const char * poutfile_secarr1,
  const char * pinfile_secarr2,
  const char * poutfile_secarr2,
  const char * pinfile_secarr3,
  const char * poutfile_secarr3,
  const char * poutfile_sfdp
)
{
  /*

    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;

  // SOH header: SOH, followed by SOH data
  // SOH data:   byte1, byte0
  enum SOH_STEP_t {
    SOH_GET_BYTE1 = -2,         // byte 1 of header         : get command
    SOH_GET_BYTE0,              // byte 0 of header (LSB)   : get command argument
    SOH_OFF                     // off = 0
  } SOH_step = SOH_OFF;
  unsigned char SOH_data1;   // (MSB)   // header command
  unsigned char SOH_data0;   // (LSB)   // header command argument

  // file transmission line type
  int file_linetype;

  // 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!
  int wheelctr = 0;
  char* pspinwheel = spinwheel + 3;
  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;
# ifdef ENABLE_FATAL_TIMEOUT
  unsigned long fatal_timeout;
# endif

  // 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 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) || (SOH_step != SOH_OFF)) {
        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);
        }
      }

# if !defined(TEST_FAIL_PSELECT) && !defined(TEST_PSELECT_TIMEOUT)
      // ===============================================================
      // 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);
# else
#   ifdef TEST_FAIL_PSELECT
      r = -1;
#   endif
#   ifdef TEST_PSELECT_TIMEOUT
      r = 0;
#   endif
# endif

      // check pselect() ready
      if(r > 0) {

        // write to stdout
        if(STDOUT_FILENO > -1 && FD_ISSET(STDOUT_FILENO, &wrfds)) {
# ifndef TEST_FAIL_WRITE_STDOUT
          r = write(STDOUT_FILENO, buf_stdout + prd_stdout, pwr_stdout - prd_stdout);
# else
          r = -1;
# endif
          if(r > 0) {
            prd_stdout += r;
          }
          else if(r < 0) {
            if(errno != EAGAIN && errno != EINTR)
              RETFAIL("write()");
          }
        }

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

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

        // read from stdin: any keystroke?
        if(STDIN_FILENO > -1 && FD_ISSET(STDIN_FILENO, &rdfds)) {
# ifndef TEST_FAIL_READ_STDIN
          r = read(STDIN_FILENO, &c, 1);
# else
          r = -1;
# endif
          if(r < 0) {
            if(errno != EAGAIN && errno != EINTR)
              RETFAIL("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)) {
# ifndef TEST_FAIL_READ_FDTTY
            r = read(fdtty, buf_rdtty + pwr_rdtty, SIZE_BUF_RDTTY - pwr_rdtty);
# else
            r = -1;
# endif
            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)
                RETFAIL("read() into buf_rdtty");
              continue;
            }
          }
# ifdef ENABLE_FATAL_TIMEOUT
          // FIXME how to escape from transmission freeze, nicely??
          else if(tx == WAIT_CHAR) {
            if(fatal_timeout) {
              fatal_timeout--;
              usleep(2);
            }
            else {
              errcode = ERRC__FATAL;
              tx = FLUSH_BUFFERS;
            }
          }
# endif
        }

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

            case BYTE_PREVIEW:
              b = a;
# ifndef TEST_FAIL_READ_FDRD
              r = read(fdrd, &a, 1);
# else
              r = -1;
# endif
              if(r == 0) {
                a = EM;
                if(b == EM) {
                  tx = SEND_EOT;
                  continue;
                }
              }
              else if(r < 0) {
                if(errno != EAGAIN && errno != EINTR)
                  RETFAIL ("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_nofeedback = usec_byte;
                if(file_linetype == 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_linetype == LINETYPE_HEXD)
                    tx = SEND_CLIM;
                  else
                    tx = SEND_ETX_OR_CAN;
                }
                else
                  tx = BYTE_PREVIEW;
              }
              else if(b == NEW_LINE) {
                if(file_linetype == LINETYPE_HEXD)
                  tx = SEND_CLIM;
                else
                  tx = SEND_ETX_OR_CAN;
              }
              else
                tx = SEND_BYTE;
              continue;

            case SEND_BYTE:
              tx = BYTE_PREVIEW;
              if(b) {
# ifdef USE_SRECORD_BINARY_PAYLOAD
                /*
                 * Enable this code block for binary S-Record payloads:
                 */
                if((ispayload_file2chip = lineinfo(file_linetype, &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
                }
# else
                /*
                 * 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
# endif
              }
              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;

# ifdef ENABLE_FATAL_TIMEOUT
              // FIXME would be good to setup a timeout here,
              // in case firmware will not respond due to too fast byte transmission
              // kick2: Uses a watchdog counter to detect timeout and to report error -- we must be slower!
              fatal_timeout = FATAL_CNT;
# endif
              iline_file2chip = SCANNER_OFF;
              continue;

            case WAIT_CHAR:
              continue;

            case NAK_RECEIVED:
              continue;                // get argument: error code

            case ACK_RECEIVED:
              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 {
# ifndef TEST_FAIL_CLOSE_INFILE
                r = close(fdrd);
# else
                r = -1;
# endif
                if(r < 0) {
                  if(errno != EAGAIN && errno != EINTR)
                    RETFAIL("close()");
                }
              } while (r < 0);
              fdrd = -1;
              printf(" \r");      // clear remaining character of spinwheel (if any), then carriage return

              // stop watch
              clock_gettime(clk, &ts_stop);

              // reset buffer pointers
              // TODO: why is that necessary?
              prd_stdout = pwr_stdout = 0;
              prd_file = pwr_file = 0;

              // display error status and time
# ifdef TEST_ERRC__FATAL
              errcode = ERRC__FATAL;
# endif
              errout(errcode, infile);
              if(errcode != ERRC__FATAL) {
                time_elapsed(&ts_start, &ts_stop);
                continue;
              }
              else {
                RETFAIL("fatal exit");      // FIXME poor action, device does not respond, exiting
              }
          }
        }
      }
      // check pselect() error
      else if(r < 0) {
        if(errno != EAGAIN && errno != EINTR)
          RETFAIL("pselect()");
      }
      // check pselect() timeout
      else if(r == 0) {

        // no menu keyboard input??
        if(STDIN_FILENO > -1 && !(fdwr > -1) && !(fdrd > -1) && !FD_ISSET(STDIN_FILENO, &rdfds)) {
          continue;
        }

        // timeout while receiving chip data for file write (after job cancel)
        if(fdtty > -1 && fdwr > -1 && !FD_ISSET(fdtty, &rdfds)) {
          errcode = ERRC__LINE_TIMEOUT;
          // FIXME what to do exactly??
        }

        // FIXME
        //   unhandled timeout, should not occur.
        //   but if, we should exit safely, closing files, etc.
        RETFAIL("pselect() -- timeout!");

      }
    }
    // is data in tty buffer?
    else if(prd_rdtty < pwr_rdtty) {

      // read from buf_rdtty
      c = buf_rdtty[prd_rdtty++];

      // header in progress?
      if(SOH_step) {

        switch(SOH_step) {

          case SOH_OFF:                 // dummy
            break;

          case SOH_GET_BYTE1:           // get command
            SOH_data1 = c;
            SOH_step = SOH_GET_BYTE0;
            break;

          case SOH_GET_BYTE0:           // get command argument byte
            SOH_data0 = c;
            SOH_step = SOH_OFF;

            // restore flags
            errcode = ERRC__SUCCESS;    // reset
            flag_can = OFF;             // reset
            ispayload = -1;             // reset start condition for color flag

            // evaluate SOH_data1 (e.g. command)
            switch(SOH_data1) {

              case THIS_IS_KICK2_CONNECT:
              case THIS_IS_KICK_CONNECT:
                fw_handshake = SOH_data1;
                // send ACK
                pc = &ack;
                write_tty = ON;
                break;

              case SET_USEC_BYTE:
                usec_byte = SOH_data0;
                break;

              case REPORT_MENU_KEYID:
                fw_menu_keyid = SOH_data0;
                break;

              case REPORT_SPI_POWER:
                fw_SPI_is_powered = SOH_data0;
                break;

              case REPORT_MENU_LEVEL:
                fw_menu_level = SOH_data0;
                break;

              case DEBUG_BYTE0:
                printf("\r\nBYTE0:0x%02x\r\n", SOH_data0);
                break;

              case DEBUG_BYTEN:
                printf("BYTEN:0x%02x\r\n", SOH_data0);
                break;

              case STDIN_ENABLE:
                read_stdin = ON;
                break;

              case STDIN_DISABLE:
                read_stdin = OFF;
                break;

              case SET_LINE_VISIBILITY:
                screen_output = SOH_data0 & 1;
                break;

              case GOOD_BYE:
                if(!dry_line && iline_chip2file < SCANNER_INI && iline_file2chip < SCANNER_INI) {
                  // return firmware status
                  return SOH_data0;
                }
                break;

              case FILE_TO_CHIP:
                // store command argument
                file_linetype = SOH_data0;

                // initiate transmission, stream will go active
# ifndef TEST_FAIL_OPEN_INFILE
                fdrd = open(infile, O_RDONLY);
# else
                fdrd = -1;
# endif
                if(fdrd == -1) {
                  // issue error message
                  errcode = ERRC__NO_SUCH_FILE;
                  errout(errcode, infile);
                  // send EOT
                  pc = &eot;
                  write_tty = ON;
                }
                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, infile, MSG_STOP);
                  else
                    printf("%sReading %s, sending data ...\r\n", MSG_START, infile);
                  clock_gettime(clk, &ts_start);
                }

                // end of case FILE_TO_CHIP
                break;

              case FILE_TO_SECARR3:
              case FILE_TO_SECARR2:
              case FILE_TO_SECARR1:
              case FILE_TO_SECARR0:
                {
                  // temporary local
                  const char * pfile;

                  // get file
                  switch (SOH_data1) {
                    case FILE_TO_SECARR3:   pfile = pinfile_secarr3; break;
                    case FILE_TO_SECARR2:   pfile = pinfile_secarr2; break;
                    case FILE_TO_SECARR1:   pfile = pinfile_secarr1; break;
                    case FILE_TO_SECARR0:   pfile = pinfile_secarr0; break;
                  }

                  // store command argument
                  file_linetype = SOH_data0;

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

              case CHIP_TO_FILE:
                // store command argument
                file_linetype = SOH_data0;

                // open port (no error handling with kick!)
# ifndef TEST_FAIL_OPEN_OUTFILE
                fdwr = open(
                  outfile,
                  O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK,
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
                );
# else
                fdwr = -1;
# endif
                if(fdwr == -1) {
                   RETFAIL("open()");
                }
                if(screen_output)   //remove host color setting if spinning wheel is not present
                  printf("%sReceiving data, writing %s ...%s\r\n", MSG_START, outfile, MSG_STOP);
                else
                  printf("%sReceiving data, writing %s ...\r\n", MSG_START, outfile);
                clock_gettime(clk, &ts_start);

                break;    // end of case CHIP_TO_FILE

              case SFDP_TO_FILE:
                // store command argument
                file_linetype = SOH_data0;

                // open port (no error handling with kick!)
                fdwr = open(
                  poutfile_sfdp,
                  O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK,
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
                );
                if(fdwr == -1) {
                   RETFAIL("open()");
                }
                if(screen_output)   //remove host color setting if spinning wheel is not present
                  printf("%sReceiving SFDP Array data, writing %s ...%s\r\n", MSG_START, poutfile_sfdp, MSG_STOP);
                else
                  printf("%sReceiving SFDP Array data, writing %s ...\r\n", MSG_START, poutfile_sfdp);
                clock_gettime(clk, &ts_start);

                break;    // end of case SFDP_TO_FILE

              case SECARR3_TO_FILE:
              case SECARR2_TO_FILE:
              case SECARR1_TO_FILE:
              case SECARR0_TO_FILE:
                {
                  // temporary local
                  const char * pfile;

                  // get file
                  switch (SOH_data1) {
                      case SECARR3_TO_FILE:   pfile = poutfile_secarr3; break;
                      case SECARR2_TO_FILE:   pfile = poutfile_secarr2; break;
                      case SECARR1_TO_FILE:   pfile = poutfile_secarr1; break;
                      case SECARR0_TO_FILE:   pfile = poutfile_secarr0; break;
                  }

                  // store command argument
                  file_linetype = SOH_data0;

                  // open port (no error handling with kick!)
                  fdwr = open(
                    pfile,
                    O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK,
                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
                  );
                  if(fdwr == -1) {
                     RETFAIL("open()");
                  }
                  printf("%sReceiving Security Array %d data, writing %s ...", MSG_START, (SOH_data1 - SECARR0_TO_FILE) >> 2, pfile);
                  if(screen_output)   //remove host color setting if spinning wheel is not present
                    printf("%s\r\n", MSG_STOP);
                  else
                    printf("\r\n");
                  clock_gettime(clk, &ts_start);
                }
                break;    // end of case SECARRx_TO_FILE

              // unknown header command??
              default:
                break;
            }
            break;    // end of case SOH_GET_BYTE0

        }   // end of switch(SOH_step)
      }
      // chip to file (data and control chars): S-Record with binary load
      else if(fdwr > -1) {
        // binary data transmission (payload only, no control chars)
        if(lineinfo(file_linetype, &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): S-Record and hexdump
        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 NAK:
              break;

            case EOT:
              if(iline_chip2file == SCANNER_OFF) {
                do {
# ifndef TEST_FAIL_CLOSE_OUTFILE
                  r = close(fdwr);
# else
                  r = -1;
# endif
                  if(r < 0) {
                    if(errno != EAGAIN && errno != EINTR)
                      RETFAIL("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, infile);
                time_elapsed(&ts_start, &ts_stop);
              }
              break;

            case ETX:

              // cancellation in progress?
              if(flag_can) {
                flag_can = 0;
                errcode = ERRC__JOB_CANCELLATION;
              }

              // set other errors
              if(!errcode && !flag_nodata) {
                switch(file_linetype) {
                  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;
                }
              }

              // switch line scanner off
              iline_chip2file = SCANNER_OFF;

              // write new line to file after selected errors
              if(errcode <= ERRC__JOB_CANCELLATION && !flag_nodata) {
                // start Write CLIM_NL
                c = CLIM_NL;                    // temporary use of c
                WRITE_BUF_FILE(c);
                write_file = ON;
                // end Write CLIM_NL
              }

              // evaluate errcode
              switch(errcode) {
                case ERRC__JOB_CANCELLATION:
                  pc = &can;      // default reply to ETX for job cancellation request
                  break;

                case ERRC__SUCCESS:
                  // provide a spinning wheel if no screen output
                  if(!(wheelctr-- & 3) && (!screen_output || flag_nodata)) {
                    WRITE_BUF_STDOUT__MSG_START;
                    WRITE_BUF_STDOUT(*pspinwheel);
                    WRITE_BUF_STDOUT('\b');
                    WRITE_BUF_STDOUT__MSG_STOP;
                    write_stdout = ON;
                    pspinwheel = pspinwheel == spinwheel ? spinwheel + 3 : pspinwheel - 1;
                  }
                  pc = &ack;      // default reply to ETX for no error, please continue
                  break;

                case ERRC__CHECKSUM_MISMATCH:
                case ERRC__LINE_LENGTH_MISMATCH:
                case ERRC__WRONG_CHARACTER:
                case ERRC__LINE_TIMEOUT:

                  // pointer reset
                  prd_stdout = pwr_stdout = 0;
                  prd_file = pwr_file = 0;
                  prd_rdtty = pwr_rdtty = 0;

                  dry_line = OFF;

                // default action for errors: send NAK
                default:    // any other error?
                  pc = &nak;
                  break;
              }
              write_tty = ON;
              break;

            default:
              // check charset
              switch(file_linetype) {
                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;
              }

              // NL is written after ETX
              if((c != CLIM_NL) && (c != CLIM_CR)) {
                WRITE_BUF_FILE(c);
              }

              // clear flag, at least one valid character received
              flag_nodata = 0;
              break;
          }
        }
      }
      // file to chip (data and control chars)
      else if(fdrd > -1) {

/* NOTICE wait_feedback is not used any more

        if(c == *pc)
          wait_feedback = OFF;

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

        switch(tx) {
          case CLOSE_TX:
            continue;

          case WAIT_CHAR:
            switch(c) {
              case ACK:
                if(!(wheelctr-- & 3) && !screen_output) {    // provide a spinning wheel if no output on screen available
                  WRITE_BUF_STDOUT(*pspinwheel);
                  WRITE_BUF_STDOUT('\b');
                  write_stdout = ON;
                  pspinwheel = pspinwheel == spinwheel ? spinwheel + 3 : pspinwheel - 1;
                }
                tx = ACK_RECEIVED;
                break;

              case NAK:
                tx = NAK_RECEIVED;
                break;

              case SOH:     // header received at and of line, tx = WAIT_CHAR still enabled, waiting for ACK/NAK
                SOH_step = SOH_GET_BYTE1;
                break;
            }
            continue;

          case NAK_RECEIVED:
            errcode = c;
            tx = SEND_EOT;
            continue;

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

              default:    // process feedback of line endings

/* NOTICE no feedback of line ending any more

                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;
                }
*/
                break;
            }
            continue;
        }
      }
      // catch-all-but-header-in-progress receiver
      else {
        switch(c) {
          case SOH:   // start of header for tx, rx
            SOH_step = SOH_GET_BYTE1;
            continue;

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

  // This line should never be reached
  RETFAIL("polling() -- broken infinite loop");
}



int terminal_save (
  const int fdstdout,
  const int fdstdin
)
{
  int r;

  // save current settings into global struct
  r = tcgetattr(fdstdout, &stdio_save);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in tcgetattr()");
    return 1;
  }

  return 0;
}



int terminal_restore (
  const int fdstdout,
  const int fdstdin
)
{
  int r;

  // restore settings after all queues have been processed
  r = tcsetattr(fdstdout, TCSAFLUSH, &stdio_save);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in tcsetattr()");
    return 1;
  }

  return 0;
}



int terminal_configure (
  const int fdstdout,
  const int fdstdin
)
{
  struct termios stdio;
  int r;

  // clear struct memory
  memset(&stdio, 0, sizeof(stdio));

  // initialize struct
  cfmakeraw(&stdio);

  // configure stdio terminal
  stdio.c_lflag |= ISIG;
  stdio.c_cc[VINTR] = 3;    // ASCII ETX    'Ctrl-C'
  stdio.c_cc[VQUIT] = 28;   // ASCII FS     'Ctrl-\'
  stdio.c_cc[VMIN] = 1;
  stdio.c_cc[VTIME] = 0;

  // make the change after waiting until all queued output and input has been processed
  r = tcsetattr(fdstdout, TCSAFLUSH, &stdio);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in tcsetattr()");
    return 1;
  }

  // make the writes non-blocking
  r = fcntl(fdstdout, F_SETFL, O_NONBLOCK);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in tcsetattr()");
    return 1;
  }

  // make the reads non-blocking
  // FIXME how to restore original setting?
  r = fcntl(fdstdin, F_SETFL, O_NONBLOCK);
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in tcsetattr()");
    return 1;
  }

  // success
  return 0;
}



int serial_open (
  const char * pport
)
{
  int fdtty;
  int r;

  /*
   * 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.
   *    We cannot use (O_CREAT | O_EXCL) here.
   */
  do {
    r = open(pport, O_RDWR | O_NOCTTY | O_NONBLOCK | O_EXCL);       // open existing serial port
  } while (r < 0 && (errno == EAGAIN || errno == EINTR));
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in open()");
    return -1;
  }
  else {
    // store serial port file descriptor
    fdtty = r;
  }

  // success
  return fdtty;
}



int serial_configure (
  const int fdtty,
  const speed_t baudrate
)
{
  struct termios sio;
  int r;

  // initialize memory
  memset(&sio, 0, sizeof(sio));

  // initialize struct
  cfmakeraw(&sio);

  // set serial settings
  sio.c_cflag |= CS8 | CREAD | CLOCAL;  // flag constants (8n1, see termios.h for more information)
  sio.c_iflag |= IGNPAR | IGNBRK;
  if (baudrate == B230400)              // CSTOPB is required to allow SPI write at 230400 baud
      sio.c_cflag |= CSTOPB;            // 8n2, give Propeller’s serial driver some extra time to process bytes
  sio.c_cc[VMIN] = 0;                   // minimum of input chars
  sio.c_cc[VTIME] = 0;                  // input timeout in deciseconds

  // set speed
  r = cfsetspeed(&sio, baudrate);               // specify in/out speed
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in cfsetspeed()");
    return 1;
  }

  // apply settings
  r = tcsetattr(fdtty, TCSANOW, &sio);          // apply changes now
  if (r < 0) {
    perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in tcsetattr()");
    return 1;
  }

  // success
  return 0;
}



int serial_reset (
  const int fdtty,
  const int t_assert,         // time in nanoseconds
  const int t_deassert,       // time in nanoseconds
  const struct RST_t * prst
)
{
  int r;

  /* 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
   */

  // assert reset on DTR, if not already by open()
  if (prst->i == 0) {
    r = set_DTR(fdtty, SPACE);
    if (r != 0) {
      fprintf(stderr, "\r%sFailure:%s Error in set_DTR()\r\n", MSG_ERROR, MSG_RESET);
      return 1;
    }
  }
  // assert reset on RTS, if not already by open()
  else {
    r = set_RTS(fdtty, SPACE);
    if (r != 0) {
      fprintf(stderr, "\r%sFailure:%s Error in set_RTS()\r\n", MSG_ERROR, MSG_RESET);
      return 1;
    }
  }

  // sleep t_assert
  do {
    r = usleep(t_assert);
  } while (r && (errno == EINTR || errno == EAGAIN));

  // disable board reset and trigger the Propeller (on DTR)
  if (prst->i == 0) {
    r = set_DTR(fdtty, MARK);   // remove reset on DTR
    if (r != 0) {
      fprintf(stderr, "\r%sFailure:%s Error in set_DTR()\r\n", MSG_ERROR, MSG_RESET);
      return 1;
    }
  }
  // disable board reset and trigger the Propeller (on RTS)
  else {
    r = set_RTS(fdtty, MARK);   // remove reset on RTS
    if (r != 0) {
      fprintf(stderr, "\r%sFailure:%s Error in set_RTS()\r\n", MSG_ERROR, MSG_RESET);
      return 1;
    }
  }

  // sleep t_deassert
  do {
    r = usleep(t_deassert);
  } while (r && (errno == EINTR || errno == EAGAIN));

  // success
  return 0;
}



int serial_close (
  const int fdtty
)
{
  int r;

  // close serial (fdtty)
  do {
    r = close(fdtty);
    if(r < 0) {
      if(errno != EAGAIN && errno != EINTR) {
        perror("\r"MSG_ERROR"Failure:"MSG_RESET" Error in close()");
        return 1;
      }
    }
  } while (r < 0);

  return 0;
}



int connect (
  const char * pport,
  const struct rate_t * prate,
  const struct RST_t * prst,
  const char * pinfile,
  const char * poutfile,
  const char * pinfile_secarr0,
  const char * poutfile_secarr0,
  const char * pinfile_secarr1,
  const char * poutfile_secarr1,
  const char * pinfile_secarr2,
  const char * poutfile_secarr2,
  const char * pinfile_secarr3,
  const char * poutfile_secarr3,
  const char * poutfile_sfdp
)
{
  int r;
  int rr;

  // configure terminal for communication
  r = terminal_configure(STDOUT_FILENO, STDIN_FILENO);
  if (r != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in terminal_configure()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }

  // open serial port, will return -1 on failure
  r = serial_open(pport);
  if (r < 0) {
    fprintf(stderr, "\r%sFailure:%s Error in serial_open()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }
  else {

    // store serial port file descriptor in global handle
    fdserial = r;

    // configure serial terminal
    r = serial_configure(fdserial, prate->macro);
    if (r != 0) {
      fprintf(stderr, "\r%sFailure:%s Error in serial_config()\r\n", MSG_ERROR, MSG_RESET);
      return 1;
    }

  }

  // apply reset sequence
  r = serial_reset(fdserial, 10000, 90000, prst);
  if (r != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in serial_reset()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }

  // call polling()
  rr = polling(\
    fdserial,\
    pinfile, poutfile,\
    pinfile_secarr0, poutfile_secarr0,\
    pinfile_secarr1, poutfile_secarr1,\
    pinfile_secarr2, poutfile_secarr2,\
    pinfile_secarr3, poutfile_secarr3,\
    poutfile_sfdp
  );
  if (rr != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in polling()\r\n", MSG_ERROR, MSG_RESET);
  }

  // close serial (fdtty)
  r = serial_close(fdserial);
  if (r != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in serial_close()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }

  // end successfully?
  return rr;
}



void version (
  const char * p
)
{
  printf( "    version\r\n");
  printf( "\t%s -- %s\r\n\n", p, IDENTIFIER_VERSION);
}



void usage (
  const char * p,
  const char * dfl_outfile,
  const char * dfl_outfile_security0,
  const char * dfl_outfile_security1,
  const char * dfl_outfile_security2,
  const char * dfl_outfile_security3,
  const char * dfl_outfile_sfdp,
  const char * dfl_infile,
  const char * dfl_infile_security0,
  const char * dfl_infile_security1,
  const char * dfl_infile_security2,
  const char * dfl_infile_security3
)
{
  // usage
  printf( "    Connect\r\n"\
          "\tThis is a small program of the Zerocat Chipflasher project, that is able to speak with the flasher’s firmware, kick or kick2.\r\n" \
          "\thttps://zerocat.org/chipflasher.html\r\n\n");
  printf( "    Usage\r\n" \
          "\t%s [-h]\r\n" \
          "\t%s [-v]\r\n" \
          "\t%s [-p <port>][-r <baudrate>][-s <resetline>]\\\r\n" \
          "\t\t [-o <chip2file>][-a <chip2file-security0>][-b <chip2file-security1>][-c <chip2file-security2>][-d <chip2file-security3>][-e <chip2file-sfdp>]\\\r\n" \
          "\t\t [-I <file2chip>][-A <file2chip-security0>][-B <file2chip-security1>][-C <file2chip-security2>][-D <file2chip-security3>]\r\n\n", p, p, p);
  printf( "    -h\r\n" \
          "\tprint usage information\r\n\n");
  printf( "    -v\r\n" \
          "\tprint version identifier\r\n\n");
  printf( "    -p <port>\r\n" \
          "\t/dev/ttyS0 (default) | /dev/ttyS1 | /dev/ttyS2 | /dev/ttyS3 |\r\n" \
          "\t/dev/ttyUSB0 | /dev/ttyUSB1 | /dev/ttyUSB2 | /dev/ttyUSB3\r\n\n");
  printf( "    -r <baudrate>\r\n" \
          "\tB38400 | B57600 | B115200 (default) | B230400 | ... | B921600\r\n\n");
  printf( "    -s <resetline>\r\n" \
          "\tDTR (default) | RTS\r\n\n");
  printf( "    -o <chip2file>\r\n"\
          "\tA text file that will be used to store chip’s Main Array readouts\r\n" \
          "\tas lines of Motorola S-Record or Hex-Dump, as understood by the srecord program collection.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_outfile);
  printf( "    -a <chip2file-security0>\r\n"\
          "\tA text file that will be used to store chip’s Security Array 0 readouts\r\n" \
          "\tas lines of Motorola S-Record or Hex-Dump, as understood by the srecord program collection.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_outfile_security0);
  printf( "    -b <chip2file-security1>\r\n"\
          "\tA text file that will be used to store chip’s Security Array 1 readouts\r\n" \
          "\tas lines of Motorola S-Record or Hex-Dump, as understood by the srecord program collection.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_outfile_security1);
  printf( "    -c <chip2file-security2>\r\n"\
          "\tA text file that will be used to store chip’s Security Array 2 readouts\r\n" \
          "\tas lines of Motorola S-Record or Hex-Dump, as understood by the srecord program collection.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_outfile_security2);
  printf( "    -d <chip2file-security3>\r\n"\
          "\tA text file that will be used to store chip’s Security Array 3 readouts\r\n" \
          "\tas lines of Motorola S-Record or Hex-Dump, as understood by the srecord program collection.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_outfile_security3);
  printf( "    -e <chip2file-sfdp>\r\n"\
          "\tA text file that will be used to store chip’s SFDP Array readouts\r\n" \
          "\tas lines of Motorola S-Record or Hex-Dump, as understood by the srecord program collection.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_outfile_sfdp);
  printf( "    -I <file2chip>\r\n" \
          "\tA text file that contains Main Array data to be flashed as lines\r\n" \
          "\tof Motorola S-Record or Hex-Dump.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_infile);
  printf( "    -A <file2chip-security0>\r\n" \
          "\tA text file that contains Security Array 0 data to be flashed as lines\r\n" \
          "\tof Motorola S-Record or Hex-Dump.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_infile_security0);
  printf( "    -B <file2chip-security1>\r\n" \
          "\tA text file that contains Security Array 1 data to be flashed as lines\r\n" \
          "\tof Motorola S-Record or Hex-Dump.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_infile_security1);
  printf( "    -C <file2chip-security2>\r\n" \
          "\tA text file that contains Security Array 2 data to be flashed as lines\r\n" \
          "\tof Motorola S-Record or Hex-Dump.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_infile_security2);
  printf( "    -D <file2chip-security3>\r\n" \
          "\tA text file that contains Security Array 3 data to be flashed as lines\r\n" \
          "\tof Motorola S-Record or Hex-Dump.\r\n" \
          "\tDefaults to `%s'.\r\n\n", dfl_infile_security3);
  printf( "    Output Files\r\n"\
          "\tchip2file.txt -- Data of a chip’s main array, default file name. See option -o.\r\n" \
          "\tchip2file-security0.txt -- Data of a chip’s SOTP array or equivalent, secured register, if any. See option -a.\r\n" \
          "\tchip2file-security1.txt -- Data of a chip’s second SOTP array or equivalent, secured register, if any. See option -b.\r\n" \
          "\tchip2file-security2.txt -- Data of a chip’s third SOTP array, if any. See option -c.\r\n" \
          "\tchip2file-security3.txt -- Data of a chip’s 4th SOTP array, if any. See option -d.\r\n" \
          "\tchip2file-sfdp.txt -- Data of a chip’s SFDP array, if any. See option -e.\r\n\n");
  printf( "    Input Files\r\n"\
          "\tfile2chip.txt -- Input data file for a chip’s main array, default file name. See option -I.\r\n" \
          "\tfile2chip-security0.txt -- Input data file for a chip’s SOTP array or equivalent, secured register, if any. See option -A.\r\n" \
          "\tfile2chip-security1.txt -- Input data file for a chip’s second SOTP array or equivalent, secured register, if any. See option -B.\r\n" \
          "\tfile2chip-security2.txt -- Input data file for a chip’s third SOTP array, if any. See option -C.\r\n" \
          "\tfile2chip-security3.txt -- Input data file for a chip’s 4th SOTP array, if any. See option -D.\r\n\n");
  printf( "    Signals\r\n"\
          "\tSIGTERM terminates the program and tries to terminate the device as well.\r\n" \
          "\tSIGINT does the same as SIGTERM.\r\n" \
          "\tSIGQUIT does the same as SIGINT but provides a small dump of global variables.\r\n" \
          "\tSIGUSR1 does not terminate the program, but provides a small dump of global variables.\r\n" \
          "\tYou can type Ctrl-C to send SIGINT.\r\n" \
          "\tYou can type Ctrl-\\ to send SIGQUIT.\r\n\n");
  printf( "    Notices\r\n"\
          "\tAll files are text files with lines of either Motorola S-Record or Hex-Dump format, as understood by the srecord program collection.\r\n\n");
}


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

  ***/

  char * port[] = {
    "/dev/ttyS0",
    "/dev/ttyS1",
    "/dev/ttyS2",
    "/dev/ttyS3",
    "/dev/ttyUSB0",
    "/dev/ttyUSB1",
    "/dev/ttyUSB2",
    "/dev/ttyUSB3"
  };
  struct rate_t rate[] = {
    { B921600, "B921600" },
    { B576000, "B576000" },
    { B500000, "B500000" },
    { B460800, "B460800" },
    { B230400, "B230400" },
    { B115200, "B115200" },
    { B57600,  "B57600"  },
    { B38400,  "B38400"  }
  };
  struct RST_t rst[] = {
    { 0, "DTR" },
    { 1, "RTS" }
  };

  // set default values
  char * pPort = port[PORT_DEFAULT];
  struct rate_t * pRate = &(rate[RATE_DEFAULT]);
  struct RST_t * pRST = &rst[RESETLINE_DEFAULT];
  char * file_to_send = FILE_TO_SEND;
  char * file_to_catch = FILE_TO_CATCH;
  char * secarr0_to_send = SECARR0_TO_SEND;
  char * secarr0_to_catch = SECARR0_TO_CATCH;
  char * secarr1_to_send = SECARR1_TO_SEND;
  char * secarr1_to_catch = SECARR1_TO_CATCH;
  char * secarr2_to_send = SECARR2_TO_SEND;
  char * secarr2_to_catch = SECARR2_TO_CATCH;
  char * secarr3_to_send = SECARR3_TO_SEND;
  char * secarr3_to_catch = SECARR3_TO_CATCH;
  char * sfdp_to_catch = SFDP_TO_CATCH;

  int status = 0;   // success

  char * p;
  int n;
  int r;

  // establish signal handler for SIGTERM (User’s usage of kill)
  if (signal(SIGTERM, termination) == SIG_IGN) {
    signal(SIGTERM, SIG_IGN);
  }

  // establish signal handler for SIGINT (User’s Ctrl-C)
  if (signal(SIGINT, termination) == SIG_IGN) {
    signal(SIGINT, SIG_IGN);
  }

  // establish signal handler for SIGQUIT (User’s Ctrl-\)
  if (signal(SIGQUIT, termination) == SIG_IGN) {
    signal(SIGQUIT, SIG_IGN);
  }

  // establish signal handler for SIGUSR1
  if (signal(SIGUSR1, termination) == SIG_IGN) {
    signal(SIGUSR1, SIG_IGN);
  }

  // save current stdout terminal settings to restore later
  r = terminal_save(STDOUT_FILENO, STDIN_FILENO);
  if (r != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in terminal_save()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }

  // basic check
  if (argc <= 1) {
    usage(&argv[0][0],\
      FILE_TO_CATCH,\
      SECARR0_TO_CATCH,\
      SECARR1_TO_CATCH,\
      SECARR2_TO_CATCH,\
      SECARR3_TO_CATCH,\
      SFDP_TO_CATCH,\
      FILE_TO_SEND,\
      SECARR0_TO_SEND,\
      SECARR1_TO_SEND,\
      SECARR2_TO_SEND,\
      SECARR3_TO_SEND\
    );
    return 0;
  }

  // parse loop for switches
  for (int i = 1; i < argc; ++i) {

    if (argv[i][0] == '-') {
      switch(argv[i][1]) {

        case 'p':       // -p<port> or -p <port>
          if (argv[i][2])
            p = &argv[i][2];
          else if (++i < argc)
            p = argv[i];
          else {
            fprintf(stderr, "error: no port specified with -p\r\n");
            return 1;
          }
          n = SIZE_PORT;
          while(n--) {
            if(!strcmp(p, port[n])) {
              pPort = p;
              break;
            }
          }
          break;

        case 's':       // -s<signal> or -s <signal>
          if (argv[i][2])
            p = &argv[i][2];
          else if (++i < argc)
            p = argv[i];
          else {
            fprintf(stderr, "error: no signal specified with -s\r\n");
            return 1;
          }
          // resetline
          n = SIZE_RESETLINE;
          while(n--) {
            if(!strcmp(p, rst[n].s)) {
              pRST = &rst[n];
              break;
            }
          }
          break;

        case 'I':       // -I<file2chip> or -I <file2chip>
          if (argv[i][2])
            file_to_send = &argv[i][2];
          else if (++i < argc)
            file_to_send = argv[i];
          else {
            fprintf(stderr, "error: no input file <file2chip> specified with -I\r\n");
            return 1;
          }
          break;

        case 'A':       // -A<file2chip-security0> or -A <file2chip-security0>
          if (argv[i][2])
            secarr0_to_send = &argv[i][2];
          else if (++i < argc)
            secarr0_to_send = argv[i];
          else {
            fprintf(stderr, "error: no input file <file2chip-security0> specified with -A\r\n");
            return 1;
          }
          break;

        case 'B':       // -B<file2chip-security1> or -B <file2chip-security1>
          if (argv[i][2])
            secarr1_to_send = &argv[i][2];
          else if (++i < argc)
            secarr1_to_send = argv[i];
          else {
            fprintf(stderr, "error: no input file <file2chip-security1> specified with -B\r\n");
            return 1;
          }
          break;

        case 'C':       // -C<file2chip-security2> or -C <file2chip-security2>
          if (argv[i][2])
            secarr2_to_send = &argv[i][2];
          else if (++i < argc)
            secarr2_to_send = argv[i];
          else {
            fprintf(stderr, "error: no input file <file2chip-security2> specified with -C\r\n");
            return 1;
          }
          break;

        case 'D':       // -D<file2chip-security3> or -D <file2chip-security3>
          if (argv[i][2])
            secarr3_to_send = &argv[i][2];
          else if (++i < argc)
            secarr3_to_send = argv[i];
          else {
            fprintf(stderr, "error: no input file <file2chip-security3> specified with -D\r\n");
            return 1;
          }
          break;

        case 'o':       // -o<chip2file> or -o <chip2file>
          if (argv[i][2])
            file_to_catch = &argv[i][2];
          else if (++i < argc)
            file_to_catch = argv[i];
          else {
            fprintf(stderr, "error: no output file <chip2file> specified with -o\r\n");
            return 1;
          }
          break;

        case 'a':       // -a<chip2file-security0> or -a <chip2file-security0>
          if (argv[i][2])
            secarr0_to_catch = &argv[i][2];
          else if (++i < argc)
            secarr0_to_catch = argv[i];
          else {
            fprintf(stderr, "error: no output file <chip2file-security0> specified with -a\r\n");
            return 1;
          }
          break;

        case 'b':       // -b<chip2file-security1> or -b <chip2file-security1>
          if (argv[i][2])
            secarr1_to_catch = &argv[i][2];
          else if (++i < argc)
            secarr1_to_catch = argv[i];
          else {
            fprintf(stderr, "error: no output file <chip2file-security1> specified with -b\r\n");
            return 1;
          }
          break;

        case 'c':       // -c<chip2file-security2> or -c <chip2file-security2>
          if (argv[i][2])
            secarr2_to_catch = &argv[i][2];
          else if (++i < argc)
            secarr2_to_catch = argv[i];
          else {
            fprintf(stderr, "error: no output file <chip2file-security2> specified with -c\r\n");
            return 1;
          }
          break;

        case 'd':       // -d<chip2file-security3> or -d <chip2file-security3>
          if (argv[i][2])
            secarr3_to_catch = &argv[i][2];
          else if (++i < argc)
            secarr3_to_catch = argv[i];
          else {
            fprintf(stderr, "error: no output file <chip2file-security3> specified with -d\r\n");
            return 1;
          }
          break;

        case 'e':       // -e<chip2file-sfdp> or -e <chip2file-sfdp>
          if (argv[i][2])
            sfdp_to_catch = &argv[i][2];
          else if (++i < argc)
            sfdp_to_catch = argv[i];
          else {
            fprintf(stderr, "error: no output file <chip2file-sfdp> specified with -e\r\n");
            return 1;
          }
          break;

        case 'r':       // -r<rate> or -r <rate>
          if (argv[i][2])
            p = &argv[i][2];
          else if (++i < argc)
            p = argv[i];
          else {
            fprintf(stderr, "error: no rate specified with -r\r\n");
            return 1;
          }
          // baudrate
          n = SIZE_RATE;
          while(n--) {
            if(!strcmp(p, rate[n].s)) {
              pRate = &rate[n];
              break;
            }
          }
          break;

        case 'v':       // -v   -- version
          version(&argv[0][0]);
          return 0;

        default:        // -h   -- help / usage
          usage(&argv[0][0],\
            FILE_TO_CATCH,\
            SECARR0_TO_CATCH,\
            SECARR1_TO_CATCH,\
            SECARR2_TO_CATCH,\
            SECARR3_TO_CATCH,\
            SFDP_TO_CATCH,\
            FILE_TO_SEND,\
            SECARR0_TO_SEND,\
            SECARR1_TO_SEND,\
            SECARR2_TO_SEND,\
            SECARR3_TO_SEND\
          );
          return 0;

      }
    }
  }

  // project greeter
  greeting(\
    pPort, pRate, pRST,\
    file_to_catch,\
    secarr0_to_catch,\
    secarr1_to_catch,\
    secarr2_to_catch,\
    secarr3_to_catch,\
    sfdp_to_catch,\
    file_to_send,\
    secarr0_to_send,\
    secarr1_to_send,\
    secarr2_to_send,\
    secarr3_to_send\
  );

  // get connected with Chipflasher
  status = connect(\
    pPort, pRate, pRST,\
    file_to_send, file_to_catch,\
    secarr0_to_send, secarr0_to_catch,\
    secarr1_to_send, secarr1_to_catch,\
    secarr2_to_send, secarr2_to_catch,\
    secarr3_to_send, secarr3_to_catch,\
    sfdp_to_catch\
  );

  // restore terminal in any case
  r = terminal_restore(STDOUT_FILENO, STDIN_FILENO);
  if (r != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in terminal_restore()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }

  // evaluate connect() status
  if(status != 0) {
    fprintf(stderr, "\r%sFailure:%s Error in connect()\r\n", MSG_ERROR, MSG_RESET);
    return 1;
  }

  // goodbye message
  goodbye();

  // success?
  return status;
}
