aboutsummaryrefslogtreecommitdiff
path: root/drivers/char/epca.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/char/epca.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/char/epca.c')
-rw-r--r--drivers/char/epca.c3789
1 files changed, 3789 insertions, 0 deletions
diff --git a/drivers/char/epca.c b/drivers/char/epca.c
new file mode 100644
index 00000000000..6025e1866c7
--- /dev/null
+++ b/drivers/char/epca.c
@@ -0,0 +1,3789 @@
+/*
+
+
+ Copyright (C) 1996 Digi International.
+
+ For technical support please email digiLinux@dgii.com or
+ call Digi tech support at (612) 912-3456
+
+ Much of this design and code came from epca.c which was
+ copyright (C) 1994, 1995 Troy De Jongh, and subsquently
+ modified by David Nugent, Christoph Lameter, Mike McLagan.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+--------------------------------------------------------------------------- */
+/* See README.epca for change history --DAT*/
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/serial.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#ifdef CONFIG_PCI
+#define ENABLE_PCI
+#endif /* CONFIG_PCI */
+
+#define putUser(arg1, arg2) put_user(arg1, (unsigned long __user *)arg2)
+#define getUser(arg1, arg2) get_user(arg1, (unsigned __user *)arg2)
+
+#ifdef ENABLE_PCI
+#include <linux/pci.h>
+#include "digiPCI.h"
+#endif /* ENABLE_PCI */
+
+#include "digi1.h"
+#include "digiFep1.h"
+#include "epca.h"
+#include "epcaconfig.h"
+
+#if BITS_PER_LONG != 32
+# error FIXME: this driver only works on 32-bit platforms
+#endif
+
+/* ---------------------- Begin defines ------------------------ */
+
+#define VERSION "1.3.0.1-LK"
+
+/* This major needs to be submitted to Linux to join the majors list */
+
+#define DIGIINFOMAJOR 35 /* For Digi specific ioctl */
+
+
+#define MAXCARDS 7
+#define epcaassert(x, msg) if (!(x)) epca_error(__LINE__, msg)
+
+#define PFX "epca: "
+
+/* ----------------- Begin global definitions ------------------- */
+
+static char mesg[100];
+static int nbdevs, num_cards, liloconfig;
+static int digi_poller_inhibited = 1 ;
+
+static int setup_error_code;
+static int invalid_lilo_config;
+
+/* -----------------------------------------------------------------------
+ MAXBOARDS is typically 12, but ISA and EISA cards are restricted to
+ 7 below.
+--------------------------------------------------------------------------*/
+static struct board_info boards[MAXBOARDS];
+
+
+/* ------------- Begin structures used for driver registeration ---------- */
+
+static struct tty_driver *pc_driver;
+static struct tty_driver *pc_info;
+
+/* ------------------ Begin Digi specific structures -------------------- */
+
+/* ------------------------------------------------------------------------
+ digi_channels represents an array of structures that keep track of
+ each channel of the Digi product. Information such as transmit and
+ receive pointers, termio data, and signal definitions (DTR, CTS, etc ...)
+ are stored here. This structure is NOT used to overlay the cards
+ physical channel structure.
+-------------------------------------------------------------------------- */
+
+static struct channel digi_channels[MAX_ALLOC];
+
+/* ------------------------------------------------------------------------
+ card_ptr is an array used to hold the address of the
+ first channel structure of each card. This array will hold
+ the addresses of various channels located in digi_channels.
+-------------------------------------------------------------------------- */
+static struct channel *card_ptr[MAXCARDS];
+
+static struct timer_list epca_timer;
+
+/* ---------------------- Begin function prototypes --------------------- */
+
+/* ----------------------------------------------------------------------
+ Begin generic memory functions. These functions will be alias
+ (point at) more specific functions dependent on the board being
+ configured.
+----------------------------------------------------------------------- */
+
+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);
+
+/* ---- Begin more 'specific' memory functions for cx_like products --- */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxem_globalwinon(struct channel *ch);
+static inline void pcxem_rxwinon(struct channel *ch);
+static inline void pcxem_txwinon(struct channel *ch);
+static inline void pcxem_memoff(struct channel *ch);
+
+/* ------ Begin more 'specific' memory functions for the pcxe ------- */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxe_globalwinon(struct channel *ch);
+static inline void pcxe_rxwinon(struct channel *ch);
+static inline void pcxe_txwinon(struct channel *ch);
+static inline void pcxe_memoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for the pc64xe and pcxi ---- */
+/* Note : pc64xe and pcxi share the same windowing routines */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxi_globalwinon(struct channel *ch);
+static inline void pcxi_rxwinon(struct channel *ch);
+static inline void pcxi_txwinon(struct channel *ch);
+static inline void pcxi_memoff(struct channel *ch);
+
+/* - Begin 'specific' do nothing memory functions needed for some cards - */
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win);
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win);
+static inline void dummy_globalwinon(struct channel *ch);
+static inline void dummy_rxwinon(struct channel *ch);
+static inline void dummy_txwinon(struct channel *ch);
+static inline void dummy_memoff(struct channel *ch);
+static inline void dummy_assertgwinon(struct channel *ch);
+static inline void dummy_assertmemoff(struct channel *ch);
+
+/* ------------------- Begin declare functions ----------------------- */
+
+static inline struct channel *verifyChannel(register struct tty_struct *);
+static inline void pc_sched_event(struct channel *, int);
+static void epca_error(int, char *);
+static void pc_close(struct tty_struct *, struct file *);
+static void shutdown(struct channel *);
+static void pc_hangup(struct tty_struct *);
+static void pc_put_char(struct tty_struct *, unsigned char);
+static int pc_write_room(struct tty_struct *);
+static int pc_chars_in_buffer(struct tty_struct *);
+static void pc_flush_buffer(struct tty_struct *);
+static void pc_flush_chars(struct tty_struct *);
+static int block_til_ready(struct tty_struct *, struct file *,
+ struct channel *);
+static int pc_open(struct tty_struct *, struct file *);
+static void post_fep_init(unsigned int crd);
+static void epcapoll(unsigned long);
+static void doevent(int);
+static void fepcmd(struct channel *, int, int, int, int, int);
+static unsigned termios2digi_h(struct channel *ch, unsigned);
+static unsigned termios2digi_i(struct channel *ch, unsigned);
+static unsigned termios2digi_c(struct channel *ch, unsigned);
+static void epcaparam(struct tty_struct *, struct channel *);
+static void receive_data(struct channel *);
+static int pc_ioctl(struct tty_struct *, struct file *,
+ unsigned int, unsigned long);
+static int info_ioctl(struct tty_struct *, struct file *,
+ unsigned int, unsigned long);
+static void pc_set_termios(struct tty_struct *, struct termios *);
+static void do_softint(void *);
+static void pc_stop(struct tty_struct *);
+static void pc_start(struct tty_struct *);
+static void pc_throttle(struct tty_struct * tty);
+static void pc_unthrottle(struct tty_struct *tty);
+static void digi_send_break(struct channel *ch, int msec);
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch);
+void epca_setup(char *, int *);
+void console_print(const char *);
+
+static int get_termio(struct tty_struct *, struct termio __user *);
+static int pc_write(struct tty_struct *, const unsigned char *, int);
+int pc_init(void);
+
+#ifdef ENABLE_PCI
+static int init_PCI(void);
+#endif /* ENABLE_PCI */
+
+
+/* ------------------------------------------------------------------
+ Table of functions for each board to handle memory. Mantaining
+ parallelism is a *very* good idea here. The idea is for the
+ runtime code to blindly call these functions, not knowing/caring
+ about the underlying hardware. This stuff should contain no
+ conditionals; if more functionality is needed a different entry
+ should be established. These calls are the interface calls and
+ are the only functions that should be accessed. Anyone caught
+ making direct calls deserves what they get.
+-------------------------------------------------------------------- */
+
+static inline void memwinon(struct board_info *b, unsigned int win)
+{
+ (b->memwinon)(b, win);
+}
+
+static inline void memwinoff(struct board_info *b, unsigned int win)
+{
+ (b->memwinoff)(b, win);
+}
+
+static inline void globalwinon(struct channel *ch)
+{
+ (ch->board->globalwinon)(ch);
+}
+
+static inline void rxwinon(struct channel *ch)
+{
+ (ch->board->rxwinon)(ch);
+}
+
+static inline void txwinon(struct channel *ch)
+{
+ (ch->board->txwinon)(ch);
+}
+
+static inline void memoff(struct channel *ch)
+{
+ (ch->board->memoff)(ch);
+}
+static inline void assertgwinon(struct channel *ch)
+{
+ (ch->board->assertgwinon)(ch);
+}
+
+static inline void assertmemoff(struct channel *ch)
+{
+ (ch->board->assertmemoff)(ch);
+}
+
+/* ---------------------------------------------------------
+ PCXEM windowing is the same as that used in the PCXR
+ and CX series cards.
+------------------------------------------------------------ */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win)
+{
+ outb_p(FEPWIN|win, (int)b->port + 1);
+}
+
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win)
+{
+ outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxem_globalwinon(struct channel *ch)
+{
+ outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_rxwinon(struct channel *ch)
+{
+ outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_txwinon(struct channel *ch)
+{
+ outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_memoff(struct channel *ch)
+{
+ outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ----------------- Begin pcxe memory window stuff ------------------ */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win)
+{
+ outb_p(FEPWIN | win, (int)b->port + 1);
+}
+
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win)
+{
+ outb_p(inb((int)b->port) & ~FEPMEM,
+ (int)b->port + 1);
+ outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxe_globalwinon(struct channel *ch)
+{
+ outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_rxwinon(struct channel *ch)
+{
+ outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_txwinon(struct channel *ch)
+{
+ outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_memoff(struct channel *ch)
+{
+ outb_p(0, (int)ch->board->port);
+ outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ------------- Begin pc64xe and pcxi memory window stuff -------------- */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win)
+{
+ outb_p(inb((int)b->port) | FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win)
+{
+ outb_p(inb((int)b->port) & ~FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_globalwinon(struct channel *ch)
+{
+ outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_rxwinon(struct channel *ch)
+{
+ outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_txwinon(struct channel *ch)
+{
+ outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_memoff(struct channel *ch)
+{
+ outb_p(0, (int)ch->board->port);
+}
+
+static inline void pcxi_assertgwinon(struct channel *ch)
+{
+ epcaassert(inb((int)ch->board->port) & FEPMEM, "Global memory off");
+}
+
+static inline void pcxi_assertmemoff(struct channel *ch)
+{
+ epcaassert(!(inb((int)ch->board->port) & FEPMEM), "Memory on");
+}
+
+
+/* ----------------------------------------------------------------------
+ Not all of the cards need specific memory windowing routines. Some
+ cards (Such as PCI) needs no windowing routines at all. We provide
+ these do nothing routines so that the same code base can be used.
+ The driver will ALWAYS call a windowing routine if it thinks it needs
+ to; regardless of the card. However, dependent on the card the routine
+ may or may not do anything.
+---------------------------------------------------------------------------*/
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_globalwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_rxwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_txwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_memoff(struct channel *ch)
+{
+}
+
+static inline void dummy_assertgwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_assertmemoff(struct channel *ch)
+{
+}
+
+/* ----------------- Begin verifyChannel function ----------------------- */
+static inline struct channel *verifyChannel(register struct tty_struct *tty)
+{ /* Begin verifyChannel */
+
+ /* --------------------------------------------------------------------
+ This routine basically provides a sanity check. It insures that
+ the channel returned is within the proper range of addresses as
+ well as properly initialized. If some bogus info gets passed in
+ through tty->driver_data this should catch it.
+ --------------------------------------------------------------------- */
+
+ if (tty)
+ { /* Begin if tty */
+
+ register struct channel *ch = (struct channel *)tty->driver_data;
+
+ if ((ch >= &digi_channels[0]) && (ch < &digi_channels[nbdevs]))
+ {
+ if (ch->magic == EPCA_MAGIC)
+ return ch;
+ }
+
+ } /* End if tty */
+
+ /* Else return a NULL for invalid */
+ return NULL;
+
+} /* End verifyChannel */
+
+/* ------------------ Begin pc_sched_event ------------------------- */
+
+static inline void pc_sched_event(struct channel *ch, int event)
+{ /* Begin pc_sched_event */
+
+
+ /* ----------------------------------------------------------------------
+ We call this to schedule interrupt processing on some event. The
+ kernel sees our request and calls the related routine in OUR driver.
+ -------------------------------------------------------------------------*/
+
+ ch->event |= 1 << event;
+ schedule_work(&ch->tqueue);
+
+
+} /* End pc_sched_event */
+
+/* ------------------ Begin epca_error ------------------------- */
+
+static void epca_error(int line, char *msg)
+{ /* Begin epca_error */
+
+ printk(KERN_ERR "epca_error (Digi): line = %d %s\n",line,msg);
+ return;
+
+} /* End epca_error */
+
+/* ------------------ Begin pc_close ------------------------- */
+static void pc_close(struct tty_struct * tty, struct file * filp)
+{ /* Begin pc_close */
+
+ struct channel *ch;
+ unsigned long flags;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if ch != NULL */
+
+ save_flags(flags);
+ cli();
+
+ if (tty_hung_up_p(filp))
+ {
+ restore_flags(flags);
+ return;
+ }
+
+ /* Check to see if the channel is open more than once */
+ if (ch->count-- > 1)
+ { /* Begin channel is open more than once */
+
+ /* -------------------------------------------------------------
+ Return without doing anything. Someone might still be using
+ the channel.
+ ---------------------------------------------------------------- */
+
+ restore_flags(flags);
+ return;
+ } /* End channel is open more than once */
+
+ /* Port open only once go ahead with shutdown & reset */
+
+ if (ch->count < 0)
+ {
+ ch->count = 0;
+ }
+
+ /* ---------------------------------------------------------------
+ Let the rest of the driver know the channel is being closed.
+ This becomes important if an open is attempted before close
+ is finished.
+ ------------------------------------------------------------------ */
+
+ ch->asyncflags |= ASYNC_CLOSING;
+
+ tty->closing = 1;
+
+ if (ch->asyncflags & ASYNC_INITIALIZED)
+ {
+ /* Setup an event to indicate when the transmit buffer empties */
+ setup_empty_event(tty, ch);
+ tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+ }
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ tty_ldisc_flush(tty);
+ shutdown(ch);
+ tty->closing = 0;
+ ch->event = 0;
+ ch->tty = NULL;
+
+ if (ch->blocked_open)
+ { /* Begin if blocked_open */
+
+ if (ch->close_delay)
+ {
+ msleep_interruptible(jiffies_to_msecs(ch->close_delay));
+ }
+
+ wake_up_interruptible(&ch->open_wait);
+
+ } /* End if blocked_open */
+
+ ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED |
+ ASYNC_CLOSING);
+ wake_up_interruptible(&ch->close_wait);
+
+
+ restore_flags(flags);
+
+ } /* End if ch != NULL */
+
+} /* End pc_close */
+
+/* ------------------ Begin shutdown ------------------------- */
+
+static void shutdown(struct channel *ch)
+{ /* Begin shutdown */
+
+ unsigned long flags;
+ struct tty_struct *tty;
+ volatile struct board_chan *bc;
+
+ if (!(ch->asyncflags & ASYNC_INITIALIZED))
+ return;
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+
+ bc = ch->brdchan;
+
+ /* ------------------------------------------------------------------
+ In order for an event to be generated on the receipt of data the
+ idata flag must be set. Since we are shutting down, this is not
+ necessary clear this flag.
+ --------------------------------------------------------------------- */
+
+ if (bc)
+ bc->idata = 0;
+
+ tty = ch->tty;
+
+ /* ----------------------------------------------------------------
+ If we're a modem control device and HUPCL is on, drop RTS & DTR.
+ ------------------------------------------------------------------ */
+
+ if (tty->termios->c_cflag & HUPCL)
+ {
+ ch->omodem &= ~(ch->m_rts | ch->m_dtr);
+ fepcmd(ch, SETMODEM, 0, ch->m_dtr | ch->m_rts, 10, 1);
+ }
+
+ memoff(ch);
+
+ /* ------------------------------------------------------------------
+ The channel has officialy been closed. The next time it is opened
+ it will have to reinitialized. Set a flag to indicate this.
+ ---------------------------------------------------------------------- */
+
+ /* Prevent future Digi programmed interrupts from coming active */
+
+ ch->asyncflags &= ~ASYNC_INITIALIZED;
+ restore_flags(flags);
+
+} /* End shutdown */
+
+/* ------------------ Begin pc_hangup ------------------------- */
+
+static void pc_hangup(struct tty_struct *tty)
+{ /* Begin pc_hangup */
+
+ struct channel *ch;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if ch != NULL */
+
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ shutdown(ch);
+
+ ch->tty = NULL;
+ ch->event = 0;
+ ch->count = 0;
+ restore_flags(flags);
+ ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED);
+ wake_up_interruptible(&ch->open_wait);
+
+ } /* End if ch != NULL */
+
+} /* End pc_hangup */
+
+/* ------------------ Begin pc_write ------------------------- */
+
+static int pc_write(struct tty_struct * tty,
+ const unsigned char *buf, int bytesAvailable)
+{ /* Begin pc_write */
+
+ register unsigned int head, tail;
+ register int dataLen;
+ register int size;
+ register int amountCopied;
+
+
+ struct channel *ch;
+ unsigned long flags;
+ int remain;
+ volatile struct board_chan *bc;
+
+
+ /* ----------------------------------------------------------------
+ pc_write is primarily called directly by the kernel routine
+ tty_write (Though it can also be called by put_char) found in
+ tty_io.c. pc_write is passed a line discipline buffer where
+ the data to be written out is stored. The line discipline
+ implementation itself is done at the kernel level and is not
+ brought into the driver.
+ ------------------------------------------------------------------- */
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) == NULL)
+ return 0;
+
+ /* Make a pointer to the channel data structure found on the board. */
+
+ bc = ch->brdchan;
+ size = ch->txbufsize;
+
+ amountCopied = 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, head has not wrapped around. */
+ if (head >= tail)
+ { /* Begin head has not wrapped */
+
+ /* ---------------------------------------------------------------
+ remain (much like dataLen above) represents the total amount of
+ space available on the card for data. Here dataLen represents
+ the space existing between the head pointer and the end of
+ buffer. This is important because a memcpy cannot be told to
+ automatically wrap around when it hits the buffer end.
+ ------------------------------------------------------------------ */
+
+ dataLen = size - head;
+ remain = size - (head - tail) - 1;
+
+ } /* End head has not wrapped */
+ else
+ { /* Begin head has wrapped around */
+
+ remain = tail - head - 1;
+ dataLen = remain;
+
+ } /* End head has wrapped around */
+
+ /* -------------------------------------------------------------------
+ Check the space on the card. If we have more data than
+ space; reduce the amount of data to fit the space.
+ ---------------------------------------------------------------------- */
+
+ bytesAvailable = min(remain, bytesAvailable);
+
+ txwinon(ch);
+ while (bytesAvailable > 0)
+ { /* Begin while there is data to copy onto card */
+
+ /* -----------------------------------------------------------------
+ If head is not wrapped, the below will make sure the first
+ data copy fills to the end of card buffer.
+ ------------------------------------------------------------------- */
+
+ dataLen = min(bytesAvailable, dataLen);
+ memcpy(ch->txptr + head, buf, dataLen);
+ buf += dataLen;
+ head += dataLen;
+ amountCopied += dataLen;
+ bytesAvailable -= dataLen;
+
+ if (head >= size)
+ {
+ head = 0;
+ dataLen = tail;
+ }
+
+ } /* End while there is data to copy onto card */
+
+ 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(amountCopied);
+
+} /* End pc_write */
+
+/* ------------------ Begin pc_put_char ------------------------- */
+
+static void pc_put_char(struct tty_struct *tty, unsigned char c)
+{ /* Begin pc_put_char */
+
+
+ pc_write(tty, &c, 1);
+ return;
+
+} /* End pc_put_char */
+
+/* ------------------ Begin pc_write_room ------------------------- */
+
+static int pc_write_room(struct tty_struct *tty)
+{ /* Begin pc_write_room */
+
+ int remain;
+ struct channel *ch;
+ unsigned long flags;
+ unsigned int head, tail;
+ volatile struct board_chan *bc;
+
+ remain = 0;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ {
+ 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;
+ /* Wrap tail if necessary */
+ 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 how much room is left on card */
+ return remain;
+
+} /* End pc_write_room */
+
+/* ------------------ Begin pc_chars_in_buffer ---------------------- */
+
+static int pc_chars_in_buffer(struct tty_struct *tty)
+{ /* Begin pc_chars_in_buffer */
+
+ int chars;
+ unsigned int ctail, head, tail;
+ int remain;
+ unsigned long flags;
+ struct channel *ch;
+ volatile struct board_chan *bc;
+
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(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
+ { /* Begin if some space on the card has been used */
+
+ head = bc->tin & (ch->txbufsize - 1);
+ tail &= (ch->txbufsize - 1);
+
+ /* --------------------------------------------------------------
+ The logic here is basically opposite of the above pc_write_room
+ here we are finding the amount of bytes in the buffer filled.
+ Not the amount of bytes empty.
+ ------------------------------------------------------------------- */
+
+ 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 not already set. Setup an event to indicate when the
+ transmit buffer empties
+ ----------------------------------------------------------------- */
+
+ if (!(ch->statusflags & EMPTYWAIT))
+ setup_empty_event(tty,ch);
+
+ } /* End if some space on the card has been used */
+
+ memoff(ch);
+ restore_flags(flags);
+
+ /* Return number of characters residing on card. */
+ return(chars);
+
+} /* End pc_chars_in_buffer */
+
+/* ------------------ Begin pc_flush_buffer ---------------------- */
+
+static void pc_flush_buffer(struct tty_struct *tty)
+{ /* Begin pc_flush_buffer */
+
+ unsigned int tail;
+ unsigned long flags;
+ struct channel *ch;
+ volatile struct board_chan *bc;
+
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) == NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+
+ globalwinon(ch);
+
+ bc = ch->brdchan;
+ tail = bc->tout;
+
+ /* Have FEP move tout pointer; effectively flushing transmit buffer */
+
+ fepcmd(ch, STOUT, (unsigned) tail, 0, 0, 0);
+
+ memoff(ch);
+ restore_flags(flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+
+} /* End pc_flush_buffer */
+
+/* ------------------ Begin pc_flush_chars ---------------------- */
+
+static void pc_flush_chars(struct tty_struct *tty)
+{ /* Begin pc_flush_chars */
+
+ struct channel * ch;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ {
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ /* ----------------------------------------------------------------
+ If not already set and the transmitter is busy setup an event
+ to indicate when the transmit empties.
+ ------------------------------------------------------------------- */
+
+ if ((ch->statusflags & TXBUSY) && !(ch->statusflags & EMPTYWAIT))
+ setup_empty_event(tty,ch);
+
+ restore_flags(flags);
+ }
+
+} /* End pc_flush_chars */
+
+/* ------------------ Begin block_til_ready ---------------------- */
+
+static int block_til_ready(struct tty_struct *tty,
+ struct file *filp, struct channel *ch)
+{ /* Begin block_til_ready */
+
+ DECLARE_WAITQUEUE(wait,current);
+ int retval, do_clocal = 0;
+ unsigned long flags;
+
+
+ if (tty_hung_up_p(filp))
+ {
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ return(retval);
+ }
+
+ /* -----------------------------------------------------------------
+ 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;
+ }
+
+ if (filp->f_flags & O_NONBLOCK)
+ {
+ /* -----------------------------------------------------------------
+ If non-blocking mode is set, then make the check up front
+ and then exit.
+ -------------------------------------------------------------------- */
+
+ ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+ return 0;
+ }
+
+
+ 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(&ch->open_wait, &wait);
+ save_flags(flags);
+ cli();
+
+
+ /* We dec count so that pc_close will know when to free things */
+ if (!tty_hung_up_p(filp))
+ ch->count--;
+
+ restore_flags(flags);
+
+ ch->blocked_open++;
+
+ while(1)
+ { /* Begin forever while */
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (tty_hung_up_p(filp) ||
+ !(ch->asyncflags & ASYNC_INITIALIZED))
+ {
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ if (!(ch->asyncflags & ASYNC_CLOSING) &&
+ (do_clocal || (ch->imodem & ch->dcd)))
+ break;
+
+ if (signal_pending(current))
+ {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ /* ---------------------------------------------------------------
+ Allow someone else to be scheduled. We will occasionally go
+ through this loop until one of the above conditions change.
+ The below schedule call will allow other processes to enter and
+ prevent this loop from hogging the cpu.
+ ------------------------------------------------------------------ */
+ schedule();
+
+ } /* End forever while */
+
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&ch->open_wait, &wait);
+ cli();
+ if (!tty_hung_up_p(filp))
+ ch->count++;
+ restore_flags(flags);
+
+ ch->blocked_open--;
+
+ if (retval)
+ return retval;
+
+ ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+ return 0;
+
+} /* End block_til_ready */
+
+/* ------------------ Begin pc_open ---------------------- */
+
+static int pc_open(struct tty_struct *tty, struct file * filp)
+{ /* Begin pc_open */
+
+ struct channel *ch;
+ unsigned long flags;
+ int line, retval, boardnum;<