kandy

modem.cpp

00001 /*
00002     KMLOCfg
00003 
00004     A utility to configure the ELSA MicroLink(tm) Office modem.
00005 
00006     Copyright (C) 2000 Oliver Gantz <Oliver.Gantz@epost.de>
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00021 
00022     ------
00023     ELSA and MicroLink are trademarks of ELSA AG, Aachen.
00024 */
00025 
00026 #ifdef HAVE_CONFIG_H
00027 #include "config.h"
00028 #endif
00029 
00030 #include <sys/types.h>
00031 #include <sys/stat.h>
00032 #include <sys/ioctl.h>
00033 #include <fcntl.h>
00034 #include <termios.h>
00035 #include <unistd.h>
00036 #include <stdlib.h>
00037 #include <stdio.h>
00038 #include <string.h>
00039 #include <signal.h>
00040 #include <pwd.h>
00041 #include <errno.h>
00042 
00043 #include <qglobal.h>
00044 
00045 #include <klocale.h>
00046 #include <kdebug.h>
00047 
00048 #include "modem.h"
00049 
00050 
00051 #ifndef CSOH
00052 #define CSOH  01
00053 #endif
00054 
00055 #ifndef CSTX
00056 #define CSTX  02
00057 #endif
00058 
00059 #ifndef CEOT
00060 #define CEOT  04
00061 #endif
00062 
00063 #ifndef CACK
00064 #define CACK  06
00065 #endif
00066 
00067 #ifndef CNAK
00068 #define CNAK 025
00069 #endif
00070 
00071 #ifndef CCAN
00072 #define CCAN 030
00073 #endif
00074 
00075 
00076 
00077 Modem::Modem( KandyPrefs *kprefs, QObject *parent, const char *name ) :
00078   QObject(parent, name)
00079 {
00080   mOpen = false;
00081   
00082   prefs = kprefs;
00083 
00084   timer = new QTimer( this, "modemtimer" );
00085   Q_CHECK_PTR( timer );
00086   connect( timer, SIGNAL( timeout() ), SLOT( timerDone() ) );
00087 
00088   init();
00089   xreset();
00090 }
00091 
00092 
00093 Modem::~Modem()
00094 {
00095   close();
00096 }
00097 
00098 
00099 void Modem::setSpeed( int speed )
00100 {
00101   switch ( speed ) {
00102     case 300:
00103       cspeed = B300;
00104       break;
00105     case 600:
00106       cspeed = B600;
00107       break;
00108     case 1200:
00109       cspeed = B1200;
00110       break;
00111     case 2400:
00112       cspeed = B2400;
00113       break;
00114     case 4800:
00115       cspeed = B4800;
00116       break;
00117     case 9600:
00118       cspeed = B9600;
00119       break;
00120     case 19200:
00121       cspeed = B19200;
00122       break;
00123     case 38400:
00124       cspeed = B38400;
00125       break;
00126     case 57600:
00127       cspeed = B57600;
00128       break;
00129     case 115200:
00130       cspeed = B115200;
00131       break;
00132     case 230400:
00133       cspeed = B230400;
00134       break;
00135     default:
00136 #ifdef MODEM_DEBUG
00137       fprintf(stderr, "Modem: setSpeed(): fallback to default speed.\n");
00138 #endif
00139       cspeed = B38400;
00140   }
00141 }
00142 
00143 
00144 void Modem::setData( int data )
00145 {
00146   cflag &= ~CSIZE;
00147 
00148   switch ( data ) {
00149     case 5:
00150       cflag |= CS5;
00151       break;
00152     case 6:
00153       cflag |= CS6;
00154       break;
00155     case 7:
00156       cflag |= CS7;
00157       break;
00158     default:
00159       cflag |= CS8;
00160   }
00161 }
00162 
00163 
00164 void Modem::setParity( char parity )
00165 {
00166   cflag &= ~( PARENB | PARODD );
00167 
00168   if ( parity == 'E' )
00169     cflag |= PARENB;
00170   else if ( parity == 'O' )
00171     cflag |= PARENB | PARODD;
00172 }
00173 
00174 
00175 void Modem::setStop( int stop )
00176 {
00177   if (stop == 2)
00178     cflag |= CSTOPB;
00179   else
00180     cflag &= ~CSTOPB;
00181 }
00182 
00183 
00184 bool Modem::open()
00185 {
00186   struct termios tty;
00187 
00188 
00189   close();
00190 
00191   if ( !lockDevice() )
00192     return false;
00193 
00194   const char *fdev = QFile::encodeName( (*prefs).serialDevice() ).data();
00195   if ( ( fd = ::open( fdev, O_RDWR | O_NOCTTY | O_NONBLOCK ) ) == -1 ) {
00196     emit errorMessage( i18n( "Unable to open device '%1'. "
00197                              "Please check that you have sufficient permissions." )
00198                              .arg( fdev ) );
00199     return false;
00200   }
00201 
00202   tcflush( fd, TCIOFLUSH );
00203   if ( tcgetattr( fd, &init_tty ) == -1 ) {
00204     int errnumber = errno;
00205     emit errorMessage( i18n( "Communication setup failed (tcgetattr code: %1)" )
00206         .arg(strerror(errnumber)) );
00207     ::close( fd );
00208     fd = 0;
00209     return false;
00210   }
00211 
00212   memset( &tty, 0, sizeof( tty ) );
00213   tty.c_iflag = IGNBRK | IGNPAR;
00214   tty.c_oflag = 0;
00215   tty.c_cflag = cflag;
00216   tty.c_lflag = 0;
00217   cfsetospeed( &tty, cspeed );
00218   cfsetispeed( &tty, cspeed );
00219   tcdrain( fd );
00220 
00221   if ( tcsetattr( fd, TCSANOW, &tty ) == -1 ) {
00222     emit errorMessage( i18n( "tcsetattr() failed." ) );
00223     ::close( fd );
00224     fd = 0;
00225     return false;
00226   }
00227 
00228   sn = new QSocketNotifier( fd, QSocketNotifier::Read, this,
00229                             "modemsocketnotifier" );
00230   Q_CHECK_PTR( sn );
00231   connect( sn, SIGNAL( activated( int ) ), SLOT( readChar( int ) ) );
00232 
00233   mOpen = true;
00234 
00235   return true;
00236 }
00237 
00238 
00239 void Modem::close()
00240 {
00241   timer->stop();
00242 
00243   delete sn;
00244   sn = 0;
00245 
00246   if ( fd ) {
00247     tcflush( fd, TCIOFLUSH );
00248     tcsetattr( fd, TCSANOW, &init_tty );
00249     ::close( fd );
00250     fd = 0;
00251   }
00252 
00253   xreset();
00254 
00255   unlockDevice();
00256 
00257   mOpen = false;
00258 }
00259 
00260 
00261 void Modem::flush()
00262 {
00263   if ( fd ) {
00264     tcflush( fd, TCIOFLUSH );
00265     bufpos = 0;
00266   }
00267 }
00268 
00269 #ifdef HAVE_LOCKDEV
00270 #include <lockdev.h>
00271 #endif
00272 
00273 bool Modem::lockDevice()
00274 {
00275   if ( is_locked )
00276     return true;
00277 
00278 #ifdef HAVE_LOCKDEV
00279   is_locked = !dev_lock( (*prefs).serialDevice().local8Bit() );
00280   if (!is_locked)
00281       emit errorMessage( i18n( "Unable to lock device '%1'." ).arg(
00282                              (*prefs).serialDevice() ));
00283   return is_locked;
00284 #else
00285   ssize_t count;
00286   pid_t pid;
00287   int lfd;
00288   struct passwd *pw;
00289   QStringList pathList;
00290   QString fileName, content;
00291 
00292   pathList = QStringList::split( "/", (*prefs).serialDevice() );
00293   fileName = (*prefs).lockDirectory() + "/LCK.." + pathList.last();
00294 
00295   if ( !access( QFile::encodeName( fileName ).data(), F_OK ) ) {
00296     char buf[256];
00297 
00298 
00299     if ( ( lfd = ::open( QFile::encodeName( fileName ), O_RDONLY ) ) < 0 ) {
00300       emit errorMessage( i18n( "Unable to open lock file '%1'.")
00301                                .arg( fileName ) );
00302       return false;
00303     }
00304 
00305     count = read( lfd, buf, 79 );
00306 
00307     if ( count < 0 ) {
00308        emit errorMessage( i18n( "Unable to read lock file '%1'.")
00309                                 .arg( fileName ) );
00310        ::close( lfd );
00311        return false;
00312     }
00313     buf[ count ] = 0;
00314     ::close( lfd );
00315 
00316     count = sscanf( buf, "%d", &pid );
00317     if ( ( count != 1 ) || ( pid <= 0 ) ) {
00318        emit errorMessage( i18n( "Unable to get PID from file '%1'.")
00319                                 .arg( fileName ) );
00320        return false;
00321     }
00322 
00323     if ( !kill( (pid_t) pid, 0 ) ) {
00324        emit errorMessage( i18n( "Process with PID %1, which is locking the device, is still running.")
00325                                 .arg( pid ) );
00326        return false;
00327     }
00328 
00329     if ( errno != ESRCH ) {
00330       emit errorMessage( i18n( "Unable to emit signal to PID of existing lock file.") );
00331       return false;
00332     }
00333   }
00334     
00335   if ( ( lfd = creat( QFile::encodeName( fileName ).data(), 0644 ) ) == -1 ) {
00336     emit errorMessage( i18n( "Unable to create lock file '%1'. "
00337                              "Please check that you have sufficient permissions.")
00338                              .arg( fileName ) );
00339     return false;
00340   }
00341 
00342   pid = (int) getpid();
00343   pw = getpwuid( getuid() );
00344   content.sprintf( "%08d %s %s", pid, "kandy", pw->pw_name );
00345   write( lfd, QFile::encodeName( content ).data(), content.length() );
00346   ::close( lfd );
00347 
00348   is_locked = true;
00349 
00350   return true;
00351 #endif
00352 }
00353 
00354 
00355 void Modem::unlockDevice()
00356 {
00357 #ifdef HAVE_LOCKDEV
00358   dev_unlock( (*prefs).serialDevice().local8Bit(), getpid() );
00359 #else
00360   if ( is_locked ) {
00361     QStringList pathList = QStringList::split( "/", (*prefs).serialDevice() );
00362 
00363     QFile::remove( (*prefs).lockDirectory() + "/LCK.." + pathList.last() );
00364     is_locked = false;
00365   }
00366 #endif
00367 }
00368 
00369 
00370 bool Modem::dsrOn()
00371 {
00372   int flags;
00373 
00374 
00375   if ( !fd ) {
00376 #ifdef MODEM_DEBUG
00377     fprintf( stderr, "Modem: dsrOn(): File not open.\n" );
00378 #endif
00379     return false;
00380   }
00381 
00382   if ( ioctl( fd, TIOCMGET, &flags ) == -1 ) {
00383 #ifdef MODEM_DEBUG
00384     fprintf( stderr, "Modem: dsrOn(): ioctl() failed.\n" );
00385 #endif
00386     return false;
00387   }
00388 
00389   return ( flags & TIOCM_DSR ) != 0;
00390 }
00391 
00392 
00393 bool Modem::ctsOn()
00394 {
00395   int flags;
00396 
00397 
00398   if ( !fd ) {
00399 #ifdef MODEM_DEBUG
00400     fprintf( stderr, "Modem: ctsOn(): File not open.\n" );
00401 #endif
00402     return false;
00403   }
00404 
00405   if ( ioctl( fd, TIOCMGET, &flags ) == -1) {
00406 #ifdef MODEM_DEBUG
00407     fprintf( stderr, "Modem: ctsOn(): ioctl() failed.\n" );
00408 #endif
00409     return false;
00410   }
00411 
00412   return ( flags & TIOCM_CTS ) != 0;
00413 }
00414 
00415 
00416 void Modem::writeChar( const char c )
00417 {
00418   write( fd, (const void *) &c, 1 );
00419 }
00420 
00421 
00422 void Modem::writeLine( const char *line )
00423 {
00424   kdDebug() << "Modem::writeLine(): " << line << endl;
00425 
00426   write( fd, (const void *) line, strlen( line ) );
00427   writeChar( '\r' );
00428 }
00429 
00430 
00431 void Modem::timerStart( int msec )
00432 {
00433   timer->start( msec, true );
00434 }
00435 
00436 
00437 void Modem::receiveXModem( bool crc )
00438 {
00439   disconnect( sn, 0, this, 0 );
00440   connect( sn, SIGNAL( activated( int ) ), SLOT( readXChar( int ) ) );
00441 
00442   xcrc = crc;
00443 
00444   if ( xcrc ) {
00445     writeChar( 'C' );
00446     xstate = 1;
00447     timerStart( 3000 );
00448   } else {
00449     writeChar( CNAK );
00450     xstate = 5;
00451     timerStart( 10000 );
00452   }
00453 
00454   xblock = 1;
00455 }
00456 
00457 
00458 void Modem::abortXModem()
00459 {
00460   timer->stop();
00461   writeChar( CCAN );
00462   xreset();
00463   emit xmodemDone( false );
00464 }
00465 
00466 
00467 void Modem::timerDone()
00468 {
00469 #ifdef MODEM_DEBUG
00470   fprintf( stderr, "Modem: timeout, xstate = %d.\n", xstate );
00471 #endif
00472 
00473   switch ( xstate ) {
00474     case  0:            /* non-XModem mode  */
00475       emit timeout();
00476       break;
00477 
00478     case  1:            /* 1st 'C' sent     */
00479     case  2:            /* 2nd 'C' sent     */
00480     case  3:            /* 3rd 'C' sent     */
00481       writeChar( 'C' );
00482       xstate++;
00483       timerStart( 1000 );   /* Should be 3000 in original XModem    */
00484       break;
00485 
00486     case  4:            /* 4th 'C' sent     */
00487       xcrc = false;
00488 
00489     case  5:            /* 1st <NAK> sent   */
00490     case  6:            /* 2nd <NAK> sent   */
00491     case  7:            /* 3rd <NAK> sent   */
00492     case  8:            /* 4th <NAK> sent   */
00493     case  9:            /* 5th <NAK> sent   */
00494       writeChar( CNAK );
00495       xstate++;
00496       timerStart( 1000 );   /* Should be 10000 in original XModem   */
00497       break;
00498 
00499     case 10:            /* 6th <NAK> sent   */
00500       xreset();
00501       emit xmodemDone( false );
00502       break;
00503 
00504     default:            /* pending XModem block */
00505       writeChar( CNAK );
00506       xstate = 5;
00507       timerStart( 1000 );   /* Should be 10000 in original XModem   */
00508     }
00509 }
00510 
00511 
00512 void Modem::readChar( int )
00513 {
00514   uchar c;
00515 
00516 
00517   while ( read( fd, (void *) &c, 1 ) == 1 ) {
00518     if ( c == '\n' ) {
00519       buffer[ bufpos ] = 0;
00520       bufpos = 0;
00521       emit gotLine( (const char *) buffer );
00522       break;
00523     } else
00524     if ( ( bufpos < 1000 ) && ( c != '\r' ) )
00525       buffer[ bufpos++ ] = c;
00526   }
00527 }
00528 
00529 
00530 void Modem::readXChar( int )
00531 {
00532   uchar c;
00533   static uchar crc_hi, block, cblock;
00534 
00535 
00536   while ( read( fd, (void *) &c, 1 ) == 1 ) {
00537     switch ( xstate ) {
00538       case  1:  /* 1st 'C' sent     */
00539       case  2:  /* 2nd 'C' sent     */
00540       case  3:  /* 3rd 'C' sent     */
00541       case  4:  /* 4th 'C' sent     */
00542       case  5:  /* 1st <NAK> sent   */
00543       case  6:  /* 2nd <NAK> sent   */
00544       case  7:  /* 3rd <NAK> sent   */
00545       case  8:  /* 4th <NAK> sent   */
00546       case  9:  /* 5th <NAK> sent   */
00547       case 10:  /* 6th <NAK> sent   */
00548     if ( c == CSOH ) {
00549       timerStart( 1000 );
00550       xsize = 128;
00551       xstate = 11;
00552     } else
00553     if ( c == CSTX ) {
00554       timerStart( 1000 );
00555       xsize = 1024;
00556       xstate = 11;
00557         } else
00558     if ( c == CEOT ) {
00559       timer->stop();
00560       writeChar( CACK );
00561       xreset();
00562       emit xmodemDone( true );
00563     } else
00564       timerStart( 1000 );
00565     break;
00566 
00567       case 11:  /* <SOH> or <STX> received   */
00568     timerStart( 1000 );
00569     block = c;
00570     xstate++;
00571     break;
00572 
00573       case 12:  /* block number received    */
00574     timerStart( 1000 );
00575     cblock = c;
00576     xstate++;
00577     bufpos = 0;
00578     break;
00579 
00580       case 13:  /* complement block number received */
00581     timerStart( 1000 );
00582     buffer[ bufpos++ ] = c;
00583     if ( bufpos == xsize ) {
00584       bufpos = 0;
00585       xstate++;
00586       if ( !xcrc )
00587         xstate++;
00588     }
00589     break;
00590 
00591       case 14:  /* data block received  */
00592     timerStart( 1000 );
00593     crc_hi = c;
00594     xstate++;
00595     break;
00596 
00597       case 15:  /* crc high-byte received   */
00598     timerStart( 10000 );
00599     xstate = 4;
00600     if ( (uchar) ( block ^ cblock ) != 0xff ) {
00601       writeChar( CNAK );
00602       break;
00603     }
00604     if ( block+1 == xblock ) {
00605       writeChar( CACK );
00606       break;
00607     }
00608     if ( block != xblock ) {
00609       timer->stop();
00610       writeChar( CCAN );
00611       xreset();
00612       emit xmodemDone( false );
00613       break;
00614     }
00615     if ( xcrc ) {
00616       if ( ( (ushort) crc_hi << 8 | (ushort) c ) != calcCRC() ) {
00617         writeChar( CNAK );
00618         break;
00619       }
00620     } else {
00621       if ( c != calcChecksum() ) {
00622         writeChar( CNAK );
00623         break;
00624       }
00625     }
00626     writeChar( CACK );
00627     xblock++;
00628     emit gotXBlock( buffer, xsize );
00629     break;
00630 
00631       default:
00632     break;
00633     }
00634   }
00635 }
00636 
00637 
00638 void Modem::init()
00639 {
00640   is_locked = false;
00641 
00642   fd = 0;
00643   sn = 0;
00644 
00645   cspeed = B38400;
00646 
00647   // No flow control
00648   cflag = CS8 | CREAD | CLOCAL;
00649   // cflag = CS8 | CREAD | CLOCAL | CRTSCTS;
00650 
00651   bufpos = 0;
00652 }
00653 
00654 
00655 void Modem::xreset()
00656 {
00657   bufpos = 0;
00658 
00659   xstate = 0;
00660   xcrc = false;
00661   xblock = 0;
00662   xsize = 0;
00663 
00664   if ( sn ) {
00665     disconnect( sn, 0, this, 0 );
00666     connect( sn, SIGNAL( activated( int ) ), SLOT( readChar( int ) ) );
00667   }
00668 }
00669 
00670 
00671 uchar Modem::calcChecksum()
00672 {
00673   int i;
00674   uchar c = 0;
00675 
00676 
00677   for ( i = 0; i < xsize; i++ )
00678     c += buffer[ i ];
00679 
00680   return c;
00681 }
00682 
00683 
00684 ushort Modem::calcCRC()
00685 {
00686   int i, j;
00687   ushort c = 0;
00688 
00689     
00690   for ( i = 0; i < xsize; i++ ) {
00691     c ^= (ushort) buffer[ i ] << 8;
00692     for ( j = 0; j < 8; j++ )
00693       if ( c & 0x8000 )
00694         c = c << 1 ^ 0x1021;
00695       else
00696     c <<= 1;
00697   }
00698 
00699   return c;
00700 }
00701 
00702 #include "modem.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys