/* * modm0dev.c: Serial device driver for the modem1 port, dial-in * (/dev/ttyd0) and dial-out (/dev/cua0). * This is free software without any warranty; see the file "COPYING" for * details. * * This file must be compiled with 16-bit integers. * * Author: Thierry Bousch (bousch@suntopo.matups.fr) * Version: 1.0 (oct 93) * * Revision history: * * 0.1 First working version. Very few ioctl's are implemented. * 0.2 Setting the terminal flags (break, echo...) is no longer a * privileged instruction! * Speed can be changed via ioctl's. * We now raise the SIGHUP signal only when writing to a disconnected * /dev/ttyd0, not when reading. * 0.3 Handles MFP error conditions, especially breaks. Fixed a bug in * the RAISE macro (signals should work now). * The dial-in and dial-out devices now have different tty structures, * so they can be configured independently. * The ioctl stuff has been cleaned. * 0.4 We used to check for the carrier (and control characters) only in * cua_read and cua_write, so programs which didn't read from the * standard input couldn't get signals. So we install a daemon which * will check this periodically. * 0.5 Cleaned the cua_read/cua_write/daemon code; now signals can only * be raised by the daemon, using the non-kernel Pkill(). * 0.6 Much cleanup; select() stuff removed; added utility function for * ioctl(FIONREAD/FIONWRITE). Fixed the assembly file also. * Many bugfixes in the daemon code. Now hardware errors are logged * even when the device isn't a controlling terminal. * Added ioctls to read and modify soft_carrier and hangup_control. * Moved code between modm0dev.c and modm0dev.h. * 0.7 ^S/^Q weren't handled by the daemon, bummer. Rewrote this part. * We also keep track of the connection number in a static register, * so that we can track stale FILEPTR's. * Complete rewrite of the mutual exclusion stuff; now there can be * several FILEPTR's to /dev/ttyd0 (useful for talk); we use f->pos * to store the expected connection_id, or 0 for the dial-out device. * Added the dont_emit flag, to inform the interrupt routines that the * terminal has been stopped or restarted (useful if the transmission * buffer is big). * Added support for the O_NDELAY flag, and a sanity check. * The bytes_available() function now returns UNLIMITED for stale * fileptrs, because read/write operations won't block. * More checks in the installation procedure. * 0.8 Added select() support. Increased the time in some sleep-loops. * The main program now simply terminates, instead of being a TSR. * Added (optional) syslog support. Macro-ized some constants. * 0.9 Some tasks (closing the connection, hanging up) are deferred to * the daemon in order to make open/close operations atomic. * Shutdown should work now (the mdm_close() function used to be * interruptible by a signal, thus causing the FILEPTR to be closed * twice, and MiNT crashed with a fatal error). * 1.0 Always release the SEMA_MDM semaphore on hangup, without clearing * p_curr_tty so that we can still send SIGHUPs. * Get rid of the ttyd_opened variable, since mdm_tty.use_cnt contains * the same information. * Clear p_curr_tty on hangup when the signal can't be sent immediately * (we want to kill the current process group, not the future ones * which will control the terminal -- getty should work better now.) */ #define MDM_VERSION "1.0" #include #include #include #include #include #include #include #include "atarierr.h" #include "filesys.h" #include "modm0dev.h" #ifdef USE_SYSLOG #include #endif /* * Constants for ioctl, missing in filesys.h */ #define T_TANDEM 0x1000 #define T_RTSCTS 0x2000 #define T_EVENP 0x4000 /* EVENP and ODDP are mutually exclusive */ #define T_ODDP 0x8000 #define TF_FLAGS 0xF000 /* some flags for TIOC[GS]FLAGS */ #define TF_STOPBITS 0x0003 #define TF_1STOP 0x0001 #define TF_15STOP 0x0002 #define TF_2STOP 0x0003 #define TF_CHARBITS 0x000C #define TF_8BIT 0 #define TF_7BIT 0x4 #define TF_6BIT 0x8 #define TF_5BIT 0xC /* * kernel functions */ #define TGETTIME (*kernel->dos_tab[0x2c]) #define TGETDATE (*kernel->dos_tab[0x2a]) #define YIELD (*kernel->dos_tab[0xff]) #define PGETPID (*kernel->dos_tab[0x10b]) #define PKILL (*kernel->dos_tab[0x111]) #define FSELECT (*kernel->dos_tab[0x11d]) #define PGETEUID (*kernel->dos_tab[0x138]) #define NAP (*kernel->nap) #define SLEEP (*kernel->sleep) #define WAKE (*kernel->wake) #define WAKESELECT (*kernel->wakeselect) /* * Debugging stuff */ #ifdef NDEBUG #define DEBUG(x) #define ALERT(x) #define TRACE(x) #define FATAL(x) #else #define DEBUG(x) (*kernel->debug)x #define ALERT(x) (*kernel->alert)x #define TRACE(x) (*kernel->trace)x #define FATAL(x) (*kernel->fatal)x #endif /* daemon stack size */ #define D_STACK 512L #define spl7() \ ({ register short retvalue; \ __asm__ volatile(" \ movew sr,%0; \ oriw #0x0700,sr " \ : "=d"(retvalue) \ ); retvalue; }) #define spl(N) \ ({ \ __asm__ volatile(" \ movew %0,sr " \ : \ : "d"(N) ); }) /* * Symbols from modm0asm.s */ extern long old_evt_timer; extern long old_txrint, old_rcvint, old_txerror, old_rxerror; extern int dont_emit; void dtr_on(void), dtr_off(void); void rts_on(void), rts_off(void); void carrier_monitor(void); void txrint(void), rcvint(void), txerror(void), rxerror(void); void clear_errors(void), check_errors(void); void setstack(long sp); /* * Forward definitions of the device driver functions */ long cua_open (FILEPTR *f); long cua_write (FILEPTR *f, char *buf, long bytes); long cua_read (FILEPTR *f, char *buf, long bytes); long cua_lseek (FILEPTR *f, long where, int whence); long cua_ioctl (FILEPTR *f, int mode, void *buf); long cua_datime (FILEPTR *f, int *timeptr, int rwflag); long cua_close (FILEPTR *f, int pid); long cua_select (FILEPTR *f, long proc, int mode); void cua_unselect (FILEPTR *f, long proc, int mode); long mdm_open (FILEPTR *f); long mdm_write (FILEPTR *f, char *buf, long bytes); long mdm_read (FILEPTR *f, char *buf, long bytes); long mdm_lseek (FILEPTR *f, long where, int whence); long mdm_ioctl (FILEPTR *f, int mode, void *buf); long mdm_datime (FILEPTR *f, int *timeptr, int rwflag); long mdm_close (FILEPTR *f, int pid); long mdm_select (FILEPTR *f, long proc, int mode); void mdm_unselect (FILEPTR *f, long proc, int mode); DEVDRV mdm_device = { mdm_open, mdm_write, mdm_read, mdm_lseek, mdm_ioctl, mdm_datime, mdm_close, mdm_select, mdm_unselect }; DEVDRV cua_device = { cua_open, cua_write, cua_read, cua_lseek, cua_ioctl, cua_datime, cua_close, cua_select, cua_unselect }; struct tty mdm_tty, cua_tty; struct dev_descr mdm_devinfo = { &mdm_device, 0, O_TTY, &mdm_tty }, cua_devinfo = { &cua_device, 128, O_TTY, &cua_tty }; char rx_buf_start[RX_BUF_LENGTH], *rx_buf_end, *rx_buf_tail; /* * The "volatile" variables can be modified by an interrupt, so the * compiler will not optimize instructions containing them. This is * sometimes a problem: instructions like rx_buf_used-- and * tx_buf_used++ will translate into several assembly instructions, * and if an interrupt occurs inbetween, then we lose. This explains * the hacks in cua_read and cua_write to make them atomic. */ volatile char *rx_buf_head; volatile long rx_buf_used; char tx_buf_start[TX_BUF_LENGTH], *tx_buf_end, *tx_buf_head; volatile char *tx_buf_tail; volatile long tx_buf_used; volatile int hard_carrier; /* * Which device is currently in use ? The "semaphore" variable contains * SEMA_MDM if data have been read from/written to the dial-in device, * SEMA_CUA if the dial-out device is open, and 0 otherwise. Once the * semaphore is owned (i.e. != 0) by one of the devices, it is released * on the last close of this device. */ #define SEMA_NONE 0 #define SEMA_MDM 1 #define SEMA_CUA 2 int semaphore = 0; /* no direction selected yet */ int sema_pid; /* the process owning the semaphore */ /* * Daemon state flags */ #define CLOSING_CONN 0x1 #define HANGING_UP 0x2 int daemon_state = 0; /* * Other global information */ int connection_id = 0; /* initial state, no connection */ struct tty *p_curr_tty = NULL; /* which tty will get signals? */ long _stksize = 2048L; /* 2kb of stack should be enough */ long rx_sel = 0L, tx_sel = 0L; /* selecting processes */ FILEPTR *rx_fsel, *tx_fsel; /* the corresponding FILEPTRs */ int soft_carrier; /* soft carrier flag */ int hangup_control; /* hangup-on-close flag */ int rts_cts; /* RTS/CTS control flag */ long rx_lowmark, rx_highmark; long baudrate; int parity, charstopbits; struct kerinfo *kernel; #if !defined(USE_SYSLOG) && !defined(NDEBUG) int logfile; /* where shall we log errors? */ #endif /* * Various utility macros and functions */ #define ATOMIC(x) \ do { int sr=spl7(); x; spl(sr); } while(0) void flush_rx_buffer (void) { ATOMIC(rx_buf_head = rx_buf_tail; rx_buf_used = 0L); } void flush_tx_buffer (void) { ATOMIC(tx_buf_tail = tx_buf_head; tx_buf_used = 0L); } /* Get the semaphore (if possible) and make some initializations */ int get_semaphore (int sem) { if (semaphore) return semaphore; /* Impossible */ semaphore = sem; sema_pid = PGETPID(); clear_errors(); /* Select the controlling tty structure (for signals) */ p_curr_tty = (semaphore == SEMA_CUA) ? &cua_tty : &mdm_tty; return 0; /* Success */ } /* * Hardware configuration functions: set speed, bits/char, parity, * and break handling. */ void set_baudrate (long speed) { int a; switch (speed) { case 300: a = 9; break; case 600: a = 8; break; case 1200: a = 7; break; case 1800: a = 6; break; case 2400: a = 4; break; case 4800: a = 2; break; case 9600: a = 1; break; case 19200: a = 0; break; default: return; } Rsconf(a,-1,-1,-1,-1,-1); baudrate = speed; } void set_bits (int flags) { unsigned char *ucr = (unsigned char *)(0xfffffa29UL); if (!(flags & TF_STOPBITS)) /* it would mean "synchronous" */ flags |= TF_1STOP; /* assume 1-stopbit mode */ *ucr = (*ucr & 0x87) | (flags << 3); charstopbits = flags; } void set_parity (int flags) { unsigned char *ucr = (unsigned char *)(0xfffffa29UL), tmp; tmp = *ucr & 0xf9; /* clear bits 1 and 2 */ if (flags == T_ODDP) *ucr = tmp | 4; /* odd parity */ else if (flags == T_EVENP) *ucr = tmp | 6; /* even parity */ else { *ucr = tmp; /* no parity */ flags = 0; } parity = flags; } void break_condition (int flag) { unsigned char *tsr = (unsigned char *)(0xfffffa2dUL); *tsr = flag ? (*tsr | 8) : (*tsr & ~8); } /* * Are we connected? We need two macros for that; CONNECTED() has a little * delay, but is synchronous with the daemon. The REALLY_CONNECTED() macro * is only used by the daemon. */ #define REALLY_CONNECTED() (soft_carrier || hard_carrier) #define CONNECTED() (connection_id % 2) /* * Opening the device */ long cua_open (FILEPTR *f) { #ifdef SECURE_OPEN if (PGETEUID()) { DEBUG(("cua_open: only root can open this device")); return EACCDN; } #endif if (get_semaphore(SEMA_CUA)) { DEBUG(("cua_open: serial interface is busy")); return EACCDN; } TRACE(("cua_open: dial-out device opened")); daemon_state |= HANGING_UP; /* f->pos = 0; */ f->flags |= O_TTY; return 0; } long mdm_open (FILEPTR *f) { #ifdef SECURE_OPEN if (PGETEUID()) { DEBUG(("mdm_open: only root can open this device")); return EACCDN; } #endif TRACE(("mdm_open: dial-in device opened")); /* For which connection_id is this fileptr valid ? */ f->pos = CONNECTED() ? connection_id : connection_id+1; f->flags |= O_TTY; return 0; } /* * This is a terminal, thus seek() should always return 0 */ long cua_lseek (FILEPTR *f, long where, int whence) { TRACE(("cua_lseek: returns always 0")); return 0; } #if 0 long mdm_lseek (FILEPTR *f, long where, int whence) { return cua_lseek(f, where, whence); } #else __asm__ (".stabs \"_mdm_lseek\", 5,0,0, _cua_lseek"); #endif /* * Time and date -- always return the current time. Anyway, MiNT * will never call this function since we're a terminal (at least in * the current version). */ long cua_datime (FILEPTR *f, int *timeptr, int rwflag) { if (rwflag) { DEBUG(("cua_datime: can't modify date/time")); return EACCDN; } TRACE(("cua_datime: read time and date")); *timeptr++ = TGETTIME(); *timeptr = TGETDATE(); return 0; } #if 0 long mdm_datime (FILEPTR *f, int *timeptr, int rwflag) { return cua_datime(f, timeptr, rwflag); } #else __asm__ (".stabs \"_mdm_datime\", 5,0,0, _cua_datime"); #endif /* * Closing the device. We shouldn't hang-up straight away, or data still * in the buffer could be lost. */ long cua_close (FILEPTR *f, int pid) { if (f->links == 0) { TRACE(("cua_close: closing dial-out device")); p_curr_tty = NULL; semaphore = 0; daemon_state |= (CLOSING_CONN|HANGING_UP); } return 0; } long mdm_close (FILEPTR *f, int pid) { if (p_curr_tty == &mdm_tty && mdm_tty.use_cnt == 0) { TRACE(("mdm_close: closing dial-in device")); p_curr_tty = NULL; semaphore = 0; daemon_state |= CLOSING_CONN; if (hangup_control) daemon_state |= HANGING_UP; } return 0; } /* * Utility function: returns the number of bytes which can be read from * or written to this device without blocking. */ long bytes_available (FILEPTR *f, int mode) { if (daemon_state) return 0; /* daemon is busy */ if (f->pos) { if (semaphore == SEMA_CUA) return 0; /* we don't own the stream */ if (f->pos > connection_id) return 0; /* no connection yet */ if (f->pos < connection_id) return UNLIMITED; /* stale fileptr */ } return (mode==FIONREAD) ? rx_buf_used : (TX_BUF_LENGTH-tx_buf_used); } /* * Selecting and unselecting one of the devices. * BUG: there can be only one select'ing process in each direction; this * means that you cannot simultaneously select on the dial-in and dial-out * devices; fortunately, getty doesn't use select. */ long cua_select (FILEPTR *f, long proc, int mode) { TRACE(("cua_select: mode %d", mode)); if (mode == O_WRONLY) { if (tx_sel || bytes_available(f,FIONWRITE)) return 1; TRACE(("cua_select: going to sleep -- can't write")); tx_sel = proc; tx_fsel = f; return 0; } if (mode == O_RDONLY) { if (rx_sel || bytes_available(f,FIONREAD)) return 1; TRACE(("cua_select: going to sleep -- can't read")); rx_sel = proc; rx_fsel = f; return 0; } DEBUG(("cua_select: invalid mode")); return EINVFN; } #if 0 long mdm_select (FILEPTR *f, long proc, int mode) { return cua_select(f, proc, mode); } #else __asm__ (".stabs \"_mdm_select\", 5,0,0, _cua_select"); #endif void cua_unselect (FILEPTR *f, long proc, int mode) { TRACE(("cua_unselect: mode %d", mode)); if (mode == O_WRONLY) tx_sel = 0L; if (mode == O_RDONLY) rx_sel = 0L; } #if 0 void mdm_unselect (FILEPTR *f, long proc, int mode) { return cua_unselect(f, proc, mode); } #else __asm__ (".stabs \"_mdm_unselect\", 5,0,0, _cua_unselect"); #endif /* * Reading from / writing to the device. * * Since it is a terminal, we know that "bytes" will always be a multiple * of four; but we check it anyway. */ long cua_read (FILEPTR *f, char *buf, long bytes) { long left=bytes; char c; if (bytes<0 || bytes%4) { DEBUG(("cua_read: bytes out of range")); return ERANGE; } TRACE(("cua_read: read %ld bytes", bytes)); while (left) { if (daemon_state != 0) { TRACE(("mdm_read: the daemon has something to do")); if (f->flags & O_NDELAY) break; NAP(100); continue; } if (f->pos) { /* dial-in fileptr? */ if (semaphore == SEMA_CUA) { TRACE(("mdm_read: dial-out device in use")); if (f->flags & O_NDELAY) break; NAP(1000); continue; } /* Too early? */ if (f->pos > connection_id) { TRACE(("mdm_read: no connection yet")); if (f->flags & O_NDELAY) break; NAP(100); continue; } /* Too late? */ if (f->pos < connection_id) { DEBUG(("mdm_read: stale fileptr")); break; } /* All OK! grab the semaphore if possible */ get_semaphore(SEMA_MDM); } /* Wait for data */ if (rx_buf_used == 0) { TRACE(("cua_read: no data present in buffer")); if (f->flags & O_NDELAY) break; NAP(100); continue; } c = *rx_buf_tail++; if (rx_buf_tail == rx_buf_end) rx_buf_tail = rx_buf_start; *buf++ = 0; *buf++ = 0; *buf++ = 0; *buf++ = c; #if 0 ATOMIC(rx_buf_used--); #else __asm__ volatile ("subql #1, _rx_buf_used"); #endif left -= 4; /* four bytes read */ } return bytes - left; } #if 0 long mdm_read (FILEPTR *f, char *buf, long bytes) { return cua_read(f, buf, bytes); } #else __asm__ (".stabs \"_mdm_read\", 5,0,0, _cua_read"); #endif long cua_write (FILEPTR *f, char *buf, long bytes) { long left=bytes; char c; if (bytes<0 || bytes%4) { DEBUG(("cua_write: bytes out of range")); return ERANGE; } TRACE(("cua_write: write %ld bytes", bytes)); while (left) { if (daemon_state != 0) { TRACE(("mdm_write: the daemon has something to do")); if (f->flags & O_NDELAY) break; NAP(100); continue; } if (f->pos) { /* dial-in fileptr? */ if (semaphore == SEMA_CUA) { TRACE(("mdm_write: dial-out device in use")); if (f->flags & O_NDELAY) break; NAP(1000); continue; } /* Too early? */ if (f->pos > connection_id) { TRACE(("mdm_write: no connection yet")); if (f->flags & O_NDELAY) break; NAP(100); continue; } /* Too late? */ if (f->pos < connection_id) { DEBUG(("mdm_write: stale fileptr")); break; } /* All OK! grab the semaphore if possible */ get_semaphore(SEMA_MDM); } /* Wait to transmit data */ if (tx_buf_used == TX_BUF_LENGTH) { TRACE(("cua_write: no room left in buffer")); if (f->flags & O_NDELAY) break; NAP(100); continue; } buf += 3; /* skip first three bytes */ c = *buf++; /* this one only is significant */ *tx_buf_head++ = c; if (tx_buf_head == tx_buf_end) tx_buf_head = tx_buf_start; #if 0 ATOMIC(tx_buf_used++); #else __asm__ volatile("addql #1, _tx_buf_used"); #endif left -= 4; /* four bytes written */ } return bytes - left; } #if 0 long mdm_write (FILEPTR *f, char *buf, long bytes) { return cua_write(f, buf, bytes); } #else __asm__ (".stabs \"_mdm_write\", 5,0,0, _cua_write"); #endif /* * Ioctl operations: we can flush the buffers or get/set the baudrate */ long cua_ioctl (FILEPTR *f, int mode, void *buf) { long free; long new_baudrate; int flags, new_flags; if (mode == FIONREAD || mode == FIONWRITE) { free = bytes_available(f,mode); TRACE(("cua_ioctl(0x%x) returned %ld", mode, free)); *(long *)buf = free; } else if (mode == TIOCFLUSH) { TRACE(("cua_ioctl(TIOCFLUSH): clear buffers")); flush_rx_buffer(); flush_tx_buffer(); } else if (mode == TIOCIBAUD || mode == TIOCOBAUD) { new_baudrate = *(long *)buf; *(long *)buf = baudrate; TRACE(("cua_ioctl(TIOC?BAUD): was %ld now %ld", baudrate, new_baudrate)); if (new_baudrate == 0) daemon_state |= HANGING_UP; else if (new_baudrate != baudrate && PGETEUID() == 0) set_baudrate(new_baudrate); } else if (mode == TIOCCBRK) { TRACE(("cua_ioctl(TIOCCBRK): clear break condition")); break_condition(0); } else if (mode == TIOCSBRK) { TRACE(("cua_ioctl(TIOCSBRK): set break condition")); break_condition(1); } else if (mode == TIOCGFLAGS || mode == TIOCSFLAGS) { flags = parity | charstopbits; if (rts_cts) flags |= T_RTSCTS; new_flags = *(int *)buf; *(int *)buf = flags; TRACE(("cua_ioctl(TIOCGFLAGS): flags were 0x%02x", flags)); if (mode == TIOCSFLAGS && PGETEUID() == 0) { TRACE(("cua_ioctl(TIOCSFLAGS): new flags are 0x%02x", new_flags)); set_bits(new_flags & (TF_CHARBITS|TF_STOPBITS)); set_parity(new_flags & (T_ODDP|T_EVENP)); rts_cts = !!(new_flags & T_RTSCTS); } } #ifdef NON_OFFICIAL_IOCTLS else if (mode == TIOCGHUPCL || mode == TIOCSHUPCL) { new_flags = *(int *)buf; *(int *)buf = hangup_control; TRACE(("cua_ioctl(TIOCGHUPCL) returned %d", hangup_control)); if (mode == TIOCSHUPCL && PGETEUID() == 0) { hangup_control = !!new_flags; TRACE(("cua_ioctl(TIOCSHUPCL) is now %d", hangup_control)); } } else if (mode == TIOCGSOFTCAR || mode == TIOCSSOFTCAR) { new_flags = *(int *)buf; *(int *)buf = soft_carrier; TRACE(("cua_ioctl(TIOCGSOFTCAR) returned %d", soft_carrier)); if (mode == TIOCSSOFTCAR && PGETEUID() == 0) { soft_carrier = !!new_flags; TRACE(("cua_ioctl(TIOCSSOFTCAR) is now %d", soft_carrier)); } } #endif else { TRACE(("cua_ioctl(0x%x): invalid mode", mode)); return EINVFN; } return 0; } #if 0 long mdm_ioctl (FILEPTR *f, int mode, void *buf) { return cua_ioctl(f, mode, buf); } #else __asm__ (".stabs \"_mdm_ioctl\", 5,0,0, _cua_ioctl"); #endif /* * Hardware error handlers, invoked by check_errors(). Note that they are * called by the daemon process, so they can only use non-kernel functions. */ #define Raise_And_Flush(sig) \ do { \ flush_rx_buffer(); \ flush_tx_buffer(); \ if (p_curr_tty && p_curr_tty->pgrp) { \ p_curr_tty->state &= ~TS_HOLD; \ (void) Pkill(-(p_curr_tty->pgrp),(int)(sig)); } \ } while(0) /* * Syslog(pri,s): logs message "s" with priority "pri"; this parameter is * ignored if USE_SYSLOG is not defined. */ #if defined(USE_SYSLOG) #define Syslog(pri,s) \ do { \ openlog(D_NAME,0,LOG_DAEMON); \ syslog(pri, s); \ closelog(); \ } while(0) #elif defined(NDEBUG) #define Syslog(pri,s) /*nothing*/ #else #define Syslog(pri,s) \ Fwrite(logfile,strlen(s)+17,"SERIAL LINE 1: " s "\r\n") #endif void Underrun_Error (void) { /* * This is hardly an error, it just means that characters * are not transmitted at full speed. So we do nothing. */ } void Overrun_Error (void) { /* * Reception error: a character received hasn't been treated fast * enough, and has been overwritten by the next one. * A serious error, but it doesn't deserve a signal. */ Syslog(LOG_ERROR, "Overrun error"); } void Parity_Error (void) { /* * A character with bad parity has been received. Probably * a bad modem configuration. */ Syslog(LOG_ERROR, "Parity error"); } void Frame_Error (void) { /* * A non-null character with a null stop-bit has been * received. Probably a bad modem configuration. */ Syslog(LOG_ERROR, "Frame error"); } void Break_Detected (void) { /* * Not an error, rather a "signal" sent by the remote * user (a null character with a null stop-bit). It should * flush the buffers and raise the SIGINT signal. */ Syslog(LOG_NOTICE, "Break condition detected"); Raise_And_Flush(SIGINT); } void Buffer_Overflow (void) { /* * The reception buffer has wrapped around; this is a serious * error, and should not happen if you use RTS/CTS. * Maybe we should flush the rx buffer, since the data in it * is likely to be corrupted. */ Syslog(LOG_ERROR, "Buffer overflow"); } /* * This routine is called periodically (every 200 ms) by the daemon process. * It should check for hardware errors (and report them, maybe even raise * a signal), pending interrupt characters in the buffer (^C, ^S, etc.), * wake up selecting processes if necessary. * * According to the flags in daemon_state, it should also be able to wait * for a connection to terminate, and to hang-up. */ void check_signals (void) { char c, *p, *end; int sig; /* Check for hardware errors */ check_errors(); /* Check for connection changes */ if (semaphore != SEMA_CUA) { if (!CONNECTED() && REALLY_CONNECTED()) { Syslog(LOG_NOTICE, "Communication established"); ++connection_id; } else if (CONNECTED() && !REALLY_CONNECTED()) { Syslog(LOG_NOTICE, "Communication lost"); ++connection_id; } } if (!CONNECTED() && p_curr_tty == &mdm_tty) { semaphore = 0; if (mdm_tty.pgrp) { /* Kill the controlled process group */ Raise_And_Flush(SIGHUP); } else { /* If we can't send signal now, never try again */ p_curr_tty = NULL; } } /* Is this a controlling terminal in cooked mode? */ if (rx_buf_used && p_curr_tty && p_curr_tty->pgrp && (p_curr_tty->state & TS_COOKED)) { /* Now check the rx buffer for control characters */ p = rx_buf_tail; end = (char *)rx_buf_head; /* to make GCC happy */ while (p < end) { c = *p; sig = 0; if (c == p_curr_tty->tc.t_intrc) sig = SIGINT; else if (c == p_curr_tty->tc.t_quitc) sig = SIGQUIT; else if (c == p_curr_tty->ltc.t_suspc) sig = SIGTSTP; else if (c == p_curr_tty->tc.t_startc) p_curr_tty->state &= ~TS_HOLD; else if (c == p_curr_tty->tc.t_stopc) p_curr_tty->state |= TS_HOLD; if (sig) { Raise_And_Flush(sig); break; } if (++p == rx_buf_end) p = rx_buf_start; } } /* Update the hardware transmission flag */ dont_emit = (p_curr_tty ? !!(p_curr_tty->state & TS_HOLD) : 0); /* Wake up the selecting processes, if any */ if (tx_sel && bytes_available(tx_fsel,FIONWRITE)) { WAKESELECT(tx_sel); tx_sel = 0L; } if (rx_sel && bytes_available(rx_fsel,FIONREAD)) { WAKESELECT(rx_sel); rx_sel = 0L; } /* Have we got something to do? */ if (daemon_state & CLOSING_CONN) { /* Wait for all bytes to be transmitted */ dont_emit = 0; /* paranoia */ if (tx_buf_used == 0) { daemon_state &= ~CLOSING_CONN; flush_rx_buffer(); break_condition(0); } } else if (daemon_state & HANGING_UP) { /* Hangup for 1.2 second */ dtr_off(); (void)Fselect(1200,0L,0L,0L); dtr_on(); flush_rx_buffer(); flush_tx_buffer(); break_condition(0); daemon_state &= ~HANGING_UP; } /* Pooh, that was a lot of job! */ } /* * Installs all the interrupt routines, and initializes the RS232 port. */ void install_things (void) { int sr; long old_ssp; old_ssp = Super(0L); sr = spl7(); /* First, initialize the buffers */ rx_buf_head = rx_buf_tail = rx_buf_start; tx_buf_tail = tx_buf_head = tx_buf_start; rx_buf_end = rx_buf_start + RX_BUF_LENGTH; tx_buf_end = tx_buf_start + TX_BUF_LENGTH; rx_lowmark = RX_BUF_LENGTH * 1/8; rx_highmark = RX_BUF_LENGTH * 7/8; /* Then, install the interrupt routines */ Jdisint(2); /* MFP interrupt #2 disabled */ old_evt_timer = (long) Setexc(0x100, carrier_monitor); old_txerror = (long)Setexc(0x49, txerror); /* MFP interrupt #9 */ old_txrint = (long)Setexc(0x4A, txrint); /* MFP interrupt #10 */ old_rxerror = (long)Setexc(0x4B, rxerror); /* MFP interrupt #11 */ old_rcvint = (long)Setexc(0x4C, rcvint); /* MFP interrupt #12 */ /* Initialize the serial port */ set_baudrate(INITIAL_SPEED); set_bits(INITIAL_FLAGS & (TF_CHARBITS|TF_STOPBITS)); set_parity(INITIAL_FLAGS & (T_ODDP|T_EVENP)); rts_cts = !!(INITIAL_FLAGS & T_RTSCTS); soft_carrier = INITIAL_SOFTCAR; hangup_control = INITIAL_HUPCL; break_condition(0); spl(sr); Super(old_ssp); } /* Copyright information */ void Version (void) { Cconws("Serial port 1 device driver (version " MDM_VERSION ", compiled " __DATE__ ") by T.Bousch\r\n" "This program is FREE SOFTWARE, and comes with NO WARRANTY.\r\n" "Read the file COPYING for more information.\r\n" #ifdef USE_SYSLOG "SYSLOG version\r\n" #endif ); } /* * The daemon process; it executes in non-kernel mode, so it cannot use * kernel functions (apart from WAKESELECT). */ void modm0_daemon (long bp) { setstack(bp + D_STACK - 16L); /* abandon 16 bytes for security */ Psigblock(0x7fffffffUL); /* mask all maskable signals */ #if !defined(USE_SYSLOG) && !defined(NDEBUG) logfile = Fopen(LOGFILE, O_WRONLY); #endif Super(0L); while(1) { (void)Fselect(200, 0L, 0L, 0L); /* sleep 200 ms */ check_signals(); } } /* * Main routine. It would be nice if we could pass arguments on the command * line, but it's not possible if we include , and we don't want * to add ~15kb to the code only for that. If you don't like the default * settings, change them in modm0dev.h and recompile. Or use stty. */ int main() { BASEPAGE *b; if (Syield() == EINVFN) { Cconws("modm0dev: MiNT is not running\r\n"); return EACCDN; } if (!Fsfirst(MDM_DEVNAME,0) || !Fsfirst(CUA_DEVNAME,0)) { Cconws("modm0dev: device already installed\r\n"); return EACCDN; } /* * The daemon won't work properly if not super-user */ if (Pgeteuid()) { Cconws("modm0dev: must be root to execute this program\r\n"); return EACCDN; } kernel = (struct kerinfo *) Dcntl(DEV_INSTALL, MDM_DEVNAME, &mdm_devinfo); if ((long)kernel <= 0L) { Cconws("modm0dev: can't install " MDM_DEVNAME "\r\n"); return EACCDN; } if (Dcntl(DEV_INSTALL, CUA_DEVNAME, &cua_devinfo) <= 0L) { Cconws("modm0dev: can't install " CUA_DEVNAME "\r\n"); Fdelete(MDM_DEVNAME); return EACCDN; } /* * Install the daemon (just like in Stephen Henson's Minixfs). * If something goes wrong, it is probably an out of memory * error. */ b = (BASEPAGE *) Pexec(5, 0L, "", 0L); if ((long)b <= 0L) goto no_memory; Mshrink(b, D_STACK); b->p_tbase = (char *)modm0_daemon; /* start address */ b->p_hitpa = (char *)b + D_STACK; /* top of block */ if (Pexec(104, D_NAME, b, 0L) < 0L) goto no_memory; Version(); install_things(); #if 1 /* There's no need to keep the program resident, since all its * memory is also owned by the daemon. So we simply exit. */ return 0; #else Ptermres(256L + _base->p_tlen + _base->p_dlen + _base->p_blen, 0); #endif /* Fall through if Ptermres() failed */ no_memory: Cconws("modm0dev: running out of memory\r\n"); Fdelete(MDM_DEVNAME); Fdelete(CUA_DEVNAME); return ENSMEM; }