OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [rc203soc/] [sw/] [uClinux/] [drivers/] [char/] [pcxx.c] - Rev 1777

Go to most recent revision | Compare with Previous | Blame | View Log

/*
 *  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.
 *
 * This Driver is currently maintained by Christoph Lameter (clameter@fuller.edu)
 * Please contact the mailing list for problems first. 
 *
 * Sources of Information:
 * 1. The Linux Digiboard Page at http://private.fuller.edu/clameter/digi.html
 * 2. The Linux Digiboard Mailing list at digiboard@list.fuller.edu
 *    (Simply write a message to introduce yourself to subscribe)
 *
 *  1.5.2 Fall 1995 Bug fixes by David Nugent
 *  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 assignment to kernel arrays.
 *  1.5.5 April 5, 1996 Major device numbers corrected.
 *              Mike McLagan<mike.mclagan@linux.org>: Add setup
 *              variable handling, instead of using the old pcxxconfig.h
 *  1.5.6 April 16, 1996 Christoph Lameter: Pointer cleanup, macro cleanup.
 *		Call out devices changed to /dev/cudxx.
 *  1.5.7 July 22, 1996 Martin Mares: CLOCAL fix, pcxe_table clearing.
 *		David Nugent: Bug in pcxe_open.
 *		Brian J. Murrell: Modem Control fixes, Majors correctly assigned
 *
 */
#undef MODULE
/* Module code is broken right now. Don't enable this unless you want to fix it */
 
#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 don't like these high baudrates.
*/
 
#include <linux/module.h>
#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/delay.h>
#include <linux/serial.h>
#include <linux/tty_driver.h>
#include <linux/malloc.h>
#include <linux/string.h>
 
#ifndef MODULE
 
/* is* routines not available in modules
** the need for this should go away when probing is done.  :-)
** brian@ilinx.com
*/
 
#include <linux/ctype.h>
#endif
 
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/bitops.h>
 
#define VERSION 	"1.5.7"
static char *banner = "Digiboard PC/X{i,e,eve} driver v1.5.7";
 
#include "digi.h"
#include "fep.h"
#include "pcxx.h"
#include "digi_fep.h"
#include "digi_bios.h"
 
/* Define one default setting if no digi= config line is used.
 * Default is ALTPIN = ON, PC/16E, 16 ports, I/O 200h Memory 0D0000h
 */
static struct board_info boards[MAX_DIGI_BOARDS] = { {	ENABLED, 0, ON, 16, 0x200, 0xd0000,0 } };
 
static int numcards = 1;
static int nbdevs = 0;
 
static struct channel    *digi_channels;
static struct tty_struct **pcxe_table;
static struct termios    **pcxe_termios;
static struct termios    **pcxe_termios_locked;
 
int pcxx_ncook=sizeof(pcxx_cook);
int pcxx_nbios=sizeof(pcxx_bios);
 
#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 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
 
/* function definitions */
#ifdef MODULE
int		init_module(void);
void		cleanup_module(void);
 
/*
 *	Loadable module initialization stuff.
 */
 
int init_module()
{
 
	return pcxe_init();
 
}
 
/*****************************************************************************/
 
void cleanup_module()
{
 
	unsigned long	flags;
	int crd, i;
	int e1, e2;
	struct board_info *bd;
	struct channel *ch;
 
	printk(KERN_INFO "Unloading PC/Xx: version %s\n", VERSION);
 
	save_flags(flags);
	cli();
	timer_active &= ~(1 << DIGI_TIMER);
	timer_table[DIGI_TIMER].fn = NULL;
	timer_table[DIGI_TIMER].expires = 0;
 
	if ((e1 = tty_unregister_driver(&pcxe_driver)))
		printk("SERIAL: failed to unregister serial driver (%d)\n", e1);
	if ((e2 = tty_unregister_driver(&pcxe_callout)))
		printk("SERIAL: failed to unregister callout driver (%d)\n",e2);
 
	for(crd=0; crd < numcards; crd++) {
		bd = &boards[crd];
		ch = digi_channels+bd->first_minor;
		for(i=0; i < bd->numports; i++, ch++) {
			kfree(ch->tmp_buf);
		}
		release_region(bd->port, 4);
	}
	kfree(digi_channels);
	kfree(pcxe_termios_locked);
	kfree(pcxe_termios);
	kfree(pcxe_table);
	restore_flags(flags);
}
#endif
 
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 && 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;
}	
 
 
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);
	}
 
	for(boardnum=0;boardnum<numcards;boardnum++)
		if ((line >= boards[boardnum].first_minor) && 
			(line < boards[boardnum].first_minor + boards[boardnum].numports))
		break;
 
	if(boardnum >= numcards || boards[boardnum].status == DISABLED ||
		(line - boards[boardnum].first_minor) >= 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);
	}
 
	/* flag the kernel that there is somebody using this guy */
	MOD_INC_USE_COUNT;
	/*
	 * 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 {
			/* this has to be set in order for the "block until
			 * CD" code to work correctly.  i'm not sure under
			 * what circumstances asyncflags should be set to
			 * ASYNC_NORMAL_ACTIVE though
			 * brian@ilinx.com
			 */
			ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
			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)) {
			/* flag that somebody is done with this module */
			MOD_DEC_USE_COUNT;
			restore_flags(flags);
			return;
		}
		/* this check is in serial.c, it won't hurt to do it here too */
		if ((tty->count == 1) && (info->count != 1)) {
			/*
			 * Uh, oh.  tty->count is 1, which means that the tty
			 * structure will be freed.  Info->count should always
			 * be one in these conditions.  If it's greater than
			 * one, we've got real problems, since it means the
			 * serial port won't be shutdown.
			 */
			printk("pcxe_close: bad serial port count; tty->count is 1, info->count is %d\n", info->count);
			info->count = 1;
		}
		if (info->count-- > 1) {
			restore_flags(flags);
			MOD_DEC_USE_COUNT;
			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;
#ifndef MODULE
/* ldiscs[] is not available in a MODULE
** worth noting that while I'm not sure what this hunk of code is supposed
** to do, it is not present in the serial.c driver.  Hmmm.  If you know,
** please send me a note.  brian@ilinx.com
** Don't know either what this is supposed to do clameter@waterf.org.
*/
		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);
		}
