#! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # mpudriver.c # pseudo-blk.c # pseudo-char.c # This archive created: Sun Jan 21 09:53:00 1996 export PATH; PATH=/bin:/usr/bin:$PATH cat << \SHAR_EOF > 'mpudriver.c' /* * mpu.c - Roland MPU401 MIDI device driver * * 386BSD/isa bus. Dumb UART mode. * * Overview: the mpu is a board developed by Roland to allow * a computer to read/write MIDI byte messages on a somewhat * serial (byte-sized) "bus" and as a result - to play music * on external synthesizers. This driver offers a simple * "dumb uart" (non-interrupt) mode for reading and writing * MIDI messages. * * routines: * mpuprobe * mpuattach * mpuopen * mpuclose * mpuread * mpuwrite * mpuioctl * mpuselect * mpuintr */ #include "mpu.h" #if NMPU > 0 /* * mpu driver. */ #include "param.h" #include "systm.h" #include "ioctl.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_device.h" #include "i386/isa/mpureg.h" #define mpuminor(d) int mpuprobe(), mpuattach(), mpuintr(); struct isa_driver mpudriver = { mpuprobe, mpuattach, "mpu" }; struct mpu { int dataport; int statport; int commport; int state; struct mqueue mq; } mpusoftc[NMPU]; #define DATAPORT mp->dataport #define STATPORT mp->statport #define COMMPORT mp->commport #define TIMEOUTCYCLES 100000 #define ACKCYCLES 50000 #define MPURESET 0xff /* hw reset of mpu */ #define MPUUARTMODE 0x3f /* dumb uart mode */ #define MPUTHRUOFF 0x33 /* midi in bytes not echoed in hw to midi out */ #define MPUACK 0xfe /* acknowledgement from MPU */ /* states */ #define MPUEXISTS 0x1 #define MPUOPEN 0x2 /* * midi input Q */ static void mqInit(); static int mqGet(); static int mqPut(); static int mpuReset(); static int mpuCommandAndAck(); /* turn debug printf's on/off */ int mpudebug=0; mpuprobe(dev) struct isa_device *dev; { int unit; struct mpu *mp; unit = dev->id_unit; #ifdef DEBUG printf("mpuprobe %lx unit: %d\n", dev->id_iobase, dev->id_unit); #endif if (unit > NMPU) return(0); mp = &mpusoftc[unit]; mp->state = 0; /* no state yet */ DATAPORT = dev->id_iobase; COMMPORT = STATPORT = (dev->id_iobase + 1); if (mpuReset(mp, 5)) { mp->state = MPUEXISTS; return(1); } return(0); } int mpuattach(dev) struct isa_device *dev; { register struct mpu *mp; register int unit; unit = dev->id_unit; if (unit > NMPU) { return(0); } mp = &mpusoftc[unit]; /* initialize the input queue */ mqInit(mp); return (1); } mpuopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { register int unit; register struct mpu *mp; unsigned char input; unit = minor(dev); if (unit >= NMPU) { return (ENXIO); } mp = &mpusoftc[unit]; if (mp->state & MPUOPEN) { return (EBUSY); } mp->state |= MPUOPEN; mpuReset(mp, 5); mpuCommandAndAck(mp, MPUTHRUOFF); mpuCommandAndAck(mp, MPUUARTMODE); /* drain mpu. */ while ((inb(STATPORT) & 0x80) == 0 ) { input = inb(DATAPORT); } return(0); } mpuclose(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { register int unit; register struct mpu *mp; if (mpudebug) printf("mpuclose\n"); unit = minor(dev); if (unit >= NMPU) { return (ENXIO); } mp = &mpusoftc[unit]; mp->state = 0; mpuReset(mp, 1); return(0); } int mpuread(dev_t dev, struct uio *uio, int flag) { unsigned char c; int timeout; int error; int unit; struct mpu *mp; int count; unit = minor(dev); if (unit >= NMPU) { return (ENXIO); } mp = &mpusoftc[unit]; count = uio->uio_resid; while (count) { /* service input queue first */ if (mp->mq.cc) { c = (unsigned char) mqGet(mp); if (mpudebug) printf("mpuread: Q input %x\n", c); error = uiomove(&c, 1, uio); if (error) return(error); } else { /* try for one polled midi byte */ timeout = TIMEOUTCYCLES; while(timeout--) { /* if status indicates ready to read */ if ( (inb(STATPORT) & 0x80) == 0 ) { if (mpudebug) { printf("mpuread STATport %x\n", inb(STATPORT)); } /* then read */ c = ((unsigned char) inb(DATAPORT)) & 0xff; if (mpudebug) { printf("mpuread: %x\n", (int)c); } /* copy to user space */ error = uiomove(&c, 1, uio); if (mpudebug && error) printf("mpuread: uiomove error %d\n", error); if (error) { return(error); } break; } } if (timeout <= 0) { if (mpudebug) printf("mpuread: timeout\n"); return(EIO); } } count--; } return(0); } int mpuwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int timeout; int error; int unit; struct mpu *mp; int state; unsigned char c; unit = minor(dev); if (unit >= NMPU) { return (ENXIO); } mp = &mpusoftc[unit]; while(uio->uio_resid) { error = uiomove(&c, 1, uio); if (error) return(error); timeout = TIMEOUTCYCLES; /* while not timed out and mpu not ready */ while (timeout-- && ((state = inb(STATPORT)) & 0x40)) { /* if statport not ready for output, check * for input. That may hang us up. * input bytes put in circular queue for later * reads. */ if ( ( state & 0x0080 ) == 0 ) { c = inb(DATAPORT); if (mpudebug) { printf("mpuwrite: read data %x\n", c); } mqPut(mp, c); /* start over again, we were trying to do * output until we got distracted */ timeout = TIMEOUTCYCLES; continue; } } if (timeout <= 0) { return(EIO); /* gag */ } if (mpudebug) printf("mpuwrite %x\n", c); outb(DATAPORT,c&0xff); } return(0); } /* * currently nothing */ int mpuioctl(dev, cmd, data, flag) dev_t dev; caddr_t data; { return(0); } /* * at this point, only really support READ */ int mpuselect(dev, flag) dev_t dev; int flag; { struct mpu *mp; int unit; unit = minor(dev); if (unit >= NMPU) { return (ENXIO); } mp = &mpusoftc[unit]; if ( flag == FREAD ) { /* input Q, then device */ if ( mp->mq.cc ) { if (mpudebug) printf("mpu select Q T\n"); return(1); } if ((inb(STATPORT) & 0x80) == 0 ) { if (mpudebug) printf("mpu select %x\n", inb(STATPORT)); return(1); } } return(0); } int mpuintrcount=0; int mpustate; int mpudata; /* * interrupts seem to be happening in * UART mode. Now if I knew why, it would be nice. * They don't seem to want anything though. */ mpuintr(unit) register int unit; { struct mpu *mp; if (unit >= NMPU) { return (ENXIO); } mp = &mpusoftc[unit]; mpuintrcount++; mpustate = inb(STATPORT); } /* * try to reset the mpu */ static mpuReset(mp, count) struct mpu *mp; int count; { int i; for ( i = 0; i < count; i++) { if (mpuCommandAndAck(mp, MPURESET)); return(1); } return(0); } /* send command to mpu and wait for response. * This routine will read any amount of queued response. * * returns: * -1: no ack. * 0: unexpected char. * 1: got ack. */ static mpuCommandAndAck(mp, command) struct mpu *mp; int command; { int i; unsigned char input; outb(COMMPORT, command); /* loop to soak up any ACK received from mpu. * will timeout if ACK not received. */ for(i = 0; i < ACKCYCLES; i++) { while ((inb(STATPORT) & 0x80) == 0 ) { input = inb(DATAPORT); if ( input == (unsigned char)MPUACK ) { return(1); } } } return(0); } /* queue handling code for input music queue. * The queue exists because of mpu brain-damage -- we * must funnel bytes received while attempting to write over * to the read side of the driver. */ static void mqInit(mp) struct mpu *mp; { /* inq.ovflag = FALSE; inq.cc = 0; inq.qs = inq.cf = inq.ct = inqueue; inq.qe = inqueue + INQUEUESIZE - 1; */ mp->mq.ovflag = FALSE; mp->mq.cc = 0; mp->mq.qs = mp->mq.cf = mp->mq.ct = mp->mq.buf; mp->mq.qe = mp->mq.buf + QSIZE - 1; } /* mqPut * * put byte in queue. * * if queue FULL * cycle queue * */ static int mqPut(mp, c) struct mpu *mp; int c; { /* check for queue FULL condition */ if ( mp->mq.ovflag == TRUE || (mp->mq.cf == mp->mq.ct && mp->mq.cc > 0 )) { mp->mq.ovflag = TRUE; /* if you want to cycle queue, uncomment */ mp->mq.cf++; return(0); } /* put char in queue tail or end of queue */ *mp->mq.ct++ = (char) c; /* wrap tail pointer around if necessary */ if ( mp->mq.ct > mp->mq.qe ) mp->mq.ct = mp->mq.qs; mp->mq.cc++; return(1); } /* caller should cast result */ static int mqGet(mp) struct mpu *mp; { int c; /* get the character from the queue front */ c = *mp->mq.cf++ & 0xff; /* wrap if necessary */ if ( mp->mq.cf > mp->mq.qe ) mp->mq.cf = mp->mq.qs; /* decrement char count */ mp->mq.cc--; return((int) c); } #endif /* NMPU */ SHAR_EOF chmod 644 'mpudriver.c' cat << \SHAR_EOF > 'pseudo-blk.c' /* * semi-pseudocode for Unix block device driver */ /* device register address */ #define BLOCKDEV ((struct device *)0177400) /* device register control block. This structure * maps on the BLOCKDEV address and allows per register access. */ struct device { int status; /* controller status register */ int command; /* controller command register */ int error; /* error register */ long addr; /* address register */ long count; /* transfer count */ }; struct buf blockHeader; /* device i/o queue */ struct buf rawBuffer; /* buffer needed for raw i/o interface */ /* * strategy routine. DRIVER ENTRY POINT. * queue buffers on local queue * and make sure device is "started" * i.e., interrupt driven i/o in progress. */ bkstrategy(bp) struct buf *bp; { .make sanity check on bp->b_blkno/device # may set error and return .zero out bp->av_forw since that will be end of block chain .raise priority to block local interrupt handler .put block on local device queue ( we may sort it with disksort() ) if queue empty blockHeader.b_actf <- bp else blockHeader.b_actl->av_forw <- bp blockHeader.b_actl <- bp .if i/o NOT in progress call device start routine .restore priority } /* * "start" the device doing output. * setup hardware controller with next buffer * for i/o. */ bkstart() { if no buffers queued return .get next buffer .set started flag .map device/logical block # to local true device addresses (may use partition table here to find true absolute block) .setup hardware .fireup hardware (might be dma, might be copying bytes from buffer to internal staging area for more primitive hardware setups) } /* * device interrupt routine. * Called from assembler regions of kernel by device interrupt. * Priority level is set in hardware. */ bkintr() { .if not started, return .get buffer at head of queue .set not busy .if hardware error call generic device error routine may reset device may try again set B_ERROR flag in buffer .set device block header to next buffer header.b_actf <- bp->av_forw .call local start routine again to process any more buffers in i/o queue (keep device going) .call buffer cache routine iodone(bp) to release buffer and wakeup any sleepers } /* * raw i/o interface. Call strategy routine * via common code physio() setup routine. Use raw buffer * since strategy/start interrupt routines are coded to assume * a buffer header. physio will setup address translation * for kernel to user direct data transfer. */ bkread(dev) DEVICE dev; { physio(bkstrategy, &rawBuffer, dev, B_READ); } bkwrite(dev) DEVICE dev; { physio(bkstrategy, &rawBuffer, dev, B_WRITE); } /* * typically nothing to do */ bkopen() { } /* * nothing to do */ bkclose() { } SHAR_EOF chmod 644 'pseudo-blk.c' cat << \SHAR_EOF > 'pseudo-char.c' /* * pseudocode for Unix char device driver * * This might be a console device or a multiplexed rs-232 device. */ #define CONSADDR ((struct consdev *) 0160100) struct consdev { int csr; /* control status register */ unsigned char transmit; /* output register */ unsigned char recieve; /* input register */ }; /* tty structures managed by this device driver */ struct tty conttys[SOMENUMBER]; /* as many as minor devices */ /* * open the device. May sleep if something like * carrier needs to change state; i.e., hang * in device open call (could happen in read too). * * May be called multiple times. */ conopen(dev, flag) DEVICE dev; int flag; { .check minor dev value to make sure it makes sense .map minor device to local tty structure .do any local tty structure init .e.g, set "output" start procedure .if state indicates NOT open .set more tty defaults; e.g., state is not ISOPEN .may block and wait for carrier .state is ISOPEN .set up device hardware (baud rate...) .init tty default characters (e.g., control-s) .else might be error if not root or exclusive use device u.u_error = EBUSY; call line discipline ttyopen(dev, &tty pointer) line discipline will finish tty struct init } /* * called on last close of device. */ conclose(dev, flag) DEVICE dev; int flag; { .map device to tty structure .do any local cleanup if any (typically none). .call line discipline tty_close to clean up tty structure and queue's. queue flushing is of concern. } /* * called from read-side. Doesn't do much */ conread(dev) { map device to tty call line discipline read (ttread(&tty pointer)) } conwrite(dev) { map device to tty call line discipline write (ttwrite(&tty pointer)) } /* * ioctl calls. System ioctl will jump to this * routine. this routine calls common tty code first. */ conioctl(dev, cmd, addr, flag) DEVICE dev; int cmd; ADDRESS addr; { call ttioctl() tty line discipline common ioctl routine to handle common ioctl calls. if call indicates hardware changes (e.g., was TIOCCSETP or TIOCSETN (Berkeley)) make local hardware changes (change baud rate). else if call failed error u.u_error = ENOTTY... ] /* * receiver interrupt routine * Characters are coming in. */ conrintr(dev) int flag; { .may need to map device to physical address of registers .map device to tty structure .may need to wakeup someone sleeping on a open .get char c from receiver register .may do some minimal processing (e.g., parity) .put intput char in input queue via line discipline call ttyinput(c, tp); } /* * transmit interrupt routine. * Characters are going out. */ contintr(dev) { .may need to map device to physical address of registers .map minor device to tty structure /* we may need protection against various interrupt * related troubles such as trying to output chars * when we are logically stopped... */ if device is tied up somehow (BUSY | STOPPED) return if write is sleeping on output overflow, wake him up tty state is not asleep wakeup if no chars to output return if any chars to output use putc to get from queue write them out to hardware } /* * may need start routine so that tty driver can * start output if for example output queue has drained. * Or just to get i/o going in general. */ constart() { } /* * may have local internal routine for changing hardware * parameters like baud rate. */ conparam() { } /* * may have stop routine so that tty driver can * issue stop to driver on receiving logical stop char. */ constop() { } /* * console will have local polled putchar() * used by internal printf for printing kernel * messages; e.g., panics. Famous "not for chitchat" * derives from this. */ putchar(c) { .setup hardware for output of one char .write c to transmit register .wait for status register to signal done (busy loop) } SHAR_EOF chmod 644 'pseudo-char.c' exit 0 # End of shell archive