// $Id: RlinkPortTerm.cpp 641 2015-02-01 22:12:15Z mueller $ // // Copyright 2011-2015 by Walter F.J. Mueller // // This program is free software; you may redistribute and/or modify it under // the terms of the GNU General Public License as published by the Free // Software Foundation, either version 2, or at your option any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for complete details. // // Revision History: // Date Rev Version Comment // 2015-02-01 641 1.2 support custom baud rates (5M,6M,10M,12M) // 2013-02-23 492 1.1 use RparseUrl // 2011-12-18 440 1.0.4 add kStatNPort stats; Open(): autoadd /dev/tty, // BUGFIX: Open(): set VSTART, VSTOP // 2011-12-11 438 1.0.3 Read(),Write(): added for xon handling, tcdrain(); // Open(): add more baud rates, support xon attribute // 2011-12-04 435 1.0.2 Open(): add cts attr, hw flow control now optional // 2011-07-04 388 1.0.1 add termios readback and verification // 2011-03-27 374 1.0 Initial version // --------------------------------------------------------------------------- /*! \file \version $Id: RlinkPortTerm.cpp 641 2015-02-01 22:12:15Z mueller $ \brief Implemenation of RlinkPortTerm. */ #include #include #include #include #include #include #include #include #include "RlinkPortTerm.hpp" #include "librtools/RosFill.hpp" #include "librtools/RosPrintf.hpp" using namespace std; /*! \class Retro::RlinkPortTerm \brief FIXME_docs */ // all method definitions in namespace Retro namespace Retro { //------------------------------------------+----------------------------------- // constants definitions const uint8_t RlinkPortTerm::kc_xon; const uint8_t RlinkPortTerm::kc_xoff; const uint8_t RlinkPortTerm::kc_xesc; //------------------------------------------+----------------------------------- //! Default constructor RlinkPortTerm::RlinkPortTerm() : RlinkPort() { fStats.Define(kStatNPortTxXesc, "NPortTxXesc", "Tx XESC escapes"); fStats.Define(kStatNPortRxXesc, "NPortRxXesc", "Rx XESC escapes"); } //------------------------------------------+----------------------------------- //! Destructor RlinkPortTerm::~RlinkPortTerm() { RlinkPortTerm::Close(); } //------------------------------------------+----------------------------------- //! FIXME_docs bool RlinkPortTerm::Open(const std::string& url, RerrMsg& emsg) { Close(); if (!fUrl.Set(url, "|baud=|break|cts|xon|", emsg)) return false; // if path doesn't start with a '/' prepend a '/dev/tty' if (fUrl.Path().substr(0,1) != "/") { fUrl.SetPath(string("/dev/tty" + fUrl.Path())); } speed_t speed = B115200; unsigned long nsbaud = 0; string baud; if (fUrl.FindOpt("baud", baud)) { speed = B0; if (baud=="2400") speed = B2400; if (baud=="4800") speed = B4800; if (baud=="9600") speed = B9600; if (baud=="19200" || baud=="19k") speed = B19200; if (baud=="38400" || baud=="38k") speed = B38400; if (baud=="57600" || baud=="57k") speed = B57600; if (baud=="115200" || baud=="115k") speed = B115200; if (baud=="230400" || baud=="230k") speed = B230400; if (baud=="460800" || baud=="460k") speed = B460800; if (baud=="500000" || baud=="500k") speed = B500000; if (baud=="921600" || baud=="921k") speed = B921600; if (baud=="1000000" || baud=="1000k" || baud=="1M") speed = B1000000; if (baud=="1152000" || baud=="1152k") speed = B1152000; if (baud=="1500000" || baud=="1500k") speed = B1500000; if (baud=="2000000" || baud=="2000k" || baud=="2M") speed = B2000000; if (baud=="2500000" || baud=="2500k") speed = B2500000; if (baud=="3000000" || baud=="3000k" || baud=="3M") speed = B3000000; if (baud=="3500000" || baud=="3500k") speed = B3500000; if (baud=="4000000" || baud=="4000k" || baud=="4M") speed = B4000000; // now handle non-standart baud rates if (speed == B0) { if (baud== "5000000" || baud== "5000k" || baud== "5M") nsbaud = 5000000; if (baud== "6000000" || baud== "6000k" || baud== "6M") nsbaud = 6000000; if (baud== "6666666" || baud== "6666k") nsbaud = 6666666; if (baud=="10000000" || baud=="10000k" || baud=="10M") nsbaud = 10000000; if (baud=="12000000" || baud=="12000k" || baud=="12M") nsbaud = 12000000; if (nsbaud == 0) { emsg.Init("RlinkPortTerm::Open()", string("invalid baud rate '") + baud + "' specified"); return false; } } } int fd; fd = open(fUrl.Path().c_str(), O_RDWR|O_NOCTTY); if (fd < 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("open() for '") + fUrl.Path() + "' failed: ", errno); return false; } if (!isatty(fd)) { emsg.Init("RlinkPortTerm::Open()", string("isatty() check for '") + fUrl.Path() + "' failed: not a TTY"); close(fd); return false; } if (::tcgetattr(fd, &fTiosOld) != 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("tcgetattr() for '") + fUrl.Path() + "' failed: ", errno); ::close(fd); return false; } struct serial_struct sioctl; int cdivisor = 0; if (nsbaud != 0) { if (::ioctl(fd, TIOCGSERIAL, &sioctl) < 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("ioctl(TIOCGSERIAL) for '")+fUrl.Path()+"' failed: ", errno); ::close(fd); return false; } double fcdivisor = double(sioctl.baud_base) / double(nsbaud); cdivisor = fcdivisor + 0.5; speed = B38400; } bool use_cts = fUrl.FindOpt("cts"); bool use_xon = fUrl.FindOpt("xon"); fUseXon = use_xon; fPendXesc = false; fTiosNew = fTiosOld; fTiosNew.c_iflag = IGNBRK | // ignore breaks on input IGNPAR; // ignore parity errors if (use_xon) { fTiosNew.c_iflag |= IXON| // XON/XOFF flow control output IXOFF; // XON/XOFF flow control input } fTiosNew.c_oflag = 0; fTiosNew.c_cflag = CS8 | // 8 bit chars CSTOPB | // 2 stop bits CREAD | // enable receiver CLOCAL; // ignore modem control if (use_cts) { fTiosNew.c_cflag |= CRTSCTS; // enable hardware flow control } fTiosNew.c_lflag = 0; if (::cfsetspeed(&fTiosNew, speed) != 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("cfsetspeed() for '") + baud + "' failed: ", errno); close(fd); return false; } if (cdivisor != 0) { sioctl.flags |= ASYNC_SPD_CUST; sioctl.custom_divisor = cdivisor; if (::ioctl(fd, TIOCSSERIAL, &sioctl) < 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("ioctl(TIOCSSERIAL) for '")+fUrl.Path()+"' failed: ", errno); ::close(fd); return false; } } fTiosNew.c_cc[VEOF] = 0; // undef fTiosNew.c_cc[VEOL] = 0; // undef fTiosNew.c_cc[VERASE] = 0; // undef fTiosNew.c_cc[VINTR] = 0; // undef fTiosNew.c_cc[VKILL] = 0; // undef fTiosNew.c_cc[VQUIT] = 0; // undef fTiosNew.c_cc[VSUSP] = 0; // undef fTiosNew.c_cc[VSTART] = 0; // undef fTiosNew.c_cc[VSTOP] = 0; // undef fTiosNew.c_cc[VMIN] = 1; // wait for 1 char fTiosNew.c_cc[VTIME] = 0; // if (use_xon) { fTiosNew.c_cc[VSTART] = kc_xon; // setup XON -> ^Q fTiosNew.c_cc[VSTOP] = kc_xoff; // setup XOFF -> ^S } if (::tcsetattr(fd, TCSANOW, &fTiosNew) != 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("tcsetattr() for '") + fUrl.Path() + "' failed: ", errno); ::close(fd); return false; } // tcsetattr() returns success if any of the requested changes could be // successfully carried out. Therefore the termios structure is read back // and verified. struct termios tios; if (::tcgetattr(fd, &tios) != 0) { emsg.InitErrno("RlinkPortTerm::Open()", string("2nd tcgetattr() for '") + fUrl.Path() + "' failed: ", errno); ::close(fd); return false; } const char* pmsg = 0; if (tios.c_iflag != fTiosNew.c_iflag) pmsg = "c_iflag"; if (tios.c_oflag != fTiosNew.c_oflag) pmsg = "c_oflag"; if (tios.c_cflag != fTiosNew.c_cflag) pmsg = "c_cflag"; if (tios.c_lflag != fTiosNew.c_lflag) pmsg = "c_lflag"; if (::cfgetispeed(&tios) != speed) pmsg = "ispeed"; if (::cfgetospeed(&tios) != speed) pmsg = "ospeed"; for (int i=0; i= 0) { ::tcflush(fFdWrite, TCIOFLUSH); ::tcsetattr(fFdWrite, TCSANOW, &fTiosOld); } RlinkPort::Close(); return; } //------------------------------------------+----------------------------------- //! FIXME_docs int RlinkPortTerm::Read(uint8_t* buf, size_t size, double timeout, RerrMsg& emsg) { int irc; if (fUseXon) { uint8_t* po = buf; if (fRxBuf.size() < size) fRxBuf.resize(size); // repeat read until at least one byte returned (or an error occurs) // this avoids that the Read() returns with 0 in case only one byte is // seen and this is a kc_xesc. At most two iterations possible because // in 2nd iteration fPendXesc must be set and thus po pushed. while (po == buf) { irc = RlinkPort::Read(fRxBuf.data(), size, timeout, emsg); if (irc <= 0) break; uint8_t* pi = fRxBuf.data(); for (int i=0; i