#endif
		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);
		MOD_DEC_USE_COUNT;
		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);
		/* It seems to be necessary to make sure that the value is stable here somehow
		   This is a rather odd pice of code here. */
		do
		{ tail = bc->tout;
		} while (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);
	}
}
 
/* Flag if lilo configuration option is used. If so the
 * default settings are removed
 */
 
static int liloconfig=0;
 
void pcxx_setup(char *str, int *ints)
{
 
	struct board_info board;
	int               i, j, last;
	char              *temp, *t2;
	unsigned          len;
 
#if 0
	if (!numcards)
		memset(&boards, 0, sizeof(boards));
#endif
	if (liloconfig==0) { liloconfig=1;numcards=0; }
 
	memset(&board, 0, sizeof(board));
 
	for(last=0,i=1;i<=ints[0];i++)
		switch(i)
		{
			case 1:
				board.status = ints[i];
				last = i;
				break;
 
			case 2:
				board.type = ints[i];
				last = i;
				break;
 
			case 3:
				board.altpin = ints[i];
				last = i;
				break;
 
			case 4:
				board.numports = ints[i];
				last = i;
				break;
 
			case 5:
				board.port = ints[i];
				last = i;
				break;
 
			case 6:
				board.membase = ints[i];
				last = i;
				break;
 
			default:
				printk("PC/Xx: Too many integer parms\n");
				return;
		}
 
 
	while (str && *str) 
	{
		/* find the next comma or terminator */
		temp = str;
		while (*temp && (*temp != ','))
			temp++;
 
		if (!*temp)
			temp = NULL;
		else
			*temp++ = 0;
 
		i = last + 1;
 
		switch(i)
		{
			case 1:
				len = strlen(str);
				if (strncmp("Disable", str, len) == 0) 
					board.status = 0;
				else
					if (strncmp("Enable", str, len) == 0)
						board.status = 1;
					else
					{
						printk("PC/Xx: Invalid status %s\n", str);
						return;
					}
				last = i;
				break;
 
			case 2:
				for(j=0;j<PCXX_NUM_TYPES;j++)
					if (strcmp(board_desc[j], str) == 0)
						break;
 
				if (i<PCXX_NUM_TYPES) 
					board.type = j;
				else
				{
					printk("PC/Xx: Invalid board name: %s\n", str);
					return;
				}
				last = i;
				break;
 
			case 3:
				len = strlen(str);
				if (strncmp("Disable", str, len) == 0) 
					board.altpin = 0;
				else
					if (strncmp("Enable", str, len) == 0)
						board.altpin = 1;
					else
					{
						printk("PC/Xx: Invalid altpin %s\n", str);
						return;
					}
				last = i;
				break;
 
			case 4:
				t2 = str;
#ifndef MODULE
/* is* routines not available in modules
** the need for this should go away when probing is done.  :-)
** brian@ilinx.com
*/
				while (isdigit(*t2))
					t2++;
 
				if (*t2)
				{
					printk("PC/Xx: Invalid port count %s\n", str);
					return;
				}
#endif
 
				board.numports = simple_strtoul(str, NULL, 0);
				last = i;
				break;
 
			case 5:
#ifndef MODULE
/* is* routines not available in modules
** the need for this should go away when probing is done.  :-)
** brian@ilinx.com
*/
				t2 = str;
				while (isxdigit(*t2))
					t2++;
 
				if (*t2)
				{
					printk("PC/Xx: Invalid port count %s\n", str);
					return;
				}
#endif
 
				board.port = simple_strtoul(str, NULL, 16);
				last = i;
				break;
 
			case 6:
#ifndef MODULE
/* is* routines not available in modules
** the need for this should go away when probing is done.  :-)
** brian@ilinx.com
*/
				t2 = str;
				while (isxdigit(*t2))
					t2++;
 
				if (*t2)
				{
					printk("PC/Xx: Invalid memory base %s\n", str);
					return;
				}
#endif
 
				board.membase = simple_strtoul(str, NULL, 16);
				last = i;
				break;
 
			default:
				printk("PC/Xx: Too many string parms\n");
				return;
		}
		str = temp;
	}
 
	if (last < 6)  
	{
		printk("PC/Xx: Insufficient parms specified\n");
		return;
	}
 
        /* I should REALLY validate the stuff here */
 
	memcpy(&boards[numcards],&board, sizeof(board));
	printk("PC/Xx: Added board %i, %s %s %i ports at 0x%4.4X base 0x%6.6X\n", 
		numcards, board_desc[board.type], board_mem[board.type], 
		board.numports, board.port, (unsigned int) board.membase);
 
	/* keep track of my initial minor number */
        if (numcards)
		boards[numcards].first_minor = boards[numcards-1].first_minor + boards[numcards-1].numports;
	else
		boards[numcards].first_minor = 0;
 
	/* yeha!  string parameter was successful! */
	numcards++;
}
 
 
int pcxe_init(void)
{
	ulong flags, 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;
 
	printk("%s\n", banner);
 
	if (numcards <= 0)
	{
		printk("PC/Xx: No cards configured, exiting.\n");
		return(0);
	}
 
	for (i=0;i<numcards;i++)
		nbdevs += boards[i].numports;
 
	if (nbdevs <= 0)
	{
		printk("PC/Xx: No devices activated, exiting.\n");
		return(0);
	}
 
	/*
	 * this turns out to be more memory efficient, as there are no 
	 * unused spaces.
	 */
	digi_channels = kmalloc(sizeof(struct channel) * nbdevs, GFP_KERNEL);
	if (!digi_channels)
		panic("Unable to allocate digi_channel struct");
	memset(digi_channels, 0, sizeof(struct channel) * nbdevs);
 
	pcxe_table =  kmalloc(sizeof(struct tty_struct *) * nbdevs, GFP_KERNEL);
	if (!pcxe_table)
		panic("Unable to allocate pcxe_table struct");
	memset(pcxe_table, 0, sizeof(struct tty_struct *) * nbdevs);
 
	pcxe_termios = kmalloc(sizeof(struct termios *) * nbdevs, GFP_KERNEL);
	if (!pcxe_termios)
		panic("Unable to allocate pcxe_termios struct");
	memset(pcxe_termios,0,sizeof(struct termios *)*nbdevs);
 
	pcxe_termios_locked = kmalloc(sizeof(struct termios *) * nbdevs, GFP_KERNEL);
	if (!pcxe_termios_locked)
		panic("Unable to allocate pcxe_termios_locked struct");
	memset(pcxe_termios_locked,0,sizeof(struct termios *)*nbdevs);
 
	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 = DIGI_MAJOR; 
	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 | 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 = "cud";
	pcxe_callout.major = DIGICU_MAJOR;
	pcxe_callout.subtype = SERIAL_TYPE_CALLOUT;
	pcxe_callout.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
 
	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("PC/Xx: 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/Xx: 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("PC/Xx: %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("PC/Xx: 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("PC/Xx: 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("PC/Xx: 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("PC/Xx: 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("PC/Xx: 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+bd->first_minor;
		pcxxassert(ch < digi_channels+nbdevs, "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 = bd->first_minor + 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;
			/* zero out flags as it is unassigned at this point
			 * brian@ilinx.com
			 */
			ch->asyncflags = 0;
		}
 
		printk("PC/Xx: %s (%s) I/O=0x%x Mem=0x%lx Ports=%d\n", 
			board_desc[bd->type], board_mem[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");
 
#if 0
	loops_per_sec = save_loops_per_sec;  /* reset it to what it should be */
#endif
 
	/*
	 * 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+bd->first_minor;
 
		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+bd->first_minor;
	pcxxassert(chan0 < digi_channels+nbdevs, "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) = ch->dev - ch->board->first_minor;
 
		*(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) = ch->dev - ch->board->first_minor;
		*(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 don't 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 | CLOCAL);
	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:
			error = verify_area(VERIFY_READ, (void *) arg,sizeof(long));
			if(error)
				return error;
			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:
			error = verify_area(VERIFY_READ, (void *) arg,sizeof(long));
			if(error)
				return error;
			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);
}
 

Go to most recent revision | Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.