patch-1.3.83 linux/drivers/char/pcxx.c

Next file: linux/drivers/char/pcxx.h
Previous file: linux/drivers/char/fep.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.82/linux/drivers/char/pcxx.c linux/drivers/char/pcxx.c
@@ -0,0 +1,1961 @@
+/*
+ *  linux/drivers/char/pcxe.c
+ * 
+ *  Written by Troy De Jongh, November, 1994
+ *
+ *  Copyright (C) 1994,1995 Troy De Jongh
+ *  This software may be used and distributed according to the terms 
+ *  of the GNU Public License.
+ *
+ *  This driver is for the DigiBoard PC/Xe and PC/Xi line of products.
+ *
+ *  This driver does NOT support DigiBoard's fastcook FEP option and
+ *  does not support the transparent print (i.e. digiprint) option.
+ *
+ *  Please email any suggestions or bug reports to troyd@skypoint.com
+ *
+ *
+ *  January 1996 Bug fixes by an unknown author and released as 1.5.2
+ *  1.5.3 March 9, 1996 Christoph Lameter: Fixed 115.2K Support. Memory
+ *		allocation harmonized with 1.3.X Series.
+ *  1.5.4 March 30, 1996 Christoph Lameter: Fixup for 1.3.81. Use init_bh
+ *		instead of direct assigment to kernel arrays.
+ *
+ */
+
+#undef SPEED_HACK
+/* If you define SPEED_HACK then you get the following Baudrate translation
+   19200 = 57600
+   38400 = 115K
+   The driver supports the native 57.6K and 115K Baudrates under Linux, but
+   some distributions like Slackware 3.0 dont like these high baudrates.
+*/
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/malloc.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+
+#define VERSION 	"1.5.4"
+/*#define	DEFAULT_HW_FLOW	1 */
+/*#define	DEBUG_IOCTL */
+
+#include "digi.h"
+#include "fep.h"
+#include "pcxx.h"
+#include "digi_fep.h"
+#include "digi_bios.h"
+#include "pcxxconfig.h"
+
+#define DIGIMAJOR   30
+#define DIGICUMAJOR 31 
+
+#define MAXPORTS 16     /* Max ports per PC/Xx type board */
+#define NBDEVS   (NUMCARDS * MAXPORTS)
+#define PORTNUM(x)  ((x)->dev % MAXPORTS)
+#define LINENUM(x)  (MINOR((x)->device) - (x)->driver.minor_start)
+
+int pcxx_ncook=sizeof(pcxx_cook);
+int pcxx_nbios=sizeof(pcxx_bios);
+
+struct channel digi_channels[NBDEVS];
+
+#define MIN(a,b)	((a) < (b) ? (a) : (b))
+#define pcxxassert(x, msg)  if(!(x)) pcxx_error(__LINE__, msg)
+
+#define FEPTIMEOUT 200000  
+#define SERIAL_TYPE_NORMAL	1
+#define SERIAL_TYPE_CALLOUT	2
+#define PCXE_EVENT_HANGUP   1
+
+struct tty_driver pcxe_driver;
+struct tty_driver pcxe_callout;
+static int pcxe_refcount;
+
+DECLARE_TASK_QUEUE(tq_pcxx);
+static struct tty_struct *pcxe_table[NBDEVS];
+static struct termios *pcxe_termios[NBDEVS];
+static struct termios *pcxe_termios_locked[NBDEVS];
+
+static void pcxxpoll(void);
+static void pcxxdelay(int);
+static void fepcmd(struct channel *, int, int, int, int, int);
+static void pcxe_put_char(struct tty_struct *, unsigned char);
+static void pcxe_flush_chars(struct tty_struct *);
+static void pcxx_error(int, char *);
+static void pcxe_close(struct tty_struct *, struct file *);
+static int pcxe_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);
+static void pcxe_set_termios(struct tty_struct *, struct termios *);
+static int pcxe_write(struct tty_struct *, int, const unsigned char *, int);
+static int pcxe_write_room(struct tty_struct *);
+static int pcxe_chars_in_buffer(struct tty_struct *);
+static void pcxe_flush_buffer(struct tty_struct *);
+static void doevent(int);
+static void receive_data(struct channel *);
+static void pcxxparam(struct tty_struct *, struct channel *ch);
+static void do_softint(void *);
+static inline void pcxe_sched_event(struct channel *, int);
+static void do_pcxe_bh(void);
+static void pcxe_start(struct tty_struct *);
+static void pcxe_stop(struct tty_struct *);
+static void pcxe_throttle(struct tty_struct *);
+static void pcxe_unthrottle(struct tty_struct *);
+static void digi_send_break(struct channel *ch, int msec);
+static void shutdown(struct channel *);
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch);
+static inline void memwinon(struct board_info *b, unsigned int win);
+static inline void memwinoff(struct board_info *b, unsigned int win);
+static inline void globalwinon(struct channel *ch);
+static inline void rxwinon(struct channel *ch);
+static inline void txwinon(struct channel *ch);
+static inline void memoff(struct channel *ch);
+static inline void assertgwinon(struct channel *ch);
+static inline void assertmemoff(struct channel *ch);
+
+#define TZ_BUFSZ 4096
+
+static inline struct channel *chan(register struct tty_struct *tty)
+{
+	if (tty) {
+		register struct channel *ch=(struct channel *)tty->driver_data;
+		if ((ch >= &digi_channels[0]) && (ch < &digi_channels[NBDEVS])) {
+			if (ch->magic==PCXX_MAGIC)
+				return ch;
+		}
+	}
+	return NULL;
+}
+
+/* These inline routines are to turn board memory on and off */
+static inline void memwinon(struct board_info *b, unsigned int win)
+{
+	if(b->type == PCXEVE)
+		outb_p(FEPWIN|win, b->port+1);
+	else
+		outb_p(inb(b->port)|FEPMEM, b->port);
+}
+
+static inline void memwinoff(struct board_info *b, unsigned int win)
+{
+	outb_p(inb(b->port)&~FEPMEM, b->port);
+	if(b->type == PCXEVE)
+		outb_p(0, b->port + 1);
+}
+
+static inline void globalwinon(struct channel *ch)
+{
+	if(ch->board->type == PCXEVE)
+		outb_p(FEPWIN, ch->board->port+1);
+	else
+		outb_p(FEPMEM, ch->board->port);
+}
+
+static inline void rxwinon(struct channel *ch)
+{
+	if(ch->rxwin == 0)
+		outb_p(FEPMEM, ch->board->port);
+	else 
+		outb_p(ch->rxwin, ch->board->port+1);
+}
+
+static inline void txwinon(struct channel *ch)
+{
+	if(ch->txwin == 0)
+		outb_p(FEPMEM, ch->board->port);
+	else
+		outb_p(ch->txwin, ch->board->port+1);
+}
+
+static inline void memoff(struct channel *ch)
+{
+	outb_p(0, ch->board->port);
+	if(ch->board->type == PCXEVE)
+		outb_p(0, ch->board->port+1);
+}
+
+static inline void assertgwinon(struct channel *ch)
+{
+	if(ch->board->type != PCXEVE)
+		pcxxassert(inb(ch->board->port) & FEPMEM, "Global memory off");
+}
+
+static inline void assertmemoff(struct channel *ch)
+{
+	if(ch->board->type != PCXEVE)
+		pcxxassert(!(inb(ch->board->port) & FEPMEM), "Memory on");
+}
+
+static inline void pcxe_sched_event(struct channel *info, int event)
+{
+	info->event |= 1 << event;
+	queue_task_irq_off(&info->tqueue, &tq_pcxx);
+	mark_bh(DIGI_BH);
+}
+
+static void pcxx_error(int line, char *msg)
+{
+	printk("pcxx_error (DigiBoard): line=%d %s\n", line, msg);
+}
+
+static int pcxx_waitcarrier(struct tty_struct *tty,struct file *filp,struct channel *info)
+{
+	struct wait_queue wait = { current, NULL };
+	int	retval = 0;
+	int	do_clocal = 0;
+
+	if (info->asyncflags & ASYNC_CALLOUT_ACTIVE) {
+		if (info->normal_termios.c_cflag & CLOCAL)
+			do_clocal = 1;
+	} else {
+		if (tty->termios->c_cflag & CLOCAL)
+			do_clocal = 1;
+	}
+
+	/*
+	 * Block waiting for the carrier detect and the line to become free
+	 */
+
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+	info->count--;
+	info->blocked_open++;
+
+	for (;;) {
+		cli();
+		if ((info->asyncflags & ASYNC_CALLOUT_ACTIVE) == 0) {
+			globalwinon(info);
+			info->omodem |= DTR|RTS;
+			fepcmd(info, SETMODEM, DTR|RTS, 0, 10, 1);
+			memoff(info);
+		}
+		sti();
+		current->state = TASK_INTERRUPTIBLE;
+		if(tty_hung_up_p(filp) || (info->asyncflags & ASYNC_INITIALIZED) == 0) {
+			if(info->asyncflags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;	
+			break;
+		}
+		if ((info->asyncflags & ASYNC_CALLOUT_ACTIVE) == 0 &&
+		    (info->asyncflags & ASYNC_CLOSING) == 0 &&
+			(do_clocal || (info->imodem & info->dcd)))
+			break;
+		if(current->signal & ~current->blocked) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&info->open_wait, &wait);
+
+	if(!tty_hung_up_p(filp))
+		info->count++;
+	info->blocked_open--;
+
+	return retval;
+}	
+
+
+/* static  ???why static??? */
+int pcxe_open(struct tty_struct *tty, struct file * filp)
+{
+	volatile struct board_chan *bc;
+	struct channel *ch;
+	unsigned long flags;
+	int line;
+	int boardnum;
+	int retval;
+
+	line = MINOR(tty->device) - tty->driver.minor_start;
+	if(line < 0 || line >= NBDEVS) {
+		printk("line out of range in pcxe_open\n");
+		tty->driver_data = NULL;
+		return(-ENODEV);
+	}
+
+	boardnum = line / 16;
+	if(boardnum >= NUMCARDS || boards[boardnum].status == DISABLED ||
+								(line % MAXPORTS) >= boards[boardnum].numports) {
+		tty->driver_data = NULL;   /* Mark this device as 'down' */
+		return(-ENODEV);
+	}
+	
+	ch = &digi_channels[line];
+
+	if(ch->brdchan == 0) {
+		tty->driver_data = NULL;
+		return(-ENODEV);
+	}
+
+	/*
+	 * If the device is in the middle of being closed, then block
+	 * until it's done, and then try again.
+	 */
+	if(ch->asyncflags & ASYNC_CLOSING) {
+		interruptible_sleep_on(&ch->close_wait);
+		if(ch->asyncflags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+
+	save_flags(flags);
+	cli();
+	ch->count++;
+	tty->driver_data = ch;
+	ch->tty = tty;
+
+	if ((ch->asyncflags & ASYNC_INITIALIZED) == 0) {
+		unsigned int head;
+
+		globalwinon(ch);
+		ch->statusflags = 0;
+		bc=ch->brdchan;
+		ch->imodem = bc->mstat;
+		head = bc->rin;
+		bc->rout = head;
+		ch->tty = tty;
+		pcxxparam(tty,ch);
+		ch->imodem = bc->mstat;
+		bc->idata = 1;
+		ch->omodem = DTR|RTS;
+		fepcmd(ch, SETMODEM, DTR|RTS, 0, 10, 1);
+		memoff(ch);
+		ch->asyncflags |= ASYNC_INITIALIZED;
+	}
+	restore_flags(flags);
+
+	if(ch->asyncflags & ASYNC_CLOSING) {
+		interruptible_sleep_on(&ch->close_wait);
+		if(ch->asyncflags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+	/*
+	 * If this is a callout device, then just make sure the normal
+	 * device isn't being used.
+	 */
+	if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) {
+		if (ch->asyncflags & ASYNC_NORMAL_ACTIVE)
+			return -EBUSY;
+		if (ch->asyncflags & ASYNC_CALLOUT_ACTIVE) {
+			if ((ch->asyncflags & ASYNC_SESSION_LOCKOUT) &&
+		    		(ch->session != current->session))
+			    return -EBUSY;
+			if((ch->asyncflags & ASYNC_PGRP_LOCKOUT) &&
+			    (ch->pgrp != current->pgrp))
+			    return -EBUSY;
+		}
+		ch->asyncflags |= ASYNC_CALLOUT_ACTIVE;
+	}
+	else {
+		if (filp->f_flags & O_NONBLOCK) {
+			if(ch->asyncflags & ASYNC_CALLOUT_ACTIVE)
+				return -EBUSY;
+		}
+		else {
+			if ((retval = pcxx_waitcarrier(tty, filp, ch)) != 0)
+				return retval;
+		}
+		ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+	}
+ 	
+	save_flags(flags);
+	cli();
+	if((ch->count == 1) && (ch->asyncflags & ASYNC_SPLIT_TERMIOS)) {
+		if(tty->driver.subtype == SERIAL_TYPE_NORMAL)
+			*tty->termios = ch->normal_termios;
+		else 
+			*tty->termios = ch->callout_termios;
+		globalwinon(ch);
+		pcxxparam(tty,ch);
+		memoff(ch);
+	}
+
+	ch->session = current->session;
+	ch->pgrp = current->pgrp;
+	restore_flags(flags);
+	return 0;
+} 
+
+static void shutdown(struct channel *info)
+{
+	unsigned long flags;
+	volatile struct board_chan *bc;
+	struct tty_struct *tty;
+
+	if (!(info->asyncflags & ASYNC_INITIALIZED)) 
+		return;
+
+	save_flags(flags);
+	cli();
+	globalwinon(info);
+
+	bc = info->brdchan;
+	if(bc)
+		bc->idata = 0;
+
+	tty = info->tty;
+
+	/*
+	 * If we're a modem control device and HUPCL is on, drop RTS & DTR.
+	 */
+	if(tty->termios->c_cflag & HUPCL) {
+		info->omodem &= ~(RTS|DTR);
+		fepcmd(info, SETMODEM, 0, DTR|RTS, 10, 1);
+	}
+
+	memoff(info);
+	info->asyncflags &= ~ASYNC_INITIALIZED;
+	restore_flags(flags);
+}
+
+
+static void pcxe_close(struct tty_struct * tty, struct file * filp)
+{
+	struct channel *info;
+
+	if ((info=chan(tty))!=NULL) {
+		unsigned long flags;
+		save_flags(flags);
+		cli();
+
+		if(tty_hung_up_p(filp)) {
+			restore_flags(flags);
+			return;
+		}
+		if (info->count-- > 1) {
+			restore_flags(flags);
+			return;
+		}
+		if (info->count < 0) {
+			info->count = 0;
+		}
+
+		info->asyncflags |= ASYNC_CLOSING;
+	
+		/*
+		* Save the termios structure, since this port may have
+		* separate termios for callout and dialin.
+		*/
+		if(info->asyncflags & ASYNC_NORMAL_ACTIVE)
+			info->normal_termios = *tty->termios;
+		if(info->asyncflags & ASYNC_CALLOUT_ACTIVE)
+			info->callout_termios = *tty->termios;
+		tty->closing = 1;
+		if(info->asyncflags & ASYNC_INITIALIZED) {
+			setup_empty_event(tty,info);		
+			tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+		}
+	
+		if(tty->driver.flush_buffer)
+			tty->driver.flush_buffer(tty);
+		if(tty->ldisc.flush_buffer)
+			tty->ldisc.flush_buffer(tty);
+		shutdown(info);
+		tty->closing = 0;
+		info->event = 0;
+		info->tty = NULL;
+		if(tty->ldisc.num != ldiscs[N_TTY].num) {
+			if(tty->ldisc.close)
+				(tty->ldisc.close)(tty);
+			tty->ldisc = ldiscs[N_TTY];
+			tty->termios->c_line = N_TTY;
+			if(tty->ldisc.open)
+				(tty->ldisc.open)(tty);
+		}
+		if(info->blocked_open) {
+			if(info->close_delay) {
+				current->state = TASK_INTERRUPTIBLE;
+				current->timeout = jiffies + info->close_delay;
+				schedule();
+			}
+			wake_up_interruptible(&info->open_wait);
+		}
+		info->asyncflags &= ~(ASYNC_NORMAL_ACTIVE|
+							  ASYNC_CALLOUT_ACTIVE|ASYNC_CLOSING);
+		wake_up_interruptible(&info->close_wait);
+		restore_flags(flags);
+	}
+}
+
+
+void pcxe_hangup(struct tty_struct *tty)
+{
+	struct channel *ch;
+
+	if ((ch=chan(tty))!=NULL) {
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+		shutdown(ch);
+		ch->event = 0;
+		ch->count = 0;
+		ch->tty = NULL;
+		ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
+		wake_up_interruptible(&ch->open_wait);
+		restore_flags(flags);
+	}
+}
+
+
+
+static int pcxe_write(struct tty_struct * tty, int from_user, const unsigned char *buf, int count)
+{
+	struct channel *ch;
+	volatile struct board_chan *bc;
+	int total, remain, size, stlen;
+	unsigned int head, tail;
+	unsigned long flags;
+
+	/* printk("Entering pcxe_write()\n"); */
+
+	if ((ch=chan(tty))==NULL)
+		return 0;
+
+	bc = ch->brdchan;
+	size = ch->txbufsize;
+
+	if (from_user) {
+
+		save_flags(flags);
+		cli();
+		globalwinon(ch);
+		head = bc->tin & (size - 1);
+		tail = bc->tout;
+		if (tail != bc->tout)
+			tail = bc->tout;
+		tail &= (size - 1);
+		stlen = (head >= tail) ? (size - (head - tail) - 1) : (tail - head - 1);
+		count = MIN(stlen, count);
+		if (count) {
+			if (verify_area(VERIFY_READ, (char*)buf, count))
+				count=0;
+			else memcpy_fromfs(ch->tmp_buf, buf, count);
+		}
+		buf = ch->tmp_buf;
+		memoff(ch);
+		restore_flags(flags);
+	}
+
+	/*
+	 * All data is now local
+	 */
+
+	total = 0;
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+	head = bc->tin & (size - 1);
+	tail = bc->tout;
+	if (tail != bc->tout)
+		tail = bc->tout;
+	tail &= (size - 1);
+	if (head >= tail) {
+		remain = size - (head - tail) - 1;
+		stlen = size - head;
+	}
+	else {
+		remain = tail - head - 1;
+		stlen = remain;
+	}
+	count = MIN(remain, count);
+
+	txwinon(ch);
+	while (count > 0) {
+		stlen = MIN(count, stlen);
+		memcpy(ch->txptr + head, buf, stlen);
+		buf += stlen;
+		count -= stlen;
+		total += stlen;
+		head += stlen;
+		if (head >= size) {
+			head = 0;
+			stlen = tail;
+		}
+	}
+	ch->statusflags |= TXBUSY;
+	globalwinon(ch);
+	bc->tin = head;
+	if ((ch->statusflags & LOWWAIT) == 0) {
+		ch->statusflags |= LOWWAIT;
+		bc->ilow = 1;
+	}
+	memoff(ch);
+	restore_flags(flags);
+
+	return(total);
+}
+
+
+static void pcxe_put_char(struct tty_struct *tty, unsigned char c)
+{
+	pcxe_write(tty, 0, &c, 1);
+	return;
+}
+
+
+static int pcxe_write_room(struct tty_struct *tty)
+{
+	struct channel *ch;
+	int remain;
+
+	remain = 0;
+	if ((ch=chan(tty))!=NULL) {
+		volatile struct board_chan *bc;
+		unsigned int head, tail;
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+		globalwinon(ch);
+
+		bc = ch->brdchan;
+		head = bc->tin & (ch->txbufsize - 1);
+		tail = bc->tout;
+		if (tail != bc->tout)
+			tail = bc->tout;
+		tail &= (ch->txbufsize - 1);
+
+		if((remain = tail - head - 1) < 0 )
+			remain += ch->txbufsize;
+
+		if (remain && (ch->statusflags & LOWWAIT) == 0) {
+			ch->statusflags |= LOWWAIT;
+			bc->ilow = 1;
+		}
+		memoff(ch);
+		restore_flags(flags);
+	}
+
+	return remain;
+}
+
+
+static int pcxe_chars_in_buffer(struct tty_struct *tty)
+{
+	int chars;
+	unsigned int ctail, head, tail;
+	int remain;
+	unsigned long flags;
+	struct channel *ch;
+	volatile struct board_chan *bc;
+
+	if ((ch=chan(tty))==NULL)
+		return(0);
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+
+	bc = ch->brdchan;
+	tail = bc->tout;
+	head = bc->tin;
+	ctail = ch->mailbox->cout;
+	if(tail == head && ch->mailbox->cin == ctail && bc->tbusy == 0)
+		chars = 0;
+	else {
+		head = bc->tin & (ch->txbufsize - 1);
+		tail &= (ch->txbufsize - 1);
+		if((remain = tail - head - 1) < 0 )
+			remain += ch->txbufsize;
+
+		chars = (int)(ch->txbufsize - remain);
+
+		/* 
+		 * Make it possible to wakeup anything waiting for output
+		 * in tty_ioctl.c, etc.
+		 */
+		if(!(ch->statusflags & EMPTYWAIT))
+			setup_empty_event(tty,ch);
+	}
+
+	memoff(ch);
+	restore_flags(flags);
+
+	return(chars);
+}
+
+
+static void pcxe_flush_buffer(struct tty_struct *tty)
+{
+	unsigned int tail;
+	volatile struct board_chan *bc;
+	struct channel *ch;
+	unsigned long flags;
+
+	if ((ch=chan(tty))==NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+
+	globalwinon(ch);
+	bc = ch->brdchan;
+	tail = bc->tout;
+	fepcmd(ch, STOUT, (unsigned) tail, 0, 0, 0);
+
+	memoff(ch);
+	restore_flags(flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	if((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
+		(tty->ldisc.write_wakeup)(tty);
+}
+
+static void pcxe_flush_chars(struct tty_struct *tty)
+{
+	struct channel * ch;
+
+	if ((ch=chan(tty))!=NULL) {
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+		if ((ch->statusflags & TXBUSY) && !(ch->statusflags & EMPTYWAIT))
+			setup_empty_event(tty,ch);
+		restore_flags(flags);
+	}
+}
+
+
+int pcxe_init(void)
+{
+	ulong flags, save_loops_per_sec, memory_seg=0, memory_size;
+	int lowwater, i, crd, shrinkmem=0, topwin = 0xff00L, botwin=0x100L;
+	unchar *fepos, *memaddr, *bios, v;
+	volatile struct global_data *gd;
+	struct board_info *bd;
+	volatile struct board_chan *bc;
+	struct channel *ch;
+
+	init_bh(DIGI_BH,do_pcxe_bh);
+	enable_bh(DIGI_BH);
+
+	timer_table[DIGI_TIMER].fn = pcxxpoll;
+	timer_table[DIGI_TIMER].expires = 0;
+
+	memset(&pcxe_driver, 0, sizeof(struct tty_driver));
+	pcxe_driver.magic = TTY_DRIVER_MAGIC;
+	pcxe_driver.name = "ttyd";
+	pcxe_driver.major = DIGIMAJOR; 
+	pcxe_driver.minor_start = 0;
+	pcxe_driver.num = NBDEVS;
+	pcxe_driver.type = TTY_DRIVER_TYPE_SERIAL;
+	pcxe_driver.subtype = SERIAL_TYPE_NORMAL;
+	pcxe_driver.init_termios = tty_std_termios;
+	pcxe_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+	pcxe_driver.flags = TTY_DRIVER_REAL_RAW;
+	pcxe_driver.refcount = &pcxe_refcount;
+	pcxe_driver.table = pcxe_table;
+	pcxe_driver.termios = pcxe_termios;
+	pcxe_driver.termios_locked = pcxe_termios_locked;
+
+	pcxe_driver.open = pcxe_open;
+	pcxe_driver.close = pcxe_close;
+	pcxe_driver.write = pcxe_write;
+	pcxe_driver.put_char = pcxe_put_char;
+	pcxe_driver.flush_chars = pcxe_flush_chars;
+	pcxe_driver.write_room = pcxe_write_room;
+	pcxe_driver.chars_in_buffer = pcxe_chars_in_buffer;
+	pcxe_driver.flush_buffer = pcxe_flush_buffer;
+	pcxe_driver.ioctl = pcxe_ioctl;
+	pcxe_driver.throttle = pcxe_throttle;
+	pcxe_driver.unthrottle = pcxe_unthrottle;
+	pcxe_driver.set_termios = pcxe_set_termios;
+	pcxe_driver.stop = pcxe_stop;
+	pcxe_driver.start = pcxe_start;
+	pcxe_driver.hangup = pcxe_hangup;
+
+	pcxe_callout = pcxe_driver;
+	pcxe_callout.name = "ttyD";
+	pcxe_callout.major = DIGICUMAJOR;
+	pcxe_callout.subtype = SERIAL_TYPE_CALLOUT;
+	pcxe_callout.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+
+	/* 
+	 * loops_per_sec hasn't been set at this point :-(, so fake it out... 
+	 * I set it so that I can use the __delay() function.
+	 */
+	save_loops_per_sec = loops_per_sec;
+	loops_per_sec = 13L*500000L;
+
+	save_flags(flags);
+	cli();
+
+	for(crd=0; crd < NUMCARDS; crd++) {
+		bd = &boards[crd];
+		outb(FEPRST, bd->port);
+		pcxxdelay(1);
+
+		for(i=0; (inb(bd->port) & FEPMASK) != FEPRST; i++) {
+			if(i > 1000) {
+				printk("Board not found at port 0x%x! Check switch settings.\n",
+					bd->port);
+				bd->status = DISABLED;
+				break;
+			}
+			pcxxdelay(1);
+		}
+		if(bd->status == DISABLED)
+			continue;
+
+		v = inb(bd->port);
+
+		if((v & 0x1) == 0x1) {
+			if((v & 0x30) == 0) {        /* PC/Xi 64K card */
+				memory_seg = 0xf000;
+				memory_size = 0x10000;
+			} 
+
+			if((v & 0x30) == 0x10) {     /* PC/Xi 128K card */
+				memory_seg = 0xe000;
+				memory_size = 0x20000;
+			}
+			
+			if((v & 0x30) == 0x20) {     /* PC/Xi 256K card */
+				memory_seg = 0xc000;
+				memory_size = 0x40000;
+			}
+
+			if((v & 0x30) == 0x30) {     /* PC/Xi 512K card */
+				memory_seg = 0x8000;
+				memory_size = 0x80000;
+			}
+			bd->type = PCXI;
+		} else {
+			if((v & 0x1) == 0x1) {
+				bd->status = DISABLED;   /* PC/Xm unsupported card */
+				printk("PC/Xm at 0x%x not supported!!\n", bd->port);
+				continue;
+			} else {
+				if(v & 0xC0) {    
+					topwin = 0x1f00L;
+					outb((((ulong)bd->membase>>8) & 0xe0) | 0x10, bd->port+2);
+					outb(((ulong)bd->membase>>16) & 0xff, bd->port+3);
+					bd->type = PCXEVE; /* PC/Xe 8K card */
+				} else { 
+					bd->type = PCXE;    /* PC/Xe 64K card */
+				}
+					
+				memory_seg = 0xf000;
+				memory_size = 0x10000;
+			}
+		}
+
+		memaddr = (unchar *) bd->membase;
+
+		outb(FEPRST|FEPMEM, bd->port);
+
+		for(i=0; (inb(bd->port) & FEPMASK) != (FEPRST|FEPMEM); i++) {
+			if(i > 10000) {
+				printk("%s not resetting at port 0x%x! Check switch settings.\n",
+					board_desc[bd->type], bd->port);
+				bd->status = DISABLED;
+				break;
+			}
+			pcxxdelay(1);
+		}
+		if(bd->status == DISABLED)
+			continue;
+
+		memwinon(bd,0);
+		*(ulong *)(memaddr + botwin) = 0xa55a3cc3;
+		*(ulong *)(memaddr + topwin) = 0x5aa5c33c;
+
+		if(*(ulong *)(memaddr + botwin) != 0xa55a3cc3 ||
+					*(ulong *)(memaddr + topwin) != 0x5aa5c33c) {
+			printk("Failed memory test at %lx for %s at port %x, check switch settings.\n",
+				bd->membase, board_desc[bd->type], bd->port);
+			bd->status = DISABLED;
+			continue;
+		}
+
+		for(i=0; i < 16; i++) {
+			memaddr[MISCGLOBAL+i] = 0;
+		}
+
+		if(bd->type == PCXI || bd->type == PCXE) {
+			bios = memaddr + BIOSCODE + ((0xf000 - memory_seg) << 4);
+
+			memcpy(bios, pcxx_bios, pcxx_nbios);
+
+			outb(FEPMEM, bd->port);
+
+			for(i=0; i <= 10000; i++) {
+				if(*(ushort *)((ulong)memaddr + MISCGLOBAL) == *(ushort *)"GD" ) {
+					goto load_fep;
+				}
+				pcxxdelay(1);
+			}
+
+			printk("BIOS download failed on the %s at 0x%x!\n",
+							board_desc[bd->type], bd->port);
+			bd->status = DISABLED;
+			continue;
+		}
+
+		if(bd->type == PCXEVE) {
+			bios = memaddr + (BIOSCODE & 0x1fff);
+			memwinon(bd,0xff);
+			
+			memcpy(bios, pcxx_bios, pcxx_nbios);
+
+			outb(FEPCLR, bd->port);
+			memwinon(bd,0);
+
+			for(i=0; i <= 10000; i++) {
+				if(*(ushort *)((ulong)memaddr + MISCGLOBAL) == *(ushort *)"GD" ) {
+					goto load_fep;
+				}
+				pcxxdelay(1);
+			}
+
+			printk("BIOS download failed on the %s at 0x%x!\n",
+				board_desc[bd->type], bd->port);
+			bd->status = DISABLED;
+			continue;
+		}
+
+load_fep:
+		fepos = memaddr + FEPCODE;
+		if(bd->type == PCXEVE)
+			fepos = memaddr + (FEPCODE & 0x1fff);
+
+		memwinon(bd, (FEPCODE >> 13));
+		memcpy(fepos, pcxx_cook, pcxx_ncook);
+		memwinon(bd, 0);
+
+		*(ushort *)((ulong)memaddr + MBOX +  0) = 2;
+		*(ushort *)((ulong)memaddr + MBOX +  2) = memory_seg + FEPCODESEG;
+		*(ushort *)((ulong)memaddr + MBOX +  4) = 0;
+		*(ushort *)((ulong)memaddr + MBOX +  6) = FEPCODESEG;
+		*(ushort *)((ulong)memaddr + MBOX +  8) = 0;
+		*(ushort *)((ulong)memaddr + MBOX + 10) = pcxx_ncook;
+
+		outb(FEPMEM|FEPINT, bd->port);
+		outb(FEPMEM, bd->port);
+
+		for(i=0; *(ushort *)((ulong)memaddr + MBOX); i++) {
+			if(i > 2000) {
+				printk("Command failed for the %s at 0x%x!\n",
+					board_desc[bd->type], bd->port);
+				bd->status = DISABLED;
+				break;
+			}
+			pcxxdelay(1);
+		}
+
+		if(bd->status == DISABLED)
+			continue;
+
+		*(ushort *)(memaddr + FEPSTAT) = 0;
+		*(ushort *)(memaddr + MBOX + 0) = 1;
+		*(ushort *)(memaddr + MBOX + 2) = FEPCODESEG;
+		*(ushort *)(memaddr + MBOX + 4) = 0x4L;
+
+		outb(FEPINT, bd->port);
+		outb(FEPCLR, bd->port);
+		memwinon(bd, 0);
+
+		for(i=0; *(ushort *)((ulong)memaddr + FEPSTAT) != *(ushort *)"OS"; i++) {
+			if(i > 10000) {
+				printk("FEP/OS download failed on the %s at 0x%x!\n",
+					board_desc[bd->type], bd->port);
+				bd->status = DISABLED;
+				break;
+			}
+			pcxxdelay(1);
+		}
+		if(bd->status == DISABLED)
+			continue;
+
+		ch = &digi_channels[MAXPORTS * crd];
+		pcxxassert(ch <= &digi_channels[NBDEVS-1], "ch out of range");
+
+		bc = (volatile struct board_chan *)((ulong)memaddr + CHANSTRUCT);
+		gd = (volatile struct global_data *)((ulong)memaddr + GLOBAL);
+
+		if((bd->type == PCXEVE) && (*(ushort *)((ulong)memaddr+NPORT) < 3))
+			shrinkmem = 1;
+
+		request_region(bd->port, 4, "PC/Xx");
+
+		for(i=0; i < bd->numports; i++, ch++, bc++) {
+			if(((ushort *)((ulong)memaddr + PORTBASE))[i] == 0) {
+				ch->brdchan = 0;
+				continue;
+			}
+			ch->brdchan = bc;
+			ch->mailbox = gd;
+			ch->tqueue.routine = do_softint;
+			ch->tqueue.data = ch;
+			ch->board = &boards[crd];
+#ifdef DEFAULT_HW_FLOW
+			ch->digiext.digi_flags = RTSPACE|CTSPACE;
+#endif
+			if(boards[crd].altpin) {
+				ch->dsr = CD;
+				ch->dcd = DSR;
+				ch->digiext.digi_flags |= DIGI_ALTPIN;
+			} else { 
+				ch->dcd = CD;
+				ch->dsr = DSR;
+			}
+
+			ch->magic = PCXX_MAGIC;
+			ch->boardnum = crd;
+			ch->channelnum = i;
+
+			ch->dev = (MAXPORTS * crd) + i;
+			ch->tty = 0;
+
+			if(shrinkmem) {
+				fepcmd(ch, SETBUFFER, 32, 0, 0, 0);
+				shrinkmem = 0;
+			}
+			
+			if(bd->type != PCXEVE) {
+				ch->txptr = memaddr+((bc->tseg-memory_seg) << 4);
+				ch->rxptr = memaddr+((bc->rseg-memory_seg) << 4);
+				ch->txwin = ch->rxwin = 0;
+			} else {
+				ch->txptr = memaddr+(((bc->tseg-memory_seg) << 4) & 0x1fff);
+				ch->txwin = FEPWIN | ((bc->tseg-memory_seg) >> 9);
+				ch->rxptr = memaddr+(((bc->rseg-memory_seg) << 4) & 0x1fff);
+				ch->rxwin = FEPWIN | ((bc->rseg-memory_seg) >>9 );
+			}
+
+			ch->txbufsize = bc->tmax + 1;
+			ch->rxbufsize = bc->rmax + 1;
+
+			ch->tmp_buf = kmalloc(ch->txbufsize,GFP_KERNEL);
+
+			lowwater = ch->txbufsize >= 2000 ? 1024 : ch->txbufsize/2;
+			fepcmd(ch, STXLWATER, lowwater, 0, 10, 0);
+			fepcmd(ch, SRXLWATER, ch->rxbufsize/4, 0, 10, 0);
+			fepcmd(ch, SRXHWATER, 3 * ch->rxbufsize/4, 0, 10, 0);
+
+			bc->edelay = 100;
+			bc->idata = 1;
+
+			ch->startc = bc->startc;
+			ch->stopc = bc->stopc;
+			ch->startca = bc->startca;
+			ch->stopca = bc->stopca;
+
+			ch->fepcflag = 0;
+			ch->fepiflag = 0;
+			ch->fepoflag = 0;
+			ch->fepstartc = 0;
+			ch->fepstopc = 0;
+			ch->fepstartca = 0;
+			ch->fepstopca = 0;
+
+			ch->close_delay = 50;
+			ch->count = 0;
+			ch->blocked_open = 0;
+			ch->callout_termios = pcxe_callout.init_termios;
+			ch->normal_termios = pcxe_driver.init_termios;
+			ch->open_wait = 0;
+			ch->close_wait = 0;
+		}
+
+		printk("DigiBoard PC/Xx Driver V%s:  %s I/O=0x%x Mem=0x%lx Ports=%d\n", 
+				VERSION, board_desc[bd->type], bd->port, bd->membase, bd->numports);
+
+		memwinoff(bd, 0);
+	}
+
+	if(tty_register_driver(&pcxe_driver))
+		panic("Couldn't register PC/Xe driver");
+
+	if(tty_register_driver(&pcxe_callout))
+		panic("Couldn't register PC/Xe callout");
+
+
+	loops_per_sec = save_loops_per_sec;  /* reset it to what it should be */
+
+	/*
+	 * Start up the poller to check for events on all enabled boards
+	 */
+	timer_active |= 1 << DIGI_TIMER;
+	restore_flags(flags);
+
+	return 0;
+}
+
+
+static void pcxxpoll(void)
+{
+	unsigned long flags;
+	int crd;
+	volatile unsigned int head, tail;
+	struct channel *ch;
+	struct board_info *bd;
+
+	save_flags(flags);
+	cli();
+
+	for(crd=0; crd < NUMCARDS; crd++) {
+		bd = &boards[crd];
+		ch = &digi_channels[MAXPORTS*crd];
+
+		if(bd->status == DISABLED)
+			continue;
+
+		assertmemoff(ch);
+
+		globalwinon(ch);
+		head = ch->mailbox->ein;
+		tail = ch->mailbox->eout;
+
+		if(head != tail)
+			doevent(crd);
+
+		memoff(ch);
+	}
+
+	timer_table[DIGI_TIMER].fn = pcxxpoll;
+	timer_table[DIGI_TIMER].expires = jiffies + HZ/25;
+	timer_active |= 1 << DIGI_TIMER;
+	restore_flags(flags);
+}
+
+static void doevent(int crd)
+{
+	volatile struct board_info *bd;
+	static struct tty_struct *tty;
+	volatile struct board_chan *bc;
+	volatile unchar *eventbuf;
+	volatile unsigned int head;
+	volatile unsigned int tail;
+	struct channel *ch;
+	struct channel *chan0;
+	int channel, event, mstat, lstat;
+
+	bd = &boards[crd];
+
+	chan0 = &digi_channels[MAXPORTS * crd];
+	pcxxassert(chan0 <= &digi_channels[NBDEVS-1], "ch out of range");
+
+	assertgwinon(chan0);
+
+	while ((tail = chan0->mailbox->eout) != (head = chan0->mailbox->ein)) {
+		assertgwinon(chan0);
+		eventbuf = (volatile unchar *)bd->membase + tail + ISTART;
+		channel = eventbuf[0];
+		event = eventbuf[1];
+		mstat = eventbuf[2];
+		lstat = eventbuf[3];
+
+		ch=chan0+channel;
+
+		if ((unsigned)channel >= bd->numports || !ch) { 
+			printk("physmem=%lx, tail=%x, head=%x\n", bd->membase, tail, head);
+			printk("doevent(%x) channel %x, event %x, mstat %x, lstat %x\n",
+					crd, (unsigned)channel, event, (unsigned)mstat, lstat);
+			if(channel >= bd->numports)
+				ch = chan0;
+			bc = ch->brdchan;
+			goto next;
+		}
+		if ((bc = ch->brdchan) == NULL)
+			goto next;
+
+		if (event & DATA_IND) {
+			receive_data(ch);
+			assertgwinon(ch);
+		}
+
+		if (event & MODEMCHG_IND) {
+			ch->imodem = mstat;
+			if (ch->asyncflags & (ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE)) {
+				if (ch->asyncflags & ASYNC_CHECK_CD) {
+					if (mstat & ch->dcd) {
+						wake_up_interruptible(&ch->open_wait);
+					} else {
+						pcxe_sched_event(ch, PCXE_EVENT_HANGUP);
+					}
+				}
+			}
+		}
+
+		tty = ch->tty;
+
+		if (tty) {
+
+			if (event & BREAK_IND) {
+				tty->flip.count++;
+				*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+				*tty->flip.char_buf_ptr++ = 0;
+#if 0
+				if (ch->asyncflags & ASYNC_SAK)
+					do_SAK(tty);
+#endif
+				tty_schedule_flip(tty); 
+			}
+
+			if (event & LOWTX_IND) {
+				if (ch->statusflags & LOWWAIT) {
+					ch->statusflags &= ~LOWWAIT;
+					if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+						tty->ldisc.write_wakeup)
+						(tty->ldisc.write_wakeup)(tty);
+					wake_up_interruptible(&tty->write_wait);
+				}
+			}
+
+			if (event & EMPTYTX_IND) {
+				ch->statusflags &= ~TXBUSY;
+				if (ch->statusflags & EMPTYWAIT) {
+					ch->statusflags &= ~EMPTYWAIT;
+					if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+						tty->ldisc.write_wakeup)
+						(tty->ldisc.write_wakeup)(tty);
+					wake_up_interruptible(&tty->write_wait);
+				}
+			}
+		}
+
+	next:
+		globalwinon(ch);
+		if(!bc) printk("bc == NULL in doevent!\n");
+		else bc->idata = 1;
+
+		chan0->mailbox->eout = (tail+4) & (IMAX-ISTART-4);
+		globalwinon(chan0);
+	}
+
+}
+
+
+/*
+ * pcxxdelay - delays a specified number of milliseconds
+ */
+static void pcxxdelay(int msec)
+{
+	while(msec-- > 0)
+		__delay(loops_per_sec/1000);
+}
+
+
+static void 
+fepcmd(struct channel *ch, int cmd, int word_or_byte, int byte2, int ncmds,
+						int bytecmd)
+{
+	unchar *memaddr;
+	unsigned int head, tail;
+	long count;
+	int n;
+
+	if(ch->board->status == DISABLED)
+		return;
+
+	assertgwinon(ch);
+
+	memaddr = (unchar *)ch->board->membase;
+	head = ch->mailbox->cin;
+
+	if(head >= (CMAX-CSTART) || (head & 03)) {
+		printk("line %d: Out of range, cmd=%x, head=%x\n", __LINE__, cmd, head);
+		return;
+	}
+
+	if(bytecmd) {
+		*(unchar *)(memaddr+head+CSTART+0) = cmd;
+		*(unchar *)(memaddr+head+CSTART+1) = PORTNUM(ch);
+		*(unchar *)(memaddr+head+CSTART+2) = word_or_byte;
+		*(unchar *)(memaddr+head+CSTART+3) = byte2;
+	} else {
+		*(unchar *)(memaddr+head+CSTART+0) = cmd;
+		*(unchar *)(memaddr+head+CSTART+1) = PORTNUM(ch);
+		*(ushort*)(memaddr+head+CSTART+2) = word_or_byte;
+	}
+
+	head = (head+4) & (CMAX-CSTART-4);
+	ch->mailbox->cin = head;
+
+	count = FEPTIMEOUT;
+
+	while(1) {
+		count--;
+		if(count == 0) {
+			printk("Fep not responding in fepcmd()\n");
+			return;
+		}
+
+		head = ch->mailbox->cin;
+		tail = ch->mailbox->cout;
+
+		n = (head-tail) & (CMAX-CSTART-4);
+
+		if(n <= ncmds * (sizeof(short)*4))
+			break;
+	}
+}
+
+
+static unsigned termios2digi_c(struct channel *ch, unsigned cflag)
+{
+	unsigned res = 0;
+#ifdef SPEED_HACK
+	/* CL: HACK to force 115200 at 38400 and 57600 at 19200 Baud */
+	if ((cflag & CBAUD)== B38400) cflag=cflag - B38400 + B115200;
+	if ((cflag & CBAUD)== B19200) cflag=cflag - B19200 + B57600;
+#endif
+	if (cflag & CBAUDEX)
+	{
+		ch->digiext.digi_flags |= DIGI_FAST;
+		res |= FEP_HUPCL;
+		/* This gets strange but if we dont do this we will get 78600
+		 * instead of 115200. 57600 is mapped to 50 baud yielding 57600 in
+		 * FAST mode. 115200 is mapped to 75. We need to map it to 110 to
+		 * do 115K
+		 */
+		if (cflag & B115200) res|=1;
+	}
+	else ch->digiext.digi_flags &= ~DIGI_FAST;
+	res |= cflag & (CBAUD | PARODD | PARENB | CSTOPB | CSIZE);
+	return res;
+}
+
+static unsigned termios2digi_i(struct channel *ch, unsigned iflag)
+{
+	unsigned res = iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|IXON|IXANY|IXOFF);
+	
+	if(ch->digiext.digi_flags & DIGI_AIXON)
+		res |= IAIXON;
+	return res;
+}
+
+static unsigned termios2digi_h(struct channel *ch, unsigned cflag)
+{
+	unsigned res = 0;
+
+	if(cflag & CRTSCTS) {
+		ch->digiext.digi_flags |= (RTSPACE|CTSPACE);
+		res |= (CTS | RTS);
+	}
+	if(ch->digiext.digi_flags & RTSPACE)
+		res |= RTS;
+	if(ch->digiext.digi_flags & DTRPACE)
+		res |= DTR;
+	if(ch->digiext.digi_flags & CTSPACE)
+		res |= CTS;
+	if(ch->digiext.digi_flags & DSRPACE)
+		res |= ch->dsr;
+	if(ch->digiext.digi_flags & DCDPACE)
+		res |= ch->dcd;
+
+	if (res & RTS)
+		ch->digiext.digi_flags |= RTSPACE;
+	if (res & CTS)
+		ch->digiext.digi_flags |= CTSPACE;
+
+	return res;
+}
+
+static void pcxxparam(struct tty_struct *tty, struct channel *ch)
+{
+	volatile struct board_chan *bc;
+	unsigned int head;
+	unsigned mval, hflow, cflag, iflag;
+	struct termios *ts;
+
+	bc = ch->brdchan;
+	assertgwinon(ch);
+	ts = tty->termios;
+
+	if((ts->c_cflag & CBAUD) == 0) {
+		head = bc->rin;
+		bc->rout = head;
+		head = bc->tin;
+		fepcmd(ch, STOUT, (unsigned) head, 0, 0, 0);
+		mval = 0;
+	} else {
+
+		cflag = termios2digi_c(ch, ts->c_cflag);
+
+		if(cflag != ch->fepcflag) {
+			ch->fepcflag = cflag;
+			fepcmd(ch, SETCTRLFLAGS, (unsigned) cflag, 0, 0, 0);
+		}
+
+		if(cflag & CLOCAL)
+			ch->asyncflags &= ~ASYNC_CHECK_CD;
+		else {
+			ch->asyncflags |= ASYNC_CHECK_CD;
+		}
+
+		mval = DTR | RTS;
+	}
+
+	iflag = termios2digi_i(ch, ts->c_iflag);
+
+	if(iflag != ch->fepiflag) {
+		ch->fepiflag = iflag;
+		fepcmd(ch, SETIFLAGS, (unsigned int) ch->fepiflag, 0, 0, 0);
+	}
+
+	bc->mint = ch->dcd;
+	if((ts->c_cflag & CLOCAL) || (ch->digiext.digi_flags & DIGI_FORCEDCD))
+		if(ch->digiext.digi_flags & DIGI_FORCEDCD)
+			bc->mint = 0;
+
+	ch->imodem = bc->mstat;
+
+	hflow = termios2digi_h(ch, ts->c_cflag);
+
+	if(hflow != ch->hflow) {
+		ch->hflow = hflow;
+		fepcmd(ch, SETHFLOW, hflow, 0xff, 0, 1);
+	}
+
+	/* mval ^= ch->modemfake & (mval ^ ch->modem); */
+
+	if(ch->omodem != mval) {
+		ch->omodem = mval;
+		fepcmd(ch, SETMODEM, mval, RTS|DTR, 0, 1);
+	}
+
+	if(ch->startc != ch->fepstartc || ch->stopc != ch->fepstopc) {
+		ch->fepstartc = ch->startc;
+		ch->fepstopc = ch->stopc;
+		fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+	}
+
+	if(ch->startca != ch->fepstartca || ch->stopca != ch->fepstopca) {
+		ch->fepstartca = ch->startca;
+		ch->fepstopca = ch->stopca;
+		fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+	}
+}
+
+
+static void receive_data(struct channel *ch)
+{
+	volatile struct board_chan *bc;
+	struct tty_struct *tty;
+	unsigned int tail, head, wrapmask;
+	int n;
+	int piece;
+	struct termios *ts=0;
+	unchar *rptr;
+	int rc;
+	int wrapgap;
+
+    globalwinon(ch);
+
+	if (ch->statusflags & RXSTOPPED)
+		return;
+
+	tty = ch->tty;
+	if(tty)
+		ts = tty->termios;
+
+	bc = ch->brdchan;
+
+	if(!bc) {
+		printk("bc is NULL in receive_data!\n");
+		return;
+	}
+
+	wrapmask = ch->rxbufsize - 1;
+
+	head = bc->rin;
+	head &= wrapmask;
+	tail = bc->rout & wrapmask;
+
+	n = (head-tail) & wrapmask;
+
+	if(n == 0)
+		return;
+
+	/*
+	 * If CREAD bit is off or device not open, set TX tail to head
+	 */
+	if(!tty || !ts || !(ts->c_cflag & CREAD)) {
+		bc->rout = head;
+		return;
+	}
+
+	if(tty->flip.count == TTY_FLIPBUF_SIZE) {
+		/* printk("tty->flip.count = TTY_FLIPBUF_SIZE\n"); */
+		return;
+	}
+
+	if(bc->orun) {
+		bc->orun = 0;
+		printk("overrun! DigiBoard device minor=%d\n",MINOR(tty->device));
+	}
+
+	rxwinon(ch);
+	rptr = tty->flip.char_buf_ptr;
+	rc = tty->flip.count;
+	while(n > 0) {
+		wrapgap = (head >= tail) ? head - tail : ch->rxbufsize - tail;
+		piece = (wrapgap < n) ? wrapgap : n;
+
+		/*
+		 * Make sure we don't overflow the buffer
+		 */
+
+		if ((rc + piece) > TTY_FLIPBUF_SIZE)
+			piece = TTY_FLIPBUF_SIZE - rc;
+
+		if (piece == 0)
+			break;
+
+		memcpy(rptr, ch->rxptr + tail, piece);
+		rptr += piece;
+		rc += piece;
+		tail = (tail + piece) & wrapmask;
+		n -= piece;
+	}
+	tty->flip.count = rc;
+	tty->flip.char_buf_ptr = rptr;
+    globalwinon(ch);
+	bc->rout = tail;
+
+	/* Must be called with global data */
+	tty_schedule_flip(ch->tty); 
+	return;
+}
+
+
+static int pcxe_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	int error;
+	struct channel *ch = (struct channel *) tty->driver_data;
+	volatile struct board_chan *bc;
+	int retval;
+	unsigned int mflag, mstat;
+	unsigned char startc, stopc;
+	unsigned long flags;
+	digiflow_t dflow;
+
+	if(ch)
+		bc = ch->brdchan;
+	else {
+		printk("ch is NULL in pcxe_ioctl!\n");
+		return(-EINVAL);
+	}
+
+	save_flags(flags);
+
+	switch(cmd) {
+		case TCSBRK:	/* SVID version: non-zero arg --> no break */
+			retval = tty_check_change(tty);
+			if(retval)
+				return retval;
+			setup_empty_event(tty,ch);		
+			tty_wait_until_sent(tty, 0);
+			if(!arg)
+				digi_send_break(ch, HZ/4);    /* 1/4 second */
+			return 0;
+
+		case TCSBRKP:	/* support for POSIX tcsendbreak() */
+			retval = tty_check_change(tty);
+			if(retval)
+				return retval;
+			setup_empty_event(tty,ch);		
+			tty_wait_until_sent(tty, 0);
+			digi_send_break(ch, arg ? arg*(HZ/10) : HZ/4);
+			return 0;
+
+		case TIOCGSOFTCAR:
+			error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long));
+			if(error)
+				return error;
+			put_fs_long(C_CLOCAL(tty) ? 1 : 0,
+				    (unsigned long *) arg);
+			return 0;
+
+		case TIOCSSOFTCAR:
+			arg = get_fs_long((unsigned long *) arg);
+			tty->termios->c_cflag =	((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+			return 0;
+
+		case TIOCMODG:
+		case TIOCMGET:
+			mflag = 0;
+
+			cli();
+			globalwinon(ch);
+			mstat = bc->mstat;
+			memoff(ch);
+			restore_flags(flags);
+
+			if(mstat & DTR)
+				mflag |= TIOCM_DTR;
+			if(mstat & RTS)
+				mflag |= TIOCM_RTS;
+			if(mstat & CTS)
+				mflag |= TIOCM_CTS;
+			if(mstat & ch->dsr)
+				mflag |= TIOCM_DSR;
+			if(mstat & RI)
+				mflag |= TIOCM_RI;
+			if(mstat & ch->dcd)
+				mflag |= TIOCM_CD;
+
+			error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(long));
+			if(error)
+				return error;
+			put_fs_long(mflag, (unsigned long *) arg);
+			break;
+
+		case TIOCMBIS:
+		case TIOCMBIC:
+		case TIOCMODS:
+		case TIOCMSET:
+			mstat = get_fs_long((unsigned long *) arg);
+
+			mflag = 0;
+			if(mstat & TIOCM_DTR)
+				mflag |= DTR;
+			if(mstat & TIOCM_RTS)
+				mflag |= RTS;
+
+			switch(cmd) {
+				case TIOCMODS:
+				case TIOCMSET:
+					ch->modemfake = DTR|RTS;
+					ch->modem = mflag;
+					break;
+
+				case TIOCMBIS:
+					ch->modemfake |= mflag;
+					ch->modem |= mflag;
+					break;
+
+				case TIOCMBIC:
+					ch->modemfake |= mflag;
+					ch->modem &= ~mflag;
+					break;
+			}
+
+			cli();
+			globalwinon(ch);
+			pcxxparam(tty,ch);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case TIOCSDTR:
+			cli();
+			ch->omodem |= DTR;
+			globalwinon(ch);
+			fepcmd(ch, SETMODEM, DTR, 0, 10, 1);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case TIOCCDTR:
+			ch->omodem &= ~DTR;
+			cli();
+			globalwinon(ch);
+			fepcmd(ch, SETMODEM, 0, DTR, 10, 1);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case DIGI_GETA:
+			if((error=verify_area(VERIFY_WRITE, (char*)arg, sizeof(digi_t))))
+				return(error);
+
+			memcpy_tofs((char*)arg, &ch->digiext, sizeof(digi_t));
+			break;
+
+		case DIGI_SETAW:
+		case DIGI_SETAF:
+			if(cmd == DIGI_SETAW) {
+				setup_empty_event(tty,ch);		
+				tty_wait_until_sent(tty, 0);
+			}
+			else {
+				if(tty->ldisc.flush_buffer)
+					tty->ldisc.flush_buffer(tty);
+			}
+
+			/* Fall Thru */
+
+		case DIGI_SETA:
+			if((error=verify_area(VERIFY_READ, (char*)arg,sizeof(digi_t))))
+				return(error);
+
+			memcpy_fromfs(&ch->digiext, (char*)arg, sizeof(digi_t));
+#ifdef DEBUG_IOCTL
+			printk("ioctl(DIGI_SETA): flags = %x\n", ch->digiext.digi_flags);
+#endif
+			
+			if(ch->digiext.digi_flags & DIGI_ALTPIN) {
+				ch->dcd = DSR;
+				ch->dsr = CD;
+			} else {
+				ch->dcd = CD;
+				ch->dsr = DSR;
+			}
+		
+			cli();
+			globalwinon(ch);
+			pcxxparam(tty,ch);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case DIGI_GETFLOW:
+		case DIGI_GETAFLOW:
+			cli();	
+			globalwinon(ch);
+			if(cmd == DIGI_GETFLOW) {
+				dflow.startc = bc->startc;
+				dflow.stopc = bc->stopc;
+			} else {
+				dflow.startc = bc->startca;
+				dflow.stopc = bc->stopca;
+			}
+			memoff(ch);
+			restore_flags(flags);
+
+			if((error=verify_area(VERIFY_WRITE, (char*)arg,sizeof(dflow))))
+				return(error);
+
+			memcpy_tofs((char*)arg, &dflow, sizeof(dflow));
+			break;
+
+		case DIGI_SETAFLOW:
+		case DIGI_SETFLOW:
+			if(cmd == DIGI_SETFLOW) {
+				startc = ch->startc;
+				stopc = ch->stopc;
+			} else {
+				startc = ch->startca;
+				stopc = ch->stopca;
+			}
+
+			if((error=verify_area(VERIFY_READ, (char*)arg,sizeof(dflow))))
+				return(error);
+
+			memcpy_fromfs(&dflow, (char*)arg, sizeof(dflow));
+
+			if(dflow.startc != startc || dflow.stopc != stopc) {
+				cli();
+				globalwinon(ch);
+
+				if(cmd == DIGI_SETFLOW) {
+					ch->fepstartc = ch->startc = dflow.startc;
+					ch->fepstopc = ch->stopc = dflow.stopc;
+					fepcmd(ch,SONOFFC,ch->fepstartc,ch->fepstopc,0, 1);
+				} else {
+					ch->fepstartca = ch->startca = dflow.startc;
+					ch->fepstopca  = ch->stopca = dflow.stopc;
+					fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+				}
+
+				if(ch->statusflags & TXSTOPPED)
+					pcxe_start(tty);
+
+				memoff(ch);
+				restore_flags(flags);
+			}
+			break;
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+static void pcxe_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	struct channel *info;
+
+	if ((info=chan(tty))!=NULL) {
+		unsigned long flags;
+		save_flags(flags);
+		cli();
+		globalwinon(info);
+		pcxxparam(tty,info);
+		memoff(info);
+
+		if ((old_termios->c_cflag & CRTSCTS) &&
+			((tty->termios->c_cflag & CRTSCTS) == 0))
+			tty->hw_stopped = 0;
+		if(!(old_termios->c_cflag & CLOCAL) &&
+			(tty->termios->c_cflag & CLOCAL))
+			wake_up_interruptible(&info->open_wait);
+		restore_flags(flags);
+	}
+}
+
+
+static void do_pcxe_bh(void)
+{
+	run_task_queue(&tq_pcxx);
+}
+
+
+static void do_softint(void *private_)
+{
+	struct channel *info = (struct channel *) private_;
+	
+	if(info && info->magic == PCXX_MAGIC) {
+		struct tty_struct *tty = info->tty;
+		if (tty && tty->driver_data) {
+			if(clear_bit(PCXE_EVENT_HANGUP, &info->event)) {
+				tty_hangup(tty);
+				wake_up_interruptible(&info->open_wait);
+				info->asyncflags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
+			}
+		}
+	}
+}
+
+
+static void pcxe_stop(struct tty_struct *tty)
+{
+	struct channel *info;
+
+	if ((info=chan(tty))!=NULL) {
+		unsigned long flags;
+		save_flags(flags); 
+		cli();
+		if ((info->statusflags & TXSTOPPED) == 0) {
+			globalwinon(info);
+			fepcmd(info, PAUSETX, 0, 0, 0, 0);
+			info->statusflags |= TXSTOPPED;
+			memoff(info);
+		}
+		restore_flags(flags);
+	}
+}
+
+static void pcxe_throttle(struct tty_struct * tty)
+{
+	struct channel *info;
+
+	if ((info=chan(tty))!=NULL) {
+		unsigned long flags;
+		save_flags(flags);
+		cli();
+		if ((info->statusflags & RXSTOPPED) == 0) {
+			globalwinon(info);
+			fepcmd(info, PAUSERX, 0, 0, 0, 0);
+			info->statusflags |= RXSTOPPED;
+			memoff(info);
+		}
+		restore_flags(flags);
+	}
+}
+
+static void pcxe_unthrottle(struct tty_struct *tty)
+{
+	struct channel *info;
+
+	if ((info=chan(tty)) != NULL) {
+		unsigned long flags;
+
+		/* Just in case output was resumed because of a change in Digi-flow */
+		save_flags(flags);
+		cli();
+		if(info->statusflags & RXSTOPPED) {
+			volatile struct board_chan *bc;
+			globalwinon(info);
+			bc = info->brdchan;
+			fepcmd(info, RESUMERX, 0, 0, 0, 0);
+			info->statusflags &= ~RXSTOPPED;
+			memoff(info);
+		}
+		restore_flags(flags);
+	}
+}
+
+
+static void pcxe_start(struct tty_struct *tty)
+{
+	struct channel *info;
+
+	if ((info=chan(tty))!=NULL) {
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+		/* Just in case output was resumed because of a change in Digi-flow */
+		if(info->statusflags & TXSTOPPED) {
+			volatile struct board_chan *bc;
+			globalwinon(info);
+			bc = info->brdchan;
+			if(info->statusflags & LOWWAIT)
+				bc->ilow = 1;
+			fepcmd(info, RESUMETX, 0, 0, 0, 0);
+			info->statusflags &= ~TXSTOPPED;
+			memoff(info);
+		}
+		restore_flags(flags);
+	}
+}
+
+
+void digi_send_break(struct channel *ch, int msec)
+{
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+
+	/* 
+	 * Maybe I should send an infinite break here, schedule() for
+	 * msec amount of time, and then stop the break.  This way,
+	 * the user can't screw up the FEP by causing digi_send_break()
+	 * to be called (i.e. via an ioctl()) more than once in msec amount 
+	 * of time.  Try this for now...
+	 */
+
+	fepcmd(ch, SENDBREAK, msec, 0, 10, 0);
+	memoff(ch);
+
+	restore_flags(flags);
+}
+
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch)
+{
+	volatile struct board_chan *bc;
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+	ch->statusflags |= EMPTYWAIT;
+	bc = ch->brdchan;
+	bc->iempty = 1;
+	memoff(ch);
+	restore_flags(flags);
+}
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this