/*****************************************************************************/ /* * 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 #include #include #include #include #include #include #include #include #include #include #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 #include #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)) { 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)) == 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)) == 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); } /*****************************************************************************/