WvStreams
wvmodem.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *   Copyright (C) 1999 Red Hat, Inc.
00005  *
00006  * Implementation of the WvModem class. Inherits from WvFile, but
00007  * handles various important details related to modems, like setting
00008  * the baud rate, checking carrier detect, and dropping DTR.
00009  *
00010  */
00011 
00012 #include "wvmodem.h"
00013 #include <sys/ioctl.h>
00014 
00015 #if HAVE_LINUX_SERIAL_H
00016 # include <linux/serial.h>
00017 #endif
00018 
00019 #if ! HAVE_CFMAKERAW
00020 static inline void cfmakeraw(struct termios *termios_p)
00021 {
00022     termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
00023     termios_p->c_oflag &= ~OPOST;
00024     termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
00025     termios_p->c_cflag &= ~(CSIZE|PARENB);
00026     termios_p->c_cflag |= CS8;
00027 }
00028 #endif
00029 
00030 struct SpeedLookup {
00031     int baud;
00032     speed_t speedt;
00033 };
00034 
00035 
00036 static SpeedLookup speeds[] = {
00037 #ifdef B460800
00038     {460800, B460800},
00039 #endif
00040 #ifdef B230400
00041     {230400, B230400},
00042 #endif
00043     {115200, B115200},
00044     { 57600, B57600},
00045     { 38400, B38400},
00046     { 19200, B19200},
00047     {  9600, B9600},
00048     {  4800, B4800},
00049     {  2400, B2400},
00050     {  1200, B1200},
00051     {   300, B300}
00052 };
00053 
00054 
00055 WvModemBase::WvModemBase(int _fd) : WvFile(_fd)
00056 {
00057     get_real_speed();
00058 }
00059 
00060 
00061 WvModemBase::~WvModemBase()
00062 {
00063     // nothing needed
00064 }
00065 
00066 
00067 int WvModemBase::get_real_speed()
00068 {
00069     speed_t s;
00070     
00071     if (!isok()) return 0;
00072 
00073     tcgetattr( getrfd(), &t );
00074     s = cfgetospeed( &t );
00075     for (unsigned int i = 0; i < sizeof(speeds) / sizeof(*speeds); i++)
00076     {
00077         if (speeds[i].speedt == s)
00078         {
00079             baud = speeds[i].baud;
00080             break;
00081         }
00082     }
00083 
00084     return baud;
00085 }
00086 
00087 
00088 void WvModemBase::close()
00089 {
00090     // no file open, no need to close it
00091 }
00092 
00093 
00094 bool WvModemBase::carrier()
00095 {
00096     return true;
00097 }
00098 
00099 
00100 int WvModemBase::speed(int)
00101 {
00102     return baud;
00103 }
00104 
00105 
00106 void WvModemBase::hangup()
00107 {
00108     int i, oldbaud = baud;
00109     
00110     if (die_fast || !isok()) return;
00111 
00112     // politely abort any dial in progress, to avoid locking USR modems.
00113     // we should only do this if we have received any response from the modem,
00114     // so that WvModemScan can run faster.
00115     drain();
00116     write( "\r", 1 );
00117     // FIXME: should be iswritable, but based on the numer of msec params
00118     // tossed around I assume modems are very timing-sensitive
00119     for (i = 0; !select(200, false, true) && i < 10; i++)
00120         write( "\r", 1 );
00121     drain();
00122 
00123     // drop DTR for a while, if still online
00124     if (carrier())
00125     {
00126         cfsetospeed( &t, B0 );
00127         tcsetattr( getrfd(), TCSANOW, &t );
00128         for (i = 0; carrier() && i < 10; i++)
00129             usleep( 100 * 1000 );
00130 
00131         // raise DTR again, restoring the old baud rate
00132         speed(oldbaud);
00133     }
00134     
00135     if (carrier())
00136     {
00137         // need to do +++ manual-disconnect stuff
00138         write( "+++", 3 );
00139         usleep( 1500 * 1000 );
00140         write( "ATH\r", 4 );
00141         
00142         for (i = 0; carrier() && i < 5; i++)
00143             usleep( 100 * 1000 );
00144     }
00145 }
00146 
00147 
00148 
00149 WvModem::WvModem(WvStringParm filename, int _baud, bool rtscts, bool _no_reset)
00150     : WvModemBase(), lock(filename), log("WvModem", WvLog::Debug1)
00151 {
00152     closing = false;
00153     baud = _baud;
00154     die_fast = false;
00155     no_reset = _no_reset;
00156     have_old_t = false;
00157     
00158     if (!lock.lock())
00159     {
00160         seterr(EBUSY);
00161         return;
00162     }
00163     
00164     // note: if CLOCAL is not set on the modem, open will
00165     // block until a carrier detect.  Since we have to open the modem to
00166     // generate a carrier detect, we have a problem.  So we open the modem
00167     // nonblocking.  It would then be safe to switch to blocking mode,
00168     // but that is no longer recommended for WvStream.
00169     open(filename, O_RDWR|O_NONBLOCK|O_NOCTTY);
00170     
00171     if (isok())
00172         setup_modem(rtscts);
00173 }
00174 
00175 
00176 WvModem::~WvModem()
00177 {
00178     close();
00179 }
00180 
00181 
00182 void WvModem::setup_modem(bool rtscts)
00183 {
00184     if (!isok()) return;
00185 
00186     if (tcgetattr(getrfd(), &t) || tcgetattr(getrfd(), &old_t))
00187     {
00188         closing = true;
00189         seterr(errno);
00190         return;
00191     }
00192     have_old_t = true;
00193     
00194     drain();
00195     
00196 #if 0
00197     struct serial_struct old_sinfo, sinfo;
00198     sinfo.reserved_char[0] = 0;
00199     if (ioctl(getrfd(), TIOCGSERIAL, &old_sinfo) < 0)
00200         log("Cannot get information for serial port.");
00201     else
00202     {
00203         sinfo = old_sinfo;
00204         // Why there are two closing wait timeouts, is beyond me
00205         // but there are... apparently the second one is deprecated
00206         // but why take a chance...
00207         sinfo.closing_wait = ASYNC_CLOSING_WAIT_NONE;
00208         sinfo.closing_wait2 = ASYNC_CLOSING_WAIT_NONE;
00209 
00210         if (ioctl(getrfd(), TIOCSSERIAL, &sinfo) < 0)
00211             log("Cannot set information for serial port.");
00212     }
00213 #endif
00214 
00215     // set up the terminal characteristics.
00216     // see "man tcsetattr" for more information about these options.
00217     t.c_iflag &= ~(BRKINT | ISTRIP | IUCLC | IXON | IXANY | IXOFF | IMAXBEL);
00218     t.c_iflag |= (IGNBRK | IGNPAR);
00219     t.c_oflag &= ~(OLCUC);
00220     t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD);
00221     t.c_cflag |= (CS8 | CREAD | HUPCL | CLOCAL);
00222     if(rtscts)
00223         t.c_cflag |= CRTSCTS;
00224     t.c_lflag &= ~(ISIG | XCASE | ECHO);
00225     tcsetattr(getrfd(), TCSANOW, &t);
00226     
00227     // make sure we leave the modem in CLOCAL when we exit, so normal user
00228     // tasks can open the modem without using nonblocking.
00229     old_t.c_cflag |= CLOCAL;
00230     
00231     // Send a few returns to make sure the modem is "good and zonked".
00232     if (cfgetospeed(&t) != B0 && !no_reset)
00233     {
00234         for(int i=0; i<5; i++) 
00235         {
00236             write("\r", 1);
00237             usleep(10 * 1000);
00238         }
00239     }
00240     
00241     // Set the baud rate to 0 for half a second to drop DTR...
00242     cfsetispeed(&t, B0);
00243     cfsetospeed(&t, B0);
00244     cfmakeraw(&t);
00245     tcsetattr(getrfd(), TCSANOW, &t);
00246     if (carrier())
00247         usleep(500 * 1000);
00248     
00249     speed(baud);
00250     usleep(10 * 1000);
00251     
00252     drain();
00253 }
00254 
00255 
00256 void WvModem::close()
00257 {
00258     if (!closed)
00259     {
00260         if (!closing)
00261         {
00262             closing = true;
00263             if (!no_reset)
00264                 hangup();
00265             else
00266             {
00267                 drain();
00268                 cfsetospeed(&t, B0);
00269                 // If this works??
00270                 write("\r");
00271             }
00272         }
00273     
00274         closing = true;
00275         if (getrfd() >= 0)
00276         {
00277             tcflush(getrfd(), TCIOFLUSH);
00278             if (have_old_t)
00279                 tcsetattr(getrfd(), TCSANOW, &old_t);
00280             tcflush(getrfd(), TCIOFLUSH);
00281         }
00282         WvFile::close();
00283         closing = false;
00284     }
00285 }
00286 
00287 
00288 int WvModem::speed(int _baud)
00289 {
00290     speed_t s = B0;
00291     baud = 0;
00292     for (unsigned int i = 0; i < sizeof(speeds) / sizeof(*speeds); i++)
00293     {
00294         if (speeds[i].baud <= _baud)
00295         {
00296             s = speeds[i].speedt;
00297             break;
00298         }
00299     }
00300 
00301     cfsetispeed(&t, B0); // auto-match to output speed
00302     cfsetospeed(&t, s);
00303     tcsetattr(getrfd(), TCSANOW, &t);
00304 
00305     return get_real_speed();
00306 }
00307 
00308 
00309 int WvModem::getstatus()
00310 {
00311     if (!isok()) return 0;
00312     int status = 0;
00313     ioctl(getrfd(), TIOCMGET, &status);
00314     return status;
00315 }
00316 
00317 
00318 bool WvModem::carrier()
00319 {
00320     return (getstatus() & TIOCM_CD) ? 1 : 0;
00321 }