freebsd-skq/sys/i386/isa/stallion.c
Julian Elischer b40ce4165d KSE Milestone 2
Note ALL MODULES MUST BE RECOMPILED
make the kernel aware that there are smaller units of scheduling than the
process. (but only allow one thread per process at this time).
This is functionally equivalent to teh previousl -current except
that there is a thread associated with each process.

Sorry john! (your next MFC will be a doosie!)

Reviewed by: peter@freebsd.org, dillon@freebsd.org

X-MFC after:    ha ha ha ha
2001-09-12 08:38:13 +00:00

3089 lines
78 KiB
C

/*****************************************************************************/
/*
* stallion.c -- stallion multiport serial driver.
*
* Copyright (c) 1995-1996 Greg Ungerer (gerg@stallion.oz.au).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Greg Ungerer.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*****************************************************************************/
#define TTYDEFCHARS 1
#include "opt_compat.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/bus.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/ic/scd1400.h>
#include <machine/comstats.h>
#include "pci.h"
#ifdef COMPILING_LINT
#warning "The stallion pci device is broken and not compiled with LINT"
#undef NPCI
#define NPCI 0
#endif
#if NPCI > 0
#ifndef COMPAT_OLDPCI
#error "The stallion pci driver requires the old pci compatibility shims"
#endif
#include <pci/pcivar.h>
#include <pci/pcireg.h>
#endif
/*****************************************************************************/
/*
* Define the version level of the kernel - so we can compile in the
* appropriate bits of code. By default this will compile for a 2.1
* level kernel.
*/
#define VFREEBSD 220
#if VFREEBSD >= 220
#define STATIC static
#else
#define STATIC
#endif
/*****************************************************************************/
/*
* Define different board types. At the moment I have only declared
* those boards that this driver supports. But I will use the standard
* "assigned" board numbers. In the future this driver will support
* some of the other Stallion boards. Currently supported boards are
* abbreviated as EIO = EasyIO and ECH = EasyConnection 8/32.
*/
#define BRD_EASYIO 20
#define BRD_ECH 21
#define BRD_ECHMC 22
#define BRD_ECHPCI 26
/*
* When using the BSD "config" stuff there is no easy way to specifiy
* a secondary IO address region. So it is hard wired here. Also the
* shared interrupt information is hard wired here...
*/
static unsigned int stl_ioshared = 0x280;
static unsigned int stl_irqshared = 0;
/*****************************************************************************/
/*
* Define important driver limitations.
*/
#define STL_MAXBRDS 8
#define STL_MAXPANELS 4
#define STL_PORTSPERPANEL 16
#define STL_PORTSPERBRD 64
/*
* Define the important minor number break down bits. These have been
* chosen to be "compatible" with the standard sio driver minor numbers.
* Extra high bits are used to distinguish between boards.
*/
#define STL_CALLOUTDEV 0x80
#define STL_CTRLLOCK 0x40
#define STL_CTRLINIT 0x20
#define STL_CTRLDEV (STL_CTRLLOCK | STL_CTRLINIT)
#define STL_MEMDEV 0x07000000
#define STL_DEFSPEED TTYDEF_SPEED
#define STL_DEFCFLAG (CS8 | CREAD | HUPCL)
/*
* I haven't really decided (or measured) what buffer sizes give
* a good balance between performance and memory usage. These seem
* to work pretty well...
*/
#define STL_RXBUFSIZE 2048
#define STL_TXBUFSIZE 2048
#define STL_TXBUFLOW (STL_TXBUFSIZE / 4)
#define STL_RXBUFHIGH (3 * STL_RXBUFSIZE / 4)
/*****************************************************************************/
/*
* Define our local driver identity first. Set up stuff to deal with
* all the local structures required by a serial tty driver.
*/
static const char stl_drvname[] = "stl";
static const char stl_longdrvname[] = "Stallion Multiport Serial Driver";
static const char stl_drvversion[] = "1.0.0";
static int stl_brdprobed[STL_MAXBRDS];
static int stl_nrbrds = 0;
static int stl_doingtimeout = 0;
static const char __file__[] = /*__FILE__*/ "stallion.c";
/*
* Define global stats structures. Not used often, and can be
* re-used for each stats call.
*/
static combrd_t stl_brdstats;
static comstats_t stl_comstats;
/*****************************************************************************/
/*
* Define a set of structures to hold all the board/panel/port info
* for our ports. These will be dynamically allocated as required.
*/
/*
* Define a ring queue structure for each port. This will hold the
* TX data waiting to be output. Characters are fed into this buffer
* from the line discipline (or even direct from user space!) and
* then fed into the UARTs during interrupts. Will use a clasic ring
* queue here for this. The good thing about this type of ring queue
* is that the head and tail pointers can be updated without interrupt
* protection - since "write" code only needs to change the head, and
* interrupt code only needs to change the tail.
*/
typedef struct {
char *buf;
char *endbuf;
char *head;
char *tail;
} stlrq_t;
/*
* Port, panel and board structures to hold status info about each.
* The board structure contains pointers to structures for each panel
* connected to it, and in turn each panel structure contains pointers
* for each port structure for each port on that panel. Note that
* the port structure also contains the board and panel number that it
* is associated with, this makes it (fairly) easy to get back to the
* board/panel info for a port. Also note that the tty struct is at
* the top of the structure, this is important, since the code uses
* this fact to get the port struct pointer from the tty struct
* pointer!
*/
typedef struct {
struct tty tty;
int portnr;
int panelnr;
int brdnr;
int ioaddr;
int uartaddr;
int pagenr;
int callout;
int brklen;
int dtrwait;
int dotimestamp;
int waitopens;
int hotchar;
unsigned int state;
unsigned int hwid;
unsigned int sigs;
unsigned int rxignoremsk;
unsigned int rxmarkmsk;
unsigned long clk;
struct termios initintios;
struct termios initouttios;
struct termios lockintios;
struct termios lockouttios;
struct timeval timestamp;
comstats_t stats;
stlrq_t tx;
stlrq_t rx;
stlrq_t rxstatus;
} stlport_t;
typedef struct {
int panelnr;
int brdnr;
int pagenr;
int nrports;
int iobase;
unsigned int hwid;
unsigned int ackmask;
stlport_t *ports[STL_PORTSPERPANEL];
} stlpanel_t;
typedef struct {
int brdnr;
int brdtype;
int unitid;
int state;
int nrpanels;
int nrports;
int irq;
int irqtype;
unsigned int ioaddr1;
unsigned int ioaddr2;
unsigned int iostatus;
unsigned int ioctrl;
unsigned int ioctrlval;
unsigned int hwid;
unsigned long clk;
stlpanel_t *panels[STL_MAXPANELS];
stlport_t *ports[STL_PORTSPERBRD];
} stlbrd_t;
static stlbrd_t *stl_brds[STL_MAXBRDS];
/*
* Per board state flags. Used with the state field of the board struct.
* Not really much here yet!
*/
#define BRD_FOUND 0x1
/*
* Define the port structure state flags. These set of flags are
* modified at interrupt time - so setting and reseting them needs
* to be atomic.
*/
#define ASY_TXLOW 0x1
#define ASY_RXDATA 0x2
#define ASY_DCDCHANGE 0x4
#define ASY_DTRWAIT 0x8
#define ASY_RTSFLOW 0x10
#define ASY_RTSFLOWMODE 0x20
#define ASY_CTSFLOWMODE 0x40
#define ASY_ACTIVE (ASY_TXLOW | ASY_RXDATA | ASY_DCDCHANGE)
/*
* Define an array of board names as printable strings. Handy for
* referencing boards when printing trace and stuff.
*/
static char *stl_brdnames[] = {
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
(char *) NULL,
"EasyIO",
"EC8/32-AT",
"EC8/32-MC",
(char *) NULL,
(char *) NULL,
(char *) NULL,
"EC8/32-PCI",
};
/*****************************************************************************/
/*
* Hardware ID bits for the EasyIO and ECH boards. These defines apply
* to the directly accessible io ports of these boards (not the cd1400
* uarts - they are in scd1400.h).
*/
#define EIO_8PORTRS 0x04
#define EIO_4PORTRS 0x05
#define EIO_8PORTDI 0x00
#define EIO_8PORTM 0x06
#define EIO_IDBITMASK 0x07
#define EIO_INTRPEND 0x08
#define EIO_INTEDGE 0x00
#define EIO_INTLEVEL 0x08
#define ECH_ID 0xa0
#define ECH_IDBITMASK 0xe0
#define ECH_BRDENABLE 0x08
#define ECH_BRDDISABLE 0x00
#define ECH_INTENABLE 0x01
#define ECH_INTDISABLE 0x00
#define ECH_INTLEVEL 0x02
#define ECH_INTEDGE 0x00
#define ECH_INTRPEND 0x01
#define ECH_BRDRESET 0x01
#define ECHMC_INTENABLE 0x01
#define ECHMC_BRDRESET 0x02
#define ECH_PNLSTATUS 2
#define ECH_PNL16PORT 0x20
#define ECH_PNLIDMASK 0x07
#define ECH_PNLINTRPEND 0x80
#define ECH_ADDR2MASK 0x1e0
#define EIO_CLK 25000000
#define EIO_CLK8M 20000000
#define ECH_CLK EIO_CLK
/*
* Define the offsets within the register bank for all io registers.
* These io address offsets are common to both the EIO and ECH.
*/
#define EREG_ADDR 0
#define EREG_DATA 4
#define EREG_RXACK 5
#define EREG_TXACK 6
#define EREG_MDACK 7
#define EREG_BANKSIZE 8
/*
* Define the PCI vendor and device id for ECH8/32-PCI.
*/
#define STL_PCIDEVID 0xd001100b
/*
* Define the vector mapping bits for the programmable interrupt board
* hardware. These bits encode the interrupt for the board to use - it
* is software selectable (except the EIO-8M).
*/
static unsigned char stl_vecmap[] = {
0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07,
0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03
};
/*
* Set up enable and disable macros for the ECH boards. They require
* the secondary io address space to be activated and deactivated.
* This way all ECH boards can share their secondary io region.
* If this is an ECH-PCI board then also need to set the page pointer
* to point to the correct page.
*/
#define BRDENABLE(brdnr,pagenr) \
if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \
outb(stl_brds[(brdnr)]->ioctrl, \
(stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE));\
else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI) \
outb(stl_brds[(brdnr)]->ioctrl, (pagenr));
#define BRDDISABLE(brdnr) \
if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \
outb(stl_brds[(brdnr)]->ioctrl, \
(stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE));
/*
* Define the cd1400 baud rate clocks. These are used when calculating
* what clock and divisor to use for the required baud rate. Also
* define the maximum baud rate allowed, and the default base baud.
*/
static int stl_cd1400clkdivs[] = {
CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4
};
#define STL_MAXBAUD 230400
/*****************************************************************************/
/*
* Define macros to extract a brd and port number from a minor number.
* This uses the extended minor number range in the upper 2 bytes of
* the device number. This gives us plenty of minor numbers to play
* with...
*/
#define MKDEV2BRD(m) ((minor(m) & 0x00700000) >> 20)
#define MKDEV2PORT(m) ((minor(m) & 0x1f) | ((minor(m) & 0x00010000) >> 11))
/*
* Define some handy local macros...
*/
#ifndef MIN
#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
#endif
/*****************************************************************************/
/*
* Declare all those functions in this driver! First up is the set of
* externally visible functions.
*/
static int stlprobe(struct isa_device *idp);
static int stlattach(struct isa_device *idp);
STATIC d_open_t stlopen;
STATIC d_close_t stlclose;
STATIC d_ioctl_t stlioctl;
/*
* Internal function prototypes.
*/
static stlport_t *stl_dev2port(dev_t dev);
static int stl_findfreeunit(void);
static int stl_rawopen(stlport_t *portp);
static int stl_rawclose(stlport_t *portp);
static int stl_param(struct tty *tp, struct termios *tiosp);
static void stl_start(struct tty *tp);
static void stl_stop(struct tty *tp, int);
static void stl_ttyoptim(stlport_t *portp, struct termios *tiosp);
static void stl_dotimeout(void);
static void stl_poll(void *arg);
static void stl_rxprocess(stlport_t *portp);
static void stl_dtrwakeup(void *arg);
static int stl_brdinit(stlbrd_t *brdp);
static int stl_initeio(stlbrd_t *brdp);
static int stl_initech(stlbrd_t *brdp);
static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp);
static ointhand2_t stlintr;
static __inline void stl_txisr(stlpanel_t *panelp, int ioaddr);
static __inline void stl_rxisr(stlpanel_t *panelp, int ioaddr);
static __inline void stl_mdmisr(stlpanel_t *panelp, int ioaddr);
static void stl_setreg(stlport_t *portp, int regnr, int value);
static int stl_getreg(stlport_t *portp, int regnr);
static int stl_updatereg(stlport_t *portp, int regnr, int value);
static int stl_getsignals(stlport_t *portp);
static void stl_setsignals(stlport_t *portp, int dtr, int rts);
static void stl_flowcontrol(stlport_t *portp, int hw, int sw);
static void stl_ccrwait(stlport_t *portp);
static void stl_enablerxtx(stlport_t *portp, int rx, int tx);
static void stl_startrxtx(stlport_t *portp, int rx, int tx);
static void stl_disableintrs(stlport_t *portp);
static void stl_sendbreak(stlport_t *portp, long len);
static void stl_flush(stlport_t *portp, int flag);
static int stl_memioctl(dev_t dev, unsigned long cmd, caddr_t data,
int flag, struct thread *td);
static int stl_getbrdstats(caddr_t data);
static int stl_getportstats(stlport_t *portp, caddr_t data);
static int stl_clrportstats(stlport_t *portp, caddr_t data);
static stlport_t *stl_getport(int brdnr, int panelnr, int portnr);
#if NPCI > 0
static const char *stlpciprobe(pcici_t tag, pcidi_t type);
static void stlpciattach(pcici_t tag, int unit);
static void stlpciintr(void * arg);
#endif
/*****************************************************************************/
/*
* Declare the driver isa structure.
*/
struct isa_driver stldriver = {
INTR_TYPE_TTY,
stlprobe,
stlattach,
"stl"
};
COMPAT_ISA_DRIVER(stl, stldriver);
/*****************************************************************************/
#if NPCI > 0
/*
* Declare the driver pci structure.
*/
static unsigned long stl_count;
static struct pci_device stlpcidriver = {
"stl",
stlpciprobe,
stlpciattach,
&stl_count,
NULL,
};
COMPAT_PCI_DRIVER (stlpci, stlpcidriver);
#endif
/*****************************************************************************/
#if VFREEBSD >= 220
/*
* FreeBSD-2.2+ kernel linkage.
*/
#define CDEV_MAJOR 72
static struct cdevsw stl_cdevsw = {
/* open */ stlopen,
/* close */ stlclose,
/* read */ ttyread,
/* write */ ttywrite,
/* ioctl */ stlioctl,
/* poll */ ttypoll,
/* mmap */ nommap,
/* strategy */ nostrategy,
/* name */ "stl",
/* maj */ CDEV_MAJOR,
/* dump */ nodump,
/* psize */ nopsize,
/* flags */ D_TTY | D_KQFILTER,
/* kqfilter */ ttykqfilter,
};
static void stl_drvinit(void *unused)
{
cdevsw_add(&stl_cdevsw);
}
SYSINIT(sidev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,stl_drvinit,NULL)
#endif
/*****************************************************************************/
/*
* Probe for some type of EasyIO or EasyConnection 8/32 board at
* the supplied address. All we do is check if we can find the
* board ID for the board... (Note, PCI boards not checked here,
* they are done in the stlpciprobe() routine).
*/
static int stlprobe(struct isa_device *idp)
{
unsigned int status;
#if DEBUG
printf("stlprobe(idp=%x): unit=%d iobase=%x\n", (int) idp,
idp->id_unit, idp->id_iobase);
#endif
if (idp->id_unit > STL_MAXBRDS)
return(0);
status = inb(idp->id_iobase + 1);
if ((status & ECH_IDBITMASK) == ECH_ID) {
stl_brdprobed[idp->id_unit] = BRD_ECH;
return(1);
}
status = inb(idp->id_iobase + 2);
switch (status & EIO_IDBITMASK) {
case EIO_8PORTRS:
case EIO_8PORTM:
case EIO_8PORTDI:
case EIO_4PORTRS:
stl_brdprobed[idp->id_unit] = BRD_EASYIO;
return(1);
default:
break;
}
return(0);
}
/*****************************************************************************/
/*
* Find an available internal board number (unit number). The problem
* is that the same unit numbers can be assigned to different boards
* detected during the ISA and PCI initialization phases.
*/
static int stl_findfreeunit()
{
int i;
for (i = 0; (i < STL_MAXBRDS); i++)
if (stl_brds[i] == (stlbrd_t *) NULL)
break;
return((i >= STL_MAXBRDS) ? -1 : i);
}
/*****************************************************************************/
/*
* Allocate resources for and initialize the specified board.
*/
static int stlattach(struct isa_device *idp)
{
stlbrd_t *brdp;
#if DEBUG
printf("stlattach(idp=%p): unit=%d iobase=%x\n", (void *) idp,
idp->id_unit, idp->id_iobase);
#endif
idp->id_ointr = stlintr;
brdp = (stlbrd_t *) malloc(sizeof(stlbrd_t), M_TTYS, M_NOWAIT | M_ZERO);
if (brdp == (stlbrd_t *) NULL) {
printf("STALLION: failed to allocate memory (size=%d)\n",
sizeof(stlbrd_t));
return(0);
}
if ((brdp->brdnr = stl_findfreeunit()) < 0) {
printf("STALLION: too many boards found, max=%d\n",
STL_MAXBRDS);
return(0);
}
if (brdp->brdnr >= stl_nrbrds)
stl_nrbrds = brdp->brdnr + 1;
brdp->unitid = idp->id_unit;
brdp->brdtype = stl_brdprobed[idp->id_unit];
brdp->ioaddr1 = idp->id_iobase;
brdp->ioaddr2 = stl_ioshared;
brdp->irq = ffs(idp->id_irq) - 1;
brdp->irqtype = stl_irqshared;
stl_brdinit(brdp);
return(1);
}
/*****************************************************************************/
#if NPCI > 0
/*
* Probe specifically for the PCI boards. We need to be a little
* carefull here, since it looks sort like a Nat Semi IDE chip...
*/
static const char *stlpciprobe(pcici_t tag, pcidi_t type)
{
unsigned long class;
#if DEBUG
printf("stlpciprobe(tag=%x,type=%x)\n", (int) &tag, (int) type);
#endif
switch (type) {
case STL_PCIDEVID:
break;
default:
return((char *) NULL);
}
class = pci_conf_read(tag, PCI_CLASS_REG);
if ((class & PCI_CLASS_MASK) == PCI_CLASS_MASS_STORAGE)
return((char *) NULL);
return("Stallion EasyConnection 8/32-PCI");
}
/*****************************************************************************/
/*
* Allocate resources for and initialize the specified PCI board.
*/
void stlpciattach(pcici_t tag, int unit)
{
stlbrd_t *brdp;
#if DEBUG
printf("stlpciattach(tag=%x,unit=%x)\n", (int) &tag, unit);
#endif
brdp = (stlbrd_t *) malloc(sizeof(stlbrd_t), M_TTYS, M_NOWAIT | M_ZERO);
if (brdp == (stlbrd_t *) NULL) {
printf("STALLION: failed to allocate memory (size=%d)\n",
sizeof(stlbrd_t));
return;
}
if ((unit < 0) || (unit > STL_MAXBRDS)) {
printf("STALLION: bad PCI board unit number=%d\n", unit);
return;
}
/*
* Allocate us a new driver unique unit number.
*/
if ((brdp->brdnr = stl_findfreeunit()) < 0) {
printf("STALLION: too many boards found, max=%d\n",
STL_MAXBRDS);
return;
}
if (brdp->brdnr >= stl_nrbrds)
stl_nrbrds = brdp->brdnr + 1;
brdp->unitid = 0;
brdp->brdtype = BRD_ECHPCI;
brdp->ioaddr1 = ((unsigned int) pci_conf_read(tag, 0x14)) & 0xfffc;
brdp->ioaddr2 = ((unsigned int) pci_conf_read(tag, 0x10)) & 0xfffc;
brdp->irq = ((int) pci_conf_read(tag, 0x3c)) & 0xff;
brdp->irqtype = 0;
if (pci_map_int(tag, stlpciintr, (void *) NULL, &tty_imask) == 0) {
printf("STALLION: failed to map interrupt irq=%d for unit=%d\n",
brdp->irq, brdp->brdnr);
return;
}
#if 0
printf("%s(%d): ECH-PCI iobase=%x iopage=%x irq=%d\n", __file__, __LINE__, brdp->ioaddr2, brdp->ioaddr1, brdp->irq);
#endif
stl_brdinit(brdp);
}
#endif
/*****************************************************************************/
STATIC int stlopen(dev_t dev, int flag, int mode, struct thread *td)
{
struct tty *tp;
stlport_t *portp;
int error, callout, x;
#if DEBUG
printf("stlopen(dev=%x,flag=%x,mode=%x,p=%x)\n", (int) dev, flag,
mode, (int) td);
#endif
/*
* Firstly check if the supplied device number is a valid device.
*/
if (minor(dev) & STL_MEMDEV)
return(0);
portp = stl_dev2port(dev);
if (portp == (stlport_t *) NULL)
return(ENXIO);
tp = &portp->tty;
dev->si_tty = tp;
callout = minor(dev) & STL_CALLOUTDEV;
error = 0;
x = spltty();
stlopen_restart:
/*
* Wait here for the DTR drop timeout period to expire.
*/
while (portp->state & ASY_DTRWAIT) {
error = tsleep(&portp->dtrwait, (TTIPRI | PCATCH),
"stldtr", 0);
if (error)
goto stlopen_end;
}
/*
* We have a valid device, so now we check if it is already open.
* If not then initialize the port hardware and set up the tty
* struct as required.
*/
if ((tp->t_state & TS_ISOPEN) == 0) {
tp->t_oproc = stl_start;
tp->t_stop = stl_stop;
tp->t_param = stl_param;
tp->t_dev = dev;
tp->t_termios = callout ? portp->initouttios :
portp->initintios;
stl_rawopen(portp);
if ((portp->sigs & TIOCM_CD) || callout)
(*linesw[tp->t_line].l_modem)(tp, 1);
} else {
if (callout) {
if (portp->callout == 0) {
error = EBUSY;
goto stlopen_end;
}
} else {
if (portp->callout != 0) {
if (flag & O_NONBLOCK) {
error = EBUSY;
goto stlopen_end;
}
error = tsleep(&portp->callout,
(TTIPRI | PCATCH), "stlcall", 0);
if (error)
goto stlopen_end;
goto stlopen_restart;
}
}
if ((tp->t_state & TS_XCLUDE) &&
suser_td(td)) {
error = EBUSY;
goto stlopen_end;
}
}
/*
* If this port is not the callout device and we do not have carrier
* then we need to sleep, waiting for it to be asserted.
*/
if (((tp->t_state & TS_CARR_ON) == 0) && !callout &&
((tp->t_cflag & CLOCAL) == 0) &&
((flag & O_NONBLOCK) == 0)) {
portp->waitopens++;
error = tsleep(TSA_CARR_ON(tp), (TTIPRI | PCATCH), "stldcd", 0);
portp->waitopens--;
if (error)
goto stlopen_end;
goto stlopen_restart;
}
/*
* Open the line discipline.
*/
error = (*linesw[tp->t_line].l_open)(dev, tp);
stl_ttyoptim(portp, &tp->t_termios);
if ((tp->t_state & TS_ISOPEN) && callout)
portp->callout = 1;
/*
* If for any reason we get to here and the port is not actually
* open then close of the physical hardware - no point leaving it
* active when the open failed...
*/
stlopen_end:
splx(x);
if (((tp->t_state & TS_ISOPEN) == 0) && (portp->waitopens == 0))
stl_rawclose(portp);
return(error);
}
/*****************************************************************************/
STATIC int stlclose(dev_t dev, int flag, int mode, struct thread *td)
{
struct tty *tp;
stlport_t *portp;
int x;
#if DEBUG
printf("stlclose(dev=%s,flag=%x,mode=%x,p=%p)\n", devtoname(dev),
flag, mode, (void *) td);
#endif
if (minor(dev) & STL_MEMDEV)
return(0);
portp = stl_dev2port(dev);
if (portp == (stlport_t *) NULL)
return(ENXIO);
tp = &portp->tty;
x = spltty();
(*linesw[tp->t_line].l_close)(tp, flag);
stl_ttyoptim(portp, &tp->t_termios);
stl_rawclose(portp);
ttyclose(tp);
splx(x);
return(0);
}
/*****************************************************************************/
#if VFREEBSD >= 220
STATIC void stl_stop(struct tty *tp, int rw)
{
#if DEBUG
printf("stl_stop(tp=%x,rw=%x)\n", (int) tp, rw);
#endif
stl_flush((stlport_t *) tp, rw);
}
#else
STATIC int stlstop(struct tty *tp, int rw)
{
#if DEBUG
printf("stlstop(tp=%x,rw=%x)\n", (int) tp, rw);
#endif
stl_flush((stlport_t *) tp, rw);
return(0);
}
#endif
/*****************************************************************************/
STATIC int stlioctl(dev_t dev, unsigned long cmd, caddr_t data, int flag,
struct thread *td)
{
struct termios *newtios, *localtios;
struct tty *tp;
stlport_t *portp;
int error, i, x;
#if DEBUG
printf("stlioctl(dev=%s,cmd=%lx,data=%p,flag=%x,p=%p)\n",
devtoname(dev), cmd, (void *) data, flag, (void *) td);
#endif
if (minor(dev) & STL_MEMDEV)
return(stl_memioctl(dev, cmd, data, flag, td));
portp = stl_dev2port(dev);
if (portp == (stlport_t *) NULL)
return(ENODEV);
tp = &portp->tty;
error = 0;
/*
* First up handle ioctls on the control devices.
*/
if (minor(dev) & STL_CTRLDEV) {
if ((minor(dev) & STL_CTRLDEV) == STL_CTRLINIT)
localtios = (minor(dev) & STL_CALLOUTDEV) ?
&portp->initouttios : &portp->initintios;
else if ((minor(dev) & STL_CTRLDEV) == STL_CTRLLOCK)
localtios = (minor(dev) & STL_CALLOUTDEV) ?
&portp->lockouttios : &portp->lockintios;
else
return(ENODEV);
switch (cmd) {
case TIOCSETA:
if ((error = suser_td(td)) == 0)
*localtios = *((struct termios *) data);
break;
case TIOCGETA:
*((struct termios *) data) = *localtios;
break;
case TIOCGETD:
*((int *) data) = TTYDISC;
break;
case TIOCGWINSZ:
bzero(data, sizeof(struct winsize));
break;
default:
error = ENOTTY;
break;
}
return(error);
}
/*
* Deal with 4.3 compatibility issues if we have too...
*/
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
if (1) {
struct termios tios;
unsigned long oldcmd;
tios = tp->t_termios;
oldcmd = cmd;
if ((error = ttsetcompat(tp, &cmd, data, &tios)))
return(error);
if (cmd != oldcmd)
data = (caddr_t) &tios;
}
#endif
/*
* Carry out some pre-cmd processing work first...
* Hmmm, not so sure we want this, disable for now...
*/
if ((cmd == TIOCSETA) || (cmd == TIOCSETAW) || (cmd == TIOCSETAF)) {
newtios = (struct termios *) data;
localtios = (minor(dev) & STL_CALLOUTDEV) ?
&portp->lockouttios : &portp->lockintios;
newtios->c_iflag = (tp->t_iflag & localtios->c_iflag) |
(newtios->c_iflag & ~localtios->c_iflag);
newtios->c_oflag = (tp->t_oflag & localtios->c_oflag) |
(newtios->c_oflag & ~localtios->c_oflag);
newtios->c_cflag = (tp->t_cflag & localtios->c_cflag) |
(newtios->c_cflag & ~localtios->c_cflag);
newtios->c_lflag = (tp->t_lflag & localtios->c_lflag) |
(newtios->c_lflag & ~localtios->c_lflag);
for (i = 0; (i < NCCS); i++) {
if (localtios->c_cc[i] != 0)
newtios->c_cc[i] = tp->t_cc[i];
}
if (localtios->c_ispeed != 0)
newtios->c_ispeed = tp->t_ispeed;
if (localtios->c_ospeed != 0)
newtios->c_ospeed = tp->t_ospeed;
}
/*
* Call the line discipline and the common command processing to
* process this command (if they can).
*/
error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, td);
if (error != ENOIOCTL)
return(error);
x = spltty();
error = ttioctl(tp, cmd, data, flag);
stl_ttyoptim(portp, &tp->t_termios);
if (error != ENOIOCTL) {
splx(x);
return(error);
}
error = 0;
/*
* Process local commands here. These are all commands that only we
* can take care of (they all rely on actually doing something special
* to the actual hardware).
*/
switch (cmd) {
case TIOCSBRK:
stl_sendbreak(portp, -1);
break;
case TIOCCBRK:
stl_sendbreak(portp, -2);
break;
case TIOCSDTR:
stl_setsignals(portp, 1, -1);
break;
case TIOCCDTR:
stl_setsignals(portp, 0, -1);
break;
case TIOCMSET:
i = *((int *) data);
stl_setsignals(portp, ((i & TIOCM_DTR) ? 1 : 0),
((i & TIOCM_RTS) ? 1 : 0));
break;
case TIOCMBIS:
i = *((int *) data);
stl_setsignals(portp, ((i & TIOCM_DTR) ? 1 : -1),
((i & TIOCM_RTS) ? 1 : -1));
break;
case TIOCMBIC:
i = *((int *) data);
stl_setsignals(portp, ((i & TIOCM_DTR) ? 0 : -1),
((i & TIOCM_RTS) ? 0 : -1));
break;
case TIOCMGET:
*((int *) data) = (stl_getsignals(portp) | TIOCM_LE);
break;
case TIOCMSDTRWAIT:
if ((error = suser_td(td)) == 0)
portp->dtrwait = *((int *) data) * hz / 100;
break;
case TIOCMGDTRWAIT:
*((int *) data) = portp->dtrwait * 100 / hz;
break;
case TIOCTIMESTAMP:
portp->dotimestamp = 1;
*((struct timeval *) data) = portp->timestamp;
break;
default:
error = ENOTTY;
break;
}
splx(x);
return(error);
}
/*****************************************************************************/
/*
* Convert the specified minor device number into a port struct
* pointer. Return NULL if the device number is not a valid port.
*/
STATIC stlport_t *stl_dev2port(dev_t dev)
{
stlbrd_t *brdp;
brdp = stl_brds[MKDEV2BRD(dev)];
if (brdp == (stlbrd_t *) NULL)
return((stlport_t *) NULL);
return(brdp->ports[MKDEV2PORT(dev)]);
}
/*****************************************************************************/
/*
* Initialize the port hardware. This involves enabling the transmitter
* and receiver, setting the port configuration, and setting the initial
* signal state.
*/
static int stl_rawopen(stlport_t *portp)
{
#if DEBUG
printf("stl_rawopen(portp=%p): brdnr=%d panelnr=%d portnr=%d\n",
(void *) portp, portp->brdnr, portp->panelnr, portp->portnr);
#endif
stl_param(&portp->tty, &portp->tty.t_termios);
portp->sigs = stl_getsignals(portp);
stl_setsignals(portp, 1, 1);
stl_enablerxtx(portp, 1, 1);
stl_startrxtx(portp, 1, 0);
return(0);
}
/*****************************************************************************/
/*
* Shutdown the hardware of a port. Disable its transmitter and
* receiver, and maybe drop signals if appropriate.
*/
static int stl_rawclose(stlport_t *portp)
{
struct tty *tp;
#if DEBUG
printf("stl_rawclose(portp=%p): brdnr=%d panelnr=%d portnr=%d\n",
(void *) portp, portp->brdnr, portp->panelnr, portp->portnr);
#endif
tp = &portp->tty;
stl_disableintrs(portp);
stl_enablerxtx(portp, 0, 0);
stl_flush(portp, (FWRITE | FREAD));
if (tp->t_cflag & HUPCL) {
stl_setsignals(portp, 0, 0);
if (portp->dtrwait != 0) {
portp->state |= ASY_DTRWAIT;
timeout(stl_dtrwakeup, portp, portp->dtrwait);
}
}
portp->callout = 0;
portp->brklen = 0;
portp->state &= ~(ASY_ACTIVE | ASY_RTSFLOW);
wakeup(&portp->callout);
wakeup(TSA_CARR_ON(tp));
return(0);
}
/*****************************************************************************/
/*
* Clear the DTR waiting flag, and wake up any sleepers waiting for
* DTR wait period to finish.
*/
static void stl_dtrwakeup(void *arg)
{
stlport_t *portp;
portp = (stlport_t *) arg;
portp->state &= ~ASY_DTRWAIT;
wakeup(&portp->dtrwait);
}
/*****************************************************************************/
/*
* Start (or continue) the transfer of TX data on this port. If the
* port is not currently busy then load up the interrupt ring queue
* buffer and kick of the transmitter. If the port is running low on
* TX data then refill the ring queue. This routine is also used to
* activate input flow control!
*/
static void stl_start(struct tty *tp)
{
stlport_t *portp;
unsigned int len, stlen;
char *head, *tail;
int count, x;
portp = (stlport_t *) tp;
#if DEBUG
printf("stl_start(tp=%x): brdnr=%d portnr=%d\n", (int) tp,
portp->brdnr, portp->portnr);
#endif
x = spltty();
/*
* Check if the ports input has been blocked, and take appropriate action.
* Not very often do we really need to do anything, so make it quick.
*/
if (tp->t_state & TS_TBLOCK) {
if ((portp->state & ASY_RTSFLOW) == 0)
stl_flowcontrol(portp, 0, -1);
} else {
if (portp->state & ASY_RTSFLOW)
stl_flowcontrol(portp, 1, -1);
}
#if VFREEBSD == 205
/*
* Check if the output cooked clist buffers are near empty, wake up
* the line discipline to fill it up.
*/
if (tp->t_outq.c_cc <= tp->t_lowat) {
if (tp->t_state & TS_ASLEEP) {
tp->t_state &= ~TS_ASLEEP;
wakeup(&tp->t_outq);
}
selwakeup(&tp->t_wsel);
}
#endif
if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) {
splx(x);
return;
}
/*
* Copy data from the clists into the interrupt ring queue. This will
* require at most 2 copys... What we do is calculate how many chars
* can fit into the ring queue, and how many can fit in 1 copy. If after
* the first copy there is still more room then do the second copy.
* The beauty of this type of ring queue is that we do not need to
* spl protect our-selves, since we only ever update the head pointer,
* and the interrupt routine only ever updates the tail pointer.
*/
if (tp->t_outq.c_cc != 0) {
head = portp->tx.head;
tail = portp->tx.tail;
if (head >= tail) {
len = STL_TXBUFSIZE - (head - tail) - 1;
stlen = portp->tx.endbuf - head;
} else {
len = tail - head - 1;
stlen = len;
}
if (len > 0) {
stlen = MIN(len, stlen);
count = q_to_b(&tp->t_outq, head, stlen);
len -= count;
head += count;
if (head >= portp->tx.endbuf) {
head = portp->tx.buf;
if (len > 0) {
stlen = q_to_b(&tp->t_outq, head, len);
head += stlen;
count += stlen;
}
}
portp->tx.head = head;
if (count > 0)
stl_startrxtx(portp, -1, 1);
}
/*
* If we sent something, make sure we are called again.
*/
tp->t_state |= TS_BUSY;
}
#if VFREEBSD != 205
/*
* Do any writer wakeups.
*/
ttwwakeup(tp);
#endif
splx(x);
}
/*****************************************************************************/
static void stl_flush(stlport_t *portp, int flag)
{
char *head, *tail;
int len, x;
#if DEBUG
printf("stl_flush(portp=%x,flag=%x)\n", (int) portp, flag);
#endif
if (portp == (stlport_t *) NULL)
return;
x = spltty();
if (flag & FWRITE) {
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x03));
stl_ccrwait(portp);
stl_setreg(portp, CCR, CCR_TXFLUSHFIFO);
stl_ccrwait(portp);
portp->tx.tail = portp->tx.head;
BRDDISABLE(portp->brdnr);
}
/*
* The only thing to watch out for when flushing the read side is
* the RX status buffer. The interrupt code relys on the status
* bytes as being zeroed all the time (it does not bother setting
* a good char status to 0, it expects that it already will be).
* We also need to un-flow the RX channel if flow control was
* active.
*/
if (flag & FREAD) {
head = portp->rx.head;
tail = portp->rx.tail;
if (head != tail) {
if (head >= tail) {
len = head - tail;
} else {
len = portp->rx.endbuf - tail;
bzero(portp->rxstatus.buf,
(head - portp->rx.buf));
}
bzero((tail + STL_RXBUFSIZE), len);
portp->rx.tail = head;
}
if ((portp->state & ASY_RTSFLOW) &&
((portp->tty.t_state & TS_TBLOCK) == 0))
stl_flowcontrol(portp, 1, -1);
}
splx(x);
}
/*****************************************************************************/
/*
* These functions get/set/update the registers of the cd1400 UARTs.
* Access to the cd1400 registers is via an address/data io port pair.
* (Maybe should make this inline...)
*/
static int stl_getreg(stlport_t *portp, int regnr)
{
outb(portp->ioaddr, (regnr + portp->uartaddr));
return(inb(portp->ioaddr + EREG_DATA));
}
/*****************************************************************************/
static void stl_setreg(stlport_t *portp, int regnr, int value)
{
outb(portp->ioaddr, (regnr + portp->uartaddr));
outb((portp->ioaddr + EREG_DATA), value);
}
/*****************************************************************************/
static int stl_updatereg(stlport_t *portp, int regnr, int value)
{
outb(portp->ioaddr, (regnr + portp->uartaddr));
if (inb(portp->ioaddr + EREG_DATA) != value) {
outb((portp->ioaddr + EREG_DATA), value);
return(1);
}
return(0);
}
/*****************************************************************************/
/*
* Wait for the command register to be ready. We will poll this, since
* it won't usually take too long to be ready, and it is only really
* used for non-critical actions.
*/
static void stl_ccrwait(stlport_t *portp)
{
int i;
for (i = 0; (i < CCR_MAXWAIT); i++) {
if (stl_getreg(portp, CCR) == 0) {
return;
}
}
printf("STALLION: cd1400 device not responding, brd=%d panel=%d"
"port=%d\n", portp->brdnr, portp->panelnr, portp->portnr);
}
/*****************************************************************************/
/*
* Transmit interrupt handler. This has gotta be fast! Handling TX
* chars is pretty simple, stuff as many as possible from the TX buffer
* into the cd1400 FIFO. Must also handle TX breaks here, since they
* are embedded as commands in the data stream. Oh no, had to use a goto!
* This could be optimized more, will do when I get time...
* In practice it is possible that interrupts are enabled but that the
* port has been hung up. Need to handle not having any TX buffer here,
* this is done by using the side effect that head and tail will also
* be NULL if the buffer has been freed.
*/
static __inline void stl_txisr(stlpanel_t *panelp, int ioaddr)
{
stlport_t *portp;
int len, stlen;
char *head, *tail;
unsigned char ioack, srer;
#if DEBUG
printf("stl_txisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
#endif
ioack = inb(ioaddr + EREG_TXACK);
if (((ioack & panelp->ackmask) != 0) ||
((ioack & ACK_TYPMASK) != ACK_TYPTX)) {
printf("STALLION: bad TX interrupt ack value=%x\n", ioack);
return;
}
portp = panelp->ports[(ioack >> 3)];
/*
* Unfortunately we need to handle breaks in the data stream, since
* this is the only way to generate them on the cd1400. Do it now if
* a break is to be sent. Some special cases here: brklen is -1 then
* start sending an un-timed break, if brklen is -2 then stop sending
* an un-timed break, if brklen is -3 then we have just sent an
* un-timed break and do not want any data to go out, if brklen is -4
* then a break has just completed so clean up the port settings.
*/
if (portp->brklen != 0) {
if (portp->brklen >= -1) {
outb(ioaddr, (TDR + portp->uartaddr));
outb((ioaddr + EREG_DATA), ETC_CMD);
outb((ioaddr + EREG_DATA), ETC_STARTBREAK);
if (portp->brklen > 0) {
outb((ioaddr + EREG_DATA), ETC_CMD);
outb((ioaddr + EREG_DATA), ETC_DELAY);
outb((ioaddr + EREG_DATA), portp->brklen);
outb((ioaddr + EREG_DATA), ETC_CMD);
outb((ioaddr + EREG_DATA), ETC_STOPBREAK);
portp->brklen = -4;
} else {
portp->brklen = -3;
}
} else if (portp->brklen == -2) {
outb(ioaddr, (TDR + portp->uartaddr));
outb((ioaddr + EREG_DATA), ETC_CMD);
outb((ioaddr + EREG_DATA), ETC_STOPBREAK);
portp->brklen = -4;
} else if (portp->brklen == -3) {
outb(ioaddr, (SRER + portp->uartaddr));
srer = inb(ioaddr + EREG_DATA);
srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
outb((ioaddr + EREG_DATA), srer);
} else {
outb(ioaddr, (COR2 + portp->uartaddr));
outb((ioaddr + EREG_DATA),
(inb(ioaddr + EREG_DATA) & ~COR2_ETC));
portp->brklen = 0;
}
goto stl_txalldone;
}
head = portp->tx.head;
tail = portp->tx.tail;
len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
if ((len == 0) || ((len < STL_TXBUFLOW) &&
((portp->state & ASY_TXLOW) == 0))) {
portp->state |= ASY_TXLOW;
stl_dotimeout();
}
if (len == 0) {
outb(ioaddr, (SRER + portp->uartaddr));
srer = inb(ioaddr + EREG_DATA);
if (srer & SRER_TXDATA) {
srer = (srer & ~SRER_TXDATA) | SRER_TXEMPTY;
} else {
srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
portp->tty.t_state &= ~TS_BUSY;
}
outb((ioaddr + EREG_DATA), srer);
} else {
len = MIN(len, CD1400_TXFIFOSIZE);
portp->stats.txtotal += len;
stlen = MIN(len, (portp->tx.endbuf - tail));
outb(ioaddr, (TDR + portp->uartaddr));
outsb((ioaddr + EREG_DATA), tail, stlen);
len -= stlen;
tail += stlen;
if (tail >= portp->tx.endbuf)
tail = portp->tx.buf;
if (len > 0) {
outsb((ioaddr + EREG_DATA), tail, len);
tail += len;
}
portp->tx.tail = tail;
}
stl_txalldone:
outb(ioaddr, (EOSRR + portp->uartaddr));
outb((ioaddr + EREG_DATA), 0);
}
/*****************************************************************************/
/*
* Receive character interrupt handler. Determine if we have good chars
* or bad chars and then process appropriately. Good chars are easy
* just shove the lot into the RX buffer and set all status bytes to 0.
* If a bad RX char then process as required. This routine needs to be
* fast!
*/
static __inline void stl_rxisr(stlpanel_t *panelp, int ioaddr)
{
stlport_t *portp;
struct tty *tp;
unsigned int ioack, len, buflen, stlen;
unsigned char status;
char ch;
char *head, *tail;
static char unwanted[CD1400_RXFIFOSIZE];
#if DEBUG
printf("stl_rxisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
#endif
ioack = inb(ioaddr + EREG_RXACK);
if ((ioack & panelp->ackmask) != 0) {
printf("STALLION: bad RX interrupt ack value=%x\n", ioack);
return;
}
portp = panelp->ports[(ioack >> 3)];
tp = &portp->tty;
/*
* First up, caluclate how much room there is in the RX ring queue.
* We also want to keep track of the longest possible copy length,
* this has to allow for the wrapping of the ring queue.
*/
head = portp->rx.head;
tail = portp->rx.tail;
if (head >= tail) {
buflen = STL_RXBUFSIZE - (head - tail) - 1;
stlen = portp->rx.endbuf - head;
} else {
buflen = tail - head - 1;
stlen = buflen;
}
/*
* Check if the input buffer is near full. If so then we should take
* some flow control action... It is very easy to do hardware and
* software flow control from here since we have the port selected on
* the UART.
*/
if (buflen <= (STL_RXBUFSIZE - STL_RXBUFHIGH)) {
if (((portp->state & ASY_RTSFLOW) == 0) &&
(portp->state & ASY_RTSFLOWMODE)) {
portp->state |= ASY_RTSFLOW;
stl_setreg(portp, MCOR1,
(stl_getreg(portp, MCOR1) & 0xf0));
stl_setreg(portp, MSVR2, 0);
portp->stats.rxrtsoff++;
}
}
/*
* OK we are set, process good data... If the RX ring queue is full
* just chuck the chars - don't leave them in the UART.
*/
if ((ioack & ACK_TYPMASK) == ACK_TYPRXGOOD) {
outb(ioaddr, (RDCR + portp->uartaddr));
len = inb(ioaddr + EREG_DATA);
if (buflen == 0) {
outb(ioaddr, (RDSR + portp->uartaddr));
insb((ioaddr + EREG_DATA), &unwanted[0], len);
portp->stats.rxlost += len;
portp->stats.rxtotal += len;
} else {
len = MIN(len, buflen);
portp->stats.rxtotal += len;
stlen = MIN(len, stlen);
if (len > 0) {
outb(ioaddr, (RDSR + portp->uartaddr));
insb((ioaddr + EREG_DATA), head, stlen);
head += stlen;
if (head >= portp->rx.endbuf) {
head = portp->rx.buf;
len -= stlen;
insb((ioaddr + EREG_DATA), head, len);
head += len;
}
}
}
} else if ((ioack & ACK_TYPMASK) == ACK_TYPRXBAD) {
outb(ioaddr, (RDSR + portp->uartaddr));
status = inb(ioaddr + EREG_DATA);
ch = inb(ioaddr + EREG_DATA);
if (status & ST_BREAK)
portp->stats.rxbreaks++;
if (status & ST_FRAMING)
portp->stats.rxframing++;
if (status & ST_PARITY)
portp->stats.rxparity++;
if (status & ST_OVERRUN)
portp->stats.rxoverrun++;
if (status & ST_SCHARMASK) {
if ((status & ST_SCHARMASK) == ST_SCHAR1)
portp->stats.txxon++;
if ((status & ST_SCHARMASK) == ST_SCHAR2)
portp->stats.txxoff++;
goto stl_rxalldone;
}
if ((portp->rxignoremsk & status) == 0) {
if ((tp->t_state & TS_CAN_BYPASS_L_RINT) &&
((status & ST_FRAMING) ||
((status & ST_PARITY) && (tp->t_iflag & INPCK))))
ch = 0;
if ((portp->rxmarkmsk & status) == 0)
status = 0;
*(head + STL_RXBUFSIZE) = status;
*head++ = ch;
if (head >= portp->rx.endbuf)
head = portp->rx.buf;
}
} else {
printf("STALLION: bad RX interrupt ack value=%x\n", ioack);
return;
}
portp->rx.head = head;
portp->state |= ASY_RXDATA;
stl_dotimeout();
stl_rxalldone:
outb(ioaddr, (EOSRR + portp->uartaddr));
outb((ioaddr + EREG_DATA), 0);
}
/*****************************************************************************/
/*
* Modem interrupt handler. The is called when the modem signal line
* (DCD) has changed state. Leave most of the work to the off-level
* processing routine.
*/
static __inline void stl_mdmisr(stlpanel_t *panelp, int ioaddr)
{
stlport_t *portp;
unsigned int ioack;
unsigned char misr;
#if DEBUG
printf("stl_mdmisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
#endif
ioack = inb(ioaddr + EREG_MDACK);
if (((ioack & panelp->ackmask) != 0) ||
((ioack & ACK_TYPMASK) != ACK_TYPMDM)) {
printf("STALLION: bad MODEM interrupt ack value=%x\n", ioack);
return;
}
portp = panelp->ports[(ioack >> 3)];
outb(ioaddr, (MISR + portp->uartaddr));
misr = inb(ioaddr + EREG_DATA);
if (misr & MISR_DCD) {
portp->state |= ASY_DCDCHANGE;
portp->stats.modem++;
stl_dotimeout();
}
outb(ioaddr, (EOSRR + portp->uartaddr));
outb((ioaddr + EREG_DATA), 0);
}
/*****************************************************************************/
/*
* Interrupt handler for EIO and ECH boards. This code ain't all that
* pretty, but the idea is to make it as fast as possible. This code is
* well suited to be assemblerized :-) We don't use the general purpose
* register access functions here, for speed we will go strait to the
* io register.
*/
static void stlintr(int unit)
{
stlbrd_t *brdp;
stlpanel_t *panelp;
unsigned char svrtype;
int i, panelnr, iobase;
int cnt;
#if DEBUG
printf("stlintr(unit=%d)\n", unit);
#endif
cnt = 0;
panelp = (stlpanel_t *) NULL;
for (i = 0; (i < stl_nrbrds); ) {
if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL) {
i++;
continue;
}
if (brdp->state == 0) {
i++;
continue;
}
/*
* The following section of code handles the subtle differences
* between board types. It is sort of similar, but different
* enough to handle each separately.
*/
if (brdp->brdtype == BRD_EASYIO) {
if ((inb(brdp->iostatus) & EIO_INTRPEND) == 0) {
i++;
continue;
}
panelp = brdp->panels[0];
iobase = panelp->iobase;
outb(iobase, SVRR);
svrtype = inb(iobase + EREG_DATA);
if (brdp->nrports > 4) {
outb(iobase, (SVRR + 0x80));
svrtype |= inb(iobase + EREG_DATA);
}
} else if (brdp->brdtype == BRD_ECH) {
if ((inb(brdp->iostatus) & ECH_INTRPEND) == 0) {
i++;
continue;
}
outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDENABLE));
for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
panelp = brdp->panels[panelnr];
iobase = panelp->iobase;
if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
break;
if (panelp->nrports > 8) {
iobase += 0x8;
if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
break;
}
}
if (panelnr >= brdp->nrpanels) {
i++;
continue;
}
outb(iobase, SVRR);
svrtype = inb(iobase + EREG_DATA);
outb(iobase, (SVRR + 0x80));
svrtype |= inb(iobase + EREG_DATA);
} else if (brdp->brdtype == BRD_ECHPCI) {
iobase = brdp->ioaddr2;
for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
panelp = brdp->panels[panelnr];
outb(brdp->ioctrl, panelp->pagenr);
if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
break;
if (panelp->nrports > 8) {
outb(brdp->ioctrl, (panelp->pagenr + 1));
if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
break;
}
}
if (panelnr >= brdp->nrpanels) {
i++;
continue;
}
outb(iobase, SVRR);
svrtype = inb(iobase + EREG_DATA);
outb(iobase, (SVRR + 0x80));
svrtype |= inb(iobase + EREG_DATA);
} else if (brdp->brdtype == BRD_ECHMC) {
if ((inb(brdp->iostatus) & ECH_INTRPEND) == 0) {
i++;
continue;
}
for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
panelp = brdp->panels[panelnr];
iobase = panelp->iobase;
if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
break;
if (panelp->nrports > 8) {
iobase += 0x8;
if (inb(iobase + ECH_PNLSTATUS) & ECH_PNLINTRPEND)
break;
}
}
if (panelnr >= brdp->nrpanels) {
i++;
continue;
}
outb(iobase, SVRR);
svrtype = inb(iobase + EREG_DATA);
outb(iobase, (SVRR + 0x80));
svrtype |= inb(iobase + EREG_DATA);
} else {
printf("STALLION: unknown board type=%x\n", brdp->brdtype);
i++;
continue;
}
/*
* We have determined what type of service is required for a
* port. From here on in the service of a port is the same no
* matter what the board type...
*/
if (svrtype & SVRR_RX)
stl_rxisr(panelp, iobase);
if (svrtype & SVRR_TX)
stl_txisr(panelp, iobase);
if (svrtype & SVRR_MDM)
stl_mdmisr(panelp, iobase);
if (brdp->brdtype == BRD_ECH)
outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDDISABLE));
}
}
/*****************************************************************************/
#if NPCI > 0
static void stlpciintr(void *arg)
{
stlintr(0);
}
#endif
/*****************************************************************************/
/*
* If we haven't scheduled a timeout then do it, some port needs high
* level processing.
*/
static void stl_dotimeout()
{
#if DEBUG
printf("stl_dotimeout()\n");
#endif
if (stl_doingtimeout == 0) {
timeout(stl_poll, 0, 1);
stl_doingtimeout++;
}
}
/*****************************************************************************/
/*
* Service "software" level processing. Too slow or painfull to be done
* at real hardware interrupt time. This way we might also be able to
* do some service on other waiting ports as well...
*/
static void stl_poll(void *arg)
{
stlbrd_t *brdp;
stlport_t *portp;
struct tty *tp;
int brdnr, portnr, rearm, x;
#if DEBUG
printf("stl_poll()\n");
#endif
stl_doingtimeout = 0;
rearm = 0;
x = spltty();
for (brdnr = 0; (brdnr < stl_nrbrds); brdnr++) {
if ((brdp = stl_brds[brdnr]) == (stlbrd_t *) NULL)
continue;
for (portnr = 0; (portnr < brdp->nrports); portnr++) {
if ((portp = brdp->ports[portnr]) == (stlport_t *) NULL)
continue;
if ((portp->state & ASY_ACTIVE) == 0)
continue;
tp = &portp->tty;
if (portp->state & ASY_RXDATA)
stl_rxprocess(portp);
if (portp->state & ASY_DCDCHANGE) {
portp->state &= ~ASY_DCDCHANGE;
portp->sigs = stl_getsignals(portp);
(*linesw[tp->t_line].l_modem)(tp,
(portp->sigs & TIOCM_CD));
}
if (portp->state & ASY_TXLOW) {
portp->state &= ~ASY_TXLOW;
(*linesw[tp->t_line].l_start)(tp);
}
if (portp->state & ASY_ACTIVE)
rearm++;
}
}
splx(x);
if (rearm)
stl_dotimeout();
}
/*****************************************************************************/
/*
* Process the RX data that has been buffered up in the RX ring queue.
*/
static void stl_rxprocess(stlport_t *portp)
{
struct tty *tp;
unsigned int len, stlen, lostlen;
char *head, *tail;
char status;
int ch;
#if DEBUG
printf("stl_rxprocess(portp=%x): brdnr=%d portnr=%d\n", (int) portp,
portp->brdnr, portp->portnr);
#endif
tp = &portp->tty;
portp->state &= ~ASY_RXDATA;
if ((tp->t_state & TS_ISOPEN) == 0) {
stl_flush(portp, FREAD);
return;
}
/*
* Calculate the amount of data in the RX ring queue. Also calculate
* the largest single copy size...
*/
head = portp->rx.head;
tail = portp->rx.tail;
if (head >= tail) {
len = head - tail;
stlen = len;
} else {
len = STL_RXBUFSIZE - (tail - head);
stlen = portp->rx.endbuf - tail;
}
if (tp->t_state & TS_CAN_BYPASS_L_RINT) {
if (len > 0) {
if (((tp->t_rawq.c_cc + len) >= TTYHOG) &&
((portp->state & ASY_RTSFLOWMODE) ||
(tp->t_iflag & IXOFF)) &&
((tp->t_state & TS_TBLOCK) == 0)) {
ch = TTYHOG - tp->t_rawq.c_cc - 1;
len = (ch > 0) ? ch : 0;
stlen = MIN(stlen, len);
ttyblock(tp);
}
lostlen = b_to_q(tail, stlen, &tp->t_rawq);
tail += stlen;
len -= stlen;
if (tail >= portp->rx.endbuf) {
tail = portp->rx.buf;
lostlen += b_to_q(tail, len, &tp->t_rawq);
tail += len;
}
portp->stats.rxlost += lostlen;
ttwakeup(tp);
portp->rx.tail = tail;
}
} else {
while (portp->rx.tail != head) {
ch = (unsigned char) *(portp->rx.tail);
status = *(portp->rx.tail + STL_RXBUFSIZE);
if (status) {
*(portp->rx.tail + STL_RXBUFSIZE) = 0;
if (status & ST_BREAK)
ch |= TTY_BI;
if (status & ST_FRAMING)
ch |= TTY_FE;
if (status & ST_PARITY)
ch |= TTY_PE;
if (status & ST_OVERRUN)
ch |= TTY_OE;
}
(*linesw[tp->t_line].l_rint)(ch, tp);
if (portp->rx.tail == head)
break;
if (++(portp->rx.tail) >= portp->rx.endbuf)
portp->rx.tail = portp->rx.buf;
}
}
if (head != portp->rx.tail)
portp->state |= ASY_RXDATA;
/*
* If we where flow controled then maybe the buffer is low enough that
* we can re-activate it.
*/
if ((portp->state & ASY_RTSFLOW) && ((tp->t_state & TS_TBLOCK) == 0))
stl_flowcontrol(portp, 1, -1);
}
/*****************************************************************************/
/*
* Set up the cd1400 registers for a port based on the termios port
* settings.
*/
static int stl_param(struct tty *tp, struct termios *tiosp)
{
stlport_t *portp;
unsigned int clkdiv;
unsigned char cor1, cor2, cor3;
unsigned char cor4, cor5, ccr;
unsigned char srer, sreron, sreroff;
unsigned char mcor1, mcor2, rtpr;
unsigned char clk, div;
int x;
portp = (stlport_t *) tp;
#if DEBUG
printf("stl_param(tp=%x,tiosp=%x): brdnr=%d portnr=%d\n", (int) tp,
(int) tiosp, portp->brdnr, portp->portnr);
#endif
cor1 = 0;
cor2 = 0;
cor3 = 0;
cor4 = 0;
cor5 = 0;
ccr = 0;
rtpr = 0;
clk = 0;
div = 0;
mcor1 = 0;
mcor2 = 0;
sreron = 0;
sreroff = 0;
/*
* Set up the RX char ignore mask with those RX error types we
* can ignore. We could have used some special modes of the cd1400
* UART to help, but it is better this way because we can keep stats
* on the number of each type of RX exception event.
*/
portp->rxignoremsk = 0;
if (tiosp->c_iflag & IGNPAR)
portp->rxignoremsk |= (ST_PARITY | ST_FRAMING | ST_OVERRUN);
if (tiosp->c_iflag & IGNBRK)
portp->rxignoremsk |= ST_BREAK;
portp->rxmarkmsk = ST_OVERRUN;
if (tiosp->c_iflag & (INPCK | PARMRK))
portp->rxmarkmsk |= (ST_PARITY | ST_FRAMING);
if (tiosp->c_iflag & BRKINT)
portp->rxmarkmsk |= ST_BREAK;
/*
* Go through the char size, parity and stop bits and set all the
* option registers appropriately.
*/
switch (tiosp->c_cflag & CSIZE) {
case CS5:
cor1 |= COR1_CHL5;
break;
case CS6:
cor1 |= COR1_CHL6;
break;
case CS7:
cor1 |= COR1_CHL7;
break;
default:
cor1 |= COR1_CHL8;
break;
}
if (tiosp->c_cflag & CSTOPB)
cor1 |= COR1_STOP2;
else
cor1 |= COR1_STOP1;
if (tiosp->c_cflag & PARENB) {
if (tiosp->c_cflag & PARODD)
cor1 |= (COR1_PARENB | COR1_PARODD);
else
cor1 |= (COR1_PARENB | COR1_PAREVEN);
} else {
cor1 |= COR1_PARNONE;
}
if (tiosp->c_iflag & ISTRIP)
cor5 |= COR5_ISTRIP;
/*
* Set the RX FIFO threshold at 6 chars. This gives a bit of breathing
* space for hardware flow control and the like. This should be set to
* VMIN. Also here we will set the RX data timeout to 10ms - this should
* really be based on VTIME...
*/
cor3 |= FIFO_RXTHRESHOLD;
rtpr = 2;
/*
* Calculate the baud rate timers. For now we will just assume that
* the input and output baud are the same. Could have used a baud
* table here, but this way we can generate virtually any baud rate
* we like!
*/
if (tiosp->c_ispeed == 0)
tiosp->c_ispeed = tiosp->c_ospeed;
if ((tiosp->c_ospeed < 0) || (tiosp->c_ospeed > STL_MAXBAUD))
return(EINVAL);
if (tiosp->c_ospeed > 0) {
for (clk = 0; (clk < CD1400_NUMCLKS); clk++) {
clkdiv = ((portp->clk / stl_cd1400clkdivs[clk]) /
tiosp->c_ospeed);
if (clkdiv < 0x100)
break;
}
div = (unsigned char) clkdiv;
}
/*
* Check what form of modem signaling is required and set it up.
*/
if ((tiosp->c_cflag & CLOCAL) == 0) {
mcor1 |= MCOR1_DCD;
mcor2 |= MCOR2_DCD;
sreron |= SRER_MODEM;
}
/*
* Setup cd1400 enhanced modes if we can. In particular we want to
* handle as much of the flow control as possbile automatically. As
* well as saving a few CPU cycles it will also greatly improve flow
* control reliablilty.
*/
if (tiosp->c_iflag & IXON) {
cor2 |= COR2_TXIBE;
cor3 |= COR3_SCD12;
if (tiosp->c_iflag & IXANY)
cor2 |= COR2_IXM;
}
if (tiosp->c_cflag & CCTS_OFLOW)
cor2 |= COR2_CTSAE;
if (tiosp->c_cflag & CRTS_IFLOW)
mcor1 |= FIFO_RTSTHRESHOLD;
/*
* All cd1400 register values calculated so go through and set them
* all up.
*/
#if DEBUG
printf("SETPORT: portnr=%d panelnr=%d brdnr=%d\n", portp->portnr,
portp->panelnr, portp->brdnr);
printf(" cor1=%x cor2=%x cor3=%x cor4=%x cor5=%x\n", cor1, cor2,
cor3, cor4, cor5);
printf(" mcor1=%x mcor2=%x rtpr=%x sreron=%x sreroff=%x\n",
mcor1, mcor2, rtpr, sreron, sreroff);
printf(" tcor=%x tbpr=%x rcor=%x rbpr=%x\n", clk, div, clk, div);
printf(" schr1=%x schr2=%x schr3=%x schr4=%x\n",
tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP], tiosp->c_cc[VSTART],
tiosp->c_cc[VSTOP]);
#endif
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
srer = stl_getreg(portp, SRER);
stl_setreg(portp, SRER, 0);
ccr += stl_updatereg(portp, COR1, cor1);
ccr += stl_updatereg(portp, COR2, cor2);
ccr += stl_updatereg(portp, COR3, cor3);
if (ccr) {
stl_ccrwait(portp);
stl_setreg(portp, CCR, CCR_CORCHANGE);
}
stl_setreg(portp, COR4, cor4);
stl_setreg(portp, COR5, cor5);
stl_setreg(portp, MCOR1, mcor1);
stl_setreg(portp, MCOR2, mcor2);
if (tiosp->c_ospeed == 0) {
stl_setreg(portp, MSVR1, 0);
} else {
stl_setreg(portp, MSVR1, MSVR1_DTR);
stl_setreg(portp, TCOR, clk);
stl_setreg(portp, TBPR, div);
stl_setreg(portp, RCOR, clk);
stl_setreg(portp, RBPR, div);
}
stl_setreg(portp, SCHR1, tiosp->c_cc[VSTART]);
stl_setreg(portp, SCHR2, tiosp->c_cc[VSTOP]);
stl_setreg(portp, SCHR3, tiosp->c_cc[VSTART]);
stl_setreg(portp, SCHR4, tiosp->c_cc[VSTOP]);
stl_setreg(portp, RTPR, rtpr);
mcor1 = stl_getreg(portp, MSVR1);
if (mcor1 & MSVR1_DCD)
portp->sigs |= TIOCM_CD;
else
portp->sigs &= ~TIOCM_CD;
stl_setreg(portp, SRER, ((srer & ~sreroff) | sreron));
BRDDISABLE(portp->brdnr);
portp->state &= ~(ASY_RTSFLOWMODE | ASY_CTSFLOWMODE);
portp->state |= ((tiosp->c_cflag & CRTS_IFLOW) ? ASY_RTSFLOWMODE : 0);
portp->state |= ((tiosp->c_cflag & CCTS_OFLOW) ? ASY_CTSFLOWMODE : 0);
stl_ttyoptim(portp, tiosp);
splx(x);
return(0);
}
/*****************************************************************************/
/*
* Action the flow control as required. The hw and sw args inform the
* routine what flow control methods it should try.
*/
static void stl_flowcontrol(stlport_t *portp, int hw, int sw)
{
unsigned char *head, *tail;
int len, hwflow, x;
#if DEBUG
printf("stl_flowcontrol(portp=%x,hw=%d,sw=%d)\n", (int) portp, hw, sw);
#endif
hwflow = -1;
if (portp->state & ASY_RTSFLOWMODE) {
if (hw == 0) {
if ((portp->state & ASY_RTSFLOW) == 0)
hwflow = 0;
} else if (hw > 0) {
if (portp->state & ASY_RTSFLOW) {
head = portp->rx.head;
tail = portp->rx.tail;
len = (head >= tail) ? (head - tail) :
(STL_RXBUFSIZE - (tail - head));
if (len < STL_RXBUFHIGH)
hwflow = 1;
}
}
}
/*
* We have worked out what to do, if anything. So now apply it to the
* UART port.
*/
if (hwflow >= 0) {
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x03));
if (hwflow == 0) {
portp->state |= ASY_RTSFLOW;
stl_setreg(portp, MCOR1,
(stl_getreg(portp, MCOR1) & 0xf0));
stl_setreg(portp, MSVR2, 0);
portp->stats.rxrtsoff++;
} else if (hwflow > 0) {
portp->state &= ~ASY_RTSFLOW;
stl_setreg(portp, MSVR2, MSVR2_RTS);
stl_setreg(portp, MCOR1,
(stl_getreg(portp, MCOR1) | FIFO_RTSTHRESHOLD));
portp->stats.rxrtson++;
}
BRDDISABLE(portp->brdnr);
splx(x);
}
}
/*****************************************************************************/
/*
* Set the state of the DTR and RTS signals.
*/
static void stl_setsignals(stlport_t *portp, int dtr, int rts)
{
unsigned char msvr1, msvr2;
int x;
#if DEBUG
printf("stl_setsignals(portp=%x,dtr=%d,rts=%d)\n", (int) portp,
dtr, rts);
#endif
msvr1 = 0;
msvr2 = 0;
if (dtr > 0)
msvr1 = MSVR1_DTR;
if (rts > 0)
msvr2 = MSVR2_RTS;
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x03));
if (rts >= 0)
stl_setreg(portp, MSVR2, msvr2);
if (dtr >= 0)
stl_setreg(portp, MSVR1, msvr1);
BRDDISABLE(portp->brdnr);
splx(x);
}
/*****************************************************************************/
/*
* Get the state of the signals.
*/
static int stl_getsignals(stlport_t *portp)
{
unsigned char msvr1, msvr2;
int sigs, x;
#if DEBUG
printf("stl_getsignals(portp=%x)\n", (int) portp);
#endif
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
msvr1 = stl_getreg(portp, MSVR1);
msvr2 = stl_getreg(portp, MSVR2);
BRDDISABLE(portp->brdnr);
splx(x);
sigs = 0;
sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0;
sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0;
sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0;
sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0;
sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0;
sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0;
return(sigs);
}
/*****************************************************************************/
/*
* Enable or disable the Transmitter and/or Receiver.
*/
static void stl_enablerxtx(stlport_t *portp, int rx, int tx)
{
unsigned char ccr;
int x;
#if DEBUG
printf("stl_enablerxtx(portp=%x,rx=%d,tx=%d)\n", (int) portp, rx, tx);
#endif
ccr = 0;
if (tx == 0)
ccr |= CCR_TXDISABLE;
else if (tx > 0)
ccr |= CCR_TXENABLE;
if (rx == 0)
ccr |= CCR_RXDISABLE;
else if (rx > 0)
ccr |= CCR_RXENABLE;
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x03));
stl_ccrwait(portp);
stl_setreg(portp, CCR, ccr);
stl_ccrwait(portp);
BRDDISABLE(portp->brdnr);
splx(x);
}
/*****************************************************************************/
/*
* Start or stop the Transmitter and/or Receiver.
*/
static void stl_startrxtx(stlport_t *portp, int rx, int tx)
{
unsigned char sreron, sreroff;
int x;
#if DEBUG
printf("stl_startrxtx(portp=%x,rx=%d,tx=%d)\n", (int) portp, rx, tx);
#endif
sreron = 0;
sreroff = 0;
if (tx == 0)
sreroff |= (SRER_TXDATA | SRER_TXEMPTY);
else if (tx == 1)
sreron |= SRER_TXDATA;
else if (tx >= 2)
sreron |= SRER_TXEMPTY;
if (rx == 0)
sreroff |= SRER_RXDATA;
else if (rx > 0)
sreron |= SRER_RXDATA;
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
stl_setreg(portp, SRER,
((stl_getreg(portp, SRER) & ~sreroff) | sreron));
BRDDISABLE(portp->brdnr);
if (tx > 0)
portp->tty.t_state |= TS_BUSY;
splx(x);
}
/*****************************************************************************/
/*
* Disable all interrupts from this port.
*/
static void stl_disableintrs(stlport_t *portp)
{
int x;
#if DEBUG
printf("stl_disableintrs(portp=%x)\n", (int) portp);
#endif
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
stl_setreg(portp, SRER, 0);
BRDDISABLE(portp->brdnr);
splx(x);
}
/*****************************************************************************/
static void stl_sendbreak(stlport_t *portp, long len)
{
int x;
#if DEBUG
printf("stl_sendbreak(portp=%x,len=%d)\n", (int) portp, (int) len);
#endif
x = spltty();
BRDENABLE(portp->brdnr, portp->pagenr);
stl_setreg(portp, CAR, (portp->portnr & 0x3));
stl_setreg(portp, COR2, (stl_getreg(portp, COR2) | COR2_ETC));
stl_setreg(portp, SRER,
((stl_getreg(portp, SRER) & ~SRER_TXDATA) | SRER_TXEMPTY));
BRDDISABLE(portp->brdnr);
if (len > 0) {
len = len / 5;
portp->brklen = (len > 255) ? 255 : len;
} else {
portp->brklen = len;
}
splx(x);
portp->stats.txbreaks++;
}
/*****************************************************************************/
/*
* Enable l_rint processing bypass mode if tty modes allow it.
*/
static void stl_ttyoptim(stlport_t *portp, struct termios *tiosp)
{
struct tty *tp;
tp = &portp->tty;
if (((tiosp->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR)) == 0) &&
(((tiosp->c_iflag & BRKINT) == 0) || (tiosp->c_iflag & IGNBRK)) &&
(((tiosp->c_iflag & PARMRK) == 0) ||
((tiosp->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK))) &&
((tiosp->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) ==0) &&
(linesw[tp->t_line].l_rint == ttyinput))
tp->t_state |= TS_CAN_BYPASS_L_RINT;
else
tp->t_state &= ~TS_CAN_BYPASS_L_RINT;
portp->hotchar = linesw[tp->t_line].l_hotchar;
}
/*****************************************************************************/
/*
* Try and find and initialize all the ports on a panel. We don't care
* what sort of board these ports are on - since the port io registers
* are almost identical when dealing with ports.
*/
static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp)
{
stlport_t *portp;
unsigned int chipmask;
unsigned int gfrcr;
int nrchips, uartaddr, ioaddr;
int i, j;
#if DEBUG
printf("stl_initports(panelp=%x)\n", (int) panelp);
#endif
BRDENABLE(panelp->brdnr, panelp->pagenr);
/*
* Check that each chip is present and started up OK.
*/
chipmask = 0;
nrchips = panelp->nrports / CD1400_PORTS;
for (i = 0; (i < nrchips); i++) {
if (brdp->brdtype == BRD_ECHPCI) {
outb(brdp->ioctrl, (panelp->pagenr + (i >> 1)));
ioaddr = panelp->iobase;
} else {
ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 1));
}
uartaddr = (i & 0x01) ? 0x080 : 0;
outb(ioaddr, (GFRCR + uartaddr));
outb((ioaddr + EREG_DATA), 0);
outb(ioaddr, (CCR + uartaddr));
outb((ioaddr + EREG_DATA), CCR_RESETFULL);
outb((ioaddr + EREG_DATA), CCR_RESETFULL);
outb(ioaddr, (GFRCR + uartaddr));
for (j = 0; (j < CCR_MAXWAIT); j++) {
gfrcr = inb(ioaddr + EREG_DATA);
if ((gfrcr > 0x40) && (gfrcr < 0x60))
break;
}
if (j >= CCR_MAXWAIT) {
printf("STALLION: cd1400 not responding, brd=%d "
"panel=%d chip=%d\n", panelp->brdnr,
panelp->panelnr, i);
continue;
}
chipmask |= (0x1 << i);
outb(ioaddr, (PPR + uartaddr));
outb((ioaddr + EREG_DATA), PPR_SCALAR);
}
/*
* All cd1400's are initialized (if found!). Now go through and setup
* each ports data structures. Also init the LIVR register of cd1400
* for each port.
*/
ioaddr = panelp->iobase;
for (i = 0; (i < panelp->nrports); i++) {
if (brdp->brdtype == BRD_ECHPCI) {
outb(brdp->ioctrl, (panelp->pagenr + (i >> 3)));
ioaddr = panelp->iobase;
} else {
ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 3));
}
if ((chipmask & (0x1 << (i / 4))) == 0)
continue;
portp = (stlport_t *) malloc(sizeof(stlport_t), M_TTYS,
M_NOWAIT | M_ZERO);
if (portp == (stlport_t *) NULL) {
printf("STALLION: failed to allocate port memory "
"(size=%d)\n", sizeof(stlport_t));
break;
}
portp->portnr = i;
portp->brdnr = panelp->brdnr;
portp->panelnr = panelp->panelnr;
portp->clk = brdp->clk;
portp->ioaddr = ioaddr;
portp->uartaddr = (i & 0x4) << 5;
portp->pagenr = panelp->pagenr + (i >> 3);
portp->hwid = stl_getreg(portp, GFRCR);
stl_setreg(portp, CAR, (i & 0x3));
stl_setreg(portp, LIVR, (i << 3));
panelp->ports[i] = portp;
j = STL_TXBUFSIZE + (2 * STL_RXBUFSIZE);
portp->tx.buf = (char *) malloc(j, M_TTYS, M_NOWAIT);
if (portp->tx.buf == (char *) NULL) {
printf("STALLION: failed to allocate buffer memory "
"(size=%d)\n", j);
break;
}
portp->tx.endbuf = portp->tx.buf + STL_TXBUFSIZE;
portp->tx.head = portp->tx.buf;
portp->tx.tail = portp->tx.buf;
portp->rx.buf = portp->tx.buf + STL_TXBUFSIZE;
portp->rx.endbuf = portp->rx.buf + STL_RXBUFSIZE;
portp->rx.head = portp->rx.buf;
portp->rx.tail = portp->rx.buf;
portp->rxstatus.buf = portp->rx.buf + STL_RXBUFSIZE;
portp->rxstatus.endbuf = portp->rxstatus.buf + STL_RXBUFSIZE;
portp->rxstatus.head = portp->rxstatus.buf;
portp->rxstatus.tail = portp->rxstatus.buf;
bzero(portp->rxstatus.head, STL_RXBUFSIZE);
portp->initintios.c_ispeed = STL_DEFSPEED;
portp->initintios.c_ospeed = STL_DEFSPEED;
portp->initintios.c_cflag = STL_DEFCFLAG;
portp->initintios.c_iflag = 0;
portp->initintios.c_oflag = 0;
portp->initintios.c_lflag = 0;
bcopy(&ttydefchars[0], &portp->initintios.c_cc[0],
sizeof(portp->initintios.c_cc));
portp->initouttios = portp->initintios;
portp->dtrwait = 3 * hz;
}
BRDDISABLE(panelp->brdnr);
return(0);
}
/*****************************************************************************/
/*
* Try to find and initialize an EasyIO board.
*/
static int stl_initeio(stlbrd_t *brdp)
{
stlpanel_t *panelp;
unsigned int status;
#if DEBUG
printf("stl_initeio(brdp=%x)\n", (int) brdp);
#endif
brdp->ioctrl = brdp->ioaddr1 + 1;
brdp->iostatus = brdp->ioaddr1 + 2;
brdp->clk = EIO_CLK;
status = inb(brdp->iostatus);
switch (status & EIO_IDBITMASK) {
case EIO_8PORTM:
brdp->clk = EIO_CLK8M;
/* fall thru */
case EIO_8PORTRS:
case EIO_8PORTDI:
brdp->nrports = 8;
break;
case EIO_4PORTRS:
brdp->nrports = 4;
break;
default:
return(ENODEV);
}
/*
* Check that the supplied IRQ is good and then use it to setup the
* programmable interrupt bits on EIO board. Also set the edge/level
* triggered interrupt bit.
*/
if ((brdp->irq < 0) || (brdp->irq > 15) ||
(stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
printf("STALLION: invalid irq=%d for brd=%d\n", brdp->irq,
brdp->brdnr);
return(EINVAL);
}
outb(brdp->ioctrl, (stl_vecmap[brdp->irq] |
((brdp->irqtype) ? EIO_INTLEVEL : EIO_INTEDGE)));
panelp = (stlpanel_t *) malloc(sizeof(stlpanel_t), M_TTYS,
M_NOWAIT | M_ZERO);
if (panelp == (stlpanel_t *) NULL) {
printf("STALLION: failed to allocate memory (size=%d)\n",
sizeof(stlpanel_t));
return(ENOMEM);
}
panelp->brdnr = brdp->brdnr;
panelp->panelnr = 0;
panelp->nrports = brdp->nrports;
panelp->iobase = brdp->ioaddr1;
panelp->hwid = status;
brdp->panels[0] = panelp;
brdp->nrpanels = 1;
brdp->hwid = status;
brdp->state |= BRD_FOUND;
return(0);
}
/*****************************************************************************/
/*
* Try to find an ECH board and initialize it. This code is capable of
* dealing with all types of ECH board.
*/
static int stl_initech(stlbrd_t *brdp)
{
stlpanel_t *panelp;
unsigned int status, nxtid;
int panelnr, ioaddr, i;
#if DEBUG
printf("stl_initech(brdp=%x)\n", (int) brdp);
#endif
/*
* Set up the initial board register contents for boards. This varys a
* bit between the different board types. So we need to handle each
* separately. Also do a check that the supplied IRQ is good.
*/
if (brdp->brdtype == BRD_ECH) {
brdp->ioctrl = brdp->ioaddr1 + 1;
brdp->iostatus = brdp->ioaddr1 + 1;
status = inb(brdp->iostatus);
if ((status & ECH_IDBITMASK) != ECH_ID)
return(ENODEV);
brdp->hwid = status;
if ((brdp->irq < 0) || (brdp->irq > 15) ||
(stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
printf("STALLION: invalid irq=%d for brd=%d\n",
brdp->irq, brdp->brdnr);
return(EINVAL);
}
status = ((brdp->ioaddr2 & ECH_ADDR2MASK) >> 1);
status |= (stl_vecmap[brdp->irq] << 1);
outb(brdp->ioaddr1, (status | ECH_BRDRESET));
brdp->ioctrlval = ECH_INTENABLE |
((brdp->irqtype) ? ECH_INTLEVEL : ECH_INTEDGE);
outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDENABLE));
outb(brdp->ioaddr1, status);
} else if (brdp->brdtype == BRD_ECHMC) {
brdp->ioctrl = brdp->ioaddr1 + 0x20;
brdp->iostatus = brdp->ioctrl;
status = inb(brdp->iostatus);
if ((status & ECH_IDBITMASK) != ECH_ID)
return(ENODEV);
brdp->hwid = status;
if ((brdp->irq < 0) || (brdp->irq > 15) ||
(stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
printf("STALLION: invalid irq=%d for brd=%d\n",
brdp->irq, brdp->brdnr);
return(EINVAL);
}
outb(brdp->ioctrl, ECHMC_BRDRESET);
outb(brdp->ioctrl, ECHMC_INTENABLE);
} else if (brdp->brdtype == BRD_ECHPCI) {
brdp->ioctrl = brdp->ioaddr1 + 2;
}
brdp->clk = ECH_CLK;
/*
* Scan through the secondary io address space looking for panels.
* As we find'em allocate and initialize panel structures for each.
*/
ioaddr = brdp->ioaddr2;
panelnr = 0;
nxtid = 0;
for (i = 0; (i < STL_MAXPANELS); i++) {
if (brdp->brdtype == BRD_ECHPCI) {
outb(brdp->ioctrl, nxtid);
ioaddr = brdp->ioaddr2;
}
status = inb(ioaddr + ECH_PNLSTATUS);
if ((status & ECH_PNLIDMASK) != nxtid)
break;
panelp = (stlpanel_t *) malloc(sizeof(stlpanel_t), M_TTYS,
M_NOWAIT | M_ZERO);
if (panelp == (stlpanel_t *) NULL) {
printf("STALLION: failed to allocate memory"
"(size=%d)\n", sizeof(stlpanel_t));
break;
}
panelp->brdnr = brdp->brdnr;
panelp->panelnr = panelnr;
panelp->iobase = ioaddr;
panelp->pagenr = nxtid;
panelp->hwid = status;
if (status & ECH_PNL16PORT) {
if ((brdp->nrports + 16) > 32)
break;
panelp->nrports = 16;
panelp->ackmask = 0x80;
brdp->nrports += 16;
ioaddr += (EREG_BANKSIZE * 2);
nxtid += 2;
} else {
panelp->nrports = 8;
panelp->ackmask = 0xc0;
brdp->nrports += 8;
ioaddr += EREG_BANKSIZE;
nxtid++;
}
brdp->panels[panelnr++] = panelp;
brdp->nrpanels++;
if (ioaddr >= (brdp->ioaddr2 + 0x20))
break;
}
if (brdp->brdtype == BRD_ECH)
outb(brdp->ioctrl, (brdp->ioctrlval | ECH_BRDDISABLE));
brdp->state |= BRD_FOUND;
return(0);
}
/*****************************************************************************/
/*
* Initialize and configure the specified board. This firstly probes
* for the board, if it is found then the board is initialized and
* then all its ports are initialized as well.
*/
static int stl_brdinit(stlbrd_t *brdp)
{
stlpanel_t *panelp;
int i, j, k;
#if DEBUG
printf("stl_brdinit(brdp=%x): unit=%d type=%d io1=%x io2=%x irq=%d\n",
(int) brdp, brdp->brdnr, brdp->brdtype, brdp->ioaddr1,
brdp->ioaddr2, brdp->irq);
#endif
switch (brdp->brdtype) {
case BRD_EASYIO:
stl_initeio(brdp);
break;
case BRD_ECH:
case BRD_ECHMC:
case BRD_ECHPCI:
stl_initech(brdp);
break;
default:
printf("STALLION: unit=%d is unknown board type=%d\n",
brdp->brdnr, brdp->brdtype);
return(ENODEV);
}
stl_brds[brdp->brdnr] = brdp;
if ((brdp->state & BRD_FOUND) == 0) {
#if 0
printf("STALLION: %s board not found, unit=%d io=%x irq=%d\n",
stl_brdnames[brdp->brdtype], brdp->brdnr,
brdp->ioaddr1, brdp->irq);
#endif
return(ENODEV);
}
for (i = 0, k = 0; (i < STL_MAXPANELS); i++) {
panelp = brdp->panels[i];
if (panelp != (stlpanel_t *) NULL) {
stl_initports(brdp, panelp);
for (j = 0; (j < panelp->nrports); j++)
brdp->ports[k++] = panelp->ports[j];
}
}
printf("stl%d: %s (driver version %s) unit=%d nrpanels=%d nrports=%d\n",
brdp->unitid, stl_brdnames[brdp->brdtype], stl_drvversion,
brdp->brdnr, brdp->nrpanels, brdp->nrports);
return(0);
}
/*****************************************************************************/
/*
* Return the board stats structure to user app.
*/
static int stl_getbrdstats(caddr_t data)
{
stlbrd_t *brdp;
stlpanel_t *panelp;
int i;
stl_brdstats = *((combrd_t *) data);
if (stl_brdstats.brd >= STL_MAXBRDS)
return(-ENODEV);
brdp = stl_brds[stl_brdstats.brd];
if (brdp == (stlbrd_t *) NULL)
return(-ENODEV);
bzero(&stl_brdstats, sizeof(combrd_t));
stl_brdstats.brd = brdp->brdnr;
stl_brdstats.type = brdp->brdtype;
stl_brdstats.hwid = brdp->hwid;
stl_brdstats.state = brdp->state;
stl_brdstats.ioaddr = brdp->ioaddr1;
stl_brdstats.ioaddr2 = brdp->ioaddr2;
stl_brdstats.irq = brdp->irq;
stl_brdstats.nrpanels = brdp->nrpanels;
stl_brdstats.nrports = brdp->nrports;
for (i = 0; (i < brdp->nrpanels); i++) {
panelp = brdp->panels[i];
stl_brdstats.panels[i].panel = i;
stl_brdstats.panels[i].hwid = panelp->hwid;
stl_brdstats.panels[i].nrports = panelp->nrports;
}
*((combrd_t *) data) = stl_brdstats;;
return(0);
}
/*****************************************************************************/
/*
* Resolve the referenced port number into a port struct pointer.
*/
static stlport_t *stl_getport(int brdnr, int panelnr, int portnr)
{
stlbrd_t *brdp;
stlpanel_t *panelp;
if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
return((stlport_t *) NULL);
brdp = stl_brds[brdnr];
if (brdp == (stlbrd_t *) NULL)
return((stlport_t *) NULL);
if ((panelnr < 0) || (panelnr >= brdp->nrpanels))
return((stlport_t *) NULL);
panelp = brdp->panels[panelnr];
if (panelp == (stlpanel_t *) NULL)
return((stlport_t *) NULL);
if ((portnr < 0) || (portnr >= panelp->nrports))
return((stlport_t *) NULL);
return(panelp->ports[portnr]);
}
/*****************************************************************************/
/*
* Return the port stats structure to user app. A NULL port struct
* pointer passed in means that we need to find out from the app
* what port to get stats for (used through board control device).
*/
static int stl_getportstats(stlport_t *portp, caddr_t data)
{
unsigned char *head, *tail;
if (portp == (stlport_t *) NULL) {
stl_comstats = *((comstats_t *) data);
portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
stl_comstats.port);
if (portp == (stlport_t *) NULL)
return(-ENODEV);
}
portp->stats.state = portp->state;
/*portp->stats.flags = portp->flags;*/
portp->stats.hwid = portp->hwid;
portp->stats.ttystate = portp->tty.t_state;
portp->stats.cflags = portp->tty.t_cflag;
portp->stats.iflags = portp->tty.t_iflag;
portp->stats.oflags = portp->tty.t_oflag;
portp->stats.lflags = portp->tty.t_lflag;
head = portp->tx.head;
tail = portp->tx.tail;
portp->stats.txbuffered = ((head >= tail) ? (head - tail) :
(STL_TXBUFSIZE - (tail - head)));
head = portp->rx.head;
tail = portp->rx.tail;
portp->stats.rxbuffered = (head >= tail) ? (head - tail) :
(STL_RXBUFSIZE - (tail - head));
portp->stats.signals = (unsigned long) stl_getsignals(portp);
*((comstats_t *) data) = portp->stats;
return(0);
}
/*****************************************************************************/
/*
* Clear the port stats structure. We also return it zeroed out...
*/
static int stl_clrportstats(stlport_t *portp, caddr_t data)
{
if (portp == (stlport_t *) NULL) {
stl_comstats = *((comstats_t *) data);
portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
stl_comstats.port);
if (portp == (stlport_t *) NULL)
return(-ENODEV);
}
bzero(&portp->stats, sizeof(comstats_t));
portp->stats.brd = portp->brdnr;
portp->stats.panel = portp->panelnr;
portp->stats.port = portp->portnr;
*((comstats_t *) data) = stl_comstats;
return(0);
}
/*****************************************************************************/
/*
* The "staliomem" device is used for stats collection in this driver.
*/
static int stl_memioctl(dev_t dev, unsigned long cmd, caddr_t data, int flag,
struct thread *td)
{
stlbrd_t *brdp;
int brdnr, rc;
#if DEBUG
printf("stl_memioctl(dev=%s,cmd=%lx,data=%p,flag=%x)\n",
devtoname(dev), cmd, (void *) data, flag);
#endif
brdnr = minor(dev) & 0x7;
brdp = stl_brds[brdnr];
if (brdp == (stlbrd_t *) NULL)
return(ENODEV);
if (brdp->state == 0)
return(ENODEV);
rc = 0;
switch (cmd) {
case COM_GETPORTSTATS:
rc = stl_getportstats((stlport_t *) NULL, data);
break;
case COM_CLRPORTSTATS:
rc = stl_clrportstats((stlport_t *) NULL, data);
break;
case COM_GETBRDSTATS:
rc = stl_getbrdstats(data);
break;
default:
rc = ENOTTY;
break;
}
return(rc);
}
/*****************************************************************************/