diff options
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/fdc/fdc.c | 903 | ||||
-rw-r--r-- | sys/dev/fdc/fdcreg.h | 72 | ||||
-rw-r--r-- | sys/dev/ic/i8237.h | 9 | ||||
-rw-r--r-- | sys/dev/ic/nec765.h | 71 | ||||
-rw-r--r-- | sys/dev/ic/ns16550.h | 50 | ||||
-rw-r--r-- | sys/dev/sio/sio.c | 1721 | ||||
-rw-r--r-- | sys/dev/sio/sioreg.h | 113 | ||||
-rw-r--r-- | sys/dev/speaker/spkr.c | 520 |
8 files changed, 3459 insertions, 0 deletions
diff --git a/sys/dev/fdc/fdc.c b/sys/dev/fdc/fdc.c new file mode 100644 index 0000000..a461e81 --- /dev/null +++ b/sys/dev/fdc/fdc.c @@ -0,0 +1,903 @@ +/*#define DEBUG 1*/ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Don Ahn. + * + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)fd.c 7.4 (Berkeley) 5/25/91 + * + * PATCHES MAGIC LEVEL PATCH THAT GOT US HERE + * -------------------- ----- ---------------------- + * CURRENT PATCH LEVEL: 1 00153 + * -------------------- ----- ---------------------- + * + * 20 Apr 93 Julian Elischer Heavily re worked, see notes below + * + * Largely rewritten to handle multiple controllers and drives + * By Julian Elischer, Sun Apr 4 16:34:33 WST 1993 + */ +char rev[] = "$Revision: 1.10 $"; +/* + * $Header: /usr/src/sys.386bsd/i386/isa/RCS/fd.c,v 1.10 93/04/13 16:53:29 root Exp $ + */ +/* + * $Log: fd.c,v $ + * Revision 1.10 93/04/13 16:53:29 root + * make sure turning off a drive motor doesn't deselect another + * drive active at the time. + * Also added a pointer from the fd_data to it's fd_type. + * + * Revision 1.9 93/04/13 15:31:02 root + * make all seeks go through DOSEEK state so are sure of being done right. + * + * Revision 1.8 93/04/12 21:20:13 root + * only check if old fd is the one we are working on if there IS + * an old fd pointer. (in fdstate()) + * + * Revision 1.7 93/04/11 17:05:35 root + * cleanup timeouts etc. + * also fix bug to select teh correct drive when running > 1 drive + * at a time. + * + * Revision 1.6 93/04/05 00:48:45 root + * change a timeout and add version to banner message + * + * Revision 1.5 93/04/04 16:39:08 root + * first working version.. some floppy controllers don't seem to + * like 2 int. status inquiries in a row. + * + */ + +#include "fd.h" +#if NFD > 0 + +#include "param.h" +#include "dkbad.h" +#include "systm.h" +#include "conf.h" +#include "file.h" +#include "ioctl.h" +#include "buf.h" +#include "uio.h" +#include "i386/isa/isa.h" +#include "i386/isa/isa_device.h" +#include "i386/isa/fdreg.h" +#include "i386/isa/icu.h" +#include "i386/isa/rtc.h" +#undef NFD +#define NFD 2 + +#define FDUNIT(s) ((s>>3)&1) +#define FDTYPE(s) ((s)&7) + +#define b_cylin b_resid +#define FDBLK 512 +#define NUMTYPES 4 + +struct fd_type { + int sectrac; /* sectors per track */ + int secsize; /* size code for sectors */ + int datalen; /* data len when secsize = 0 */ + int gap; /* gap len between sectors */ + int tracks; /* total num of tracks */ + int size; /* size of disk in sectors */ + int steptrac; /* steps per cylinder */ + int trans; /* transfer speed code */ + int heads; /* number of heads */ +}; + +struct fd_type fd_types[NUMTYPES] = +{ + { 18,2,0xFF,0x1B,80,2880,1,0,2 }, /* 1.44 meg HD 3.5in floppy */ + { 15,2,0xFF,0x1B,80,2400,1,0,2 }, /* 1.2 meg HD floppy */ + { 9,2,0xFF,0x23,40,720,2,1,2 }, /* 360k floppy in 1.2meg drive */ + { 9,2,0xFF,0x2A,40,720,1,1,2 }, /* 360k floppy in DD drive */ +}; + +#define DRVS_PER_CTLR 2 +/***********************************************************************\ +* Per controller structure. * +\***********************************************************************/ +struct fdc_data +{ + int fdcu; /* our unit number */ + int baseport; + int dmachan; + int flags; +#define FDC_ATTACHED 0x01 + struct fd_data *fd; + int fdu; /* the active drive */ + struct buf head; /* Head of buf chain */ + struct buf rhead; /* Raw head of buf chain */ + int state; + int retry; + int status[7]; /* copy of the registers */ +}fdc_data[(NFD+1)/DRVS_PER_CTLR]; + +/***********************************************************************\ +* Per drive structure. * +* N per controller (presently 2) (DRVS_PER_CTLR) * +\***********************************************************************/ +struct fd_data { + struct fdc_data *fdc; + int fdu; /* this unit number */ + int fdsu; /* this units number on this controller */ + int type; /* Drive type (HD, DD */ + struct fd_type *ft; /* pointer to the type descriptor */ + int flags; +#define FD_OPEN 0x01 /* it's open */ +#define FD_ACTIVE 0x02 /* it's active */ +#define FD_MOTOR 0x04 /* motor should be on */ +#define FD_MOTOR_WAIT 0x08 /* motor coming up */ + int skip; + int hddrv; + int track; /* where we think the head is */ +} fd_data[NFD]; + +/***********************************************************************\ +* Throughout this file the following conventions will be used: * +* fd is a pointer to the fd_data struct for the drive in question * +* fdc is a pointer to the fdc_data struct for the controller * +* fdu is the floppy drive unit number * +* fdcu is the floppy controller unit number * +* fdsu is the floppy drive unit number on that controller. (sub-unit) * +\***********************************************************************/ +typedef int fdu_t; +typedef int fdcu_t; +typedef int fdsu_t; +typedef struct fd_data *fd_p; +typedef struct fdc_data *fdc_p; + +#define DEVIDLE 0 +#define FINDWORK 1 +#define DOSEEK 2 +#define SEEKCOMPLETE 3 +#define IOCOMPLETE 4 +#define RECALCOMPLETE 5 +#define STARTRECAL 6 +#define RESETCTLR 7 +#define SEEKWAIT 8 +#define RECALWAIT 9 +#define MOTORWAIT 10 +#define IOTIMEDOUT 11 + +#ifdef DEBUG +char *fdstates[] = +{ +"DEVIDLE", +"FINDWORK", +"DOSEEK", +"SEEKCOMPLETE", +"IOCOMPLETE", +"RECALCOMPLETE", +"STARTRECAL", +"RESETCTLR", +"SEEKWAIT", +"RECALWAIT", +"MOTORWAIT", +"IOTIMEDOUT" +}; + + +int fd_debug = 1; +#define TRACE0(arg) if(fd_debug) printf(arg) +#define TRACE1(arg1,arg2) if(fd_debug) printf(arg1,arg2) +#else DEBUG +#define TRACE0(arg) +#define TRACE1(arg1,arg2) +#endif DEBUG + +extern int hz; +/* state needed for current transfer */ + +/****************************************************************************/ +/* autoconfiguration stuff */ +/****************************************************************************/ +int fdprobe(), fdattach(), fd_turnoff(); + +struct isa_driver fddriver = { + fdprobe, fdattach, "fd", +}; + +/* + * probe for existance of controller + */ +fdprobe(dev) +struct isa_device *dev; +{ + fdcu_t fdcu = dev->id_unit; + if(fdc_data[fdcu].flags & FDC_ATTACHED) + { + printf("fdc: same unit (%d) used multiple times\n",fdcu); + return 0; + } + + fdc_data[fdcu].baseport = dev->id_iobase; + + /* see if it can handle a command */ + if (out_fdc(fdcu,NE7CMD_SPECIFY) < 0) + { + return(0); + } + out_fdc(fdcu,0xDF); + out_fdc(fdcu,2); + return (IO_FDCSIZE); +} + +/* + * wire controller into system, look for floppy units + */ +fdattach(dev) +struct isa_device *dev; +{ + unsigned fdt,st0, cyl; + int hdr; + fdu_t fdu; + fdcu_t fdcu = dev->id_unit; + fdc_p fdc = fdc_data + fdcu; + fd_p fd; + int fdsu; + + fdc->fdcu = fdcu; + fdc->flags |= FDC_ATTACHED; + fdc->dmachan = dev->id_drq; + fdc->state = DEVIDLE; + + fdt = rtcin(RTC_FDISKETTE); + hdr = 0; + + /* check for each floppy drive */ + for (fdu = (fdcu * DRVS_PER_CTLR),fdsu = 0; + ((fdu < NFD) && (fdsu < DRVS_PER_CTLR)); + fdu++,fdsu++) + { + /* is there a unit? */ + if ((fdt & 0xf0) == RTCFDT_NONE) + continue; + +#ifdef notyet + /* select it */ + fd_turnon1(fdu); + spinwait(1000); /* 1 sec */ + out_fdc(fdcu,NE7CMD_RECAL); /* Recalibrate Function */ + out_fdc(fdcu,fdsu); + spinwait(1000); /* 1 sec */ + + /* anything responding */ + out_fdc(fdcu,NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + cyl = in_fdc(fdcu); + if (st0 & 0xd0) + continue; + +#endif + fd_data[fdu].track = -2; + fd_data[fdu].fdc = fdc; + fd_data[fdu].fdsu = fdsu; + /* yes, announce it */ + if (!hdr) + printf(" drives "); + else + printf(", "); + printf("%d: ", fdu); + + + if ((fdt & 0xf0) == RTCFDT_12M) { + printf("1.2M"); + fd_data[fdu].type = 1; + fd_data[fdu].ft = fd_types + 1; + + } + if ((fdt & 0xf0) == RTCFDT_144M) { + printf("1.44M"); + fd_data[fdu].type = 0; + fd_data[fdu].ft = fd_types + 0; + } + + fdt <<= 4; + fd_turnoff(fdu); + hdr = 1; + } + + printf(" %s ",rev); + /* Set transfer to 500kbps */ + outb(fdc->baseport+fdctl,0); /*XXX*/ +} + +int +fdsize(dev) +dev_t dev; +{ + return(0); +} + +/****************************************************************************/ +/* fdstrategy */ +/****************************************************************************/ +fdstrategy(bp) + register struct buf *bp; /* IO operation to perform */ +{ + register struct buf *dp,*dp0,*dp1; + long nblocks,blknum; + int s; + fdcu_t fdcu; + fdu_t fdu; + fdc_p fdc; + fd_p fd; + + fdu = FDUNIT(minor(bp->b_dev)); + fd = &fd_data[fdu]; + fdc = fd->fdc; + fdcu = fdc->fdcu; + /*type = FDTYPE(minor(bp->b_dev));*/ + + if ((fdu >= NFD) || (bp->b_blkno < 0)) { + printf("fdstrat: fdu = %d, blkno = %d, bcount = %d\n", + fdu, bp->b_blkno, bp->b_bcount); + pg("fd:error in fdstrategy"); + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + goto bad; + } + /* + * Set up block calculations. + */ + blknum = (unsigned long) bp->b_blkno * DEV_BSIZE/FDBLK; + nblocks = fd->ft->size; + if (blknum + (bp->b_bcount / FDBLK) > nblocks) { + if (blknum == nblocks) { + bp->b_resid = bp->b_bcount; + } else { + bp->b_error = ENOSPC; + bp->b_flags |= B_ERROR; + } + goto bad; + } + bp->b_cylin = blknum / (fd->ft->sectrac * fd->ft->heads); + dp = &(fdc->head); + s = splbio(); + disksort(dp, bp); + untimeout(fd_turnoff,fdu); /* a good idea */ + fdstart(fdcu); + splx(s); + return; + +bad: + biodone(bp); +} + +/****************************************************************************/ +/* motor control stuff */ +/* remember to not deselect the drive we're working on */ +/****************************************************************************/ +set_motor(fdcu_t fdcu, fdu_t fdu, int reset) +{ + int m0,m1; + int selunit; + fd_p fd; + if(fd = fdc_data[fdcu].fd)/* yes an assign! */ + { + selunit = fd->fdsu; + } + else + { + selunit = 0; + } + m0 = fd_data[fdcu * DRVS_PER_CTLR + 0].flags & FD_MOTOR; + m1 = fd_data[fdcu * DRVS_PER_CTLR + 1].flags & FD_MOTOR; + outb(fdc_data[fdcu].baseport+fdout, + selunit + | (reset ? 0 : (FDO_FRST|FDO_FDMAEN)) + | (m0 ? FDO_MOEN0 : 0) + | (m1 ? FDO_MOEN1 : 0)); + TRACE1("[0x%x->fdout]",( + selunit + | (reset ? 0 : (FDO_FRST|FDO_FDMAEN)) + | (m0 ? FDO_MOEN0 : 0) + | (m1 ? FDO_MOEN1 : 0))); +} + +fd_turnoff(fdu_t fdu) +{ + fd_p fd = fd_data + fdu; + fd->flags &= ~FD_MOTOR; + set_motor(fd->fdc->fdcu,fd->fdsu,0); +} + +fd_motor_on(fdu_t fdu) +{ + fd_p fd = fd_data + fdu; + fd->flags &= ~FD_MOTOR_WAIT; + if((fd->fdc->fd == fd) && (fd->fdc->state == MOTORWAIT)) + { + fd_pseudointr(fd->fdc->fdcu); + } +} + +fd_turnon(fdu_t fdu) +{ + fd_p fd = fd_data + fdu; + if(!(fd->flags & FD_MOTOR)) + { + fd_turnon1(fdu); + fd->flags |= FD_MOTOR_WAIT; + timeout(fd_motor_on,fdu,hz); /* in 1 sec its ok */ + } +} + +fd_turnon1(fdu_t fdu) +{ + fd_p fd = fd_data + fdu; + fd->flags |= FD_MOTOR; + set_motor(fd->fdc->fdcu,fd->fdsu,0); +} + +/****************************************************************************/ +/* fdc in/out */ +/****************************************************************************/ +int +in_fdc(fdcu_t fdcu) +{ + int baseport = fdc_data[fdcu].baseport; + int i, j = 100000; + while ((i = inb(baseport+fdsts) & (NE7_DIO|NE7_RQM)) + != (NE7_DIO|NE7_RQM) && j-- > 0) + if (i == NE7_RQM) return -1; + if (j <= 0) + return(-1); +#ifdef DEBUG + i = inb(baseport+fddata); + TRACE1("[fddata->0x%x]",(unsigned char)i); + return(i); +#else + return inb(baseport+fddata); +#endif +} + +out_fdc(fdcu_t fdcu,int x) +{ + int baseport = fdc_data[fdcu].baseport; + int i = 100000; + + while ((inb(baseport+fdsts) & NE7_DIO) && i-- > 0); + while ((inb(baseport+fdsts) & NE7_RQM) == 0 && i-- > 0); + if (i <= 0) return (-1); + outb(baseport+fddata,x); + TRACE1("[0x%x->fddata]",x); + return (0); +} + +static fdopenf; +/****************************************************************************/ +/* fdopen/fdclose */ +/****************************************************************************/ +Fdopen(dev, flags) + dev_t dev; + int flags; +{ + fdu_t fdu = FDUNIT(minor(dev)); + /*int type = FDTYPE(minor(dev));*/ + int s; + + /* check bounds */ + if (fdu >= NFD) return(ENXIO); + /*if (type >= NUMTYPES) return(ENXIO);*/ + fd_data[fdu].flags |= FD_OPEN; + + return 0; +} + +fdclose(dev, flags) + dev_t dev; +{ + fdu_t fdu = FDUNIT(minor(dev)); + fd_data[fdu].flags &= ~FD_OPEN; + return(0); +} + + +/***************************************************************\ +* fdstart * +* We have just queued something.. if the controller is not busy * +* then simulate the case where it has just finished a command * +* So that it (the interrupt routine) looks on the queue for more* +* work to do and picks up what we just added. * +* If the controller is already busy, we need do nothing, as it * +* will pick up our work when the present work completes * +\***************************************************************/ +fdstart(fdcu_t fdcu) +{ + register struct buf *dp,*bp; + int s; + fdu_t fdu; + + s = splbio(); + if(fdc_data[fdcu].state == DEVIDLE) + { + fdintr(fdcu); + } + splx(s); +} + +fd_timeout(fdcu_t fdcu) +{ + fdu_t fdu = fdc_data[fdcu].fdu; + int st0, st3, cyl; + struct buf *dp,*bp; + + dp = &fdc_data[fdcu].head; + bp = dp->b_actf; + + out_fdc(fdcu,NE7CMD_SENSED); + out_fdc(fdcu,fd_data[fdu].hddrv); + st3 = in_fdc(fdcu); + + out_fdc(fdcu,NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + cyl = in_fdc(fdcu); + printf("fd%d: Operation timeout ST0 %b cyl %d ST3 %b\n", + fdu, + st0, + NE7_ST0BITS, + cyl, + st3, + NE7_ST3BITS); + + if (bp) + { + retrier(fdcu); + fdc_data[fdcu].status[0] = 0xc0; + fdc_data[fdcu].state = IOTIMEDOUT; + if( fdc_data[fdcu].retry < 6) + fdc_data[fdcu].retry = 6; + } + else + { + fdc_data[fdcu].fd = (fd_p) 0; + fdc_data[fdcu].fdu = -1; + fdc_data[fdcu].state = DEVIDLE; + } + fd_pseudointr(fdcu); +} + +/* just ensure it has the right spl */ +fd_pseudointr(fdcu_t fdcu) +{ + int s; + s = splbio(); + fdintr(fdcu); + splx(s); +} + +/***********************************************************************\ +* fdintr * +* keep calling the state machine until it returns a 0 * +* ALWAYS called at SPLBIO * +\***********************************************************************/ +fdintr(fdcu_t fdcu) +{ + fdc_p fdc = fdc_data + fdcu; + while(fdstate(fdcu, fdc)); +} + +/***********************************************************************\ +* The controller state machine. * +* if it returns a non zero value, it should be called again immediatly * +\***********************************************************************/ +int fdstate(fdcu_t fdcu, fdc_p fdc) +{ + int read,head,trac,sec,i,s,sectrac,cyl,st0; + unsigned long blknum; + fdu_t fdu = fdc->fdu; + fd_p fd; + register struct buf *dp,*bp; + + dp = &(fdc->head); + bp = dp->b_actf; + if(!bp) + { + /***********************************************\ + * nothing left for this controller to do * + * Force into the IDLE state, * + \***********************************************/ + fdc->state = DEVIDLE; + if(fdc->fd) + { + printf("unexpected valid fd pointer (fdu = %d)\n" + ,fdc->fdu); + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + } + TRACE1("[fdc%d IDLE]",fdcu); + return(0); + } + fdu = FDUNIT(minor(bp->b_dev)); + fd = fd_data + fdu; + if (fdc->fd && (fd != fdc->fd)) + { + printf("confused fd pointers\n"); + } + read = bp->b_flags & B_READ; + TRACE1("fd%d",fdu); + TRACE1("[%s]",fdstates[fdc->state]); + TRACE1("(0x%x)",fd->flags); + untimeout(fd_turnoff, fdu); + timeout(fd_turnoff,fdu,4 * hz); + switch (fdc->state) + { + case DEVIDLE: + case FINDWORK: /* we have found new work */ + fdc->retry = 0; + fd->skip = 0; + fdc->fd = fd; + fdc->fdu = fdu; + /*******************************************************\ + * If the next drive has a motor startup pending, then * + * it will start up in it's own good time * + \*******************************************************/ + if(fd->flags & FD_MOTOR_WAIT) + { + fdc->state = MOTORWAIT; + return(0); /* come back later */ + } + /*******************************************************\ + * Maybe if it's not starting, it SHOULD be starting * + \*******************************************************/ + if (!(fd->flags & FD_MOTOR)) + { + fdc->state = MOTORWAIT; + fd_turnon(fdu); + return(0); + } + else /* at least make sure we are selected */ + { + set_motor(fdcu,fd->fdsu,0); + } + fdc->state = DOSEEK; + break; + case DOSEEK: + if (bp->b_cylin == fd->track) + { + fdc->state = SEEKCOMPLETE; + break; + } + out_fdc(fdcu,NE7CMD_SEEK); /* Seek function */ + out_fdc(fdcu,fd->fdsu); /* Drive number */ + out_fdc(fdcu,bp->b_cylin * fd->ft->steptrac); + fd->track = -2; + fdc->state = SEEKWAIT; + return(0); /* will return later */ + case SEEKWAIT: + /* allow heads to settle */ + timeout(fd_pseudointr,fdcu,hz/50); + fdc->state = SEEKCOMPLETE; + return(0); /* will return later */ + break; + + case SEEKCOMPLETE : /* SEEK DONE, START DMA */ + /* Make sure seek really happened*/ + if(fd->track == -2) + { + int descyl = bp->b_cylin * fd->ft->steptrac; + out_fdc(fdcu,NE7CMD_SENSEI); + i = in_fdc(fdcu); + cyl = in_fdc(fdcu); + if (cyl != descyl) + { + printf("fd%d: Seek to cyl %d failed; am at cyl %d (ST0 = 0x%x)\n", fdu, + descyl, cyl, i, NE7_ST0BITS); + return(retrier(fdcu)); + } + } + + fd->track = bp->b_cylin; + isa_dmastart(bp->b_flags, bp->b_un.b_addr+fd->skip, + FDBLK, fdc->dmachan); + blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/FDBLK + + fd->skip/FDBLK; + sectrac = fd->ft->sectrac; + sec = blknum % (sectrac * fd->ft->heads); + head = sec / sectrac; + sec = sec % sectrac + 1; +/*XXX*/ fd->hddrv = ((head&1)<<2)+fdu; + + if (read) + { + out_fdc(fdcu,NE7CMD_READ); /* READ */ + } + else + { + out_fdc(fdcu,NE7CMD_WRITE); /* WRITE */ + } + out_fdc(fdcu,head << 2 | fdu); /* head & unit */ + out_fdc(fdcu,fd->track); /* track */ + out_fdc(fdcu,head); + out_fdc(fdcu,sec); /* sector XXX +1? */ + out_fdc(fdcu,fd->ft->secsize); /* sector size */ + out_fdc(fdcu,sectrac); /* sectors/track */ + out_fdc(fdcu,fd->ft->gap); /* gap size */ + out_fdc(fdcu,fd->ft->datalen); /* data length */ + fdc->state = IOCOMPLETE; + timeout(fd_timeout,fdcu,2 * hz); + return(0); /* will return later */ + case IOCOMPLETE: /* IO DONE, post-analyze */ + untimeout(fd_timeout,fdcu); + for(i=0;i<7;i++) + { + fdc->status[i] = in_fdc(fdcu); + } + case IOTIMEDOUT: /*XXX*/ + isa_dmadone(bp->b_flags, bp->b_un.b_addr+fd->skip, + FDBLK, fdc->dmachan); + if (fdc->status[0]&0xF8) + { + return(retrier(fdcu)); + } + /* All OK */ + fd->skip += FDBLK; + if (fd->skip < bp->b_bcount) + { + /* set up next transfer */ + blknum = (unsigned long)bp->b_blkno*DEV_BSIZE/FDBLK + + fd->skip/FDBLK; + bp->b_cylin = (blknum / (fd->ft->sectrac * fd->ft->heads)); + fdc->state = DOSEEK; + } + else + { + /* ALL DONE */ + fd->skip = 0; + bp->b_resid = 0; + dp->b_actf = bp->av_forw; + biodone(bp); + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + fdc->state = FINDWORK; + } + return(1); + case RESETCTLR: + /* Try a reset, keep motor on */ + set_motor(fdcu,fd->fdsu,1); + DELAY(100); + set_motor(fdcu,fd->fdsu,0); + outb(fdc->baseport+fdctl,fd->ft->trans); + TRACE1("[0x%x->fdctl]",fd->ft->trans); + fdc->retry++; + fdc->state = STARTRECAL; + break; + case STARTRECAL: + out_fdc(fdcu,NE7CMD_SPECIFY); /* specify command */ + out_fdc(fdcu,0xDF); + out_fdc(fdcu,2); + out_fdc(fdcu,NE7CMD_RECAL); /* Recalibrate Function */ + out_fdc(fdcu,fdu); + fdc->state = RECALWAIT; + return(0); /* will return later */ + case RECALWAIT: + /* allow heads to settle */ + timeout(fd_pseudointr,fdcu,hz/30); + fdc->state = RECALCOMPLETE; + return(0); /* will return later */ + case RECALCOMPLETE: + out_fdc(fdcu,NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + cyl = in_fdc(fdcu); + if (cyl != 0) + { + printf("fd%d: recal failed ST0 %b cyl %d\n", fdu, + st0, NE7_ST0BITS, cyl); + return(retrier(fdcu)); + } + fd->track = 0; + /* Seek (probably) necessary */ + fdc->state = DOSEEK; + return(1); /* will return immediatly */ + case MOTORWAIT: + if(fd->flags & FD_MOTOR_WAIT) + { + return(0); /* time's not up yet */ + } + fdc->state = DOSEEK; + return(1); /* will return immediatly */ + default: + printf("Unexpected FD int->"); + out_fdc(fdcu,NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + cyl = in_fdc(fdcu); + printf("ST0 = %lx, PCN = %lx\n",i,sec); + out_fdc(fdcu,0x4A); + out_fdc(fdcu,fd->fdsu); + for(i=0;i<7;i++) { + fdc->status[i] = in_fdc(fdcu); + } + printf("intr status :%lx %lx %lx %lx %lx %lx %lx ", + fdc->status[0], + fdc->status[1], + fdc->status[2], + fdc->status[3], + fdc->status[4], + fdc->status[5], + fdc->status[6] ); + return(0); + } + return(1); /* Come back immediatly to new state */ +} + +retrier(fdcu_t fdcu) +{ + fdc_p fdc = fdc_data + fdcu; + register struct buf *dp,*bp; + + dp = &(fdc->head); + bp = dp->b_actf; + + switch(fdc->retry) + { + case 0: case 1: case 2: + fdc->state = SEEKCOMPLETE; + break; + case 3: case 4: case 5: + fdc->state = STARTRECAL; + break; + case 6: + fdc->state = RESETCTLR; + break; + case 7: + break; + default: + { + printf("fd%d: hard error (ST0 %b ", + fdc->fdu, fdc->status[0], NE7_ST0BITS); + printf(" ST1 %b ", fdc->status[1], NE7_ST1BITS); + printf(" ST2 %b ", fdc->status[2], NE7_ST2BITS); + printf(" ST3 %b ", fdc->status[3], NE7_ST3BITS); + printf("cyl %d hd %d sec %d)\n", + fdc->status[4], fdc->status[5], fdc->status[6]); + } + bp->b_flags |= B_ERROR; + bp->b_error = EIO; + bp->b_resid = bp->b_bcount - fdc->fd->skip; + dp->b_actf = bp->av_forw; + fdc->fd->skip = 0; + biodone(bp); + fdc->state = FINDWORK; + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + return(1); + } + fdc->retry++; + return(1); +} + +#endif + diff --git a/sys/dev/fdc/fdcreg.h b/sys/dev/fdc/fdcreg.h new file mode 100644 index 0000000..948f566 --- /dev/null +++ b/sys/dev/fdc/fdcreg.h @@ -0,0 +1,72 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)fdreg.h 7.1 (Berkeley) 5/9/91 + * + * PATCHES MAGIC LEVEL PATCH THAT GOT US HERE + * -------------------- ----- ---------------------- + * CURRENT PATCH LEVEL: 1 00153 + * -------------------- ----- ---------------------- + * + * 20 Apr 93 Julian Elischer Heavily re worked, see notes below + */ + +/* + * AT floppy controller registers and bitfields + */ + +/* uses NEC765 controller */ +#include "../i386/isa/ic/nec765.h" + +/* registers */ +#define fdout 2 /* Digital Output Register (W) */ +#define FDO_FDSEL 0x03 /* floppy device select */ +#define FDO_FRST 0x04 /* floppy controller reset */ +#define FDO_FDMAEN 0x08 /* enable floppy DMA and Interrupt */ +#define FDO_MOEN0 0x10 /* motor enable drive 0 */ +#define FDO_MOEN1 0x20 /* motor enable drive 1 */ +#define FDO_MOEN2 0x30 /* motor enable drive 2 */ +#define FDO_MOEN3 0x40 /* motor enable drive 3 */ + +#define fdsts 4 /* NEC 765 Main Status Register (R) */ +#define fddata 5 /* NEC 765 Data Register (R/W) */ + +#define fdctl 7 /* Control Register (W) */ +#define FDC_500KBPS 0x00 /* 500KBPS MFM drive transfer rate */ +#define FDC_300KBPS 0x01 /* 300KBPS MFM drive transfer rate */ +#define FDC_250KBPS 0x02 /* 250KBPS MFM drive transfer rate */ +#define FDC_125KBPS 0x03 /* 125KBPS FM drive transfer rate */ + +#define fdin 7 /* Digital Input Register (R) */ +#define FDI_DCHG 0x80 /* diskette has been changed */ + + diff --git a/sys/dev/ic/i8237.h b/sys/dev/ic/i8237.h new file mode 100644 index 0000000..dc269f2 --- /dev/null +++ b/sys/dev/ic/i8237.h @@ -0,0 +1,9 @@ +/* + * Intel 8237 DMA Controller + */ + +#define DMA37MD_SINGLE 0x40 /* single pass mode */ +#define DMA37MD_CASCADE 0xc0 /* cascade mode */ +#define DMA37MD_WRITE 0x04 /* read the device, write memory operation */ +#define DMA37MD_READ 0x08 /* write the device, read memory operation */ + diff --git a/sys/dev/ic/nec765.h b/sys/dev/ic/nec765.h new file mode 100644 index 0000000..b84b46e --- /dev/null +++ b/sys/dev/ic/nec765.h @@ -0,0 +1,71 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)nec765.h 7.1 (Berkeley) 5/9/91 + */ + +/* + * Nec 765 floppy disc controller definitions + */ + +/* Main status register */ +#define NE7_DAB 0x01 /* Diskette drive A is seeking, thus busy */ +#define NE7_DBB 0x02 /* Diskette drive B is seeking, thus busy */ +#define NE7_CB 0x10 /* Diskette Controller Busy */ +#define NE7_NDM 0x20 /* Diskette Controller in Non Dma Mode */ +#define NE7_DIO 0x40 /* Diskette Controller Data register I/O */ +#define NE7_RQM 0x80 /* Diskette Controller ReQuest for Master */ + +/* Status register ST0 */ +#define NE7_ST0BITS "\020\010invld\007abnrml\006seek_cmplt\005drv_chck\004drive_rdy\003top_head" + +/* Status register ST1 */ +#define NE7_ST1BITS "\020\010end_of_cyl\006bad_crc\005data_overrun\003sec_not_fnd\002write_protect\001no_am" + +/* Status register ST2 */ +#define NE7_ST2BITS "\020\007ctrl_mrk\006bad_crc\005wrong_cyl\004scn_eq\003scn_not_fnd\002bad_cyl\001no_dam" + +/* Status register ST3 */ +#define NE7_ST3BITS "\020\010fault\007write_protect\006drdy\005tk0\004two_side\003side_sel\002" + +/* Commands */ +#define NE7CMD_SPECIFY 3 /* specify drive parameters - requires unit + parameters byte */ +#define NE7CMD_SENSED 4 /* sense drive - requires unit select byte */ +#define NE7CMD_WRITE 0xc5 /* write - requires eight additional bytes */ +#define NE7CMD_READ 0xe6 /* read - requires eight additional bytes */ +#define NE7CMD_FORMAT 0x4c /* format - requires five additional bytes */ +#define NE7CMD_RECAL 7 /* recalibrate drive - requires + unit select byte */ +#define NE7CMD_SENSEI 8 /* sense controller interrupt status */ +#define NE7CMD_SEEK 15 /* seek drive - requires unit select byte + and new cyl byte */ diff --git a/sys/dev/ic/ns16550.h b/sys/dev/ic/ns16550.h new file mode 100644 index 0000000..e20e9aa --- /dev/null +++ b/sys/dev/ic/ns16550.h @@ -0,0 +1,50 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)ns16550.h 7.1 (Berkeley) 5/9/91 + */ + +/* + * NS16550 UART registers + */ + +#define com_data 0 /* data register (R/W) */ +#define com_dlbl 0 /* divisor latch low (W) */ +#define com_dlbh 1 /* divisor latch high (W) */ +#define com_ier 1 /* interrupt enable (W) */ +#define com_iir 2 /* interrupt identification (R) */ +#define com_fifo 2 /* FIFO control (W) */ +#define com_lctl 3 /* line control register (R/W) */ +#define com_cfcr 3 /* line control register (R/W) */ +#define com_mcr 4 /* modem control register (R/W) */ +#define com_lsr 5 /* line status register (R/W) */ +#define com_msr 6 /* modem status register (R/W) */ diff --git a/sys/dev/sio/sio.c b/sys/dev/sio/sio.c new file mode 100644 index 0000000..38503fd --- /dev/null +++ b/sys/dev/sio/sio.c @@ -0,0 +1,1721 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)sio.c 7.5 (Berkeley) 5/16/91 + * + * 27 May 93 Bruce Evans From com-0.2 package, fast interrupt + * com port driver. + * 27 May 93 Guido van Rooij Ported in Chris Demetriou's BIDIR + * code, add multiport support. + * 27 May 93 Rodney W. Grimes I then renamed it to sio.c for putting + * into the patch kit. Added in sioselect + * from com.c. Added port 4 support. + */ +static char rcsid[] = "$Header: /usr/bill/working/sys/i386/isa/RCS/com.c,v 1.2 92/01/21 14:34:11 william Exp $"; + +#include "sio.h" +#if NSIO > 0 +/* + * COM driver, based on HP dca driver. + * Mostly rewritten to use pseudo-DMA. + * Works for National Semiconductor NS8250-NS16550AF UARTs. + */ +#include "param.h" +#include "systm.h" +#include "ioctl.h" +#include "tty.h" +#include "proc.h" +#include "user.h" +#include "conf.h" +#include "file.h" +#include "uio.h" +#include "kernel.h" +#include "syslog.h" + +#include "i386/isa/isa.h" +#include "i386/isa/isa_device.h" +#include "i386/isa/comreg.h" +#include "i386/isa/ic/ns16550.h" + +#undef CRTS_IFLOW +#define CRTS_IFLOW CRTSCTS /* XXX, CCTS_OFLOW = CRTSCTS already */ +#define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ +#define RB_I_HIGH_WATER (RBSZ - 2 * RS_IBUFSIZE) +#define RB_I_LOW_WATER ((RBSZ - 2 * RS_IBUFSIZE) * 7 / 8) +#define RS_IBUFSIZE 256 +#define TS_RTSBLOCK TS_TBLOCK /* XXX */ +#define TTY_BI TTY_FE /* XXX */ +#define TTY_OE TTY_PE /* XXX */ +#ifndef COM_BIDIR +#define UNIT(x) (minor(x)) /* XXX */ +#else /* COM_BIDIR */ +#define COM_UNITMASK 0x7f +#define COM_CALLOUTMASK 0x80 + +#define UNIT(x) (minor(x) & COM_UNITMASK) +#define CALLOUT(x) (minor(x) & COM_CALLOUTMASK) +#endif /* COM_BIDIR */ + +#ifdef COM_MULTIPORT +/* checks in flags for multiport and which is multiport "master chip" + * for a given card + */ +#define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) +#define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) +#endif /* COM_MULTIPORT */ + +#define com_scr 7 /* scratch register for 16450-16550 (R/W) */ +#define schedsoftcom() (ipending |= 1 << 4) /* XXX */ + +/* + * Input buffer watermarks. + * The external device is asked to stop sending when the buffer exactly reaches + * high water, or when the high level requests it. + * The high level is notified immediately (rather than at a later clock tick) + * when this watermark is reached. + * The buffer size is chosen so the watermark should almost never be reached. + * The low watermark is invisibly 0 since the buffer is always emptied all at + * once. + */ +#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4) + +/* + * com state bits. + * (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher + * than the other bits so that they can be tested as a group without masking + * off the low bits. + * + * The following com and tty flags correspond closely: + * TS_BUSY = CS_BUSY (maintained by comstart() and comflush()) + * CS_TTGO = ~TS_TTSTOP (maintained by comstart() and siostop()) + * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) + * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) + * TS_FLUSH is not used. + * Bug: I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. + */ +#define CS_BUSY 0x80 /* output in progress */ +#define CS_TTGO 0x40 /* output not stopped by XOFF */ +#define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ +#define CS_CHECKMSR 1 /* check of MSR scheduled */ +#define CS_CTS_OFLOW 2 /* use CTS output flow control */ +#define CS_ODONE 4 /* output completed */ +#define CS_RTS_IFLOW 8 /* use RTS input flow control */ + +static char *error_desc[] = { +#define CE_OVERRUN 0 + "silo overflow", +#define CE_INTERRUPT_BUF_OVERFLOW 1 + "interrupt-level buffer overflow", +#define CE_TTY_BUF_OVERFLOW 2 + "tty-level buffer overflow", +}; + +#define CE_NTYPES 3 +#define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum]) + +/* types. XXX - should be elsewhere */ +typedef u_int Port_t; /* hardware port */ +typedef int Bool_t; /* promoted boolean */ +typedef u_char bool_t; /* boolean */ + +/* com device structure */ +struct com_s { + u_char state; /* miscellaneous flag bits */ + u_char cfcr_image; /* copy of value written to CFCR */ + bool_t hasfifo; /* nonzero for 16550 UARTs */ + u_char mcr_image; /* copy of value written to MCR */ + bool_t softDCD; /* nonzero for faked carrier detect */ +#ifdef COM_BIDIR + bool_t bidir; /* is this unit bidirectional? */ + bool_t active; /* is the port active _at all_? */ + bool_t active_in; /* is the incoming port in use? */ + bool_t active_out; /* is the outgoing port in use? */ +#endif /* COM_BIDIR */ +#ifdef COM_MULTIPORT + bool_t multiport; /* is this unit part of a multiport device? */ +#endif /* COM_MULTIPORT */ + + /* + * The high level of the driver never reads status registers directly + * because there would be too many side effects to handle conveniently. + * Instead, it reads copies of the registers stored here by the + * interrupt handler. + */ + u_char last_modem_status; /* last MSR read by intr handler */ + u_char prev_modem_status; /* last MSR handled by high level */ + + u_char *ibuf; /* start of input buffer */ + u_char *ibufend; /* end of input buffer */ + u_char *ihighwater; /* threshold in input buffer */ + u_char *iptr; /* next free spot in input buffer */ + + u_char *obufend; /* end of output buffer */ + int ocount; /* original count for current output */ + u_char *optr; /* next char to output */ + + Port_t data_port; /* i/o ports */ + Port_t int_id_port; + Port_t iobase; + Port_t modem_ctl_port; + Port_t line_status_port; + Port_t modem_status_port; + + struct tty *tp; /* cross reference */ + + u_long bytes_in; /* statistics */ + u_long bytes_out; + u_int delta_error_counts[CE_NTYPES]; + u_int error_counts[CE_NTYPES]; + + /* + * Ping-pong input buffers. The extra factor of 2 in the sizes is + * to allow for an error byte for each input byte. + */ +#define CE_INPUT_OFFSET RS_IBUFSIZE + u_char ibuf1[2 * RS_IBUFSIZE]; + u_char ibuf2[2 * RS_IBUFSIZE]; +}; + + +/* + * These functions in the com module ought to be declared (with a prototype) + * in a com-driver system header. The void ones may need to be int to match + * ancient devswitch declarations, but they don't actually return anything. + */ +#define Dev_t int /* promoted dev_t */ +struct consdev; + +int sioclose __P((Dev_t dev, int fflag, int devtype, + struct proc *p)); +void siointr __P((int unit)); +#ifdef COM_MULTIPORT +bool_t comintr1 __P((struct com_s *com)); +#endif /* COM_MULTIPORT */ +int sioioctl __P((Dev_t dev, int cmd, caddr_t data, + int fflag, struct proc *p)); +int siocngetc __P((Dev_t dev)); +void siocninit __P((struct consdev *cp)); +void siocnprobe __P((struct consdev *cp)); +void siocnputc __P((Dev_t dev, int c)); +int sioopen __P((Dev_t dev, int oflags, int devtype, + struct proc *p)); +/* + * sioopen gets compared to the d_open entry in struct cdevsw. d_open and + * other functions are declared in <sys/conf.h> with short types like dev_t + * in the prototype. Such declarations are broken because they vary with + * __P (significantly in theory - the compiler is allowed to push a short + * arg if it has seen the prototype; insignificantly in practice - gcc + * doesn't push short args and it would be slower on 386's to do so). + * + * Also, most of the device switch functions are still declared old-style + * so they take a Dev_t arg and shorten it to a dev_t. It would be simpler + * and faster if dev_t's were always promoted (to ints or whatever) as + * early as possible. + * + * Until <sys/conf.h> is fixed, we cast sioopen to the following `wrong' type + * when comparing it to the d_open entry just to avoid compiler warnings. + */ +typedef int (*bogus_open_t) __P((dev_t dev, int oflags, int devtype, + struct proc *p)); +int sioread __P((Dev_t dev, struct uio *uio, int ioflag)); +void siostop __P((struct tty *tp, int rw)); +int siowrite __P((Dev_t dev, struct uio *uio, int ioflag)); +void softsio0 __P((void)); +void softsio1 __P((void)); +void softsio2 __P((void)); +void softsio3 __P((void)); +void softsio4 __P((void)); +void softsio5 __P((void)); +void softsio6 __P((void)); +void softsio7 __P((void)); +void softsio8 __P((void)); + +static int sioattach __P((struct isa_device *dev)); +static void comflush __P((struct com_s *com)); +static void comhardclose __P((struct com_s *com)); +static void cominit __P((int unit, int rate)); +static int commctl __P((struct com_s *com, int bits, int how)); +static int comparam __P((struct tty *tp, struct termios *t)); +static int sioprobe __P((struct isa_device *dev)); +static void compoll __P((void)); +static int comstart __P((struct tty *tp)); +static void comwakeup __P((void)); + +/* table and macro for fast conversion from a unit number to its com struct */ +static struct com_s *p_com_addr[NSIO]; +#define com_addr(unit) (p_com_addr[unit]) + +static struct com_s com_structs[NSIO]; + +struct isa_driver siodriver = { + sioprobe, sioattach, "sio" +}; + +#ifdef COMCONSOLE +static int comconsole = COMCONSOLE; +#else +static int comconsole = -1; +#endif +static bool_t comconsinit; +static speed_t comdefaultrate = TTYDEF_SPEED; +static u_int com_events; /* input chars + weighted output completions */ +static int commajor; +struct tty sio_tty[NSIO]; +extern struct tty *constty; +extern u_int ipending; /* XXX */ +extern int tk_nin; /* XXX */ +extern int tk_rawcc; /* XXX */ + +#ifdef KGDB +#include "machine/remote-sl.h" + +extern int kgdb_dev; +extern int kgdb_rate; +extern int kgdb_debug_init; +#endif + +static struct speedtab comspeedtab[] = { + 0, 0, + 50, COMBRD(50), + 75, COMBRD(75), + 110, COMBRD(110), + 134, COMBRD(134), + 150, COMBRD(150), + 200, COMBRD(200), + 300, COMBRD(300), + 600, COMBRD(600), + 1200, COMBRD(1200), + 1800, COMBRD(1800), + 2400, COMBRD(2400), + 4800, COMBRD(4800), + 9600, COMBRD(9600), + 19200, COMBRD(19200), + 38400, COMBRD(38400), + 57600, COMBRD(57600), + 115200, COMBRD(115200), + -1, -1 +}; + +/* XXX - configure this list */ +static Port_t likely_com_ports[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, }; + +static int +sioprobe(dev) + struct isa_device *dev; +{ + static bool_t already_init; + Port_t *com_ptr; + Port_t iobase; + int result; + + if (!already_init) { + /* + * Turn off MCR_IENABLE for all likely serial ports. An unused + * port with its MCR_IENABLE gate open will inhibit interrupts + * from any used port that shares the interrupt vector. + */ + for (com_ptr = likely_com_ports; + com_ptr < &likely_com_ports[sizeof likely_com_ports + / sizeof likely_com_ports[0]]; + ++com_ptr) + outb(*com_ptr + com_mcr, 0); + already_init = TRUE; + } + iobase = dev->id_iobase; + result = 1; + + /* + * We don't want to get actual interrupts, just masked ones. + * Interrupts from this line should already be masked in the ICU, + * but mask them in the processor as well in case there are some + * (misconfigured) shared interrupts. + */ + disable_intr(); + + /* + * Enable output interrupts (only) and check the following: + * o the CFCR, IER and MCR in UART hold the values written to them + * (the values happen to be all distinct - this is good for + * avoiding false positive tests from bus echoes). + * o an output interrupt is generated and its vector is correct. + * o the interrupt goes away when the IIR in the UART is read. + */ + outb(iobase + com_cfcr, CFCR_8BITS); /* ensure IER is addressed */ + outb(iobase + com_mcr, MCR_IENABLE); /* open gate early */ + outb(iobase + com_ier, 0); /* ensure edge on next intr */ + outb(iobase + com_ier, IER_ETXRDY); /* generate interrupt */ + if ( inb(iobase + com_cfcr) != CFCR_8BITS + || inb(iobase + com_ier) != IER_ETXRDY + || inb(iobase + com_mcr) != MCR_IENABLE + || !isa_irq_pending(dev) + || (inb(iobase + com_iir) & IIR_IMASK) != IIR_TXRDY + || isa_irq_pending(dev) + || (inb(iobase + com_iir) & IIR_IMASK) != IIR_NOPEND) + result = 0; + + /* + * Turn off all device interrupts and check that they go off properly. + * Leave MCR_IENABLE set. It gates the OUT2 output of the UART to + * the ICU input. Closing the gate would give a floating ICU input + * (unless there is another device driving at) and spurious interrupts. + * (On the system that this was first tested on, the input floats high + * and gives a (masked) interrupt as soon as the gate is closed.) + */ + outb(iobase + com_ier, 0); + outb(iobase + com_mcr, MCR_IENABLE); /* dummy to avoid bus echo */ + if ( inb(iobase + com_ier) != 0 + || isa_irq_pending(dev) + || (inb(iobase + com_iir) & IIR_IMASK) != IIR_NOPEND) + result = 0; + + enable_intr(); + + return (result); +} + +static int /* XXX - should be void */ +sioattach(isdp) + struct isa_device *isdp; +{ + struct com_s *com; + static bool_t comwakeup_started = FALSE; + Port_t iobase; + int s; + u_char scr; + u_char scr1; + u_char scr2; + int unit; + + iobase = isdp->id_iobase; + unit = isdp->id_unit; + if (unit == comconsole) + DELAY(1000); /* XXX */ + s = spltty(); + + /* + * sioprobe() has initialized the device registers as follows: + * o cfcr = CFCR_8BITS. + * It is most important that CFCR_DLAB is off, so that the + * data port is not hidden when we enable interrupts. + * o ier = 0. + * Interrupts are only enabled when the line is open. + * o mcr = MCR_IENABLE. + * Keeping MCR_DTR and MCR_RTS off might stop the external + * device from sending before we are ready. + */ + + com = &com_structs[unit]; + com->cfcr_image = CFCR_8BITS; + com->mcr_image = MCR_IENABLE; +#if 0 + com->softDCD = TRUE; +#endif + com->iptr = com->ibuf = com->ibuf1; + com->ibufend = com->ibuf1 + RS_IBUFSIZE; + com->ihighwater = com->ibuf1 + RS_IHIGHWATER; + com->iobase = iobase; + com->data_port = iobase + com_data; + com->int_id_port = iobase + com_iir; + com->modem_ctl_port = iobase + com_mcr; + com->line_status_port = iobase + com_lsr; + com->modem_status_port = iobase + com_msr; + com->tp = &sio_tty[unit]; +#ifdef COM_BIDIR + /* + * if bidirectional ports possible, clear the bidir port info; + */ + com->bidir = FALSE; + com->active = FALSE; + com->active_in = com->active_out = FALSE; +#endif /* COM_BIDIR */ + + /* attempt to determine UART type */ + scr = inb(iobase + com_scr); + outb(iobase + com_scr, 0xa5); + scr1 = inb(iobase + com_scr); + outb(iobase + com_scr, 0x5a); + scr2 = inb(iobase + com_scr); + outb(iobase + com_scr, scr); + if (scr1 != 0xa5 || scr2 != 0x5a) + printf(" <8250>"); + else { + outb(iobase + com_fifo, FIFO_ENABLE | FIFO_TRIGGER_14); + DELAY(100); + switch (inb(iobase + com_iir) & IIR_FIFO_MASK) { + case FIFO_TRIGGER_1: + printf(" <16450>"); + break; + case FIFO_TRIGGER_4: + printf(" <16450?>"); + break; + case FIFO_TRIGGER_8: + printf(" <16550?>"); + break; + case FIFO_TRIGGER_14: + com->hasfifo = TRUE; + printf(" <16550A>"); + break; + } + outb(iobase + com_fifo, 0); + } +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(isdp)) { + struct isa_device *masterdev; + + com->multiport = TRUE; + printf(" (multiport)"); + + /* set the master's common-interrupt-enable reg., + * as appropriate. YYY See your manual + */ + /* enable only common interrupt for port */ + outb(iobase + com_mcr, 0); + + masterdev = find_isadev(isa_devtab_tty, &siodriver, + COM_MPMASTER(isdp)); + outb(masterdev->id_iobase+com_scr, 0x80); + } + else + com->multiport = FALSE; +#endif /* COM_MULTIPORT */ + +#ifdef KGDB + if (kgdb_dev == makedev(commajor, unit)) { + if (comconsole == unit) + kgdb_dev = -1; /* can't debug over console port */ + else { + cominit(unit, kgdb_rate); + if (kgdb_debug_init) { + /* + * Print prefix of device name, + * let kgdb_connect print the rest. + */ + printf("com%d: ", unit); + kgdb_connect(1); + } + else + printf("com%d: kgdb enabled\n", unit); + } + } +#endif + + /* + * Need to reset baud rate, etc. of next print so reset comconsinit. + * Also make sure console is always "hardwired" + */ + if (unit == comconsole) { + comconsinit = FALSE; + com->softDCD = TRUE; + } + + com_addr(unit) = com; + + splx(s); + + if (!comwakeup_started) { + comwakeup(); + comwakeup_started = TRUE; + } + + return (1); +} + +/* ARGSUSED */ +int +sioopen(dev, flag, mode, p) + dev_t dev; + int flag; + int mode; + struct proc *p; +{ + struct com_s *com; + int error = 0; + Port_t iobase; + int s; + struct tty *tp; + int unit = UNIT(dev); +#ifdef COM_BIDIR + bool_t callout = CALLOUT(dev); +#endif /* COM_BIDIR */ + if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) + return (ENXIO); +#ifdef COM_BIDIR + /* if it's a callout device, and bidir not possible on that dev, die */ + if (callout && !(com->bidir)) + return (ENXIO); +#endif /* COM_BIDIR */ + + tp = com->tp; + s = spltty(); + +#ifdef COM_BIDIR + +bidir_open_top: + /* if it's bidirectional, we've gotta deal with it... */ + if (com->bidir) { + if (callout) { + if (com->active_in) { + /* it's busy. die */ + splx(s); + return (EBUSY); + } else { + /* it's ours. lock it down, and set it up */ + com->active_out = TRUE; + com->softDCD = TRUE; + } + } else { + if (com->active_out) { + /* it's busy, outgoing. wait, if possible */ + if (flag & O_NONBLOCK) { + /* can't wait; bail */ + splx(s); + return (EBUSY); + } else { + /* wait for it... */ + error = tsleep(&com->active_out, + TTIPRI|PCATCH, + "comoth", + 0); + /* if there was an error, take off. */ + if (error != 0) { + splx(s); + return (error); + } + /* else take it from the top */ + goto bidir_open_top; + } + } else if (com->last_modem_status & MSR_DCD) { + /* there's a carrier on the line; we win */ + com->active_in = TRUE; + com->softDCD = FALSE; + } else { + /* there is no carrier on the line */ + if (flag & O_NONBLOCK) { + /* can't wait; let it open */ + com->active_in = TRUE; + com->softDCD = FALSE; + } else { + /* put DTR & RTS up */ + /* NOTE: cgd'sdriver used the ier register + * to enable/disable interrupts. This one + * uses both ier and IENABLE in the mcr. + */ + (void) commctl(com, MCR_DTR | MCR_RTS, DMSET); + outb(com->iobase + com_ier, IER_EMSC); + /* wait for it... */ + error = tsleep(&com->active_in, + TTIPRI|PCATCH, + "comdcd", + 0); + + /* if not active, turn DTR & RTS off */ + if (!com->active) + (void) commctl(com, MCR_DTR | MCR_RTS, DMBIC); + + /* if there was an error, take off. */ + if (error != 0) { + splx(s); + return (error); + } + /* else take it from the top */ + goto bidir_open_top; + } + } + } + } + + com->active = TRUE; +#endif /* COM_BIDIR */ + + tp->t_oproc = comstart; + tp->t_param = comparam; + tp->t_dev = dev; + if (!(tp->t_state & TS_ISOPEN)) { + tp->t_state |= TS_WOPEN; + ttychars(tp); + if (tp->t_ispeed == 0) { + /* + * We no longer use the flags from <sys/ttydefaults.h> + * since those are only relevant for logins. It's + * important to have echo off initially so that the + * line doesn't start blathering before the echo flag + * can be turned off. It's useful to have clocal on + * initially so that "stty changed-defaults </dev/comx" + * doesn't hang waiting for carrier. + */ + tp->t_iflag = 0; + tp->t_oflag = 0; + tp->t_cflag = CREAD | CS8 | CLOCAL; + tp->t_lflag = 0; + tp->t_ispeed = tp->t_ospeed = comdefaultrate; + } + (void) commctl(com, MCR_DTR | MCR_RTS, DMSET); + error = comparam(tp, &tp->t_termios); + if (error != 0) + goto out; + ttsetwater(tp); + iobase = com->iobase; + disable_intr(); + if (com->hasfifo) + /* (re)enable and drain FIFO */ + outb(iobase + com_fifo, FIFO_ENABLE | FIFO_TRIGGER_14 + | FIFO_RCV_RST | FIFO_XMT_RST); + (void) inb(com->line_status_port); + (void) inb(com->data_port); + com->last_modem_status = + com->prev_modem_status = inb(com->modem_status_port); + outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS + | IER_EMSC); + enable_intr(); + if (com->softDCD || com->prev_modem_status & MSR_DCD) + tp->t_state |= TS_CARR_ON; + } + else if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { + splx(s); + return (EBUSY); + } + while (!(flag & O_NONBLOCK) && !(tp->t_cflag & CLOCAL) +#ifdef COM_BIDIR + /* We went through a lot of trouble to open it, + * but it's certain we have a carrier now, so + * don't spend any time on it now. + */ + && !(com->bidir) +#endif /* COM_BIDIR */ + && !(tp->t_state & TS_CARR_ON)) { + tp->t_state |= TS_WOPEN; + error = ttysleep(tp, (caddr_t)&tp->t_raw, TTIPRI | PCATCH, + ttopen, 0); + if (error != 0) + break; + } +out: + splx(s); + if (error == 0) + error = (*linesw[tp->t_line].l_open)(dev, tp); + +#ifdef COM_BIDIR + /* wakeup sleepers */ + wakeup((caddr_t) &com->active_in); +#endif /* COM_BIDIR */ + + /* + * XXX - the next step was once not done, so interrupts, DTR and RTS + * remainded hot if the process was killed while it was sleeping + * waiting for carrier. Now there is the opposite problem. If several + * processes are sleeping waiting for carrier on the same line and one + * is killed, interrupts are turned off so the other processes will + * never see the carrier rise. + */ + if (error != 0 && !(tp->t_state & TS_ISOPEN)) +{ + comhardclose(com); +} + tp->t_state &= ~TS_WOPEN; + + return (error); +} + +/*ARGSUSED*/ +int +sioclose(dev, flag, mode, p) + dev_t dev; + int flag; + int mode; + struct proc *p; +{ + struct com_s *com; + struct tty *tp; + + com = com_addr(UNIT(dev)); + tp = com->tp; + (*linesw[tp->t_line].l_close)(tp, flag); + comhardclose(com); + ttyclose(tp); + return (0); +} + +void +comhardclose(com) + struct com_s *com; +{ + Port_t iobase; + int s; + struct tty *tp; + + s = spltty(); + iobase = com->iobase; + outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); +#ifdef KGDB + /* do not disable interrupts if debugging */ + if (kgdb_dev != makedev(commajor, com - &com_structs[0])) +#endif + outb(iobase + com_ier, 0); + tp = com->tp; + if (tp->t_cflag & HUPCL || tp->t_state & TS_WOPEN + || !(tp->t_state & TS_ISOPEN)) + (void) commctl(com, 0, DMSET); +#ifdef COM_BIDIR + com->active = com->active_in = com->active_out = FALSE; + com->softDCD = FALSE; + + /* wakeup sleepers who are waiting for out to finish */ + wakeup((caddr_t) &com->active_out); +#endif /* COM_BIDIR */ + + splx(s); +} + +int +sioread(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + struct tty *tp = com_addr(UNIT(dev))->tp; + + return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); +} + +int +siowrite(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + int unit = UNIT(dev); + struct tty *tp = com_addr(unit)->tp; + + /* + * (XXX) We disallow virtual consoles if the physical console is + * a serial port. This is in case there is a display attached that + * is not the console. In that situation we don't need/want the X + * server taking over the console. + */ + if (constty && unit == comconsole) + constty = NULL; + return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); +} + +void +siointr(unit) + int unit; +{ + struct com_s *com; +#ifndef COM_MULTIPORT + u_char line_status; + u_char modem_status; + u_char *ioptr; + u_char recv_data; + + com = com_addr(unit); +#else /* COM_MULTIPORT */ + int i; + bool_t donesomething; + + do { + donesomething = FALSE; + for(i=0;i<NSIO;i++) { + com=com_addr(i); + if(com != NULL) { + /* XXX When siointr is called + * to start output, maybe + * it should be changed to a + * call to comintr1. Doesn't + * seem a good idea: interrupts + * are disabled all the time. + */ +enable_intr(); +disable_intr(); + donesomething = comintr1(com); + } + } + } while (donesomething); + return; +} + +bool_t +comintr1(struct com_s *com) +{ + u_char line_status; + u_char modem_status; + u_char *ioptr; + u_char recv_data; + bool_t donesomething; + + donesomething = FALSE; +#endif /* COM_MULTIPORT */ + + while (TRUE) { + line_status = inb(com->line_status_port); + + /* input event? (check first to help avoid overruns) */ + while (line_status & LSR_RCV_MASK) { + /* break/unnattached error bits or real input? */ +#ifdef COM_MULTIPORT + donesomething = TRUE; +#endif /* COM_MULTIPORT */ + if (!(line_status & LSR_RXRDY)) + recv_data = 0; + else + recv_data = inb(com->data_port); + ++com->bytes_in; +#ifdef KGDB + /* trap into kgdb? (XXX - needs testing and optim) */ + if (recv_data == FRAME_END + && !(com->tp->t_state & TS_ISOPEN) + && kgdb_dev == makedev(commajor, unit)) { + kgdb_connect(0); + continue; + } +#endif /* KGDB */ + ioptr = com->iptr; + if (ioptr >= com->ibufend) + CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); + else { + ++com_events; + ioptr[0] = recv_data; + ioptr[CE_INPUT_OFFSET] = line_status; + com->iptr = ++ioptr; + if (ioptr == com->ihighwater + && com->state & CS_RTS_IFLOW) + outb(com->modem_ctl_port, + com->mcr_image &= ~MCR_RTS); + } + + /* + * "& 0x7F" is to avoid the gcc-1.40 generating a slow + * jump from the top of the loop to here + */ + line_status = inb(com->line_status_port) & 0x7F; + } + + /* modem status change? (always check before doing output) */ + modem_status = inb(com->modem_status_port); + if (modem_status != com->last_modem_status) { + /* + * Schedule high level to handle DCD changes. Note + * that we don't use the delta bits anywhere. Some + * UARTs mess them up, and it's easy to remember the + * previous bits and calculate the delta. + */ +#ifdef COM_MULTIPORT + donesomething = TRUE; +#endif /* COM_MULTIPORT */ + com->last_modem_status = modem_status; + if (!(com->state & CS_CHECKMSR)) { + com_events += LOTS_OF_EVENTS; + com->state |= CS_CHECKMSR; + schedsoftcom(); + } + + /* handle CTS change immediately for crisp flow ctl */ + if (com->state & CS_CTS_OFLOW) { + if (modem_status & MSR_CTS) + com->state |= CS_ODEVREADY; + else + com->state &= ~CS_ODEVREADY; + } + } + + /* output queued and everything ready? */ + if (line_status & LSR_TXRDY + && com->state >= (CS_ODEVREADY | CS_BUSY | CS_TTGO)) { +#ifdef COM_MULTIPORT + donesomething = TRUE; +#endif /* COM_MULTIPORT */ + ioptr = com->optr; + outb(com->data_port, *ioptr); + ++com->bytes_out; + com->optr = ++ioptr; + if (ioptr >= com->obufend) { + /* output just completed */ + com_events += LOTS_OF_EVENTS; + com->state ^= (CS_ODONE | CS_BUSY); + schedsoftcom(); /* handle at high level ASAP */ + } + } + + /* finished? */ + if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) +#ifdef COM_MULTIPORT + return (donesomething); +#else + return; +#endif /* COM_MULTIPORT */ + } +} + +int +sioioctl(dev, cmd, data, flag, p) + dev_t dev; + int cmd; + caddr_t data; + int flag; + struct proc *p; +{ + struct com_s *com; + int error; + Port_t iobase; + int s; + struct tty *tp; + + com = com_addr(UNIT(dev)); + tp = com->tp; + error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag); + if (error >= 0) + return (error); + error = ttioctl(tp, cmd, data, flag); + if (error >= 0) + return (error); + + iobase = com->iobase; + s = spltty(); + switch (cmd) { + case TIOCSBRK: + outb(iobase + com_cfcr, com->cfcr_image |= CFCR_SBREAK); + break; + case TIOCCBRK: + outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); + break; + case TIOCSDTR: + (void) commctl(com, MCR_DTR | MCR_RTS, DMBIS); + break; + case TIOCCDTR: + (void) commctl(com, MCR_DTR | MCR_RTS, DMBIC); + break; + case TIOCMSET: + (void) commctl(com, *(int *)data, DMSET); + break; + case TIOCMBIS: + (void) commctl(com, *(int *)data, DMBIS); + break; + case TIOCMBIC: + (void) commctl(com, *(int *)data, DMBIC); + break; + case TIOCMGET: + *(int *)data = commctl(com, 0, DMGET); + break; +#ifdef COM_BIDIR + case TIOCMSBIDIR: + /* must be root to set bidir. capability */ + if (p->p_ucred->cr_uid != 0) + return(EPERM); + + /* if it's the console, can't do it */ + if (UNIT(dev) == comconsole) + return(ENOTTY); + + /* can't do the next, for obvious reasons... + * but there are problems to be looked at... + */ + + /* if the port is active, don't do it */ + /* if (com->active) + return(EBUSY); */ + + com->bidir = *(int *)data; + break; + case TIOCMGBIDIR: + *(int *)data = com->bidir; + break; +#endif /* COM_BIDIR */ + default: + splx(s); + return (ENOTTY); + } + splx(s); + return (0); +} + +/* cancel pending output */ +static void +comflush(com) + struct com_s *com; +{ + struct ringb *rbp; + + disable_intr(); + if (com->state & CS_ODONE) + com_events -= LOTS_OF_EVENTS; + com->state &= ~(CS_ODONE | CS_BUSY); + enable_intr(); + rbp = &com->tp->t_out; + rbp->rb_hd += com->ocount; + rbp->rb_hd = RB_ROLLOVER(rbp, rbp->rb_hd); + com->ocount = 0; + com->tp->t_state &= ~TS_BUSY; +} + +static void +compoll() +{ + static bool_t awake = FALSE; + struct com_s *com; + int s; + int unit; + + if (com_events == 0) + return; + disable_intr(); + if (awake) { + enable_intr(); + return; + } + awake = TRUE; + enable_intr(); + s = spltty(); +repeat: + for (unit = 0; unit < NSIO; ++unit) { + u_char *buf; + u_char *ibuf; + int incc; + struct tty *tp; + + com = com_addr(unit); + if (com == NULL) + continue; + tp = com->tp; + + /* switch the role of the low-level input buffers */ + if (com->iptr == (ibuf = com->ibuf)) + incc = 0; + else { + buf = ibuf; + disable_intr(); + incc = com->iptr - buf; + com_events -= incc; + if (ibuf == com->ibuf1) + ibuf = com->ibuf2; + else + ibuf = com->ibuf1; + com->ibufend = ibuf + RS_IBUFSIZE; + com->ihighwater = ibuf + RS_IHIGHWATER; + com->iptr = ibuf; + + /* + * There is now room for another low-level buffer full + * of input, so enable RTS if it is now disabled and + * there is room in the high-level buffer. + */ + if (!(com->mcr_image & MCR_RTS) + && !(tp->t_state & TS_RTSBLOCK)) + outb(com->modem_ctl_port, + com->mcr_image |= MCR_RTS); + enable_intr(); + com->ibuf = ibuf; + } + + if (com->state & CS_CHECKMSR) { + u_char delta_modem_status; + + disable_intr(); + delta_modem_status = com->last_modem_status + ^ com->prev_modem_status; + com->prev_modem_status = com->last_modem_status; + com_events -= LOTS_OF_EVENTS; + com->state &= ~CS_CHECKMSR; + enable_intr(); + if (delta_modem_status & MSR_DCD && + unit != comconsole) { +#ifdef COM_BIDIR + if (com->prev_modem_status & MSR_DCD) { + (*linesw[tp->t_line].l_modem)(tp, 1); + com->softDCD = FALSE; + wakeup((caddr_t) &com->active_in); + } +#else + if (com->prev_modem_status & MSR_DCD) + (*linesw[tp->t_line].l_modem)(tp, 1); +#endif /* COM_BIDIR */ + else if ((*linesw[tp->t_line].l_modem)(tp, 0) + == 0) { + disable_intr(); + outb(com->modem_ctl_port, + com->mcr_image + &= ~(MCR_DTR | MCR_RTS)); + enable_intr(); + } + } + } + + /* XXX */ + if (TRUE) { + u_int delta; + u_int delta_error_counts[CE_NTYPES]; + int errnum; + u_long total; + + disable_intr(); + bcopy(com->delta_error_counts, delta_error_counts, + sizeof delta_error_counts); + bzero(com->delta_error_counts, + sizeof delta_error_counts); + enable_intr(); + for (errnum = 0; errnum < CE_NTYPES; ++errnum) { + delta = delta_error_counts[errnum]; + if (delta != 0) { + total = + com->error_counts[errnum] += delta; + log(LOG_WARNING, + "com%d: %u more %s%s (total %lu)\n", + unit, delta, error_desc[errnum], + delta == 1 ? "" : "s", total); + } + } + } + if (com->state & CS_ODONE) { + comflush(com); + /* XXX - why isn't the table used for t_line == 0? */ + if (tp->t_line != 0) + (*linesw[tp->t_line].l_start)(tp); + else + comstart(tp); + } + if (incc <= 0 || !(tp->t_state & TS_ISOPEN)) + continue; + if (com->state & CS_RTS_IFLOW + && RB_LEN(&tp->t_raw) + incc >= RB_I_HIGH_WATER + && !(tp->t_state & TS_RTSBLOCK) + /* + * XXX - need RTS flow control for all line disciplines. + * Only have it in standard one now. + */ + && linesw[tp->t_line].l_rint == ttyinput) { + tp->t_state |= TS_RTSBLOCK; + ttstart(tp); + } + /* + * Avoid the grotesquely inefficient lineswitch routine + * (ttyinput) in "raw" mode. It usually takes about 450 + * instructions (that's without canonical processing or echo!). + * slinput is reasonably fast (usually 40 instructions plus + * call overhead). + */ + if (!(tp->t_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP + | IXOFF | IXON)) + && !(tp->t_lflag & (ECHO | ECHONL | ICANON | IEXTEN | ISIG + | PENDIN)) + && !(tp->t_state & (TS_CNTTB | TS_LNCH)) + && linesw[tp->t_line].l_rint == ttyinput) { + tk_nin += incc; + tk_rawcc += incc; + tp->t_rawcc += incc; + com->delta_error_counts[CE_TTY_BUF_OVERFLOW] + += incc - rb_write(&tp->t_raw, (char *) buf, + incc); + ttwakeup(tp); + if (tp->t_state & TS_TTSTOP + && (tp->t_iflag & IXANY + || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { + tp->t_state &= ~TS_TTSTOP; + tp->t_lflag &= ~FLUSHO; + ttstart(tp); + } + } + else { + do { + u_char line_status; + int recv_data; + + line_status = (u_char) buf[CE_INPUT_OFFSET]; + recv_data = (u_char) *buf++; + if (line_status + & (LSR_BI | LSR_FE | LSR_OE | LSR_PE)) { + if (line_status & LSR_BI) + recv_data |= TTY_BI; + if (line_status & LSR_FE) + recv_data |= TTY_FE; + if (line_status & LSR_OE) + recv_data |= TTY_OE; + if (line_status & LSR_PE) + recv_data |= TTY_PE; + } + (*linesw[tp->t_line].l_rint)(recv_data, tp); + } while (--incc > 0); + } + if (com_events == 0) + break; + } + if (com_events >= LOTS_OF_EVENTS) + goto repeat; + splx(s); + awake = FALSE; +} + +static int +comparam(tp, t) + struct tty *tp; + struct termios *t; +{ + u_int cfcr; + int cflag; + struct com_s *com; + int divisor; + int error; + Port_t iobase; + int s; + int unit; + + /* check requested parameters */ + divisor = ttspeedtab(t->c_ospeed, comspeedtab); + if (divisor < 0 || t->c_ispeed != 0 && t->c_ispeed != t->c_ospeed) + return (EINVAL); + + /* parameters are OK, convert them to the com struct and the device */ + unit = UNIT(tp->t_dev); + com = com_addr(unit); + iobase = com->iobase; + s = spltty(); + if (divisor == 0) { + (void) commctl(com, 0, DMSET); /* hang up line */ + splx(s); + return (0); + } + cflag = t->c_cflag; + switch (cflag & CSIZE) { + case CS5: + cfcr = CFCR_5BITS; + break; + case CS6: + cfcr = CFCR_6BITS; + break; + case CS7: + cfcr = CFCR_7BITS; + break; + default: + cfcr = CFCR_8BITS; + break; + } + if (cflag & PARENB) { + cfcr |= CFCR_PENAB; + if (!(cflag & PARODD)) + cfcr |= CFCR_PEVEN; + } + if (cflag & CSTOPB) + cfcr |= CFCR_STOPB; + + /* + * Some UARTs lock up if the divisor latch registers are selected + * while the UART is doing output (they refuse to transmit anything + * more until given a hard reset). Fix this by stopping filling + * the device buffers and waiting for them to drain. Reading the + * line status port outside of siointr() might lose some receiver + * error bits, but that is acceptable here. + */ + disable_intr(); + com->state &= ~CS_TTGO; + enable_intr(); + while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) { + error = ttysleep(tp, (caddr_t)&tp->t_raw, TTIPRI | PCATCH, + "comparam", 1); + if (error != 0 && error != EAGAIN) { + if (!(tp->t_state & TS_TTSTOP)) { + disable_intr(); + com->state |= CS_TTGO; + enable_intr(); + } + splx(s); + return (error); + } + } + + disable_intr(); /* very important while com_data is hidden */ + outb(iobase + com_cfcr, cfcr | CFCR_DLAB); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + outb(iobase + com_cfcr, com->cfcr_image = cfcr); + if (!(tp->t_state & TS_TTSTOP)) + com->state |= CS_TTGO; + if (cflag & CRTS_IFLOW) + com->state |= CS_RTS_IFLOW; /* XXX - secondary changes? */ + else + com->state &= ~CS_RTS_IFLOW; + + /* + * Set up state to handle output flow control. + * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? + * Now has 16+ msec latency, while CTS flow has 50- usec latency. + * Note that DCD flow control stupidly uses the same state flag + * (TS_TTSTOP) as XON/XOFF flow control. + */ + com->state &= ~CS_CTS_OFLOW; + com->state |= CS_ODEVREADY; + if (cflag & CCTS_OFLOW) { + com->state |= CS_CTS_OFLOW; + if (!(com->prev_modem_status & MSR_CTS)) + com->state &= ~CS_ODEVREADY; + } + + enable_intr(); + siointr(unit); /* recover from fiddling with CS_TTGO */ + splx(s); + return (0); +} + +static int /* XXX - should be void */ +comstart(tp) + struct tty *tp; +{ + struct com_s *com; + int s; + int unit; + + unit = UNIT(tp->t_dev); + com = com_addr(unit); + s = spltty(); + disable_intr(); + if (tp->t_state & TS_TTSTOP) + com->state &= ~CS_TTGO; + else + com->state |= CS_TTGO; + if (tp->t_state & TS_RTSBLOCK) { + if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) + outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); + } + else { + if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater) + outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); + } + enable_intr(); + if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) + goto out; + if (RB_LEN(&tp->t_out) <= tp->t_lowat) { + if (tp->t_state & TS_ASLEEP) { + tp->t_state &= ~TS_ASLEEP; + wakeup((caddr_t)&tp->t_out); + } + if (tp->t_wsel) { + selwakeup(tp->t_wsel, tp->t_state & TS_WCOLL); + tp->t_wsel = 0; + tp->t_state &= ~TS_WCOLL; + } + } + if (com->ocount != 0) { + disable_intr(); + siointr(unit); + enable_intr(); + } + else if (RB_LEN(&tp->t_out) != 0) { + tp->t_state |= TS_BUSY; + com->ocount = RB_CONTIGGET(&tp->t_out); + disable_intr(); + com->obufend = (com->optr = (u_char *) tp->t_out.rb_hd) + + com->ocount; + com->state |= CS_BUSY; + siointr(unit); /* fake interrupt to start output */ + enable_intr(); + } +out: + splx(s); + return (1); +} + +void +siostop(tp, rw) + struct tty *tp; + int rw; +{ + struct com_s *com; + + com = com_addr(UNIT(tp->t_dev)); + if (rw & FWRITE) + comflush(com); + disable_intr(); + if (tp->t_state & TS_TTSTOP) + com->state &= ~CS_TTGO; + else + com->state |= CS_TTGO; + enable_intr(); +} + +static int +commctl(com, bits, how) + struct com_s *com; + int bits; + int how; +{ + disable_intr(); + switch (how) { + case DMSET: +#ifdef COM_MULTIPORT + /* YYY maybe your card doesn't want IENABLE to be reset? */ + if(com->multiport) + outb(com->modem_ctl_port, + com->mcr_image = bits); + else +#endif /* COM_MULTIPORT */ + outb(com->modem_ctl_port, + com->mcr_image = bits | MCR_IENABLE); + break; + case DMBIS: + outb(com->modem_ctl_port, com->mcr_image |= bits); + break; + case DMBIC: +#ifdef COM_MULTIPORT + /* YYY maybe your card doesn't want IENABLE to be reset? */ + if(com->multiport) + outb(com->modem_ctl_port, + com->mcr_image &= ~(bits)); + else +#endif /* COM_MULTIPORT */ + outb(com->modem_ctl_port, + com->mcr_image &= ~(bits & ~MCR_IENABLE)); + break; + case DMGET: + bits = com->prev_modem_status; + break; + } + enable_intr(); + return (bits); +} + +static void +comwakeup() +{ + struct com_s *com; + int unit; + + timeout((timeout_func_t) comwakeup, (caddr_t) NULL, 1); + if (com_events != 0) + /* schedule compoll() to run when the cpl allows */ + schedsoftcom(); + + /* recover from lost output interrupts */ + for (unit = 0; unit < NSIO; ++unit) { + com = com_addr(unit); + if (com != NULL && com->state >= (CS_BUSY | CS_TTGO)) { + disable_intr(); + siointr(unit); + enable_intr(); + } + } +} + +void +softsio0() { compoll(); } + +void +softsio1() { compoll(); } + +void +softsio2() { compoll(); } + +void +softsio3() { compoll(); } + +void +softsio4() { compoll(); } + +void +softsio5() { compoll(); } + +void +softsio6() { compoll(); } + +void +softsio7() { compoll(); } + +void +softsio8() { compoll(); } + +/* + * Following are all routines needed for COM to act as console + * XXX - not tested in this version + * XXX - check that the corresponding serial interrupts are never enabled + */ +#include "i386/i386/cons.h" + +void +siocnprobe(cp) + struct consdev *cp; +{ + int unit; + + /* locate the major number */ + for (commajor = 0; commajor < nchrdev; commajor++) + if (cdevsw[commajor].d_open == (bogus_open_t) sioopen) + break; + + /* XXX: ick */ + unit = CONUNIT; + com_addr(unit) = &com_structs[unit]; + com_addr(unit)->iobase = CONADDR; + + /* make sure hardware exists? XXX */ + + /* initialize required fields */ + cp->cn_dev = makedev(commajor, unit); + cp->cn_tp = &sio_tty[unit]; +#ifdef COMCONSOLE + cp->cn_pri = CN_REMOTE; /* Force a serial port console */ +#else + cp->cn_pri = CN_NORMAL; +#endif +} + +void +siocninit(cp) + struct consdev *cp; +{ + int unit; + + unit = UNIT(cp->cn_dev); + cominit(unit, comdefaultrate); + comconsole = unit; + comconsinit = TRUE; +} + +static void +cominit(unit, rate) + int unit; + int rate; +{ + Port_t iobase; + int s; + + iobase = com_addr(unit)->iobase; + s = splhigh(); + outb(iobase + com_cfcr, CFCR_DLAB); + rate = ttspeedtab(comdefaultrate, comspeedtab); + outb(iobase + com_data, rate & 0xFF); + outb(iobase + com_ier, rate >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + + /* + * XXX - fishy to enable interrupts and then poll. + * It shouldn't be necessary to ready the iir. + */ + outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); + outb(iobase + com_fifo, + FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_TRIGGER_14); + (void) inb(iobase + com_iir); + splx(s); +} + +int +siocngetc(dev) + dev_t dev; +{ + int c; + Port_t iobase; + int s; + + iobase = com_addr(UNIT(dev))->iobase; + s = splhigh(); + while (!(inb(iobase + com_lsr) & LSR_RXRDY)) + ; + c = inb(iobase + com_data); + (void) inb(iobase + com_iir); + splx(s); + return (c); +} + +void +siocnputc(dev, c) + dev_t dev; + int c; +{ + Port_t iobase; + int s; + int timo; + + iobase = com_addr(UNIT(dev))->iobase; + s = splhigh(); +#ifdef KGDB + if (dev != kgdb_dev) +#endif + if (!comconsinit) { + (void) cominit(UNIT(dev), comdefaultrate); + comconsinit = TRUE; + } + /* wait for any pending transmission to finish */ + timo = 50000; + while (!(inb(iobase + com_lsr) & LSR_TXRDY) && --timo) + ; + outb(iobase + com_data, c); + /* wait for this transmission to complete */ + timo = 1500000; + while (!(inb(iobase + com_lsr) & LSR_TXRDY) && --timo) + ; + /* clear any interrupts generated by this transmission */ + (void) inb(iobase + com_iir); + splx(s); +} + +/* + * 10 Feb 93 Jordan K. Hubbard Added select code + * 27 May 93 Rodney W. Grimes Stole the select code from com.c.pl5 + */ + +int +sioselect(dev, rw, p) + dev_t dev; + int rw; + struct proc *p; +{ + register struct tty *tp = &sio_tty[UNIT(dev)]; + int nread; + int s = spltty(); + struct proc *selp; + + switch (rw) { + + case FREAD: + nread = ttnread(tp); + if (nread > 0 || + ((tp->t_cflag&CLOCAL) == 0 && (tp->t_state&TS_CARR_ON) == 0)) + goto win; + if (tp->t_rsel && (selp = pfind(tp->t_rsel)) && selp->p_wchan == (caddr_t)&selwait) + tp->t_state |= TS_RCOLL; + else + tp->t_rsel = p->p_pid; + break; + + case FWRITE: + if (RB_LEN(&tp->t_out) <= tp->t_lowat) + goto win; + if (tp->t_wsel && (selp = pfind(tp->t_wsel)) && selp->p_wchan == (caddr_t)&selwait) + tp->t_state |= TS_WCOLL; + else + tp->t_wsel = p->p_pid; + break; + } + splx(s); + return (0); + win: + splx(s); + return (1); +} + +#endif /* NSIO > 0 */ diff --git a/sys/dev/sio/sioreg.h b/sys/dev/sio/sioreg.h new file mode 100644 index 0000000..2a62683 --- /dev/null +++ b/sys/dev/sio/sioreg.h @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)comreg.h 7.2 (Berkeley) 5/9/91 + */ + + +/* 16 bit baud rate divisor (lower byte in dca_data, upper in dca_ier) */ +#define COMBRD(x) (1843200 / (16*(x))) + +/* interrupt enable register */ +#define IER_ERXRDY 0x1 +#define IER_ETXRDY 0x2 +#define IER_ERLS 0x4 +#define IER_EMSC 0x8 + +/* interrupt identification register */ +#define IIR_IMASK 0xf +#define IIR_RXTOUT 0xc +#define IIR_RLS 0x6 +#define IIR_RXRDY 0x4 +#define IIR_TXRDY 0x2 +#define IIR_NOPEND 0x1 +#define IIR_MLSC 0x0 +#define IIR_FIFO_MASK 0xc0 /* set if FIFOs are enabled */ + +/* fifo control register */ +#define FIFO_ENABLE 0x01 +#define FIFO_RCV_RST 0x02 +#define FIFO_XMT_RST 0x04 +#define FIFO_DMA_MODE 0x08 +#define FIFO_TRIGGER_1 0x00 +#define FIFO_TRIGGER_4 0x40 +#define FIFO_TRIGGER_8 0x80 +#define FIFO_TRIGGER_14 0xc0 + +/* character format control register */ +#define CFCR_DLAB 0x80 +#define CFCR_SBREAK 0x40 +#define CFCR_PZERO 0x30 +#define CFCR_PONE 0x20 +#define CFCR_PEVEN 0x10 +#define CFCR_PODD 0x00 +#define CFCR_PENAB 0x08 +#define CFCR_STOPB 0x04 +#define CFCR_8BITS 0x03 +#define CFCR_7BITS 0x02 +#define CFCR_6BITS 0x01 +#define CFCR_5BITS 0x00 + +/* modem control register */ +#define MCR_LOOPBACK 0x10 +#define MCR_IENABLE 0x08 +#define MCR_DRS 0x04 +#define MCR_RTS 0x02 +#define MCR_DTR 0x01 + +/* line status register */ +#define LSR_RCV_FIFO 0x80 +#define LSR_TSRE 0x40 +#define LSR_TXRDY 0x20 +#define LSR_BI 0x10 +#define LSR_FE 0x08 +#define LSR_PE 0x04 +#define LSR_OE 0x02 +#define LSR_RXRDY 0x01 +#define LSR_RCV_MASK 0x1f + +/* modem status register */ +#define MSR_DCD 0x80 +#define MSR_RI 0x40 +#define MSR_DSR 0x20 +#define MSR_CTS 0x10 +#define MSR_DDCD 0x08 +#define MSR_TERI 0x04 +#define MSR_DDSR 0x02 +#define MSR_DCTS 0x01 + +/* + * WARNING: Serial console is assumed to be at COM1 address + * and CONUNIT must be 0. + */ +#define CONADDR (0x3f8) +#define CONUNIT (0) diff --git a/sys/dev/speaker/spkr.c b/sys/dev/speaker/spkr.c new file mode 100644 index 0000000..ffeec08 --- /dev/null +++ b/sys/dev/speaker/spkr.c @@ -0,0 +1,520 @@ +/* + * spkr.c -- device driver for console speaker on 80386 + * + * v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990 + * modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su> + * 386bsd only clean version, all SYSV stuff removed + * use hz value from param.c + */ + +#include "speaker.h" + +#if NSPEAKER > 0 + +#include "param.h" +#include "kernel.h" +#include "errno.h" +#include "buf.h" +#include "uio.h" +#include "spkr.h" + +/**************** MACHINE DEPENDENT PART STARTS HERE ************************* + * + * This section defines a function tone() which causes a tone of given + * frequency and duration from the 80x86's console speaker. + * Another function endtone() is defined to force sound off, and there is + * also a rest() entry point to do pauses. + * + * Audible sound is generated using the Programmable Interval Timer (PIT) and + * Programmable Peripheral Interface (PPI) attached to the 80x86's speaker. The + * PPI controls whether sound is passed through at all; the PIT's channel 2 is + * used to generate clicks (a square wave) of whatever frequency is desired. + */ + +/* + * PIT and PPI port addresses and control values + * + * Most of the magic is hidden in the TIMER_PREP value, which selects PIT + * channel 2, frequency LSB first, square-wave mode and binary encoding. + * The encoding is as follows: + * + * +----------+----------+---------------+-----+ + * | 1 0 | 1 1 | 0 1 1 | 0 | + * | SC1 SC0 | RW1 RW0 | M2 M1 M0 | BCD | + * +----------+----------+---------------+-----+ + * Counter Write Mode 3 Binary + * Channel 2 LSB first, (Square Wave) Encoding + * MSB second + */ +#define PPI 0x61 /* port of Programmable Peripheral Interface */ +#define PPI_SPKR 0x03 /* turn these PPI bits on to pass sound */ +#define PIT_CTRL 0x43 /* PIT control address */ +#define PIT_COUNT 0x42 /* PIT count address */ +#define PIT_MODE 0xB6 /* set timer mode for sound generation */ + +/* + * Magic numbers for timer control. + */ +#define TIMER_CLK 1193180L /* corresponds to 18.2 MHz tick rate */ + +static int endtone() +/* turn off the speaker, ending current tone */ +{ + wakeup((caddr_t)endtone); + outb(PPI, inb(PPI) & ~PPI_SPKR); +} + +static void tone(hz, ticks) +/* emit tone of frequency hz for given number of ticks */ +unsigned int hz, ticks; +{ + unsigned int divisor = TIMER_CLK / hz; + int sps; + +#ifdef DEBUG + printf("tone: hz=%d ticks=%d\n", hz, ticks); +#endif /* DEBUG */ + + /* set timer to generate clicks at given frequency in Hertz */ + sps = spltty(); + outb(PIT_CTRL, PIT_MODE); /* prepare timer */ + outb(PIT_COUNT, (unsigned char) divisor); /* send lo byte */ + outb(PIT_COUNT, (divisor >> 8)); /* send hi byte */ + splx(sps); + + /* turn the speaker on */ + outb(PPI, inb(PPI) | PPI_SPKR); + + /* + * Set timeout to endtone function, then give up the timeslice. + * This is so other processes can execute while the tone is being + * emitted. + */ + timeout((caddr_t)endtone, (caddr_t)NULL, ticks); + sleep((caddr_t)endtone, PZERO - 1); +} + +static int endrest() +/* end a rest */ +{ + wakeup((caddr_t)endrest); +} + +static void rest(ticks) +/* rest for given number of ticks */ +int ticks; +{ + /* + * Set timeout to endrest function, then give up the timeslice. + * This is so other processes can execute while the rest is being + * waited out. + */ +#ifdef DEBUG + printf("rest: %d\n", ticks); +#endif /* DEBUG */ + timeout((caddr_t)endrest, (caddr_t)NULL, ticks); + sleep((caddr_t)endrest, PZERO - 1); +} + +/**************** PLAY STRING INTERPRETER BEGINS HERE ********************** + * + * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; + * M[LNS] are missing and the ~ synonym and octave-tracking facility is added. + * Requires tone(), rest(), and endtone(). String play is not interruptible + * except possibly at physical block boundaries. + */ + +typedef int bool; +#define TRUE 1 +#define FALSE 0 + +#define toupper(c) ((c) - ' ' * (((c) >= 'a') && ((c) <= 'z'))) +#define isdigit(c) (((c) >= '0') && ((c) <= '9')) +#define dtoi(c) ((c) - '0') + +static int octave; /* currently selected octave */ +static int whole; /* whole-note time at current tempo, in ticks */ +static int value; /* whole divisor for note time, quarter note = 1 */ +static int fill; /* controls spacing of notes */ +static bool octtrack; /* octave-tracking on? */ +static bool octprefix; /* override current octave-tracking state? */ + +/* + * Magic number avoidance... + */ +#define SECS_PER_MIN 60 /* seconds per minute */ +#define WHOLE_NOTE 4 /* quarter notes per whole note */ +#define MIN_VALUE 64 /* the most we can divide a note by */ +#define DFLT_VALUE 4 /* default value (quarter-note) */ +#define FILLTIME 8 /* for articulation, break note in parts */ +#define STACCATO 6 /* 6/8 = 3/4 of note is filled */ +#define NORMAL 7 /* 7/8ths of note interval is filled */ +#define LEGATO 8 /* all of note interval is filled */ +#define DFLT_OCTAVE 4 /* default octave */ +#define MIN_TEMPO 32 /* minimum tempo */ +#define DFLT_TEMPO 120 /* default tempo */ +#define MAX_TEMPO 255 /* max tempo */ +#define NUM_MULT 3 /* numerator of dot multiplier */ +#define DENOM_MULT 2 /* denominator of dot multiplier */ + +/* letter to half-tone: A B C D E F G */ +static int notetab[8] = {9, 11, 0, 2, 4, 5, 7}; + +/* + * This is the American Standard A440 Equal-Tempered scale with frequencies + * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook... + * our octave 0 is standard octave 2. + */ +#define OCTAVE_NOTES 12 /* semitones per octave */ +static int pitchtab[] = +{ +/* C C# D D# E F F# G G# A A# B*/ +/* 0 */ 65, 69, 73, 78, 82, 87, 93, 98, 103, 110, 117, 123, +/* 1 */ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, +/* 2 */ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, +/* 3 */ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, +/* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975, +/* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, +/* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902, +}; + +static void playinit() +{ + octave = DFLT_OCTAVE; + whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO; + fill = NORMAL; + value = DFLT_VALUE; + octtrack = FALSE; + octprefix = TRUE; /* act as though there was an initial O(n) */ +} + +static void playtone(pitch, value, sustain) +/* play tone of proper duration for current rhythm signature */ +int pitch, value, sustain; +{ + register int sound, silence, snum = 1, sdenom = 1; + + /* this weirdness avoids floating-point arithmetic */ + for (; sustain; sustain--) + { + snum *= NUM_MULT; + sdenom *= DENOM_MULT; + } + + if (pitch == -1) + rest(whole * snum / (value * sdenom)); + else + { + sound = (whole * snum) / (value * sdenom) + - (whole * (FILLTIME - fill)) / (value * FILLTIME); + silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom); + +#ifdef DEBUG + printf("playtone: pitch %d for %d ticks, rest for %d ticks\n", + pitch, sound, silence); +#endif /* DEBUG */ + + tone(pitchtab[pitch], sound); + if (fill != LEGATO) + rest(silence); + } +} + +static int abs(n) +int n; +{ + if (n < 0) + return(-n); + else + return(n); +} + +static void playstring(cp, slen) +/* interpret and play an item from a notation string */ +char *cp; +size_t slen; +{ + int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE; + +#define GETNUM(cp, v) for(v=0; isdigit(cp[1]) && slen > 0; ) \ + {v = v * 10 + (*++cp - '0'); slen--;} + for (; slen--; cp++) + { + int sustain, timeval, tempo; + register char c = toupper(*cp); + +#ifdef DEBUG + printf("playstring: %c (%x)\n", c, c); +#endif /* DEBUG */ + + switch (c) + { + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + + /* compute pitch */ + pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES; + + /* this may be followed by an accidental sign */ + if (cp[1] == '#' || cp[1] == '+') + { + ++pitch; + ++cp; + slen--; + } + else if (cp[1] == '-') + { + --pitch; + ++cp; + slen--; + } + + /* + * If octave-tracking mode is on, and there has been no octave- + * setting prefix, find the version of the current letter note + * closest to the last regardless of octave. + */ + if (octtrack && !octprefix) + { + if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) + { + ++octave; + pitch += OCTAVE_NOTES; + } + + if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) + { + --octave; + pitch -= OCTAVE_NOTES; + } + } + octprefix = FALSE; + lastpitch = pitch; + + /* ...which may in turn be followed by an override time value */ + GETNUM(cp, timeval); + if (timeval <= 0 || timeval > MIN_VALUE) + timeval = value; + + /* ...and/or sustain dots */ + for (sustain = 0; cp[1] == '.'; cp++) + { + slen--; + sustain++; + } + + /* time to emit the actual tone */ + playtone(pitch, timeval, sustain); + break; + + case 'O': + if (cp[1] == 'N' || cp[1] == 'n') + { + octprefix = octtrack = FALSE; + ++cp; + slen--; + } + else if (cp[1] == 'L' || cp[1] == 'l') + { + octtrack = TRUE; + ++cp; + slen--; + } + else + { + GETNUM(cp, octave); + if (octave >= sizeof(pitchtab) / OCTAVE_NOTES) + octave = DFLT_OCTAVE; + octprefix = TRUE; + } + break; + + case '>': + if (octave < sizeof(pitchtab) / OCTAVE_NOTES - 1) + octave++; + octprefix = TRUE; + break; + + case '<': + if (octave > 0) + octave--; + octprefix = TRUE; + break; + + case 'N': + GETNUM(cp, pitch); + for (sustain = 0; cp[1] == '.'; cp++) + { + slen--; + sustain++; + } + playtone(pitch - 1, value, sustain); + break; + + case 'L': + GETNUM(cp, value); + if (value <= 0 || value > MIN_VALUE) + value = DFLT_VALUE; + break; + + case 'P': + case '~': + /* this may be followed by an override time value */ + GETNUM(cp, timeval); + if (timeval <= 0 || timeval > MIN_VALUE) + timeval = value; + for (sustain = 0; cp[1] == '.'; cp++) + { + slen--; + sustain++; + } + playtone(-1, timeval, sustain); + break; + + case 'T': + GETNUM(cp, tempo); + if (tempo < MIN_TEMPO || tempo > MAX_TEMPO) + tempo = DFLT_TEMPO; + whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo; + break; + + case 'M': + if (cp[1] == 'N' || cp[1] == 'n') + { + fill = NORMAL; + ++cp; + slen--; + } + else if (cp[1] == 'L' || cp[1] == 'l') + { + fill = LEGATO; + ++cp; + slen--; + } + else if (cp[1] == 'S' || cp[1] == 's') + { + fill = STACCATO; + ++cp; + slen--; + } + break; + } + } +} + +/******************* UNIX DRIVER HOOKS BEGIN HERE ************************** + * + * This section implements driver hooks to run playstring() and the tone(), + * endtone(), and rest() functions defined above. + */ + +static int spkr_active; /* exclusion flag */ +static struct buf *spkr_inbuf; /* incoming buf */ + +int spkropen(dev) +dev_t dev; +{ +#ifdef DEBUG + printf("spkropen: entering with dev = %x\n", dev); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else if (spkr_active) + return(EBUSY); + else + { + playinit(); + spkr_inbuf = geteblk(DEV_BSIZE); + spkr_active = 1; + } + return(0); +} + +int spkrwrite(dev, uio) +dev_t dev; +struct uio *uio; +{ + register unsigned n; + char *cp; + int error; +#ifdef DEBUG + printf("spkrwrite: entering with dev = %x, count = %d\n", + dev, uio->uio_resid); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else + { + n = MIN(DEV_BSIZE, uio->uio_resid); + cp = spkr_inbuf->b_un.b_addr; + error = uiomove(cp, n, uio); + if (!error) + playstring(cp, n); + return(error); + } +} + +int spkrclose(dev) +dev_t dev; +{ +#ifdef DEBUG + printf("spkrclose: entering with dev = %x\n", dev); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else + { + endtone(); + brelse(spkr_inbuf); + spkr_active = 0; + } + return(0); +} + +int spkrioctl(dev, cmd, cmdarg) +dev_t dev; +int cmd; +caddr_t cmdarg; +{ +#ifdef DEBUG + printf("spkrioctl: entering with dev = %x, cmd = %x\n", dev, cmd); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else if (cmd == SPKRTONE) + { + tone_t *tp = (tone_t *)cmdarg; + + if (tp->frequency == 0) + rest(tp->duration); + else + tone(tp->frequency, tp->duration); + } + else if (cmd == SPKRTUNE) + { + tone_t *tp = (tone_t *)(*(caddr_t *)cmdarg); + tone_t ttp; + int error; + + for (; ; tp++) { + error = copyin(tp, &ttp, sizeof(tone_t)); + if (error) + return(error); + if (ttp.duration == 0) + break; + if (ttp.frequency == 0) + rest(ttp.duration); + else + tone(ttp.frequency, ttp.duration); + } + } + else + return(EINVAL); + return(0); +} + +#endif /* NSPEAKER > 0 */ +/* spkr.c ends here */ |