// ************************************************************************* // * Taken from the GSM TA/ME library // * // * File: gsm_unix_port.cc // * // * Purpose: UNIX serial port implementation with extras // * // * Original Author: Peter Hofmann (software@pxh.de) // * Modified by: Justin Huff (jjhuff@mspin.net) // * // * Created: 10.5.1999 // ************************************************************************* #include "config.h" #ifdef INCLUDE_SMSGSM #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kopete_unix_serial.h" using namespace std; using namespace gsmlib; static const int holdoff[] = {2000000, 1000000, 400000}; static const int holdoffArraySize = sizeof(holdoff)/sizeof(int); // alarm handling for socket read/write // the timerMtx is necessary since several threads cannot use the // timer indepently of each other static pthread_mutex_t timerMtx = PTHREAD_MUTEX_INITIALIZER; #define pthread_mutex_lock(x) #define pthread_mutex_unlock(x) // for non-GNU systems, define alarm() #ifndef HAVE_ALARM unsigned int alarm(unsigned int seconds) { struct itimerval old, newt; newt.it_interval.tv_usec = 0; newt.it_interval.tv_sec = 0; newt.it_value.tv_usec = 0; newt.it_value.tv_sec = (long int)seconds; if (setitimer(ITIMER_REAL, &newt, &old) < 0) return 0; else return old.it_value.tv_sec; } #endif // this routine is called in case of a timeout static void catchAlarm(int) { // do nothing } // start timer static void startTimer() { pthread_mutex_lock(&timerMtx); struct sigaction newAction; newAction.sa_handler = catchAlarm; newAction.sa_flags = 0; sigaction(SIGALRM, &newAction, NULL); alarm(1); } // reset timer static void stopTimer() { alarm(0); sigaction(SIGALRM, NULL, NULL); pthread_mutex_unlock(&timerMtx); } // KopeteUnixSerialPort members void KopeteUnixSerialPort::throwModemException(string message) throw(GsmException) { ostringstream os; os << message << " (errno: " << errno << "/" << strerror(errno) << ")"; throw GsmException(os.str(), OSError, errno); } void KopeteUnixSerialPort::putBack(unsigned char c) { assert(_oldChar == -1); _oldChar = c; } int KopeteUnixSerialPort::readByte() throw(GsmException) { if (_oldChar != -1) { int result = _oldChar; _oldChar = -1; return result; } unsigned char c; int timeElapsed = 0; struct timeval oneSecond; bool readDone = false; while (! readDone && timeElapsed < _timeoutVal) { if (interrupted()) throwModemException("interrupted when reading from TA"); // setup fd_set data structure for select() fd_set fdSet; oneSecond.tv_sec = 1; oneSecond.tv_usec = 0; FD_ZERO(&fdSet); FD_SET(_fd, &fdSet); switch (select(FD_SETSIZE, &fdSet, NULL, NULL, &oneSecond)) { case 1: { int res = read(_fd, &c, 1); if (res != 1) throwModemException("end of file when reading from TA"); else readDone = true; break; } case 0: ++timeElapsed; break; default: if (errno != EINTR) throwModemException("reading from TA"); break; } } if (! readDone) throwModemException("timeout when reading from TA"); #ifndef NDEBUG if (debugLevel() >= 2) { // some useful debugging code if (c == LF) cerr << ""; else if (c == CR) cerr << ""; else cerr << "<'" << (char) c << "'>"; cerr.flush(); } #endif return c; } KopeteUnixSerialPort::KopeteUnixSerialPort(string device, speed_t lineSpeed, string initString, bool swHandshake) throw(GsmException) : _oldChar(-1), _timeoutVal(TIMEOUT_SECS) { _readNotifier = NULL; struct termios t; // open device _fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); if (_fd == -1) throwModemException("opening device"); // switch off non-blocking mode int fdFlags; if ((fdFlags = fcntl(_fd, F_GETFL)) == -1) { close(_fd); throwModemException("getting file status flags failed"); } fdFlags &= ~O_NONBLOCK; if (fcntl(_fd, F_SETFL, fdFlags) == -1) { close(_fd); throwModemException("switching of non-blocking mode failed"); } // Set the close on exec flag if ((fdFlags = fcntl(_fd, F_GETFD)) == -1) { close(_fd); throwModemException("getting file status flags failed"); } fdFlags |= FD_CLOEXEC; if (fcntl(_fd, F_SETFD, fdFlags) == -1) { close(_fd); throwModemException("switching of non-blocking mode failed"); } long int saveTimeoutVal = _timeoutVal; _timeoutVal = 3; int initTries = holdoffArraySize; while (initTries-- > 0) { // flush all pending output tcflush(_fd, TCOFLUSH); // toggle DTR to reset modem int mctl = TIOCM_DTR; if (ioctl(_fd, TIOCMBIC, &mctl) < 0 && errno != ENOTTY) { close(_fd); throwModemException("clearing DTR failed"); } // the waiting time for DTR toggling is increased with each loop usleep(holdoff[initTries]); if (ioctl(_fd, TIOCMBIS, &mctl) < 0 && errno != ENOTTY) { close(_fd); throwModemException("setting DTR failed"); } // get line modes if (tcgetattr(_fd, &t) < 0) { close(_fd); throwModemException("tcgetattr device"); } // set line speed cfsetispeed(&t, lineSpeed); cfsetospeed(&t, lineSpeed); // set the device to a sane state t.c_iflag |= IGNPAR | (swHandshake ? IXON | IXOFF : 0); t.c_iflag &= ~(INPCK | ISTRIP | IMAXBEL | (swHandshake ? 0 : IXON | IXOFF) | IXANY | IGNCR | ICRNL | IMAXBEL | INLCR | IGNBRK); t.c_oflag &= ~(OPOST); // be careful, only touch "known" flags t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | (swHandshake ? CRTSCTS : 0 )); t.c_cflag |= CS8 | CREAD | HUPCL | (swHandshake ? 0 : CRTSCTS) | CLOCAL; t.c_lflag &= ~(ECHO | ECHOE | ECHOPRT | ECHOK | ECHOKE | ECHONL | ECHOCTL | ISIG | IEXTEN | TOSTOP | FLUSHO | ICANON); t.c_lflag |= NOFLSH; t.c_cc[VMIN] = 1; t.c_cc[VTIME] = 0; t.c_cc[VSUSP] = 0; // write back if(tcsetattr (_fd, TCSANOW, &t) < 0) { close(_fd); throwModemException("tcsetattr device"); } // the waiting time for writing to the ME/TA is increased with each loop usleep(holdoff[initTries]); // flush all pending input tcflush(_fd, TCIFLUSH); try { // reset modem putLine("ATZ"); bool foundOK = false; int readTries = 5; while (readTries-- > 0) { // for the first call getLine() waits only 3 seconds // because of _timeoutVal = 3 string s = getLine(); if (s.tqfind("OK") != string::npos || s.tqfind("CABLE: GSM") != string::npos) { foundOK = true; readTries = 0; // found OK, exit loop } else if (s.tqfind("ERROR") != string::npos) readTries = 0; // error, exit loop } // set getLine/putLine timeout back to old value _timeoutVal = saveTimeoutVal; if (foundOK) { // init modem readTries = 5; putLine("AT" + initString); while (readTries-- > 0) { string s = getLine(); if (s.tqfind("OK") != string::npos || s.tqfind("CABLE: GSM") != string::npos) { _readNotifier = new TQSocketNotifier(_fd, TQSocketNotifier::Read); connect( _readNotifier, TQT_SIGNAL(activated(int)), this, TQT_SIGNAL(activated())); return; // found OK, return } } } } catch (GsmException &e) { _timeoutVal = saveTimeoutVal; if (initTries == 0) { close(_fd); throw e; } } } // no response after 3 tries close(_fd); throwModemException("reset modem failed"); } string KopeteUnixSerialPort::getLine() throw(GsmException) { string result; int c; while ((c = readByte()) >= 0) { while (c == CR) { c = readByte(); } if (c == LF) break; result += c; } #ifndef NDEBUG if (debugLevel() >= 1) cerr << "<-- " << result << endl; #endif return result; } void KopeteUnixSerialPort::putLine(string line, bool carriageReturn) throw(GsmException) { #ifndef NDEBUG if (debugLevel() >= 1) cerr << "--> " << line << endl; #endif if (carriageReturn) line += CR; const char *l = line.c_str(); int timeElapsed = 0; struct timeval oneSecond; ssize_t bytesWritten = 0; while (bytesWritten < (ssize_t)line.length() && timeElapsed < _timeoutVal) { if (interrupted()) throwModemException("interrupted when writing to TA"); // setup fd_set data structure for select() fd_set fdSet; oneSecond.tv_sec = 1; oneSecond.tv_usec = 0; FD_ZERO(&fdSet); FD_SET(_fd, &fdSet); switch (select(FD_SETSIZE, NULL, &fdSet, NULL, &oneSecond)) { case 1: { ssize_t bw = write(_fd, l + bytesWritten, line.length() - bytesWritten); if (bw < 0) throwModemException("writing to TA"); bytesWritten += bw; break; } case 0: ++timeElapsed; break; default: if (errno != EINTR) throwModemException("writing to TA"); break; } } while (timeElapsed < _timeoutVal) { if (interrupted()) throwModemException("interrupted when writing to TA"); ::startTimer(); int res = tcdrain(_fd); // wait for output to be read by TA ::stopTimer(); if (res == 0) break; else { assert(errno == EINTR); ++timeElapsed; } } if (timeElapsed >= _timeoutVal) throwModemException("timeout when writing to TA"); // echo CR LF must be removed by higher layer functions in gsm_at because // in order to properly handle unsolicited result codes from the ME/TA } bool KopeteUnixSerialPort::wait(GsmTime timeout) throw(GsmException) { fd_set fds; FD_ZERO(&fds); FD_SET(_fd, &fds); return select(FD_SETSIZE, &fds, NULL, NULL, timeout) != 0; } // set timeout for read or write in seconds. void KopeteUnixSerialPort::setTimeOut(unsigned int timeout) { _timeoutVal = timeout; } KopeteUnixSerialPort::~KopeteUnixSerialPort() { delete _readNotifier; _readNotifier = NULL; if (_fd != -1) close(_fd); } #include "kopete_unix_serial.moc" #endif