diff options
Diffstat (limited to 'sys/i386/isa')
119 files changed, 62383 insertions, 0 deletions
diff --git a/sys/i386/isa/aha1542.c b/sys/i386/isa/aha1542.c new file mode 100644 index 0000000..0a64cef --- /dev/null +++ b/sys/i386/isa/aha1542.c @@ -0,0 +1,1478 @@ +/* + * (Mostly) Written by Julian Elischer (julian@tfs.com) + * for TRW Financial Systems for use under the MACH(2.5) operating system. + * + * TRW Financial Systems, in accordance with their agreement with Carnegie + * Mellon University, makes this software available to CMU to distribute + * or use in any manner that they see fit as long as this message is kept with + * the software. For this reason TFS also grants any other persons or + * organisations permission to use or modify this software. + * + * TFS supplies this software to be publicly redistributed + * on the understanding that TFS is not responsible for the correct + * functioning of this software in any circumstances. + * + * $Id: aha1542.c,v 1.32 1994/08/20 03:48:37 davidg Exp $ + */ + +/* + * Ported to run under 386BSD by Julian Elischer (julian@tfs.com) Sept 1992 + */ + +#include <sys/types.h> +#ifdef KERNEL /* don't laugh.. look for main() */ +#include "aha.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/user.h> +#include <i386/isa/isa_device.h> +#endif /* KERNEL */ +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +#ifdef KERNEL +#include "ddb.h" +#include <sys/kernel.h> +#else /*KERNEL */ +#define NAHA 1 +#endif /*KERNEL */ + +/************************** board definitions *******************************/ + +/* + * I/O Port Interface + */ + +#define AHA_BASE aha->aha_base +#define AHA_CTRL_STAT_PORT (AHA_BASE + 0x0) /* control & status */ +#define AHA_CMD_DATA_PORT (AHA_BASE + 0x1) /* cmds and datas */ +#define AHA_INTR_PORT (AHA_BASE + 0x2) /* Intr. stat */ + +/* + * AHA_CTRL_STAT bits (write) + */ + +#define AHA_HRST 0x80 /* Hardware reset */ +#define AHA_SRST 0x40 /* Software reset */ +#define AHA_IRST 0x20 /* Interrupt reset */ +#define AHA_SCRST 0x10 /* SCSI bus reset */ + +/* + * AHA_CTRL_STAT bits (read) + */ + +#define AHA_STST 0x80 /* Self test in Progress */ +#define AHA_DIAGF 0x40 /* Diagnostic Failure */ +#define AHA_INIT 0x20 /* Mbx Init required */ +#define AHA_IDLE 0x10 /* Host Adapter Idle */ +#define AHA_CDF 0x08 /* cmd/data out port full */ +#define AHA_DF 0x04 /* Data in port full */ +#define AHA_INVDCMD 0x01 /* Invalid command */ + +/* + * AHA_CMD_DATA bits (write) + */ + +#define AHA_NOP 0x00 /* No operation */ +#define AHA_MBX_INIT 0x01 /* Mbx initialization */ +#define AHA_START_SCSI 0x02 /* start scsi command */ +#define AHA_START_BIOS 0x03 /* start bios command */ +#define AHA_INQUIRE 0x04 /* Adapter Inquiry */ +#define AHA_MBO_INTR_EN 0x05 /* Enable MBO available interrupt */ +#define AHA_SEL_TIMEOUT_SET 0x06 /* set selection time-out */ +#define AHA_BUS_ON_TIME_SET 0x07 /* set bus-on time */ +#define AHA_BUS_OFF_TIME_SET 0x08 /* set bus-off time */ +#define AHA_SPEED_SET 0x09 /* set transfer speed */ +#define AHA_DEV_GET 0x0a /* return installed devices */ +#define AHA_CONF_GET 0x0b /* return configuration data */ +#define AHA_TARGET_EN 0x0c /* enable target mode */ +#define AHA_SETUP_GET 0x0d /* return setup data */ +#define AHA_WRITE_CH2 0x1a /* write channel 2 buffer */ +#define AHA_READ_CH2 0x1b /* read channel 2 buffer */ +#define AHA_WRITE_FIFO 0x1c /* write fifo buffer */ +#define AHA_READ_FIFO 0x1d /* read fifo buffer */ +#define AHA_ECHO 0x1e /* Echo command data */ +#define AHA_EXT_BIOS 0x28 /* return extended bios info */ +#define AHA_MBX_ENABLE 0x29 /* enable mail box interface */ + +struct aha_cmd_buf { + u_char byte[16]; +}; + +/* + * AHA_INTR_PORT bits (read) + */ + +#define AHA_ANY_INTR 0x80 /* Any interrupt */ +#define AHA_SCRD 0x08 /* SCSI reset detected */ +#define AHA_HACC 0x04 /* Command complete */ +#define AHA_MBOA 0x02 /* MBX out empty */ +#define AHA_MBIF 0x01 /* MBX in full */ + +/* + * Mail box defs + */ + +#define AHA_MBX_SIZE 16 /* mail box size */ + +struct aha_mbx { + struct aha_mbx_out { + unsigned char cmd; + unsigned char ccb_addr[3]; + } mbo[AHA_MBX_SIZE]; + struct aha_mbx_in { + unsigned char stat; + unsigned char ccb_addr[3]; + } mbi[AHA_MBX_SIZE]; +}; + +/* + * mbo.cmd values + */ + +#define AHA_MBO_FREE 0x0 /* MBO entry is free */ +#define AHA_MBO_START 0x1 /* MBO activate entry */ +#define AHA_MBO_ABORT 0x2 /* MBO abort entry */ + +/* + * mbi.stat values + */ + +#define AHA_MBI_FREE 0x0 /* MBI entry is free */ +#define AHA_MBI_OK 0x1 /* completed without error */ +#define AHA_MBI_ABORT 0x2 /* aborted ccb */ +#define AHA_MBI_UNKNOWN 0x3 /* Tried to abort invalid CCB */ +#define AHA_MBI_ERROR 0x4 /* Completed with error */ + +/* FOR OLD VERSIONS OF THE !%$@ this may have to be 16 (yuk) */ +#define AHA_NSEG 17 /* Number of scatter gather segments <= 16 */ + /* allow 64 K i/o (min) */ + +struct aha_ccb { + unsigned char opcode; + unsigned char lun:3; + unsigned char data_in:1; /* must be 0 */ + unsigned char data_out:1; /* must be 0 */ + unsigned char target:3; + unsigned char scsi_cmd_length; + unsigned char req_sense_length; + unsigned char data_length[3]; + unsigned char data_addr[3]; + unsigned char link_addr[3]; + unsigned char link_id; + unsigned char host_stat; + unsigned char target_stat; + unsigned char reserved[2]; + struct scsi_generic scsi_cmd; + struct scsi_sense_data scsi_sense; + struct aha_scat_gath { + unsigned char seg_len[3]; + unsigned char seg_addr[3]; + } scat_gath[AHA_NSEG]; + struct aha_ccb *next; + struct scsi_xfer *xfer; /* the scsi_xfer for this cmd */ + struct aha_mbx_out *mbx; /* pointer to mail box */ + int flags; +#define CCB_FREE 0 +#define CCB_ACTIVE 1 +#define CCB_ABORTED 2 +}; + +/* + * opcode fields + */ + +#define AHA_INITIATOR_CCB 0x00 /* SCSI Initiator CCB */ +#define AHA_TARGET_CCB 0x01 /* SCSI Target CCB */ +#define AHA_INIT_SCAT_GATH_CCB 0x02 /* SCSI Initiator with scatter gather */ +#define AHA_RESET_CCB 0x81 /* SCSI Bus reset */ + +/* + * aha_ccb.host_stat values + */ + +#define AHA_OK 0x00 /* cmd ok */ +#define AHA_LINK_OK 0x0a /* Link cmd ok */ +#define AHA_LINK_IT 0x0b /* Link cmd ok + int */ +#define AHA_SEL_TIMEOUT 0x11 /* Selection time out */ +#define AHA_OVER_UNDER 0x12 /* Data over/under run */ +#define AHA_BUS_FREE 0x13 /* Bus dropped at unexpected time */ +#define AHA_INV_BUS 0x14 /* Invalid bus phase/sequence */ +#define AHA_BAD_MBO 0x15 /* Incorrect MBO cmd */ +#define AHA_BAD_CCB 0x16 /* Incorrect ccb opcode */ +#define AHA_BAD_LINK 0x17 /* Not same values of LUN for links */ +#define AHA_INV_TARGET 0x18 /* Invalid target direction */ +#define AHA_CCB_DUP 0x19 /* Duplicate CCB received */ +#define AHA_INV_CCB 0x1a /* Invalid CCB or segment list */ +#define AHA_ABORTED 42 + +struct aha_setup { + u_char sync_neg:1; + u_char parity:1; + u_char:6; + u_char speed; + u_char bus_on; + u_char bus_off; + u_char num_mbx; + u_char mbx[3]; + struct { + u_char offset:4; + u_char period:3; + u_char valid:1; + } sync[8]; + u_char disc_sts; +}; + +struct aha_config { + u_char chan; + u_char intr; + u_char scsi_dev:3; + u_char:5; +}; + +struct aha_inquire +{ + u_char boardid; /* type of board */ + /* 0x20 = BusLogic 545, but it gets + the command wrong, only returns + one byte */ + /* 0x31 = AHA-1540 */ + /* 0x41 = AHA-1540A/1542A/1542B */ + /* 0x42 = AHA-1640 */ + /* 0x43 = AHA-1542C */ + /* 0x44 = AHA-1542CF */ + /* 0x45 = AHA-1542CF, BIOS v2.01 */ + u_char spec_opts; /* special options ID */ + /* 0x41 = Board is standard model */ + u_char revision_1; /* firmware revision [0-9A-Z] */ + u_char revision_2; /* firmware revision [0-9A-Z] */ +}; + +struct aha_extbios +{ + u_char flags; /* Bit 3 == 1 extended bios enabled */ + u_char mailboxlock; /* mail box lock code to unlock it */ +}; + +#define INT9 0x01 +#define INT10 0x02 +#define INT11 0x04 +#define INT12 0x08 +#define INT14 0x20 +#define INT15 0x40 + +#define CHAN0 0x01 +#define CHAN5 0x20 +#define CHAN6 0x40 +#define CHAN7 0x80 + +/*********************************** end of board definitions***************/ + +#define PHYSTOKV(x) (((long int)(x)) ^ aha->kv_phys_xor) +#define KVTOPHYS(x) vtophys(x) +#define AHA_DMA_PAGES AHA_NSEG + +#define PAGESIZ 4096 +#define INVALIDATE_CACHE {asm volatile( ".byte 0x0F ;.byte 0x08" ); } + +u_char aha_scratch_buf[256]; +#ifdef AHADEBUG +int aha_debug = 1; +#endif /*AHADEBUG */ + +struct aha_data { + short aha_base; /* base port for each board */ + /* + * xor this with a physaddr to get a kv addr and visa versa + * for items in THIS STRUCT only. + * Used to get the CCD's physical and kv addresses from each + * other. + */ + long int kv_phys_xor; + struct aha_mbx aha_mbx; /* all the mailboxes */ + struct aha_ccb *aha_ccb_free; /* the next free ccb */ + struct aha_ccb aha_ccb[AHA_MBX_SIZE]; /* all the CCBs */ + int aha_int; /* our irq level */ + int aha_dma; /* out DMA req channel */ + int aha_scsi_dev; /* our scsi bus address */ + struct scsi_link sc_link; /* prototype for subdevs */ +} *ahadata[NAHA]; + +struct aha_ccb *aha_get_ccb(); +int ahaprobe(); +void aha_done(); +int ahaattach(); +int ahaintr(); +int32 aha_scsi_cmd(); +timeout_t aha_timeout; +void ahaminphys(); +u_int32 aha_adapter_info(); + +#ifdef KERNEL +struct scsi_adapter aha_switch = +{ + aha_scsi_cmd, + ahaminphys, + 0, + 0, + aha_adapter_info, + "aha", + { 0, 0 } +}; + +/* the below structure is so we have a default dev struct for out link struct */ +struct scsi_device aha_dev = +{ + NULL, /* Use default error handler */ + NULL, /* have a queue, served by this */ + NULL, /* have no async handler */ + NULL, /* Use default 'done' routine */ + "aha", + 0, + { 0, 0 } +}; + +struct isa_driver ahadriver = +{ + ahaprobe, + ahaattach, + "aha" +}; + +#endif /* KERNEL */ + +static int ahaunit = 0; + +#define aha_abortmbx(mbx) \ + (mbx)->cmd = AHA_MBO_ABORT; \ + outb(AHA_CMD_DATA_PORT, AHA_START_SCSI); +#define aha_startmbx(mbx) \ + (mbx)->cmd = AHA_MBO_START; \ + outb(AHA_CMD_DATA_PORT, AHA_START_SCSI); + +#define AHA_RESET_TIMEOUT 2000 /* time to wait for reset (mSec) */ +#ifndef KERNEL +main() +{ + printf("size of aha_data is %d\n", sizeof(struct aha_data)); + printf("size of aha_ccb is %d\n", sizeof(struct aha_ccb)); + printf("size of aha_mbx is %d\n", sizeof(struct aha_mbx)); +} + +#else /*KERNEL */ + +/* + * aha_cmd(unit,icnt, ocnt,wait, retval, opcode, args) + * Activate Adapter command + * icnt: number of args (outbound bytes written after opcode) + * ocnt: number of expected returned bytes + * wait: number of seconds to wait for response + * retval: buffer where to place returned bytes + * opcode: opcode AHA_NOP, AHA_MBX_INIT, AHA_START_SCSI ... + * args: parameters + * + * Performs an adapter command through the ports. Not to be confused + * with a scsi command, which is read in via the dma. One of the adapter + * commands tells it to read in a scsi command but that one is done + * separately. This is only called during set-up. + */ +int +aha_cmd(unit, icnt, ocnt, wait, retval, opcode, args) + int unit; + int icnt; + int ocnt; + int wait; + u_char *retval; + unsigned opcode; + u_char args; +{ + struct aha_data *aha = ahadata[unit]; + unsigned *ic = &opcode; + u_char oc; + register i; + int sts; + + /* + * multiply the wait argument by a big constant + * zero defaults to 1 sec.. + * all wait loops are in 50uSec cycles + */ + if (wait) + wait *= 20000; + else + wait = 20000; + /* + * Wait for the adapter to go idle, unless it's one of + * the commands which don't need this + */ + if (opcode != AHA_MBX_INIT && opcode != AHA_START_SCSI) { + i = 20000; /*do this for upto about a second */ + while (--i) { + sts = inb(AHA_CTRL_STAT_PORT); + if (sts & AHA_IDLE) { + break; + } + DELAY(50); + } + if (!i) { + printf("aha%d: aha_cmd, host not idle(0x%x)\n", + unit, sts); + return (ENXIO); + } + } + /* + * Now that it is idle, if we expect output, preflush the + * queue feeding to us. + */ + if (ocnt) { + while ((inb(AHA_CTRL_STAT_PORT)) & AHA_DF) + inb(AHA_CMD_DATA_PORT); + } + /* + * Output the command and the number of arguments given + * for each byte, first check the port is empty. + */ + icnt++; + /* include the command */ + while (icnt--) { + sts = inb(AHA_CTRL_STAT_PORT); + for (i = wait; i; i--) { + sts = inb(AHA_CTRL_STAT_PORT); + if (!(sts & AHA_CDF)) + break; + DELAY(50); + } + if (i == 0) { + printf("aha%d: aha_cmd, cmd/data port full\n", unit); + outb(AHA_CTRL_STAT_PORT, AHA_SRST); + return (ENXIO); + } + outb(AHA_CMD_DATA_PORT, (u_char) (*ic++)); + } + /* + * If we expect input, loop that many times, each time, + * looking for the data register to have valid data + */ + while (ocnt--) { + sts = inb(AHA_CTRL_STAT_PORT); + for (i = wait; i; i--) { + sts = inb(AHA_CTRL_STAT_PORT); + if (sts & AHA_DF) + break; + DELAY(50); + } + if (i == 0) { + printf("aha%d: aha_cmd, cmd/data port empty %d\n", + unit, ocnt); + return (ENXIO); + } + oc = inb(AHA_CMD_DATA_PORT); + if (retval) + *retval++ = oc; + } + /* + * Wait for the board to report a finised instruction + */ + i = 20000; + while (--i) { + sts = inb(AHA_INTR_PORT); + if (sts & AHA_HACC) { + break; + } + DELAY(50); + } + if (i == 0) { + printf("aha%d: aha_cmd, host not finished(0x%x)\n", unit, sts); + return (ENXIO); + } + outb(AHA_CTRL_STAT_PORT, AHA_IRST); + return 0; +} + +/* + * Check if the device can be found at the port given + * and if so, set it up ready for further work + * as an argument, takes the isa_device structure from + * autoconf.c + */ +int +ahaprobe(dev) + struct isa_device *dev; +{ + int unit = ahaunit; + struct aha_data *aha; + + /* + * find unit and check we have that many defined + */ + if (unit >= NAHA) { + printf("aha%d: unit number too high\n", unit); + return 0; + } + dev->id_unit = unit; + + /* + * a quick safety check so we can be sleazy later + */ + if (sizeof(struct aha_data) > PAGESIZ) { + printf("aha struct > pagesize\n"); + return 0; + } + /* + * Allocate a storage area for us + */ + if (ahadata[unit]) { + printf("aha%d: memory already allocated\n", unit); + return 0; + } + aha = malloc(sizeof(struct aha_data), M_TEMP, M_NOWAIT); + if (!aha) { + printf("aha%d: cannot malloc!\n", unit); + return 0; + } + bzero(aha, sizeof(struct aha_data)); + ahadata[unit] = aha; + aha->aha_base = dev->id_iobase; + /* + * Try initialise a unit at this location + * sets up dma and bus speed, loads aha->aha_int + */ + if (aha_init(unit) != 0) { + ahadata[unit] = NULL; + free(aha, M_TEMP); + return 0; + } + /* + * Calculate the xor product of the aha struct's + * physical and virtual address. This allows us + * to change addresses within the structure + * from physical to virtual easily, as long as + * the structure is less than 1 page in size. + * This is used to recognise CCBs which are in + * this struct and which are refered to by the + * hardware using physical addresses. + * (assumes malloc returns a chunk that doesn't + * span pages) + * eventually use the hash table in aha1742.c + */ + aha->kv_phys_xor = (long int) aha ^ (KVTOPHYS(aha)); + + /* + * If it's there, put in it's interrupt vectors + */ + dev->id_irq = (1 << aha->aha_int); + dev->id_drq = aha->aha_dma; + ahaunit++; + return 0x4; +} + +/* + * Attach all the sub-devices we can find + */ +int +ahaattach(dev) + struct isa_device *dev; +{ + int unit = dev->id_unit; + struct aha_data *aha = ahadata[unit]; + + /* + * fill in the prototype scsi_link. + */ + aha->sc_link.adapter_unit = unit; + aha->sc_link.adapter_targ = aha->aha_scsi_dev; + aha->sc_link.adapter = &aha_switch; + aha->sc_link.device = &aha_dev; + aha->sc_link.flags = SDEV_BOUNCE; + + /* + * ask the adapter what subunits are present + */ + scsi_attachdevs(&(aha->sc_link)); + + return 1; +} + +/* + * Return some information to the caller about the adapter and its + * capabilities. + */ +u_int32 +aha_adapter_info(unit) + int unit; +{ + return (2); /* 2 outstanding requests at a time per device */ +} + +/* + * Catch an interrupt from the adaptor + */ +int +ahaintr(unit) + int unit; +{ + struct aha_ccb *ccb; + unsigned char stat; + register i; + struct aha_data *aha = ahadata[unit]; + +#ifdef AHADEBUG + printf("ahaintr "); +#endif /*AHADEBUG */ + /* + * First acknowlege the interrupt, Then if it's not telling about + * a completed operation just return. + */ + stat = inb(AHA_INTR_PORT); + outb(AHA_CTRL_STAT_PORT, AHA_IRST); + if (!(stat & AHA_MBIF)) + return 1; +#ifdef AHADEBUG + printf("mbxin "); +#endif /*AHADEBUG */ + /* + * If it IS then process the competed operation + */ + for (i = 0; i < AHA_MBX_SIZE; i++) { + if (aha->aha_mbx.mbi[i].stat != AHA_MBI_FREE) { + ccb = (struct aha_ccb *) PHYSTOKV( + (_3btol(aha->aha_mbx.mbi[i].ccb_addr))); + + if ((stat = aha->aha_mbx.mbi[i].stat) != AHA_MBI_OK) { + switch (stat) { + case AHA_MBI_ABORT: +#ifdef AHADEBUG + if (aha_debug) + printf("abort"); +#endif /*AHADEBUG */ + ccb->host_stat = AHA_ABORTED; + break; + + case AHA_MBI_UNKNOWN: + ccb = (struct aha_ccb *) 0; +#ifdef AHADEBUG + if (aha_debug) + printf("unknown ccb for abort "); +#endif /*AHADEBUG */ + /* may have missed it */ + /* no such ccb known for abort */ + + case AHA_MBI_ERROR: + break; + + default: + panic("Impossible mbxi status"); + + } +#ifdef AHADEBUG + if (aha_debug && ccb) { + u_char *cp; + cp = (u_char *) (&(ccb->scsi_cmd)); + printf("op=%x %x %x %x %x %x\n", + cp[0], cp[1], cp[2], + cp[3], cp[4], cp[5]); + printf("stat %x for mbi[%d]\n" + ,aha->aha_mbx.mbi[i].stat, i); + printf("addr = 0x%x\n", ccb); + } +#endif /*AHADEBUG */ + } + if (ccb) { + untimeout(aha_timeout, (caddr_t)ccb); + aha_done(unit, ccb); + } + aha->aha_mbx.mbi[i].stat = AHA_MBI_FREE; + } + } + return 1; +} + +/* + * A ccb (and hence a mbx-out is put onto the + * free list. + */ +void +aha_free_ccb(unit, ccb, flags) + int unit; + struct aha_ccb *ccb; + int flags; +{ + struct aha_data *aha = ahadata[unit]; + unsigned int opri = 0; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + + ccb->next = aha->aha_ccb_free; + aha->aha_ccb_free = ccb; + ccb->flags = CCB_FREE; + /* + * If there were none, wake anybody waiting for + * one to come free, starting with queued entries + */ + if (!ccb->next) { + wakeup((caddr_t)&aha->aha_ccb_free); + } + if (!(flags & SCSI_NOMASK)) + splx(opri); +} + +/* + * Get a free ccb (and hence mbox-out entry) + */ +struct aha_ccb * +aha_get_ccb(unit, flags) + int unit; + int flags; +{ + struct aha_data *aha = ahadata[unit]; + unsigned opri = 0; + struct aha_ccb *rc; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + /* + * If we can and have to, sleep waiting for one + * to come free + */ + while ((!(rc = aha->aha_ccb_free)) && (!(flags & SCSI_NOSLEEP))) { + tsleep((caddr_t)&aha->aha_ccb_free, PRIBIO, "ahaccb", 0); + } + if (rc) { + aha->aha_ccb_free = aha->aha_ccb_free->next; + rc->flags = CCB_ACTIVE; + } + if (!(flags & SCSI_NOMASK)) + splx(opri); + return (rc); +} + +/* + * We have a ccb which has been processed by the + * adaptor, now we look to see how the operation + * went. Wake up the owner if waiting + */ +void +aha_done(unit, ccb) + int unit; + struct aha_ccb *ccb; +{ + struct aha_data *aha = ahadata[unit]; + struct scsi_sense_data *s1, *s2; + struct scsi_xfer *xs = ccb->xfer; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("aha_done\n")); + /* + * Otherwise, put the results of the operation + * into the xfer and call whoever started it + */ + if (!(xs->flags & INUSE)) { + printf("aha%d: exiting but not in use!\n", unit); + Debugger("aha1542"); + } + if (((ccb->host_stat != AHA_OK) || (ccb->target_stat != SCSI_OK)) + && ((xs->flags & SCSI_ERR_OK) == 0)) { + /* + * We have an error, that we cannot ignore. + */ + s1 = (struct scsi_sense_data *) (((char *) (&ccb->scsi_cmd)) + + ccb->scsi_cmd_length); + s2 = &(xs->sense); + + if (ccb->host_stat) { + SC_DEBUG(xs->sc_link, SDEV_DB3, ("host err 0x%x\n", + ccb->host_stat)); + switch (ccb->host_stat) { + case AHA_ABORTED: + case AHA_SEL_TIMEOUT: /* No response */ + xs->error = XS_TIMEOUT; + break; + default: /* Other scsi protocol messes */ + xs->error = XS_DRIVER_STUFFUP; + printf("aha%d:host_stat%x\n", + unit, ccb->host_stat); + } + } else { + SC_DEBUG(xs->sc_link, SDEV_DB3, ("target err 0x%x\n", + ccb->target_stat)); + switch (ccb->target_stat) { + case 0x02: + /* structure copy!!!!! */ + *s2 = *s1; + xs->error = XS_SENSE; + break; + case 0x08: + xs->error = XS_BUSY; + break; + default: + printf("aha%d:target_stat%x\n", + unit, ccb->target_stat); + xs->error = XS_DRIVER_STUFFUP; + } + } + } else { + /* All went correctly OR errors expected */ + xs->resid = 0; + } + xs->flags |= ITSDONE; + aha_free_ccb(unit, ccb, xs->flags); + scsi_done(xs); +} + +/* + * Start the board, ready for normal operation + */ +int +aha_init(unit) + int unit; +{ + struct aha_data *aha = ahadata[unit]; + unsigned char ad[3]; + volatile int i, sts; + struct aha_config conf; + struct aha_inquire inquire; + struct aha_extbios extbios; + + /* + * reset board, If it doesn't respond, assume + * that it's not there.. good for the probe + */ + + outb(AHA_CTRL_STAT_PORT, AHA_HRST | AHA_SRST); + + for (i = AHA_RESET_TIMEOUT; i; i--) { + sts = inb(AHA_CTRL_STAT_PORT); + if (sts == (AHA_IDLE | AHA_INIT)) { + break; + } + DELAY(1000); /* calibrated in msec */ + } +#ifdef AHADEBUG + printf("aha_init: AHA_RESET_TIMEOUT went to %d\n", i); +#endif /* AHADEBUG */ + if (i == 0) { +#ifdef AHADEBUG + if (aha_debug) + printf("aha_init: No answer from board\n"); +#endif /*AHADEBUG */ + return (ENXIO); + } + + /* + * Assume we have a board at this stage, do an adapter inquire + * to find out what type of controller it is. If the AHA_INQUIRE + * command fails, blatter about it, nuke the boardid so the 1542C + * stuff gets skipped over, and reset the board again. + */ + if(aha_cmd(unit, 0, sizeof(inquire), 1 ,&inquire, AHA_INQUIRE)) { + /* + * Blah.. not a real adaptec board!!! + * Seems that the Buslogic 545S and the DTC3290 both get + * this wrong. + */ + printf ("aha%d: not a REAL adaptec board, may cause warnings\n", + unit); + inquire.boardid = 0; + outb(AHA_CTRL_STAT_PORT, AHA_HRST | AHA_SRST); + for (i = AHA_RESET_TIMEOUT; i; i--) { + sts = inb(AHA_CTRL_STAT_PORT); + if (sts == (AHA_IDLE | AHA_INIT)) { + break; + } + DELAY(1000); /* calibrated in msec */ + } +#ifdef AHADEBUG + printf("aha_init2: AHA_RESET_TIMEOUT went to %d\n", i); +#endif /* AHADEBUG */ + if (i == 0) { +#ifdef AHADEBUG + if (aha_debug) + printf("aha_init2: No answer from board\n"); +#endif /*AHADEBUG */ + return (ENXIO); + } + } +#ifdef AHADEBUG + printf("aha%d: inquire %x, %x, %x, %x\n", + unit, + inquire.boardid, inquire.spec_opts, + inquire.revision_1, inquire.revision_2); +#endif /* AHADEBUG */ + + /* + * If we are a 1542C or 1542CF disable the extended bios so that the + * mailbox interface is unlocked. + * This is also true for the 1542B Version 3.20. First Adaptec + * board that supports >1Gb drives. + * No need to check the extended bios flags as some of the + * extensions that cause us problems are not flagged in that byte. + */ + if ((inquire.boardid == 0x43) || (inquire.boardid == 0x44) || + (inquire.boardid == 0x45) || (inquire.boardid == 0x41 + && inquire.revision_1 == 0x31 && inquire.revision_2 == 0x34)) { + aha_cmd(unit, 0, sizeof(extbios), 0, &extbios, AHA_EXT_BIOS); +#ifdef AHADEBUG + printf("aha%d: extended bios flags %x\n", unit, extbios.flags); +#endif /* AHADEBUG */ + printf("aha%d: 1542C/CF detected, unlocking mailbox\n", unit); + aha_cmd(unit, 2, 0, 0, 0, AHA_MBX_ENABLE, + 0, extbios.mailboxlock); + } + + /* + * setup dma channel from jumpers and save int + * level + */ + printf("aha%d: reading board settings, ", unit); +#define PRNT(x) printf(x) + DELAY(1000); /* for Bustek 545 */ + aha_cmd(unit, 0, sizeof(conf), 0, &conf, AHA_CONF_GET); + switch (conf.chan) { + case CHAN0: + outb(0x0b, 0x0c); + outb(0x0a, 0x00); + aha->aha_dma = 0; + PRNT("dma=0 "); + break; + case CHAN5: + outb(0xd6, 0xc1); + outb(0xd4, 0x01); + aha->aha_dma = 5; + PRNT("dma=5 "); + break; + case CHAN6: + outb(0xd6, 0xc2); + outb(0xd4, 0x02); + aha->aha_dma = 6; + PRNT("dma=6 "); + break; + case CHAN7: + outb(0xd6, 0xc3); + outb(0xd4, 0x03); + aha->aha_dma = 7; + PRNT("dma=7 "); + break; + default: + printf("illegal dma jumper setting\n"); + return (EIO); + } + switch (conf.intr) { + case INT9: + aha->aha_int = 9; + PRNT("int=9 "); + break; + case INT10: + aha->aha_int = 10; + PRNT("int=10 "); + break; + case INT11: + aha->aha_int = 11; + PRNT("int=11 "); + break; + case INT12: + aha->aha_int = 12; + PRNT("int=12 "); + break; + case INT14: + aha->aha_int = 14; + PRNT("int=14 "); + break; + case INT15: + aha->aha_int = 15; + PRNT("int=15 "); + break; + default: + printf("illegal int jumper setting\n"); + return (EIO); + } + + /* who are we on the scsi bus? */ + aha->aha_scsi_dev = conf.scsi_dev; + + /* + * Change the bus on/off times to not clash with other dma users. + */ + aha_cmd(unit, 1, 0, 0, 0, AHA_BUS_ON_TIME_SET, 7); + aha_cmd(unit, 1, 0, 0, 0, AHA_BUS_OFF_TIME_SET, 4); + +#ifdef TUNE_1542 + /* + * Initialize memory transfer speed + * Not compiled in by default because it breaks some machines + */ + if (!(aha_set_bus_speed(unit))) { + return (EIO); + } +#else + printf (" (bus speed defaulted)\n"); +#endif /*TUNE_1542*/ + /* + * Initialize mail box + */ + lto3b(KVTOPHYS(&aha->aha_mbx), ad); + + aha_cmd(unit, 4, 0, 0, 0, AHA_MBX_INIT, + AHA_MBX_SIZE, + ad[0], + ad[1], + ad[2]); + + /* + * link the ccb's with the mbox-out entries and + * into a free-list + * this is a kludge but it works + */ + for (i = 0; i < AHA_MBX_SIZE; i++) { + aha->aha_ccb[i].next = aha->aha_ccb_free; + aha->aha_ccb_free = &aha->aha_ccb[i]; + aha->aha_ccb_free->flags = CCB_FREE; + aha->aha_ccb_free->mbx = &aha->aha_mbx.mbo[i]; + lto3b(KVTOPHYS(aha->aha_ccb_free), aha->aha_mbx.mbo[i].ccb_addr); + } + /* + * Note that we are going and return (to probe) + */ + return 0; +} + +void +ahaminphys(bp) + struct buf *bp; +{ +/* aha seems to explode with 17 segs (64k may require 17 segs) */ +/* on old boards so use a max of 16 segs if you have problems here */ + if (bp->b_bcount > ((AHA_NSEG - 1) * PAGESIZ)) { + bp->b_bcount = ((AHA_NSEG - 1) * PAGESIZ); + } +} + +/* + * start a scsi operation given the command and + * the data address. Also needs the unit, target + * and lu + */ +int32 +aha_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_link *sc_link = xs->sc_link; + int unit = sc_link->adapter_unit; + struct aha_data *aha = ahadata[unit]; + struct scsi_sense_data *s1, *s2; + struct aha_ccb *ccb; + struct aha_scat_gath *sg; + int seg; /* scatter gather seg being worked on */ + int i = 0; + int rc = 0; + int thiskv; + int thisphys, nextphys; + int bytes_this_seg, bytes_this_page, datalen, flags; + struct iovec *iovp; + int s; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("aha_scsi_cmd\n")); + /* + * get a ccb (mbox-out) to use. If the transfer + * is from a buf (possibly from interrupt time) + * then we can't allow it to sleep + */ + flags = xs->flags; + if (!(ccb = aha_get_ccb(unit, flags))) { + xs->error = XS_DRIVER_STUFFUP; + return (TRY_AGAIN_LATER); + } + if (ccb->mbx->cmd != AHA_MBO_FREE) + printf("aha%d: MBO not free\n", unit); + + /* + * Put all the arguments for the xfer in the ccb + */ + ccb->xfer = xs; + if (flags & SCSI_RESET) { + ccb->opcode = AHA_RESET_CCB; + } else { + /* can't use S/G if zero length */ + ccb->opcode = (xs->datalen ? + AHA_INIT_SCAT_GATH_CCB + : AHA_INITIATOR_CCB); + } + ccb->target = sc_link->target; + ccb->data_out = 0; + ccb->data_in = 0; + ccb->lun = sc_link->lun; + ccb->scsi_cmd_length = xs->cmdlen; + ccb->req_sense_length = sizeof(ccb->scsi_sense); + + if ((xs->datalen) && (!(flags & SCSI_RESET))) { + /* can use S/G only if not zero length */ + lto3b(KVTOPHYS(ccb->scat_gath), ccb->data_addr); + sg = ccb->scat_gath; + seg = 0; +#ifdef TFS_ONLY + if (flags & SCSI_DATA_UIO) { + iovp = ((struct uio *) xs->data)->uio_iov; + datalen = ((struct uio *) xs->data)->uio_iovcnt; + while ((datalen) && (seg < AHA_NSEG)) { + lto3b(iovp->iov_base, sg->seg_addr); + lto3b(iovp->iov_len, sg->seg_len); + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("UIO(0x%x@0x%x)" + ,iovp->iov_len + ,iovp->iov_base)); + sg++; + iovp++; + seg++; + datalen--; + } + } else +#endif /*TFS_ONLY */ + { + /* + * Set up the scatter gather block + */ + + SC_DEBUG(xs->sc_link, SDEV_DB4, + ("%d @0x%x:- ", xs->datalen, xs->data)); + datalen = xs->datalen; + thiskv = (int) xs->data; + thisphys = KVTOPHYS(thiskv); + + while ((datalen) && (seg < AHA_NSEG)) { + bytes_this_seg = 0; + + /* put in the base address */ + lto3b(thisphys, sg->seg_addr); + + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("0x%x", thisphys)); + + /* do it at least once */ + nextphys = thisphys; + while ((datalen) && (thisphys == nextphys)) { + /* + * This page is contiguous (physically) + * with the the last, just extend the + * length + */ + /* check it fits on the ISA bus */ + if (thisphys > 0xFFFFFF) + { + printf("aha%d: DMA beyond" + " end Of ISA\n", unit); + xs->error = XS_DRIVER_STUFFUP; + aha_free_ccb(unit, ccb, flags); + return (HAD_ERROR); + } + /** how far to the end of the page ***/ + nextphys = (thisphys & (~(PAGESIZ - 1))) + + PAGESIZ; + bytes_this_page = nextphys - thisphys; + /**** or the data ****/ + bytes_this_page = min(bytes_this_page + ,datalen); + bytes_this_seg += bytes_this_page; + datalen -= bytes_this_page; + + /**** get more ready for the next page ****/ + thiskv = (thiskv & (~(PAGESIZ - 1))) + + PAGESIZ; + if (datalen) + thisphys = KVTOPHYS(thiskv); + } + /* + * next page isn't contiguous, finish the seg + */ + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("(0x%x)", bytes_this_seg)); + lto3b(bytes_this_seg, sg->seg_len); + sg++; + seg++; + } + } + lto3b(seg * sizeof(struct aha_scat_gath), ccb->data_length); + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("\n")); + + if (datalen) { /* there's still data, must have run out of segs! */ + printf("aha%d: aha_scsi_cmd, more than %d DMA segs\n", + unit, AHA_NSEG); + xs->error = XS_DRIVER_STUFFUP; + aha_free_ccb(unit, ccb, flags); + return (HAD_ERROR); + } + } else { /* No data xfer, use non S/G values */ + lto3b(0, ccb->data_addr); + lto3b(0, ccb->data_length); + } + lto3b(0, ccb->link_addr); + /* + * Put the scsi command in the ccb and start it + */ + if (!(flags & SCSI_RESET)) + bcopy(xs->cmd, &ccb->scsi_cmd, ccb->scsi_cmd_length); + if (!(flags & SCSI_NOMASK)) { + s = splbio(); /* stop instant timeouts */ + timeout(aha_timeout, (caddr_t)ccb, (xs->timeout * hz) / 1000); + aha_startmbx(ccb->mbx); + /* + * Usually return SUCCESSFULLY QUEUED + */ + splx(s); + SC_DEBUG(xs->sc_link, SDEV_DB3, ("sent\n")); + return (SUCCESSFULLY_QUEUED); + } + aha_startmbx(ccb->mbx); + SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd sent, waiting\n")); + + /* + * If we can't use interrupts, poll on completion + */ + return (aha_poll(unit, xs, ccb)); /* only during boot */ +} + +/* + * Poll a particular unit, looking for a particular xs + */ +int +aha_poll(unit, xs, ccb) + int unit; + struct scsi_xfer *xs; + struct aha_ccb *ccb; +{ + struct aha_data *aha = ahadata[unit]; + int done = 0; + int count = xs->timeout; + u_char stat; + + /*timeouts are in msec, so we loop in 1000uSec cycles */ + while (count) { + /* + * If we had interrupts enabled, would we + * have got an interrupt? + */ + stat = inb(AHA_INTR_PORT); + if (stat & AHA_ANY_INTR) { + ahaintr(unit); + } + if (xs->flags & ITSDONE) { + break; + } + DELAY(1000); /* only happens in boot so ok */ + count--; + } + if (count == 0) { + /* + * We timed out, so call the timeout handler + * manually, accout for the fact that the + * clock is not running yet by taking out the + * clock queue entry it makes + */ + aha_timeout((caddr_t)ccb); + + /* + * because we are polling, + * take out the timeout entry aha_timeout made + */ + untimeout(aha_timeout, (caddr_t)ccb); + count = 2000; + while (count) { + /* + * Once again, wait for the int bit + */ + stat = inb(AHA_INTR_PORT); + if (stat & AHA_ANY_INTR) { + ahaintr(unit); + } + if (xs->flags & ITSDONE) { + break; + } + DELAY(1000); /* only happens in boot so ok */ + count--; + } + if (count == 0) { + /* + * We timed out again.. this is bad + * Notice that this time there is no + * clock queue entry to remove + */ + aha_timeout((caddr_t)ccb); + } + } + if (xs->error) + return (HAD_ERROR); + return (COMPLETE); + +} + +#ifdef TUNE_1542 +/* + * Try all the speeds from slowest to fastest.. if it finds a + * speed that fails, back off one notch from the last working + * speed (unless there is no other notch). + * Returns the nSEC value of the time used + * or 0 if it could get a working speed (or the NEXT speed + * failed) + */ +static struct bus_speed +{ + char arg; + int nsecs; +}aha_bus_speeds[] = +{ + {0x88,100}, + {0x99,150}, + {0xaa,200}, + {0xbb,250}, + {0xcc,300}, + {0xdd,350}, + {0xee,400}, + {0xff,450} +}; + +int +aha_set_bus_speed(unit) + int unit; +{ + int speed; + int lastworking; + int retval,retval2; + struct aha_data *aha = ahadata[unit]; + + lastworking = -1; + speed = 7; + while (1) { + retval = aha_bus_speed_check(unit,speed); + if(retval != 0) { + lastworking = speed; + } + if((retval == 0) || (speed == 0)) { + if(lastworking == -1) { + printf("No working bus speed for aha154X\n"); + return 0; + } + printf("%d nSEC ok, using " + ,aha_bus_speeds[lastworking].nsecs); + if(lastworking == 7) { /* is slowest already */ + printf("marginal "); + } else { + lastworking++; + } + retval2 = aha_bus_speed_check(unit,lastworking); + if(retval2 == 0) { + printf("test retry failed.. aborting.\n"); + return 0; + } + printf("%d nSEC\n",retval2); + return retval2 ; + + } + speed--; + } +} + +/* + * Set the DMA speed to the Nth speed and try an xfer. If it + * fails return 0, if it succeeds return the nSec value selected + * If there is no such speed return HAD_ERROR. + */ +static char aha_test_string[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz!@"; + +int +aha_bus_speed_check(unit, speed) + int unit, speed; +{ + int numspeeds = sizeof(aha_bus_speeds) / sizeof(struct bus_speed); + int loopcount; + u_char ad[3]; + struct aha_data *aha = ahadata[unit]; + + /* + * Check we have such an entry + */ + if (speed >= numspeeds) + return (HAD_ERROR); /* illegal speed */ + + /* + * Set the dma-speed + */ + aha_cmd(unit, 1, 0, 0, 0, AHA_SPEED_SET, aha_bus_speeds[speed].arg); + + /* + * put the test data into the buffer and calculate + * it's address. Read it onto the board + */ + lto3b(KVTOPHYS(aha_scratch_buf), ad); + for(loopcount = 2000;loopcount;loopcount--) + { + strcpy(aha_scratch_buf, aha_test_string); + + aha_cmd(unit, 3, 0, 0, 0, AHA_WRITE_FIFO, ad[0], ad[1], ad[2]); + + /* + * clear the buffer then copy the contents back from the + * board. + */ + bzero(aha_scratch_buf, 54); /* 54 bytes transfered by test */ + + aha_cmd(unit, 3, 0, 0, 0, AHA_READ_FIFO, ad[0], ad[1], ad[2]); + + /* + * Compare the original data and the final data and + * return the correct value depending upon the result + */ + if (strcmp(aha_test_string, aha_scratch_buf)) + return 0; /* failed test */ + } + /* copy succeded assume speed ok */ + + return (aha_bus_speeds[speed].nsecs); + +} +#endif /*TUNE_1542*/ + +void +aha_timeout(void *arg1) +{ + struct aha_ccb * ccb = (struct aha_ccb *)arg1; + int unit; + int s = splbio(); + struct aha_data *aha; + + unit = ccb->xfer->sc_link->adapter_unit; + aha = ahadata[unit]; + sc_print_addr(ccb->xfer->sc_link); + printf("timed out "); + + /* + * If The ccb's mbx is not free, then + * the board has gone south + */ + if (ccb->mbx->cmd != AHA_MBO_FREE) { + printf("\nadapter not taking commands.. frozen?!\n"); + Debugger("aha1542"); + } + /* + * If it has been through before, then + * a previous abort has failed, don't + * try abort again + */ + if (ccb->flags == CCB_ABORTED) { + /* abort timed out */ + printf(" AGAIN\n"); + ccb->xfer->retries = 0; /* I MEAN IT ! */ + ccb->host_stat = AHA_ABORTED; + aha_done(unit, ccb); + } else { + /* abort the operation that has timed out */ + printf("\n"); + aha_abortmbx(ccb->mbx); + /* 4 secs for the abort */ + timeout(aha_timeout, (caddr_t)ccb, 4 * hz); + ccb->flags = CCB_ABORTED; + } splx(s); +} +#endif /* KERNEL */ diff --git a/sys/i386/isa/aha1742.c b/sys/i386/isa/aha1742.c new file mode 100644 index 0000000..1f770e2 --- /dev/null +++ b/sys/i386/isa/aha1742.c @@ -0,0 +1,1240 @@ +/* + * Written by Julian Elischer (julian@tfs.com) + * for TRW Financial Systems for use under the MACH(2.5) operating system. + * + * TRW Financial Systems, in accordance with their agreement with Carnegie + * Mellon University, makes this software available to CMU to distribute + * or use in any manner that they see fit as long as this message is kept with + * the software. For this reason TFS also grants any other persons or + * organisations permission to use or modify this software. + * + * TFS supplies this software to be publicly redistributed + * on the understanding that TFS is not responsible for the correct + * functioning of this software in any circumstances. + * + * commenced: Sun Sep 27 18:14:01 PDT 1992 + * + * $Id: aha1742.c,v 1.18 1994/08/20 03:48:38 davidg Exp $ + */ + +#include <sys/types.h> + +#ifdef KERNEL /* don't laugh, it compiles as a program too.. look */ +#include "ahb.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/user.h> +#include <i386/include/pio.h> +#include <i386/isa/isa_device.h> +#endif /*KERNEL */ +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +/* */ + +#ifdef KERNEL +# ifdef NetBSD +# ifdef DDB +int Debugger(); +# else /* DDB */ +#define Debugger() panic("should call debugger here (adaptec.c)") +# endif /* DDB */ +# else +#include "ddb.h" +#endif /* netbsd */ +#else /* KERNEL */ +#define NAHB 1 +#endif /* kernel */ + +typedef unsigned long int physaddr; +#include <sys/kernel.h> + +#define KVTOPHYS(x) vtophys(x) + +#define AHB_ECB_MAX 32 /* store up to 32ECBs at any one time */ + /* in aha1742 H/W ( Not MAX ? ) */ +#define ECB_HASH_SIZE 32 /* when we have a physical addr. for */ + /* a ecb and need to find the ecb in */ + /* space, look it up in the hash table */ +#define ECB_HASH_SHIFT 9 /* only hash on multiples of 512 */ +#define ECB_HASH(x) ((((long int)(x))>>ECB_HASH_SHIFT) % ECB_HASH_SIZE) + +#define AHB_NSEG 33 /* number of dma segments supported */ + +/* + * AHA1740 standard EISA Host ID regs (Offset from slot base) + */ +#define HID0 0xC80 /* 0,1: msb of ID2, 3-7: ID1 */ +#define HID1 0xC81 /* 0-4: ID3, 4-7: LSB ID2 */ +#define HID2 0xC82 /* product, 0=174[20] 1 = 1744 */ +#define HID3 0xC83 /* firmware revision */ + +#define CHAR1(B1,B2) (((B1>>2) & 0x1F) | '@') +#define CHAR2(B1,B2) (((B1<<3) & 0x18) | ((B2>>5) & 0x7)|'@') +#define CHAR3(B1,B2) ((B2 & 0x1F) | '@') + +/* AHA1740 EISA board control registers (Offset from slot base) */ +#define EBCTRL 0xC84 +#define CDEN 0x01 +/* + * AHA1740 EISA board mode registers (Offset from slot base) + */ +#define PORTADDR 0xCC0 +#define PORTADDR_ENHANCED 0x80 +#define BIOSADDR 0xCC1 +#define INTDEF 0xCC2 +#define SCSIDEF 0xCC3 +#define BUSDEF 0xCC4 +#define RESV0 0xCC5 +#define RESV1 0xCC6 +#define RESV2 0xCC7 +/**** bit definitions for INTDEF ****/ +#define INT9 0x00 +#define INT10 0x01 +#define INT11 0x02 +#define INT12 0x03 +#define INT14 0x05 +#define INT15 0x06 +#define INTHIGH 0x08 /* int high=ACTIVE (else edge) */ +#define INTEN 0x10 +/**** bit definitions for SCSIDEF ****/ +#define HSCSIID 0x0F /* our SCSI ID */ +#define RSTPWR 0x10 /* reset scsi bus on power up or reset */ +/**** bit definitions for BUSDEF ****/ +#define B0uS 0x00 /* give up bus immediatly */ +#define B4uS 0x01 /* delay 4uSec. */ +#define B8uS 0x02 +/* + * AHA1740 ENHANCED mode mailbox control regs (Offset from slot base) + */ +#define MBOXOUT0 0xCD0 +#define MBOXOUT1 0xCD1 +#define MBOXOUT2 0xCD2 +#define MBOXOUT3 0xCD3 + +#define ATTN 0xCD4 +#define G2CNTRL 0xCD5 +#define G2INTST 0xCD6 +#define G2STAT 0xCD7 + +#define MBOXIN0 0xCD8 +#define MBOXIN1 0xCD9 +#define MBOXIN2 0xCDA +#define MBOXIN3 0xCDB + +#define G2STAT2 0xCDC + +/* + * Bit definitions for the 5 control/status registers + */ +#define ATTN_TARGET 0x0F +#define ATTN_OPCODE 0xF0 +#define OP_IMMED 0x10 +#define AHB_TARG_RESET 0x80 +#define OP_START_ECB 0x40 +#define OP_ABORT_ECB 0x50 + +#define G2CNTRL_SET_HOST_READY 0x20 +#define G2CNTRL_CLEAR_EISA_INT 0x40 +#define G2CNTRL_HARD_RESET 0x80 + +#define G2INTST_TARGET 0x0F +#define G2INTST_INT_STAT 0xF0 +#define AHB_ECB_OK 0x10 +#define AHB_ECB_RECOVERED 0x50 +#define AHB_HW_ERR 0x70 +#define AHB_IMMED_OK 0xA0 +#define AHB_ECB_ERR 0xC0 +#define AHB_ASN 0xD0 /* for target mode */ +#define AHB_IMMED_ERR 0xE0 + +#define G2STAT_BUSY 0x01 +#define G2STAT_INT_PEND 0x02 +#define G2STAT_MBOX_EMPTY 0x04 + +#define G2STAT2_HOST_READY 0x01 + +struct ahb_dma_seg { + physaddr addr; + long len; +}; + +struct ahb_ecb_status { + u_short status; +#define ST_DON 0x0001 +#define ST_DU 0x0002 +#define ST_QF 0x0008 +#define ST_SC 0x0010 +#define ST_DO 0x0020 +#define ST_CH 0x0040 +#define ST_INT 0x0080 +#define ST_ASA 0x0100 +#define ST_SNS 0x0200 +#define ST_INI 0x0800 +#define ST_ME 0x1000 +#define ST_ECA 0x4000 + u_char ha_status; +#define HS_OK 0x00 +#define HS_CMD_ABORTED_HOST 0x04 +#define HS_CMD_ABORTED_ADAPTER 0x05 +#define HS_TIMED_OUT 0x11 +#define HS_HARDWARE_ERR 0x20 +#define HS_SCSI_RESET_ADAPTER 0x22 +#define HS_SCSI_RESET_INCOMING 0x23 + u_char targ_status; +#define TS_OK 0x00 +#define TS_CHECK_CONDITION 0x02 +#define TS_BUSY 0x08 + u_long resid_count; + u_long resid_addr; + u_short addit_status; + u_char sense_len; + u_char unused[9]; + u_char cdb[6]; +}; + + +struct ecb { + u_char opcode; +#define ECB_SCSI_OP 0x01 + u_char:4; + u_char options:3; + u_char:1; + short opt1; +#define ECB_CNE 0x0001 +#define ECB_DI 0x0080 +#define ECB_SES 0x0400 +#define ECB_S_G 0x1000 +#define ECB_DSB 0x4000 +#define ECB_ARS 0x8000 + short opt2; +#define ECB_LUN 0x0007 +#define ECB_TAG 0x0008 +#define ECB_TT 0x0030 +#define ECB_ND 0x0040 +#define ECB_DAT 0x0100 +#define ECB_DIR 0x0200 +#define ECB_ST 0x0400 +#define ECB_CHK 0x0800 +#define ECB_REC 0x4000 +#define ECB_NRB 0x8000 + u_short unused1; + physaddr data; + u_long datalen; + physaddr status; + physaddr chain; + short unused2; + short unused3; + physaddr sense; + u_char senselen; + u_char cdblen; + short cksum; + u_char cdb[12]; + /*-----------------end of hardware supported fields----------------*/ + struct ecb *next; /* in free list */ + struct scsi_xfer *xs; /* the scsi_xfer for this cmd */ + int flags; +#define ECB_FREE 0 +#define ECB_ACTIVE 1 +#define ECB_ABORTED 2 +#define ECB_IMMED 4 +#define ECB_IMMED_FAIL 8 + struct ahb_dma_seg ahb_dma[AHB_NSEG]; + struct ahb_ecb_status ecb_status; + struct scsi_sense_data ecb_sense; + struct ecb *nexthash; + physaddr hashkey; /* physaddr of this struct */ +}; + +struct ahb_data { + int flags; +#define AHB_INIT 0x01; + int baseport; + struct ecb *ecbhash[ECB_HASH_SIZE]; + struct ecb *free_ecb; + int our_id; /* our scsi id */ + int vect; + struct ecb *immed_ecb; /* an outstanding immediete command */ + struct scsi_link sc_link; + int numecbs; +} *ahbdata[NAHB]; + +int ahbprobe(); +int ahbprobe1 __P((struct isa_device *dev)); +int ahb_attach(); +int ahb_init __P((int unit)); +int ahbintr(); +int32 ahb_scsi_cmd(); +timeout_t ahb_timeout; +void ahb_done(); +struct ecb *cheat; +void ahb_free_ecb(); +void ahbminphys(); +struct ecb *ahb_ecb_phys_kv(); +u_int32 ahb_adapter_info(); + +#define MAX_SLOTS 8 /* XXX should this be 16?? Need EISA spec */ +static ahb_slot = 0; /* slot last board was found in */ +static ahb_unit = 0; +int ahb_debug = 0; +#define AHB_SHOWECBS 0x01 +#define AHB_SHOWINTS 0x02 +#define AHB_SHOWCMDS 0x04 +#define AHB_SHOWMISC 0x08 +#define FAIL 1 +#define SUCCESS 0 +#define PAGESIZ 4096 + +#ifdef KERNEL +struct isa_driver ahbdriver = +{ + ahbprobe, + ahb_attach, + "ahb" +}; + +struct scsi_adapter ahb_switch = +{ + ahb_scsi_cmd, + ahbminphys, + 0, + 0, + ahb_adapter_info, + "ahb", + { 0, 0 } +}; + +/* the below structure is so we have a default dev struct for our link struct */ +struct scsi_device ahb_dev = +{ + NULL, /* Use default error handler */ + NULL, /* have a queue, served by this */ + NULL, /* have no async handler */ + NULL, /* Use default 'done' routine */ + "ahb", + 0, + { 0, 0 } +}; + +#endif /*KERNEL */ + +#ifndef KERNEL +main() +{ + printf("ahb_data size is %d\n", sizeof(struct ahb_data)); + printf("ecb size is %d\n", sizeof(struct ecb)); +} + +#else /*KERNEL */ + +/* + * Function to send a command out through a mailbox + */ +void +ahb_send_mbox(int unit, int opcode, int target, struct ecb *ecb) +{ + int port = ahbdata[unit]->baseport; + int wait = 300; /* 3ms should be enough */ + int stport = port + G2STAT; + int s = splbio(); + + while (--wait) { + if ((inb(stport) & (G2STAT_BUSY | G2STAT_MBOX_EMPTY)) + == (G2STAT_MBOX_EMPTY)) + break; + DELAY(10); + } + if (wait == 0) { + printf("ahb%d: board not responding\n", unit); + Debugger("aha1742"); + } + outl(port + MBOXOUT0, KVTOPHYS(ecb)); /* don't know this will work */ + outb(port + ATTN, opcode | target); + + splx(s); +} + +/* + * Function to poll for command completion when in poll mode + */ +int +ahb_poll(int unit, int wait) +{ /* in msec */ + struct ahb_data *ahb = ahbdata[unit]; + int port = ahb->baseport; + int stport = port + G2STAT; + + retry: + while (--wait) { + if (inb(stport) & G2STAT_INT_PEND) + break; + DELAY(1000); + } if (wait == 0) { + printf("ahb%d: board not responding\n", unit); + return (EIO); + } + if (cheat != ahb_ecb_phys_kv(ahb, inl(port + MBOXIN0))) { + printf("discarding %x ", inl(port + MBOXIN0)); + outb(port + G2CNTRL, G2CNTRL_CLEAR_EISA_INT); + DELAY(50000); + goto retry; + } + /* don't know this will work */ + ahbintr(unit); + return (0); +} + +/* + * Function to send an immediate type command to the adapter + */ +void +ahb_send_immed(int unit, int target, u_long cmd) +{ + int port = ahbdata[unit]->baseport; + int s = splbio(); + int stport = port + G2STAT; + int wait = 100; /* 1 ms enough? */ + + while (--wait) { + if ((inb(stport) & (G2STAT_BUSY | G2STAT_MBOX_EMPTY)) + == (G2STAT_MBOX_EMPTY)) + break; + DELAY(10); + } if (wait == 0) { + printf("ahb%d: board not responding\n", unit); + Debugger("aha1742"); + } + outl(port + MBOXOUT0, cmd); /* don't know this will work */ + outb(port + G2CNTRL, G2CNTRL_SET_HOST_READY); + outb(port + ATTN, OP_IMMED | target); + splx(s); +} + +/* + * Check the slots looking for a board we recognise + * If we find one, note it's address (slot) and call + * the actual probe routine to check it out. + */ +int +ahbprobe(dev) + struct isa_device *dev; +{ + int port; + u_char byte1, byte2, byte3; + + ahb_slot++; + while (ahb_slot <= MAX_SLOTS) { + port = 0x1000 * ahb_slot; + byte1 = inb(port + HID0); + byte2 = inb(port + HID1); + byte3 = inb(port + HID2); + if (byte1 == 0xff) { + ahb_slot++; + continue; + } + if ((CHAR1(byte1, byte2) == 'A') + && (CHAR2(byte1, byte2) == 'D') + && (CHAR3(byte1, byte2) == 'P') + && ((byte3 == 0) || (byte3 == 1))) { + dev->id_iobase = port; + return ahbprobe1(dev); + } + ahb_slot++; + } + return 0; +} + +/* + * Check if the device can be found at the port given + * and if so, set it up ready for further work + * as an argument, takes the isa_device structure from + * autoconf.c. + */ +int +ahbprobe1(dev) + struct isa_device *dev; +{ + /* + * find unit and check we have that many defined + */ + int unit = ahb_unit; + struct ahb_data *ahb; + + if (unit >= NAHB) { + printf("ahb: unit number (%d) too high\n", unit); + return 0; + } + dev->id_unit = unit; + + /* + * Allocate a storage area for us + */ + if (ahbdata[unit]) { + printf("ahb%d: memory already allocated\n", unit); + return 0; + } + ahb = malloc(sizeof(struct ahb_data), M_TEMP, M_NOWAIT); + if (!ahb) { + printf("ahb%d: cannot malloc!\n", unit); + return 0; + } + bzero(ahb, sizeof(struct ahb_data)); + ahbdata[unit] = ahb; + ahb->baseport = dev->id_iobase; + /* + * Try initialise a unit at this location + * sets up dma and bus speed, loads ahb->vect + */ + if (ahb_init(unit) != 0) { + ahbdata[unit] = NULL; + free(ahb, M_TEMP); + return (0); + } + /* + * If it's there, put in it's interrupt vectors + */ + dev->id_irq = (1 << ahb->vect); + dev->id_drq = -1; /* use EISA dma */ + + ahb_unit++; + return 0x1000; +} + +/* + * Attach all the sub-devices we can find + */ +int +ahb_attach(dev) + struct isa_device *dev; +{ +#ifdef NetBSD + int unit = dev->id_masunit; +#else + int unit = dev->id_unit; +#endif + struct ahb_data *ahb = ahbdata[unit]; + + /* + * fill in the prototype scsi_link. + */ + ahb->sc_link.adapter_unit = unit; + ahb->sc_link.adapter_targ = ahb->our_id; + ahb->sc_link.adapter = &ahb_switch; + ahb->sc_link.device = &ahb_dev; + + /* + * ask the adapter what subunits are present + */ + scsi_attachdevs(&(ahb->sc_link)); + + return 1; +} + +/* + * Return some information to the caller about + * the adapter and it's capabilities + */ +u_int32 +ahb_adapter_info(unit) + int unit; +{ + return (2); /* 2 outstanding requests at a time per device */ +} + +/* + * Catch an interrupt from the adaptor + */ +int +ahbintr(unit) + int unit; +{ + struct ecb *ecb; + unsigned char stat; + u_char ahbstat; + int target; + long int mboxval; + struct ahb_data *ahb = ahbdata[unit]; + + int port = ahb->baseport; + +#ifdef AHBDEBUG + printf("ahbintr "); +#endif /*AHBDEBUG */ + + while (inb(port + G2STAT) & G2STAT_INT_PEND) { + /* + * First get all the information and then + * acknowlege the interrupt + */ + ahbstat = inb(port + G2INTST); + target = ahbstat & G2INTST_TARGET; + stat = ahbstat & G2INTST_INT_STAT; + mboxval = inl(port + MBOXIN0); /* don't know this will work */ + outb(port + G2CNTRL, G2CNTRL_CLEAR_EISA_INT); +#ifdef AHBDEBUG + printf("status = 0x%x ", stat); +#endif /*AHBDEBUG */ + /* + * Process the completed operation + */ + + if (stat == AHB_ECB_OK) { /* common case is fast */ + ecb = ahb_ecb_phys_kv(ahb, mboxval); + } else { + switch (stat) { + case AHB_IMMED_OK: + ecb = ahb->immed_ecb; + ahb->immed_ecb = 0; + break; + case AHB_IMMED_ERR: + ecb = ahb->immed_ecb; + ecb->flags |= ECB_IMMED_FAIL; + ahb->immed_ecb = 0; + break; + case AHB_ASN: /* for target mode */ + printf("ahb%d: Unexpected ASN interrupt(%x)\n", + unit, mboxval); + ecb = 0; + break; + case AHB_HW_ERR: + printf("ahb%d: Hardware error interrupt(%x)\n", + unit, mboxval); + ecb = 0; + break; + case AHB_ECB_RECOVERED: + ecb = ahb_ecb_phys_kv(ahb, mboxval); + break; + case AHB_ECB_ERR: + ecb = ahb_ecb_phys_kv(ahb, mboxval); + break; + default: + printf(" Unknown return from ahb%d(%x)\n", unit, ahbstat); + ecb = 0; + } + } if (ecb) { +#ifdef AHBDEBUG + if (ahb_debug & AHB_SHOWCMDS) { + show_scsi_cmd(ecb->xs); + } + if ((ahb_debug & AHB_SHOWECBS) && ecb) + printf("<int ecb(%x)>", ecb); +#endif /*AHBDEBUG */ + untimeout(ahb_timeout, (caddr_t)ecb); + ahb_done(unit, ecb, ((stat == AHB_ECB_OK) ? SUCCESS : FAIL)); + } + } + return 1; +} + +/* + * We have a ecb which has been processed by the + * adaptor, now we look to see how the operation + * went. + */ +void +ahb_done(unit, ecb, state) + int unit, state; + struct ecb *ecb; +{ + struct ahb_ecb_status *stat = &ecb->ecb_status; + struct scsi_sense_data *s1, *s2; + struct scsi_xfer *xs = ecb->xs; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("ahb_done\n")); + /* + * Otherwise, put the results of the operation + * into the xfer and call whoever started it + */ + if (ecb->flags & ECB_IMMED) { + if (ecb->flags & ECB_IMMED_FAIL) { + xs->error = XS_DRIVER_STUFFUP; + } + goto done; + } + if ((state == SUCCESS) || (xs->flags & SCSI_ERR_OK)) { /* All went correctly OR errors expected */ + xs->resid = 0; + xs->error = 0; + } else { + + s1 = &(ecb->ecb_sense); + s2 = &(xs->sense); + + if (stat->ha_status) { + switch (stat->ha_status) { + case HS_SCSI_RESET_ADAPTER: + break; + case HS_SCSI_RESET_INCOMING: + break; + case HS_CMD_ABORTED_HOST: /* No response */ + case HS_CMD_ABORTED_ADAPTER: /* No response */ + break; + case HS_TIMED_OUT: /* No response */ +#ifdef AHBDEBUG + if (ahb_debug & AHB_SHOWMISC) { + printf("timeout reported back\n"); + } +#endif /*AHBDEBUG */ + xs->error = XS_TIMEOUT; + break; + default: /* Other scsi protocol messes */ + xs->error = XS_DRIVER_STUFFUP; +#ifdef AHBDEBUG + if (ahb_debug & AHB_SHOWMISC) { + printf("unexpected ha_status: %x\n", + stat->ha_status); + } +#endif /*AHBDEBUG */ + } + } else { + switch (stat->targ_status) { + case TS_CHECK_CONDITION: + /* structure copy!!!!! */ + *s2 = *s1; + xs->error = XS_SENSE; + break; + case TS_BUSY: + xs->error = XS_BUSY; + break; + default: +#ifdef AHBDEBUG + if (ahb_debug & AHB_SHOWMISC) { + printf("unexpected targ_status: %x\n", + stat->targ_status); + } +#endif /*AHBDEBUG */ + xs->error = XS_DRIVER_STUFFUP; + } + } + } +done: xs->flags |= ITSDONE; + ahb_free_ecb(unit, ecb, xs->flags); + scsi_done(xs); +} + +/* + * A ecb (and hence a mbx-out is put onto the + * free list. + */ +void +ahb_free_ecb(unit, ecb, flags) + int unit, flags; + struct ecb *ecb; +{ + unsigned int opri = 0; + struct ahb_data *ahb = ahbdata[unit]; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + + ecb->next = ahb->free_ecb; + ahb->free_ecb = ecb; + ecb->flags = ECB_FREE; + /* + * If there were none, wake abybody waiting for + * one to come free, starting with queued entries + */ + if (!ecb->next) { + wakeup((caddr_t)&ahb->free_ecb); + } + if (!(flags & SCSI_NOMASK)) + splx(opri); +} + +/* + * Get a free ecb + * If there are none, see if we can allocate a + * new one. If so, put it in the hash table too + * otherwise either return an error or sleep + */ +struct ecb * +ahb_get_ecb(unit, flags) + int unit, flags; +{ + struct ahb_data *ahb = ahbdata[unit]; + unsigned opri = 0; + struct ecb *ecbp; + int hashnum; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + /* + * If we can and have to, sleep waiting for one to come free + * but only if we can't allocate a new one. + */ + while (!(ecbp = ahb->free_ecb)) { + if (ahb->numecbs < AHB_ECB_MAX) { + ecbp = (struct ecb *) malloc(sizeof(struct ecb), + M_TEMP, + M_NOWAIT); + if (ecbp) { + bzero(ecbp, sizeof(struct ecb)); + ahb->numecbs++; + ecbp->flags = ECB_ACTIVE; + /* + * put in the phystokv hash table + * Never gets taken out. + */ + ecbp->hashkey = KVTOPHYS(ecbp); + hashnum = ECB_HASH(ecbp->hashkey); + ecbp->nexthash = ahb->ecbhash[hashnum]; + ahb->ecbhash[hashnum] = ecbp; + } else { + printf("ahb%d: Can't malloc ECB\n", unit); + } goto gottit; + } else { + if (!(flags & SCSI_NOSLEEP)) { + tsleep((caddr_t)&ahb->free_ecb, PRIBIO, + "ahbecb", 0); + } + } + } if (ecbp) { + /* Get ECB from from free list */ + ahb->free_ecb = ecbp->next; + ecbp->flags = ECB_ACTIVE; + } +gottit: if (!(flags & SCSI_NOMASK)) + splx(opri); + + return (ecbp); +} + +/* + * given a physical address, find the ecb that + * it corresponds to: + */ +struct ecb * +ahb_ecb_phys_kv(ahb, ecb_phys) + struct ahb_data *ahb; + physaddr ecb_phys; +{ + int hashnum = ECB_HASH(ecb_phys); + struct ecb *ecbp = ahb->ecbhash[hashnum]; + + while (ecbp) { + if (ecbp->hashkey == ecb_phys) + break; + ecbp = ecbp->nexthash; + } + return ecbp; +} + +/* + * Start the board, ready for normal operation + */ +int +ahb_init(unit) + int unit; +{ + struct ahb_data *ahb = ahbdata[unit]; + int port = ahb->baseport; + int intdef; + int wait = 1000; /* 1 sec enough? */ + int i; + int stport = port + G2STAT; +#define NO_NO 1 +#ifdef NO_NO + /* + * reset board, If it doesn't respond, assume + * that it's not there.. good for the probe + */ + outb(port + EBCTRL, CDEN); /* enable full card */ + outb(port + PORTADDR, PORTADDR_ENHANCED); + + outb(port + G2CNTRL, G2CNTRL_HARD_RESET); + DELAY(1000); + outb(port + G2CNTRL, 0); + DELAY(10000); + while (--wait) { + if ((inb(stport) & G2STAT_BUSY) == 0) + break; + DELAY(1000); + } if (wait == 0) { +#ifdef AHBDEBUG + if (ahb_debug & AHB_SHOWMISC) + printf("ahb_init: No answer from aha1742 board\n"); +#endif /*AHBDEBUG */ + return (ENXIO); + } + i = inb(port + MBOXIN0) & 0xff; + if (i) { + printf("self test failed, val = 0x%x\n", i); + return (EIO); + } +#endif + while (inb(stport) & G2STAT_INT_PEND) { + printf("."); + outb(port + G2CNTRL, G2CNTRL_CLEAR_EISA_INT); + DELAY(10000); + } + outb(port + EBCTRL, CDEN); /* enable full card */ + outb(port + PORTADDR, PORTADDR_ENHANCED); + /* + * Assume we have a board at this stage + * setup dma channel from jumpers and save int + * level + */ + printf("ahb%d: reading board settings, ", unit); + + intdef = inb(port + INTDEF); + switch (intdef & 0x07) { + case INT9: + ahb->vect = 9; + break; + case INT10: + ahb->vect = 10; + break; + case INT11: + ahb->vect = 11; + break; + case INT12: + ahb->vect = 12; + break; + case INT14: + ahb->vect = 14; + break; + case INT15: + ahb->vect = 15; + break; + default: + printf("illegal int setting\n"); + return (EIO); + } + printf("int=%d\n", ahb->vect); + + outb(port + INTDEF, (intdef | INTEN)); /* make sure we can interrupt */ + + /* who are we on the scsi bus? */ + ahb->our_id = (inb(port + SCSIDEF) & HSCSIID); + + /* + * Note that we are going and return (to probe) + */ + ahb->flags |= AHB_INIT; + return (0); +} + +#ifndef min +#define min(x,y) (x < y ? x : y) +#endif /* min */ + +void +ahbminphys(bp) + struct buf *bp; +{ + if (bp->b_bcount > ((AHB_NSEG - 1) * PAGESIZ)) { + bp->b_bcount = ((AHB_NSEG - 1) * PAGESIZ); + } +} + +/* + * start a scsi operation given the command and + * the data address. Also needs the unit, target + * and lu + */ +int32 +ahb_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct ecb *ecb; + struct ahb_dma_seg *sg; + int seg; /* scatter gather seg being worked on */ + int thiskv; + physaddr thisphys, nextphys; + int unit = xs->sc_link->adapter_unit; + int bytes_this_seg, bytes_this_page, datalen, flags; + struct ahb_data *ahb = ahbdata[unit]; + int s; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("ahb_scsi_cmd\n")); + /* + * get a ecb (mbox-out) to use. If the transfer + * is from a buf (possibly from interrupt time) + * then we can't allow it to sleep + */ + flags = xs->flags; + if (xs->bp) + flags |= (SCSI_NOSLEEP); /* just to be sure */ + if (flags & ITSDONE) { + printf("ahb%d: Already done?", unit); + xs->flags &= ~ITSDONE; + } + if (!(flags & INUSE)) { + printf("ahb%d: Not in use?", unit); + xs->flags |= INUSE; + } + if (!(ecb = ahb_get_ecb(unit, flags))) { + xs->error = XS_DRIVER_STUFFUP; + return (TRY_AGAIN_LATER); + } + cheat = ecb; + SC_DEBUG(xs->sc_link, SDEV_DB3, ("start ecb(%x)\n", ecb)); + ecb->xs = xs; + /* + * If it's a reset, we need to do an 'immediate' + * command, and store it's ecb for later + * if there is already an immediate waiting, + * then WE must wait + */ + if (flags & SCSI_RESET) { + ecb->flags |= ECB_IMMED; + if (ahb->immed_ecb) { + return (TRY_AGAIN_LATER); + } + ahb->immed_ecb = ecb; + if (!(flags & SCSI_NOMASK)) { + s = splbio(); + ahb_send_immed(unit, xs->sc_link->target, AHB_TARG_RESET); + timeout(ahb_timeout, (caddr_t)ecb, (xs->timeout * hz) / 1000); + splx(s); + return (SUCCESSFULLY_QUEUED); + } else { + ahb_send_immed(unit, xs->sc_link->target, AHB_TARG_RESET); + /* + * If we can't use interrupts, poll on completion + */ + SC_DEBUG(xs->sc_link, SDEV_DB3, ("wait\n")); + if (ahb_poll(unit, xs->timeout)) { + ahb_free_ecb(unit, ecb, flags); + xs->error = XS_TIMEOUT; + return (HAD_ERROR); + } + return (COMPLETE); + } + } + /* + * Put all the arguments for the xfer in the ecb + */ + ecb->opcode = ECB_SCSI_OP; + ecb->opt1 = ECB_SES | ECB_DSB | ECB_ARS; + if (xs->datalen) { + ecb->opt1 |= ECB_S_G; + } + ecb->opt2 = xs->sc_link->lun | ECB_NRB; + ecb->cdblen = xs->cmdlen; + ecb->sense = KVTOPHYS(&(ecb->ecb_sense)); + ecb->senselen = sizeof(ecb->ecb_sense); + ecb->status = KVTOPHYS(&(ecb->ecb_status)); + + if (xs->datalen) { /* should use S/G only if not zero length */ + ecb->data = KVTOPHYS(ecb->ahb_dma); + sg = ecb->ahb_dma; + seg = 0; +#ifdef TFS + if (flags & SCSI_DATA_UIO) { + iovp = ((struct uio *) xs->data)->uio_iov; + datalen = ((struct uio *) xs->data)->uio_iovcnt; + xs->datalen = 0; + while ((datalen) && (seg < AHB_NSEG)) { + sg->addr = (physaddr) iovp->iov_base; + xs->datalen += sg->len = iovp->iov_len; + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("(0x%x@0x%x)", iovp->iov_len + ,iovp->iov_base)); + sg++; + iovp++; + seg++; + datalen--; + } + } + else +#endif /*TFS */ + { + /* + * Set up the scatter gather block + */ + + SC_DEBUG(xs->sc_link, SDEV_DB4, + ("%d @0x%x:- ", xs->datalen, xs->data)); + datalen = xs->datalen; + thiskv = (int) xs->data; + thisphys = KVTOPHYS(thiskv); + + while ((datalen) && (seg < AHB_NSEG)) { + bytes_this_seg = 0; + + /* put in the base address */ + sg->addr = thisphys; + + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("0x%x", thisphys)); + + /* do it at least once */ + nextphys = thisphys; + while ((datalen) && (thisphys == nextphys)) { + /* + * This page is contiguous (physically) with + * the the last, just extend the length + */ + /* how far to the end of the page */ + nextphys = (thisphys & (~(PAGESIZ - 1))) + + PAGESIZ; + bytes_this_page = nextphys - thisphys; + /**** or the data ****/ + bytes_this_page = min(bytes_this_page + ,datalen); + bytes_this_seg += bytes_this_page; + datalen -= bytes_this_page; + + /* get more ready for the next page */ + thiskv = (thiskv & (~(PAGESIZ - 1))) + + PAGESIZ; + if (datalen) + thisphys = KVTOPHYS(thiskv); + } + /* + * next page isn't contiguous, finish the seg + */ + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("(0x%x)", bytes_this_seg)); + sg->len = bytes_this_seg; + sg++; + seg++; + } + } /*end of iov/kv decision */ + ecb->datalen = seg * sizeof(struct ahb_dma_seg); + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("\n")); + if (datalen) { /* there's still data, must have run out of segs! */ + printf("ahb_scsi_cmd%d: more than %d DMA segs\n", + unit, AHB_NSEG); + xs->error = XS_DRIVER_STUFFUP; + ahb_free_ecb(unit, ecb, flags); + return (HAD_ERROR); + } + } else { /* No data xfer, use non S/G values */ + ecb->data = (physaddr) 0; + ecb->datalen = 0; + } ecb->chain = (physaddr) 0; + /* + * Put the scsi command in the ecb and start it + */ + bcopy(xs->cmd, ecb->cdb, xs->cmdlen); + /* + * Usually return SUCCESSFULLY QUEUED + */ + if (!(flags & SCSI_NOMASK)) { + s = splbio(); + ahb_send_mbox(unit, OP_START_ECB, xs->sc_link->target, ecb); + timeout(ahb_timeout, (caddr_t)ecb, (xs->timeout * hz) / 1000); + splx(s); + SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd_sent\n")); + return (SUCCESSFULLY_QUEUED); + } + /* + * If we can't use interrupts, poll on completion + */ + ahb_send_mbox(unit, OP_START_ECB, xs->sc_link->target, ecb); + SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd_wait\n")); + do { + if (ahb_poll(unit, xs->timeout)) { + if (!(xs->flags & SCSI_SILENT)) + printf("cmd fail\n"); + ahb_send_mbox(unit, OP_ABORT_ECB, xs->sc_link->target, ecb); + if (ahb_poll(unit, 2000)) { + printf("abort failed in wait\n"); + ahb_free_ecb(unit, ecb, flags); + } + xs->error = XS_DRIVER_STUFFUP; + return (HAD_ERROR); + } + } while (!(xs->flags & ITSDONE)); /* something (?) else finished */ + if (xs->error) { + return (HAD_ERROR); + } + return (COMPLETE); +} + +void +ahb_timeout(void *arg1) +{ + struct ecb * ecb = (struct ecb *)arg1; + int unit; + struct ahb_data *ahb; + int s = splbio(); + + unit = ecb->xs->sc_link->adapter_unit; + ahb = ahbdata[unit]; + printf("ahb%d:%d:%d (%s%d) timed out ", unit + ,ecb->xs->sc_link->target + ,ecb->xs->sc_link->lun + ,ecb->xs->sc_link->device->name + ,ecb->xs->sc_link->dev_unit); + +#ifdef AHBDEBUG + if (ahb_debug & AHB_SHOWECBS) + ahb_print_active_ecb(unit); +#endif /*AHBDEBUG */ + + /* + * If it's immediate, don't try abort it + */ + if (ecb->flags & ECB_IMMED) { + ecb->xs->retries = 0; /* I MEAN IT ! */ + ecb->flags |= ECB_IMMED_FAIL; + ahb_done(unit, ecb, FAIL); + splx(s); + return; + } + /* + * If it has been through before, then + * a previous abort has failed, don't + * try abort again + */ + if (ecb->flags == ECB_ABORTED) { + /* + * abort timed out + */ + printf("AGAIN"); + ecb->xs->retries = 0; /* I MEAN IT ! */ + ecb->ecb_status.ha_status = HS_CMD_ABORTED_HOST; + ahb_done(unit, ecb, FAIL); + } else { /* abort the operation that has timed out */ + printf("\n"); + ahb_send_mbox(unit, OP_ABORT_ECB, ecb->xs->sc_link->target, ecb); + /* 2 secs for the abort */ + timeout(ahb_timeout, (caddr_t)ecb, 2 * hz); + ecb->flags = ECB_ABORTED; + } + splx(s); +} + +#ifdef AHBDEBUG +void +ahb_print_ecb(ecb) + struct ecb *ecb; +{ + printf("ecb:%x op:%x cmdlen:%d senlen:%d\n" + ,ecb + ,ecb->opcode + ,ecb->cdblen + ,ecb->senselen); + printf(" datlen:%d hstat:%x tstat:%x flags:%x\n" + ,ecb->datalen + ,ecb->ecb_status.ha_status + ,ecb->ecb_status.targ_status + ,ecb->flags); + show_scsi_cmd(ecb->xs); +} + +void +ahb_print_active_ecb(int unit) +{ + struct ahb_data *ahb = ahbdata[unit]; + struct ecb *ecb; + int i = 0; + + while (i < ECB_HASH_SIZE) { + ecb = ahb->ecbhash[i]; + while (ecb) { + if (ecb->flags != ECB_FREE) { + ahb_print_ecb(ecb); + } + ecb = ecb->nexthash; + } i++; + } +} +#endif /*AHBDEBUG */ +#endif /*KERNEL */ diff --git a/sys/i386/isa/bt742a.c b/sys/i386/isa/bt742a.c new file mode 100644 index 0000000..08e8926 --- /dev/null +++ b/sys/i386/isa/bt742a.c @@ -0,0 +1,1529 @@ +/* + * Written by Julian Elischer (julian@tfs.com) + * for TRW Financial Systems for use under the MACH(2.5) operating system. + * + * TRW Financial Systems, in accordance with their agreement with Carnegie + * Mellon University, makes this software available to CMU to distribute + * or use in any manner that they see fit as long as this message is kept with + * the software. For this reason TFS also grants any other persons or + * organisations permission to use or modify this software. + * + * TFS supplies this software to be publicly redistributed + * on the understanding that TFS is not responsible for the correct + * functioning of this software in any circumstances. + * + * $Id: bt742a.c,v 1.20 1994/08/21 20:16:13 paul Exp $ + */ + +/* + * bt742a SCSI driver + */ + +#include <sys/types.h> + +#ifdef KERNEL /* don't laugh.. it compiles to a program too.. look */ +#include "bt.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/user.h> +#endif /* KERNEL */ + +#include <i386/isa/isa_device.h> +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +#ifdef KERNEL +#include "ddb.h" +#include <sys/kernel.h> +#else /*KERNEL */ +#define NBT 1 +#endif /*KERNEL */ + +typedef unsigned long int physaddr; + +/* + * I/O Port Interface + */ + +#define BT_BASE bt->bt_base +#define BT_CTRL_STAT_PORT (BT_BASE + 0x0) /* control & status */ +#define BT_CMD_DATA_PORT (BT_BASE + 0x1) /* cmds and datas */ +#define BT_INTR_PORT (BT_BASE + 0x2) /* Intr. stat */ + +/* + * BT_CTRL_STAT bits (write) + */ + +#define BT_HRST 0x80 /* Hardware reset */ +#define BT_SRST 0x40 /* Software reset */ +#define BT_IRST 0x20 /* Interrupt reset */ +#define BT_SCRST 0x10 /* SCSI bus reset */ + +/* + * BT_CTRL_STAT bits (read) + */ + +#define BT_STST 0x80 /* Self test in Progress */ +#define BT_DIAGF 0x40 /* Diagnostic Failure */ +#define BT_INIT 0x20 /* Mbx Init required */ +#define BT_IDLE 0x10 /* Host Adapter Idle */ +#define BT_CDF 0x08 /* cmd/data out port full */ +#define BT_DF 0x04 /* Data in port full */ +#define BT_INVDCMD 0x01 /* Invalid command */ + +/* + * BT_CMD_DATA bits (write) + */ + +#define BT_NOP 0x00 /* No operation */ +#define BT_MBX_INIT 0x01 /* Mbx initialization */ +#define BT_START_SCSI 0x02 /* start scsi command */ +#define BT_START_BIOS 0x03 /* start bios command */ +#define BT_INQUIRE 0x04 /* Adapter Inquiry */ +#define BT_MBO_INTR_EN 0x05 /* Enable MBO available interrupt */ +#define BT_SEL_TIMEOUT_SET 0x06 /* set selection time-out */ +#define BT_BUS_ON_TIME_SET 0x07 /* set bus-on time */ +#define BT_BUS_OFF_TIME_SET 0x08 /* set bus-off time */ +#define BT_SPEED_SET 0x09 /* set transfer speed */ +#define BT_DEV_GET 0x0a /* return installed devices */ +#define BT_CONF_GET 0x0b /* return configuration data */ +#define BT_TARGET_EN 0x0c /* enable target mode */ +#define BT_SETUP_GET 0x0d /* return setup data */ +#define BT_WRITE_CH2 0x1a /* write channel 2 buffer */ +#define BT_READ_CH2 0x1b /* read channel 2 buffer */ +#define BT_WRITE_FIFO 0x1c /* write fifo buffer */ +#define BT_READ_FIFO 0x1d /* read fifo buffer */ +#define BT_ECHO 0x1e /* Echo command data */ +#define BT_MBX_INIT_EXTENDED 0x81 /* Mbx initialization */ +#define BT_INQUIRE_EXTENDED 0x8D /* Adapter Setup Inquiry */ + +/* Follows command appeared at FirmWare 3.31 */ +#define BT_ROUND_ROBIN 0x8f /* Enable/Disable(default) round robin */ +#define BT_DISABLE 0x00 /* Parameter value for Disable */ +#define BT_ENABLE 0x01 /* Parameter value for Enable */ + +struct bt_cmd_buf { + u_char byte[16]; +}; + +/* + * BT_INTR_PORT bits (read) + */ + +#define BT_ANY_INTR 0x80 /* Any interrupt */ +#define BT_SCRD 0x08 /* SCSI reset detected */ +#define BT_HACC 0x04 /* Command complete */ +#define BT_MBOA 0x02 /* MBX out empty */ +#define BT_MBIF 0x01 /* MBX in full */ + +/* + * Mail box defs etc. + * these could be bigger but we need the bt_data to fit on a single page.. + */ + +#define BT_MBX_SIZE 16 /* mail box size (MAX 255 MBxs) */ + /* don't need that many really */ +#define BT_CCB_MAX 32 /* store up to 32CCBs at any one time */ + /* in bt742a H/W ( Not MAX ? ) */ +#define CCB_HASH_SIZE 32 /* when we have a physical addr. for */ + /* a ccb and need to find the ccb in */ + /* space, look it up in the hash table */ +#define CCB_HASH_SHIFT 9 /* only hash on multiples of 512 */ +#define CCB_HASH(x) ((((long int)(x))>>CCB_HASH_SHIFT) % CCB_HASH_SIZE) + +#define bt_nextmbx( wmb, mbx, mbio ) \ + if ( (wmb) == &((mbx)->mbio[BT_MBX_SIZE - 1 ]) ) \ + (wmb) = &((mbx)->mbio[0]); \ + else \ + (wmb)++; + +typedef struct bt_mbx_out { + physaddr ccb_addr; + unsigned char dummy[3]; + unsigned char cmd; +} BT_MBO; + +typedef struct bt_mbx_in { + physaddr ccb_addr; + unsigned char btstat; + unsigned char sdstat; + unsigned char dummy; + unsigned char stat; +} BT_MBI; + +struct bt_mbx { + BT_MBO mbo[BT_MBX_SIZE]; + BT_MBI mbi[BT_MBX_SIZE]; + BT_MBO *tmbo; /* Target Mail Box out */ + BT_MBI *tmbi; /* Target Mail Box in */ +}; + +/* + * mbo.cmd values + */ + +#define BT_MBO_FREE 0x0 /* MBO entry is free */ +#define BT_MBO_START 0x1 /* MBO activate entry */ +#define BT_MBO_ABORT 0x2 /* MBO abort entry */ + +/* + * mbi.stat values + */ + +#define BT_MBI_FREE 0x0 /* MBI entry is free */ +#define BT_MBI_OK 0x1 /* completed without error */ +#define BT_MBI_ABORT 0x2 /* aborted ccb */ +#define BT_MBI_UNKNOWN 0x3 /* Tried to abort invalid CCB */ +#define BT_MBI_ERROR 0x4 /* Completed with error */ + +#if defined(BIG_DMA) +WARNING...THIS WON'T WORK(won't fit on 1 page) +/* #define BT_NSEG 2048*/ /* Number of scatter gather segments - to much vm */ +#define BT_NSEG 128 +#else +#define BT_NSEG 33 +#endif /* BIG_DMA */ + +struct bt_scat_gath { + unsigned long seg_len; + physaddr seg_addr; +}; + +struct bt_ccb { + unsigned char opcode; + unsigned char:3, data_in:1, data_out:1,:3; + unsigned char scsi_cmd_length; + unsigned char req_sense_length; + /*------------------------------------longword boundary */ + unsigned long data_length; + /*------------------------------------longword boundary */ + physaddr data_addr; + /*------------------------------------longword boundary */ + unsigned char dummy[2]; + unsigned char host_stat; + unsigned char target_stat; + /*------------------------------------longword boundary */ + unsigned char target; + unsigned char lun; + unsigned char scsi_cmd[12]; /* 12 bytes (bytes only) */ + unsigned char dummy2[1]; + unsigned char link_id; + /*------------------------------------4 longword boundary */ + physaddr link_addr; + /*------------------------------------longword boundary */ + physaddr sense_ptr; +/*-----end of HW fields-------------------------------longword boundary */ + struct scsi_sense_data scsi_sense; + /*------------------------------------longword boundary */ + struct bt_scat_gath scat_gath[BT_NSEG]; + /*------------------------------------longword boundary */ + struct bt_ccb *next; + /*------------------------------------longword boundary */ + struct scsi_xfer *xfer; /* the scsi_xfer for this cmd */ + /*------------------------------------longword boundary */ + struct bt_mbx_out *mbx; /* pointer to mail box */ + /*------------------------------------longword boundary */ + int flags; +#define CCB_FREE 0 +#define CCB_ACTIVE 1 +#define CCB_ABORTED 2 + /*------------------------------------longword boundary */ + struct bt_ccb *nexthash; /* if two hash the same */ + /*------------------------------------longword boundary */ + physaddr hashkey; /*physaddr of this ccb */ + /*------------------------------------longword boundary */ +}; + +/* + * opcode fields + */ + +#define BT_INITIATOR_CCB 0x00 /* SCSI Initiator CCB */ +#define BT_TARGET_CCB 0x01 /* SCSI Target CCB */ +#define BT_INIT_SCAT_GATH_CCB 0x02 /* SCSI Initiator with scattter gather */ +#define BT_RESET_CCB 0x81 /* SCSI Bus reset */ + +/* + * bt_ccb.host_stat values + */ + +#define BT_OK 0x00 /* cmd ok */ +#define BT_LINK_OK 0x0a /* Link cmd ok */ +#define BT_LINK_IT 0x0b /* Link cmd ok + int */ +#define BT_SEL_TIMEOUT 0x11 /* Selection time out */ +#define BT_OVER_UNDER 0x12 /* Data over/under run */ +#define BT_BUS_FREE 0x13 /* Bus dropped at unexpected time */ +#define BT_INV_BUS 0x14 /* Invalid bus phase/sequence */ +#define BT_BAD_MBO 0x15 /* Incorrect MBO cmd */ +#define BT_BAD_CCB 0x16 /* Incorrect ccb opcode */ +#define BT_BAD_LINK 0x17 /* Not same values of LUN for links */ +#define BT_INV_TARGET 0x18 /* Invalid target direction */ +#define BT_CCB_DUP 0x19 /* Duplicate CCB received */ +#define BT_INV_CCB 0x1a /* Invalid CCB or segment list */ +#define BT_ABORTED 42 /* pseudo value from driver */ + +struct bt_boardID { + u_char board_type; + u_char custom_feture; + char firm_revision; + u_char firm_version; +}; + +struct bt_setup { + u_char sync_neg:1; + u_char parity:1; + u_char :6; + u_char speed; + u_char bus_on; + u_char bus_off; + u_char num_mbx; + u_char mbx[3]; /*XXX */ + /* doesn't make sense with 32bit addresses */ + struct { + u_char offset:4; + u_char period:3; + u_char valid:1; + } sync[8]; + u_char disc_sts; +}; + +struct bt_config { + u_char chan; + u_char intr; + u_char scsi_dev:3; + u_char :5; +}; + +#define INT9 0x01 +#define INT10 0x02 +#define INT11 0x04 +#define INT12 0x08 +#define INT14 0x20 +#define INT15 0x40 + +#define EISADMA 0x00 +#define CHAN0 0x01 +#define CHAN5 0x20 +#define CHAN6 0x40 +#define CHAN7 0x80 + +#define KVTOPHYS(x) vtophys(x) +#define PAGESIZ 4096 +#define INVALIDATE_CACHE {asm volatile( ".byte 0x0F ;.byte 0x08" ); } + +u_char bt_scratch_buf[256]; + +struct bt_data { + short bt_base; /* base port for each board */ + struct bt_mbx bt_mbx; /* all our mailboxes */ + struct bt_ccb *bt_ccb_free; /* list of free CCBs */ + struct bt_ccb *ccbhash[CCB_HASH_SIZE]; /* phys to kv hash */ + int bt_int; /* int. read off board */ + int bt_dma; /* DMA channel read of board */ + int bt_scsi_dev; /* adapters scsi id */ + int numccbs; /* how many we have malloc'd */ + struct scsi_link sc_link; /* prototype for devs */ +} *btdata[NBT]; + +/***********debug values *************/ +#define BT_SHOWCCBS 0x01 +#define BT_SHOWINTS 0x02 +#define BT_SHOWCMDS 0x04 +#define BT_SHOWMISC 0x08 +int bt_debug = 0; + +#ifdef KERNEL +int btprobe(); +int btattach(); +int btintr(); +int32 bt_scsi_cmd(); +timeout_t bt_timeout; +void bt_inquire_setup_information(); +void bt_done(); +void btminphys(); +u_int32 bt_adapter_info(); +struct bt_ccb *bt_get_ccb(); +struct bt_ccb *bt_ccb_phys_kv(); + +static int btunit = 0; + +struct isa_driver btdriver = +{ + btprobe, + btattach, + "bt" +}; + +struct scsi_adapter bt_switch = +{ + bt_scsi_cmd, + btminphys, + 0, + 0, + bt_adapter_info, + "bt", + { 0, 0 } +}; + +/* the below structure is so we have a default dev struct for out link struct */ +struct scsi_device bt_dev = +{ + NULL, /* Use default error handler */ + NULL, /* have a queue, served by this */ + NULL, /* have no async handler */ + NULL, /* Use default 'done' routine */ + "bt", + 0, + { 0, 0 } +}; + +#endif /*KERNEL */ + +#define BT_RESET_TIMEOUT 1000 +#ifndef KERNEL +main() +{ + printf("bt_data is %d bytes\n", sizeof(struct bt_data)); + printf("bt_ccb is %d bytes\n", sizeof(struct bt_ccb)); + printf("bt_mbx is %d bytes\n", sizeof(struct bt_mbx)); +} + +#else /*KERNEL */ + +/* + * bt_cmd(unit,icnt, ocnt,wait, retval, opcode, args) + * + * Activate Adapter command + * icnt: number of args (outbound bytes written after opcode) + * ocnt: number of expected returned bytes + * wait: number of seconds to wait for response + * retval: buffer where to place returned bytes + * opcode: opcode BT_NOP, BT_MBX_INIT, BT_START_SCSI ... + * args: parameters + * + * Performs an adapter command through the ports. Not to be confused with a + * scsi command, which is read in via the dma; one of the adapter commands + * tells it to read in a scsi command. + */ +int +bt_cmd(unit, icnt, ocnt, wait, retval, opcode, args) + int unit; + int icnt; + int ocnt; + int wait; + u_char *retval; + unsigned opcode; + u_char args; +{ + struct bt_data *bt = btdata[unit]; + unsigned *ic = &opcode; + u_char oc; + register i; + int sts; + + /* + * multiply the wait argument by a big constant + * zero defaults to 1 + */ + if (wait) + wait *= 100000; + else + wait = 100000; + /* + * Wait for the adapter to go idle, unless it's one of + * the commands which don't need this + */ + if (opcode != BT_MBX_INIT && opcode != BT_START_SCSI) { + i = 100000; /* 1 sec? */ + while (--i) { + sts = inb(BT_CTRL_STAT_PORT); + if (sts & BT_IDLE) { + break; + } + DELAY(10); + } + if (i == 0) { + printf("bt%d: bt_cmd, host not idle(0x%x)\n", unit, sts); + return (ENXIO); + } + } + /* + * Now that it is idle, if we expect output, preflush the + * queue feeding to us. + */ + if (ocnt) { + while ((inb(BT_CTRL_STAT_PORT)) & BT_DF) + inb(BT_CMD_DATA_PORT); + } + /* + * Output the command and the number of arguments given + * for each byte, first check the port is empty. + */ + icnt++; + /* include the command */ + while (icnt--) { + sts = inb(BT_CTRL_STAT_PORT); + for (i = wait; i; i--) { + sts = inb(BT_CTRL_STAT_PORT); + if (!(sts & BT_CDF)) + break; + DELAY(10); + } + if (i == 0) { + printf("bt%d: bt_cmd, cmd/data port full\n", unit); + outb(BT_CTRL_STAT_PORT, BT_SRST); + return (ENXIO); + } + outb(BT_CMD_DATA_PORT, (u_char) (*ic++)); + } + /* + * If we expect input, loop that many times, each time, + * looking for the data register to have valid data + */ + while (ocnt--) { + sts = inb(BT_CTRL_STAT_PORT); + for (i = wait; i; i--) { + sts = inb(BT_CTRL_STAT_PORT); + if (sts & BT_DF) + break; + DELAY(10); + } + if (i == 0) { + printf("bt%d: bt_cmd, cmd/data port empty %d\n", + unit, ocnt); + return (ENXIO); + } + oc = inb(BT_CMD_DATA_PORT); + if (retval) + *retval++ = oc; + } + /* + * Wait for the board to report a finised instruction + */ + i = 100000; /* 1 sec? */ + while (--i) { + sts = inb(BT_INTR_PORT); + if (sts & BT_HACC) { + break; + } + DELAY(10); + } + if (i == 0) { + printf("bt%d: bt_cmd, host not finished(0x%x)\n", unit, sts); + return (ENXIO); + } + outb(BT_CTRL_STAT_PORT, BT_IRST); + return (0); +} + +/* + * Check if the device can be found at the port given + * and if so, set it up ready for further work + * as an argument, takes the isa_device structure from + * autoconf.c + */ +int +btprobe(dev) + struct isa_device *dev; +{ + /* + * find unit and check we have that many defined + */ + int unit = btunit; + struct bt_data *bt; + + if (unit >= NBT) { + printf("bt%d: unit number too high\n", unit); + return 0; + } + /* + * Allocate a storage area for us + */ + if (btdata[unit]) { + printf("bt%d: memory already allocated\n", unit); + return 0; + } + bt = malloc(sizeof(struct bt_data), M_TEMP, M_NOWAIT); + if (!bt) { + printf("bt%d: cannot malloc!\n", unit); + return 0; + } + bzero(bt, sizeof(struct bt_data)); + btdata[unit] = bt; + bt->bt_base = dev->id_iobase; + + /* + * Try initialise a unit at this location + * sets up dma and bus speed, loads bt->bt_int + */ + if (bt_init(unit) != 0) { + btdata[unit] = NULL; + free(bt, M_TEMP); + return 0; + } + /* + * If it's there, put in it's interrupt vectors + */ dev->id_unit = unit; + dev->id_irq = (1 << bt->bt_int); + dev->id_drq = bt->bt_dma; + + btunit++; + return 1; +} + +/* + * Attach all the sub-devices we can find + */ +int +btattach(dev) + struct isa_device *dev; +{ + int unit = dev->id_unit; + struct bt_data *bt = btdata[unit]; + + /* + * fill in the prototype scsi_link. + */ + bt->sc_link.adapter_unit = unit; + bt->sc_link.adapter_targ = bt->bt_scsi_dev; + bt->sc_link.adapter = &bt_switch; + bt->sc_link.device = &bt_dev; + bt->sc_link.flags = SDEV_BOUNCE; + + /* + * ask the adapter what subunits are present + */ + scsi_attachdevs(&(bt->sc_link)); + return 1; +} + +/* + * Return some information to the caller about the adapter and its + * capabilities. + */ +u_int32 +bt_adapter_info(unit) + int unit; +{ + return (2); /* 2 outstanding requests at a time per device */ +} + +/* + * Catch an interrupt from the adaptor + */ +int +btintr(unit) + int unit; +{ + struct bt_data *bt = btdata[unit]; + BT_MBI *wmbi; + struct bt_mbx *wmbx; + struct bt_ccb *ccb; + unsigned char stat; + int i, wait; + int found = 0; + +#ifdef UTEST + printf("btintr "); +#endif + /* + * First acknowlege the interrupt, Then if it's + * not telling about a completed operation + * just return. + */ + stat = inb(BT_INTR_PORT); + + /* Mail Box out empty ? */ + if (stat & BT_MBOA) { + printf("bt%d: Available Free mbo post\n", unit); + /* Disable MBO available interrupt */ + outb(BT_CMD_DATA_PORT, BT_MBO_INTR_EN); + wait = 100000; /* 1 sec enough? */ + for (i = wait; i; i--) { + if (!(inb(BT_CTRL_STAT_PORT) & BT_CDF)) + break; + DELAY(10); + } + if (i == 0) { + printf("bt%d: bt_intr, cmd/data port full\n", unit); + outb(BT_CTRL_STAT_PORT, BT_SRST); + return 1; + } + outb(BT_CMD_DATA_PORT, 0x00); /* Disable */ + wakeup((caddr_t)&bt->bt_mbx); + outb(BT_CTRL_STAT_PORT, BT_IRST); + return 1; + } + if (!(stat & BT_MBIF)) { + outb(BT_CTRL_STAT_PORT, BT_IRST); + return 1; + } + /* + * If it IS then process the competed operation + */ + wmbx = &bt->bt_mbx; + wmbi = wmbx->tmbi; + AGAIN: + while (wmbi->stat != BT_MBI_FREE) { + ccb = bt_ccb_phys_kv(bt, (wmbi->ccb_addr)); + if (!ccb) { + wmbi->stat = BT_MBI_FREE; + printf("bt: BAD CCB ADDR!\n"); + continue; + } + found++; + if ((stat = wmbi->stat) != BT_MBI_OK) { + switch (stat) { + case BT_MBI_ABORT: +#ifdef UTEST + if (bt_debug & BT_SHOWMISC) + printf("abort "); +#endif + ccb->host_stat = BT_ABORTED; + break; + + case BT_MBI_UNKNOWN: + ccb = (struct bt_ccb *) 0; +#ifdef UTEST + if (bt_debug & BT_SHOWMISC) + printf("unknown ccb for abort"); +#endif + break; + + case BT_MBI_ERROR: + break; + + default: + panic("Impossible mbxi status"); + + } +#ifdef UTEST + if ((bt_debug & BT_SHOWCMDS) && ccb) { + u_char *cp; + cp = ccb->scsi_cmd; + printf("op=%x %x %x %x %x %x\n", + cp[0], cp[1], cp[2], + cp[3], cp[4], cp[5]); + printf("stat %x for mbi addr = 0x%08x\n" + ,wmbi->stat, wmbi); + printf("addr = 0x%x\n", ccb); + } +#endif + } + wmbi->stat = BT_MBI_FREE; + if (ccb) { + untimeout(bt_timeout, (caddr_t)ccb); + bt_done(unit, ccb); + } + /* Set the IN mail Box pointer for next */ bt_nextmbx(wmbi, wmbx, mbi); + } + if (!found) { + for (i = 0; i < BT_MBX_SIZE; i++) { + if (wmbi->stat != BT_MBI_FREE) { + found++; + break; + } + bt_nextmbx(wmbi, wmbx, mbi); + } + if (!found) { + printf("bt%d: mbi at 0x%08x should be found, stat=%02x..resync\n", + unit, wmbi, stat); + } else { + found = 0; + goto AGAIN; + } + } + wmbx->tmbi = wmbi; + outb(BT_CTRL_STAT_PORT, BT_IRST); + return 1; +} + +/* + * A ccb is put onto the free list. + */ +void +bt_free_ccb(unit, ccb, flags) + int unit; + struct bt_ccb *ccb; + int flags; +{ + struct bt_data *bt = btdata[unit]; + unsigned int opri = 0; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + + ccb->next = bt->bt_ccb_free; + bt->bt_ccb_free = ccb; + ccb->flags = CCB_FREE; + /* + * If there were none, wake anybody waiting for one to come free, + * starting with queued entries. + */ + if (!ccb->next) { + wakeup((caddr_t)&bt->bt_ccb_free); + } + + if (!(flags & SCSI_NOMASK)) + splx(opri); +} + +/* + * Get a free ccb + * + * If there are none, see if we can allocate a new one. If so, put it in + * the hash table too otherwise either return an error or sleep. + */ +struct bt_ccb * +bt_get_ccb(unit, flags) + int unit; + int flags; +{ + struct bt_data *bt = btdata[unit]; + unsigned opri = 0; + struct bt_ccb *ccbp; + struct bt_mbx *wmbx; /* Mail Box pointer specified unit */ + BT_MBO *wmbo; /* Out Mail Box pointer */ + int hashnum; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + /* + * If we can and have to, sleep waiting for one to come free + * but only if we can't allocate a new one. + */ + while (!(ccbp = bt->bt_ccb_free)) { + if (bt->numccbs < BT_CCB_MAX) { + if (ccbp = (struct bt_ccb *) malloc(sizeof(struct bt_ccb), + M_TEMP, + M_NOWAIT)) { + bzero(ccbp, sizeof(struct bt_ccb)); + bt->numccbs++; + ccbp->flags = CCB_ACTIVE; + /* + * put in the phystokv hash table + * Never gets taken out. + */ + ccbp->hashkey = KVTOPHYS(ccbp); + hashnum = CCB_HASH(ccbp->hashkey); + ccbp->nexthash = bt->ccbhash[hashnum]; + bt->ccbhash[hashnum] = ccbp; + } else { + printf("bt%d: Can't malloc CCB\n", unit); + } + goto gottit; + } else { + if (!(flags & SCSI_NOSLEEP)) { + tsleep((caddr_t)&bt->bt_ccb_free, PRIBIO, + "btccb", 0); + } + } + } + if (ccbp) { + /* Get CCB from from free list */ + bt->bt_ccb_free = ccbp->next; + ccbp->flags = CCB_ACTIVE; + } + gottit: + if (!(flags & SCSI_NOMASK)) + splx(opri); + + return (ccbp); +} + +/* + * given a physical address, find the ccb that + * it corresponds to: + */ +struct bt_ccb * +bt_ccb_phys_kv(bt, ccb_phys) + struct bt_data *bt; + physaddr ccb_phys; +{ + int hashnum = CCB_HASH(ccb_phys); + struct bt_ccb *ccbp = bt->ccbhash[hashnum]; + + while (ccbp) { + if (ccbp->hashkey == ccb_phys) + break; + ccbp = ccbp->nexthash; + } + return ccbp; +} + +/* + * Get a MBO and then Send it + */ +BT_MBO * +bt_send_mbo(int unit, int flags, int cmd, struct bt_ccb *ccb) +{ + struct bt_data *bt = btdata[unit]; + unsigned opri = 0; + BT_MBO *wmbo; /* Mail Box Out pointer */ + struct bt_mbx *wmbx; /* Mail Box pointer specified unit */ + int i, wait; + + wmbx = &bt->bt_mbx; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + + /* Get the Target OUT mail Box pointer and move to Next */ + wmbo = wmbx->tmbo; + wmbx->tmbo = (wmbo == &(wmbx->mbo[BT_MBX_SIZE - 1]) ? + &(wmbx->mbo[0]) : wmbo + 1); + + /* + * Check the outmail box is free or not. + * Note: Under the normal operation, it shuld NOT happen to wait. + */ + while (wmbo->cmd != BT_MBO_FREE) { + wait = 100000; /* 1 sec enough? */ + /* Enable MBO available interrupt */ + outb(BT_CMD_DATA_PORT, BT_MBO_INTR_EN); + for (i = wait; i; i--) { + if (!(inb(BT_CTRL_STAT_PORT) & BT_CDF)) + break; + DELAY(10); + } + if (i == 0) { + printf("bt%d: bt_send_mbo, cmd/data port full\n", unit); + outb(BT_CTRL_STAT_PORT, BT_SRST); + return ((BT_MBO *) 0); + } + outb(BT_CMD_DATA_PORT, 0x01); /* Enable */ + tsleep((caddr_t)wmbx, PRIBIO, "btsend", 0); + /* XXX */ /*can't do this! */ + /* May be servicing an int */ + } + /* Link CCB to the Mail Box */ + wmbo->ccb_addr = KVTOPHYS(ccb); + ccb->mbx = wmbo; + wmbo->cmd = cmd; + + /* Send it! */ + outb(BT_CMD_DATA_PORT, BT_START_SCSI); + + if (!(flags & SCSI_NOMASK)) + splx(opri); + + return (wmbo); +} + +/* + * We have a ccb which has been processed by the + * adaptor, now we look to see how the operation + * went. Wake up the owner if waiting + */ +void +bt_done(unit, ccb) + int unit; + struct bt_ccb *ccb; +{ + struct bt_data *bt = btdata[unit]; + struct scsi_sense_data *s1, *s2; + struct scsi_xfer *xs = ccb->xfer; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("bt_done\n")); + /* + * Otherwise, put the results of the operation + * into the xfer and call whoever started it + */ + if ((ccb->host_stat != BT_OK || ccb->target_stat != SCSI_OK) + && (!(xs->flags & SCSI_ERR_OK))) { + + s1 = &(ccb->scsi_sense); + s2 = &(xs->sense); + + if (ccb->host_stat) { + switch (ccb->host_stat) { + case BT_ABORTED: /* No response */ + case BT_SEL_TIMEOUT: /* No response */ + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("timeout reported back\n")); + xs->error = XS_TIMEOUT; + break; + default: /* Other scsi protocol messes */ + xs->error = XS_DRIVER_STUFFUP; + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("unexpected host_stat: %x\n", + ccb->host_stat)); + } + } else { + switch (ccb->target_stat) { + case 0x02: + *s2 = *s1; + xs->error = XS_SENSE; + break; + case 0x08: + xs->error = XS_BUSY; + break; + default: + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("unexpected target_stat: %x\n", + ccb->target_stat)); + xs->error = XS_DRIVER_STUFFUP; + } + } + } else { /* All went correctly OR errors expected */ + xs->resid = 0; + } + xs->flags |= ITSDONE; + bt_free_ccb(unit, ccb, xs->flags); + scsi_done(xs); +} + +/* + * Start the board, ready for normal operation + */ +int +bt_init(unit) + int unit; +{ + struct bt_data *bt = btdata[unit]; + unsigned char ad[4]; + volatile int i, sts; + struct bt_config conf; + + /* + * reset board, If it doesn't respond, assume + * that it's not there.. good for the probe + */ + + outb(BT_CTRL_STAT_PORT, BT_HRST | BT_SRST); + + for (i = BT_RESET_TIMEOUT; i; i--) { + sts = inb(BT_CTRL_STAT_PORT); + if (sts == (BT_IDLE | BT_INIT)) + break; + DELAY(1000); + } + if (i == 0) { +#ifdef UTEST + printf("bt_init: No answer from bt742a board\n"); +#endif + return (ENXIO); + } + /* + * Assume we have a board at this stage + * setup dma channel from jumpers and save int + * level + */ + printf("bt%d: reading board settings, ", unit); + + bt_cmd(unit, 0, sizeof(conf), 0, &conf, BT_CONF_GET); + switch (conf.chan) { + case EISADMA: + bt->bt_dma = -1; + break; + case CHAN0: + outb(0x0b, 0x0c); + outb(0x0a, 0x00); + bt->bt_dma = 0; + break; + case CHAN5: + outb(0xd6, 0xc1); + outb(0xd4, 0x01); + bt->bt_dma = 5; + break; + case CHAN6: + outb(0xd6, 0xc2); + outb(0xd4, 0x02); + bt->bt_dma = 6; + break; + case CHAN7: + outb(0xd6, 0xc3); + outb(0xd4, 0x03); + bt->bt_dma = 7; + break; + default: + printf("illegal dma setting %x\n", conf.chan); + return (EIO); + } + if (bt->bt_dma == -1) + printf("eisa dma, "); + else + printf("dma=%d, ", bt->bt_dma); + + switch (conf.intr) { + case INT9: + bt->bt_int = 9; + break; + case INT10: + bt->bt_int = 10; + break; + case INT11: + bt->bt_int = 11; + break; + case INT12: + bt->bt_int = 12; + break; + case INT14: + bt->bt_int = 14; + break; + case INT15: + bt->bt_int = 15; + break; + default: + printf("illegal int setting\n"); + return (EIO); + } + printf("int=%d\n", bt->bt_int); + + /* who are we on the scsi bus */ + bt->bt_scsi_dev = conf.scsi_dev; + /* + * Initialize mail box + */ + *((physaddr *) ad) = KVTOPHYS(&bt->bt_mbx); + bt_cmd(unit, 5, 0, 0, 0, BT_MBX_INIT_EXTENDED + ,BT_MBX_SIZE + ,ad[0] + ,ad[1] + ,ad[2] + ,ad[3]); + + /* + * Set Pointer chain null for just in case + * Link the ccb's into a free-list W/O mbox + * Initialize mail box status to free + */ + if (bt->bt_ccb_free != (struct bt_ccb *) 0) { + printf("bt%d: bt_ccb_free is NOT initialized but init here\n", + unit); + bt->bt_ccb_free = (struct bt_ccb *) 0; + } + for (i = 0; i < BT_MBX_SIZE; i++) { + bt->bt_mbx.mbo[i].cmd = BT_MBO_FREE; + bt->bt_mbx.mbi[i].stat = BT_MBI_FREE; + } + /* + * Set up initial mail box for round-robin operation. + */ + bt->bt_mbx.tmbo = &bt->bt_mbx.mbo[0]; + bt->bt_mbx.tmbi = &bt->bt_mbx.mbi[0]; + bt_inquire_setup_information(unit); + + /* Enable round-robin scheme - appeared at firmware rev. 3.31 */ + bt_cmd(unit, 1, 0, 0, 0, BT_ROUND_ROBIN, BT_ENABLE); + + /* + * Note that we are going and return (to probe) + */ + return 0; +} + +void +bt_inquire_setup_information(unit) + int unit; +{ + struct bt_data *bt = btdata[unit]; + struct bt_setup setup; + struct bt_boardID bID; + int i; + + /* Inquire Board ID to Bt742 for firmware version */ + bt_cmd(unit, 0, sizeof(bID), 0, &bID, BT_INQUIRE); + printf("bt%d: version %c.%c, ", + unit, bID.firm_revision, bID.firm_version); + + /* Obtain setup information from Bt742. */ + bt_cmd(unit, 1, sizeof(setup), 0, &setup, BT_SETUP_GET, sizeof(setup)); + + if (setup.sync_neg) { + printf("sync, "); + } else { + printf("async, "); + } + if (setup.parity) { + printf("parity, "); + } else { + printf("no parity, "); + } + printf("%d mbxs, %d ccbs\n", setup.num_mbx, bt->numccbs); + + for (i = 0; i < 8; i++) { + if (!setup.sync[i].offset && + !setup.sync[i].period && + !setup.sync[i].valid) + continue; + + printf("bt%d: dev%02d Offset=%d,Transfer period=%d, Synchronous? %s", + unit, i, + setup.sync[i].offset, setup.sync[i].period, + setup.sync[i].valid ? "Yes" : "No"); + } +} + +#ifndef min +#define min(x,y) (x < y ? x : y) +#endif /* min */ + +void +btminphys(bp) + struct buf *bp; +{ + if (bp->b_bcount > ((BT_NSEG - 1) * PAGESIZ)) { + bp->b_bcount = ((BT_NSEG - 1) * PAGESIZ); + } +} + +/* + * start a scsi operation given the command and the data address. Also needs + * the unit, target and lu. + */ +int32 +bt_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_sense_data *s1, *s2; + struct bt_ccb *ccb; + struct bt_scat_gath *sg; + int seg; /* scatter gather seg being worked on */ + int i = 0; + int c = 0; + int thiskv; + physaddr thisphys, nextphys; + int unit = xs->sc_link->adapter_unit; + int bytes_this_seg, bytes_this_page, datalen, flags; + struct iovec *iovp; + struct bt_data *bt = btdata[unit]; + BT_MBO *mbo; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("bt_scsi_cmd\n")); + /* + * get a ccb (mbox-out) to use. If the transfer + * is from a buf (possibly from interrupt time) + * then we can't allow it to sleep + */ + flags = xs->flags; + if (xs->bp) + flags |= (SCSI_NOSLEEP); /* just to be sure */ + if (flags & ITSDONE) { + printf("bt%d: Already done?\n", unit); + xs->flags &= ~ITSDONE; + } + if (!(flags & INUSE)) { + printf("bt%d: Not in use?\n", unit); + xs->flags |= INUSE; + } + if (!(ccb = bt_get_ccb(unit, flags))) { + xs->error = XS_DRIVER_STUFFUP; + return (TRY_AGAIN_LATER); + } + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("start ccb(%x)\n", ccb)); + /* + * Put all the arguments for the xfer in the ccb + */ + ccb->xfer = xs; + if (flags & SCSI_RESET) { + ccb->opcode = BT_RESET_CCB; + } else { + /* can't use S/G if zero length */ + ccb->opcode = (xs->datalen ? + BT_INIT_SCAT_GATH_CCB + : BT_INITIATOR_CCB); + } + ccb->target = xs->sc_link->target; + ccb->data_out = 0; + ccb->data_in = 0; + ccb->lun = xs->sc_link->lun; + ccb->scsi_cmd_length = xs->cmdlen; + ccb->sense_ptr = KVTOPHYS(&(ccb->scsi_sense)); + ccb->req_sense_length = sizeof(ccb->scsi_sense); + + if ((xs->datalen) && (!(flags & SCSI_RESET))) { /* can use S/G only if not zero length */ + ccb->data_addr = KVTOPHYS(ccb->scat_gath); + sg = ccb->scat_gath; + seg = 0; +#ifdef TFS + if (flags & SCSI_DATA_UIO) { + iovp = ((struct uio *) xs->data)->uio_iov; + datalen = ((struct uio *) xs->data)->uio_iovcnt; + xs->datalen = 0; + while ((datalen) && (seg < BT_NSEG)) { + sg->seg_addr = (physaddr) iovp->iov_base; + xs->datalen += sg->seg_len = iovp->iov_len; + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("(0x%x@0x%x)" + ,iovp->iov_len, iovp->iov_base)); + sg++; + iovp++; + seg++; + datalen--; + } + } else +#endif /* TFS */ + { + /* + * Set up the scatter gather block + */ + + SC_DEBUG(xs->sc_link, SDEV_DB4, + ("%d @0x%x:- ", xs->datalen, xs->data)); + datalen = xs->datalen; + thiskv = (int) xs->data; + thisphys = KVTOPHYS(thiskv); + + while ((datalen) && (seg < BT_NSEG)) { + bytes_this_seg = 0; + + /* put in the base address */ + sg->seg_addr = thisphys; + + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("0x%x", thisphys)); + + /* do it at least once */ + nextphys = thisphys; + while ((datalen) && (thisphys == nextphys)) + /* + * This page is contiguous (physically) with + * the the last, just extend the length + */ + { + + /* check it fits on the ISA bus */ + if (thisphys > 0xFFFFFF) + { + printf("bt%d: DMA beyond" + " end Of ISA\n", unit); + xs->error = XS_DRIVER_STUFFUP; + bt_free_ccb(unit, ccb, flags); + return (HAD_ERROR); + } + /** how far to the end of the page ***/ + /* how far to the end of the page */ + nextphys = (thisphys & (~(PAGESIZ - 1))) + + PAGESIZ; + bytes_this_page = nextphys - thisphys; + /**** or the data ****/ + bytes_this_page = min(bytes_this_page + ,datalen); + bytes_this_seg += bytes_this_page; + datalen -= bytes_this_page; + + /* get more ready for the next page */ + thiskv = (thiskv & (~(PAGESIZ - 1))) + + PAGESIZ; + if (datalen) + thisphys = KVTOPHYS(thiskv); + } + /* + * next page isn't contiguous, finish the seg + */ + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("(0x%x)", bytes_this_seg)); + sg->seg_len = bytes_this_seg; + sg++; + seg++; + } + } + /* end of iov/kv decision */ + ccb->data_length = seg * sizeof(struct bt_scat_gath); + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("\n")); + if (datalen) { + /* + * there's still data, must have run out of segs! + */ + printf("bt%d: bt_scsi_cmd, more than %d DMA segs\n", + unit, BT_NSEG); + xs->error = XS_DRIVER_STUFFUP; + bt_free_ccb(unit, ccb, flags); + return (HAD_ERROR); + } + } else { /* No data xfer, use non S/G values */ + ccb->data_addr = (physaddr) 0; + ccb->data_length = 0; + } + ccb->link_id = 0; + ccb->link_addr = (physaddr) 0; + /* + * Put the scsi command in the ccb and start it + */ + if (!(flags & SCSI_RESET)) { + bcopy(xs->cmd, ccb->scsi_cmd, ccb->scsi_cmd_length); + } + if (bt_send_mbo(unit, flags, BT_MBO_START, ccb) == (BT_MBO *) 0) { + xs->error = XS_DRIVER_STUFFUP; + bt_free_ccb(unit, ccb, flags); + return (TRY_AGAIN_LATER); + } + /* + * Usually return SUCCESSFULLY QUEUED + */ + SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd_sent\n")); + if (!(flags & SCSI_NOMASK)) { + timeout(bt_timeout, (caddr_t)ccb, (xs->timeout * hz) / 1000); + return (SUCCESSFULLY_QUEUED); + } + /* + * If we can't use interrupts, poll on completion + */ + return (bt_poll(unit, xs, ccb)); +} + +/* + * Poll a particular unit, looking for a particular xs + */ +int +bt_poll(unit, xs, ccb) + int unit; + struct scsi_xfer *xs; + struct bt_ccb *ccb; +{ + struct bt_data *bt = btdata[unit]; + int done = 0; + int count = xs->timeout; + u_char stat; + + /* timeouts are in msec, so we loop in 1000 usec cycles */ + while (count) { + /* + * If we had interrupts enabled, would we + * have got an interrupt? + */ + stat = inb(BT_INTR_PORT); + if (stat & BT_ANY_INTR) { + btintr(unit); + } + if (xs->flags & ITSDONE) { + break; + } + DELAY(1000); /* only happens in boot so ok */ + count--; + } + if (count == 0) { + /* + * We timed out, so call the timeout handler manually, + * accounting for the fact that the clock is not running yet + * by taking out the clock queue entry it makes. + */ + bt_timeout((caddr_t)ccb); + + /* + * because we are polling, take out the timeout entry + * bt_timeout made + */ + untimeout(bt_timeout, (caddr_t)ccb); + count = 2000; + while (count) { + /* + * Once again, wait for the int bit + */ + stat = inb(BT_INTR_PORT); + if (stat & BT_ANY_INTR) { + btintr(unit); + } + if (xs->flags & ITSDONE) { + break; + } + DELAY(1000); /* only happens in boot so ok */ + count--; + } + if (count == 0) { + /* + * We timed out again... This is bad. Notice that + * this time there is no clock queue entry to remove. + */ + bt_timeout((caddr_t)ccb); + } + } + if (xs->error) + return (HAD_ERROR); + return (COMPLETE); +} + +void +bt_timeout(void *arg1) +{ + struct bt_ccb * ccb = (struct bt_ccb *)arg1; + int unit; + struct bt_data *bt; + int s = splbio(); + + unit = ccb->xfer->sc_link->adapter_unit; + bt = btdata[unit]; + printf("bt%d:%d:%d (%s%d) timed out ", unit + ,ccb->xfer->sc_link->target + ,ccb->xfer->sc_link->lun + ,ccb->xfer->sc_link->device->name + ,ccb->xfer->sc_link->dev_unit); + +#ifdef UTEST + bt_print_active_ccbs(unit); +#endif + + /* + * If the ccb's mbx is not free, then the board has gone Far East? + */ + if (bt_ccb_phys_kv(bt, ccb->mbx->ccb_addr) == ccb && + ccb->mbx->cmd != BT_MBO_FREE) { + printf("bt%d: not taking commands!\n", unit); + Debugger("bt742a"); + } + /* + * If it has been through before, then + * a previous abort has failed, don't + * try abort again + */ + if (ccb->flags == CCB_ABORTED) { + /* + * abort timed out + */ + printf("bt%d: Abort Operation has timed out\n", unit); + ccb->xfer->retries = 0; /* I MEAN IT ! */ + ccb->host_stat = BT_ABORTED; + bt_done(unit, ccb); + } else { /* abort the operation that has timed out */ + printf("bt%d: Try to abort\n", unit); + bt_send_mbo(unit, ~SCSI_NOMASK, + BT_MBO_ABORT, ccb); + /* 2 secs for the abort */ + timeout(bt_timeout, (caddr_t)ccb, 2 * hz); + ccb->flags = CCB_ABORTED; + } + splx(s); +} + +#ifdef UTEST +void +bt_print_ccb(ccb) + struct bt_ccb *ccb; +{ + printf("ccb:%x op:%x cmdlen:%d senlen:%d\n" + ,ccb + ,ccb->opcode + ,ccb->scsi_cmd_length + ,ccb->req_sense_length); + printf(" datlen:%d hstat:%x tstat:%x flags:%x\n" + ,ccb->data_length + ,ccb->host_stat + ,ccb->target_stat + ,ccb->flags); +} + +void +bt_print_active_ccbs(int unit) +{ + struct bt_data *bt = btdata[unit]; + struct bt_ccb *ccb; + int i = 0; + + while (i < CCB_HASH_SIZE) { + ccb = bt->ccbhash[i]; + while (ccb) { + if (ccb->flags != CCB_FREE) + bt_print_ccb(ccb); + ccb = ccb->nexthash; + } + i++; + } +} +#endif /*UTEST */ +#endif /*KERNEL */ diff --git a/sys/i386/isa/clock.c b/sys/i386/isa/clock.c new file mode 100644 index 0000000..e72d6a3 --- /dev/null +++ b/sys/i386/isa/clock.c @@ -0,0 +1,539 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz and 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. + * + * from: @(#)clock.c 7.2 (Berkeley) 5/12/91 + * $Id: clock.c,v 1.15 1994/08/18 05:09:21 davidg Exp $ + */ + +/* + * Primitive clock interrupt routines. + */ +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <machine/segments.h> +#include <machine/frame.h> +#include <i386/isa/icu.h> +#include <i386/isa/isa.h> +#include <i386/isa/rtc.h> +#include <i386/isa/timerreg.h> +#include <machine/cpu.h> + +/* X-tals being what they are, it's nice to be able to fudge this one... */ +/* Note, the name changed here from XTALSPEED to TIMER_FREQ rgrimes 4/26/93 */ +#ifndef TIMER_FREQ +#define TIMER_FREQ 1193182 /* XXX - should be in isa.h */ +#endif +#define TIMER_DIV(x) ((TIMER_FREQ+(x)/2)/(x)) + +static int beeping; +int timer0_divisor = TIMER_DIV(100); /* XXX should be hz */ +u_int timer0_prescale; +static char timer0_state = 0, timer2_state = 0; +static char timer0_reprogram = 0; +static void (*timer_func)() = hardclock; +static void (*new_function)(); +static u_int new_rate; +static u_int hardclock_divisor; + +#ifdef I586_CPU +int pentium_mhz = 0; +#endif + +void +clkintr(frame) + struct clockframe frame; +{ +#ifdef I586_CPU + /* + * This resets the CPU cycle counter to zero, to make our + * job easier in microtime(). Some fancy ifdefs could speed + * this up for Pentium-only kernels. + * We want this to be done as close as possible to the actual + * timer incrementing in hardclock(), because there is a window + * between the two where the value is no longer valid. Experimentation + * may reveal a good precompensation to apply in microtime(). + */ + if(pentium_mhz) { + __asm __volatile("movl $0x10,%%ecx\n" + "xorl %%eax,%%eax\n" + "movl %%eax,%%edx\n" + ".byte 0x0f, 0x30\n" + "#%0%1" + : "=m"(frame) /* no outputs */ + : "b"(&frame) /* fake input */ + : "ax", "cx", "dx"); + } +#endif + hardclock(&frame); +} + +static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; + +/* + * This routine receives statistical clock interrupts from the RTC. + * As explained above, these occur at 128 interrupts per second. + * When profiling, we receive interrupts at a rate of 1024 Hz. + * + * This does not actually add as much overhead as it sounds, because + * when the statistical clock is active, the hardclock driver no longer + * needs to keep (inaccurate) statistics on its own. This decouples + * statistics gathering from scheduling interrupts. + * + * The RTC chip requires that we read status register C (RTC_INTR) + * to acknowledge an interrupt, before it will generate the next one. + */ +void +rtcintr(struct clockframe frame) +{ + u_char stat; + stat = rtcin(RTC_INTR); + if(stat & RTCIR_PERIOD) { + statclock(&frame); + } +} + +#ifdef DEBUG +void +printrtc(void) +{ + outb(IO_RTC, RTC_STATUSA); + printf("RTC status A = %x", inb(IO_RTC+1)); + outb(IO_RTC, RTC_STATUSB); + printf(", B = %x", inb(IO_RTC+1)); + outb(IO_RTC, RTC_INTR); + printf(", C = %x\n", inb(IO_RTC+1)); +} +#endif + +#if 0 +void +timerintr(struct clockframe frame) +{ + timer_func(&frame); + switch (timer0_state) { + case 0: + break; + case 1: + if ((timer0_prescale+=timer0_divisor) >= hardclock_divisor) { + hardclock(&frame); + timer0_prescale = 0; + } + break; + case 2: + disable_intr(); + outb(TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT); + outb(TIMER_CNTR0, TIMER_DIV(new_rate)%256); + outb(TIMER_CNTR0, TIMER_DIV(new_rate)/256); + enable_intr(); + timer0_divisor = TIMER_DIV(new_rate); + timer0_prescale = 0; + timer_func = new_function; + timer0_state = 1; + break; + case 3: + if ((timer0_prescale+=timer0_divisor) >= hardclock_divisor) { + hardclock(&frame); + disable_intr(); + outb(TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT); + outb(TIMER_CNTR0, TIMER_DIV(hz)%256); + outb(TIMER_CNTR0, TIMER_DIV(hz)/256); + enable_intr(); + timer0_divisor = TIMER_DIV(hz); + timer0_prescale = 0; + timer_func = hardclock;; + timer0_state = 0; + } + break; + } +} + +#endif + +int +acquire_timer0(int rate, void (*function)() ) +{ + if (timer0_state || !function) + return -1; + + new_function = function; + new_rate = rate; + timer0_state = 2; + return 0; +} + + +int +acquire_timer2(int mode) +{ + if (timer2_state) + return -1; + timer2_state = 1; + outb(TIMER_MODE, TIMER_SEL2 | (mode &0x3f)); + return 0; +} + + +int +release_timer0() +{ + if (!timer0_state) + return -1; + timer0_state = 3; + return 0; +} + + +int +release_timer2() +{ + if (!timer2_state) + return -1; + timer2_state = 0; + outb(TIMER_MODE, TIMER_SEL2|TIMER_SQWAVE|TIMER_16BIT); + return 0; +} + + +static int +getit() +{ + int high, low; + + disable_intr(); + /* select timer0 and latch counter value */ + outb(TIMER_MODE, TIMER_SEL0); + low = inb(TIMER_CNTR0); + high = inb(TIMER_CNTR0); + enable_intr(); + return ((high << 8) | low); +} + +#ifdef I586_CPU +static long long cycles_per_sec = 0; + +/* + * Figure out how fast the cyclecounter runs. This must be run with + * clock interrupts disabled, but with the timer/counter programmed + * and running. + */ +void +calibrate_cyclecounter(void) +{ + volatile long edx, eax, lasteax, lastedx; + + __asm __volatile(".byte 0x0f, 0x31" : "=a"(lasteax), "=d"(lastedx) : ); + DELAY(1000000); + __asm __volatile(".byte 0x0f, 0x31" : "=a"(eax), "=d"(edx) : ); + + /* + * This assumes that you will never have a clock rate higher + * than 4GHz, probably a good assumption. + */ + cycles_per_sec = (long long)edx + eax; + cycles_per_sec -= (long long)lastedx + lasteax; + pentium_mhz = ((long)cycles_per_sec + 500000) / 1000000; /* round up */ +} +#endif + +/* + * Wait "n" microseconds. + * Relies on timer 1 counting down from (TIMER_FREQ / hz) + * Note: timer had better have been programmed before this is first used! + */ +void +DELAY(int n) +{ + int counter_limit, prev_tick, tick, ticks_left, sec, usec; + +#ifdef DELAYDEBUG + int getit_calls = 1; + int n1; + static int state = 0; + + if (state == 0) { + state = 1; + for (n1 = 1; n1 <= 10000000; n1 *= 10) + DELAY(n1); + state = 2; + } + if (state == 1) + printf("DELAY(%d)...", n); +#endif + /* + * Read the counter first, so that the rest of the setup overhead is + * counted. Guess the initial overhead is 20 usec (on most systems it + * takes about 1.5 usec for each of the i/o's in getit(). The loop + * takes about 6 usec on a 486/33 and 13 usec on a 386/20. The + * multiplications and divisions to scale the count take a while). + */ + prev_tick = getit(0, 0); + n -= 20; + /* + * Calculate (n * (TIMER_FREQ / 1e6)) without using floating point + * and without any avoidable overflows. + */ + sec = n / 1000000; + usec = n - sec * 1000000; + ticks_left = sec * TIMER_FREQ + + usec * (TIMER_FREQ / 1000000) + + usec * ((TIMER_FREQ % 1000000) / 1000) / 1000 + + usec * (TIMER_FREQ % 1000) / 1000000; + + while (ticks_left > 0) { + tick = getit(0, 0); +#ifdef DELAYDEBUG + ++getit_calls; +#endif + if (tick > prev_tick) + ticks_left -= prev_tick - (tick - timer0_divisor); + else + ticks_left -= prev_tick - tick; + prev_tick = tick; + } +#ifdef DELAYDEBUG + if (state == 1) + printf(" %d calls to getit() at %d usec each\n", + getit_calls, (n + 5) / getit_calls); +#endif +} + + +static void +sysbeepstop() +{ + outb(IO_PPI, inb(IO_PPI)&0xFC); /* disable counter2 output to speaker */ + release_timer2(); + beeping = 0; +} + + +int +sysbeep(int pitch, int period) +{ + + if (acquire_timer2(TIMER_SQWAVE|TIMER_16BIT)) + return -1; + disable_intr(); + outb(TIMER_CNTR2, pitch); + outb(TIMER_CNTR2, (pitch>>8)); + enable_intr(); + if (!beeping) { + outb(IO_PPI, inb(IO_PPI) | 3); /* enable counter2 output to speaker */ + beeping = period; + timeout(sysbeepstop, 0, period); + } + return 0; +} + + +void +startrtclock() +{ + int s; + + /* initialize 8253 clock */ + outb(TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT); + + /* Correct rounding will buy us a better precision in timekeeping */ + outb (IO_TIMER1, TIMER_DIV(hz)%256); + outb (IO_TIMER1, TIMER_DIV(hz)/256); + timer0_divisor = hardclock_divisor = TIMER_DIV(hz); + + /* initialize brain-dead battery powered clock */ + outb (IO_RTC, RTC_STATUSA); + outb (IO_RTC+1, rtc_statusa); + outb (IO_RTC, RTC_STATUSB); + outb (IO_RTC+1, RTCSB_24HR); + + outb (IO_RTC, RTC_DIAG); + if (s = inb (IO_RTC+1)) + printf("RTC BIOS diagnostic error %b\n", s, RTCDG_BITS); +} + + +/* convert 2 digit BCD number */ +int +bcd(int i) +{ + return ((i/16)*10 + (i%16)); +} + + +/* convert years to seconds (from 1970) */ +unsigned long +ytos(int y) +{ + int i; + unsigned long ret; + + ret = 0; + for(i = 1970; i < y; i++) { + if (i % 4) ret += 365*24*60*60; + else ret += 366*24*60*60; + } + return ret; +} + + +/* convert months to seconds */ +unsigned long +mtos(int m, int leap) +{ + int i; + unsigned long ret; + + ret = 0; + for(i=1; i<m; i++) { + switch(i){ + case 1: case 3: case 5: case 7: case 8: case 10: case 12: + ret += 31*24*60*60; break; + case 4: case 6: case 9: case 11: + ret += 30*24*60*60; break; + case 2: + if (leap) ret += 29*24*60*60; + else ret += 28*24*60*60; + } + } + return ret; +} + + +/* + * Initialize the time of day register, based on the time base which is, e.g. + * from a filesystem. + */ +void +inittodr(time_t base) +{ + unsigned long sec; + int leap, day_week, t, yd; + int sa,s; + + /* do we have a realtime clock present? (otherwise we loop below) */ + sa = rtcin(RTC_STATUSA); + if (sa == 0xff || sa == 0) return; + + /* ready for a read? */ + while ((sa&RTCSA_TUP) == RTCSA_TUP) + sa = rtcin(RTC_STATUSA); + + sec = bcd(rtcin(RTC_YEAR)) + 1900; + if (sec < 1970) + sec += 100; + + leap = !(sec % 4); sec = ytos(sec); /* year */ + yd = mtos(bcd(rtcin(RTC_MONTH)),leap); sec+=yd; /* month */ + t = (bcd(rtcin(RTC_DAY))-1) * 24*60*60; sec+=t; yd+=t; /* date */ + day_week = rtcin(RTC_WDAY); /* day */ + sec += bcd(rtcin(RTC_HRS)) * 60*60; /* hour */ + sec += bcd(rtcin(RTC_MIN)) * 60; /* minutes */ + sec += bcd(rtcin(RTC_SEC)); /* seconds */ + sec += tz.tz_minuteswest * 60; + time.tv_sec = sec; +} + + +#ifdef garbage +/* + * Initialze the time of day register, based on the time base which is, e.g. + * from a filesystem. + */ +test_inittodr(time_t base) +{ + + outb(IO_RTC,9); /* year */ + printf("%d ",bcd(inb(IO_RTC+1))); + outb(IO_RTC,8); /* month */ + printf("%d ",bcd(inb(IO_RTC+1))); + outb(IO_RTC,7); /* day */ + printf("%d ",bcd(inb(IO_RTC+1))); + outb(IO_RTC,4); /* hour */ + printf("%d ",bcd(inb(IO_RTC+1))); + outb(IO_RTC,2); /* minutes */ + printf("%d ",bcd(inb(IO_RTC+1))); + outb(IO_RTC,0); /* seconds */ + printf("%d\n",bcd(inb(IO_RTC+1))); + + time.tv_sec = base; +} +#endif + +/* + * Wire clock interrupt in. + */ +void +enablertclock() +{ + register_intr(/* irq */ 0, /* XXX id */ 0, /* flags */ 0, clkintr, + HWI_MASK | SWI_MASK, /* unit */ 0); + INTREN(IRQ0); + register_intr(/* irq */ 8, /* XXX id */ 1, /* flags */ 0, rtcintr, + SWI_CLOCK_MASK, /* unit */ 0); + INTREN(IRQ8); + outb(IO_RTC, RTC_STATUSB); + outb(IO_RTC+1, RTCSB_PINTR | RTCSB_24HR); +} + + +/* + * Delay for some number of milliseconds. + */ +void +spinwait(int millisecs) +{ + DELAY(1000 * millisecs); +} + +void +cpu_initclocks() +{ + stathz = RTC_NOPROFRATE; + profhz = RTC_PROFRATE; + enablertclock(); +} + +void +setstatclockrate(int newhz) +{ + if(newhz == RTC_PROFRATE) { + rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF; + } else { + rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF; + } + outb(IO_RTC, RTC_STATUSA); + outb(IO_RTC+1, rtc_statusa); +} diff --git a/sys/i386/isa/elink.c b/sys/i386/isa/elink.c new file mode 100644 index 0000000..cd00576 --- /dev/null +++ b/sys/i386/isa/elink.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 1994 Charles Hannum. 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 Charles Hannum. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * $Id: elink.c,v 1.1 1994/05/25 20:06:40 ats Exp $ + */ + +/* + * Common code for dealing with 3COM ethernet cards. + */ + +#include <sys/types.h> +#include <sys/cdefs.h> +#include <machine/pio.h> +#include <i386/isa/elink.h> + +/* + * Issue a `global reset' to all cards. We have to be careful to do this only + * once during autoconfig, to prevent resetting boards that have already been + * configured. + */ +void +elink_reset() +{ + static int x = 0; + + if (x == 0) { + x = 1; + outb(ELINK_ID_PORT, ELINK_RESET); + } +} + +/* + * The `ID sequence' is really just snapshots of an 8-bit CRC register as 0 + * bits are shifted in. Different board types use different polynomials. + */ +void +elink_idseq(p) + register u_char p; +{ + register int i; + register u_char c; + + c = 0xff; + for (i = 255; i; i--) { + outb(ELINK_ID_PORT, c); + if (c & 0x80) { + c <<= 1; + c ^= p; + } else + c <<= 1; + } +} diff --git a/sys/i386/isa/elink.h b/sys/i386/isa/elink.h new file mode 100644 index 0000000..93a5dac --- /dev/null +++ b/sys/i386/isa/elink.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 1994 Charles Hannum. 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 Charles Hannum. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * $Id: elink.h,v 1.1 1994/05/25 20:06:43 ats Exp $ + */ + +#define ELINK_ID_PORT 0x100 +#define ELINK_RESET 0xc0 + +#define ELINK_507_POLY 0xe7 +#define ELINK_509_POLY 0xcf + +void elink_reset __P((void)); +void elink_idseq __P((u_char p)); diff --git a/sys/i386/isa/fd.c b/sys/i386/isa/fd.c new file mode 100644 index 0000000..61e385f9 --- /dev/null +++ b/sys/i386/isa/fd.c @@ -0,0 +1,1255 @@ +/*#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. + * + * from: @(#)fd.c 7.4 (Berkeley) 5/25/91 + * $Id: fd.c,v 1.27 1994/08/13 03:49:57 wollman Exp $ + * + */ + +#include "ft.h" +#if NFT < 1 +#undef NFDC +#endif +#include "fd.h" + +#if NFDC > 0 + +#include <sys/param.h> +#include <sys/dkbad.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <machine/ioctl_fd.h> +#include <sys/disklabel.h> +#include <sys/buf.h> +#include <sys/uio.h> +#include <sys/malloc.h> +#include <sys/syslog.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/fdreg.h> +#include <i386/isa/fdc.h> +#include <i386/isa/icu.h> +#include <i386/isa/rtc.h> + +#if NFT > 0 +extern int ftopen(), ftintr(), ftattach(), ftclose(), ftioctl(); +#endif + +#define b_cylin b_resid +#define FDBLK 512 + +/* misuse a flag to identify format operation */ +#define B_FORMAT B_XXX + +#define NUMTYPES 14 +#define NUMDENS (NUMTYPES - 6) + +/* This defines (-1) must match index for fd_types */ +#define F_TAPE_TYPE 0x020 /* bit for fd_types to indicate tape */ +#define NO_TYPE 0 /* must match NO_TYPE in ft.c */ +#define FD_1720 1 +#define FD_1480 2 +#define FD_1440 3 +#define FD_1200 4 +#define FD_820 5 +#define FD_800 6 +#define FD_720 7 +#define FD_360 8 + +#define FD_1480in5_25 9 +#define FD_1440in5_25 10 +#define FD_820in5_25 11 +#define FD_800in5_25 12 +#define FD_720in5_25 13 +#define FD_360in5_25 14 + + +struct fd_type fd_types[NUMTYPES] = +{ +{ 21,2,0xFF,0x04,82,3444,1,FDC_500KBPS,2,0x0C,2 }, /* 1.72M in HD 3.5in */ +{ 18,2,0xFF,0x1B,82,2952,1,FDC_500KBPS,2,0x6C,1 }, /* 1.48M in HD 3.5in */ +{ 18,2,0xFF,0x1B,80,2880,1,FDC_500KBPS,2,0x6C,1 }, /* 1.44M in HD 3.5in */ +{ 15,2,0xFF,0x1B,80,2400,1,FDC_500KBPS,2,0x54,1 }, /* 1.2M in HD 5.25/3.5 */ +{ 10,2,0xFF,0x10,82,1640,1,FDC_250KBPS,2,0x2E,1 }, /* 820K in HD 3.5in */ +{ 10,2,0xFF,0x10,80,1600,1,FDC_250KBPS,2,0x2E,1 }, /* 800K in HD 3.5in */ +{ 9,2,0xFF,0x20,80,1440,1,FDC_250KBPS,2,0x50,1 }, /* 720K in HD 3.5in */ +{ 9,2,0xFF,0x2A,40, 720,1,FDC_250KBPS,2,0x50,1 }, /* 360K in DD 5.25in */ + +{ 18,2,0xFF,0x02,82,2952,1,FDC_500KBPS,2,0x02,2 }, /* 1.48M in HD 5.25in */ +{ 18,2,0xFF,0x02,80,2880,1,FDC_500KBPS,2,0x02,2 }, /* 1.44M in HD 5.25in */ +{ 10,2,0xFF,0x10,82,1640,1,FDC_300KBPS,2,0x2E,1 }, /* 820K in HD 5.25in */ +{ 10,2,0xFF,0x10,80,1600,1,FDC_300KBPS,2,0x2E,1 }, /* 800K in HD 5.25in */ +{ 9,2,0xFF,0x20,80,1440,1,FDC_300KBPS,2,0x50,1 }, /* 720K in HD 5.25in */ +{ 9,2,0xFF,0x23,40, 720,2,FDC_300KBPS,2,0x50,1 }, /* 360K in HD 5.25in */ +}; + +#define DRVS_PER_CTLR 2 /* 2 floppies */ +/***********************************************************************\ +* Per controller structure. * +\***********************************************************************/ +struct fdc_data fdc_data[NFDC]; + +/***********************************************************************\ +* Per drive structure. * +* N per controller (DRVS_PER_CTLR) * +\***********************************************************************/ +struct fd_data { + struct fdc_data *fdc; /* pointer to controller structure */ + 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) * +\***********************************************************************/ + +#define id_physid id_scsiid /* this biotab field doubles as a field */ + /* for the physical unit number on the controller */ + +static int retrier(fdcu_t); + +#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 */ + +static void fdstart(fdcu_t); +void fdintr(fdcu_t); +static timeout_t fd_turnoff; + +/****************************************************************************/ +/* autoconfiguration stuff */ +/****************************************************************************/ +static int fdprobe(struct isa_device *); +static int fdattach(struct isa_device *); + +struct isa_driver fdcdriver = { + fdprobe, fdattach, "fdc", +}; + +/* + * probe for existance of controller + */ +int +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; + + /* First - lets reset the floppy controller */ + + outb(dev->id_iobase+fdout,0); + DELAY(100); + outb(dev->id_iobase+fdout,FDO_FRST); + + /* 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 + */ +int +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; + struct isa_device *fdup; + + fdc->fdcu = fdcu; + fdc->flags |= FDC_ATTACHED; + fdc->dmachan = dev->id_drq; + fdc->state = DEVIDLE; + hdr = 0; + printf("fdc%d:", fdcu); + + /* check for each floppy drive */ + for (fdup = isa_biotab_fdc; fdup->id_driver != 0; fdup++) { + if (fdup->id_iobase != dev->id_iobase) + continue; + fdu = fdup->id_unit; + fd = &fd_data[fdu]; + if (fdu >= (NFD+NFT)) + continue; + fdsu = fdup->id_physid; + /* look up what bios thinks we have */ + switch (fdu) { + case 0: fdt = (rtcin(RTC_FDISKETTE) & 0xf0); + break; + case 1: fdt = ((rtcin(RTC_FDISKETTE) << 4) & 0xf0); + break; + default: fdt = RTCFDT_NONE; + break; + } + /* is there a unit? */ + if ((fdt == RTCFDT_NONE) +#if NFT > 0 + || (fdsu >= DRVS_PER_CTLR)) { +#else + ) { + fd->type = NO_TYPE; +#endif +#if NFT > 0 + /* If BIOS says no floppy, or > 2nd device */ + /* Probe for and attach a floppy tape. */ + if (ftattach(dev, fdup)) + continue; + if (fdsu < DRVS_PER_CTLR) + fd->type = NO_TYPE; +#endif + 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->track = -2; + fd->fdc = fdc; + fd->fdsu = fdsu; + printf(" [%d: fd%d: ", fdsu, fdu); + + switch (fdt) { + case RTCFDT_12M: + printf("1.2MB 5.25in]"); + fd->type = FD_1200; + break; + case RTCFDT_144M: + printf("1.44MB 3.5in]"); + fd->type = FD_1440; + break; + case RTCFDT_360K: + printf("360KB 5.25in]"); + fd->type = FD_360; + break; + case RTCFDT_720K: + printf("720KB 3.5in]"); + fd->type = FD_720; + break; + default: + printf("unknown]"); + fd->type = NO_TYPE; + break; + } + + fd_turnoff((caddr_t)fdu); + hdr = 1; + } + printf("\n"); + + /* Set transfer to 500kbps */ + outb(fdc->baseport+fdctl,0); /*XXX*/ + return 1; +} + +int +fdsize(dev) + dev_t dev; +{ + return(0); +} + +/****************************************************************************/ +/* fdstrategy */ +/****************************************************************************/ +void fdstrategy(struct buf *bp) +{ + 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; + +#if NFT > 0 + /* check for controller already busy with tape */ + if (fdc->flags & FDC_TAPE_BUSY) { + bp->b_error = EBUSY; + bp->b_flags |= B_ERROR; + return; + } +#endif + 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); + bp->b_pblkno = bp->b_blkno; + dp = &(fdc->head); + s = splbio(); + disksort(dp, bp); + untimeout(fd_turnoff, (caddr_t)fdu); /* a good idea */ + fdstart(fdcu); + splx(s); + return; + +bad: + biodone(bp); + return; +} + +/****************************************************************************/ +/* motor control stuff */ +/* remember to not deselect the drive we're working on */ +/****************************************************************************/ +void +set_motor(fdcu, fdu, reset) + 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))); +} + +static void +fd_turnoff(void *arg1) +{ + fdu_t fdu = (fdu_t)arg1; + int s; + + fd_p fd = fd_data + fdu; + s = splbio(); + fd->flags &= ~FD_MOTOR; + set_motor(fd->fdc->fdcu,fd->fdsu,0); + splx(s); +} + +void +fd_motor_on(void *arg1) +{ + fdu_t fdu = (fdu_t)arg1; + int s; + + fd_p fd = fd_data + fdu; + s = splbio(); + fd->flags &= ~FD_MOTOR_WAIT; + if((fd->fdc->fd == fd) && (fd->fdc->state == MOTORWAIT)) + { + fdintr(fd->fdc->fdcu); + } + splx(s); +} + +static void fd_turnon1(fdu_t); + +void +fd_turnon(fdu) + 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, (caddr_t)fdu, hz); /* in 1 sec its ok */ + } +} + +static void +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) + 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 +} + +int +out_fdc(fdcu, x) + fdcu_t fdcu; + int x; +{ + int baseport = fdc_data[fdcu].baseport; + int i; + + /* Check that the direction bit is set */ + i = 100000; + while ((inb(baseport+fdsts) & NE7_DIO) && i-- > 0); + if (i <= 0) return (-1); /* Floppy timed out */ + + /* Check that the floppy controller is ready for a command */ + i = 100000; + while ((inb(baseport+fdsts) & NE7_RQM) == 0 && i-- > 0); + if (i <= 0) return (-1); /* Floppy timed out */ + + /* Send the command and return */ + outb(baseport+fddata,x); + TRACE1("[0x%x->fddata]",x); + return (0); +} + +/****************************************************************************/ +/* fdopen/fdclose */ +/****************************************************************************/ +int +Fdopen(dev, flags) + dev_t dev; + int flags; +{ + fdu_t fdu = FDUNIT(minor(dev)); + int type = FDTYPE(minor(dev)); + fdc_p fdc; + +#if NFT > 0 + /* check for a tape open */ + if (type & F_TAPE_TYPE) + return(ftopen(dev, flags)); +#endif + /* check bounds */ + if (fdu >= NFD) + return(ENXIO); + fdc = fd_data[fdu].fdc; + if ((fdc == NULL) || (fd_data[fdu].type == NO_TYPE)) + return(ENXIO); + if (type > NUMDENS) + return(ENXIO); + if (type == 0) + type = fd_data[fdu].type; + else { + if (type != fd_data[fdu].type) { + switch (fd_data[fdu].type) { + case FD_360: + return(ENXIO); + case FD_720: + if ( type != FD_820 + && type != FD_800 + ) + return(ENXIO); + break; + case FD_1200: + switch (type) { + case FD_1480: + type = FD_1480in5_25; + break; + case FD_1440: + type = FD_1440in5_25; + break; + case FD_820: + type = FD_820in5_25; + break; + case FD_800: + type = FD_800in5_25; + break; + case FD_720: + type = FD_720in5_25; + break; + case FD_360: + type = FD_360in5_25; + break; + default: + return(ENXIO); + } + break; + case FD_1440: + if ( type != FD_1720 + && type != FD_1480 + && type != FD_1200 + && type != FD_820 + && type != FD_800 + && type != FD_720 + ) + return(ENXIO); + break; + } + } + } + fd_data[fdu].ft = fd_types + type - 1; + fd_data[fdu].flags |= FD_OPEN; + + return 0; +} + +int +fdclose(dev, flags) + dev_t dev; + int flags; +{ + fdu_t fdu = FDUNIT(minor(dev)); + int type = FDTYPE(minor(dev)); + +#if NFT > 0 + if (type & F_TAPE_TYPE) + return ftclose(0); +#endif + 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 * +\***************************************************************/ +static void +fdstart(fdcu) + 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); +} + +static void +fd_timeout(void *arg1) +{ + fdcu_t fdcu = (fdcu_t)arg1; + fdu_t fdu = fdc_data[fdcu].fdu; + int st0, st3, cyl; + struct buf *dp,*bp; + int s; + + dp = &fdc_data[fdcu].head; + s = splbio(); + 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; + } + fdintr(fdcu); + splx(s); +} + +/* just ensure it has the right spl */ +static void +fd_pseudointr(void *arg1) +{ + fdcu_t fdcu = (fdcu_t)arg1; + int s; + s = splbio(); + fdintr(fdcu); + splx(s); +} + +/***********************************************************************\ +* fdintr * +* keep calling the state machine until it returns a 0 * +* ALWAYS called at SPLBIO * +\***********************************************************************/ +void +fdintr(fdcu_t fdcu) +{ + fdc_p fdc = fdc_data + fdcu; +#if NFT > 0 + fdu_t fdu = fdc->fdu; + + if (fdc->flags & FDC_TAPE_BUSY) + (ftintr(fdu)); + else +#endif + while(fdstate(fdcu, fdc)) + ; +} + +/***********************************************************************\ +* The controller state machine. * +* if it returns a non zero value, it should be called again immediatly * +\***********************************************************************/ +int +fdstate(fdcu, fdc) + fdcu_t fdcu; + fdc_p fdc; +{ + int read, format, head, trac, sec = 0, i = 0, s, sectrac, cyl, st0; + unsigned long blknum; + fdu_t fdu = fdc->fdu; + fd_p fd; + register struct buf *dp,*bp; + struct fd_formb *finfo = NULL; + + 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; + format = bp->b_flags & B_FORMAT; + if(format) + finfo = (struct fd_formb *)bp->b_un.b_addr; + TRACE1("fd%d",fdu); + TRACE1("[%s]",fdstates[fdc->state]); + TRACE1("(0x%x)",fd->flags); + untimeout(fd_turnoff, (caddr_t)fdu); + timeout(fd_turnoff, (caddr_t)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; + outb(fdc->baseport+fdctl, fd->ft->trans); + /*******************************************************\ + * 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; + timeout(fd_timeout, (caddr_t)fdcu, 2 * hz); + return(0); /* will return later */ + case SEEKWAIT: + untimeout(fd_timeout, (caddr_t)fdcu); + /* allow heads to settle */ + timeout(fd_pseudointr, (caddr_t)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; + if(format) + fd->skip = (char *)&(finfo->fd_formb_cylno(0)) + - (char *)finfo; + isa_dmastart(bp->b_flags, bp->b_un.b_addr+fd->skip, + format ? bp->b_bcount : 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(format) + { + /* formatting */ + out_fdc(fdcu,/* NE7CMD_FORMAT */ 0x4d); + out_fdc(fdcu,head << 2 | fdu); + out_fdc(fdcu,finfo->fd_formb_secshift); + out_fdc(fdcu,finfo->fd_formb_nsecs); + out_fdc(fdcu,finfo->fd_formb_gaplen); + out_fdc(fdcu,finfo->fd_formb_fillbyte); + } + else + { + 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, (caddr_t)fdcu, 2 * hz); + return(0); /* will return later */ + case IOCOMPLETE: /* IO DONE, post-analyze */ + untimeout(fd_timeout, (caddr_t)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, + format ? bp->b_bcount : FDBLK, fdc->dmachan); + if (fdc->status[0]&0xF8) + { + if (fdc->status[1] & 0x10) { + /* + * Operation not completed in reasonable time. + * Just restart it, don't increment retry count. + * (vak) + */ + fdc->state = SEEKCOMPLETE; + return (1); + } + return(retrier(fdcu)); + } + /* All OK */ + fd->skip += FDBLK; + if (!format && 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->b_actf; + 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, (caddr_t)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 */ +} + +static int +retrier(fdcu) + 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: + { + dev_t sav_b_dev = bp->b_dev; + /* Trick diskerr */ + bp->b_dev = makedev(major(bp->b_dev), (FDUNIT(minor(bp->b_dev))<<3)|3); + diskerr(bp, "fd", "hard error", LOG_PRINTF, + fdc->fd->skip, (struct disklabel *)NULL); + bp->b_dev = sav_b_dev; + printf(" (ST0 %b ", fdc->status[0], NE7_ST0BITS); + printf(" ST1 %b ", fdc->status[1], NE7_ST1BITS); + printf(" ST2 %b ", fdc->status[2], NE7_ST2BITS); + printf("cyl %d hd %d sec %d)\n", + fdc->status[3], fdc->status[4], fdc->status[5]); + } + bp->b_flags |= B_ERROR; + bp->b_error = EIO; + bp->b_resid = bp->b_bcount - fdc->fd->skip; + dp->b_actf = bp->b_actf; + fdc->fd->skip = 0; + biodone(bp); + fdc->state = FINDWORK; + fdc->fd = (fd_p) 0; + fdc->fdu = -1; + /* XXX abort current command, if any. */ + return(1); + } + fdc->retry++; + return(1); +} + +static int +fdformat(dev, finfo, p) + dev_t dev; + struct fd_formb *finfo; + struct proc *p; +{ + fdu_t fdu; + fd_p fd; + + struct buf *bp; + int rv = 0, s; + + fdu = FDUNIT(minor(dev)); + fd = &fd_data[fdu]; + + /* set up a buffer header for fdstrategy() */ + bp = (struct buf *)malloc(sizeof(struct buf), M_TEMP, M_NOWAIT); + if(bp == 0) + return ENOBUFS; + bzero((void *)bp, sizeof(struct buf)); + bp->b_flags = B_BUSY | B_PHYS | B_FORMAT; + bp->b_proc = p; + bp->b_dev = dev; + + /* + * calculate a fake blkno, so fdstrategy() would initiate a + * seek to the requested cylinder + */ + bp->b_blkno = (finfo->cyl * (fd->ft->sectrac * fd->ft->heads) + + finfo->head * fd->ft->sectrac) * FDBLK / DEV_BSIZE; + + bp->b_bcount = sizeof(struct fd_idfield_data) * finfo->fd_formb_nsecs; + bp->b_un.b_addr = (caddr_t)finfo; + + /* now do the format */ + fdstrategy(bp); + + /* ...and wait for it to complete */ + s = splbio(); + while(!(bp->b_flags & B_DONE)) + { + rv = tsleep((caddr_t)bp, PRIBIO, "fdform", 20 * hz); + if(rv == EWOULDBLOCK) + break; + } + splx(s); + + if(rv == EWOULDBLOCK) + { + /* timed out */ + biodone(bp); + rv = EIO; + } + free(bp, M_TEMP); + return rv; +} + +/* + * fdioctl() from jc@irbs.UUCP (John Capo) + * i386/i386/conf.c needs to have fdioctl() declared and remove the line that + * defines fdioctl to be enxio. + * + * TODO: Reformat. + * Think about allocating buffer off stack. + * Don't pass uncast 0's and NULL's to read/write/setdisklabel(). + * Watch out for NetBSD's different *disklabel() interface. + * + * Added functionality for floppy formatting + * joerg_wunsch@uriah.sax.de (Joerg Wunsch) + */ + +int +fdioctl (dev, cmd, addr, flag, p) + dev_t dev; + int cmd; + caddr_t addr; + int flag; + struct proc *p; +{ + struct fd_type *fdt; + struct disklabel *dl; + char buffer[DEV_BSIZE]; + int error; + +#if NFT > 0 + int type = FDTYPE(minor(dev)); + + /* check for a tape ioctl */ + if (type & F_TAPE_TYPE) + return ftioctl(dev, cmd, addr, flag, p); +#endif + + error = 0; + + switch (cmd) + { + case DIOCGDINFO: + bzero(buffer, sizeof (buffer)); + dl = (struct disklabel *)buffer; + dl->d_secsize = FDBLK; + fdt = fd_data[FDUNIT(minor(dev))].ft; + dl->d_secpercyl = fdt->size / fdt->tracks; + dl->d_type = DTYPE_FLOPPY; + + if (readdisklabel(dev, fdstrategy, dl, NULL, 0, 0) == NULL) + error = 0; + else + error = EINVAL; + + *(struct disklabel *)addr = *dl; + break; + + case DIOCSDINFO: + if ((flag & FWRITE) == 0) + error = EBADF; + break; + + case DIOCWLABEL: + if ((flag & FWRITE) == 0) + error = EBADF; + break; + + case DIOCWDINFO: + if ((flag & FWRITE) == 0) + { + error = EBADF; + break; + } + + dl = (struct disklabel *)addr; + + if (error = setdisklabel ((struct disklabel *)buffer, + dl, 0, NULL)) + break; + + error = writedisklabel(dev, fdstrategy, + (struct disklabel *)buffer, NULL); + break; + + case FD_FORM: + if((flag & FWRITE) == 0) + error = EBADF; /* must be opened for writing */ + else if(((struct fd_formb *)addr)->format_version != + FD_FORMAT_VERSION) + error = EINVAL; /* wrong version of formatting prog */ + else + error = fdformat(dev, (struct fd_formb *)addr, p); + break; + + case FD_GTYPE: /* get drive type */ + *(struct fd_type *)addr = *fd_data[FDUNIT(minor(dev))].ft; + break; + + default: + error = EINVAL; + break; + } + return (error); +} + +#endif diff --git a/sys/i386/isa/fdc.h b/sys/i386/isa/fdc.h new file mode 100644 index 0000000..1542f0e --- /dev/null +++ b/sys/i386/isa/fdc.h @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 1990 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. + * + * from: @(#)fd.c 7.4 (Berkeley) 5/25/91 + * $Id:$ + * + */ + + +/***********************************************************************\ +* Per controller structure. * +\***********************************************************************/ +struct fdc_data +{ + int fdcu; /* our unit number */ + int baseport; + int dmachan; + int flags; +#define FDC_ATTACHED 0x01 +#define FDC_HASFTAPE 0x02 +#define FDC_TAPE_BUSY 0x04 + 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 */ +}; + +/***********************************************************************\ +* 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 FDUNIT(s) (((s)>>6)&03) +#define FDTYPE(s) ((s)&077) diff --git a/sys/i386/isa/fdreg.h b/sys/i386/isa/fdreg.h new file mode 100644 index 0000000..d0d69e2 --- /dev/null +++ b/sys/i386/isa/fdreg.h @@ -0,0 +1,65 @@ +/*- + * 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. + * + * from: @(#)fdreg.h 7.1 (Berkeley) 5/9/91 + * $Id: fdreg.h,v 1.4 1994/02/07 22:12:42 alm Exp $ + */ + +/* + * 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/i386/isa/ft.c b/sys/i386/isa/ft.c new file mode 100644 index 0000000..7102c49 --- /dev/null +++ b/sys/i386/isa/ft.c @@ -0,0 +1,2184 @@ +/* + * Copyright (c) 1993 Steve Gerakines + * + * This is freely redistributable software. You may do anything you wish with + * it, so long as the above notice stays intact. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR(S) 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. + * + * ft.c - QIC-40/80 floppy tape driver $Id: ft.c,v 1.9 1994/08/21 20:16:14 paul + * Exp $ + * + * + * 01/26/94 v0.3b - Jim Babb Got rid of the hard coded device selection. Moved + * (some of) the static variables into a structure for support of multiple + * devices. ( still has a way to go for 2 controllers - but closer ) Changed + * the interface with fd.c so we no longer 'steal' it's driver routine + * vectors. + * + * 10/30/93 v0.3 Fixed a couple more bugs. Reading was sometimes looping when + * an an error such as address-mark-missing was encountered. Both reading + * and writing was having more backup-and-retries than was necessary. Added + * support to get hardware info. Updated for use with FreeBSD. + * + * 09/15/93 v0.2 pl01 Fixed a bunch of bugs: extra isa_dmadone() in + * async_write() (shouldn't matter), fixed double buffering in async_req(), + * changed tape_end() in set_fdcmode() to reduce unexpected interrupts, + * changed end of track processing in async_req(), protected more of + * ftreq_rw() with an splbio(). Changed some of the ftreq_*() functions so + * that they wait for inactivity and then go, instead of aborting + * immediately. + * + * 08/07/93 v0.2 release Shifted from ftstrat to ioctl support for I/O. + * Streaming is now much more reliable. Added internal support for error + * correction, QIC-40, and variable length tapes. Random access of segments + * greatly improved. Formatting and verification support is close but still + * incomplete. + * + * 06/03/93 v0.1 Alpha release Hopefully the last re-write. Many bugs fixed, + * many remain. + * + * $Id: ft.c,v 1.9 1994/08/21 20:16:14 paul Exp $ + */ + +#include "ft.h" +#if NFT > 0 +#include "fd.h" + +#include <sys/param.h> +#include <sys/dkbad.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/uio.h> +#include <sys/ftape.h> +#include <machine/pio.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/fdreg.h> +#include <i386/isa/fdc.h> +#include <i386/isa/icu.h> +#include <i386/isa/rtc.h> +#include <i386/isa/ftreg.h> + +/* Enable or disable debugging messages. */ +#define FTDBGALL 0 /* everything */ +/* #define DPRT(a) printf a */ +#define DPRT(a) + +/* Constants private to the driver */ +#define FTPRI (PRIBIO)/* sleep priority */ + +/* The following items are needed from the fd driver. */ +extern int in_fdc(int); /* read fdc registers */ +extern int out_fdc(int, int); /* write fdc registers */ + +extern int hz; /* system clock rate */ + +/* Type of tape attached */ +/* use numbers that don't interfere with the possible floppy types */ +#define NO_TYPE 0 /* (same as NO_TYPE in fd.c) */ +/* F_TAPE_TYPE must match value in fd.c */ +#define F_TAPE_TYPE 0x020 /* bit for ft->types to indicate tape */ +#define FT_MOUNTAIN (F_TAPE_TYPE | 1) +#define FT_COLORADO (F_TAPE_TYPE | 2) + + +/* Mode FDC is currently in: tape or disk */ +enum { + FDC_TAPE_MODE, FDC_DISK_MODE +}; + +/* Command we are awaiting completion of */ +enum { + FTCMD_NONE, FTCMD_RESET, FTCMD_RECAL, FTCMD_SEEK, FTCMD_READID +}; + +/* Tape interrupt status of current request */ +enum { + FTSTS_NONE, FTSTS_SNOOZE, FTSTS_INTERRUPT, FTSTS_TIMEOUT +}; + +/* Tape I/O status */ +enum { + FTIO_READY, /* No I/O activity */ + FTIO_READING, /* Currently reading blocks */ + FTIO_RDAHEAD, /* Currently reading ahead */ + FTIO_WRITING /* Buffers are being written */ +}; + +/* Current tape mode */ +enum { + FTM_PRIMARY, /* Primary mode */ + FTM_VERIFY, /* Verify mode */ + FTM_FORMAT, /* Format mode */ + FTM_DIAG1, /* Diagnostic mode 1 */ + FTM_DIAG2 /* Diagnostic mode 2 */ +}; + +/* Tape geometries table */ +QIC_Geom ftgtbl[] = { + {0, 0, "Unformatted", "Unknown", 0, 0, 0, 0, 0}, /* XXX */ + {1, 1, "QIC-40", "205/550", 20, 68, 2176, 128, 21760}, + {1, 2, "QIC-40", "307.5/550", 20, 102, 3264, 128, 32640}, + {1, 3, "QIC-40", "295/900", 0, 0, 0, 0, 0}, /* ??? */ + {1, 4, "QIC-40", "1100/550", 20, 365, 11680, 128, 32512}, + {1, 5, "QIC-40", "1100/900", 0, 0, 0, 0, 0}, /* ??? */ + {2, 1, "QIC-80", "205/550", 28, 100, 3200, 128, 19200}, + {2, 2, "QIC-80", "307.5/550", 28, 150, 4800, 128, 19200}, + {2, 3, "QIC-80", "295/900", 0, 0, 0, 0, 0}, /* ??? */ + {2, 4, "QIC-80", "1100/550", 28, 537, 17184, 128, 32512}, + {2, 5, "QIC-80", "1100/900", 0, 0, 0, 0, 0}, /* ??? */ + {3, 1, "QIC-500", "205/550", 0, 0, 0, 0, 0}, /* ??? */ + {3, 2, "QIC-500", "307.5/550", 0, 0, 0, 0, 0}, /* ??? */ + {3, 3, "QIC-500", "295/900", 0, 0, 0, 0, 0}, /* ??? */ + {3, 4, "QIC-500", "1100/550", 0, 0, 0, 0, 0}, /* ??? */ + {3, 5, "QIC-500", "1100/900", 0, 0, 0, 0, 0} /* ??? */ +}; +#define NGEOM (sizeof(ftgtbl) / sizeof(QIC_Geom)) + +QIC_Geom *ftg = NULL; /* Current tape's geometry */ + +/* + * things relating to asynchronous commands + */ +static int astk_depth; /* async_cmd stack depth */ +static int awr_state; /* state of async write */ +static int ard_state; /* state of async read */ +static int arq_state; /* state of async request */ +static int async_retries; /* retries, one per invocation */ +static int async_func; /* function to perform */ +static int async_state; /* state current function is at */ +static int async_arg[5]; /* up to 5 arguments for async cmds */ +static int async_ret; /* return value */ + +/* List of valid async (interrupt driven) tape support functions. */ +enum { + ACMD_NONE, /* no command */ + ACMD_SEEK, /* command seek */ + ACMD_STATUS, /* report status */ + ACMD_STATE, /* wait for state bits to be true */ + ACMD_SEEKSTS, /* perform command and wait for status */ + ACMD_READID, /* read id */ + ACMD_RUNBLK /* ready tape for I/O on the given block */ +}; + +/* Call another asyncronous command from within async_cmd(). */ +#define CALL_ACMD(r,f,a,b,c,d,e) \ + astk[astk_depth].over_retries = async_retries; \ + astk[astk_depth].over_func = async_func; \ + astk[astk_depth].over_state = (r); \ + for (i = 0; i < 5; i++) \ + astk[astk_depth].over_arg[i] = async_arg[i]; \ + async_func = (f); async_state = 0; async_retries = 0; \ + async_arg[0]=(a); async_arg[1]=(b); async_arg[2]=(c); \ + async_arg[3]=(d); async_arg[4]=(e); \ + astk_depth++; \ + goto restate + +/* Perform an asyncronous command from outside async_cmd(). */ +#define ACMD_FUNC(r,f,a,b,c,d,e) over_async = (r); astk_depth = 0; \ + async_func = (f); async_state = 0; async_retries = 0; \ + async_arg[0]=(a); async_arg[1]=(b); async_arg[2]=(c); \ + async_arg[3]=(d); async_arg[4]=(e); \ + async_cmd(ftu); \ + return + +/* Various wait channels */ +static struct { + int buff_avail; + int iosts_change; + int long_delay; + int intr_wait; +} ftsem; + +/***********************************************************************\ +* Per controller structure. * +\***********************************************************************/ +extern struct fdc_data fdc_data[NFDC]; + +/***********************************************************************\ +* Per tape drive structure. * +\***********************************************************************/ +struct ft_data { + struct fdc_data *fdc; /* pointer to controller structure */ + int ftsu; /* this units number on this controller */ + int type; /* Drive type (Mountain, Colorado) */ + /* QIC_Geom *ftg; *//* pointer to Current tape's geometry */ + int flags; + int cmd_wait; /* Command we are awaiting completion + * of */ + int sts_wait; /* Tape interrupt status of current + * request */ + int io_sts; /* Tape I/O status */ + int mode; + int pcn; /* present cylinder number */ + int attaching; /* true when ft is attaching */ + unsigned char *xptr; /* pointer to buffer blk to xfer */ + int xcnt; /* transfer count */ + int xblk; /* block number to transfer */ + SegReq *curseg; /* Current segment to do I/O on */ + SegReq *bufseg; /* Buffered segment to r/w ahead */ + /* the next 3 should be defines in 'flags' */ + int active; /* TRUE if transfer is active */ + int rdonly; /* TRUE if tape is read-only */ + int newcart;/* TRUE if new cartridge detected */ + int laststs;/* last reported status code */ + int lastcfg;/* last reported QIC config */ + int lasterr;/* last QIC error code */ + int lastpos;/* last known segment number */ + int moving; /* TRUE if tape is moving */ + int rid[7]; /* read_id return values */ + +} ft_data[NFT]; + +/***********************************************************************\ +* Throughout this file the following conventions will be used: * +* ft is a pointer to the ft_data struct for the drive in question * +* fdc is a pointer to the fdc_data struct for the controller * +* ftu is the tape drive unit number * +* fdcu is the floppy controller unit number * +* ftsu is the tape drive unit number on that controller. (sub-unit) * +\***********************************************************************/ + + +typedef int ftu_t; +typedef int ftsu_t; +typedef struct ft_data *ft_p; + +#define id_physid id_scsiid /* this biotab field doubles as a field */ +/* for the physical unit number on the controller */ + +int ftopen(dev_t, int); +int ftclose(dev_t, int); +void ftstrategy(struct buf *); +int ftioctl(dev_t, int, caddr_t, int, struct proc *); +int ftdump(dev_t); +int ftsize(dev_t); +static timeout_t ft_timeout; +void async_cmd(ftu_t); +void async_req(ftu_t, int); +void async_read(ftu_t, int); +void async_write(ftu_t, int); +void tape_start(ftu_t); +void tape_end(ftu_t); +void tape_inactive(ftu_t); + + + + + +/* + * Probe/attach floppy tapes. + */ +int +ftattach(isadev, fdup) + struct isa_device *isadev, *fdup; +{ + fdcu_t fdcu = isadev->id_unit; /* fdc active unit */ + fdc_p fdc = fdc_data + fdcu; /* pointer to controller + * structure */ + ftu_t ftu = fdup->id_unit; + ft_p ft; + ftsu_t ftsu = fdup->id_physid; + + if (ftu >= NFT) + return 0; + ft = &ft_data[ftu]; + /* Probe for tape */ + ft->attaching = 1; + ft->type = NO_TYPE; + ft->fdc = fdc; + ft->ftsu = ftsu; + + tape_start(ftu); /* ready controller for tape */ + tape_cmd(ftu, QC_COL_ENABLE1); + tape_cmd(ftu, QC_COL_ENABLE2); + if (tape_status(ftu) >= 0) { + ft->type = FT_COLORADO; + fdc->flags |= FDC_HASFTAPE; + printf(" [%d: ft%d: Colorado tape]", + fdup->id_physid, fdup->id_unit); + tape_cmd(ftu, QC_COL_DISABLE); + goto out; + } + tape_start(ftu); /* ready controller for tape */ + tape_cmd(ftu, QC_MTN_ENABLE1); + tape_cmd(ftu, QC_MTN_ENABLE2); + if (tape_status(ftu) >= 0) { + ft->type = FT_MOUNTAIN; + fdc->flags |= FDC_HASFTAPE; + printf(" [%d: ft%d: Mountain tape]", + fdup->id_physid, fdup->id_unit); + tape_cmd(ftu, QC_MTN_DISABLE); + goto out; + } +out: + tape_end(ftu); + ft->attaching = 0; + return (ft->type); +} + + +/* + * Perform common commands asynchronously. + */ +void +async_cmd(ftu_t ftu) +{ + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; + int cmd, i, st0, st3, pcn; + static int bitn, retval, retpos, nbits, newcn; + static struct { + int over_func; + int over_state; + int over_retries; + int over_arg[5]; + } astk[15]; + static int wanttrk, wantblk, wantdir; + static int curpos, curtrk, curblk, curdir, curdiff; + static int errcnt = 0; + +restate: +#if FTDBGALL + DPRT(("async_cmd state: func: %d state: %d\n", async_func, async_state)); +#endif + switch (async_func) { + case ACMD_SEEK: + /* + * Arguments: 0 - command to perform + */ + switch (async_state) { + case 0: + cmd = async_arg[0]; +#if FTDBGALL + DPRT(("===>async_seek cmd = %d\n", cmd)); +#endif + newcn = (cmd <= ft->pcn) ? ft->pcn - cmd : ft->pcn + cmd; + async_state = 1; + i = 0; + if (out_fdc(fdcu, NE7CMD_SEEK) < 0) + i = 1; + if (!i && out_fdc(fdcu, 0x00) < 0) + i = 1; + if (!i && out_fdc(fdcu, newcn) < 0) + i = 1; + if (i) { + if (++async_retries >= 10) { + DPRT(("ft%d: async_cmd command seek failed!!\n", ftu)); + goto complete; + } + DPRT(("ft%d: async_cmd command seek retry...\n", ftu)); + async_state = 0; + goto restate; + } + break; + case 1: + out_fdc(fdcu, NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + pcn = in_fdc(fdcu); + if (st0 < 0 || pcn < 0 || newcn != pcn) { + if (++async_retries >= 10) { + DPRT(("ft%d: async_cmd seek retries exceeded\n", ftu)); + goto complete; + } + DPRT(("ft%d: async_cmd command bad st0=$%02x pcn=$%02x\n", + ftu, st0, pcn)); + async_state = 0; + timeout(ft_timeout, (caddr_t) ftu, hz / 10); + break; + } + if (st0 & 0x20) { /* seek done */ + ft->pcn = pcn; + } +#if FTDBGALL + else + DPRT(("ft%d: async_seek error st0 = $%02x pcn = %d\n", + ftu, st0, pcn)); +#endif + if (async_arg[1]) + goto complete; + async_state = 2; + timeout(ft_timeout, (caddr_t) ftu, hz / 50); + break; + case 2: + goto complete; + /* NOTREACHED */ + } + break; + + case ACMD_STATUS: + /* + * Arguments: 0 - command to issue report from 1 - number of + * bits modifies: bitn, retval, st3 + */ + switch (async_state) { + case 0: + bitn = 0; + retval = 0; + cmd = async_arg[0]; + nbits = async_arg[1]; + DPRT(("async_status got cmd = %d nbits = %d\n", cmd, nbits)); + CALL_ACMD(5, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0); + /* NOTREACHED */ + case 1: + out_fdc(fdcu, NE7CMD_SENSED); + out_fdc(fdcu, 0x00); + st3 = in_fdc(fdcu); + if (st3 < 0) { + DPRT(("ft%d: async_status timed out on bit %d r=$%02x\n", + ftu, bitn, retval)); + async_ret = -1; + goto complete; + } + if ((st3 & 0x10) != 0) + retval |= (1 << bitn); + bitn++; + if (bitn >= (nbits + 2)) { + if ((retval & 1) && (retval & (1 << (nbits + 1)))) { + async_ret = (retval & ~(1 << (nbits + 1))) >> 1; + if (async_arg[0] == QC_STATUS && async_arg[2] == 0 && + (async_ret & (QS_ERROR | QS_NEWCART))) { + async_state = 2; + goto restate; + } + DPRT(("async status got $%04x ($%04x)\n", async_ret, retval)); + } else { + DPRT(("ft%d: async_status failed: retval=$%04x nbits=%d\n", + ftu, retval, nbits)); + async_ret = -2; + } + goto complete; + } + CALL_ACMD(1, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0); + /* NOTREACHED */ + case 2: + if (async_ret & QS_NEWCART) + ft->newcart = 1; + CALL_ACMD(3, ACMD_STATUS, QC_ERRCODE, 16, 1, 0, 0); + case 3: + ft->lasterr = async_ret; + if ((ft->lasterr & QS_NEWCART) == 0 && ft->lasterr) { + DPRT(("ft%d: QIC error %d occurred on cmd %d\n", + ftu, ft->lasterr & 0xff, ft->lasterr >> 8)); + } + cmd = async_arg[0]; + nbits = async_arg[1]; + CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 1, 0, 0); + case 4: + goto complete; + case 5: + CALL_ACMD(6, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0); + case 6: + CALL_ACMD(7, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0); + case 7: + CALL_ACMD(8, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0); + case 8: + cmd = async_arg[0]; + CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0, 0, 0); + } + break; + + case ACMD_STATE: + /* + * Arguments: 0 - status bits to check + */ + switch (async_state) { + case 0: + CALL_ACMD(1, ACMD_STATUS, QC_STATUS, 8, 0, 0, 0); + case 1: + if ((async_ret & async_arg[0]) != 0) + goto complete; + async_state = 0; + if (++async_retries == 360) { /* 90 secs. */ + DPRT(("ft%d: acmd_state exceeded retry count\n", ftu)); + goto complete; + } + timeout(ft_timeout, (caddr_t) ftu, hz / 4); + break; + } + break; + + case ACMD_SEEKSTS: + /* + * Arguments: 0 - command to perform 1 - status bits to check + * 2 - (optional) seconds to wait until completion + */ + switch (async_state) { + case 0: + cmd = async_arg[0]; + async_retries = (async_arg[2]) ? (async_arg[2] * 4) : 10; + CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0, 0, 0); + case 1: + CALL_ACMD(2, ACMD_STATUS, QC_STATUS, 8, 0, 0, 0); + case 2: + if ((async_ret & async_arg[1]) != 0) + goto complete; + if (--async_retries == 0) { + DPRT(("ft%d: acmd_seeksts retries exceeded\n", ftu)); + goto complete; + } + async_state = 1; + timeout(ft_timeout, (caddr_t) ftu, hz / 4); + break; + } + break; + + case ACMD_READID: + /* + * Arguments: (none) + */ + switch (async_state) { + case 0: + if (!ft->moving) { + CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + /* NOTREACHED */ + } + async_state = 1; + out_fdc(fdcu, 0x4a); /* READ_ID */ + out_fdc(fdcu, 0); + break; + case 1: + for (i = 0; i < 7; i++) + ft->rid[i] = in_fdc(fdcu); + async_ret = (ft->rid[3] * ftg->g_fdtrk) + + (ft->rid[4] * ftg->g_fdside) + ft->rid[5] - 1; + DPRT(("readid st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d\n", + ft->rid[0], ft->rid[1], ft->rid[2], ft->rid[3], + ft->rid[4], ft->rid[5], async_ret)); + if ((ft->rid[0] & 0xc0) == 0x40) { + if (++errcnt >= 10) { + DPRT(("ft%d: acmd_readid errcnt exceeded\n", fdcu)); + async_ret = ft->lastpos; + errcnt = 0; + goto complete; + } + if (errcnt > 2) { + ft->moving = 0; + CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + } + DPRT(("readid retry...\n")); + async_state = 0; + goto restate; + } + if ((async_ret % ftg->g_blktrk) == (ftg->g_blktrk - 1)) { + DPRT(("acmd_readid detected last block on track\n")); + retpos = async_ret; + CALL_ACMD(2, ACMD_STATE, QS_BOT | QS_EOT, 0, 0, 0, 0); + /* NOTREACHED */ + } + ft->lastpos = async_ret; + errcnt = 0; + goto complete; + /* NOTREACHED */ + case 2: + CALL_ACMD(3, ACMD_STATE, QS_READY, 0, 0, 0, 0); + case 3: + ft->moving = 0; + async_ret = retpos + 1; + goto complete; + case 4: + CALL_ACMD(5, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0); + case 5: + ft->moving = 1; + async_state = 0; + timeout(ft_timeout, (caddr_t) ftu, hz / 10); /* XXX */ + break; + } + break; + + case ACMD_RUNBLK: + /* + * Arguments: 0 - block number I/O will be performed on + * + * modifies: curpos + */ + switch (async_state) { + case 0: + wanttrk = async_arg[0] / ftg->g_blktrk; + wantblk = async_arg[0] % ftg->g_blktrk; + wantdir = wanttrk & 1; + ft->moving = 0; + CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + case 1: + curtrk = wanttrk; + curdir = curtrk & 1; + DPRT(("Changing to track %d\n", wanttrk)); + CALL_ACMD(2, ACMD_SEEK, QC_SEEKTRACK, 0, 0, 0, 0); + case 2: + cmd = wanttrk + 2; + CALL_ACMD(3, ACMD_SEEKSTS, cmd, QS_READY, 0, 0, 0); + case 3: + CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 0, 0, 0); + case 4: + ft->laststs = async_ret; + if (wantblk == 0) { + curblk = 0; + cmd = (wantdir) ? QC_SEEKEND : QC_SEEKSTART; + CALL_ACMD(6, ACMD_SEEKSTS, cmd, QS_READY, 90, 0, 0); + } + if (ft->laststs & QS_BOT) { + DPRT(("Tape is at BOT\n")); + curblk = (wantdir) ? 4800 : 0; + async_state = 6; + goto restate; + } + if (ft->laststs & QS_EOT) { + DPRT(("Tape is at EOT\n")); + curblk = (wantdir) ? 0 : 4800; + async_state = 6; + goto restate; + } + CALL_ACMD(5, ACMD_READID, 0, 0, 0, 0, 0); + case 5: + curtrk = (async_ret + 1) / ftg->g_blktrk; + curblk = (async_ret + 1) % ftg->g_blktrk; + DPRT(("gotid: curtrk=%d wanttrk=%d curblk=%d wantblk=%d\n", + curtrk, wanttrk, curblk, wantblk)); + if (curtrk != wanttrk) { /* oops! */ + DPRT(("oops!! wrong track!\n")); + CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + } + async_state = 6; + goto restate; + case 6: + DPRT(("curtrk = %d nextblk = %d\n", curtrk, curblk)); + if (curblk == wantblk) { + ft->lastpos = curblk - 1; + async_ret = ft->lastpos; + if (ft->moving) + goto complete; + CALL_ACMD(7, ACMD_STATE, QS_READY, 0, 0, 0, 0); + } + if (curblk > wantblk) { /* passed it */ + ft->moving = 0; + CALL_ACMD(10, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + } + if ((wantblk - curblk) <= 96) { /* approaching it */ + CALL_ACMD(5, ACMD_READID, 0, 0, 0, 0, 0); + } + /* way up ahead */ + ft->moving = 0; + CALL_ACMD(14, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + break; + case 7: + ft->moving = 1; + CALL_ACMD(8, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0); + break; + case 8: + async_state = 9; + timeout(ft_timeout, (caddr_t) ftu, hz / 10); /* XXX */ + break; + case 9: + goto complete; + case 10: + curdiff = ((curblk - wantblk) / QCV_BLKSEG) + 2; + if (curdiff >= ftg->g_segtrk) + curdiff = ftg->g_segtrk - 1; + DPRT(("pos %d past %d, reverse %d\n", curblk, wantblk, curdiff)); + CALL_ACMD(11, ACMD_SEEK, QC_SEEKREV, 0, 0, 0, 0); + case 11: + DPRT(("reverse 1 done\n")); + CALL_ACMD(12, ACMD_SEEK, (curdiff & 0xf) + 2, 0, 0, 0, 0); + case 12: + DPRT(("reverse 2 done\n")); + CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff >> 4) & 0xf) + 2, QS_READY, 90, 0, 0); + case 13: + CALL_ACMD(5, ACMD_READID, 0, 0, 0, 0, 0); + case 14: + curdiff = ((wantblk - curblk) / QCV_BLKSEG) - 2; + if (curdiff < 0) + curdiff = 0; + DPRT(("pos %d before %d, forward %d\n", curblk, wantblk, curdiff)); + CALL_ACMD(15, ACMD_SEEK, QC_SEEKFWD, 0, 0, 0, 0); + case 15: + DPRT(("forward 1 done\n")); + CALL_ACMD(16, ACMD_SEEK, (curdiff & 0xf) + 2, 0, 0, 0, 0); + case 16: + DPRT(("forward 2 done\n")); + CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff >> 4) & 0xf) + 2, QS_READY, 90, 0, 0); + } + break; + } + + return; + +complete: + if (astk_depth) { + astk_depth--; + async_retries = astk[astk_depth].over_retries; + async_func = astk[astk_depth].over_func; + async_state = astk[astk_depth].over_state; + for (i = 0; i < 5; i++) + async_arg[i] = astk[astk_depth].over_arg[i]; + goto restate; + } + async_func = ACMD_NONE; + async_state = 0; + switch (ft->io_sts) { + case FTIO_READY: + async_req(ftu, 2); + break; + case FTIO_READING: + async_read(ftu, 2); + break; + case FTIO_WRITING: + async_write(ftu, 2); + break; + default: + DPRT(("ft%d: bad async_cmd ending I/O state!\n", ftu)); + break; + } +} + + +/* + * Entry point for the async request processor. + */ +void +async_req(ftu_t ftu, int from) +{ + ft_p ft = &ft_data[ftu]; + SegReq *sp; + static int over_async, lastreq, domore; + int cmd; + + if (from == 2) + arq_state = over_async; + +restate: + switch (arq_state) { + case 0: /* Process segment */ + ft->io_sts = ft->curseg->reqtype; + if (ft->io_sts == FTIO_WRITING) + async_write(ftu, from); + else + async_read(ftu, from); + if (ft->io_sts != FTIO_READY) + return; + + /* Swap buffered and current segment */ + lastreq = ft->curseg->reqtype; + ft->curseg->reqtype = FTIO_READY; + sp = ft->curseg; + ft->curseg = ft->bufseg; + ft->bufseg = sp; + + wakeup((caddr_t) & ftsem.buff_avail); + + /* Detect end of track */ + if (((ft->xblk / QCV_BLKSEG) % ftg->g_segtrk) == 0) { + domore = (ft->curseg->reqtype != FTIO_READY); + ACMD_FUNC(2, ACMD_STATE, QS_BOT | QS_EOT, 0, 0, 0, 0); + } + arq_state = 1; + goto restate; + + case 1: /* Next request */ + if (ft->curseg->reqtype != FTIO_READY) { + ft->curseg->reqcrc = 0; + arq_state = ard_state = awr_state = 0; + ft->xblk = ft->curseg->reqblk; + ft->xcnt = 0; + ft->xptr = ft->curseg->buff; + DPRT(("I/O reqblk = %d\n", ft->curseg->reqblk)); + goto restate; + } + if (lastreq == FTIO_READING) { + ft->curseg->reqtype = FTIO_RDAHEAD; + ft->curseg->reqblk = ft->xblk; + ft->curseg->reqcrc = 0; + ft->curseg->reqcan = 0; + bzero(ft->curseg->buff, QCV_SEGSIZE); + arq_state = ard_state = awr_state = 0; + ft->xblk = ft->curseg->reqblk; + ft->xcnt = 0; + ft->xptr = ft->curseg->buff; + DPRT(("Processing readahead reqblk = %d\n", ft->curseg->reqblk)); + goto restate; + } + if (ft->moving) { + DPRT(("No more I/O.. Stopping.\n")); + ACMD_FUNC(7, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0); + break; + } + arq_state = 7; + goto restate; + + case 2: /* End of track */ + ft->moving = 0; + ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0, 0, 0); + break; + + case 3: + DPRT(("async_req seek head to track %d\n", ft->xblk / ftg->g_blktrk)); + ACMD_FUNC(4, ACMD_SEEK, QC_SEEKTRACK, 0, 0, 0, 0); + break; + + case 4: + cmd = (ft->xblk / ftg->g_blktrk) + 2; + if (domore) { + ACMD_FUNC(5, ACMD_SEEKSTS, cmd, QS_READY, 0, 0, 0); + } else { + ACMD_FUNC(7, ACMD_SEEKSTS, cmd, QS_READY, 0, 0, 0); + } + break; + + case 5: + ft->moving = 1; + ACMD_FUNC(6, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0); + break; + + case 6: + arq_state = 1; + timeout(ft_timeout, (caddr_t) ftu, hz / 10); /* XXX */ + break; + + case 7: + ft->moving = 0; + + /* Check one last time to see if a request came in. */ + if (ft->curseg->reqtype != FTIO_READY) { + DPRT(("async_req: Never say no!\n")); + arq_state = 1; + goto restate; + } + /* Time to rest. */ + ft->active = 0; + wakeup((caddr_t) & ftsem.iosts_change); /* wakeup those who want + * an i/o chg */ + break; + } +} + +/* + * Entry for async read. + */ +void +async_read(ftu_t ftu, int from) +{ + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + unsigned long paddr; + int i, cmd, newcn, rddta[7]; + int st0, pcn, where; + static int over_async; + + if (from == 2) + ard_state = over_async; + +restate: +#if FTDBGALL + DPRT(("async_read: state: %d from = %d\n", ard_state, from)); +#endif + switch (ard_state) { + case 0: /* Start off */ + /* If tape is not at desired position, stop and locate */ + if (ft->lastpos != (ft->xblk - 1)) { + DPRT(("ft%d: position unknown: lastpos:%d ft->xblk:%d\n", + ftu, ft->lastpos, ft->xblk)); + ACMD_FUNC(1, ACMD_RUNBLK, ft->xblk, 0, 0, 0, 0); + } + /* Tape is in position but stopped. */ + if (!ft->moving) { + DPRT(("async_read ******STARTING TAPE\n")); + ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0, 0, 0); + } + ard_state = 1; + goto restate; + + case 1: /* Start DMA */ + /* Tape is now moving and in position-- start DMA now! */ + isa_dmastart(B_READ, ft->xptr, QCV_BLKSIZE, 2); + out_fdc(fdcu, 0x66); /* read */ + out_fdc(fdcu, 0x00); /* unit */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdside) / ftg->g_fdtrk); /* cylinder */ + out_fdc(fdcu, ft->xblk / ftg->g_fdside); /* head */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* sector */ + out_fdc(fdcu, 0x03); /* 1K sectors */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* count */ + out_fdc(fdcu, 0x74); /* gap length */ + out_fdc(fdcu, 0xff); /* transfer size */ + ard_state = 2; + break; + + case 2: /* DMA completed */ + /* Transfer complete, get status */ + for (i = 0; i < 7; i++) + rddta[i] = in_fdc(fdcu); + isa_dmadone(B_READ, ft->xptr, QCV_BLKSIZE, 2); + +#if FTDBGALL + /* Compute where the controller thinks we are */ + where = (rddta[3] * ftg->g_fdtrk) + (rddta[4] * ftg->g_fdside) + rddta[5] - 1; + DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n", + rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5], + where, ft->xblk)); +#endif + + /* Check for errors */ + if ((rddta[0] & 0xc0) != 0x00) { + if (rddta[1] & 0x04) { + /* Probably wrong position */ + ft->lastpos = ft->xblk; + ard_state = 0; + goto restate; + } else { + /* CRC/Address-mark/Data-mark, et. al. */ + DPRT(("ft%d: CRC error on block %d\n", fdcu, ft->xblk)); + ft->curseg->reqcrc |= (1 << ft->xcnt); + } + } + /* Otherwise, transfer completed okay. */ + ft->lastpos = ft->xblk; + ft->xblk++; + ft->xcnt++; + ft->xptr += QCV_BLKSIZE; + if (ft->xcnt < QCV_BLKSEG && ft->curseg->reqcan == 0) { + ard_state = 0; + goto restate; + } + DPRT(("Read done.. Cancel = %d\n", ft->curseg->reqcan)); + ft->io_sts = FTIO_READY; + break; + + case 3: + ft->moving = 1; + ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0); + break; + + case 4: + ard_state = 1; + timeout(ft_timeout, (caddr_t) ftu, hz / 10); /* XXX */ + break; + + default: + DPRT(("ft%d: bad async_read state %d!!\n", ftu, ard_state)); + break; + } +} + + +/* + * Entry for async write. If from is 0, this came from the interrupt + * routine, if it's 1 then it was a timeout, if it's 2, then an async_cmd + * completed. + */ +void +async_write(ftu_t ftu, int from) +{ + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + unsigned long paddr; + int i, cmd, newcn, rddta[7]; + int st0, pcn, where; + static int over_async; + static int retries = 0; + + if (from == 2) + awr_state = over_async; + +restate: +#if FTDBGALL + DPRT(("async_write: state: %d from = %d\n", awr_state, from)); +#endif + switch (awr_state) { + case 0: /* Start off */ + /* If tape is not at desired position, stop and locate */ + if (ft->lastpos != (ft->xblk - 1)) { + DPRT(("ft%d: position unknown: lastpos:%d ft->xblk:%d\n", + ftu, ft->lastpos, ft->xblk)); + ACMD_FUNC(1, ACMD_RUNBLK, ft->xblk, 0, 0, 0, 0); + } + /* Tape is in position but stopped. */ + if (!ft->moving) { + DPRT(("async_write ******STARTING TAPE\n")); + ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0, 0, 0); + } + awr_state = 1; + goto restate; + + case 1: /* Start DMA */ + /* Tape is now moving and in position-- start DMA now! */ + isa_dmastart(B_WRITE, ft->xptr, QCV_BLKSIZE, 2); + out_fdc(fdcu, 0x45); /* write */ + out_fdc(fdcu, 0x00); /* unit */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdside) / ftg->g_fdtrk); /* cylinder */ + out_fdc(fdcu, ft->xblk / ftg->g_fdside); /* head */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* sector */ + out_fdc(fdcu, 0x03); /* 1K sectors */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* count */ + out_fdc(fdcu, 0x74); /* gap length */ + out_fdc(fdcu, 0xff); /* transfer size */ + awr_state = 2; + break; + + case 2: /* DMA completed */ + /* Transfer complete, get status */ + for (i = 0; i < 7; i++) + rddta[i] = in_fdc(fdcu); + isa_dmadone(B_WRITE, ft->xptr, QCV_BLKSIZE, 2); + +#if FTDBGALL + /* Compute where the controller thinks we are */ + where = (rddta[3] * ftg->g_fdtrk) + (rddta[4] * ftg->g_fdside) + rddta[5] - 1; + DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n", + rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5], + where, ft->xblk)); +#endif + + /* Check for errors */ + if ((rddta[0] & 0xc0) != 0x00) { + if (rddta[1] & 0x04) { + /* Probably wrong position */ + ft->lastpos = ft->xblk; + awr_state = 0; + goto restate; + } else if (retries < 5) { + /* Something happened -- try again */ + ft->lastpos = ft->xblk; + awr_state = 0; + retries++; + goto restate; + } else { + /* + * Retries failed. Note the unrecoverable + * error. Marking the block as bad is fairly + * useless. + */ + printf("ft%d: unrecoverable write error on block %d\n", + ftu, ft->xblk); + ft->curseg->reqcrc |= (1 << ft->xcnt); + } + } + /* Otherwise, transfer completed okay. */ + retries = 0; + ft->lastpos = ft->xblk; + ft->xblk++; + ft->xcnt++; + ft->xptr += QCV_BLKSIZE; + if (ft->xcnt < QCV_BLKSEG) { + awr_state = 0; /* next block */ + goto restate; + } +#if FTDBGALL + DPRT(("Write done.\n")); +#endif + ft->io_sts = FTIO_READY; + break; + + case 3: + ft->moving = 1; + ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0); + break; + + case 4: + awr_state = 1; + timeout(ft_timeout, (caddr_t) ftu, hz / 10); /* XXX */ + break; + + default: + DPRT(("ft%d: bad async_write state %d!!\n", ftu, awr_state)); + break; + } +} + + +/* + * Interrupt handler for active tape. Bounced off of fdintr(). + */ +int +ftintr(ftu_t ftu) +{ + int st0, pcn, i; + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + st0 = 0; + pcn = 0; + + /* I/O segment transfer completed */ + if (ft->active) { + if (async_func != ACMD_NONE) { + async_cmd(ftu); + return (1); + } +#if FTDBGALL + DPRT(("Got request interrupt\n")); +#endif + async_req(ftu, 0); + return (1); + } + /* Get interrupt status */ + if (ft->cmd_wait != FTCMD_READID) { + out_fdc(fdcu, NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + pcn = in_fdc(fdcu); + } + if (ft->cmd_wait == FTCMD_NONE || ft->sts_wait != FTSTS_SNOOZE) { +huh_what: + printf("ft%d: unexpected interrupt; st0 = $%02x pcn = %d\n", + ftu, st0, pcn); + return (1); + } + switch (ft->cmd_wait) { + case FTCMD_RESET: + ft->sts_wait = FTSTS_INTERRUPT; + wakeup((caddr_t) & ftsem.intr_wait); + break; + case FTCMD_RECAL: + case FTCMD_SEEK: + if (st0 & 0x20) { /* seek done */ + ft->sts_wait = FTSTS_INTERRUPT; + ft->pcn = pcn; + wakeup((caddr_t) & ftsem.intr_wait); + } +#if FTDBGALL + else + DPRT(("ft%d: seek error st0 = $%02x pcn = %d\n", + ftu, st0, pcn)); +#endif + break; + case FTCMD_READID: + for (i = 0; i < 7; i++) + ft->rid[i] = in_fdc(fdcu); + ft->sts_wait = FTSTS_INTERRUPT; + wakeup((caddr_t) & ftsem.intr_wait); + break; + + default: + goto huh_what; + } + + return (1); +} + +/* + * Interrupt timeout routine. + */ +static void +ft_timeout(void *arg1) +{ + int s; + ftu_t ftu = (ftu_t) arg1; + ft_p ft = &ft_data[ftu]; + + s = splbio(); + if (ft->active) { + if (async_func != ACMD_NONE) { + async_cmd(ftu); + splx(s); + return; + } + async_req(ftu, 1); + } else { + ft->sts_wait = FTSTS_TIMEOUT; + wakeup((caddr_t) & ftsem.intr_wait); + } + splx(s); +} + +/* + * Wait for a particular interrupt to occur. ftintr() will wake us up if it + * sees what we want. Otherwise, time out and return error. Should always + * disable ints before trigger is sent and calling here. + */ +int +ftintr_wait(ftu_t ftu, int cmd, int ticks) +{ + int retries, st0, pcn; + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + ft->cmd_wait = cmd; + ft->sts_wait = FTSTS_SNOOZE; + + /* At attach time, we can't rely on having interrupts serviced */ + if (ft->attaching) { + switch (cmd) { + case FTCMD_RESET: + DELAY(100); + ft->sts_wait = FTSTS_INTERRUPT; + goto intrdone; + case FTCMD_RECAL: + case FTCMD_SEEK: + for (retries = 0; retries < 10000; retries++) { + out_fdc(fdcu, NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + pcn = in_fdc(fdcu); + if (st0 & 0x20) { + ft->sts_wait = FTSTS_INTERRUPT; + ft->pcn = pcn; + goto intrdone; + } + DELAY(100); + } + break; + } + ft->sts_wait = FTSTS_TIMEOUT; + goto intrdone; + } + if (ticks) + timeout(ft_timeout, (caddr_t) ftu, ticks); + tsleep((caddr_t) & ftsem.intr_wait, FTPRI, "ftwait", 0); + +intrdone: + if (ft->sts_wait == FTSTS_TIMEOUT) { /* timeout */ + if (ft->cmd_wait != FTCMD_RESET) + DPRT(("ft%d: timeout on command %d\n", ftu, ft->cmd_wait)); + ft->cmd_wait = FTCMD_NONE; + ft->sts_wait = FTSTS_NONE; + return (1); + } + /* got interrupt */ + if (ft->attaching == 0 && ticks) + untimeout(ft_timeout, (caddr_t) ftu); + ft->cmd_wait = FTCMD_NONE; + ft->sts_wait = FTSTS_NONE; + return (0); +} + +/* + * Recalibrate tape drive. Parameter totape is true, if we should + * recalibrate to tape drive settings. + */ +int +tape_recal(ftu_t ftu, int totape) +{ + int s; + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + DPRT(("tape_recal start\n")); + + out_fdc(fdcu, NE7CMD_SPECIFY); + out_fdc(fdcu, (totape) ? 0xAD : 0xDF); + out_fdc(fdcu, 0x02); + + s = splbio(); + out_fdc(fdcu, NE7CMD_RECAL); + out_fdc(fdcu, 0x00); + + if (ftintr_wait(ftu, FTCMD_RECAL, hz)) { + splx(s); + DPRT(("ft%d: recalibrate timeout\n", ftu)); + return (1); + } + splx(s); + + out_fdc(fdcu, NE7CMD_SPECIFY); + out_fdc(fdcu, (totape) ? 0xFD : 0xDF); + out_fdc(fdcu, 0x02); + + DPRT(("tape_recal end\n")); + return (0); +} + +static void +state_timeout(void *arg1) +{ + ftu_t ftu = (ftu_t) arg1; + + wakeup((caddr_t) & ftsem.long_delay); +} + +/* + * Wait for a particular tape status to be met. If all is TRUE, then all + * states must be met, otherwise any state can be met. + */ +int +tape_state(ftu_t ftu, int all, int mask, int seconds) +{ + int r, tries, maxtries; + + maxtries = (seconds) ? (4 * seconds) : 1; + for (tries = 0; tries < maxtries; tries++) { + r = tape_status(ftu); + if (r >= 0) { + if (all && (r & mask) == mask) + return (r); + if ((r & mask) != 0) + return (r); + } + if (seconds) { + timeout(state_timeout, (caddr_t) ftu, hz / 4); + tsleep((caddr_t) & ftsem.long_delay, FTPRI, "ftstate", 0); + } + } + DPRT(("ft%d: tape_state failed on mask=$%02x maxtries=%d\n", + ftu, mask, maxtries)); + return (-1); +} + +/* + * Send a QIC command to tape drive, wait for completion. + */ +int +tape_cmd(ftu_t ftu, int cmd) +{ + int newcn; + int retries = 0; + int s; + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + DPRT(("===> tape_cmd: %d\n", cmd)); + newcn = (cmd <= ft->pcn) ? ft->pcn - cmd : ft->pcn + cmd; + +retry: + + /* Perform seek */ + s = splbio(); + out_fdc(fdcu, NE7CMD_SEEK); + out_fdc(fdcu, 0x00); + out_fdc(fdcu, newcn); + + if (ftintr_wait(ftu, FTCMD_SEEK, hz)) { + DPRT(("ft%d: tape_cmd seek timeout\n", ftu)); +redo: + splx(s); + if (++retries < 5) + goto retry; + DPRT(("ft%d: tape_cmd seek failed!\n", ftu)); + return (1); + } + splx(s); + + if (ft->pcn != newcn) { + DPRT(("ft%d: bad seek in tape_cmd; pcn = %d newcn = %d\n", + ftu, ft->pcn, newcn)); + goto redo; + } + DELAY(2500); + return (0); +} + +/* + * Return status of tape drive + */ +int +tape_status(ftu_t ftu) +{ + int r, err, tries; + ft_p ft = &ft_data[ftu]; + + for (r = -1, tries = 0; r < 0 && tries < 3; tries++) + r = qic_status(ftu, QC_STATUS, 8); + if (tries == 3) + return (-1); + DPRT(("tape_status got $%04x\n", r)); + ft->laststs = r; + + if (r & (QS_ERROR | QS_NEWCART)) { + if (r & QS_NEWCART) + ft->newcart = 1; + err = qic_status(ftu, QC_ERRCODE, 16); + ft->lasterr = err; + if ((r & QS_NEWCART) == 0 && err && ft->attaching == 0) { + DPRT(("ft%d: QIC error %d occurred on cmd %d\n", + ftu, err & 0xff, err >> 8)); + } + r = qic_status(ftu, QC_STATUS, 8); + ft->laststs = r; + DPRT(("tape_status got error code $%04x new sts = $%02x\n", err, r)); + } + ft->rdonly = (r & QS_RDONLY); + return (r); +} + +/* + * Transfer control to tape drive. + */ +void +tape_start(ftu_t ftu) +{ + ft_p ft = &ft_data[ftu]; + fdc_p fdc = ft->fdc; + int s; + + DPRT(("tape_start start\n")); + + s = splbio(); + + /* reset, dma disable */ + outb(fdc->baseport + fdout, 0x00); + (void) ftintr_wait(ftu, FTCMD_RESET, hz / 10); + + /* raise reset, enable DMA */ + outb(fdc->baseport + fdout, FDO_FRST | FDO_FDMAEN); + (void) ftintr_wait(ftu, FTCMD_RESET, hz / 10); + + splx(s); + + tape_recal(ftu, 1); + + /* set transfer speed */ + outb(fdc->baseport + fdctl, FDC_500KBPS); + DELAY(10); + + DPRT(("tape_start end\n")); +} + +/* + * Transfer control back to floppy disks. + */ +void +tape_end(ftu_t ftu) +{ + ft_p ft = &ft_data[ftu]; + fdc_p fdc = ft->fdc; + int s; + + DPRT(("tape_end start\n")); + tape_recal(ftu, 0); + + s = splbio(); + + /* reset, dma disable */ + outb(fdc->baseport + fdout, 0x00); + (void) ftintr_wait(ftu, FTCMD_RESET, hz / 10); + + /* raise reset, enable DMA */ + outb(fdc->baseport + fdout, FDO_FRST | FDO_FDMAEN); + (void) ftintr_wait(ftu, FTCMD_RESET, hz / 10); + + splx(s); + + /* set transfer speed */ + outb(fdc->baseport + fdctl, FDC_500KBPS); + DELAY(10); + fdc->flags &= ~FDC_TAPE_BUSY; + + DPRT(("tape_end end\n")); +} + +/* + * Wait for the driver to go inactive, cancel readahead if necessary. + */ +void +tape_inactive(ftu_t ftu) +{ + ft_p ft = &ft_data[ftu]; + + if (ft->curseg->reqtype == FTIO_RDAHEAD) { + ft->curseg->reqcan = 1; /* XXX cancel rdahead */ + while (ft->active) + tsleep((caddr_t) & ftsem.iosts_change, FTPRI, "ftinact", 0); + } + while (ft->active) + tsleep((caddr_t) & ftsem.iosts_change, FTPRI, "ftinact", 0); +} + +/* + * Get the geometry of the tape currently in the drive. + */ +int +ftgetgeom(ftu_t ftu) +{ + int r, i, tries; + int cfg, qic80, ext; + int sts, fmt, len; + ft_p ft = &ft_data[ftu]; + + r = tape_status(ftu); + + /* XXX fix me when format mode is finished */ + if ((r & QS_CART) == 0 || (r & QS_FMTOK) == 0) { + DPRT(("ftgetgeom: no cart or not formatted 0x%04x\n", r)); + ftg = NULL; + ft->newcart = 1; + return (0); + } + /* Report drive configuration */ + for (cfg = -1, tries = 0; cfg < 0 && tries < 3; tries++) + cfg = qic_status(ftu, QC_CONFIG, 8); + if (tries == 3) { + DPRT(("ftgetgeom report config failed\n")); + ftg = NULL; + return (-1); + } + DPRT(("ftgetgeom report config got $%04x\n", cfg)); + ft->lastcfg = cfg; + + qic80 = cfg & QCF_QIC80; + ext = cfg & QCF_EXTRA; + + /* + * XXX - This doesn't seem to work on my Colorado Jumbo 250... if it + * works on your drive, I'd sure like to hear about it. + */ +#if 0 + /* Report drive status */ + for (sts = -1, tries = 0; sts < 0 && tries < 3; tries++) + sts = qic_status(ftu, QC_TSTATUS, 8); + if (tries == 3) { + DPRT(("ftgetgeom report tape status failed\n")); + ftg = NULL; + return (-1); + } + DPRT(("ftgetgeom report tape status got $%04x\n", sts)); +#else + /* + * XXX - Forge a fake tape status based upon the returned + * configuration, since the above command or code is broken for my + * drive and probably other older drives. + */ + sts = 0; + sts = (qic80) ? QTS_QIC80 : QTS_QIC40; + sts |= (ext) ? QTS_LEN2 : QTS_LEN1; +#endif + + fmt = sts & QTS_FMMASK; + len = (sts & QTS_LNMASK) >> 4; + + if (fmt > QCV_NFMT) { + ftg = NULL; + printf("ft%d: unsupported tape format\n", ftu); + return (-1); + } + if (len > QCV_NLEN) { + ftg = NULL; + printf("ft%d: unsupported tape length\n", ftu); + return (-1); + } + /* Look up geometry in the table */ + for (i = 1; i < NGEOM; i++) + if (ftgtbl[i].g_fmtno == fmt && ftgtbl[i].g_lenno == len) + break; + if (i == NGEOM) { + printf("ft%d: unknown tape geometry\n", ftu); + ftg = NULL; + return (-1); + } + ftg = &ftgtbl[i]; + if (!ftg->g_trktape) { + printf("ft%d: unsupported format %s w/len %s\n", + ftu, ftg->g_fmtdesc, ftg->g_lendesc); + ftg = NULL; + return (-1); + } + DPRT(("Tape format is %s, length is %s\n", ftg->g_fmtdesc, ftg->g_lendesc)); + ft->newcart = 0; + return (0); +} + +/* + * Switch between tape/floppy. This will send the tape enable/disable codes + * for this drive's manufacturer. + */ +int +set_fdcmode(dev_t dev, int newmode) +{ + ftu_t ftu = FDUNIT(minor(dev)); + ft_p ft = &ft_data[ftu]; + fdc_p fdc = ft->fdc; + + static int havebufs = 0; + void *buf; + int r, s, i; + SegReq *sp; + + if (newmode == FDC_TAPE_MODE) { + /* Wake up the tape drive */ + switch (ft->type) { + case NO_TYPE: + fdc->flags &= ~FDC_TAPE_BUSY; + return (ENXIO); + case FT_COLORADO: + tape_start(ftu); + if (tape_cmd(ftu, QC_COL_ENABLE1)) { + tape_end(ftu); + return (EIO); + } + if (tape_cmd(ftu, QC_COL_ENABLE2)) { + tape_end(ftu); + return (EIO); + } + break; + case FT_MOUNTAIN: + tape_start(ftu); + if (tape_cmd(ftu, QC_MTN_ENABLE1)) { + tape_end(ftu); + return (EIO); + } + if (tape_cmd(ftu, QC_MTN_ENABLE2)) { + tape_end(ftu); + return (EIO); + } + break; + default: + DPRT(("ft%d: bad tape type\n", ftu)); + return (ENXIO); + } + if (tape_status(ftu) < 0) { + tape_cmd(ftu, (ft->type == FT_COLORADO) ? QC_COL_DISABLE : QC_MTN_DISABLE); + tape_end(ftu); + return (EIO); + } + /* Grab buffers from memory. */ + if (!havebufs) { + ft->curseg = malloc(sizeof(SegReq), M_DEVBUF, M_NOWAIT); + if (ft->curseg == NULL) { + printf("ft%d: not enough memory for buffers\n", ftu); + return (ENOMEM); + } + ft->bufseg = malloc(sizeof(SegReq), M_DEVBUF, M_NOWAIT); + if (ft->bufseg == NULL) { + free(ft->curseg, M_DEVBUF); + printf("ft%d: not enough memory for buffers\n", ftu); + return (ENOMEM); + } + havebufs = 1; + } + ft->curseg->reqtype = FTIO_READY; + ft->bufseg->reqtype = FTIO_READY; + ft->io_sts = FTIO_READY; /* tape drive is ready */ + ft->active = 0; /* interrupt driver not active */ + ft->moving = 0; /* tape not moving */ + ft->rdonly = 0; /* tape read only */ + ft->newcart = 0;/* a new cart was inserted */ + ft->lastpos = -1; /* tape is rewound */ + tape_state(ftu, 0, QS_READY, 60); + tape_cmd(ftu, QC_RATE); + tape_cmd(ftu, QCF_RT500 + 2); /* 500K bps */ + tape_state(ftu, 0, QS_READY, 60); + ft->mode = FTM_PRIMARY; + tape_cmd(ftu, QC_PRIMARY); /* Make sure we're in primary + * mode */ + tape_state(ftu, 0, QS_READY, 60); + ftg = NULL; /* No geometry yet */ + ftgetgeom(ftu); /* Get tape geometry */ + ftreq_rewind(ftu); /* Make sure tape is rewound */ + } else { + tape_cmd(ftu, (ft->type == FT_COLORADO) ? QC_COL_DISABLE : QC_MTN_DISABLE); + tape_end(ftu); + ft->newcart = 0;/* clear new cartridge */ + havebufs = 0; + free(ft->curseg, M_DEVBUF); + free(ft->bufseg, M_DEVBUF); + } + return (0); +} + + +/* + * Perform a QIC status function. + */ +int +qic_status(ftu_t ftu, int cmd, int nbits) +{ + int st3, val, r, i; + ft_p ft = &ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + if (tape_cmd(ftu, cmd)) { + DPRT(("ft%d: QIC status timeout\n", ftu)); + return (-1); + } + /* Sense drive status */ + out_fdc(fdcu, NE7CMD_SENSED); + out_fdc(fdcu, 0x00); + st3 = in_fdc(fdcu); + + if ((st3 & 0x10) == 0) {/* track 0 */ + DPRT(("qic_status has dead drive... st3 = $%02x\n", st3)); + return (-1); + } + for (i = r = 0; i <= nbits; i++) { + if (tape_cmd(ftu, QC_NEXTBIT)) { + DPRT(("ft%d: QIC status bit timed out on %d\n", ftu, i)); + return (-1); + } + out_fdc(fdcu, NE7CMD_SENSED); + out_fdc(fdcu, 0x00); + st3 = in_fdc(fdcu); + if (st3 < 0) { + DPRT(("ft%d: controller timed out on bit %d r=$%02x\n", + ftu, i, r)); + return (-1); + } + r >>= 1; + if (i < nbits) + r |= ((st3 & 0x10) ? 1 : 0) << nbits; + else if ((st3 & 0x10) == 0) { + DPRT(("ft%d: qic status stop bit missing at %d, st3=$%02x r=$%04x\n", + ftu, i, st3, r)); + return (-1); + } + } + + DPRT(("qic_status returned $%02x\n", r)); + return (r); +} + +/* + * Open tape drive for use. Bounced off of Fdopen if tape minor is detected. + */ +int +ftopen(dev_t dev, int arg2) +{ + ftu_t ftu = FDUNIT(minor(dev)); + int type = FDTYPE(minor(dev)); + fdc_p fdc; + + /* check bounds */ + if (ftu >= NFT) + return (ENXIO); + fdc = ft_data[ftu].fdc; + /* check for controller already busy with tape */ + if (fdc->flags & FDC_TAPE_BUSY) + return (EBUSY); + /* make sure we found a tape when probed */ + if (!(fdc->flags & FDC_HASFTAPE)) + return (ENODEV); + fdc->fdu = ftu; + fdc->flags |= FDC_TAPE_BUSY; + return (set_fdcmode(dev, FDC_TAPE_MODE)); /* try to switch to tape */ +} + +/* + * Close tape and return floppy controller to disk mode. + */ +int +ftclose(dev_t dev, int flags) +{ + int s; + SegReq *sp; + ftu_t ftu = FDUNIT(minor(dev)); + ft_p ft = &ft_data[ftu]; + + /* Wait for any remaining I/O activity to complete. */ + if (ft->curseg->reqtype == FTIO_RDAHEAD) + ft->curseg->reqcan = 1; + while (ft->active) + tsleep((caddr_t) & ftsem.iosts_change, FTPRI, "ftclose", 0); + + ft->mode = FTM_PRIMARY; + tape_cmd(ftu, QC_PRIMARY); + tape_state(ftu, 0, QS_READY, 60); + ftreq_rewind(ftu); + return (set_fdcmode(dev, FDC_DISK_MODE)); /* Otherwise, close tape */ +} + +/* + * Perform strategy on a given buffer (not!). The driver was not performing + * very efficiently using the buffering routines. After support for error + * correction was added, this routine became obsolete in favor of doing + * ioctl's. Ugly, yes. + */ +void +ftstrategy(struct buf * bp) +{ + return; +} + +/* Read or write a segment. */ +int +ftreq_rw(ftu_t ftu, int cmd, QIC_Segment * sr, struct proc * p) +{ + int r, i, j; + SegReq *sp; + int s; + long blk, bad; + unsigned char *cp, *cp2; + ft_p ft = &ft_data[ftu]; + + if (!ft->active) { + r = tape_status(ftu); + if ((r & QS_CART) == 0) { + return (ENXIO); /* No cartridge */ + } + if ((r & QS_FMTOK) == 0) { + return (ENXIO); /* Not formatted */ + } + tape_state(ftu, 0, QS_READY, 90); + } + if (ftg == NULL || ft->newcart) { + while (ft->active) + tsleep((caddr_t) & ftsem.iosts_change, FTPRI, "ftrw", 0); + tape_state(ftu, 0, QS_READY, 90); + if (ftgetgeom(ftu) < 0) { + return (ENXIO); + } + } + /* Write not allowed on a read-only tape. */ + if (cmd == QIOWRITE && ft->rdonly) { + return (EROFS); + } + /* Quick check of request and buffer. */ + if (sr == NULL || sr->sg_data == NULL) { + return (EINVAL); + } + if (sr->sg_trk >= ftg->g_trktape || + sr->sg_seg >= ftg->g_segtrk) { + return (EINVAL); + } + blk = sr->sg_trk * ftg->g_blktrk + sr->sg_seg * QCV_BLKSEG; + + s = splbio(); + if (cmd == QIOREAD) { + if (ft->curseg->reqtype == FTIO_RDAHEAD) { + if (blk == ft->curseg->reqblk) { + sp = ft->curseg; + sp->reqtype = FTIO_READING; + sp->reqbad = sr->sg_badmap; + goto rdwait; + } else + ft->curseg->reqcan = 1; /* XXX cancel rdahead */ + } + /* Wait until we're ready. */ + while (ft->active) + tsleep((caddr_t) & ftsem.iosts_change, FTPRI, "ftrw", 0); + + /* Set up a new read request. */ + sp = ft->curseg; + sp->reqcrc = 0; + sp->reqbad = sr->sg_badmap; + sp->reqblk = blk; + sp->reqcan = 0; + sp->reqtype = FTIO_READING; + + /* Start the read request off. */ + DPRT(("Starting read I/O chain\n")); + arq_state = ard_state = awr_state = 0; + ft->xblk = sp->reqblk; + ft->xcnt = 0; + ft->xptr = sp->buff; + ft->active = 1; + timeout(ft_timeout, (caddr_t) ftu, 1); + +rdwait: + tsleep((caddr_t) & ftsem.buff_avail, FTPRI, "ftrw", 0); + bad = sp->reqbad; + sr->sg_crcmap = sp->reqcrc & ~bad; + + /* Copy out segment and discard bad mapped blocks. */ + cp = sp->buff; + cp2 = sr->sg_data; + for (i = 0; i < QCV_BLKSEG; cp += QCV_BLKSIZE, i++) { + if (bad & (1 << i)) + continue; + copyout(cp, cp2, QCV_BLKSIZE); + cp2 += QCV_BLKSIZE; + } + } else { + if (ft->curseg->reqtype == FTIO_RDAHEAD) { + ft->curseg->reqcan = 1; /* XXX cancel rdahead */ + while (ft->active) + tsleep((caddr_t) & ftsem.iosts_change, FTPRI, "ftrw", 0); + } + /* Sleep until a buffer becomes available. */ + while (ft->bufseg->reqtype != FTIO_READY) + tsleep((caddr_t) & ftsem.buff_avail, FTPRI, "ftrwbuf", 0); + sp = (ft->curseg->reqtype == FTIO_READY) ? ft->curseg : ft->bufseg; + + /* Copy in segment and expand bad blocks. */ + bad = sr->sg_badmap; + cp = sr->sg_data; + cp2 = sp->buff; + for (i = 0; i < QCV_BLKSEG; cp2 += QCV_BLKSIZE, i++) { + if (bad & (1 << i)) + continue; + copyin(cp, cp2, QCV_BLKSIZE); + cp += QCV_BLKSIZE; + } + + sp->reqblk = blk; + sp->reqcan = 0; + sp->reqtype = FTIO_WRITING; + + if (!ft->active) { + DPRT(("Starting write I/O chain\n")); + arq_state = ard_state = awr_state = 0; + ft->xblk = sp->reqblk; + ft->xcnt = 0; + ft->xptr = sp->buff; + ft->active = 1; + timeout(ft_timeout, (caddr_t) ftu, 1); + } + } + splx(s); + return (0); +} + + +/* Rewind to beginning of tape */ +int +ftreq_rewind(ftu_t ftu) +{ + ft_p ft = &ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + tape_cmd(ftu, QC_SEEKSTART); + tape_state(ftu, 0, QS_READY, 90); + tape_cmd(ftu, QC_SEEKTRACK); + tape_cmd(ftu, 2); + tape_state(ftu, 0, QS_READY, 90); + ft->lastpos = -1; + ft->moving = 0; + return (0); +} + +/* Move to logical beginning or end of track */ +int +ftreq_trkpos(ftu_t ftu, int req) +{ + int curtrk, r, cmd; + ft_p ft = &ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + + r = tape_status(ftu); + if ((r & QS_CART) == 0) + return (ENXIO); /* No cartridge */ + if ((r & QS_FMTOK) == 0) + return (ENXIO); /* Not formatted */ + + if (ftg == NULL || ft->newcart) { + if (ftgetgeom(ftu) < 0) + return (ENXIO); + } + curtrk = (ft->lastpos < 0) ? 0 : ft->lastpos / ftg->g_blktrk; + if (req == QIOBOT) + cmd = (curtrk & 1) ? QC_SEEKEND : QC_SEEKSTART; + else + cmd = (curtrk & 1) ? QC_SEEKSTART : QC_SEEKEND; + tape_cmd(ftu, cmd); + tape_state(ftu, 0, QS_READY, 90); + return (0); +} + +/* Seek tape head to a particular track. */ +int +ftreq_trkset(ftu_t ftu, int *trk) +{ + int curtrk, r, cmd; + ft_p ft = &ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + + r = tape_status(ftu); + if ((r & QS_CART) == 0) + return (ENXIO); /* No cartridge */ + if ((r & QS_FMTOK) == 0) + return (ENXIO); /* Not formatted */ + if (ftg == NULL || ft->newcart) { + if (ftgetgeom(ftu) < 0) + return (ENXIO); + } + tape_cmd(ftu, QC_SEEKTRACK); + tape_cmd(ftu, *trk + 2); + tape_state(ftu, 0, QS_READY, 90); + return (0); +} + +/* Start tape moving forward. */ +int +ftreq_lfwd(ftu_t ftu) +{ + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + tape_cmd(ftu, QC_FORWARD); + return (0); +} + +/* Stop the tape */ +int +ftreq_stop(ftu_t ftu) +{ + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + return (0); +} + +/* Set the particular mode the drive should be in. */ +int +ftreq_setmode(ftu_t ftu, int cmd) +{ + int r; + ft_p ft = &ft_data[ftu]; + + tape_inactive(ftu); + r = tape_status(ftu); + + switch (cmd) { + case QIOPRIMARY: + ft->mode = FTM_PRIMARY; + tape_cmd(ftu, QC_PRIMARY); + break; + case QIOFORMAT: + if (r & QS_RDONLY) + return (ENXIO); + if ((r & QS_BOT) == 0) + return (ENXIO); + tape_cmd(ftu, QC_FORMAT); + break; + case QIOVERIFY: + if ((r & QS_FMTOK) == 0) + return (ENXIO); /* Not formatted */ + tape_cmd(ftu, QC_VERIFY); + break; + } + tape_state(ftu, 0, QS_READY, 60); + return (0); +} + +/* Return drive status bits */ +int +ftreq_status(ftu_t ftu, int cmd, int *sts, struct proc * p) +{ + ft_p ft = &ft_data[ftu]; + + if (ft->active) + *sts = ft->laststs & ~QS_READY; + else + *sts = tape_status(ftu); + return (0); +} + +/* Return drive configuration bits */ +int +ftreq_config(ftu_t ftu, int cmd, int *cfg, struct proc * p) +{ + int r, tries; + ft_p ft = &ft_data[ftu]; + + if (ft->active) + r = ft->lastcfg; + else { + for (r = -1, tries = 0; r < 0 && tries < 3; tries++) + r = qic_status(ftu, QC_CONFIG, 8); + if (tries == 3) + return (ENXIO); + } + *cfg = r; + return (0); +} + +/* Return current tape's geometry. */ +int +ftreq_geom(ftu_t ftu, QIC_Geom * g) +{ + tape_inactive(ftu); + if (ftg == NULL && ftgetgeom(ftu) < 0) + return (ENXIO); + bcopy(ftg, g, sizeof(QIC_Geom)); + return (0); +} + +/* Return drive hardware information */ +int +ftreq_hwinfo(ftu_t ftu, QIC_HWInfo * hwp) +{ + int r, tries; + int rom, vend; + + tape_inactive(ftu); + bzero(hwp, sizeof(QIC_HWInfo)); + + for (rom = -1, tries = 0; rom < 0 && tries < 3; tries++) + rom = qic_status(ftu, QC_VERSION, 8); + if (rom > 0) { + hwp->hw_rombeta = (rom >> 7) & 0x01; + hwp->hw_romid = rom & 0x7f; + } + for (vend = -1, tries = 0; vend < 0 && tries < 3; tries++) + vend = qic_status(ftu, QC_VENDORID, 16); + if (vend > 0) { + hwp->hw_make = (vend >> 6) & 0x3ff; + hwp->hw_model = vend & 0x3f; + } + return (0); +} + +/* + * I/O functions. + */ +int +ftioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc * p) +{ + ftu_t ftu = FDUNIT(minor(dev)); + ft_p ft = &ft_data[ftu]; + + switch (cmd) { + case QIOREAD: /* Request reading a segment from tape. */ + case QIOWRITE: /* Request writing a segment to tape. */ + return (ftreq_rw(ftu, cmd, (QIC_Segment *) data, p)); + + case QIOREWIND: /* Rewind tape. */ + return (ftreq_rewind(ftu)); + + case QIOBOT: /* Seek to logical beginning of track. */ + case QIOEOT: /* Seek to logical end of track. */ + return (ftreq_trkpos(ftu, cmd)); + + case QIOTRACK: /* Seek tape head to specified track. */ + return (ftreq_trkset(ftu, (int *) data)); + + case QIOSEEKLP: /* Seek load point. */ + goto badreq; + + case QIOFORWARD: /* Move tape in logical forward direction. */ + return (ftreq_lfwd(ftu)); + + case QIOSTOP: /* Causes tape to stop. */ + return (ftreq_stop(ftu)); + + case QIOPRIMARY: /* Enter primary mode. */ + case QIOFORMAT: /* Enter format mode. */ + case QIOVERIFY: /* Enter verify mode. */ + return (ftreq_setmode(ftu, cmd)); + + case QIOWRREF: /* Write reference burst. */ + goto badreq; + + case QIOSTATUS: /* Get drive status. */ + return (ftreq_status(ftu, cmd, (int *) data, p)); + + case QIOCONFIG: /* Get tape configuration. */ + return (ftreq_config(ftu, cmd, (int *) data, p)); + + case QIOGEOM: + return (ftreq_geom(ftu, (QIC_Geom *) data)); + + case QIOHWINFO: + return (ftreq_hwinfo(ftu, (QIC_HWInfo *) data)); + } +badreq: + DPRT(("ft%d: unknown ioctl(%d) request\n", ftu, cmd)); + return (ENXIO); +} + +/* Not implemented */ +int +ftdump(dev_t dev) +{ + return (EINVAL); +} + +/* Not implemented */ +int +ftsize(dev_t dev) +{ + return (EINVAL); +} +#endif diff --git a/sys/i386/isa/ftreg.h b/sys/i386/isa/ftreg.h new file mode 100644 index 0000000..c54249f --- /dev/null +++ b/sys/i386/isa/ftreg.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 1993 Steve Gerakines + * + * This is freely redistributable software. You may do anything you + * wish with it, so long as the above notice stays intact. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) 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. + * + * ftreg.h - QIC-40/80 floppy tape driver header + * 10/30/93 v0.3 + * More things will end up here. QC_VENDORID and QC_VERSION now used. + * + * 08/07/93 v0.2 release + * Things that should've been here in the first place were moved. + * Tape geometry and segment request types were added. + * + * 06/03/93 v0.1 Alpha release + * Initial revision. Many more things should be moved here. + * + * $Id$ + */ + +/* QIC-117 command set. */ +#define QC_RESET 1 /* reset */ +#define QC_NEXTBIT 2 /* report next bit */ +#define QC_PAUSE 3 /* pause */ +#define QC_STPAUSE 4 /* step pause */ +#define QC_TIMEOUT 5 /* alt timeout */ +#define QC_STATUS 6 /* report status */ +#define QC_ERRCODE 7 /* report error code */ +#define QC_CONFIG 8 /* report config */ +#define QC_VERSION 9 /* report version */ +#define QC_FORWARD 10 /* logical forward */ +#define QC_SEEKSTART 11 /* seek to track start */ +#define QC_SEEKEND 12 /* seek to track end */ +#define QC_SEEKTRACK 13 /* seek head to track */ +#define QC_SEEKLOAD 14 /* seek load point */ +#define QC_FORMAT 15 /* format mode */ +#define QC_WRITEREF 16 /* write reference */ +#define QC_VERIFY 17 /* verify mode */ +#define QC_STOP 18 /* stop tape */ +#define QC_STEPUP 21 /* step head up */ +#define QC_STEPDOWN 22 /* step head down */ +#define QC_SEEKREV 25 /* seek reverse */ +#define QC_SEEKFWD 26 /* seek forward */ +#define QC_RATE 27 /* select data rate */ +#define QC_DIAG1 28 /* diagnostic mode 1 */ +#define QC_DIAG2 29 /* diagnostic mode 2 */ +#define QC_PRIMARY 30 /* primary mode */ +#define QC_VENDORID 32 /* vendor id */ +#define QC_TSTATUS 33 /* report tape status */ +#define QC_EXTREV 34 /* extended skip reverse */ +#define QC_EXTFWD 35 /* extended skip forward */ + +/* Colorado enable/disable. */ +#define QC_COL_ENABLE1 46 /* enable */ +#define QC_COL_ENABLE2 2 /* null-op */ +#define QC_COL_DISABLE 47 /* disable */ + +/* Mountain enable/disable. */ +#define QC_MTN_ENABLE1 23 /* enable 1 */ +#define QC_MTN_ENABLE2 20 /* enable 2 */ +#define QC_MTN_DISABLE 24 /* disable */ + +/* Segment I/O request. */ +typedef struct segq { + unsigned char buff[QCV_SEGSIZE];/* Segment data; first for alignment */ + int reqtype; /* Request type */ + long reqcrc; /* CRC Errors found */ + long reqbad; /* Bad sector map */ + long reqblk; /* Block request starts at */ + int reqcan; /* Cancel read-ahead */ +} SegReq; diff --git a/sys/i386/isa/ic/am7990.h b/sys/i386/isa/ic/am7990.h new file mode 100644 index 0000000..ea8a0e6 --- /dev/null +++ b/sys/i386/isa/ic/am7990.h @@ -0,0 +1,109 @@ +/* + * AMD 7990 (LANCE) definitions + * + * + */ + +#if defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN +#define LN_BITFIELD2(a, b) b, a +#define LN_BITFIELD3(a, b, c) c, b, a +#define LN_BITFIELD4(a, b, c, d) d, c, b, a +#else +#define LN_BITFIELD2(a, b) a, b +#define LN_BITFIELD3(a, b, c) a, b, c +#define LN_BITFIELD4(a, b, c, d) a, b, c, d +#endif + +#define LN_ADDR_LO(addr) ((addr) & 0xFFFF) +#define LN_ADDR_HI(addr) (((addr) >> 16) & 0xFF) + +typedef struct { + unsigned short r_addr_lo; + unsigned short LN_BITFIELD3(r_addr_hi : 8, + : 5, + r_log2_size : 3); +} ln_ring_t; + +#define LN_MC_MASK 0x3F /* Only 6 bits of the CRC */ + +typedef struct { + unsigned short ln_mode; +#define LN_MODE_RXD 0x0001 /* ( W) Receiver Disabled */ +#define LN_MODE_TXD 0x0002 /* ( W) Transmitter Disabled */ +#define LN_MODE_LOOP 0x0004 /* ( W) Enable Loopback */ +#define LN_MODE_NOTXCRC 0x0008 /* ( W) Don't Calculate TX CRCs */ +#define LN_MODE_FRCCOLL 0x0010 /* ( W) Force Collision */ +#define LN_MODE_NORETRY 0x0020 /* ( W) No Transmit Retries */ +#define LN_MODE_INTLOOP 0x0040 /* ( W) Internal Loopback */ +#define LN_MODE_PROMISC 0x8000 /* ( W) Promiscious Mode */ + unsigned short ln_physaddr[3]; + unsigned short ln_multi_mask[4]; + ln_ring_t ln_rxring; + ln_ring_t ln_txring; +} ln_initb_t; + +typedef struct { + unsigned short d_addr_lo; + unsigned char d_addr_hi; + unsigned char d_flag; +#define LN_DFLAG_EOP 0x0001 /* (RW) End Of Packet */ +#define LN_DFLAG_SOP 0x0002 /* (RW) Start Of Packet */ +#define LN_DFLAG_RxBUFERROR 0x0004 /* (R ) Receive - Buffer Error */ +#define LN_DFLAG_TxDEFERRED 0x0004 /* (R ) Transmit - Initially Deferred */ +#define LN_DFLAG_RxBADCRC 0x0008 /* (R ) Receive - Bad Checksum */ +#define LN_DFLAG_TxONECOLL 0x0008 /* (R ) Transmit - Single Collision */ +#define LN_DFLAG_RxOVERFLOW 0x0010 /* (R ) Receive - Overflow Error */ +#define LN_DFLAG_TxMULTCOLL 0x0010 /* (R ) Transmit - Multiple Collisions */ +#define LN_DFLAG_RxFRAMING 0x0020 /* (R ) Receive - Framing Error */ +#define LN_DFLAG_RxERRSUM 0x0040 /* (R ) Receive - Error Summary */ +#define LN_DFLAG_TxERRSUM 0x0040 /* (R ) Transmit - Error Summary */ +#define LN_DFLAG_OWNER 0x0080 /* (RW) Owner (1=Am7990, 0=host) */ + signed short d_buflen; /* ( W) Two's complement */ + unsigned short d_status; +#define LN_DSTS_RxLENMASK 0x0FFF /* (R ) Recieve Length */ +#define LN_DSTS_TxTDRMASK 0x03FF /* (R ) Transmit - Time Domain Reflectometer */ +#define LN_DSTS_TxEXCCOLL 0x0400 /* (R ) Transmit - Excessive Collisions */ +#define LN_DSTS_TxCARRLOSS 0x0800 /* (R ) Transmit - Carrier Loss */ +#define LN_DSTS_TxLATECOLL 0x1000 /* (R ) Transmit - Late Collision */ +#define LN_DSTS_TxUNDERFLOW 0x4000 /* (R ) Transmit - Underflow */ +#define LN_DSTS_TxBUFERROR 0x8000 /* (R ) Transmit - Buffer Error */ +} ln_desc_t; + + + + +#define LN_CSR0 0x0000 + +#define LN_CSR0_INIT 0x0001 /* (RS) Initialize Am 7990 */ +#define LN_CSR0_START 0x0002 /* (RS) Start Am7990 */ +#define LN_CSR0_STOP 0x0004 /* (RS) Reset Am7990 */ +#define LN_CSR0_TXDEMAND 0x0008 /* (RS) Transmit On Demand */ +#define LN_CSR0_TXON 0x0010 /* (R ) Transmitter Enabled */ +#define LN_CSR0_RXON 0x0020 /* (R ) Receiver Enabled */ +#define LN_CSR0_ENABINTR 0x0040 /* (RW) Interrupt Enabled */ +#define LN_CSR0_PENDINTR 0x0080 /* (R ) Interrupt Pending */ +#define LN_CSR0_INITDONE 0x0100 /* (RC) Initialization Done */ +#define LN_CSR0_TXINT 0x0200 /* (RC) Transmit Interrupt */ +#define LN_CSR0_RXINT 0x0400 /* (RC) Receive Interrupt */ +#define LN_CSR0_MEMERROR 0x0800 /* (RC) Memory Error */ +#define LN_CSR0_MISS 0x1000 /* (RC) No Available Receive Buffers */ +#define LN_CSR0_CERR 0x2000 /* (RC) SQE failed */ +#define LN_CSR0_BABL 0x4000 /* (RC) Transmit Babble */ +#define LN_CSR0_ERRSUM 0x8000 /* (R ) Error Summary (last 4) */ +#define LN_CSR0_CLEAR 0x7F00 /* Clear Status Bit */ + +/* + * CSR1 -- Init Block Address (Low 16 Bits -- Must be Word Aligned) + * CSR2 -- Init Block Address (High 8 Bits) + */ +#define LN_CSR1 0x0001 +#define LN_CSR2 0x0002 + +/* + * CSR3 -- Hardware Control + */ + +#define LN_CSR3 0x0003 +#define LN_CSR3_BCON 0x0001 /* (RW) BM/HOLD Control */ +#define LN_CSR3_ALE 0x0002 /* (RW) ALE Control */ +#define LN_CSR3_BSWP 0x0004 /* (RW) Byte Swap */ diff --git a/sys/i386/isa/ic/i8042.h b/sys/i386/isa/ic/i8042.h new file mode 100644 index 0000000..84ee90f --- /dev/null +++ b/sys/i386/isa/ic/i8042.h @@ -0,0 +1,27 @@ +/* + * $Id$ + */ + +#define KBSTATP 0x64 /* kbd controller status port (I) */ +#define KBS_DIB 0x01 /* kbd data in buffer */ +#define KBS_IBF 0x02 /* kbd input buffer low */ +#define KBS_WARM 0x04 /* kbd input buffer low */ +#define KBS_OCMD 0x08 /* kbd output buffer has command */ +#define KBS_NOSEC 0x10 /* kbd security lock not engaged */ +#define KBS_TERR 0x20 /* kbd transmission error */ +#define KBS_RERR 0x40 /* kbd receive error */ +#define KBS_PERR 0x80 /* kbd parity error */ + +#define KBCMDP 0x64 /* kbd controller port (O) */ +#define KBDATAP 0x60 /* kbd data port (I) */ +#define KBOUTP 0x60 /* kbd data port (O) */ + +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 /* convert to old scan codes */ +#define KC8_OLDPC 0x20 /* old 9bit codes instead of new 11bit */ +#define KC8_DISABLE 0x10 /* disable keyboard */ +#define KC8_IGNSEC 0x08 /* ignore security lock */ +#define KC8_CPU 0x04 /* exit from protected mode reset */ +#define KC8_IEN 0x01 /* enable interrupt */ +#define CMDBYTE (KC8_TRANS|KC8_IGNSEC|KC8_CPU|KC8_IEN) diff --git a/sys/i386/isa/ic/i82365.h b/sys/i386/isa/ic/i82365.h new file mode 100644 index 0000000..ab38125 --- /dev/null +++ b/sys/i386/isa/ic/i82365.h @@ -0,0 +1,190 @@ +#ifndef __83265_H__ +#define __83265_H__ + +/*********************************************************************** + * 82365.h -- information necessary for direct manipulation of PCMCIA + * cards and controllers + * + * Support is included for Intel 82365SL PCIC controllers and clones + * thereof. + * + * originally by Barry Jaspan; hacked over by Keith Moore + * + ***********************************************************************/ + +/* + * PCIC Registers + * Each register is given a name, and most of the bits are named too. + * I should really name them all. + * + * Finally, since the banks can be addressed with a regular syntax, + * some macros are provided for that purpose. + */ + +#define PCIC_BASE 0x03e0 /* base adddress of pcic register set */ + +/* First, all the registers */ +#define PCIC_ID_REV 0x00 /* Identification and Revision */ +#define PCIC_STATUS 0x01 /* Interface Status */ +#define PCIC_POWER 0x02 /* Power and RESETDRV control */ +#define PCIC_INT_GEN 0x03 /* Interrupt and General Control */ +#define PCIC_STAT_CHG 0x04 /* Card Status Change */ +#define PCIC_STAT_INT 0x05 /* Card Status Change Interrupt Config */ +#define PCIC_ADDRWINE 0x06 /* Address Window Enable */ +#define PCIC_IOCTL 0x07 /* I/O Control */ +#define PCIC_IO0_STL 0x08 /* I/O Address 0 Start Low Byte */ +#define PCIC_IO0_STH 0x09 /* I/O Address 0 Start High Byte */ +#define PCIC_IO0_SPL 0x0a /* I/O Address 0 Stop Low Byte */ +#define PCIC_IO0_SPH 0x0b /* I/O Address 0 Stop High Byte */ +#define PCIC_IO1_STL 0x0c /* I/O Address 1 Start Low Byte */ +#define PCIC_IO1_STH 0x0d /* I/O Address 1 Start High Byte */ +#define PCIC_IO1_SPL 0x0e /* I/O Address 1 Stop Low Byte */ +#define PCIC_IO1_SPH 0x0f /* I/O Address 1 Stop High Byte */ +#define PCIC_SM0_STL 0x10 /* System Memory Address 0 Mapping Start Low Byte */ +#define PCIC_SM0_STH 0x11 /* System Memory Address 0 Mapping Start High Byte */ +#define PCIC_SM0_SPL 0x12 /* System Memory Address 0 Mapping Stop Low Byte */ +#define PCIC_SM0_SPH 0x13 /* System Memory Address 0 Mapping Stop High Byte */ +#define PCIC_CM0_L 0x14 /* Card Memory Offset Address 0 Low Byte */ +#define PCIC_CM0_H 0x15 /* Card Memory Offset Address 0 High Byte */ +#define PCIC_CDGC 0x16 /* Card Detect and General Control */ +#define PCIC_RES17 0x17 /* Reserved */ +#define PCIC_SM1_STL 0x18 /* System Memory Address 1 Mapping Start Low Byte */ +#define PCIC_SM1_STH 0x19 /* System Memory Address 1 Mapping Start High Byte */ +#define PCIC_SM1_SPL 0x1a /* System Memory Address 1 Mapping Stop Low Byte */ +#define PCIC_SM1_SPH 0x1b /* System Memory Address 1 Mapping Stop High Byte */ +#define PCIC_CM1_L 0x1c /* Card Memory Offset Address 1 Low Byte */ +#define PCIC_CM1_H 0x1d /* Card Memory Offset Address 1 High Byte */ +#define PCIC_GLO_CTRL 0x1e /* Global Control Register */ +#define PCIC_RES1F 0x1f /* Reserved */ +#define PCIC_SM2_STL 0x20 /* System Memory Address 2 Mapping Start Low Byte */ +#define PCIC_SM2_STH 0x21 /* System Memory Address 2 Mapping Start High Byte */ +#define PCIC_SM2_SPL 0x22 /* System Memory Address 2 Mapping Stop Low Byte */ +#define PCIC_SM2_SPH 0x23 /* System Memory Address 2 Mapping Stop High Byte */ +#define PCIC_CM2_L 0x24 /* Card Memory Offset Address 2 Low Byte */ +#define PCIC_CM2_H 0x25 /* Card Memory Offset Address 2 High Byte */ +#define PCIC_RES26 0x26 /* Reserved */ +#define PCIC_RES27 0x27 /* Reserved */ +#define PCIC_SM3_STL 0x28 /* System Memory Address 3 Mapping Start Low Byte */ +#define PCIC_SM3_STH 0x29 /* System Memory Address 3 Mapping Start High Byte */ +#define PCIC_SM3_SPL 0x2a /* System Memory Address 3 Mapping Stop Low Byte */ +#define PCIC_SM3_SPH 0x2b /* System Memory Address 3 Mapping Stop High Byte */ +#define PCIC_CM3_L 0x2c /* Card Memory Offset Address 3 Low Byte */ +#define PCIC_CM3_H 0x2d /* Card Memory Offset Address 3 High Byte */ +#define PCIC_RES2E 0x2e /* Reserved */ +#define PCIC_RES2F 0x2f /* Reserved */ +#define PCIC_SM4_STL 0x30 /* System Memory Address 4 Mapping Start Low Byte */ +#define PCIC_SM4_STH 0x31 /* System Memory Address 4 Mapping Start High Byte */ +#define PCIC_SM4_SPL 0x32 /* System Memory Address 4 Mapping Stop Low Byte */ +#define PCIC_SM4_SPH 0x33 /* System Memory Address 4 Mapping Stop High Byte */ +#define PCIC_CM4_L 0x34 /* Card Memory Offset Address 4 Low Byte */ +#define PCIC_CM4_H 0x35 /* Card Memory Offset Address 4 High Byte */ +#define PCIC_RES36 0x36 /* Reserved */ +#define PCIC_RES37 0x37 /* Reserved */ +#define PCIC_RES38 0x38 /* Reserved */ +#define PCIC_RES39 0x39 /* Reserved */ +#define PCIC_RES3A 0x3a /* Reserved */ +#define PCIC_RES3B 0x3b /* Reserved */ +#define PCIC_RES3C 0x3c /* Reserved */ +#define PCIC_RES3D 0x3d /* Reserved */ +#define PCIC_RES3E 0x3e /* Reserved */ +#define PCIC_RES3F 0x3f /* Reserved */ + +/* Now register bits, ordered by reg # */ + +/* For Identification and Revision (PCIC_ID_REV) */ +#define PCIC_INTEL0 0x82 /* Intel 82365SL Rev. 0; Both Memory and I/O */ +#define PCIC_INTEL1 0x83 /* Intel 82365SL Rev. 1; Both Memory and I/O */ +#define PCIC_IBM1 0x88 /* IBM PCIC clone; Both Memory and I/O */ +#define PCIC_IBM2 0x89 /* IBM PCIC clone; Both Memory and I/O */ + +/* For Interface Status register (PCIC_STATUS) */ +#define PCIC_VPPV 0x80 /* Vpp_valid */ +#define PCIC_POW 0x40 /* PC Card power active */ +#define PCIC_READY 0x20 /* Ready/~Busy */ +#define PCIC_MWP 0x10 /* Memory Write Protect */ +#define PCIC_CD 0x0C /* Both card detect bits */ +#define PCIC_BVD 0x03 /* Both Battery Voltage Detect bits */ + +/* For the Power and RESETDRV register (PCIC_POWER) */ +#define PCIC_OUTENA 0x80 /* Output Enable */ +#define PCIC_DISRST 0x40 /* Disable RESETDRV */ +#define PCIC_APSENA 0x20 /* Auto Pwer Switch Enable */ +#define PCIC_PCPWRE 0x10 /* PC Card Power Enable */ + +/* For the Interrupt and General Control register (PCIC_INT_GEN) */ +#define PCIC_CARDTYPE 0x20 /* Card Type 0 = memory, 1 = I/O */ +#define PCIC_IOCARD 0x20 +#define PCIC_MEMCARD 0x00 +#define PCIC_CARDRESET 0x40 /* Card reset 0 = Reset, 1 = Normal */ + +/* For the Card Status Change register (PCIC_STAT_CHG) */ +#define PCIC_CDTCH 0x08 /* Card Detect Change */ +#define PCIC_RDYCH 0x04 /* Ready Change */ +#define PCIC_BATWRN 0x02 /* Battery Warning */ +#define PCIC_BATDED 0x01 /* Battery Dead */ + +/* For the Address Window Enable Register (PCIC_ADDRWINE) */ +#define PCIC_SM0_EN 0x01 /* Memory Window 0 Enable */ +#define PCIC_SM1_EN 0x02 /* Memory Window 1 Enable */ +#define PCIC_SM2_EN 0x04 /* Memory Window 2 Enable */ +#define PCIC_SM3_EN 0x08 /* Memory Window 3 Enable */ +#define PCIC_SM4_EN 0x10 /* Memory Window 4 Enable */ +#define PCIC_MEMCS16 0x20 /* ~MEMCS16 Decode A23-A12 */ +#define PCIC_IO0_EN 0x40 /* I/O Window 0 Enable */ +#define PCIC_IO1_EN 0x80 /* I/O Window 1 Enable */ + +/* For the I/O Control Register (PCIC_IOCTL) */ +#define PCIC_IO0_16BIT 0x01 /* I/O to this segment is 16 bit */ +#define PCIC_IO0_CS16 0x02 /* I/O cs16 source is the card */ +#define PCIC_IO0_0WS 0x04 /* zero wait states added on 8 bit cycles */ +#define PCIC_IO0_WS 0x08 /* Wait states added for 16 bit cycles */ +#define PCIC_IO1_16BIT 0x10 /* I/O to this segment is 16 bit */ +#define PCIC_IO1_CS16 0x20 /* I/O cs16 source is the card */ +#define PCIC_IO1_0WS 0x04 /* zero wait states added on 8 bit cycles */ +#define PCIC_IO1_WS 0x80 /* Wait states added for 16 bit cycles */ + +/* For the various I/O and Memory windows */ +#define PCIC_ADDR_LOW 0 +#define PCIC_ADDR_HIGH 1 +#define PCIC_START 0x00 /* Start of mapping region */ +#define PCIC_END 0x02 /* End of mapping region */ +#define PCIC_MOFF 0x04 /* Card Memory Mapping region offset */ +#define PCIC_IO0 0x08 /* I/O Address 0 */ +#define PCIC_IO1 0x0c /* I/O Address 1 */ +#define PCIC_SM0 0x10 /* System Memory Address 0 Mapping */ +#define PCIC_SM1 0x18 /* System Memory Address 1 Mapping */ +#define PCIC_SM2 0x20 /* System Memory Address 2 Mapping */ +#define PCIC_SM3 0x28 /* System Memory Address 3 Mapping */ +#define PCIC_SM4 0x30 /* System Memory Address 4 Mapping */ + +/* For System Memory Window start registers + (PCIC_SMx|PCIC_START|PCIC_ADDR_HIGH) */ +#define PCIC_ZEROWS 0x40 /* Zero wait states */ +#define PCIC_DATA16 0x80 /* Data width is 16 bits */ + +/* For System Memory Window stop registers + (PCIC_SMx|PCIC_END|PCIC_ADDR_HIGH) */ +#define PCIC_MW0 0x40 /* Wait state bit 0 */ +#define PCIC_MW1 0x80 /* Wait state bit 1 */ + +/* For System Memory Window offset registers + (PCIC_SMx|PCIC_MOFF|PCIC_ADDR_HIGH) */ +#define PCIC_REG 0x40 /* Attribute/Common select (why called Reg?) */ +#define PCIC_WP 0x80 /* Write-protect this window */ + +/* For Card Detect and General Control register (PCIC_CDGC) */ +#define PCIC_16_DL_INH 0x01 /* 16-bit memory delay inhibit */ +#define PCIC_CNFG_RST_EN 0x02 /* configuration reset enable */ +#define PCIC_GPI_EN 0x04 /* GPI Enable */ +#define PCIC_GPI_TRANS 0x08 /* GPI Transition Control */ +#define PCIC_CDRES_EN 0x10 /* card detect resume enable */ +#define PCIC_SW_CD_INT 0x20 /* s/w card detect interrupt */ + +/* For Global Control register (PCIC_GLO_CTRL) */ +#define PCIC_PWR_DOWN 0x01 /* power down */ +#define PCIC_LVL_MODE 0x02 /* level mode interrupt enable */ +#define PCIC_WB_CSCINT 0x04 /* explicit write-back csc intr */ +#define PCIC_IRQ14_PULSE 0x08 /* irq 14 pulse mode enable */ + +/* DON'T ADD ANYTHING AFTER THIS #endif */ +#endif /* __83265_H__ */ diff --git a/sys/i386/isa/ic/i8237.h b/sys/i386/isa/ic/i8237.h new file mode 100644 index 0000000..2199e73 --- /dev/null +++ b/sys/i386/isa/ic/i8237.h @@ -0,0 +1,11 @@ +/* + * Intel 8237 DMA Controller + * + * $Id$ + */ + +#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/i386/isa/ic/i82586.h b/sys/i386/isa/ic/i82586.h new file mode 100644 index 0000000..577313d --- /dev/null +++ b/sys/i386/isa/ic/i82586.h @@ -0,0 +1,325 @@ +/*- + * Copyright (c) 1992, University of Vermont and State Agricultural College. + * Copyright (c) 1992, Garrett A. Wollman. + * 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 + * Vermont and State Agricultural College and Garrett A. Wollman. + * 4. Neither the name of the University nor the name of the author + * 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 UNIVERSITY OR AUTHOR 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. + * + * $Id$ + */ + +/* + * Intel 82586 Ethernet chip + * Register, bit, and structure definitions. + * + * Written by GAW with reference to the Clarkson Packet Driver code for this + * chip written by Russ Nelson and others. + */ + +struct ie_en_addr { + u_char data[6]; +}; + +/* + * This is the master configuration block. It tells the hardware where all + * the rest of the stuff is. + */ +struct ie_sys_conf_ptr { + u_short mbz; /* must be zero */ + u_char ie_bus_use; /* true if 8-bit only */ + u_char mbz2[5]; /* must be zero */ + caddr_t ie_iscp_ptr; /* 24-bit physaddr of ISCP */ +}; + +/* + * Note that this is wired in hardware; the SCP is always located here, no + * matter what. + */ +#define IE_SCP_ADDR 0xfffff4 + +/* + * The tells the hardware where all the rest of the stuff is, too. + * FIXME: some of these should be re-commented after we figure out their + * REAL function. + */ +struct ie_int_sys_conf_ptr { + u_char ie_busy; /* zeroed after init */ + u_char mbz; + u_short ie_scb_offset; /* 16-bit physaddr of next struct */ + caddr_t ie_base; /* 24-bit physaddr for all 16-bit vars */ +}; + +/* + * This FINALLY tells the hardware what to do and where to put it. + */ +struct ie_sys_ctl_block { + u_short ie_status; /* status word */ + u_short ie_command; /* command word */ + u_short ie_command_list; /* 16-pointer to command block list */ + u_short ie_recv_list; /* 16-pointer to receive frame list */ + u_short ie_err_crc; /* CRC errors */ + u_short ie_err_align; /* Alignment errors */ + u_short ie_err_resource; /* Resource errors */ + u_short ie_err_overrun; /* Overrun errors */ +}; + +/* Command values */ +#define IE_RU_COMMAND 0x0070 /* mask for RU command */ +#define IE_RU_NOP 0 /* for completeness */ +#define IE_RU_START 0x0010 /* start receive unit command */ +#define IE_RU_ENABLE 0x0020 /* enable receiver command */ +#define IE_RU_DISABLE 0x0030 /* disable receiver command */ +#define IE_RU_ABORT 0x0040 /* abort current receive operation */ + +#define IE_CU_COMMAND 0x0700 /* mask for CU command */ +#define IE_CU_NOP 0 /* included for completeness */ +#define IE_CU_START 0x0100 /* do-command command */ +#define IE_CU_RESUME 0x0200 /* resume a suspended cmd list */ +#define IE_CU_STOP 0x0300 /* SUSPEND was already taken */ +#define IE_CU_ABORT 0x0400 /* abort current command */ + +#define IE_ACK_COMMAND 0xf000 /* mask for ACK command */ +#define IE_ACK_CX 0x8000 /* ack IE_ST_DONE */ +#define IE_ACK_FR 0x4000 /* ack IE_ST_RECV */ +#define IE_ACK_CNA 0x2000 /* ack IE_ST_ALLDONE */ +#define IE_ACK_RNR 0x1000 /* ack IE_ST_RNR */ + +#define IE_ACTION_COMMAND(x) (((x) & IE_CU_COMMAND) == IE_CU_START) + /* is this command an action command? */ + +/* Status values */ +#define IE_ST_WHENCE 0xf000 /* mask for cause of interrupt */ +#define IE_ST_DONE 0x8000 /* command with I bit completed */ +#define IE_ST_RECV 0x4000 /* frame received */ +#define IE_ST_ALLDONE 0x2000 /* all commands completed */ +#define IE_ST_RNR 0x1000 /* receive not ready */ + +#define IE_CU_STATUS 0x700 /* mask for command unit status */ +#define IE_CU_ACTIVE 0x200 /* command unit is active */ +#define IE_CU_SUSPEND 0x100 /* command unit is suspended */ + +#define IE_RU_STATUS 0x70 /* mask for receiver unit status */ +#define IE_RU_SUSPEND 0x10 /* receiver is suspended */ +#define IE_RU_NOSPACE 0x20 /* receiver has no resources */ +#define IE_RU_READY 0x40 /* reveiver is ready */ + +/* + * This is filled in partially by the chip, partially by us. + */ +struct ie_recv_frame_desc { + u_short ie_fd_status; /* status for this frame */ + u_short ie_fd_last; /* end of frame list flag */ + u_short ie_fd_next; /* 16-pointer to next RFD */ + u_short ie_fd_buf_desc; /* 16-pointer to list of buffer desc's */ + struct ie_en_addr dest; /* destination ether */ + struct ie_en_addr src; /* source ether */ + u_short ie_length; /* 802 length/Ether type */ + u_short mbz; /* must be zero */ +}; + +#define IE_FD_LAST 0x8000 /* last rfd in list */ +#define IE_FD_SUSP 0x4000 /* suspend RU after receipt */ + +#define IE_FD_COMPLETE 0x8000 /* frame is complete */ +#define IE_FD_BUSY 0x4000 /* frame is busy */ +#define IE_FD_OK 0x2000 /* frame is bad */ +#define IE_FD_RNR 0x0200 /* receiver out of resources here */ + +/* + * linked list of buffers... + */ +struct ie_recv_buf_desc { + u_short ie_rbd_actual; /* status for this buffer */ + u_short ie_rbd_next; /* 16-pointer to next RBD */ + caddr_t ie_rbd_buffer; /* 24-pointer to buffer for this RBD */ + u_short ie_rbd_length; /* length of the buffer */ + u_short mbz; /* must be zero */ +}; + +#define IE_RBD_LAST 0x8000 /* last buffer */ +#define IE_RBD_USED 0x4000 /* this buffer has data */ +/* + * All commands share this in common. + */ +struct ie_cmd_common { + u_short ie_cmd_status; /* status of this command */ + u_short ie_cmd_cmd; /* command word */ + u_short ie_cmd_link; /* link to next command */ +}; + +#define IE_STAT_COMPL 0x8000 /* command is completed */ +#define IE_STAT_BUSY 0x4000 /* command is running now */ +#define IE_STAT_OK 0x2000 /* command completed successfully */ + +#define IE_CMD_NOP 0x0000 /* NOP */ +#define IE_CMD_IASETUP 0x0001 /* initial address setup */ +#define IE_CMD_CONFIG 0x0002 /* configure command */ +#define IE_CMD_MCAST 0x0003 /* multicast setup command */ +#define IE_CMD_XMIT 0x0004 /* transmit command */ +#define IE_CMD_TDR 0x0005 /* time-domain reflectometer command */ +#define IE_CMD_DUMP 0x0006 /* dump command */ +#define IE_CMD_DIAGNOSE 0x0007 /* diagnostics command */ + +#define IE_CMD_LAST 0x8000 /* this is the last command in the list */ +#define IE_CMD_SUSPEND 0x4000 /* suspend CU after this command */ +#define IE_CMD_INTR 0x2000 /* post an interrupt after completion */ + +/* + * This is the command to transmit a frame. + */ +struct ie_xmit_cmd { + struct ie_cmd_common com; /* common part */ +#define ie_xmit_status com.ie_cmd_status + + u_short ie_xmit_desc; /* 16-pointer to buffer descriptor */ + struct ie_en_addr ie_xmit_addr; /* destination address */ + + u_short ie_xmit_length; /* 802.3 length/Ether type field */ +}; + +#define IE_XS_MAXCOLL 0x000f /* number of collisions during transmit */ +#define IE_XS_EXCMAX 0x0020 /* exceeded maximum number of collisions */ +#define IE_XS_SQE 0x0040 /* SQE positive */ +#define IE_XS_DEFERRED 0x0080 /* transmission deferred */ +#define IE_XS_UNDERRUN 0x0100 /* DMA underrun */ +#define IE_XS_LOSTCTS 0x0200 /* Lost CTS */ +#define IE_XS_NOCARRIER 0x0400 /* No Carrier */ +#define IE_XS_LATECOLL 0x0800 /* Late collision */ + +/* + * This is a buffer descriptor for a frame to be transmitted. + */ + +struct ie_xmit_buf { + u_short ie_xmit_flags; /* see below */ + u_short ie_xmit_next; /* 16-pointer to next desc. */ + caddr_t ie_xmit_buf; /* 24-pointer to the actual buffer */ +}; + +#define IE_XMIT_LAST 0x8000 /* this TBD is the last one */ +/* The rest of the `flags' word is actually the length. */ + +/* + * Multicast setup command. + */ + +#define MAXMCAST 50 /* must fit in transmit buffer */ + +struct ie_mcast_cmd { + struct ie_cmd_common com; /* common part */ +#define ie_mcast_status com.ie_cmd_status + + u_short ie_mcast_bytes; /* size (in bytes) of multicast addresses */ + struct ie_en_addr ie_mcast_addrs[MAXMCAST + 1]; /* space for them */ +}; + +/* + * Time Domain Reflectometer command. + */ + +struct ie_tdr_cmd { + struct ie_cmd_common com; /* common part */ +#define ie_tdr_status com.ie_cmd_status + + u_short ie_tdr_time; /* error bits and time */ +}; + +#define IE_TDR_SUCCESS 0x8000 /* TDR succeeded without error */ +#define IE_TDR_XCVR 0x4000 /* detected a transceiver problem */ +#define IE_TDR_OPEN 0x2000 /* detected an open */ +#define IE_TDR_SHORT 0x1000 /* TDR detected a short */ +#define IE_TDR_TIME 0x07ff /* mask for reflection time */ + +/* + * Initial Address Setup command + */ +struct ie_iasetup_cmd { + struct ie_cmd_common com; +#define ie_iasetup_status com.ie_cmd_status + + struct ie_en_addr ie_address; +}; + +/* + * Configuration command + */ +struct ie_config_cmd { + struct ie_cmd_common com; /* common part */ +#define ie_config_status com.ie_cmd_status + + u_char ie_config_count; /* byte count (0x0c) */ + u_char ie_fifo; /* fifo (8) */ + u_char ie_save_bad; /* save bad frames (0x40) */ + u_char ie_addr_len; /* address length (0x2e) (AL-LOC == 1) */ + u_char ie_priority; /* priority and backoff (0x0) */ + u_char ie_ifs; /* inter-frame spacing (0x60) */ + u_char ie_slot_low; /* slot time, LSB (0x0) */ + u_char ie_slot_high; /* slot time, MSN, and retries (0xf2) */ + u_char ie_promisc; /* 1 if promiscuous, else 0 */ + u_char ie_crs_cdt; /* CSMA/CD parameters (0x0) */ + u_char ie_min_len; /* min frame length (0x40) */ + u_char ie_junk; /* stuff for 82596 (0xff) */ +}; + +/* + * Here are a few useful functions. We could have done these as macros, + * but since we have the inline facility, it makes sense to use that + * instead. + */ +inline void +ie_setup_config(volatile struct ie_config_cmd *cmd, + int promiscuous, int manchester) { + cmd->ie_config_count = 0x0c; + cmd->ie_fifo = 8; + cmd->ie_save_bad = 0x40; + cmd->ie_addr_len = 0x2e; + cmd->ie_priority = 0; + cmd->ie_ifs = 0x60; + cmd->ie_slot_low = 0; + cmd->ie_slot_high = 0xf2; + cmd->ie_promisc = !!promiscuous | manchester << 2; + cmd->ie_crs_cdt = 0; + cmd->ie_min_len = 64; + cmd->ie_junk = 0xff; +} + +inline caddr_t +Align(caddr_t ptr) { + unsigned long l = (unsigned long)ptr; + l = (l + 3) & ~3L; + return (caddr_t)l; +} + +inline void +ie_ack(volatile struct ie_sys_ctl_block *scb, + u_int mask, int unit, + void (*ca)(int)) { + scb->ie_command = scb->ie_status & mask; + (*ca)(unit); +} diff --git a/sys/i386/isa/ic/lemac.h b/sys/i386/isa/ic/lemac.h new file mode 100644 index 0000000..2c919d8 --- /dev/null +++ b/sys/i386/isa/ic/lemac.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 1994 Matt Thomas (thomas@lkg.dec.com) + * 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. The name of the author may not be used to endorse or promote products + * derived from this software withough specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * $Id: lemac.h,v 1.1 1994/08/01 16:03:42 thomas Exp $ + */ +#ifndef _LEMAC_H_ +#define _LEMAC_H_ + +/* + * This is list of registers used on a DEC EtherWORKS III card. + * Each board occupies a 32 byte register space. This can be + * in either EISA or ISA space. Currently we only support ISA + * space. + */ + +#define LEMAC_REG_CS 0x00 /* Control and Status */ +#define LEMAC_REG_CTL 0x01 /* Control */ +#define LEMAC_REG_IC 0x02 /* Interrupt Control */ +#define LEMAC_REG_TS 0x03 /* Transmit Status */ +#define LEMAC_REG_RSVD1 0x04 /* Reserved (not used) */ +#define LEMAC_REG_RSVD2 0x05 /* Reserved (not used) */ +#define LEMAC_REG_FMQ 0x06 /* Free Memory Queue */ +#define LEMAC_REG_FMC 0x07 /* Free Memory Queue Count */ +#define LEMAC_REG_RQ 0x08 /* Receive Queue */ +#define LEMAC_REG_RQC 0x09 /* Receive Queue Count */ +#define LEMAC_REG_TQ 0x0A /* Transmit Queue */ +#define LEMAC_REG_TQC 0x0B /* Transmit Queue Count */ +#define LEMAC_REG_TDQ 0x0C /* Transmit Done Queue */ +#define LEMAC_REG_TDC 0x0D /* Transmit Done Queue Count */ +#define LEMAC_REG_PI1 0x0E /* Page Index #1 */ +#define LEMAC_REG_PI2 0x0F /* Page Index #2 */ +#define LEMAC_REG_DAT 0x10 /* Data */ +#define LEMAC_REG_IOP 0x11 /* I/O Page */ +#define LEMAC_REG_IOB 0x12 /* I/O Base */ +#define LEMAC_REG_MPN 0x13 /* Memory Page */ +#define LEMAC_REG_MBR 0x14 /* Memory Base */ +#define LEMAC_REG_APD 0x15 /* Address PROM */ +#define LEMAC_REG_EE1 0x16 /* EEPROM Data #1 */ +#define LEMAC_REG_EE2 0x17 /* EEPROM Data #2 */ +#define LEMAC_REG_PA0 0x18 /* Physical Address (Byte 0) */ +#define LEMAC_REG_PA1 0x19 /* Physical Address (Byte 1) */ +#define LEMAC_REG_PA2 0x1A /* Physical Address (Byte 2) */ +#define LEMAC_REG_PA3 0x1B /* Physical Address (Byte 3) */ +#define LEMAC_REG_PA4 0x1C /* Physical Address (Byte 4) */ +#define LEMAC_REG_PA5 0x1D /* Physical Address (Byte 5) */ +#define LEMAC_REG_CNF 0x1E /* Configuration Management */ +#define LEMAC_IOSPACE 0x20 /* LEMAC uses 32 bytes of IOSPACE */ + + +#define LEMAC_REG_EID0 0x80 /* EISA Identification 0 */ +#define LEMAC_REG_EID1 0x81 /* EISA Identification 1 */ +#define LEMAC_REG_EID2 0x82 /* EISA Identification 2 */ +#define LEMAC_REG_EID3 0x83 /* EISA Identification 3 */ +#define LEMAC_REG_EIC 0x84 /* EISA Control */ + +/* Control Page (Page 0) Definitions */ + +#define LEMAC_MCTBL_BITS 9 +#define LEMAC_MCTBL_OFF 512 +#define LEMAC_MCTBL_SIZE (1 << (LEMAC_MCTBL_BITS - 3)) +#define LEMAC_CRC32_POLY 0xEDB88320UL /* CRC-32 Poly -- Little Endian) */ + +/* EEPROM Definitions */ + +#define LEMAC_EEP_CKSUM 0 /* The valid checksum is 0 */ +#define LEMAC_EEP_SIZE 32 /* EEPROM is 32 bytes */ +#define LEMAC_EEP_DELAY 2000 /* 2ms = 2000us */ +#define LEMAC_EEP_PRDNM 8 /* Product Name Offset */ +#define LEMAC_EEP_PRDNMSZ 8 /* Product Name Size */ +#define LEMAC_EEP_SWFLAGS 16 /* Software Options Offset */ +#define LEMAC_EEP_SETUP 23 /* Setup Options Offset */ + +#define LEMAC_EEP_SW_SQE 0x10 /* Enable TX_SQE on Transmits */ +#define LEMAC_EEP_SW_LAB 0x08 /* Enable TX_LAB on Transmits */ +#define LEMAC_EEP_ST_DRAM 0x02 /* Enable extra DRAM */ + +#define LEMAC_ADP_ROMSZ 32 /* Size of Address PROM */ + +/* Receive Status Definitions */ + +#define LEMAC_RX_PLL 0x01 /* Phase Lock Lost */ +#define LEMAC_RX_CRC 0x02 /* CRC Error */ +#define LEMAC_RX_DBE 0x04 /* Dribble Bit Error */ +#define LEMAC_RX_MCM 0x08 /* Multicast Match */ +#define LEMAC_RX_IAM 0x10 /* Individual Address Match */ +#define LEMAC_RX_OK 0x80 /* No Errors */ + +/* Transmit Status Definitions (not valid if TXD == 0) */ + +#define LEMAC_TS_RTRYMSK 0x0F /* Retries of last TX PDU */ +#define LEMAC_TS_ECL 0x10 /* Excessive collision of ... */ +#define LEMAC_TS_LCL 0x20 /* Late collision of ... */ +#define LEMAC_TS_ID 0x40 /* Initially Deferred ... */ + +/* Transmit Control Definitions */ + +#define LEMAC_TX_ISA 0x01 /* Insert Source Address (no) */ +#define LEMAC_TX_IFC 0x02 /* Insert Frame Check (yes) */ +#define LEMAC_TX_PAD 0x04 /* Zero PAD to mininum length (yes) */ +#define LEMAC_TX_LAB 0x08 /* Less Agressive Backoff (no) */ +#define LEMAC_TX_QMD 0x10 /* Q-Mode (yes) */ +#define LEMAC_TX_STP 0x20 /* Stop on Error (yes) */ +#define LEMAC_TX_SQE 0x40 /* SQE Enable (yes) */ + +#define LEMAC_TX_FLAGS (LEMAC_TX_IFC|LEMAC_TX_PAD|LEMAC_TX_QMD|\ + LEMAC_TX_STP|LEMAC_TX_SQE) +#define LEMAC_TX_HDRSZ 4 /* Size of TX header */ + +/* Transmit Done Queue Status Definitions */ + +#define LEMAC_TDQ_COL 0x03 /* Collision Mask */ +#define LEMAC_TDQ_NOCOL 0x00 /* No Collisions */ +#define LEMAC_TDQ_ONECOL 0x01 /* One Collision */ +#define LEMAC_TDQ_MULCOL 0x02 /* Multiple Collisions */ +#define LEMAC_TDQ_EXCCOL 0x03 /* Excesive Collisions */ +#define LEMAC_TDQ_ID 0x04 /* Initially Deferred */ +#define LEMAC_TDQ_LCL 0x08 /* Late Collision (will TX_STP) */ + +/* Control / Status Definitions */ + +#define LEMAC_CS_RXD 0x01 /* Receiver Disabled */ +#define LEMAC_CS_TXD 0x02 /* Transmitter Disabled */ +#define LEMAC_CS_RNE 0x04 /* Receive Queue Not Empty */ +#define LEMAC_CS_TNE 0x08 /* Transmit Done Queue Not Empty */ +#define LEMAC_CS_MBZ4 0x10 /* MBZ */ +#define LEMAC_CS_MCE 0x20 /* Multicast Enable */ +#define LEMAC_CS_PME 0x40 /* Promiscuous Mode Enable */ +#define LEMAC_CS_RA 0x80 /* Runt Accept */ + +/* Control Definitions */ + +#define LEMAC_CTL_LED 0x02 /* LED state (inverted) */ + +/* Interrupt Control Definitions */ + +#define LEMAC_IC_RXD 0x01 /* Enable RXD Interrupt */ +#define LEMAC_IC_TXD 0x02 /* Enable TXD Interrupt */ +#define LEMAC_IC_RNE 0x04 /* Enable RNE Interrupt */ +#define LEMAC_IC_TNE 0x08 /* Enable TNE Interrupt */ +#define LEMAC_IC_ALL 0x0F /* Enable RXD,TXD,RNE,TNE */ +#define LEMAC_IC_IRQMSK 0x60 /* Interrupt Select */ +#define LEMAC_IC_IRQ5 0x00 /* Select IRQ 5 */ +#define LEMAC_IC_IRQ10 0x20 /* Select IRQ 10 */ +#define LEMAC_IC_IRQ11 0x40 /* Select IRQ 11 */ +#define LEMAC_IC_IRQ15 0x60 /* Select IRQ 15 */ +#define LEMAC_IC_IE 0x80 /* Interrupt Enable */ + +/* I/O Page Definitions */ + +#define LEMAC_IOP_EEINIT 0xC0 /* Perform a board init/reset */ +#define LEMAC_IOP_EEREAD 0xE0 /* Start a read from EEPROM */ + +/* Configuration / Management Definitions */ + +#define LEMAC_CNF_DRAM 0x02 /* Extra on-board DRAM is available */ + +#endif /* _LEMAC_H_ */ diff --git a/sys/i386/isa/ic/ncr_5380.h b/sys/i386/isa/ic/ncr_5380.h new file mode 100644 index 0000000..1effe49 --- /dev/null +++ b/sys/i386/isa/ic/ncr_5380.h @@ -0,0 +1,130 @@ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ + +#ifndef _IC_NCR_5380_H_ +#define _IC_NCR_5380_H_ + +/* + * Register map + */ + +typedef struct { + volatile u_char sci_data; /* r: Current data */ +#define sci_odata sci_data /* w: Out data */ + + volatile u_char sci_icmd; /* rw:Initiator command */ + + volatile u_char sci_mode; /* rw:Mode */ + + volatile u_char sci_tcmd; /* rw:Target command */ + + volatile u_char sci_bus_csr;/* r: Bus Status */ +#define sci_sel_enb sci_bus_csr /* w: Select enable */ + + volatile u_char sci_csr; /* r: Status */ +#define sci_dma_send sci_csr /* w: Start dma send data */ + + volatile u_char sci_idata; /* r: Input data */ +#define sci_trecv sci_idata /* w: Start dma recv, target */ + + volatile u_char sci_iack; /* r: Interrupt Acknowledge */ +#define sci_irecv sci_iack /* w: Start dma recv, initiator */ +} sci_regmap_t; + + +/* + * Initiator command register + */ + +#define SCI_ICMD_DATA 0x01 /* rw:Assert data bus */ +#define SCI_ICMD_ATN 0x02 /* rw:Assert ATN signal */ +#define SCI_ICMD_SEL 0x04 /* rw:Assert SEL signal */ +#define SCI_ICMD_BSY 0x08 /* rw:Assert BSY signal */ +#define SCI_ICMD_ACK 0x10 /* rw:Assert ACK signal */ +#define SCI_ICMD_LST 0x20 /* r: Lost arbitration */ +#define SCI_ICMD_DIFF SCI_ICMD_LST /* w: Differential cable */ +#define SCI_ICMD_AIP 0x40 /* r: Arbitration in progress */ +#define SCI_ICMD_TEST SCI_ICMD_AIP /* w: Test mode */ +#define SCI_ICMD_RST 0x80 /* rw:Assert RST signal */ + + +/* + * Mode register + */ + +#define SCI_MODE_ARB 0x01 /* rw: Start arbitration */ +#define SCI_MODE_DMA 0x02 /* rw: Enable DMA xfers */ +#define SCI_MODE_MONBSY 0x04 /* rw: Monitor BSY signal */ +#define SCI_MODE_DMA_IE 0x08 /* rw: Enable DMA complete interrupt */ +#define SCI_MODE_PERR_IE 0x10 /* rw: Interrupt on parity errors */ +#define SCI_MODE_PAR_CHK 0x20 /* rw: Check parity */ +#define SCI_MODE_TARGET 0x40 /* rw: Target mode (Initiator if 0) */ +#define SCI_MODE_BLOCKDMA 0x80 /* rw: Block-mode DMA handshake (MBZ) */ + + +/* + * Target command register + */ + +#define SCI_TCMD_IO 0x01 /* rw: Assert I/O signal */ +#define SCI_TCMD_CD 0x02 /* rw: Assert C/D signal */ +#define SCI_TCMD_MSG 0x04 /* rw: Assert MSG signal */ +#define SCI_TCMD_PHASE_MASK 0x07 /* r: Mask for current bus phase */ +#define SCI_TCMD_REQ 0x08 /* rw: Assert REQ signal */ +#define SCI_TCMD_LAST_SENT 0x80 /* ro: Last byte was xferred + * (not on 5380/1) */ + +#define SCI_PHASE(x) SCSI_PHASE(x) + +/* + * Current (SCSI) Bus status + */ + +#define SCI_BUS_DBP 0x01 /* r: Data Bus parity */ +#define SCI_BUS_SEL 0x02 /* r: SEL signal */ +#define SCI_BUS_IO 0x04 /* r: I/O signal */ +#define SCI_BUS_CD 0x08 /* r: C/D signal */ +#define SCI_BUS_MSG 0x10 /* r: MSG signal */ +#define SCI_BUS_REQ 0x20 /* r: REQ signal */ +#define SCI_BUS_BSY 0x40 /* r: BSY signal */ +#define SCI_BUS_RST 0x80 /* r: RST signal */ + +#define SCI_CUR_PHASE(x) SCSI_PHASE((x)>>2) + +/* + * Bus and Status register + */ + +#define SCI_CSR_ACK 0x01 /* r: ACK signal */ +#define SCI_CSR_ATN 0x02 /* r: ATN signal */ +#define SCI_CSR_DISC 0x04 /* r: Disconnected (BSY==0) */ +#define SCI_CSR_PHASE_MATCH 0x08 /* r: Bus and SCI_TCMD match */ +#define SCI_CSR_INT 0x10 /* r: Interrupt request */ +#define SCI_CSR_PERR 0x20 /* r: Parity error */ +#define SCI_CSR_DREQ 0x40 /* r: DMA request */ +#define SCI_CSR_DONE 0x80 /* r: DMA count is zero */ + +#endif /* _IC_NCR_5380_H_ */ diff --git a/sys/i386/isa/ic/nec765.h b/sys/i386/isa/ic/nec765.h new file mode 100644 index 0000000..1895db7 --- /dev/null +++ b/sys/i386/isa/ic/nec765.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. + * + * from: @(#)nec765.h 7.1 (Berkeley) 5/9/91 + * $Id$ + */ + +/* + * 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/i386/isa/ic/ns16450.h b/sys/i386/isa/ic/ns16450.h new file mode 100644 index 0000000..aa6280d --- /dev/null +++ b/sys/i386/isa/ic/ns16450.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. + * + * from: @(#)ns16450.h 7.1 (Berkeley) 5/9/91 + * $Id$ + */ + +/* + * NS16450 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_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/i386/isa/ic/ns16550.h b/sys/i386/isa/ic/ns16550.h new file mode 100644 index 0000000..ff59757 --- /dev/null +++ b/sys/i386/isa/ic/ns16550.h @@ -0,0 +1,51 @@ +/*- + * 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. + * + * from: @(#)ns16550.h 7.1 (Berkeley) 5/9/91 + * $Id$ + */ + +/* + * 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/i386/isa/icu.h b/sys/i386/isa/icu.h new file mode 100644 index 0000000..13216b0 --- /dev/null +++ b/sys/i386/isa/icu.h @@ -0,0 +1,97 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * from: @(#)icu.h 5.6 (Berkeley) 5/9/91 + * $Id: icu.h,v 1.2 1993/10/16 13:45:51 rgrimes Exp $ + */ + +/* + * AT/386 Interrupt Control constants + * W. Jolitz 8/89 + */ + +#ifndef __ICU__ +#define __ICU__ + +#ifndef LOCORE + +/* + * Interrupt "level" mechanism variables, masks, and macros + */ +extern unsigned imen; /* interrupt mask enable */ + +#define INTREN(s) (imen &= ~(s), SET_ICUS()) +#define INTRDIS(s) (imen |= (s), SET_ICUS()) +#define INTRMASK(msk,s) (msk |= (s)) +#if 0 +#define SET_ICUS() (outb(IO_ICU1 + 1, imen), outb(IU_ICU2 + 1, imen >> 8)) +#else +/* + * XXX - IO_ICU* are defined in isa.h, not icu.h, and nothing much bothers to + * include isa.h, while too many things include icu.h. + */ +#define SET_ICUS() (outb(0x21, imen), outb(0xa1, imen >> 8)) +#endif + +#endif + +/* + * Interrupt enable bits - in normal order of priority (which we change) + */ +#define IRQ0 0x0001 /* highest priority - timer */ +#define IRQ1 0x0002 +#define IRQ_SLAVE 0x0004 +#define IRQ8 0x0100 +#define IRQ9 0x0200 +#define IRQ2 IRQ9 +#define IRQ10 0x0400 +#define IRQ11 0x0800 +#define IRQ12 0x1000 +#define IRQ13 0x2000 +#define IRQ14 0x4000 +#define IRQ15 0x8000 +#define IRQ3 0x0008 /* this is highest after rotation */ +#define IRQ4 0x0010 +#define IRQ5 0x0020 +#define IRQ6 0x0040 +#define IRQ7 0x0080 /* lowest - parallel printer */ + +/* + * Interrupt Control offset into Interrupt descriptor table (IDT) + */ +#define ICU_OFFSET 32 /* 0-31 are processor exceptions */ +#define ICU_LEN 16 /* 32-47 are ISA interrupts */ + +#endif __ICU__ diff --git a/sys/i386/isa/icu.s b/sys/i386/isa/icu.s new file mode 100644 index 0000000..4956ab8 --- /dev/null +++ b/sys/i386/isa/icu.s @@ -0,0 +1,332 @@ +/*- + * Copyright (c) 1989, 1990 William F. Jolitz. + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * @(#)icu.s 7.2 (Berkeley) 5/21/91 + * + * $Id: icu.s,v 1.11 1994/08/15 03:15:19 wollman Exp $ + */ + +/* + * AT/386 + * Vector interrupt control section + */ + +/* + * XXX this file should be named ipl.s. All spls are now soft and the + * only thing related to the hardware icu is that the h/w interrupt + * numbers are used without translation in the masks. + */ + +#include <net/netisr.h> + + .data + .globl _cpl +_cpl: .long HWI_MASK | SWI_MASK /* current priority (all off) */ + .globl _imen +_imen: .long HWI_MASK /* interrupt mask enable (all h/w off) */ + .globl _stat_imask +_stat_imask: .long (1 << 8) + .globl _tty_imask +_tty_imask: .long 0 + .globl _bio_imask +_bio_imask: .long 0 + .globl _net_imask +_net_imask: .long 0 + .globl _ipending +_ipending: .long 0 + .globl _astpending +_astpending: .long 0 /* tells us an AST needs to be taken */ + .globl _netisr +_netisr: .long 0 /* set with bits for which queue to service */ +vec: + .long vec0, vec1, vec2, vec3, vec4, vec5, vec6, vec7 + .long vec8, vec9, vec10, vec11, vec12, vec13, vec14, vec15 + + .text + +/* + * Handle return from interrupts, traps and syscalls. + */ + SUPERALIGN_TEXT +_doreti: + FAKE_MCOUNT(_bintr) /* init "from" _bintr -> _doreti */ + addl $4,%esp /* discard unit number */ + popl %eax /* cpl to restore */ +doreti_next: + /* + * Check for pending HWIs and SWIs atomically with restoring cpl + * and exiting. The check has to be atomic with exiting to stop + * (ipending & ~cpl) changing from zero to nonzero while we're + * looking at it (this wouldn't be fatal but it would increase + * interrupt latency). Restoring cpl has to be atomic with exiting + * so that the stack cannot pile up (the nesting level of interrupt + * handlers is limited by the number of bits in cpl). + */ + movl %eax,%ecx + notl %ecx + cli + andl _ipending,%ecx + jne doreti_unpend +doreti_exit: + movl %eax,_cpl + MEXITCOUNT + popl %es + popl %ds + popal + addl $8,%esp + iret + + ALIGN_TEXT +doreti_unpend: + /* + * Enabling interrupts is safe because we haven't restored cpl yet. + * The locking from the "btrl" test is probably no longer necessary. + * We won't miss any new pending interrupts because we will check + * for them again. + */ + sti + bsfl %ecx,%ecx /* slow, but not worth optimizing */ + btrl %ecx,_ipending + jnc doreti_next /* some intr cleared memory copy */ + movl ihandlers(,%ecx,4),%edx + testl %edx,%edx + je doreti_next /* "can't happen" */ + cmpl $NHWI,%ecx + jae doreti_swi + cli + movl %eax,_cpl + MEXITCOUNT + jmp %edx + + ALIGN_TEXT +doreti_swi: + pushl %eax + /* + * The SWI_AST handler has to run at cpl = SWI_AST_MASK and the + * SWI_CLOCK handler at cpl = SWI_CLOCK_MASK, so we have to restore + * all the h/w bits in cpl now and have to worry about stack growth. + * The worst case is currently (30 Jan 1994) 2 SWI handlers nested + * in dying interrupt frames and about 12 HWIs nested in active + * interrupt frames. There are only 4 different SWIs and the HWI + * and SWI masks limit the nesting further. + */ + orl imasks(,%ecx,4),%eax + movl %eax,_cpl + call %edx + popl %eax + jmp doreti_next + + ALIGN_TEXT +swi_ast: + addl $8,%esp /* discard raddr & cpl to get trap frame */ + testb $SEL_RPL_MASK,TRAPF_CS_OFF(%esp) + je swi_ast_phantom + movl $T_ASTFLT,(2+8+0)*4(%esp) + call _trap + subl %eax,%eax /* recover cpl */ + jmp doreti_next + + ALIGN_TEXT +swi_ast_phantom: + /* + * These happen when there is an interrupt in a trap handler before + * ASTs can be masked or in an lcall handler before they can be + * masked or after they are unmasked. They could be avoided for + * trap entries by using interrupt gates, and for lcall exits by + * using by using cli, but they are unavoidable for lcall entries. + */ + cli + orl $SWI_AST_PENDING,_ipending + jmp doreti_exit /* SWI_AST is highest so we must be done */ + +/* + * Interrupt priority mechanism + * -- soft splXX masks with group mechanism (cpl) + * -- h/w masks for currently active or unused interrupts (imen) + * -- ipending = active interrupts currently masked by cpl + */ + +ENTRY(splz) + /* + * The caller has restored cpl and checked that (ipending & ~cpl) + * is nonzero. We have to repeat the check since if there is an + * interrupt while we're looking, _doreti processing for the + * interrupt will handle all the unmasked pending interrupts + * because we restored early. We're repeating the calculation + * of (ipending & ~cpl) anyway so that the caller doesn't have + * to pass it, so this only costs one "jne". "bsfl %ecx,%ecx" + * is undefined when %ecx is 0 so we can't rely on the secondary + * btrl tests. + */ + movl _cpl,%eax +splz_next: + /* + * We don't need any locking here. (ipending & ~cpl) cannot grow + * while we're looking at it - any interrupt will shrink it to 0. + */ + movl %eax,%ecx + notl %ecx + andl _ipending,%ecx + jne splz_unpend + ret + + ALIGN_TEXT +splz_unpend: + bsfl %ecx,%ecx + btrl %ecx,_ipending + jnc splz_next + movl ihandlers(,%ecx,4),%edx + testl %edx,%edx + je splz_next /* "can't happen" */ + cmpl $NHWI,%ecx + jae splz_swi + /* + * We would prefer to call the intr handler directly here but that + * doesn't work for badly behaved handlers that want the interrupt + * frame. Also, there's a problem determining the unit number. + * We should change the interface so that the unit number is not + * determined at config time. + */ + jmp *vec(,%ecx,4) + + ALIGN_TEXT +splz_swi: + cmpl $SWI_AST,%ecx + je splz_next /* "can't happen" */ + pushl %eax + orl imasks(,%ecx,4),%eax + movl %eax,_cpl + call %edx + popl %eax + movl %eax,_cpl + jmp splz_next + +/* + * Fake clock interrupt(s) so that they appear to come from our caller instead + * of from here, so that system profiling works. + * XXX do this more generally (for all vectors; look up the C entry point). + * XXX frame bogusness stops us from just jumping to the C entry point. + */ + ALIGN_TEXT +vec0: + popl %eax /* return address */ + pushfl +#define KCSEL 8 + pushl $KCSEL + pushl %eax + cli + MEXITCOUNT + jmp _Xintr0 /* XXX might need _Xfastintr0 */ + + ALIGN_TEXT +vec8: + popl %eax + pushfl + pushl $KCSEL + pushl %eax + cli + MEXITCOUNT + jmp _Xintr8 /* XXX might need _Xfastintr8 */ + +#define BUILD_VEC(irq_num) \ + ALIGN_TEXT ; \ +vec/**/irq_num: ; \ + int $ICU_OFFSET + (irq_num) ; \ + ret + + BUILD_VEC(1) + BUILD_VEC(2) + BUILD_VEC(3) + BUILD_VEC(4) + BUILD_VEC(5) + BUILD_VEC(6) + BUILD_VEC(7) + BUILD_VEC(9) + BUILD_VEC(10) + BUILD_VEC(11) + BUILD_VEC(12) + BUILD_VEC(13) + BUILD_VEC(14) + BUILD_VEC(15) + + ALIGN_TEXT +swi_clock: + MCOUNT + subl %eax,%eax + cmpl $_splz,(%esp) /* XXX call from splz()? */ + jae 1f /* yes, usermode = 0 */ + movl 4+4+TRAPF_CS_OFF(%esp),%eax /* no, check trap frame */ + andl $SEL_RPL_MASK,%eax +1: + pushl %eax + call _softclock + addl $4,%esp + ret + +#define DONET(s, c, event) ; \ + .globl c ; \ + btrl $s,_netisr ; \ + jnc 9f ; \ + call c ; \ +9: + + ALIGN_TEXT +swi_net: + MCOUNT +#ifdef INET + DONET(NETISR_ARP, _arpintr,netisr_ip) + DONET(NETISR_IP, _ipintr,netisr_ip) +#endif +#ifdef NS + DONET(NETISR_NS, _nsintr,netisr_ns) +#endif +#ifdef ISO + DONET(NETISR_ISO, _clnlintr,netisr_iso) +#endif +#ifdef CCITT + DONET(NETISR_CCITT, _ccittintr, 29) +#endif + ret + + ALIGN_TEXT +swi_tty: + MCOUNT +#include "sio.h" +#if NSIO > 0 + jmp _siopoll +#else + ret +#endif diff --git a/sys/i386/isa/if_ed.c b/sys/i386/isa/if_ed.c new file mode 100644 index 0000000..3231b93 --- /dev/null +++ b/sys/i386/isa/if_ed.c @@ -0,0 +1,2734 @@ +/* + * Device driver for National Semiconductor DS8390/WD83C690 based ethernet + * adapters. By David Greenman, 29-April-1993 + * + * Copyright (C) 1993, David Greenman. This software may be used, modified, + * copied, distributed, and sold, in both source and binary form provided + * that the above copyright and these terms are retained. Under no + * circumstances is the author responsible for the proper functioning + * of this software, nor does the author assume any responsibility + * for damages incurred with its use. + * + * Currently supports the Western Digital/SMC 8003 and 8013 series, + * the SMC Elite Ultra (8216), the 3Com 3c503, the NE1000 and NE2000, + * and a variety of similar clones. + * + * $Id: if_ed.c,v 1.45 1994/08/18 22:34:52 wollman Exp $ + */ + +#include "ed.h" +#if NED > 0 +/* bpfilter included here in case it is needed in future net includes */ +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#endif + +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif + +#if NBPFILTER > 0 +#include <net/bpf.h> +#include <net/bpfdesc.h> +#endif + +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/icu.h> +#include <i386/isa/if_edreg.h> + +#include <i386/include/pio.h> + +/* For backwards compatibility */ +#ifndef IFF_ALTPHYS +#define IFF_ALTPHYS IFF_LINK0 +#endif + +/* + * ed_softc: per line info and status + */ +struct ed_softc { + struct arpcom arpcom; /* ethernet common */ + + char *type_str; /* pointer to type string */ + u_char vendor; /* interface vendor */ + u_char type; /* interface type code */ + + u_short asic_addr; /* ASIC I/O bus address */ + u_short nic_addr; /* NIC (DS8390) I/O bus address */ + +/* + * The following 'proto' variable is part of a work-around for 8013EBT asics + * being write-only. It's sort of a prototype/shadow of the real thing. + */ + u_char wd_laar_proto; + u_char isa16bit; /* width of access to card 0=8 or 1=16 */ + int is790; /* set by the probe code if the card is 790 + * based */ + + caddr_t bpf; /* BPF "magic cookie" */ + caddr_t mem_start; /* NIC memory start address */ + caddr_t mem_end; /* NIC memory end address */ + u_long mem_size; /* total NIC memory size */ + caddr_t mem_ring; /* start of RX ring-buffer (in NIC mem) */ + + u_char mem_shared; /* NIC memory is shared with host */ + u_char xmit_busy; /* transmitter is busy */ + u_char txb_cnt; /* number of transmit buffers */ + u_char txb_inuse; /* number of TX buffers currently in-use */ + + u_char txb_new; /* pointer to where new buffer will be added */ + u_char txb_next_tx; /* pointer to next buffer ready to xmit */ + u_short txb_len[8]; /* buffered xmit buffer lengths */ + u_char tx_page_start; /* first page of TX buffer area */ + u_char rec_page_start; /* first page of RX ring-buffer */ + u_char rec_page_stop; /* last page of RX ring-buffer */ + u_char next_packet; /* pointer to next unread RX packet */ +} ed_softc[NED]; + +int ed_attach(struct isa_device *); +void ed_init(int); +void edintr(int); +int ed_ioctl(struct ifnet *, int, caddr_t); +int ed_probe(struct isa_device *); +void ed_start(struct ifnet *); +void ed_reset(int); +void ed_watchdog(int); + +#ifdef MULTICAST +void ds_getmcaf(); + +#endif + +static void ed_get_packet(struct ed_softc *, char *, int /* u_short */ , int); +static void ed_stop(int); + +static inline void ed_rint(); +static inline void ed_xmit(); +static inline char *ed_ring_copy(); + +void ed_pio_readmem(), ed_pio_writemem(); +u_short ed_pio_write_mbufs(); + +void ed_setrcr(struct ifnet *, struct ed_softc *); + +struct trailer_header { + u_short ether_type; + u_short ether_residual; +}; + +struct isa_driver eddriver = { + ed_probe, + ed_attach, + "ed" +}; + +/* + * Interrupt conversion table for WD/SMC ASIC + * (IRQ* are defined in icu.h) + */ +static unsigned short ed_intr_mask[] = { + IRQ9, + IRQ3, + IRQ5, + IRQ7, + IRQ10, + IRQ11, + IRQ15, + IRQ4 +}; + +/* + * Interrupt conversion table for 585/790 Combo + */ +static unsigned short ed_790_intr_mask[] = { + 0, + IRQ9, + IRQ3, + IRQ5, + IRQ7, + IRQ10, + IRQ11, + IRQ15 +}; + +#define ETHER_MIN_LEN 64 +#define ETHER_MAX_LEN 1518 +#define ETHER_ADDR_LEN 6 +#define ETHER_HDR_SIZE 14 + +/* + * Determine if the device is present + * + * on entry: + * a pointer to an isa_device struct + * on exit: + * NULL if device not found + * or # of i/o addresses used (if found) + */ +int +ed_probe(isa_dev) + struct isa_device *isa_dev; +{ + struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; + int nports; + + if (nports = ed_probe_WD80x3(isa_dev)) + return (nports); + + if (nports = ed_probe_3Com(isa_dev)) + return (nports); + + if (nports = ed_probe_Novell(isa_dev)) + return (nports); + + return (0); +} + +/* + * Generic probe routine for testing for the existance of a DS8390. + * Must be called after the NIC has just been reset. This routine + * works by looking at certain register values that are gauranteed + * to be initialized a certain way after power-up or reset. Seems + * not to currently work on the 83C690. + * + * Specifically: + * + * Register reset bits set bits + * Command Register (CR) TXP, STA RD2, STP + * Interrupt Status (ISR) RST + * Interrupt Mask (IMR) All bits + * Data Control (DCR) LAS + * Transmit Config. (TCR) LB1, LB0 + * + * We only look at the CR and ISR registers, however, because looking at + * the others would require changing register pages (which would be + * intrusive if this isn't an 8390). + * + * Return 1 if 8390 was found, 0 if not. + */ + +int +ed_probe_generic8390(sc) + struct ed_softc *sc; +{ + if ((inb(sc->nic_addr + ED_P0_CR) & + (ED_CR_RD2 | ED_CR_TXP | ED_CR_STA | ED_CR_STP)) != + (ED_CR_RD2 | ED_CR_STP)) + return (0); + if ((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) != ED_ISR_RST) + return (0); + + return (1); +} + +/* + * Probe and vendor-specific initialization routine for SMC/WD80x3 boards + */ +int +ed_probe_WD80x3(isa_dev) + struct isa_device *isa_dev; +{ + struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; + int i; + u_int memsize; + u_char iptr, isa16bit, sum; + + sc->asic_addr = isa_dev->id_iobase; + sc->nic_addr = sc->asic_addr + ED_WD_NIC_OFFSET; + sc->is790 = 0; + +#ifdef TOSH_ETHER + outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_POW); + DELAY(10000); +#endif + + /* + * Attempt to do a checksum over the station address PROM. If it + * fails, it's probably not a SMC/WD board. There is a problem with + * this, though: some clone WD boards don't pass the checksum test. + * Danpex boards for one. + */ + for (sum = 0, i = 0; i < 8; ++i) + sum += inb(sc->asic_addr + ED_WD_PROM + i); + + if (sum != ED_WD_ROM_CHECKSUM_TOTAL) { + + /* + * Checksum is invalid. This often happens with cheap WD8003E + * clones. In this case, the checksum byte (the eighth byte) + * seems to always be zero. + */ + if (inb(sc->asic_addr + ED_WD_CARD_ID) != ED_TYPE_WD8003E || + inb(sc->asic_addr + ED_WD_PROM + 7) != 0) + return (0); + } + /* reset card to force it into a known state. */ +#ifdef TOSH_ETHER + outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST | ED_WD_MSR_POW); +#else + outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_RST); +#endif + DELAY(100); + outb(sc->asic_addr + ED_WD_MSR, inb(sc->asic_addr + ED_WD_MSR) & ~ED_WD_MSR_RST); + /* wait in the case this card is reading it's EEROM */ + DELAY(5000); + + sc->vendor = ED_VENDOR_WD_SMC; + sc->type = inb(sc->asic_addr + ED_WD_CARD_ID); + + /* + * Set initial values for width/size. + */ + memsize = 8192; + isa16bit = 0; + switch (sc->type) { + case ED_TYPE_WD8003S: + sc->type_str = "WD8003S"; + break; + case ED_TYPE_WD8003E: + sc->type_str = "WD8003E"; + break; + case ED_TYPE_WD8003EB: + sc->type_str = "WD8003EB"; + break; + case ED_TYPE_WD8003W: + sc->type_str = "WD8003W"; + break; + case ED_TYPE_WD8013EBT: + sc->type_str = "WD8013EBT"; + memsize = 16384; + isa16bit = 1; + break; + case ED_TYPE_WD8013W: + sc->type_str = "WD8013W"; + memsize = 16384; + isa16bit = 1; + break; + case ED_TYPE_WD8013EP: /* also WD8003EP */ + if (inb(sc->asic_addr + ED_WD_ICR) + & ED_WD_ICR_16BIT) { + isa16bit = 1; + memsize = 16384; + sc->type_str = "WD8013EP"; + } else { + sc->type_str = "WD8003EP"; + } + break; + case ED_TYPE_WD8013WC: + sc->type_str = "WD8013WC"; + memsize = 16384; + isa16bit = 1; + break; + case ED_TYPE_WD8013EBP: + sc->type_str = "WD8013EBP"; + memsize = 16384; + isa16bit = 1; + break; + case ED_TYPE_WD8013EPC: + sc->type_str = "WD8013EPC"; + memsize = 16384; + isa16bit = 1; + break; + case ED_TYPE_SMC8216C: + sc->type_str = "SMC8216/SMC8216C"; + memsize = 16384; + isa16bit = 1; + sc->is790 = 1; + break; + case ED_TYPE_SMC8216T: + sc->type_str = "SMC8216T"; + memsize = 16384; + isa16bit = 1; + sc->is790 = 1; + break; +#ifdef TOSH_ETHER + case ED_TYPE_TOSHIBA1: + sc->type_str = "Toshiba1"; + memsize = 32768; + isa16bit = 1; + break; + case ED_TYPE_TOSHIBA4: + sc->type_str = "Toshiba4"; + memsize = 32768; + isa16bit = 1; + break; +#endif + default: + sc->type_str = ""; + break; + } + + /* + * Make some adjustments to initial values depending on what is found + * in the ICR. + */ + if (isa16bit && (sc->type != ED_TYPE_WD8013EBT) +#ifdef TOSH_ETHER + && (sc->type != ED_TYPE_TOSHIBA1) && (sc->type != ED_TYPE_TOSHIBA4) +#endif + && ((inb(sc->asic_addr + ED_WD_ICR) & ED_WD_ICR_16BIT) == 0)) { + isa16bit = 0; + memsize = 8192; + } +#if ED_DEBUG + printf("type = %x type_str=%s isa16bit=%d memsize=%d id_msize=%d\n", + sc->type, sc->type_str, isa16bit, memsize, isa_dev->id_msize); + for (i = 0; i < 8; i++) + printf("%x -> %x\n", i, inb(sc->asic_addr + i)); +#endif + + /* + * Allow the user to override the autoconfiguration + */ + if (isa_dev->id_msize) + memsize = isa_dev->id_msize; + + /* + * (note that if the user specifies both of the following flags that + * '8bit' mode intentionally has precedence) + */ + if (isa_dev->id_flags & ED_FLAGS_FORCE_16BIT_MODE) + isa16bit = 1; + if (isa_dev->id_flags & ED_FLAGS_FORCE_8BIT_MODE) + isa16bit = 0; + + /* + * Check 83C584 interrupt configuration register if this board has one + * XXX - we could also check the IO address register. But why + * bother...if we get past this, it *has* to be correct. + */ + if ((sc->type & ED_WD_SOFTCONFIG) && (!sc->is790)) { + + /* + * Assemble together the encoded interrupt number. + */ + iptr = (inb(isa_dev->id_iobase + ED_WD_ICR) & ED_WD_ICR_IR2) | + ((inb(isa_dev->id_iobase + ED_WD_IRR) & + (ED_WD_IRR_IR0 | ED_WD_IRR_IR1)) >> 5); + + /* + * Translate it using translation table, and check for + * correctness. + */ + if (ed_intr_mask[iptr] != isa_dev->id_irq) { + printf("ed%d: kernel configured irq %d doesn't match board configured irq %d\n", + isa_dev->id_unit, ffs(isa_dev->id_irq) - 1, + ffs(ed_intr_mask[iptr]) - 1); + return (0); + } + + /* + * Enable the interrupt. + */ + outb(isa_dev->id_iobase + ED_WD_IRR, + inb(isa_dev->id_iobase + ED_WD_IRR) | ED_WD_IRR_IEN); + } + if (sc->is790) { + outb(isa_dev->id_iobase + ED_WD790_HWR, + inb(isa_dev->id_iobase + ED_WD790_HWR) | ED_WD790_HWR_SWH); + iptr = (((inb(isa_dev->id_iobase + ED_WD790_GCR) & ED_WD790_GCR_IR2) >> 4) | + (inb(isa_dev->id_iobase + ED_WD790_GCR) & + (ED_WD790_GCR_IR1 | ED_WD790_GCR_IR0)) >> 2); + outb(isa_dev->id_iobase + ED_WD790_HWR, + inb(isa_dev->id_iobase + ED_WD790_HWR) & ~ED_WD790_HWR_SWH); + + if (ed_790_intr_mask[iptr] != isa_dev->id_irq) { + printf("ed%d: kernel configured irq %d doesn't match board configured irq %d %d\n", + isa_dev->id_unit, ffs(isa_dev->id_irq) - 1, + ffs(ed_790_intr_mask[iptr]) - 1, iptr); + return 0; + } + + /* + * Enable interrupts. + */ + outb(isa_dev->id_iobase + ED_WD790_ICR, + inb(isa_dev->id_iobase + ED_WD790_ICR) | ED_WD790_ICR_EIL); + } + sc->isa16bit = isa16bit; + +/* XXX - I'm not sure if PIO mode is even possible on WD/SMC boards */ +#ifdef notyet + + /* + * The following allows the WD/SMC boards to be used in Programmed I/O + * mode - without mapping the NIC memory shared. ...Not the prefered + * way, but it might be the only way. + */ + if (isa_dev->id_flags & ED_FLAGS_FORCE_PIO) { + sc->mem_shared = 0; + isa_dev->id_maddr = 0; + } else { + sc->mem_shared = 1; + } +#else + sc->mem_shared = 1; +#endif + isa_dev->id_msize = memsize; + + sc->mem_start = (caddr_t) isa_dev->id_maddr; + + /* + * allocate one xmit buffer if < 16k, two buffers otherwise + */ + if ((memsize < 16384) || (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING)) { + sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); + sc->txb_cnt = 1; + sc->rec_page_start = ED_TXBUF_SIZE; + } else { + sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE * 2); + sc->txb_cnt = 2; + sc->rec_page_start = ED_TXBUF_SIZE * 2; + } + sc->mem_size = memsize; + sc->mem_end = sc->mem_start + memsize; + sc->rec_page_stop = memsize / ED_PAGE_SIZE; + sc->tx_page_start = ED_WD_PAGE_OFFSET; + + /* + * Get station address from on-board ROM + */ + for (i = 0; i < ETHER_ADDR_LEN; ++i) + sc->arpcom.ac_enaddr[i] = inb(sc->asic_addr + ED_WD_PROM + i); + + if (sc->mem_shared) { + + /* + * Set upper address bits and 8/16 bit access to shared memory + */ + if (isa16bit) { + if (sc->is790) { + sc->wd_laar_proto = inb(sc->asic_addr + ED_WD_LAAR); + outb(sc->asic_addr + ED_WD_LAAR, ED_WD_LAAR_M16EN); + } else { + outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto = + ED_WD_LAAR_L16EN | ED_WD_LAAR_M16EN | + ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI))); + } + } else { + if ((sc->type & ED_WD_SOFTCONFIG) || +#ifdef TOSH_ETHER + (sc->type == ED_TYPE_TOSHIBA1) || (sc->type == ED_TYPE_TOSHIBA4) || +#endif + (sc->type == ED_TYPE_WD8013EBT) && (!sc->is790)) { + outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto = + ((kvtop(sc->mem_start) >> 19) & ED_WD_LAAR_ADDRHI))); + } + } + + /* + * Set address and enable interface shared memory. + */ + if (!sc->is790) { +#ifdef TOSH_ETHER + outb(sc->asic_addr + ED_WD_MSR + 1, ((kvtop(sc->mem_start) >> 8) & 0xe0) | 4); + outb(sc->asic_addr + ED_WD_MSR + 2, ((kvtop(sc->mem_start) >> 16) & 0x0f)); + outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB | ED_WD_MSR_POW); + +#else + outb(sc->asic_addr + ED_WD_MSR, ((kvtop(sc->mem_start) >> 13) & + ED_WD_MSR_ADDR) | ED_WD_MSR_MENB); +#endif + } else { + outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); + outb(sc->asic_addr + 0x04, (inb(sc->asic_addr + 0x04) | 0x80)); + outb(sc->asic_addr + 0x0b, ((kvtop(sc->mem_start) >> 13) & 0x0f) | + ((kvtop(sc->mem_start) >> 11) & 0x40) | + (inb(sc->asic_addr + 0x0b) & 0xb0)); + outb(sc->asic_addr + 0x04, (inb(sc->asic_addr + 0x04) & ~0x80)); + } + + /* + * Now zero memory and verify that it is clear + */ + bzero(sc->mem_start, memsize); + + for (i = 0; i < memsize; ++i) + if (sc->mem_start[i]) { + printf("ed%d: failed to clear shared memory at %x - check configuration\n", + isa_dev->id_unit, kvtop(sc->mem_start + i)); + + /* + * Disable 16 bit access to shared memory + */ + if (isa16bit) { + if (sc->is790) { + outb(sc->asic_addr + ED_WD_MSR, 0x00); + } + outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= + ~ED_WD_LAAR_M16EN)); + } + return (0); + } + + /* + * Disable 16bit access to shared memory - we leave it + * disabled so that 1) machines reboot properly when the board + * is set 16 bit mode and there are conflicting 8bit + * devices/ROMS in the same 128k address space as this boards + * shared memory. and 2) so that other 8 bit devices with + * shared memory can be used in this 128k region, too. + */ + if (isa16bit) { + if (sc->is790) { + outb(sc->asic_addr + ED_WD_MSR, 0x00); + } + outb(sc->asic_addr + ED_WD_LAAR, (sc->wd_laar_proto &= + ~ED_WD_LAAR_M16EN)); + } + } + return (ED_WD_IO_PORTS); +} + +/* + * Probe and vendor-specific initialization routine for 3Com 3c503 boards + */ +int +ed_probe_3Com(isa_dev) + struct isa_device *isa_dev; +{ + struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; + int i; + u_int memsize; + u_char isa16bit, sum; + + sc->asic_addr = isa_dev->id_iobase + ED_3COM_ASIC_OFFSET; + sc->nic_addr = isa_dev->id_iobase + ED_3COM_NIC_OFFSET; + + /* + * Verify that the kernel configured I/O address matches the board + * configured address + */ + switch (inb(sc->asic_addr + ED_3COM_BCFR)) { + case ED_3COM_BCFR_300: + if (isa_dev->id_iobase != 0x300) + return (0); + break; + case ED_3COM_BCFR_310: + if (isa_dev->id_iobase != 0x310) + return (0); + break; + case ED_3COM_BCFR_330: + if (isa_dev->id_iobase != 0x330) + return (0); + break; + case ED_3COM_BCFR_350: + if (isa_dev->id_iobase != 0x350) + return (0); + break; + case ED_3COM_BCFR_250: + if (isa_dev->id_iobase != 0x250) + return (0); + break; + case ED_3COM_BCFR_280: + if (isa_dev->id_iobase != 0x280) + return (0); + break; + case ED_3COM_BCFR_2A0: + if (isa_dev->id_iobase != 0x2a0) + return (0); + break; + case ED_3COM_BCFR_2E0: + if (isa_dev->id_iobase != 0x2e0) + return (0); + break; + default: + return (0); + } + + /* + * Verify that the kernel shared memory address matches the board + * configured address. + */ + switch (inb(sc->asic_addr + ED_3COM_PCFR)) { + case ED_3COM_PCFR_DC000: + if (kvtop(isa_dev->id_maddr) != 0xdc000) + return (0); + break; + case ED_3COM_PCFR_D8000: + if (kvtop(isa_dev->id_maddr) != 0xd8000) + return (0); + break; + case ED_3COM_PCFR_CC000: + if (kvtop(isa_dev->id_maddr) != 0xcc000) + return (0); + break; + case ED_3COM_PCFR_C8000: + if (kvtop(isa_dev->id_maddr) != 0xc8000) + return (0); + break; + default: + return (0); + } + + + /* + * Reset NIC and ASIC. Enable on-board transceiver throughout reset + * sequence because it'll lock up if the cable isn't connected if we + * don't. + */ + outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_RST | ED_3COM_CR_XSEL); + + /* + * Wait for a while, then un-reset it + */ + DELAY(50); + + /* + * The 3Com ASIC defaults to rather strange settings for the CR after + * a reset - it's important to set it again after the following outb + * (this is done when we map the PROM below). + */ + outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); + + /* + * Wait a bit for the NIC to recover from the reset + */ + DELAY(5000); + + sc->vendor = ED_VENDOR_3COM; + sc->type_str = "3c503"; + + sc->mem_shared = 1; + + /* + * Hmmm...a 16bit 3Com board has 16k of memory, but only an 8k window + * to it. + */ + memsize = 8192; + + /* + * Get station address from on-board ROM + */ + + /* + * First, map ethernet address PROM over the top of where the NIC + * registers normally appear. + */ + outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_EALO | ED_3COM_CR_XSEL); + + for (i = 0; i < ETHER_ADDR_LEN; ++i) + sc->arpcom.ac_enaddr[i] = inb(sc->nic_addr + i); + + /* + * Unmap PROM - select NIC registers. The proper setting of the + * tranceiver is set in ed_init so that the attach code is given a + * chance to set the default based on a compile-time config option + */ + outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); + + /* + * Determine if this is an 8bit or 16bit board + */ + + /* + * select page 0 registers + */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + + /* + * Attempt to clear WTS bit. If it doesn't clear, then this is a 16bit + * board. + */ + outb(sc->nic_addr + ED_P0_DCR, 0); + + /* + * select page 2 registers + */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_2 | ED_CR_RD2 | ED_CR_STP); + + /* + * The 3c503 forces the WTS bit to a one if this is a 16bit board + */ + if (inb(sc->nic_addr + ED_P2_DCR) & ED_DCR_WTS) + isa16bit = 1; + else + isa16bit = 0; + + /* + * select page 0 registers + */ + outb(sc->nic_addr + ED_P2_CR, ED_CR_RD2 | ED_CR_STP); + + sc->mem_start = (caddr_t) isa_dev->id_maddr; + sc->mem_size = memsize; + sc->mem_end = sc->mem_start + memsize; + + /* + * We have an entire 8k window to put the transmit buffers on the + * 16bit boards. But since the 16bit 3c503's shared memory is only + * fast enough to overlap the loading of one full-size packet, trying + * to load more than 2 buffers can actually leave the transmitter idle + * during the load. So 2 seems the best value. (Although a mix of + * variable-sized packets might change this assumption. Nonetheless, + * we optimize for linear transfers of same-size packets.) + */ + if (isa16bit) { + if (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING) + sc->txb_cnt = 1; + else + sc->txb_cnt = 2; + + sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_16BIT; + sc->rec_page_start = ED_3COM_RX_PAGE_OFFSET_16BIT; + sc->rec_page_stop = memsize / ED_PAGE_SIZE + + ED_3COM_RX_PAGE_OFFSET_16BIT; + sc->mem_ring = sc->mem_start; + } else { + sc->txb_cnt = 1; + sc->tx_page_start = ED_3COM_TX_PAGE_OFFSET_8BIT; + sc->rec_page_start = ED_TXBUF_SIZE + ED_3COM_TX_PAGE_OFFSET_8BIT; + sc->rec_page_stop = memsize / ED_PAGE_SIZE + + ED_3COM_TX_PAGE_OFFSET_8BIT; + sc->mem_ring = sc->mem_start + (ED_PAGE_SIZE * ED_TXBUF_SIZE); + } + + sc->isa16bit = isa16bit; + + /* + * Initialize GA page start/stop registers. Probably only needed if + * doing DMA, but what the hell. + */ + outb(sc->asic_addr + ED_3COM_PSTR, sc->rec_page_start); + outb(sc->asic_addr + ED_3COM_PSPR, sc->rec_page_stop); + + /* + * Set IRQ. 3c503 only allows a choice of irq 2-5. + */ + switch (isa_dev->id_irq) { + case IRQ2: + outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ2); + break; + case IRQ3: + outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ3); + break; + case IRQ4: + outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ4); + break; + case IRQ5: + outb(sc->asic_addr + ED_3COM_IDCFR, ED_3COM_IDCFR_IRQ5); + break; + default: + printf("ed%d: Invalid irq configuration (%d) must be 2-5 for 3c503\n", + isa_dev->id_unit, ffs(isa_dev->id_irq) - 1); + return (0); + } + + /* + * Initialize GA configuration register. Set bank and enable shared + * mem. + */ + outb(sc->asic_addr + ED_3COM_GACFR, ED_3COM_GACFR_RSEL | + ED_3COM_GACFR_MBS0); + + /* + * Initialize "Vector Pointer" registers. These gawd-awful things are + * compared to 20 bits of the address on ISA, and if they match, the + * shared memory is disabled. We set them to 0xffff0...allegedly the + * reset vector. + */ + outb(sc->asic_addr + ED_3COM_VPTR2, 0xff); + outb(sc->asic_addr + ED_3COM_VPTR1, 0xff); + outb(sc->asic_addr + ED_3COM_VPTR0, 0x00); + + /* + * Zero memory and verify that it is clear + */ + bzero(sc->mem_start, memsize); + + for (i = 0; i < memsize; ++i) + if (sc->mem_start[i]) { + printf("ed%d: failed to clear shared memory at %x - check configuration\n", + isa_dev->id_unit, kvtop(sc->mem_start + i)); + return (0); + } + isa_dev->id_msize = memsize; + return (ED_3COM_IO_PORTS); +} + +/* + * Probe and vendor-specific initialization routine for NE1000/2000 boards + */ +int +ed_probe_Novell(isa_dev) + struct isa_device *isa_dev; +{ + struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; + u_int memsize, n; + u_char romdata[16], isa16bit = 0, tmp; + static char test_pattern[32] = "THIS is A memory TEST pattern"; + char test_buffer[32]; + + sc->asic_addr = isa_dev->id_iobase + ED_NOVELL_ASIC_OFFSET; + sc->nic_addr = isa_dev->id_iobase + ED_NOVELL_NIC_OFFSET; + + /* XXX - do Novell-specific probe here */ + + /* Reset the board */ +#ifdef GWETHER + outb(sc->asic_addr + ED_NOVELL_RESET, 0); + DELAY(200); +#endif /* GWETHER */ + tmp = inb(sc->asic_addr + ED_NOVELL_RESET); + + /* + * I don't know if this is necessary; probably cruft leftover from + * Clarkson packet driver code. Doesn't do a thing on the boards I've + * tested. -DG [note that a outb(0x84, 0) seems to work here, and is + * non-invasive...but some boards don't seem to reset and I don't have + * complete documentation on what the 'right' thing to do is...so we + * do the invasive thing for now. Yuck.] + */ + outb(sc->asic_addr + ED_NOVELL_RESET, tmp); + DELAY(5000); + + /* + * This is needed because some NE clones apparently don't reset the + * NIC properly (or the NIC chip doesn't reset fully on power-up) XXX + * - this makes the probe invasive! ...Done against my better + * judgement. -DLG + */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + + DELAY(5000); + + /* Make sure that we really have an 8390 based board */ + if (!ed_probe_generic8390(sc)) + return (0); + + sc->vendor = ED_VENDOR_NOVELL; + sc->mem_shared = 0; + isa_dev->id_maddr = 0; + + /* + * Test the ability to read and write to the NIC memory. This has the + * side affect of determining if this is an NE1000 or an NE2000. + */ + + /* + * This prevents packets from being stored in the NIC memory when the + * readmem routine turns on the start bit in the CR. + */ + outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); + + /* Temporarily initialize DCR for byte operations */ + outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); + + outb(sc->nic_addr + ED_P0_PSTART, 8192 / ED_PAGE_SIZE); + outb(sc->nic_addr + ED_P0_PSTOP, 16384 / ED_PAGE_SIZE); + + sc->isa16bit = 0; + + /* + * Write a test pattern in byte mode. If this fails, then there + * probably isn't any memory at 8k - which likely means that the board + * is an NE2000. + */ + ed_pio_writemem(sc, test_pattern, 8192, sizeof(test_pattern)); + ed_pio_readmem(sc, 8192, test_buffer, sizeof(test_pattern)); + + if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) { + /* not an NE1000 - try NE2000 */ + + outb(sc->nic_addr + ED_P0_DCR, ED_DCR_WTS | ED_DCR_FT1 | ED_DCR_LS); + outb(sc->nic_addr + ED_P0_PSTART, 16384 / ED_PAGE_SIZE); + outb(sc->nic_addr + ED_P0_PSTOP, 32768 / ED_PAGE_SIZE); + + sc->isa16bit = 1; + + /* + * Write a test pattern in word mode. If this also fails, then + * we don't know what this board is. + */ + ed_pio_writemem(sc, test_pattern, 16384, sizeof(test_pattern)); + ed_pio_readmem(sc, 16384, test_buffer, sizeof(test_pattern)); + + if (bcmp(test_pattern, test_buffer, sizeof(test_pattern))) + return (0); /* not an NE2000 either */ + + sc->type = ED_TYPE_NE2000; + sc->type_str = "NE2000"; + } else { + sc->type = ED_TYPE_NE1000; + sc->type_str = "NE1000"; + } + + /* 8k of memory plus an additional 8k if 16bit */ + memsize = 8192 + sc->isa16bit * 8192; + +#if 0 /* probably not useful - NE boards only come two ways */ + /* allow kernel config file overrides */ + if (isa_dev->id_msize) + memsize = isa_dev->id_msize; +#endif + + sc->mem_size = memsize; + + /* NIC memory doesn't start at zero on an NE board */ + /* The start address is tied to the bus width */ + sc->mem_start = (char *) 8192 + sc->isa16bit * 8192; + sc->mem_end = sc->mem_start + memsize; + sc->tx_page_start = memsize / ED_PAGE_SIZE; + +#ifdef GWETHER + { + int x, i, mstart = 0, msize = 0; + char pbuf0[ED_PAGE_SIZE], pbuf[ED_PAGE_SIZE], tbuf[ED_PAGE_SIZE]; + + for (i = 0; i < ED_PAGE_SIZE; i++) + pbuf0[i] = 0; + + /* Clear all the memory. */ + for (x = 1; x < 256; x++) + ed_pio_writemem(sc, pbuf0, x * 256, ED_PAGE_SIZE); + + /* Search for the start of RAM. */ + for (x = 1; x < 256; x++) { + ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); + if (memcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { + for (i = 0; i < ED_PAGE_SIZE; i++) + pbuf[i] = 255 - x; + ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); + ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); + if (memcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) { + mstart = x * ED_PAGE_SIZE; + msize = ED_PAGE_SIZE; + break; + } + } + } + + if (mstart == 0) { + printf("ed%d: Cannot find start of RAM.\n", isa_dev->id_unit); + return 0; + } + /* Search for the start of RAM. */ + for (x = (mstart / ED_PAGE_SIZE) + 1; x < 256; x++) { + ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); + if (memcmp(pbuf0, tbuf, ED_PAGE_SIZE) == 0) { + for (i = 0; i < ED_PAGE_SIZE; i++) + pbuf[i] = 255 - x; + ed_pio_writemem(sc, pbuf, x * 256, ED_PAGE_SIZE); + ed_pio_readmem(sc, x * 256, tbuf, ED_PAGE_SIZE); + if (memcmp(pbuf, tbuf, ED_PAGE_SIZE) == 0) + msize += ED_PAGE_SIZE; + else { + break; + } + } else { + break; + } + } + + if (msize == 0) { + printf("ed%d: Cannot find any RAM, start : %d, x = %d.\n", isa_dev->id_unit, mstart, x); + return 0; + } + printf("ed%d: RAM start at %d, size : %d.\n", isa_dev->id_unit, mstart, msize); + + sc->mem_size = msize; + sc->mem_start = (char *) mstart; + sc->mem_end = (char *) (msize + mstart); + sc->tx_page_start = mstart / ED_PAGE_SIZE; + } +#endif /* GWETHER */ + + /* + * Use one xmit buffer if < 16k, two buffers otherwise (if not told + * otherwise). + */ + if ((memsize < 16384) || (isa_dev->id_flags & ED_FLAGS_NO_MULTI_BUFFERING)) + sc->txb_cnt = 1; + else + sc->txb_cnt = 2; + + sc->rec_page_start = sc->tx_page_start + sc->txb_cnt * ED_TXBUF_SIZE; + sc->rec_page_stop = sc->tx_page_start + memsize / ED_PAGE_SIZE; + + sc->mem_ring = sc->mem_start + sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE; + + ed_pio_readmem(sc, 0, romdata, 16); + for (n = 0; n < ETHER_ADDR_LEN; n++) + sc->arpcom.ac_enaddr[n] = romdata[n * (sc->isa16bit + 1)]; + +#ifdef GWETHER + if (sc->arpcom.ac_enaddr[2] == 0x86) + sc->type_str = "Gateway AT"; +#endif /* GWETHER */ + + /* clear any pending interrupts that might have occurred above */ + outb(sc->nic_addr + ED_P0_ISR, 0xff); + + return (ED_NOVELL_IO_PORTS); +} + +/* + * Install interface into kernel networking data structures + */ +int +ed_attach(isa_dev) + struct isa_device *isa_dev; +{ + struct ed_softc *sc = &ed_softc[isa_dev->id_unit]; + struct ifnet *ifp = &sc->arpcom.ac_if; + + /* + * Set interface to stopped condition (reset) + */ + ed_stop(isa_dev->id_unit); + + /* + * Initialize ifnet structure + */ + ifp->if_unit = isa_dev->id_unit; + ifp->if_name = "ed"; + ifp->if_init = ed_init; + ifp->if_output = ether_output; + ifp->if_start = ed_start; + ifp->if_ioctl = ed_ioctl; + ifp->if_reset = ed_reset; + ifp->if_watchdog = ed_watchdog; + + /* + * Set default state for ALTPHYS flag (used to disable the tranceiver + * for AUI operation), based on compile-time config option. + */ + if (isa_dev->id_flags & ED_FLAGS_DISABLE_TRANCEIVER) + ifp->if_flags = + (IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_ALTPHYS); + else + ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS); +#ifdef MULTICAST + ifp->if_flags |= IFF_MULTICAST; +#endif + + /* + * Attach the interface + */ + if_attach(ifp); + + /* + * Print additional info when attached + */ + printf("ed%d: address %s, ", isa_dev->id_unit, + ether_sprintf(sc->arpcom.ac_enaddr)); + + if (sc->type_str && (*sc->type_str != 0)) + printf("type %s ", sc->type_str); + else + printf("type unknown (0x%x) ", sc->type); + + printf("%s ", sc->isa16bit ? "(16 bit)" : "(8 bit)"); + + printf("%s\n", ((sc->vendor == ED_VENDOR_3COM) && + (ifp->if_flags & IFF_ALTPHYS)) ? " tranceiver disabled" : ""); + + /* + * If BPF is in the kernel, call the attach for it + */ +#if NBPFILTER > 0 + bpfattach(&sc->bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); +#endif + return 1; +} + +/* + * Reset interface. + */ +void +ed_reset(unit) + int unit; +{ + int s; + + s = splimp(); + + /* + * Stop interface and re-initialize. + */ + ed_stop(unit); + ed_init(unit); + + (void) splx(s); +} + +/* + * Take interface offline. + */ +void +ed_stop(unit) + int unit; +{ + struct ed_softc *sc = &ed_softc[unit]; + int n = 5000; + + /* + * Stop everything on the interface, and select page 0 registers. + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + } + + /* + * Wait for interface to enter stopped state, but limit # of checks to + * 'n' (about 5ms). It shouldn't even take 5us on modern DS8390's, but + * just in case it's an old one. + */ + while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RST) == 0) && --n); + +} + +/* + * Device timeout/watchdog routine. Entered if the device neglects to + * generate an interrupt after a transmit has been started on it. + */ +void +ed_watchdog(unit) + int unit; +{ + struct ed_softc *sc = &ed_softc[unit]; + + log(LOG_ERR, "ed%d: device timeout\n", unit); + ++sc->arpcom.ac_if.if_oerrors; + + ed_reset(unit); +} + +/* + * Initialize device. + */ +void +ed_init(unit) + int unit; +{ + struct ed_softc *sc = &ed_softc[unit]; + struct ifnet *ifp = &sc->arpcom.ac_if; + int i, s; + u_char command; + + + /* address not known */ + if (ifp->if_addrlist == (struct ifaddr *) 0) + return; + + /* + * Initialize the NIC in the exact order outlined in the NS manual. + * This init procedure is "mandatory"...don't change what or when + * things happen. + */ + s = splimp(); + + /* reset transmitter flags */ + sc->xmit_busy = 0; + sc->arpcom.ac_if.if_timer = 0; + + sc->txb_inuse = 0; + sc->txb_new = 0; + sc->txb_next_tx = 0; + + /* This variable is used below - don't move this assignment */ + sc->next_packet = sc->rec_page_start + 1; + + /* + * Set interface for page 0, Remote DMA complete, Stopped + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + } + if (sc->isa16bit) { + + /* + * Set FIFO threshold to 8, No auto-init Remote DMA, byte + * order=80x86, word-wide DMA xfers, + */ + outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_WTS | ED_DCR_LS); + } else { + + /* + * Same as above, but byte-wide DMA xfers + */ + outb(sc->nic_addr + ED_P0_DCR, ED_DCR_FT1 | ED_DCR_LS); + } + + /* + * Clear Remote Byte Count Registers + */ + outb(sc->nic_addr + ED_P0_RBCR0, 0); + outb(sc->nic_addr + ED_P0_RBCR1, 0); + + /* + * For the moment, don't store incoming packets in memory. + */ + outb(sc->nic_addr + ED_P0_RCR, ED_RCR_MON); + + /* + * Place NIC in internal loopback mode + */ + outb(sc->nic_addr + ED_P0_TCR, ED_TCR_LB0); + + /* + * Initialize transmit/receive (ring-buffer) Page Start + */ + outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start); + outb(sc->nic_addr + ED_P0_PSTART, sc->rec_page_start); + /* Set lower bits of byte addressable framing to 0 */ + if (sc->is790) + outb(sc->nic_addr + 0x09, 0); + + /* + * Initialize Receiver (ring-buffer) Page Stop and Boundry + */ + outb(sc->nic_addr + ED_P0_PSTOP, sc->rec_page_stop); + outb(sc->nic_addr + ED_P0_BNRY, sc->rec_page_start); + + /* + * Clear all interrupts. A '1' in each bit position clears the + * corresponding flag. + */ + outb(sc->nic_addr + ED_P0_ISR, 0xff); + + /* + * Enable the following interrupts: receive/transmit complete, + * receive/transmit error, and Receiver OverWrite. + * + * Counter overflow and Remote DMA complete are *not* enabled. + */ + outb(sc->nic_addr + ED_P0_IMR, + ED_IMR_PRXE | ED_IMR_PTXE | ED_IMR_RXEE | ED_IMR_TXEE | ED_IMR_OVWE); + + /* + * Program Command Register for page 1 + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_RD2 | ED_CR_STP); + } + + /* + * Copy out our station address + */ + for (i = 0; i < ETHER_ADDR_LEN; ++i) + outb(sc->nic_addr + ED_P1_PAR0 + i, sc->arpcom.ac_enaddr[i]); + + /* + * Set Current Page pointer to next_packet (initialized above) + */ + outb(sc->nic_addr + ED_P1_CURR, sc->next_packet); + + /* + * Program Receiver Configuration Register and multicast filter. CR is + * set to page 0 on return. + */ + ed_setrcr(ifp, sc); + + /* + * Take interface out of loopback + */ + outb(sc->nic_addr + ED_P0_TCR, 0); + + /* + * If this is a 3Com board, the tranceiver must be software enabled + * (there is no settable hardware default). + */ + if (sc->vendor == ED_VENDOR_3COM) { + if (ifp->if_flags & IFF_ALTPHYS) { + outb(sc->asic_addr + ED_3COM_CR, 0); + } else { + outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); + } + } + + /* + * Set 'running' flag, and clear output active flag. + */ + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + + /* + * ...and attempt to start output + */ + ed_start(ifp); + + (void) splx(s); +} + +/* + * This routine actually starts the transmission on the interface + */ +static inline void +ed_xmit(ifp) + struct ifnet *ifp; +{ + struct ed_softc *sc = &ed_softc[ifp->if_unit]; + unsigned short len; + + len = sc->txb_len[sc->txb_next_tx]; + + /* + * Set NIC for page 0 register access + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + } + + /* + * Set TX buffer start page + */ + outb(sc->nic_addr + ED_P0_TPSR, sc->tx_page_start + + sc->txb_next_tx * ED_TXBUF_SIZE); + + /* + * Set TX length + */ + outb(sc->nic_addr + ED_P0_TBCR0, len); + outb(sc->nic_addr + ED_P0_TBCR1, len >> 8); + + /* + * Set page 0, Remote DMA complete, Transmit Packet, and *Start* + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_TXP | ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_TXP | ED_CR_STA); + } + sc->xmit_busy = 1; + + /* + * Point to next transmit buffer slot and wrap if necessary. + */ + sc->txb_next_tx++; + if (sc->txb_next_tx == sc->txb_cnt) + sc->txb_next_tx = 0; + + /* + * Set a timer just in case we never hear from the board again + */ + ifp->if_timer = 2; +} + +/* + * Start output on interface. + * We make two assumptions here: + * 1) that the current priority is set to splimp _before_ this code + * is called *and* is returned to the appropriate priority after + * return + * 2) that the IFF_OACTIVE flag is checked before this code is called + * (i.e. that the output part of the interface is idle) + */ +void +ed_start(ifp) + struct ifnet *ifp; +{ + struct ed_softc *sc = &ed_softc[ifp->if_unit]; + struct mbuf *m0, *m; + caddr_t buffer; + int len; + +outloop: + + /* + * First, see if there are buffered packets and an idle transmitter - + * should never happen at this point. + */ + if (sc->txb_inuse && (sc->xmit_busy == 0)) { + printf("ed: packets buffered, but transmitter idle\n"); + ed_xmit(ifp); + } + + /* + * See if there is room to put another packet in the buffer. + */ + if (sc->txb_inuse == sc->txb_cnt) { + + /* + * No room. Indicate this to the outside world and exit. + */ + ifp->if_flags |= IFF_OACTIVE; + return; + } + IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); + if (m == 0) { + + /* + * We are using the !OACTIVE flag to indicate to the outside + * world that we can accept an additional packet rather than + * that the transmitter is _actually_ active. Indeed, the + * transmitter may be active, but if we haven't filled all the + * buffers with data then we still want to accept more. + */ + ifp->if_flags &= ~IFF_OACTIVE; + return; + } + + /* + * Copy the mbuf chain into the transmit buffer + */ + + m0 = m; + + /* txb_new points to next open buffer slot */ + buffer = sc->mem_start + (sc->txb_new * ED_TXBUF_SIZE * ED_PAGE_SIZE); + + if (sc->mem_shared) { + + /* + * Special case setup for 16 bit boards... + */ + if (sc->isa16bit) { + switch (sc->vendor) { + + /* + * For 16bit 3Com boards (which have 16k of + * memory), we have the xmit buffers in a + * different page of memory ('page 0') - so + * change pages. + */ + case ED_VENDOR_3COM: + outb(sc->asic_addr + ED_3COM_GACFR, + ED_3COM_GACFR_RSEL); + break; + + /* + * Enable 16bit access to shared memory on + * WD/SMC boards Don't update wd_laar_proto + * because we want to restore the previous + * state (because an arp reply in the input + * code may cause a call-back to ed_start) XXX + * - the call-back to 'start' is a bug, IMHO. + */ + case ED_VENDOR_WD_SMC:{ + outb(sc->asic_addr + ED_WD_LAAR, + (sc->wd_laar_proto | ED_WD_LAAR_M16EN)); + if (sc->is790) { + outb(sc->asic_addr + ED_WD_MSR, ED_WD_MSR_MENB); + } + break; + } + } + } + for (len = 0; m != 0; m = m->m_next) { + bcopy(mtod(m, caddr_t), buffer, m->m_len); + buffer += m->m_len; + len += m->m_len; + } + + /* + * Restore previous shared memory access + */ + if (sc->isa16bit) { + switch (sc->vendor) { + case ED_VENDOR_3COM: + outb(sc->asic_addr + ED_3COM_GACFR, + ED_3COM_GACFR_RSEL | ED_3COM_GACFR_MBS0); + break; + case ED_VENDOR_WD_SMC:{ + if (sc->is790) { + outb(sc->asic_addr + ED_WD_MSR, 0x00); + } + outb(sc->asic_addr + ED_WD_LAAR, sc->wd_laar_proto); + break; + } + } + } + } else { + len = ed_pio_write_mbufs(sc, m, buffer); + } + + sc->txb_len[sc->txb_new] = max(len, ETHER_MIN_LEN); + + sc->txb_inuse++; + + /* + * Point to next buffer slot and wrap if necessary. + */ + sc->txb_new++; + if (sc->txb_new == sc->txb_cnt) + sc->txb_new = 0; + + if (sc->xmit_busy == 0) + ed_xmit(ifp); + + /* + * If there is BPF support in the configuration, tap off here. The + * following has support for converting trailer packets back to + * normal. XXX - support for trailer packets in BPF should be moved + * into the bpf code proper to avoid code duplication in all of the + * drivers. + */ +#if NBPFILTER > 0 + if (sc->bpf) { + u_short etype; + int off, datasize, resid; + struct ether_header *eh; + struct trailer_header trailer_header; + char ether_packet[ETHER_MAX_LEN]; + char *ep; + + ep = ether_packet; + + /* + * We handle trailers below: Copy ether header first, then + * residual data, then data. Put all this in a temporary + * buffer 'ether_packet' and send off to bpf. Since the system + * has generated this packet, we assume that all of the + * offsets in the packet are correct; if they're not, the + * system will almost certainly crash in m_copydata. We make + * no assumptions about how the data is arranged in the mbuf + * chain (i.e. how much data is in each mbuf, if mbuf clusters + * are used, etc.), which is why we use m_copydata to get the + * ether header rather than assume that this is located in the + * first mbuf. + */ + /* copy ether header */ + m_copydata(m0, 0, sizeof(struct ether_header), ep); + eh = (struct ether_header *) ep; + ep += sizeof(struct ether_header); + etype = ntohs(eh->ether_type); + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER) { + datasize = ((etype - ETHERTYPE_TRAIL) << 9); + off = datasize + sizeof(struct ether_header); + + /* copy trailer_header into a data structure */ + m_copydata(m0, off, sizeof(struct trailer_header), + (caddr_t) & trailer_header.ether_type); + + /* copy residual data */ + m_copydata(m0, off + sizeof(struct trailer_header), + resid = ntohs(trailer_header.ether_residual) - + sizeof(struct trailer_header), ep); + ep += resid; + + /* copy data */ + m_copydata(m0, sizeof(struct ether_header), + datasize, ep); + ep += datasize; + + /* restore original ether packet type */ + eh->ether_type = trailer_header.ether_type; + + bpf_tap(sc->bpf, ether_packet, ep - ether_packet); + } else + bpf_mtap(sc->bpf, m0); + } +#endif + + m_freem(m0); + + /* + * Loop back to the top to possibly buffer more packets + */ + goto outloop; +} + +/* + * Ethernet interface receiver interrupt. + */ +static inline void +ed_rint(unit) + int unit; +{ + register struct ed_softc *sc = &ed_softc[unit]; + u_char boundry, current; + u_short len; + struct ed_ring packet_hdr; + char *packet_ptr; + + /* + * Set NIC to page 1 registers to get 'current' pointer + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_RD2 | ED_CR_STA); + } + + /* + * 'sc->next_packet' is the logical beginning of the ring-buffer - + * i.e. it points to where new data has been buffered. The 'CURR' + * (current) register points to the logical end of the ring-buffer - + * i.e. it points to where additional new data will be added. We loop + * here until the logical beginning equals the logical end (or in + * other words, until the ring-buffer is empty). + */ + while (sc->next_packet != inb(sc->nic_addr + ED_P1_CURR)) { + + /* get pointer to this buffer's header structure */ + packet_ptr = sc->mem_ring + + (sc->next_packet - sc->rec_page_start) * ED_PAGE_SIZE; + + /* + * The byte count includes a 4 byte header that was added by + * the NIC. + */ + if (sc->mem_shared) + packet_hdr = *(struct ed_ring *) packet_ptr; + else + ed_pio_readmem(sc, packet_ptr, (char *) &packet_hdr, + sizeof(packet_hdr)); + len = packet_hdr.count; + if ((len >= ETHER_MIN_LEN) && (len <= ETHER_MAX_LEN)) { + + /* + * Go get packet. + */ + ed_get_packet(sc, packet_ptr + sizeof(struct ed_ring), + len - sizeof(struct ed_ring), packet_hdr.rsr & ED_RSR_PHY); + ++sc->arpcom.ac_if.if_ipackets; + } else { + + /* + * Really BAD...probably indicates that the ring + * pointers are corrupted. Also seen on early rev + * chips under high load - the byte order of the + * length gets switched. + */ + log(LOG_ERR, + "ed%d: NIC memory corrupt - invalid packet length %d\n", + unit, len); + ++sc->arpcom.ac_if.if_ierrors; + ed_reset(unit); + return; + } + + /* + * Update next packet pointer + */ + sc->next_packet = packet_hdr.next_packet; + + /* + * Update NIC boundry pointer - being careful to keep it one + * buffer behind. (as recommended by NS databook) + */ + boundry = sc->next_packet - 1; + if (boundry < sc->rec_page_start) + boundry = sc->rec_page_stop - 1; + + /* + * Set NIC to page 0 registers to update boundry register + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + } + outb(sc->nic_addr + ED_P0_BNRY, boundry); + + /* + * Set NIC to page 1 registers before looping to top (prepare + * to get 'CURR' current pointer) + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_RD2 | ED_CR_STA); + } + } +} + +/* + * Ethernet interface interrupt processor + */ +void +edintr(unit) + int unit; +{ + struct ed_softc *sc = &ed_softc[unit]; + u_char isr; + + /* + * Set NIC to page 0 registers + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + } + + /* + * loop until there are no more new interrupts + */ + while (isr = inb(sc->nic_addr + ED_P0_ISR)) { + + /* + * reset all the bits that we are 'acknowledging' by writing a + * '1' to each bit position that was set (writing a '1' + * *clears* the bit) + */ + outb(sc->nic_addr + ED_P0_ISR, isr); + + /* + * Handle transmitter interrupts. Handle these first because + * the receiver will reset the board under some conditions. + */ + if (isr & (ED_ISR_PTX | ED_ISR_TXE)) { + u_char collisions = inb(sc->nic_addr + ED_P0_NCR) & 0x0f; + + /* + * Check for transmit error. If a TX completed with an + * error, we end up throwing the packet away. Really + * the only error that is possible is excessive + * collisions, and in this case it is best to allow + * the automatic mechanisms of TCP to backoff the + * flow. Of course, with UDP we're screwed, but this + * is expected when a network is heavily loaded. + */ + (void) inb(sc->nic_addr + ED_P0_TSR); + if (isr & ED_ISR_TXE) { + + /* + * Excessive collisions (16) + */ + if ((inb(sc->nic_addr + ED_P0_TSR) & ED_TSR_ABT) + && (collisions == 0)) { + + /* + * When collisions total 16, the + * P0_NCR will indicate 0, and the + * TSR_ABT is set. + */ + collisions = 16; + } + + /* + * update output errors counter + */ + ++sc->arpcom.ac_if.if_oerrors; + } else { + + /* + * Update total number of successfully + * transmitted packets. + */ + ++sc->arpcom.ac_if.if_opackets; + } + + /* + * reset tx busy and output active flags + */ + sc->xmit_busy = 0; + sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + + /* + * clear watchdog timer + */ + sc->arpcom.ac_if.if_timer = 0; + + /* + * Add in total number of collisions on last + * transmission. + */ + sc->arpcom.ac_if.if_collisions += collisions; + + /* + * Decrement buffer in-use count if not zero (can only + * be zero if a transmitter interrupt occured while + * not actually transmitting). If data is ready to + * transmit, start it transmitting, otherwise defer + * until after handling receiver + */ + if (sc->txb_inuse && --sc->txb_inuse) + ed_xmit(&sc->arpcom.ac_if); + } + + /* + * Handle receiver interrupts + */ + if (isr & (ED_ISR_PRX | ED_ISR_RXE | ED_ISR_OVW)) { + + /* + * Overwrite warning. In order to make sure that a + * lockup of the local DMA hasn't occurred, we reset + * and re-init the NIC. The NSC manual suggests only a + * partial reset/re-init is necessary - but some chips + * seem to want more. The DMA lockup has been seen + * only with early rev chips - Methinks this bug was + * fixed in later revs. -DG + */ + if (isr & ED_ISR_OVW) { + ++sc->arpcom.ac_if.if_ierrors; +#ifdef DIAGNOSTIC + log(LOG_WARNING, + "ed%d: warning - receiver ring buffer overrun\n", + unit); +#endif + + /* + * Stop/reset/re-init NIC + */ + ed_reset(unit); + } else { + + /* + * Receiver Error. One or more of: CRC error, + * frame alignment error FIFO overrun, or + * missed packet. + */ + if (isr & ED_ISR_RXE) { + ++sc->arpcom.ac_if.if_ierrors; +#ifdef ED_DEBUG + printf("ed%d: receive error %x\n", unit, + inb(sc->nic_addr + ED_P0_RSR)); +#endif + } + + /* + * Go get the packet(s) XXX - Doing this on an + * error is dubious because there shouldn't be + * any data to get (we've configured the + * interface to not accept packets with + * errors). + */ + + /* + * Enable 16bit access to shared memory first + * on WD/SMC boards. + */ + if (sc->isa16bit && + (sc->vendor == ED_VENDOR_WD_SMC)) { + + outb(sc->asic_addr + ED_WD_LAAR, + (sc->wd_laar_proto |= + ED_WD_LAAR_M16EN)); + if (sc->is790) { + outb(sc->asic_addr + ED_WD_MSR, + ED_WD_MSR_MENB); + } + } + ed_rint(unit); + + /* disable 16bit access */ + if (sc->isa16bit && + (sc->vendor == ED_VENDOR_WD_SMC)) { + + if (sc->is790) { + outb(sc->asic_addr + ED_WD_MSR, 0x00); + } + outb(sc->asic_addr + ED_WD_LAAR, + (sc->wd_laar_proto &= + ~ED_WD_LAAR_M16EN)); + } + } + } + + /* + * If it looks like the transmitter can take more data, + * attempt to start output on the interface. This is done + * after handling the receiver to give the receiver priority. + */ + if ((sc->arpcom.ac_if.if_flags & IFF_OACTIVE) == 0) + ed_start(&sc->arpcom.ac_if); + + /* + * return NIC CR to standard state: page 0, remote DMA + * complete, start (toggling the TXP bit off, even if was just + * set in the transmit routine, is *okay* - it is 'edge' + * triggered from low to high) + */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STA); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + } + + /* + * If the Network Talley Counters overflow, read them to reset + * them. It appears that old 8390's won't clear the ISR flag + * otherwise - resulting in an infinite loop. + */ + if (isr & ED_ISR_CNT) { + (void) inb(sc->nic_addr + ED_P0_CNTR0); + (void) inb(sc->nic_addr + ED_P0_CNTR1); + (void) inb(sc->nic_addr + ED_P0_CNTR2); + } + } +} + +/* + * Process an ioctl request. This code needs some work - it looks + * pretty ugly. + */ +int +ed_ioctl(ifp, command, data) + register struct ifnet *ifp; + int command; + caddr_t data; +{ + register struct ifaddr *ifa = (struct ifaddr *) data; + struct ed_softc *sc = &ed_softc[ifp->if_unit]; + struct ifreq *ifr = (struct ifreq *) data; + int s, error = 0; + + s = splimp(); + + switch (command) { + + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + + switch (ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + ed_init(ifp->if_unit); /* before arpwhohas */ + + /* + * See if another station has *our* IP address. i.e.: + * There is an address conflict! If a conflict exists, + * a message is sent to the console. + */ + ((struct arpcom *) ifp)->ac_ipaddr = IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *) ifp, &IA_SIN(ifa)->sin_addr); + break; +#endif +#ifdef NS + + /* + * XXX - This code is probably wrong + */ + case AF_NS: + { + register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + + if (ns_nullhost(*ina)) + ina->x_host = + *(union ns_host *) (sc->arpcom.ac_enaddr); + else { + bcopy((caddr_t) ina->x_host.c_host, + (caddr_t) sc->arpcom.ac_enaddr, + sizeof(sc->arpcom.ac_enaddr)); + } + + /* + * Set new address + */ + ed_init(ifp->if_unit); + break; + } +#endif + default: + ed_init(ifp->if_unit); + break; + } + break; + + case SIOCGIFADDR: + { + struct sockaddr *sa; + + sa = (struct sockaddr *) & ifr->ifr_data; + bcopy((caddr_t) sc->arpcom.ac_enaddr, + (caddr_t) sa->sa_data, ETHER_ADDR_LEN); + } + break; + + case SIOCSIFFLAGS: + + /* + * If interface is marked down and it is running, then stop it + */ + if (((ifp->if_flags & IFF_UP) == 0) && + (ifp->if_flags & IFF_RUNNING)) { + ed_stop(ifp->if_unit); + ifp->if_flags &= ~IFF_RUNNING; + } else { + + /* + * If interface is marked up and it is stopped, then + * start it + */ + if ((ifp->if_flags & IFF_UP) && + ((ifp->if_flags & IFF_RUNNING) == 0)) + ed_init(ifp->if_unit); + } + +#if NBPFILTER > 0 + + /* + * Promiscuous flag may have changed, so reprogram the RCR. + */ + ed_setrcr(ifp, sc); +#endif + + /* + * An unfortunate hack to provide the (required) software + * control of the tranceiver for 3Com boards. The ALTPHYS flag + * disables the tranceiver if set. + */ + if (sc->vendor == ED_VENDOR_3COM) { + if (ifp->if_flags & IFF_ALTPHYS) { + outb(sc->asic_addr + ED_3COM_CR, 0); + } else { + outb(sc->asic_addr + ED_3COM_CR, ED_3COM_CR_XSEL); + } + } + break; +#ifdef MULTICAST + case SIOCADDMULTI: + case SIOCDELMULTI: + + /* + * Update out multicast list. + */ + error = (command == SIOCADDMULTI) ? + ether_addmulti(ifr, &sc->arpcom) : + ether_delmulti(ifr, &sc->arpcom); + + if (error == ENETRESET) { + + /* + * Multicast list has changed; set the hardware filter + * accordingly. + */ + ed_setrcr(ifp, sc); + error = 0; + } + break; +#endif + case SIOCSIFMTU: + + /* + * Set the interface MTU. + */ + if (ifr->ifr_mtu > ETHERMTU) { + error = EINVAL; + } else { + ifp->if_mtu = ifr->ifr_mtu; + } + break; + + default: + error = EINVAL; + } + (void) splx(s); + return (error); +} + +/* + * Macro to calculate a new address within shared memory when given an offset + * from an address, taking into account ring-wrap. + */ +#define ringoffset(sc, start, off, type) \ + ((type)( ((caddr_t)(start)+(off) >= (sc)->mem_end) ? \ + (((caddr_t)(start)+(off))) - (sc)->mem_end \ + + (sc)->mem_ring: \ + ((caddr_t)(start)+(off)) )) + +/* + * Retreive packet from shared memory and send to the next level up via + * ether_input(). If there is a BPF listener, give a copy to BPF, too. + */ +static void +ed_get_packet(sc, buf, len, multicast) + struct ed_softc *sc; + char *buf; + u_short len; + int multicast; +{ + struct ether_header *eh; + struct mbuf *m, *head = 0, *ed_ring_to_mbuf(); + u_short off; + int resid; + u_short etype; + struct trailer_header trailer_header; + + /* Allocate a header mbuf */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == 0) + goto bad; + m->m_pkthdr.rcvif = &sc->arpcom.ac_if; + m->m_pkthdr.len = len; + m->m_len = 0; + head = m; + + /* The following sillines is to make NFS happy */ +#define EROUND ((sizeof(struct ether_header) + 3) & ~3) +#define EOFF (EROUND - sizeof(struct ether_header)) + + /* + * The following assumes there is room for the ether header in the + * header mbuf + */ + head->m_data += EOFF; + eh = mtod(head, struct ether_header *); + + if (sc->mem_shared) + bcopy(buf, mtod(head, caddr_t), sizeof(struct ether_header)); + else + ed_pio_readmem(sc, buf, mtod(head, caddr_t), + sizeof(struct ether_header)); + buf += sizeof(struct ether_header); + head->m_len += sizeof(struct ether_header); + len -= sizeof(struct ether_header); + + etype = ntohs((u_short) eh->ether_type); + + /* + * Deal with trailer protocol: If trailer protocol, calculate the + * datasize as 'off', which is also the offset to the trailer header. + * Set resid to the amount of packet data following the trailer + * header. Finally, copy residual data into mbuf chain. + */ + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER) { + + off = (etype - ETHERTYPE_TRAIL) << 9; + if ((off + sizeof(struct trailer_header)) > len) + goto bad; /* insanity */ + + /* + * If we have shared memory, we can get info directly from the + * stored packet, otherwise we must get a local copy of the + * trailer header using PIO. + */ + if (sc->mem_shared) { + eh->ether_type = *ringoffset(sc, buf, off, u_short *); + resid = ntohs(*ringoffset(sc, buf, off + 2, u_short *)); + } else { + struct trailer_header trailer_header; + + ed_pio_readmem(sc, + ringoffset(sc, buf, off, caddr_t), + (char *) &trailer_header, + sizeof(trailer_header)); + eh->ether_type = trailer_header.ether_type; + resid = trailer_header.ether_residual; + } + + if ((off + resid) > len) + goto bad; /* insanity */ + + resid -= sizeof(struct trailer_header); + if (resid < 0) + goto bad; /* insanity */ + + m = ed_ring_to_mbuf(sc, ringoffset(sc, buf, off + 4, char *), + head, resid); + if (m == 0) + goto bad; + + len = off; + head->m_pkthdr.len -= 4; /* subtract trailer header */ + } + + /* + * Pull packet off interface. Or if this was a trailer packet, the + * data portion is appended. + */ + m = ed_ring_to_mbuf(sc, buf, m, len); + if (m == 0) + goto bad; + +#if NBPFILTER > 0 + + /* + * Check if there's a BPF listener on this interface. If so, hand off + * the raw packet to bpf. + */ + if (sc->bpf) { + bpf_mtap(sc->bpf, head); + + /* + * Note that the interface cannot be in promiscuous mode if + * there are no BPF listeners. And if we are in promiscuous + * mode, we have to check if this packet is really ours. + */ + if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && + bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, + sizeof(eh->ether_dhost)) != 0 && multicast == 0) { + m_freem(head); + return; + } + } +#endif + + /* + * Fix up data start offset in mbuf to point past ether header + */ + m_adj(head, sizeof(struct ether_header)); + + /* + * silly ether_input routine needs 'type' in host byte order + */ + eh->ether_type = ntohs(eh->ether_type); + + ether_input(&sc->arpcom.ac_if, eh, head); + return; + +bad: if (head) + m_freem(head); + return; +} + +/* + * Supporting routines + */ + +/* + * Given a NIC memory source address and a host memory destination + * address, copy 'amount' from NIC to host using Programmed I/O. + * The 'amount' is rounded up to a word - okay as long as mbufs + * are word sized. + * This routine is currently Novell-specific. + */ +void +ed_pio_readmem(sc, src, dst, amount) + struct ed_softc *sc; + unsigned short src; + unsigned char *dst; + unsigned short amount; +{ + unsigned short tmp_amount; + + /* select page 0 registers */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + + /* round up to a word */ + tmp_amount = amount; + if (amount & 1) + ++amount; + + /* set up DMA byte count */ + outb(sc->nic_addr + ED_P0_RBCR0, amount); + outb(sc->nic_addr + ED_P0_RBCR1, amount >> 8); + + /* set up source address in NIC mem */ + outb(sc->nic_addr + ED_P0_RSAR0, src); + outb(sc->nic_addr + ED_P0_RSAR1, src >> 8); + + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD0 | ED_CR_STA); + + if (sc->isa16bit) { + insw(sc->asic_addr + ED_NOVELL_DATA, dst, amount / 2); + } else + insb(sc->asic_addr + ED_NOVELL_DATA, dst, amount); + +} + +/* + * Stripped down routine for writing a linear buffer to NIC memory. + * Only used in the probe routine to test the memory. 'len' must + * be even. + */ +void +ed_pio_writemem(sc, src, dst, len) + struct ed_softc *sc; + char *src; + unsigned short dst; + unsigned short len; +{ + int maxwait = 100; /* about 120us */ + + /* select page 0 registers */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + + /* reset remote DMA complete flag */ + outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); + + /* set up DMA byte count */ + outb(sc->nic_addr + ED_P0_RBCR0, len); + outb(sc->nic_addr + ED_P0_RBCR1, len >> 8); + + /* set up destination address in NIC mem */ + outb(sc->nic_addr + ED_P0_RSAR0, dst); + outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); + + /* set remote DMA write */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); + + if (sc->isa16bit) + outsw(sc->asic_addr + ED_NOVELL_DATA, src, len / 2); + else + outsb(sc->asic_addr + ED_NOVELL_DATA, src, len); + + /* + * Wait for remote DMA complete. This is necessary because on the + * transmit side, data is handled internally by the NIC in bursts and + * we can't start another remote DMA until this one completes. Not + * waiting causes really bad things to happen - like the NIC + * irrecoverably jamming the ISA bus. + */ + while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); +} + +/* + * Write an mbuf chain to the destination NIC memory address using + * programmed I/O. + */ +u_short +ed_pio_write_mbufs(sc, m, dst) + struct ed_softc *sc; + struct mbuf *m; + unsigned short dst; +{ + unsigned short len, mb_offset; + struct mbuf *mp; + unsigned char residual[2]; + int maxwait = 100; /* about 120us */ + + /* First, count up the total number of bytes to copy */ + for (len = 0, mp = m; mp; mp = mp->m_next) + len += mp->m_len; + + /* select page 0 registers */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STA); + + /* reset remote DMA complete flag */ + outb(sc->nic_addr + ED_P0_ISR, ED_ISR_RDC); + + /* set up DMA byte count */ + outb(sc->nic_addr + ED_P0_RBCR0, len); + outb(sc->nic_addr + ED_P0_RBCR1, len >> 8); + + /* set up destination address in NIC mem */ + outb(sc->nic_addr + ED_P0_RSAR0, dst); + outb(sc->nic_addr + ED_P0_RSAR1, dst >> 8); + + /* set remote DMA write */ + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD1 | ED_CR_STA); + + mb_offset = 0; + + /* + * Transfer the mbuf chain to the NIC memory. The following code isn't + * too pretty. The problem is that we can only transfer words to the + * board, and if an mbuf has an odd number of bytes in it, this is a + * problem. It's not a simple matter of just removing a byte from the + * next mbuf (adjusting data++ and len--) because this will hose-over + * the mbuf chain which might be needed later for BPF. Instead, we + * maintain an offset (mb_offset) which let's us skip over the first + * byte in the following mbuf. + */ + while (m) { + if (m->m_len - mb_offset) { + if (sc->isa16bit) { + if ((m->m_len - mb_offset) > 1) + outsw(sc->asic_addr + ED_NOVELL_DATA, + mtod(m, caddr_t) + mb_offset, + (m->m_len - mb_offset) / 2); + + /* + * if odd number of bytes, get the odd byte + * from the next mbuf with data + */ + if ((m->m_len - mb_offset) & 1) { + /* first the last byte in current mbuf */ + residual[0] = *(mtod(m, caddr_t) + + m->m_len - 1); + + /* advance past any empty mbufs */ + while (m->m_next && (m->m_next->m_len == 0)) + m = m->m_next; + + if (m->m_next) { + + /* + * remove first byte in next + * mbuf + */ + residual[1] = *(mtod(m->m_next, caddr_t)); + mb_offset = 1; + } + outw(sc->asic_addr + ED_NOVELL_DATA, + *((unsigned short *) residual)); + } else + mb_offset = 0; + } else + outsb(sc->asic_addr + ED_NOVELL_DATA, m->m_data, m->m_len); + + } + m = m->m_next; + } + + /* + * Wait for remote DMA complete. This is necessary because on the + * transmit side, data is handled internally by the NIC in bursts and + * we can't start another remote DMA until this one completes. Not + * waiting causes really bad things to happen - like the NIC + * irrecoverably jamming the ISA bus. + */ + while (((inb(sc->nic_addr + ED_P0_ISR) & ED_ISR_RDC) != ED_ISR_RDC) && --maxwait); + + if (!maxwait) { + log(LOG_WARNING, "ed%d: remote transmit DMA failed to complete\n", + sc->arpcom.ac_if.if_unit); + ed_reset(sc->arpcom.ac_if.if_unit); + } + return (len); +} + +/* + * Given a source and destination address, copy 'amount' of a packet from + * the ring buffer into a linear destination buffer. Takes into account + * ring-wrap. + */ +static inline char * +ed_ring_copy(sc, src, dst, amount) + struct ed_softc *sc; + char *src; + char *dst; + u_short amount; +{ + u_short tmp_amount; + + /* does copy wrap to lower addr in ring buffer? */ + if (src + amount > sc->mem_end) { + tmp_amount = sc->mem_end - src; + + /* copy amount up to end of NIC memory */ + if (sc->mem_shared) + bcopy(src, dst, tmp_amount); + else + ed_pio_readmem(sc, src, dst, tmp_amount); + + amount -= tmp_amount; + src = sc->mem_ring; + dst += tmp_amount; + } + if (sc->mem_shared) + bcopy(src, dst, amount); + else + ed_pio_readmem(sc, src, dst, amount); + + return (src + amount); +} + +/* + * Copy data from receive buffer to end of mbuf chain + * allocate additional mbufs as needed. return pointer + * to last mbuf in chain. + * sc = ed info (softc) + * src = pointer in ed ring buffer + * dst = pointer to last mbuf in mbuf chain to copy to + * amount = amount of data to copy + */ +struct mbuf * +ed_ring_to_mbuf(sc, src, dst, total_len) + struct ed_softc *sc; + char *src; + struct mbuf *dst; + u_short total_len; +{ + register struct mbuf *m = dst; + + while (total_len) { + register u_short amount = min(total_len, M_TRAILINGSPACE(m)); + + if (amount == 0) { /* no more data in this mbuf, alloc + * another */ + + /* + * If there is enough data for an mbuf cluster, + * attempt to allocate one of those, otherwise, a + * regular mbuf will do. Note that a regular mbuf is + * always required, even if we get a cluster - getting + * a cluster does not allocate any mbufs, and one is + * needed to assign the cluster to. The mbuf that has + * a cluster extension can not be used to contain data + * - only the cluster can contain data. + */ + dst = m; + MGET(m, M_DONTWAIT, MT_DATA); + if (m == 0) + return (0); + + if (total_len >= MINCLSIZE) + MCLGET(m, M_DONTWAIT); + + m->m_len = 0; + dst->m_next = m; + amount = min(total_len, M_TRAILINGSPACE(m)); + } + src = ed_ring_copy(sc, src, mtod(m, caddr_t) + m->m_len, amount); + + m->m_len += amount; + total_len -= amount; + + } + return (m); +} + +void +ed_setrcr(ifp, sc) + struct ifnet *ifp; + struct ed_softc *sc; +{ + int i; + + /* set page 1 registers */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_PAGE_1 | ED_CR_RD2 | ED_CR_STP); + } + + if (ifp->if_flags & IFF_PROMISC) { + + /* + * Reconfigure the multicast filter. + */ + for (i = 0; i < 8; i++) + outb(sc->nic_addr + ED_P1_MAR0 + i, 0xff); + + /* + * And turn on promiscuous mode. Also enable reception of + * runts and packets with CRC & alignment errors. + */ + /* Set page 0 registers */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + } + outb(sc->nic_addr + ED_P0_RCR, ED_RCR_PRO | ED_RCR_AM | + ED_RCR_AB | ED_RCR_AR | ED_RCR_SEP); + } else { +#ifndef MULTICAST + + /* + * Initialize multicast address hashing registers to not + * accept multicasts. + */ + for (i = 0; i < 8; ++i) + outb(sc->nic_addr + ED_P1_MAR0 + i, 0x00); + + /* Set page 0 registers */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + } + + outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); +#else + /* set up multicast addresses and filter modes */ + if (ifp->if_flags & IFF_MULTICAST) { + u_long mcaf[2]; + + if (ifp->if_flags & IFF_ALLMULTI) { + mcaf[0] = 0xffffffff; + mcaf[1] = 0xffffffff; + } else + ds_getmcaf(sc, mcaf); + + /* + * Set multicast filter on chip. + */ + for (i = 0; i < 8; i++) + outb(sc->nic_addr + ED_P1_MAR0 + i, ((u_char *) mcaf)[i]); + + /* Set page 0 registers */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + } + + outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AM | ED_RCR_AB); + } else { + + /* + * Initialize multicast address hashing registers to + * not accept multicasts. + */ + for (i = 0; i < 8; ++i) + outb(sc->nic_addr + ED_P1_MAR0 + i, 0x00); + + /* Set page 0 registers */ + if (sc->is790) { + outb(sc->nic_addr + ED_P0_CR, ED_CR_STP); + } else { + outb(sc->nic_addr + ED_P0_CR, ED_CR_RD2 | ED_CR_STP); + } + + outb(sc->nic_addr + ED_P0_RCR, ED_RCR_AB); + } +#endif /* MULTICAST */ + } +} + +#ifdef MULTICAST +/* + * Compute crc for ethernet address + */ +u_long +ds_crc(ep) + u_char *ep; +{ +#define POLYNOMIAL 0x04c11db6 + register u_long crc = 0xffffffffL; + register int carry, i, j; + register u_char b; + + for (i = 6; --i >= 0;) { + b = *ep++; + for (j = 8; --j >= 0;) { + carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); + crc <<= 1; + b >>= 1; + if (carry) + crc = ((crc ^ POLYNOMIAL) | carry); + } + } + return crc; +#undef POLYNOMIAL +} + +/* + * Compute the multicast address filter from the + * list of multicast addresses we need to listen to. + */ +void +ds_getmcaf(sc, mcaf) + struct ed_softc *sc; + u_long *mcaf; +{ + register u_int index; + register u_char *af = (u_char *) mcaf; + register struct ether_multi *enm; + register struct ether_multistep step; + + mcaf[0] = 0; + mcaf[1] = 0; + + ETHER_FIRST_MULTI(step, &sc->arpcom, enm); + while (enm != NULL) { + if (bcmp(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) { + mcaf[0] = 0xffffffff; + mcaf[1] = 0xffffffff; + return; + } + index = ds_crc(enm->enm_addrlo, 6) >> 26; + af[index >> 3] |= 1 << (index & 7); + + ETHER_NEXT_MULTI(step, enm); + } +} +#endif +#endif diff --git a/sys/i386/isa/if_edreg.h b/sys/i386/isa/if_edreg.h new file mode 100644 index 0000000..1d56fdc --- /dev/null +++ b/sys/i386/isa/if_edreg.h @@ -0,0 +1,963 @@ +/* + * Copyright (C) 1993, David Greenman. This software may be used, modified, + * copied, distributed, and sold, in both source and binary form provided + * that the above copyright and these terms are retained. Under no + * circumstances is the author responsible for the proper functioning + * of this software, nor does the author assume any responsibility + * for damages incurred with its use. + * + * $Id: if_edreg.h,v 1.15 1994/08/02 07:39:30 davidg Exp $ + */ +/* + * National Semiconductor DS8390 NIC register definitions + * + * + * Modification history + * + * Revision 2.2 1993/11/29 16:33:39 davidg + * From Thomas Sandford <t.d.g.sandford@comp.brad.ac.uk> + * Add support for the 8013W board type + * + * Revision 2.1 1993/11/22 10:52:33 davidg + * patch to add support for SMC8216 (Elite-Ultra) boards + * from Glen H. Lowe + * + * Revision 2.0 93/09/29 00:37:15 davidg + * changed double buffering flag to multi buffering + * made changes/additions for 3c503 multi-buffering + * ...companion to Rev. 2.0 of 'ed' driver. + * + * Revision 1.1 93/06/23 03:01:07 davidg + * Initial revision + * + */ + +/* + * Page 0 register offsets + */ +#define ED_P0_CR 0x00 /* Command Register */ + +#define ED_P0_CLDA0 0x01 /* Current Local DMA Addr low (read) */ +#define ED_P0_PSTART 0x01 /* Page Start register (write) */ + +#define ED_P0_CLDA1 0x02 /* Current Local DMA Addr high (read) */ +#define ED_P0_PSTOP 0x02 /* Page Stop register (write) */ + +#define ED_P0_BNRY 0x03 /* Boundary Pointer */ + +#define ED_P0_TSR 0x04 /* Transmit Status Register (read) */ +#define ED_P0_TPSR 0x04 /* Transmit Page Start (write) */ + +#define ED_P0_NCR 0x05 /* Number of Collisions Reg (read) */ +#define ED_P0_TBCR0 0x05 /* Transmit Byte count, low (write) */ + +#define ED_P0_FIFO 0x06 /* FIFO register (read) */ +#define ED_P0_TBCR1 0x06 /* Transmit Byte count, high (write) */ + +#define ED_P0_ISR 0x07 /* Interrupt Status Register */ + +#define ED_P0_CRDA0 0x08 /* Current Remote DMA Addr low (read) */ +#define ED_P0_RSAR0 0x08 /* Remote Start Address low (write) */ + +#define ED_P0_CRDA1 0x09 /* Current Remote DMA Addr high (read) */ +#define ED_P0_RSAR1 0x09 /* Remote Start Address high (write) */ + +#define ED_P0_RBCR0 0x0a /* Remote Byte Count low (write) */ + +#define ED_P0_RBCR1 0x0b /* Remote Byte Count high (write) */ + +#define ED_P0_RSR 0x0c /* Receive Status (read) */ +#define ED_P0_RCR 0x0c /* Receive Configuration Reg (write) */ + +#define ED_P0_CNTR0 0x0d /* frame alignment error counter (read) */ +#define ED_P0_TCR 0x0d /* Transmit Configuration Reg (write) */ + +#define ED_P0_CNTR1 0x0e /* CRC error counter (read) */ +#define ED_P0_DCR 0x0e /* Data Configuration Reg (write) */ + +#define ED_P0_CNTR2 0x0f /* missed packet counter (read) */ +#define ED_P0_IMR 0x0f /* Interrupt Mask Register (write) */ + +/* + * Page 1 register offsets + */ +#define ED_P1_CR 0x00 /* Command Register */ +#define ED_P1_PAR0 0x01 /* Physical Address Register 0 */ +#define ED_P1_PAR1 0x02 /* Physical Address Register 1 */ +#define ED_P1_PAR2 0x03 /* Physical Address Register 2 */ +#define ED_P1_PAR3 0x04 /* Physical Address Register 3 */ +#define ED_P1_PAR4 0x05 /* Physical Address Register 4 */ +#define ED_P1_PAR5 0x06 /* Physical Address Register 5 */ +#define ED_P1_CURR 0x07 /* Current RX ring-buffer page */ +#define ED_P1_MAR0 0x08 /* Multicast Address Register 0 */ +#define ED_P1_MAR1 0x09 /* Multicast Address Register 1 */ +#define ED_P1_MAR2 0x0a /* Multicast Address Register 2 */ +#define ED_P1_MAR3 0x0b /* Multicast Address Register 3 */ +#define ED_P1_MAR4 0x0c /* Multicast Address Register 4 */ +#define ED_P1_MAR5 0x0d /* Multicast Address Register 5 */ +#define ED_P1_MAR6 0x0e /* Multicast Address Register 6 */ +#define ED_P1_MAR7 0x0f /* Multicast Address Register 7 */ + +/* + * Page 2 register offsets + */ +#define ED_P2_CR 0x00 /* Command Register */ +#define ED_P2_PSTART 0x01 /* Page Start (read) */ +#define ED_P2_CLDA0 0x01 /* Current Local DMA Addr 0 (write) */ +#define ED_P2_PSTOP 0x02 /* Page Stop (read) */ +#define ED_P2_CLDA1 0x02 /* Current Local DMA Addr 1 (write) */ +#define ED_P2_RNPP 0x03 /* Remote Next Packet Pointer */ +#define ED_P2_TPSR 0x04 /* Transmit Page Start (read) */ +#define ED_P2_LNPP 0x05 /* Local Next Packet Pointer */ +#define ED_P2_ACU 0x06 /* Address Counter Upper */ +#define ED_P2_ACL 0x07 /* Address Counter Lower */ +#define ED_P2_RCR 0x0c /* Receive Configuration Register (read) */ +#define ED_P2_TCR 0x0d /* Transmit Configuration Register (read) */ +#define ED_P2_DCR 0x0e /* Data Configuration Register (read) */ +#define ED_P2_IMR 0x0f /* Interrupt Mask Register (read) */ + +/* + * Command Register (CR) definitions + */ + +/* + * STP: SToP. Software reset command. Takes the controller offline. No + * packets will be received or transmitted. Any reception or + * transmission in progress will continue to completion before + * entering reset state. To exit this state, the STP bit must + * reset and the STA bit must be set. The software reset has + * executed only when indicated by the RST bit in the ISR being + * set. + */ +#define ED_CR_STP 0x01 + +/* + * STA: STArt. This bit is used to activate the NIC after either power-up, + * or when the NIC has been put in reset mode by software command + * or error. + */ +#define ED_CR_STA 0x02 + +/* + * TXP: Transmit Packet. This bit must be set to indicate transmission of + * a packet. TXP is internally reset either after the transmission is + * completed or aborted. This bit should be set only after the Transmit + * Byte Count and Transmit Page Start register have been programmed. + */ +#define ED_CR_TXP 0x04 + +/* + * RD0, RD1, RD2: Remote DMA Command. These three bits control the operation + * of the remote DMA channel. RD2 can be set to abort any remote DMA + * command in progress. The Remote Byte Count registers should be cleared + * when a remote DMA has been aborted. The Remote Start Addresses are not + * restored to the starting address if the remote DMA is aborted. + * + * RD2 RD1 RD0 function + * 0 0 0 not allowed + * 0 0 1 remote read + * 0 1 0 remote write + * 0 1 1 send packet + * 1 X X abort + */ +#define ED_CR_RD0 0x08 +#define ED_CR_RD1 0x10 +#define ED_CR_RD2 0x20 + +/* + * PS0, PS1: Page Select. The two bits select which register set or 'page' to + * access. + * + * PS1 PS0 page + * 0 0 0 + * 0 1 1 + * 1 0 2 + * 1 1 reserved + */ +#define ED_CR_PS0 0x40 +#define ED_CR_PS1 0x80 +/* bit encoded aliases */ +#define ED_CR_PAGE_0 0x00 /* (for consistency) */ +#define ED_CR_PAGE_1 0x40 +#define ED_CR_PAGE_2 0x80 + +/* + * Interrupt Status Register (ISR) definitions + */ + +/* + * PRX: Packet Received. Indicates packet received with no errors. + */ +#define ED_ISR_PRX 0x01 + +/* + * PTX: Packet Transmitted. Indicates packet transmitted with no errors. + */ +#define ED_ISR_PTX 0x02 + +/* + * RXE: Receive Error. Indicates that a packet was received with one or more + * the following errors: CRC error, frame alignment error, FIFO overrun, + * missed packet. + */ +#define ED_ISR_RXE 0x04 + +/* + * TXE: Transmission Error. Indicates that an attempt to transmit a packet + * resulted in one or more of the following errors: excessive + * collisions, FIFO underrun. + */ +#define ED_ISR_TXE 0x08 + +/* + * OVW: OverWrite. Indicates a receive ring-buffer overrun. Incoming network + * would exceed (has exceeded?) the boundry pointer, resulting in data + * that was previously received and not yet read from the buffer to be + * overwritten. + */ +#define ED_ISR_OVW 0x10 + +/* + * CNT: Counter Overflow. Set when the MSB of one or more of the Network Talley + * Counters has been set. + */ +#define ED_ISR_CNT 0x20 + +/* + * RDC: Remote Data Complete. Indicates that a Remote DMA operation has completed. + */ +#define ED_ISR_RDC 0x40 + +/* + * RST: Reset status. Set when the NIC enters the reset state and cleared when a + * Start Command is issued to the CR. This bit is also set when a receive + * ring-buffer overrun (OverWrite) occurs and is cleared when one or more + * packets have been removed from the ring. This is a read-only bit. + */ +#define ED_ISR_RST 0x80 + +/* + * Interrupt Mask Register (IMR) definitions + */ + +/* + * PRXE: Packet Received interrupt Enable. If set, a received packet will cause + * an interrupt. + */ +#define ED_IMR_PRXE 0x01 + +/* + * PTXE: Packet Transmit interrupt Enable. If set, an interrupt is generated when + * a packet transmission completes. + */ +#define ED_IMR_PTXE 0x02 + +/* + * RXEE: Receive Error interrupt Enable. If set, an interrupt will occur whenever a + * packet is received with an error. + */ +#define ED_IMR_RXEE 0x04 + +/* + * TXEE: Transmit Error interrupt Enable. If set, an interrupt will occur whenever + * a transmission results in an error. + */ +#define ED_IMR_TXEE 0x08 + +/* + * OVWE: OverWrite error interrupt Enable. If set, an interrupt is generated whenever + * the receive ring-buffer is overrun. i.e. when the boundry pointer is exceeded. + */ +#define ED_IMR_OVWE 0x10 + +/* + * CNTE: Counter overflow interrupt Enable. If set, an interrupt is generated whenever + * the MSB of one or more of the Network Statistics counters has been set. + */ +#define ED_IMR_CNTE 0x20 + +/* + * RDCE: Remote DMA Complete interrupt Enable. If set, an interrupt is generated + * when a remote DMA transfer has completed. + */ +#define ED_IMR_RDCE 0x40 + +/* + * bit 7 is unused/reserved + */ + +/* + * Data Configuration Register (DCR) definitions + */ + +/* + * WTS: Word Transfer Select. WTS establishes byte or word transfers for + * both remote and local DMA transfers + */ +#define ED_DCR_WTS 0x01 + +/* + * BOS: Byte Order Select. BOS sets the byte order for the host. + * Should be 0 for 80x86, and 1 for 68000 series processors + */ +#define ED_DCR_BOS 0x02 + +/* + * LAS: Long Address Select. When LAS is 1, the contents of the remote + * DMA registers RSAR0 and RSAR1 are used to provide A16-A31 + */ +#define ED_DCR_LAS 0x04 + +/* + * LS: Loopback Select. When 0, loopback mode is selected. Bits D1 and D2 + * of the TCR must also be programmed for loopback operation. + * When 1, normal operation is selected. + */ +#define ED_DCR_LS 0x08 + +/* + * AR: Auto-initialize Remote. When 0, data must be removed from ring-buffer + * under program control. When 1, remote DMA is automatically initiated + * and the boundry pointer is automatically updated + */ +#define ED_DCR_AR 0x10 + +/* + * FT0, FT1: Fifo Threshold select. + * FT1 FT0 Word-width Byte-width + * 0 0 1 word 2 bytes + * 0 1 2 words 4 bytes + * 1 0 4 words 8 bytes + * 1 1 8 words 12 bytes + * + * During transmission, the FIFO threshold indicates the number of bytes + * or words that the FIFO has filled from the local DMA before BREQ is + * asserted. The transmission threshold is 16 bytes minus the receiver + * threshold. + */ +#define ED_DCR_FT0 0x20 +#define ED_DCR_FT1 0x40 + +/* + * bit 7 (0x80) is unused/reserved + */ + +/* + * Transmit Configuration Register (TCR) definitions + */ + +/* + * CRC: Inhibit CRC. If 0, CRC will be appended by the transmitter, if 0, CRC + * is not appended by the transmitter. + */ +#define ED_TCR_CRC 0x01 + +/* + * LB0, LB1: Loopback control. These two bits set the type of loopback that is + * to be performed. + * + * LB1 LB0 mode + * 0 0 0 - normal operation (DCR_LS = 0) + * 0 1 1 - internal loopback (DCR_LS = 0) + * 1 0 2 - external loopback (DCR_LS = 1) + * 1 1 3 - external loopback (DCR_LS = 0) + */ +#define ED_TCR_LB0 0x02 +#define ED_TCR_LB1 0x04 + +/* + * ATD: Auto Transmit Disable. Clear for normal operation. When set, allows + * another station to disable the NIC's transmitter by transmitting to + * a multicast address hashing to bit 62. Reception of a multicast address + * hashing to bit 63 enables the transmitter. + */ +#define ED_TCR_ATD 0x08 + +/* + * OFST: Collision Offset enable. This bit when set modifies the backoff + * algorithm to allow prioritization of nodes. + */ +#define ED_TCR_OFST 0x10 + +/* + * bits 5, 6, and 7 are unused/reserved + */ + +/* + * Transmit Status Register (TSR) definitions + */ + +/* + * PTX: Packet Transmitted. Indicates successful transmission of packet. + */ +#define ED_TSR_PTX 0x01 + +/* + * bit 1 (0x02) is unused/reserved + */ + +/* + * COL: Transmit Collided. Indicates that the transmission collided at least + * once with another station on the network. + */ +#define ED_TSR_COL 0x04 + +/* + * ABT: Transmit aborted. Indicates that the transmission was aborted due to + * excessive collisions. + */ +#define ED_TSR_ABT 0x08 + +/* + * CRS: Carrier Sense Lost. Indicates that carrier was lost during the + * transmission of the packet. (Transmission is not aborted because + * of a loss of carrier) + */ +#define ED_TSR_CRS 0x10 + +/* + * FU: FIFO Underrun. Indicates that the NIC wasn't able to access bus/ + * transmission memory before the FIFO emptied. Transmission of the + * packet was aborted. + */ +#define ED_TSR_FU 0x20 + +/* + * CDH: CD Heartbeat. Indicates that the collision detection circuitry + * isn't working correctly during a collision heartbeat test. + */ +#define ED_TSR_CDH 0x40 + +/* + * OWC: Out of Window Collision: Indicates that a collision occurred after + * a slot time (51.2us). The transmission is rescheduled just as in + * normal collisions. + */ +#define ED_TSR_OWC 0x80 + +/* + * Receiver Configuration Register (RCR) definitions + */ + +/* + * SEP: Save Errored Packets. If 0, error packets are discarded. If set to 1, + * packets with CRC and frame errors are not discarded. + */ +#define ED_RCR_SEP 0x01 + +/* + * AR: Accept Runt packet. If 0, packet with less than 64 byte are discarded. + * If set to 1, packets with less than 64 byte are not discarded. + */ +#define ED_RCR_AR 0x02 + +/* + * AB: Accept Broadcast. If set, packets sent to the broadcast address will be + * accepted. + */ +#define ED_RCR_AB 0x04 + +/* + * AM: Accept Multicast. If set, packets sent to a multicast address are checked + * for a match in the hashing array. If clear, multicast packets are ignored. + */ +#define ED_RCR_AM 0x08 + +/* + * PRO: Promiscuous Physical. If set, all packets with a physical addresses are + * accepted. If clear, a physical destination address must match this + * station's address. Note: for full promiscuous mode, RCR_AB and RCR_AM + * must also be set. In addition, the multicast hashing array must be set + * to all 1's so that all multicast addresses are accepted. + */ +#define ED_RCR_PRO 0x10 + +/* + * MON: Monitor Mode. If set, packets will be checked for good CRC and framing, + * but are not stored in the ring-buffer. If clear, packets are stored (normal + * operation). + */ +#define ED_RCR_MON 0x20 + +/* + * bits 6 and 7 are unused/reserved. + */ + +/* + * Receiver Status Register (RSR) definitions + */ + +/* + * PRX: Packet Received without error. + */ +#define ED_RSR_PRX 0x01 + +/* + * CRC: CRC error. Indicates that a packet has a CRC error. Also set for frame + * alignment errors. + */ +#define ED_RSR_CRC 0x02 + +/* + * FAE: Frame Alignment Error. Indicates that the incoming packet did not end on + * a byte boundry and the CRC did not match at the last byte boundry. + */ +#define ED_RSR_FAE 0x04 + +/* + * FO: FIFO Overrun. Indicates that the FIFO was not serviced (during local DMA) + * causing it to overrun. Reception of the packet is aborted. + */ +#define ED_RSR_FO 0x08 + +/* + * MPA: Missed Packet. Indicates that the received packet couldn't be stored in + * the ring-buffer because of insufficient buffer space (exceeding the + * boundry pointer), or because the transfer to the ring-buffer was inhibited + * by RCR_MON - monitor mode. + */ +#define ED_RSR_MPA 0x10 + +/* + * PHY: Physical address. If 0, the packet received was sent to a physical address. + * If 1, the packet was accepted because of a multicast/broadcast address + * match. + */ +#define ED_RSR_PHY 0x20 + +/* + * DIS: Receiver Disabled. Set to indicate that the receiver has enetered monitor + * mode. Cleared when the receiver exits monitor mode. + */ +#define ED_RSR_DIS 0x40 + +/* + * DFR: Deferring. Set to indicate a 'jabber' condition. The CRS and COL inputs + * are active, and the transceiver has set the CD line as a result of the + * jabber. + */ +#define ED_RSR_DFR 0x80 + +/* + * receive ring discriptor + * + * The National Semiconductor DS8390 Network interface controller uses + * the following receive ring headers. The way this works is that the + * memory on the interface card is chopped up into 256 bytes blocks. + * A contiguous portion of those blocks are marked for receive packets + * by setting start and end block #'s in the NIC. For each packet that + * is put into the receive ring, one of these headers (4 bytes each) is + * tacked onto the front. The first byte is a copy of the receiver status + * register at the time the packet was received. + */ +struct ed_ring { + u_char rsr; /* receiver status */ + u_char next_packet; /* pointer to next packet */ + u_short count; /* bytes in packet (length + 4) */ +}; + +/* + * Common constants + */ +#define ED_PAGE_SIZE 256 /* Size of RAM pages in bytes */ +#define ED_TXBUF_SIZE 6 /* Size of TX buffer in pages */ + +/* + * Vendor types + */ +#define ED_VENDOR_WD_SMC 0x00 /* Western Digital/SMC */ +#define ED_VENDOR_3COM 0x01 /* 3Com */ +#define ED_VENDOR_NOVELL 0x02 /* Novell */ + +/* + * Compile-time config flags + */ +/* + * this sets the default for enabling/disablng the tranceiver + */ +#define ED_FLAGS_DISABLE_TRANCEIVER 0x0001 + +/* + * This forces the board to be used in 8/16bit mode even if it + * autoconfigs differently + */ +#define ED_FLAGS_FORCE_8BIT_MODE 0x0002 +#define ED_FLAGS_FORCE_16BIT_MODE 0x0004 + +/* + * This disables the use of double transmit buffers. + */ +#define ED_FLAGS_NO_MULTI_BUFFERING 0x0008 + +/* + * This forces all operations with the NIC memory to use Programmed + * I/O (i.e. not via shared memory) + */ +#define ED_FLAGS_FORCE_PIO 0x0010 + +/* + * Definitions for Western digital/SMC WD80x3 series ASIC + */ +/* + * Memory Select Register (MSR) + */ +#define ED_WD_MSR 0 + +/* next three definitions for Toshiba */ +#define ED_WD_MSR_POW 0x02 /* 0 = power save, 1 = normal (R/W) */ +#define ED_WD_MSR_BSY 0x04 /* gate array busy (R) */ +#define ED_WD_MSR_LEN 0x20 /* data bus width, 0 = 16 bits, + 1 = 8 bits (R/W) */ +#define ED_WD_MSR_ADDR 0x3f /* Memory decode bits 18-13 */ +#define ED_WD_MSR_MENB 0x40 /* Memory enable */ +#define ED_WD_MSR_RST 0x80 /* Reset board */ + +/* + * Interface Configuration Register (ICR) + */ +#define ED_WD_ICR 1 + +#define ED_WD_ICR_16BIT 0x01 /* 16-bit interface */ +#define ED_WD_ICR_OAR 0x02 /* select register. 0=BIO 1=EAR */ +#define ED_WD_ICR_IR2 0x04 /* high order bit of encoded IRQ */ +#define ED_WD_ICR_MSZ 0x08 /* memory size (0=8k 1=32k) */ +#define ED_WD_ICR_RLA 0x10 /* recall LAN address */ +#define ED_WD_ICR_RX7 0x20 /* recall all but i/o and LAN address */ +#define ED_WD_ICR_RIO 0x40 /* recall i/o address */ +#define ED_WD_ICR_STO 0x80 /* store to non-volatile memory */ +#ifdef TOSH_ETHER +#define ED_WD_ICR_MEM 0xe0 /* shared mem address A15-A13 (R/W) */ +#define ED_WD_ICR_MSZ1 0x0f /* memory size, 0x08 = 64K, 0x04 = 32K, + 0x02 = 16K, 0x01 = 8K */ + /* 64K can only be used if mem address + above 1Mb */ + /* IAR holds address A23-A16 (R/W) */ +#endif + +/* + * IO Address Register (IAR) + */ +#define ED_WD_IAR 2 + +/* + * EEROM Address Register + */ +#define ED_WD_EAR 3 + +/* + * Interrupt Request Register (IRR) + */ +#define ED_WD_IRR 4 + +#define ED_WD_IRR_0WS 0x01 /* use 0 wait-states on 8 bit bus */ +#define ED_WD_IRR_OUT1 0x02 /* WD83C584 pin 1 output */ +#define ED_WD_IRR_OUT2 0x04 /* WD83C584 pin 2 output */ +#define ED_WD_IRR_OUT3 0x08 /* WD83C584 pin 3 output */ +#define ED_WD_IRR_FLASH 0x10 /* Flash RAM is in the ROM socket */ + +/* + * The three bits of the encoded IRQ are decoded as follows: + * + * IR2 IR1 IR0 IRQ + * 0 0 0 2/9 + * 0 0 1 3 + * 0 1 0 5 + * 0 1 1 7 + * 1 0 0 10 + * 1 0 1 11 + * 1 1 0 15 + * 1 1 1 4 + */ +#define ED_WD_IRR_IR0 0x20 /* bit 0 of encoded IRQ */ +#define ED_WD_IRR_IR1 0x40 /* bit 1 of encoded IRQ */ +#define ED_WD_IRR_IEN 0x80 /* Interrupt enable */ + +/* + * LA Address Register (LAAR) + */ +#define ED_WD_LAAR 5 + +#define ED_WD_LAAR_ADDRHI 0x1f /* bits 23-19 of RAM address */ +#define ED_WD_LAAR_0WS16 0x20 /* enable 0 wait-states on 16 bit bus */ +#define ED_WD_LAAR_L16EN 0x40 /* enable 16-bit operation */ +#define ED_WD_LAAR_M16EN 0x80 /* enable 16-bit memory access */ + +/* i/o base offset to station address/card-ID PROM */ +#define ED_WD_PROM 8 + +/* + * 83C790 specific registers + */ +/* + * Hardware Support Register (HWR) ('790) + */ +#define ED_WD790_HWR 4 + +#define WD_WD790_HWR_NUKE 0x10 /* hardware reset */ +#define ED_WD790_HWR_LPRM 0x40 /* LAN PROM select */ +#define ED_WD790_HWR_SWH 0x80 /* switch register set */ + +/* + * ICR790 Interrupt Control Register for the 83C790 + */ +#define ED_WD790_ICR 6 + +#define ED_WD790_ICR_EIL 0x01 /* enable interrupts */ + +/* + * General Control Register (GCR) + * Enabled with SWH bit=1 in HWR register + */ +#define ED_WD790_GCR 0x0d + +#define ED_WD790_GCR_IR0 0x04 /* bit 0 of encoded IRQ */ +#define ED_WD790_GCR_IR1 0x08 /* bit 1 of encoded IRQ */ +#define ED_WD790_GCR_ZWSEN 0x20 /* zero wait state enable */ +#define ED_WD790_GCR_IR2 0x40 /* bit 2 of encoded IRQ */ +/* + * The three bits of the encoded IRQ are decoded as follows: + * + * IR2 IR1 IR0 IRQ + * 0 0 0 none + * 0 0 1 9 + * 0 1 0 3 + * 0 1 1 5 + * 1 0 0 7 + * 1 0 1 10 + * 1 1 0 11 + * 1 1 1 15 + */ + +/* i/o base offset to CARD ID */ +#define ED_WD_CARD_ID ED_WD_PROM+6 + +/* Board type codes in card ID */ +#define ED_TYPE_WD8003S 0x02 +#define ED_TYPE_WD8003E 0x03 +#define ED_TYPE_WD8013EBT 0x05 +#define ED_TYPE_TOSHIBA1 0x11 /* named PCETA1 */ +#define ED_TYPE_TOSHIBA2 0x12 /* named PCETA2 */ +#define ED_TYPE_TOSHIBA3 0x13 /* named PCETB */ +#define ED_TYPE_TOSHIBA4 0x14 /* named PCETC */ +#define ED_TYPE_WD8003W 0x24 +#define ED_TYPE_WD8003EB 0x25 +#define ED_TYPE_WD8013W 0x26 +#define ED_TYPE_WD8013EP 0x27 +#define ED_TYPE_WD8013WC 0x28 +#define ED_TYPE_WD8013EPC 0x29 +#define ED_TYPE_SMC8216T 0x2a +#define ED_TYPE_SMC8216C 0x2b +#define ED_TYPE_WD8013EBP 0x2c + +/* Bit definitions in card ID */ +#define ED_WD_REV_MASK 0x1f /* Revision mask */ +#define ED_WD_SOFTCONFIG 0x20 /* Soft config */ +#define ED_WD_LARGERAM 0x40 /* Large RAM */ +#define ED_MICROCHANEL 0x80 /* Microchannel bus (vs. isa) */ + +/* + * Checksum total. All 8 bytes in station address PROM will add up to this + */ +#ifdef TOSH_ETHER +#define ED_WD_ROM_CHECKSUM_TOTAL 0xA5 +#else +#define ED_WD_ROM_CHECKSUM_TOTAL 0xFF +#endif + +#define ED_WD_NIC_OFFSET 0x10 /* I/O base offset to NIC */ +#define ED_WD_ASIC_OFFSET 0 /* I/O base offset to ASIC */ +#define ED_WD_IO_PORTS 32 /* # of i/o addresses used */ + +#define ED_WD_PAGE_OFFSET 0 /* page offset for NIC access to mem */ + +/* + * Definitions for 3Com 3c503 + */ +#define ED_3COM_NIC_OFFSET 0 +#define ED_3COM_ASIC_OFFSET 0x400 /* offset to nic i/o regs */ + +/* + * XXX - The I/O address range is fragmented in the 3c503; this is the + * number of regs at iobase. + */ +#define ED_3COM_IO_PORTS 16 /* # of i/o addresses used */ + +/* tx memory starts in second bank on 8bit cards */ +#define ED_3COM_TX_PAGE_OFFSET_8BIT 0x20 + +/* tx memory starts in first bank on 16bit cards */ +#define ED_3COM_TX_PAGE_OFFSET_16BIT 0x0 + +/* ...and rx memory starts in second bank */ +#define ED_3COM_RX_PAGE_OFFSET_16BIT 0x20 + + +/* + * Page Start Register. Must match PSTART in NIC + */ +#define ED_3COM_PSTR 0 + +/* + * Page Stop Register. Must match PSTOP in NIC + */ +#define ED_3COM_PSPR 1 + +/* + * Drq Timer Register. Determines number of bytes to be transfered during + * a DMA burst. + */ +#define ED_3COM_DQTR 2 + +/* + * Base Configuration Register. Read-only register which contains the + * board-configured I/O base address of the adapter. Bit encoded. + */ +#define ED_3COM_BCFR 3 + +#define ED_3COM_BCFR_2E0 0x01 +#define ED_3COM_BCFR_2A0 0x02 +#define ED_3COM_BCFR_280 0x04 +#define ED_3COM_BCFR_250 0x08 +#define ED_3COM_BCFR_350 0x10 +#define ED_3COM_BCFR_330 0x20 +#define ED_3COM_BCFR_310 0x40 +#define ED_3COM_BCFR_300 0x80 + +/* + * EPROM Configuration Register. Read-only register which contains the + * board-configured memory base address. Bit encoded. + */ +#define ED_3COM_PCFR 4 + +#define ED_3COM_PCFR_C8000 0x10 +#define ED_3COM_PCFR_CC000 0x20 +#define ED_3COM_PCFR_D8000 0x40 +#define ED_3COM_PCFR_DC000 0x80 + +/* + * GA Configuration Register. Gate-Array Configuration Register. + */ +#define ED_3COM_GACFR 5 + +/* + * mbs2 mbs1 mbs0 start address + * 0 0 0 0x0000 + * 0 0 1 0x2000 + * 0 1 0 0x4000 + * 0 1 1 0x6000 + * + * Note that with adapters with only 8K, the setting for 0x2000 must + * always be used. + */ +#define ED_3COM_GACFR_MBS0 0x01 +#define ED_3COM_GACFR_MBS1 0x02 +#define ED_3COM_GACFR_MBS2 0x04 + +#define ED_3COM_GACFR_RSEL 0x08 /* enable shared memory */ +#define ED_3COM_GACFR_TEST 0x10 /* for GA testing */ +#define ED_3COM_GACFR_OWS 0x20 /* select 0WS access to GA */ +#define ED_3COM_GACFR_TCM 0x40 /* Mask DMA interrupts */ +#define ED_3COM_GACFR_NIM 0x80 /* Mask NIC interrupts */ + +/* + * Control Register. Miscellaneous control functions. + */ +#define ED_3COM_CR 6 + +#define ED_3COM_CR_RST 0x01 /* Reset GA and NIC */ +#define ED_3COM_CR_XSEL 0x02 /* Transceiver select. BNC=1(def) AUI=0 */ +#define ED_3COM_CR_EALO 0x04 /* window EA PROM 0-15 to I/O base */ +#define ED_3COM_CR_EAHI 0x08 /* window EA PROM 16-31 to I/O base */ +#define ED_3COM_CR_SHARE 0x10 /* select interrupt sharing option */ +#define ED_3COM_CR_DBSEL 0x20 /* Double buffer select */ +#define ED_3COM_CR_DDIR 0x40 /* DMA direction select */ +#define ED_3COM_CR_START 0x80 /* Start DMA controller */ + +/* + * Status Register. Miscellaneous status information. + */ +#define ED_3COM_STREG 7 + +#define ED_3COM_STREG_REV 0x07 /* GA revision */ +#define ED_3COM_STREG_DIP 0x08 /* DMA in progress */ +#define ED_3COM_STREG_DTC 0x10 /* DMA terminal count */ +#define ED_3COM_STREG_OFLW 0x20 /* Overflow */ +#define ED_3COM_STREG_UFLW 0x40 /* Underflow */ +#define ED_3COM_STREG_DPRDY 0x80 /* Data port ready */ + +/* + * Interrupt/DMA Configuration Register + */ +#define ED_3COM_IDCFR 8 + +#define ED_3COM_IDCFR_DRQ0 0x01 /* DMA request 1 select */ +#define ED_3COM_IDCFR_DRQ1 0x02 /* DMA request 2 select */ +#define ED_3COM_IDCFR_DRQ2 0x04 /* DMA request 3 select */ +#define ED_3COM_IDCFR_UNUSED 0x08 /* not used */ +#define ED_3COM_IDCFR_IRQ2 0x10 /* Interrupt request 2 select */ +#define ED_3COM_IDCFR_IRQ3 0x20 /* Interrupt request 3 select */ +#define ED_3COM_IDCFR_IRQ4 0x40 /* Interrupt request 4 select */ +#define ED_3COM_IDCFR_IRQ5 0x80 /* Interrupt request 5 select */ + +/* + * DMA Address Register MSB + */ +#define ED_3COM_DAMSB 9 + +/* + * DMA Address Register LSB + */ +#define ED_3COM_DALSB 0x0a + +/* + * Vector Pointer Register 2 + */ +#define ED_3COM_VPTR2 0x0b + +/* + * Vector Pointer Register 1 + */ +#define ED_3COM_VPTR1 0x0c + +/* + * Vector Pointer Register 0 + */ +#define ED_3COM_VPTR0 0x0d + +/* + * Register File Access MSB + */ +#define ED_3COM_RFMSB 0x0e + +/* + * Register File Access LSB + */ +#define ED_3COM_RFLSB 0x0f + +/* + * Definitions for Novell NE1000/2000 boards + */ + +/* + * Board type codes + */ +#define ED_TYPE_NE1000 0x01 +#define ED_TYPE_NE2000 0x02 + +/* + * Register offsets/total + */ +#define ED_NOVELL_NIC_OFFSET 0x00 +#define ED_NOVELL_ASIC_OFFSET 0x10 +#define ED_NOVELL_IO_PORTS 32 + +/* + * Remote DMA data register; for reading or writing to the NIC mem + * via programmed I/O (offset from ASIC base) + */ +#define ED_NOVELL_DATA 0x00 + +/* + * Reset register; reading from this register causes a board reset + */ +#define ED_NOVELL_RESET 0x0f diff --git a/sys/i386/isa/if_el.c b/sys/i386/isa/if_el.c new file mode 100644 index 0000000..ceaac8f --- /dev/null +++ b/sys/i386/isa/if_el.c @@ -0,0 +1,814 @@ +/* Copyright (c) 1994, Matthew E. Kimmel. Permission is hereby granted + * to use, copy, modify and distribute this software provided that both + * the copyright notice and this permission notice appear in all copies + * of the software, derivative works or modified versions, and any + * portions thereof. + * + * Questions, comments, bug reports and fixes to kimmel@cs.umass.edu. + * + * $Id: if_el.c,v 1.4 1994/08/08 13:33:12 davidg Exp $ + */ +/* Except of course for the portions of code lifted from other FreeBSD + * drivers (mainly elread, elget and el_ioctl) + */ +/* 3COM Etherlink 3C501 device driver for FreeBSD */ +/* Yeah, I know these cards suck, but you can also get them for free + * really easily... + */ +/* Bugs/possible improvements: + * - Does not currently support DMA + * - Does not currently support multicasts + */ +#include "el.h" +#if NEL > 0 +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#endif + +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif + +#if NBPFILTER > 0 +#include <net/bpf.h> +#include <net/bpfdesc.h> +#endif + +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/icu.h> +#include <i386/isa/if_elreg.h> + +#define ETHER_MIN_LEN 64 +#define ETHER_MAX_LEN 1518 + +/* For debugging convenience */ +#ifdef EL_DEBUG +#define dprintf(x) printf x +#else +#define dprintf(x) +#endif + +/* el_softc: per line info and status */ +struct el_softc { + struct arpcom arpcom; /* Ethernet common */ + u_short el_base; /* Base I/O addr */ + caddr_t bpf; /* BPF magic cookie */ + char el_pktbuf[EL_BUFSIZ]; /* Frame buffer */ +} el_softc[NEL]; + +/* Prototypes */ +int el_attach(struct isa_device *); +void el_init(int); +void elintr(int); +int el_ioctl(struct ifnet *,int,caddr_t); +int el_probe(struct isa_device *); +void el_start(struct ifnet *); +void el_reset(int,int); +void el_watchdog(int); + +static void el_stop(int); +static int el_xmit(struct el_softc *,int); +static inline void elread(struct el_softc *,caddr_t,int); +static struct mbuf *elget(caddr_t,int,int,struct ifnet *); +static inline void el_hardreset(int); + +/* isa_driver structure for autoconf */ +struct isa_driver eldriver = { + el_probe, el_attach, "el" +}; + +/* Probe routine. See if the card is there and at the right place. */ +int el_probe(struct isa_device *idev) +{ + struct el_softc *sc; + u_short base; /* Just for convenience */ + u_char station_addr[ETHER_ADDR_LEN]; + int i; + + /* Grab some info for our structure */ + sc = &el_softc[idev->id_unit]; + sc->el_base = idev->id_iobase; + base = sc->el_base; + + /* First check the base */ + if((base < 0x280) || (base > 0x3f0)) { + printf("el%d: ioaddr must be between 0x280 and 0x3f0\n", + idev->id_unit); + return(0); + } + + /* Now attempt to grab the station address from the PROM + * and see if it contains the 3com vendor code. + */ + dprintf(("Probing 3c501 at 0x%x...\n",base)); + + /* Reset the board */ + dprintf(("Resetting board...\n")); + outb(base+EL_AC,EL_AC_RESET); + DELAY(5); + outb(base+EL_AC,0); + dprintf(("Reading station address...\n")); + /* Now read the address */ + for(i=0;i<ETHER_ADDR_LEN;i++) { + outb(base+EL_GPBL,i); + station_addr[i] = inb(base+EL_EAW); + } + dprintf(("Address is %s\n",ether_sprintf(station_addr))); + + /* If the vendor code is ok, return a 1. We'll assume that + * whoever configured this system is right about the IRQ. + */ + if((station_addr[0] != 0x02) || (station_addr[1] != 0x60) + || (station_addr[2] != 0x8c)) { + dprintf(("Bad vendor code.\n")); + return(0); + } else { + dprintf(("Vendor code ok.\n")); + /* Copy the station address into the arpcom structure */ + bcopy(station_addr,sc->arpcom.ac_enaddr,ETHER_ADDR_LEN); + return(1); + } +} + +/* Attach the interface to the kernel data structures. By the time + * this is called, we know that the card exists at the given I/O address. + * We still assume that the IRQ given is correct. + */ +int el_attach(struct isa_device *idev) +{ + struct el_softc *sc; + struct ifnet *ifp; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + u_short base; + int t; + + dprintf(("Attaching el%d...\n",idev->id_unit)); + + /* Get things pointing to the right places. */ + sc = &el_softc[idev->id_unit]; + ifp = &sc->arpcom.ac_if; + base = sc->el_base; + + /* Now reset the board */ + dprintf(("Resetting board...\n")); + el_hardreset(idev->id_unit); + + /* Initialize ifnet structure */ + ifp->if_unit = idev->id_unit; + ifp->if_name = "el"; + ifp->if_mtu = ETHERMTU; + ifp->if_init = el_init; + ifp->if_output = ether_output; + ifp->if_start = el_start; + ifp->if_ioctl = el_ioctl; + ifp->if_reset = el_reset; + ifp->if_watchdog = el_watchdog; + ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS); + + /* Now we can attach the interface */ + dprintf(("Attaching interface...\n")); + if_attach(ifp); + + /* Put the station address in the ifa address list's AF_LINK + * entry, if any. + */ + ifa = ifp->if_addrlist; + while ((ifa != NULL) && (ifa->ifa_addr != NULL) && + (ifa->ifa_addr->sa_family != AF_LINK)) + ifa = ifa->ifa_next; + if((ifa != NULL) && (ifa->ifa_addr != NULL)) { + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = ETHER_ADDR_LEN; + sdl->sdl_slen = 0; + bcopy(sc->arpcom.ac_enaddr,LLADDR(sdl),ETHER_ADDR_LEN); + } + + /* Print out some information for the user */ + printf("el%d: 3c501 address %s\n",idev->id_unit, + ether_sprintf(sc->arpcom.ac_enaddr)); + + /* Finally, attach to bpf filter if it is present. */ +#if NBPFILTER > 0 + dprintf(("Attaching to BPF...\n")); + bpfattach(&sc->bpf,ifp,DLT_EN10MB,sizeof(struct ether_header)); +#endif + + dprintf(("el_attach() finished.\n")); + return(1); +} + +/* This routine resets the interface. */ +void el_reset(int unit,int uban) +{ + int s; + + dprintf(("elreset()\n")); + s = splimp(); + el_stop(unit); + el_init(unit); + splx(s); +} + +static void el_stop(int unit) +{ + struct el_softc *sc; + + sc = &el_softc[unit]; + outb(sc->el_base+EL_AC,0); +} + +/* Do a hardware reset of the 3c501. Do not call until after el_probe()! */ +static inline void el_hardreset(int unit) +{ + register struct el_softc *sc; + register int base; + register int j; + + sc = &el_softc[unit]; + base = sc->el_base; + + /* First reset the board */ + outb(base+EL_AC,EL_AC_RESET); + DELAY(5); + outb(base+EL_AC,0); + + /* Then give it back its ethernet address. Thanks to the mach + * source code for this undocumented goodie... + */ + for(j=0;j<ETHER_ADDR_LEN;j++) + outb(base+j,sc->arpcom.ac_enaddr[j]); +} + +/* Initialize interface. */ +void el_init(int unit) +{ + struct el_softc *sc; + struct ifnet *ifp; + int s; + u_short base; + + /* Set up pointers */ + sc = &el_softc[unit]; + ifp = &sc->arpcom.ac_if; + base = sc->el_base; + + /* If address not known, do nothing. */ + if(ifp->if_addrlist == (struct ifaddr *)0) + return; + + s = splimp(); + + /* First, reset the board. */ + dprintf(("Resetting board...\n")); + el_hardreset(unit); + + /* Configure rx */ + dprintf(("Configuring rx...\n")); + if(ifp->if_flags & IFF_PROMISC) + outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); + else + outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); + outb(base+EL_RBC,0); + + /* Configure TX */ + dprintf(("Configuring tx...\n")); + outb(base+EL_TXC,0); + + /* Start reception */ + dprintf(("Starting reception...\n")); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + + /* Set flags appropriately */ + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + + /* And start output. */ + el_start(ifp); + + splx(s); +} + +/* Start output on interface. Get datagrams from the queue and output + * them, giving the receiver a chance between datagrams. Call only + * from splimp or interrupt level! + */ +void el_start(struct ifnet *ifp) +{ + struct el_softc *sc; + u_short base; + struct mbuf *m, *m0; + int s, i, len, retries, done; + + /* Get things pointing in the right directions */ + sc = &el_softc[ifp->if_unit]; + base = sc->el_base; + + dprintf(("el_start()...\n")); + s = splimp(); + + /* Don't do anything if output is active */ + if(sc->arpcom.ac_if.if_flags & IFF_OACTIVE) + return; + sc->arpcom.ac_if.if_flags |= IFF_OACTIVE; + + /* The main loop. They warned me against endless loops, but + * would I listen? NOOO.... + */ + while(1) { + /* Dequeue the next datagram */ + IF_DEQUEUE(&sc->arpcom.ac_if.if_snd,m0); + + /* If there's nothing to send, return. */ + if(m0 == NULL) { + sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + splx(s); + return; + } + + /* Disable the receiver */ + outb(base+EL_AC,EL_AC_HOST); + outb(base+EL_RBC,0); + + /* Copy the datagram to the buffer. */ + len = 0; + for(m = m0; m != NULL; m = m->m_next) { + if(m->m_len == 0) + continue; + bcopy(mtod(m,caddr_t),sc->el_pktbuf+len,m->m_len); + len += m->m_len; + } + m_freem(m0); + + len = max(len,ETHER_MIN_LEN); + + /* Give the packet to the bpf, if any */ +#if NBPFILTER > 0 + if(sc->bpf) + bpf_tap(sc->bpf,sc->el_pktbuf,len); +#endif + + /* Transfer datagram to board */ + dprintf(("el: xfr pkt length=%d...\n",len)); + i = EL_BUFSIZ - len; + outb(base+EL_GPBL,(i & 0xff)); + outb(base+EL_GPBH,((i>>8)&0xff)); + outsb(base+EL_BUF,sc->el_pktbuf,len); + + /* Now transmit the datagram */ + retries=0; + done=0; + while(!done) { + if(el_xmit(sc,len)) { /* Something went wrong */ + done = -1; + break; + } + /* Check out status */ + i = inb(base+EL_TXS); + dprintf(("tx status=0x%x\n",i)); + if(!(i & EL_TXS_READY)) { + dprintf(("el: err txs=%x\n",i)); + sc->arpcom.ac_if.if_oerrors++; + if(i & (EL_TXS_COLL|EL_TXS_COLL16)) { + if((!(i & EL_TXC_DCOLL16)) && retries < 15) { + retries++; + outb(base+EL_AC,EL_AC_HOST); + } + } + else + done = 1; + } + else { + sc->arpcom.ac_if.if_opackets++; + done = 1; + } + } + if(done == -1) /* Packet not transmitted */ + continue; + + /* Now give the card a chance to receive. + * Gotta love 3c501s... + */ + (void)inb(base+EL_AS); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + splx(s); + /* Interrupt here */ + s = splimp(); + } +} + +/* This function actually attempts to transmit a datagram downloaded + * to the board. Call at splimp or interrupt, after downloading data! + * Returns 0 on success, non-0 on failure + */ +static int el_xmit(struct el_softc *sc,int len) +{ + int gpl; + int i; + + gpl = EL_BUFSIZ - len; + dprintf(("el: xmit...")); + outb((sc->el_base)+EL_GPBL,(gpl & 0xff)); + outb((sc->el_base)+EL_GPBH,((gpl>>8)&0xff)); + outb((sc->el_base)+EL_AC,EL_AC_TXFRX); + i = 20000; + while((inb((sc->el_base)+EL_AS) & EL_AS_TXBUSY) && (i>0)) + i--; + if(i == 0) { + dprintf(("tx not ready\n")); + sc->arpcom.ac_if.if_oerrors++; + return(-1); + } + dprintf(("%d cycles.\n",(20000-i))); + return(0); +} + +/* controller interrupt */ +void elintr(int unit) +{ + register struct el_softc *sc; + register base; + int stat, rxstat, len, done; + + /* Get things pointing properly */ + sc = &el_softc[unit]; + base = sc->el_base; + + dprintf(("elintr: ")); + + /* Check board status */ + stat = inb(base+EL_AS); + if(stat & EL_AS_RXBUSY) { + (void)inb(base+EL_RXC); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + return; + } + + done = 0; + while(!done) { + rxstat = inb(base+EL_RXS); + if(rxstat & EL_RXS_STALE) { + (void)inb(base+EL_RXC); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + return; + } + + /* If there's an overflow, reinit the board. */ + if(!(rxstat & EL_RXS_NOFLOW)) { + dprintf(("overflow.\n")); + el_hardreset(unit); + /* Put board back into receive mode */ + if(sc->arpcom.ac_if.if_flags & IFF_PROMISC) + outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); + else + outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); + (void)inb(base+EL_AS); + outb(base+EL_RBC,0); + (void)inb(base+EL_RXC); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + return; + } + + /* Incoming packet */ + len = inb(base+EL_RBL); + len |= inb(base+EL_RBH) << 8; + dprintf(("receive len=%d rxstat=%x ",len,rxstat)); + outb(base+EL_AC,EL_AC_HOST); + + /* If packet too short or too long, restore rx mode and return + */ + if((len <= sizeof(struct ether_header)) || (len > ETHER_MAX_LEN)) { + if(sc->arpcom.ac_if.if_flags & IFF_PROMISC) + outb(base+EL_RXC,(EL_RXC_PROMISC|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); + else + outb(base+EL_RXC,(EL_RXC_ABROAD|EL_RXC_AGF|EL_RXC_DSHORT|EL_RXC_DDRIB|EL_RXC_DOFLOW)); + (void)inb(base+EL_AS); + outb(base+EL_RBC,0); + (void)inb(base+EL_RXC); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + return; + } + + sc->arpcom.ac_if.if_ipackets++; + + /* Copy the data into our buffer */ + outb(base+EL_GPBL,0); + outb(base+EL_GPBH,0); + insb(base+EL_BUF,sc->el_pktbuf,len); + outb(base+EL_RBC,0); + outb(base+EL_AC,EL_AC_RX); + dprintf(("%s-->",ether_sprintf(sc->el_pktbuf+6))); + dprintf(("%s\n",ether_sprintf(sc->el_pktbuf))); + + /* Pass data up to upper levels */ + len -= sizeof(struct ether_header); + elread(sc,(caddr_t)(sc->el_pktbuf),len); + + /* Is there another packet? */ + stat = inb(base+EL_AS); + + /* If so, do it all again (i.e. don't set done to 1) */ + if(!(stat & EL_AS_RXBUSY)) + dprintf(("<rescan> ")); + else + done = 1; + } + + (void)inb(base+EL_RXC); + outb(base+EL_AC,(EL_AC_IRQE|EL_AC_RX)); + return; +} + +/* Pass a packet up to the higher levels. Deal with trailer protocol. */ +static inline void elread(struct el_softc *sc,caddr_t buf,int len) +{ + register struct ether_header *eh; + struct mbuf *m; + int off, resid; + + /* Deal with trailer protocol: if type is trailer type + * get true type from first 16-bit word past data. + * Remember that type was trailer by setting off. + */ + eh = (struct ether_header *)buf; + eh->ether_type = ntohs((u_short)eh->ether_type); +#define eldataaddr(eh,off,type) ((type)(((caddr_t)((eh)+1)+(off)))) + if(eh->ether_type >= ETHERTYPE_TRAIL && + eh->ether_type < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) { + off = (eh->ether_type - ETHERTYPE_TRAIL) * 512; + if(off >= ETHERMTU) + return; + eh->ether_type = ntohs(*eldataaddr(eh,off,u_short *)); + resid = ntohs(*(eldataaddr(eh,off+2,u_short *))); + if((off+resid) > len) + return; + len = off + resid; + } + else + off = 0; + + if(len <= 0) + return; + +#if NBPFILTER > 0 + /* + * Check if there's a bpf filter listening on this interface. + * If so, hand off the raw packet to bpf, which must deal with + * trailers in its own way. + */ + if(sc->bpf) { + eh->ether_type = htons((u_short)eh->ether_type); + bpf_tap(sc->bpf,buf,len+sizeof(struct ether_header)); + eh->ether_type = ntohs((u_short)eh->ether_type); + + /* + * Note that the interface cannot be in promiscuous mode if + * there are no bpf listeners. And if el are in promiscuous + * mode, el have to check if this packet is really ours. + * + * This test does not support multicasts. + */ + if((sc->arpcom.ac_if.if_flags & IFF_PROMISC) + && bcmp(eh->ether_dhost,sc->arpcom.ac_enaddr, + sizeof(eh->ether_dhost)) != 0 + && bcmp(eh->ether_dhost,etherbroadcastaddr, + sizeof(eh->ether_dhost)) != 0) + return; + } +#endif + + /* + * Pull packet off interface. Off is nonzero if packet + * has trailing header; neget will then force this header + * information to be at the front, but we still have to drop + * the type and length which are at the front of any trailer data. + */ + m = elget(buf,len,off,&sc->arpcom.ac_if); + if(m == 0) + return; + + ether_input(&sc->arpcom.ac_if,eh,m); +} + +/* + * Pull read data off a interface. + * Len is length of data, with local net header stripped. + * Off is non-zero if a trailer protocol was used, and + * gives the offset of the trailer information. + * We copy the trailer information and then all the normal + * data into mbufs. When full cluster sized units are present + * we copy into clusters. + */ +struct mbuf * +elget(buf, totlen, off0, ifp) + caddr_t buf; + int totlen, off0; + struct ifnet *ifp; +{ + struct mbuf *top, **mp, *m, *p; + int off = off0, len; + register caddr_t cp = buf; + char *epkt; + + buf += sizeof(struct ether_header); + cp = buf; + epkt = cp + totlen; + + + if (off) { + cp += off + 2 * sizeof(u_short); + totlen -= 2 * sizeof(u_short); + } + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == 0) + return (0); + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = totlen; + m->m_len = MHLEN; + top = 0; + mp = ⊤ + while (totlen > 0) { + if (top) { + MGET(m, M_DONTWAIT, MT_DATA); + if (m == 0) { + m_freem(top); + return (0); + } + m->m_len = MLEN; + } + len = min(totlen, epkt - cp); + if (len >= MINCLSIZE) { + MCLGET(m, M_DONTWAIT); + if (m->m_flags & M_EXT) + m->m_len = len = min(len, MCLBYTES); + else + len = m->m_len; + } else { + /* + * Place initial small packet/header at end of mbuf. + */ + if (len < m->m_len) { + if (top == 0 && len + max_linkhdr <= m->m_len) + m->m_data += max_linkhdr; + m->m_len = len; + } else + len = m->m_len; + } + bcopy(cp, mtod(m, caddr_t), (unsigned)len); + cp += len; + *mp = m; + mp = &m->m_next; + totlen -= len; + if (cp == epkt) + cp = buf; + } + return (top); +} + +/* + * Process an ioctl request. This code needs some work - it looks + * pretty ugly. + */ +int +el_ioctl(ifp, command, data) + register struct ifnet *ifp; + int command; + caddr_t data; +{ + register struct ifaddr *ifa = (struct ifaddr *)data; + struct el_softc *sc = &el_softc[ifp->if_unit]; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + + s = splimp(); + + switch (command) { + + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + + switch (ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + el_init(ifp->if_unit); /* before arpwhohas */ + /* + * See if another station has *our* IP address. + * i.e.: There is an address conflict! If a + * conflict exists, a message is sent to the + * console. + */ + ((struct arpcom *)ifp)->ac_ipaddr = IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); + break; +#endif +#ifdef NS + /* + * XXX - This code is probably wrong + */ + case AF_NS: + { + register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + + if (ns_nullhost(*ina)) + ina->x_host = + *(union ns_host *)(sc->arpcom.ac_enaddr); + else { + /* + * + */ + bcopy((caddr_t)ina->x_host.c_host, + (caddr_t)sc->arpcom.ac_enaddr, + sizeof(sc->arpcom.ac_enaddr)); + } + /* + * Set new address + */ + el_init(ifp->if_unit); + break; + } +#endif + default: + el_init(ifp->if_unit); + break; + } + break; + + case SIOCGIFADDR: + { + struct sockaddr *sa; + sa = (struct sockaddr *)&ifr->ifr_data; + bcopy((caddr_t)sc->arpcom.ac_enaddr, + (caddr_t) sa->sa_data, ETHER_ADDR_LEN); + } + break; + + case SIOCSIFFLAGS: + /* + * If interface is marked down and it is running, then stop it + */ + if (((ifp->if_flags & IFF_UP) == 0) && + (ifp->if_flags & IFF_RUNNING)) { + el_stop(ifp->if_unit); + ifp->if_flags &= ~IFF_RUNNING; + } else { + /* + * If interface is marked up and it is stopped, then start it + */ + if ((ifp->if_flags & IFF_UP) && + ((ifp->if_flags & IFF_RUNNING) == 0)) + el_init(ifp->if_unit); + } + + case SIOCSIFMTU: + + /* + * Set the interface MTU. + */ + if (ifr->ifr_mtu > ETHERMTU) { + error = EINVAL; + } else { + ifp->if_mtu = ifr->ifr_mtu; + } + break; + + default: + error = EINVAL; + } + (void) splx(s); + return (error); +} + +/* Device timeout routine */ +void el_watchdog(int unit) +{ + struct el_softc *sc; + + sc = &el_softc[unit]; + + log(LOG_ERR,"el%d: device timeout\n",unit); + sc->arpcom.ac_if.if_oerrors++; + el_reset(unit,0); +} +#endif diff --git a/sys/i386/isa/if_elreg.h b/sys/i386/isa/if_elreg.h new file mode 100644 index 0000000..4d7859d --- /dev/null +++ b/sys/i386/isa/if_elreg.h @@ -0,0 +1,78 @@ +/* Copyright (c) 1994, Matthew E. Kimmel. Permission is hereby granted + * to use, copy, modify and distribute this software provided that both + * the copyright notice and this permission notice appear in all copies + * of the software, derivative works or modified versions, and any + * portions thereof. + * + * $Id$ + */ +/* 3COM Etherlink 3C501 Register Definitions */ + +/* I/O Ports */ +#define EL_RXS 0x6 /* Receive status register */ +#define EL_RXC 0x6 /* Receive command register */ +#define EL_TXS 0x7 /* Transmit status register */ +#define EL_TXC 0x7 /* Transmit command register */ +#define EL_GPBL 0x8 /* GP buffer ptr low byte */ +#define EL_GPBH 0x9 /* GP buffer ptr high byte */ +#define EL_RBL 0xa /* Receive buffer ptr low byte */ +#define EL_RBC 0xa /* Receive buffer clear */ +#define EL_RBH 0xb /* Receive buffer ptr high byte */ +#define EL_EAW 0xc /* Ethernet address window */ +#define EL_AS 0xe /* Auxillary status register */ +#define EL_AC 0xe /* Auxillary command register */ +#define EL_BUF 0xf /* Data buffer */ + +/* Receive status register bits */ +#define EL_RXS_OFLOW 0x01 /* Overflow error */ +#define EL_RXS_FCS 0x02 /* FCS error */ +#define EL_RXS_DRIB 0x04 /* Dribble error */ +#define EL_RXS_SHORT 0x08 /* Short frame */ +#define EL_RXS_NOFLOW 0x10 /* No overflow */ +#define EL_RXS_GOOD 0x20 /* Received good frame */ +#define EL_RXS_STALE 0x80 /* Stale receive status */ + +/* Receive command register bits */ +#define EL_RXC_DISABLE 0x00 /* Receiver disabled */ +#define EL_RXC_DOFLOW 0x01 /* Detect overflow */ +#define EL_RXC_DFCS 0x02 /* Detect FCS errs */ +#define EL_RXC_DDRIB 0x04 /* Detect dribble errors */ +#define EL_RXC_DSHORT 0x08 /* Detect short frames */ +#define EL_RXC_DNOFLOW 0x10 /* Detect frames w/o overflow ??? */ +#define EL_RXC_AGF 0x20 /* Accept Good Frames */ +#define EL_RXC_PROMISC 0x40 /* Promiscuous mode */ +#define EL_RXC_ABROAD 0x80 /* Accept address, broadcast */ +#define EL_RXC_AMULTI 0xc0 /* Accept address, multicast */ + +/* Transmit status register bits */ +#define EL_TXS_UFLOW 0x01 /* Underflow */ +#define EL_TXS_COLL 0x02 /* Collision */ +#define EL_TXS_COLL16 0x04 /* Collision 16 */ +#define EL_TXS_READY 0x08 /* Ready for new frame */ + +/* Transmit command register bits */ +#define EL_TXC_DUFLOW 0x01 /* Detect underflow */ +#define EL_TXC_DCOLL 0x02 /* Detect collisions */ +#define EL_TXC_DCOLL16 0x04 /* Detect collision 16 */ +#define EL_TXC_DSUCCESS 0x08 /* Detect success */ + +/* Auxillary status register bits */ +#define EL_AS_RXBUSY 0x01 /* Receive busy */ +#define EL_AS_DMADONE 0x10 /* DMA finished */ +#define EL_AS_TXBUSY 0x80 /* Transmit busy */ + +/* Auxillary command register bits */ +#define EL_AC_HOST 0x00 /* System bus can access buffer */ +#define EL_AC_IRQE 0x01 /* IRQ enable */ +#define EL_AC_TXBAD 0x02 /* Transmit frames with bad FCS */ +#define EL_AC_TXFRX 0x04 /* Transmit followed by receive */ +#define EL_AC_RX 0x08 /* Receive */ +#define EL_AC_LB 0x0c /* Loopback */ +#define EL_AC_DRQ 0x20 /* DMA request */ +#define EL_AC_RIDE 0x40 /* DRQ and IRQ enabled */ +#define EL_AC_RESET 0x80 /* Reset */ + +/* Packet buffer size */ +#define EL_BUFSIZ 2048 + +#define ETHER_ADDR_LEN 6 diff --git a/sys/i386/isa/if_ep.c b/sys/i386/isa/if_ep.c new file mode 100644 index 0000000..57e17691 --- /dev/null +++ b/sys/i386/isa/if_ep.c @@ -0,0 +1,1005 @@ +/* + * Copyright (c) 1993 Herb Peyerl <hpeyerl@novatel.ca> + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * From: if_ep.c,v 1.9 1994/01/25 10:46:29 deraadt Exp $ + * $Id: if_ep.c,v 1.10 1994/05/25 08:59:10 rgrimes Exp $ + */ + +#include "ep.h" +#if NEP > 0 + +#include "bpfilter.h" + +#include <sys/param.h> +#if defined(__FreeBSD__) +#include <sys/systm.h> +#include <sys/kernel.h> +#endif +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#if defined(__NetBSD__) +#include <sys/select.h> +#endif + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#endif + +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif + +#if NBPFILTER > 0 +#include <net/bpf.h> +#include <net/bpfdesc.h> +#endif + +#include <machine/pio.h> + +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/icu.h> +#include <i386/isa/if_epreg.h> + +#define ETHER_MIN_LEN 64 +#define ETHER_MAX_LEN 1518 +#define ETHER_ADDR_LEN 6 + +/* + * Ethernet software status per interface. + */ +struct ep_softc { + struct arpcom arpcom; /* Ethernet common part */ + short ep_io_addr; /* i/o bus address */ + char ep_connectors; /* Connectors on this card. */ +#define MAX_MBS 8 /* # of mbufs we keep around */ + struct mbuf *mb[MAX_MBS]; /* spare mbuf storage. */ + int next_mb; /* Which mbuf to use next. */ + int last_mb; /* Last mbuf. */ + int tx_start_thresh; /* Current TX_start_thresh. */ + caddr_t bpf; /* BPF "magic cookie" */ + char bus32bit; /* 32bit access possible */ +} ep_softc[NEP]; + +static int epprobe __P((struct isa_device *)); +static int epattach __P((struct isa_device *)); +static int epioctl __P((struct ifnet * ifp, int, caddr_t)); + +void epinit __P((int)); +void epintr __P((int)); +void epmbuffill __P((caddr_t)); +void epmbufempty __P((struct ep_softc *)); +void epread __P((struct ep_softc *)); +void epreset __P((int)); +void epstart __P((struct ifnet *)); +void epstop __P((int)); +void epwatchdog __P((int)); + +struct isa_driver epdriver = { + epprobe, + epattach, + "ep" +}; + +static int send_ID_sequence __P((u_short)); +static u_short get_eeprom_data __P((int, int)); +static int is_eeprom_busy __P((struct isa_device *)); + +/* + * Rudimentary support for multiple cards is here but is not + * currently handled. In the future we will have to add code + * for tagging the cards for later activation. We wanna do something + * about the id_port. We're limited due to current config procedure. + * Magnum config holds promise of a fix but we'll have to wait a bit. + */ +int +epprobe(is) + struct isa_device *is; +{ + struct ep_softc *sc = &ep_softc[is->id_unit]; + u_short k; + int id_port = 0x100; /* XXX */ + + outw(BASE + EP_COMMAND, GLOBAL_RESET); + DELAY(1000); + outb(id_port, 0xc0); /* Global reset to id_port. */ + DELAY(1000); + send_ID_sequence(id_port); + DELAY(1000); + + /* + * MFG_ID should have 0x6d50. + * PROD_ID should be 0x9[0-f]50 + */ + k = get_eeprom_data(id_port, EEPROM_MFG_ID); + if (k != MFG_ID) + return (0); + k = get_eeprom_data(id_port, EEPROM_PROD_ID); + if ((k & 0xf0ff) != (PROD_ID & 0xf0ff)) + return (0); + + k = get_eeprom_data(id_port, EEPROM_ADDR_CFG); /* get addr cfg */ + k = (k & 0x1f) * 0x10 + 0x200; /* decode base addr. */ + if (k != (u_short)is->id_iobase) + return (0); + + k = get_eeprom_data(id_port, EEPROM_RESOURCE_CFG); + k >>= 12; + if (is->id_irq != (1 << ((k == 2) ? 9 : k))) + return (0); + + outb(id_port, ACTIVATE_ADAPTER_TO_CONFIG); + + return (0x10); /* 16 bytes of I/O space used. */ +} + +static int +epattach(is) + struct isa_device *is; +{ + struct ep_softc *sc = &ep_softc[is->id_unit]; + struct ifnet *ifp = &sc->arpcom.ac_if; + u_short i; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + + sc->ep_io_addr = is->id_iobase; + + printf("ep%d: ", is->id_unit); + + sc->ep_connectors = 0; + i = inw(is->id_iobase + EP_W0_CONFIG_CTRL); + if (i & IS_AUI) { + printf("aui"); + sc->ep_connectors |= AUI; + } + if (i & IS_BNC) { + if (sc->ep_connectors) + printf("/"); + printf("bnc"); + sc->ep_connectors |= BNC; + } + if (i & IS_UTP) { + if (sc->ep_connectors) + printf("/"); + printf("utp"); + sc->ep_connectors |= UTP; + } + if (!sc->ep_connectors) + printf("no connectors!"); + + /* + * Read the station address from the eeprom + */ + for (i = 0; i < 3; i++) { + u_short *p; + GO_WINDOW(0); + if (is_eeprom_busy(is)) + return(0); + outw(BASE + EP_W0_EEPROM_COMMAND, READ_EEPROM | i); + if (is_eeprom_busy(is)) + return(0); + p =(u_short *)&sc->arpcom.ac_enaddr[i*2]; + *p = htons(inw(BASE + EP_W0_EEPROM_DATA)); + GO_WINDOW(2); + outw(BASE + EP_W2_ADDR_0 + (i * 2), ntohs(*p)); + } + printf(" address %s\n", ether_sprintf(sc->arpcom.ac_enaddr)); + + ifp->if_unit = is->id_unit; + ifp->if_name = "ep"; + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS; + ifp->if_init = epinit; + ifp->if_output = ether_output; + ifp->if_start = epstart; + ifp->if_ioctl = epioctl; + ifp->if_watchdog = epwatchdog; + + if_attach(ifp); + + /* + * Fill the hardware address into ifa_addr if we find an + * AF_LINK entry. We need to do this so bpf's can get the hardware + * addr of this card. netstat likes this too! + */ + ifa = ifp->if_addrlist; + while ((ifa != 0) && (ifa->ifa_addr != 0) && + (ifa->ifa_addr->sa_family != AF_LINK)) + ifa = ifa->ifa_next; + + if ((ifa != 0) && (ifa->ifa_addr != 0)) { + sdl = (struct sockaddr_dl *) ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = ETHER_ADDR_LEN; + sdl->sdl_slen = 0; + bcopy(sc->arpcom.ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); + } +#if NBPFILTER > 0 + bpfattach(&sc->bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); +#endif + return 1; +} + + +/* + * The order in here seems important. Otherwise we may not receive + * interrupts. ?! + */ +void +epinit(unit) + int unit; +{ + register struct ep_softc *sc = &ep_softc[unit]; + register struct ifnet *ifp = &sc->arpcom.ac_if; + int s, i; + + if (ifp->if_addrlist == (struct ifaddr *) 0) + return; + + s = splimp(); + while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS) + ; + + GO_WINDOW(0); + + /* Disable the card */ + outw(BASE + EP_W0_CONFIG_CTRL, 0); + + /* Enable the card */ + outw(BASE + EP_W0_CONFIG_CTRL, ENABLE_DRQ_IRQ); + + GO_WINDOW(2); + + /* Reload the ether_addr. */ + for (i = 0; i < 6; i++) + outb(BASE + EP_W2_ADDR_0 + i, sc->arpcom.ac_enaddr[i]); + + outw(BASE + EP_COMMAND, RX_RESET); + outw(BASE + EP_COMMAND, TX_RESET); + + /* Window 1 is operating window */ + GO_WINDOW(1); + for (i = 0; i < 31; i++) + inb(BASE + EP_W1_TX_STATUS); + + /* get rid of stray intr's */ + outw(BASE + EP_COMMAND, ACK_INTR | 0xff); + + outw(BASE + EP_COMMAND, SET_RD_0_MASK | S_CARD_FAILURE | S_RX_COMPLETE | + S_TX_COMPLETE | S_TX_AVAIL); + outw(BASE + EP_COMMAND, SET_INTR_MASK | S_CARD_FAILURE | S_RX_COMPLETE | + S_TX_COMPLETE | S_TX_AVAIL); + + outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL | + FIL_GROUP | FIL_BRDCST); + + /* + * you can `ifconfig (link0|-link0) ep0' to get the following + * behaviour: + * -link0 disable AUI/UTP. enable BNC. + * link0 disable BNC. enable AUI. if the card has a UTP + * connector, that is enabled too. not sure, but it + * seems you have to be careful to not plug things + * into both AUI & UTP. + */ +#if defined(__NetBSD__) + if (!(ifp->if_flags & IFF_LINK0) && (sc->ep_connectors & BNC)) { +#else + if (!(ifp->if_flags & IFF_ALTPHYS) && (sc->ep_connectors & BNC)) { +#endif + outw(BASE + EP_COMMAND, START_TRANSCEIVER); + DELAY(1000); + } +#if defined(__NetBSD__) + if ((ifp->if_flags & IFF_LINK0) && (sc->ep_connectors & UTP)) { +#else + if ((ifp->if_flags & IFF_ALTPHYS) && (sc->ep_connectors & UTP)) { +#endif + GO_WINDOW(4); + outw(BASE + EP_W4_MEDIA_TYPE, ENABLE_UTP); + GO_WINDOW(1); + } + + outw(BASE + EP_COMMAND, RX_ENABLE); + outw(BASE + EP_COMMAND, TX_ENABLE); + + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; /* just in case */ + sc->tx_start_thresh = 20; /* probably a good starting point. */ + /* + * Store up a bunch of mbuf's for use later. (MAX_MBS). First we + * free up any that we had in case we're being called from intr or + * somewhere else. + */ + sc->last_mb = 0; + sc->next_mb = 0; + epmbuffill((caddr_t)sc, 0); + + epstart(ifp); + + splx(s); +} + +static const char padmap[] = {0, 3, 2, 1}; + +void +epstart(ifp) + struct ifnet *ifp; +{ + register struct ep_softc *sc = &ep_softc[ifp->if_unit]; + struct mbuf *m, *top; + int s, len, pad; + + s = splimp(); + if (sc->arpcom.ac_if.if_flags & IFF_OACTIVE) { + splx(s); + return; + } + +startagain: + /* Sneak a peek at the next packet */ + m = sc->arpcom.ac_if.if_snd.ifq_head; + if (m == 0) { + splx(s); + return; + } +#if 0 + len = m->m_pkthdr.len; +#else + for (len = 0, top = m; m; m = m->m_next) + len += m->m_len; +#endif + + pad = padmap[len & 3]; + + /* + * The 3c509 automatically pads short packets to minimum ethernet + * length, but we drop packets that are too large. Perhaps we should + * truncate them instead? + */ + if (len + pad > ETHER_MAX_LEN) { + /* packet is obviously too large: toss it */ + ++sc->arpcom.ac_if.if_oerrors; + IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); + m_freem(m); + goto readcheck; + } + + if (inw(BASE + EP_W1_FREE_TX) < len + pad + 4) { + /* no room in FIFO */ + outw(BASE + EP_COMMAND, SET_TX_AVAIL_THRESH | (len + pad + 4)); + sc->arpcom.ac_if.if_flags |= IFF_OACTIVE; + splx(s); + return; + } + IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); + if (m == 0) { /* not really needed */ + splx(s); + return; + } + outw(BASE + EP_COMMAND, SET_TX_START_THRESH | + (len / 4 + sc->tx_start_thresh)); + + outw(BASE + EP_W1_TX_PIO_WR_1, len); + outw(BASE + EP_W1_TX_PIO_WR_1, 0xffff); /* Second dword meaningless */ + + for (top = m; m != 0; m = m->m_next) { + if (sc->bus32bit) { + outsl(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), + m->m_len/4); + if (m->m_len & 3) + outsb(BASE + EP_W1_TX_PIO_WR_1, + mtod(m, caddr_t) + m->m_len/4, + m->m_len & 3); + } else { + outsw(BASE + EP_W1_TX_PIO_WR_1, mtod(m, caddr_t), m->m_len/2); + if (m->m_len & 1) + outb(BASE + EP_W1_TX_PIO_WR_1, + *(mtod(m, caddr_t) + m->m_len - 1)); + } + } + while (pad--) + outb(BASE + EP_W1_TX_PIO_WR_1, 0); /* Padding */ + +#if NBPFILTER > 0 + if (sc->bpf) { + u_short etype; + int off, datasize, resid; + struct ether_header *eh; + struct trailer_header { + u_short ether_type; + u_short ether_residual; + } trailer_header; + char ether_packet[ETHER_MAX_LEN]; + char *ep; + + ep = ether_packet; + + /* + * We handle trailers below: + * Copy ether header first, then residual data, + * then data. Put all this in a temporary buffer + * 'ether_packet' and send off to bpf. Since the + * system has generated this packet, we assume + * that all of the offsets in the packet are + * correct; if they're not, the system will almost + * certainly crash in m_copydata. + * We make no assumptions about how the data is + * arranged in the mbuf chain (i.e. how much + * data is in each mbuf, if mbuf clusters are + * used, etc.), which is why we use m_copydata + * to get the ether header rather than assume + * that this is located in the first mbuf. + */ + /* copy ether header */ + m_copydata(top, 0, sizeof(struct ether_header), ep); + eh = (struct ether_header *) ep; + ep += sizeof(struct ether_header); + eh->ether_type = etype = ntohs(eh->ether_type); + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER) { + datasize = ((etype - ETHERTYPE_TRAIL) << 9); + off = datasize + sizeof(struct ether_header); + + /* copy trailer_header into a data structure */ + m_copydata(top, off, sizeof(struct trailer_header), + (caddr_t)&trailer_header.ether_type); + + /* copy residual data */ + resid = trailer_header.ether_residual - + sizeof(struct trailer_header); + resid = ntohs(resid); + m_copydata(top, off + sizeof(struct trailer_header), + resid, ep); + ep += resid; + + /* copy data */ + m_copydata(top, sizeof(struct ether_header), + datasize, ep); + ep += datasize; + + /* restore original ether packet type */ + eh->ether_type = trailer_header.ether_type; + + bpf_tap(sc->bpf, ether_packet, ep - ether_packet); + } else + bpf_mtap(sc->bpf, top); + } +#endif + + m_freem(top); + ++sc->arpcom.ac_if.if_opackets; + + /* + * Is another packet coming in? We don't want to overflow the + * tiny RX fifo. + */ +readcheck: + if (inw(BASE + EP_W1_RX_STATUS) & RX_BYTES_MASK) { + splx(s); + return; + } + goto startagain; +} + +void +epintr(unit) + int unit; +{ + int status, i; + register struct ep_softc *sc = &ep_softc[unit]; + struct ifnet *ifp = &sc->arpcom.ac_if; + struct mbuf *m; + + status = 0; +checkintr: + status = inw(BASE + EP_STATUS) & + (S_TX_COMPLETE | S_TX_AVAIL | S_RX_COMPLETE | S_CARD_FAILURE); + if (status == 0) { + /* No interrupts. */ + outw(BASE + EP_COMMAND, C_INTR_LATCH); + return; + } + /* important that we do this first. */ + outw(BASE + EP_COMMAND, ACK_INTR | status); + + if (status & S_TX_AVAIL) { + status &= ~S_TX_AVAIL; + inw(BASE + EP_W1_FREE_TX); + sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + epstart(&sc->arpcom.ac_if); + } + if (status & S_RX_COMPLETE) { + status &= ~S_RX_COMPLETE; + epread(sc); + } + if (status & S_CARD_FAILURE) { + printf("ep%d: reset (status: %x)\n", unit, status); + outw(BASE + EP_COMMAND, C_INTR_LATCH); + epinit(unit); + return; + } + if (status & S_TX_COMPLETE) { + status &= ~S_TX_COMPLETE; + /* + * We need to read TX_STATUS until we get a 0 status in + * order to turn off the interrupt flag. + */ + while ((i = inb(BASE + EP_W1_TX_STATUS)) & TXS_COMPLETE) { + outw(BASE + EP_W1_TX_STATUS, 0x0); + if (i & (TXS_MAX_COLLISION | TXS_JABBER | TXS_UNDERRUN)) { + if (i & TXS_MAX_COLLISION) + ++sc->arpcom.ac_if.if_collisions; + if (i & (TXS_JABBER | TXS_UNDERRUN)) { + outw(BASE + EP_COMMAND, TX_RESET); + if (i & TXS_UNDERRUN) { + if (sc->tx_start_thresh < ETHER_MAX_LEN) { + sc->tx_start_thresh += 20; + outw(BASE + EP_COMMAND, + SET_TX_START_THRESH | + sc->tx_start_thresh); + } + } + } + outw(BASE + EP_COMMAND, TX_ENABLE); + ++sc->arpcom.ac_if.if_oerrors; + } + } + epstart(ifp); + } + goto checkintr; +} + +void +epread(sc) + register struct ep_softc *sc; +{ + struct ether_header *eh; + struct mbuf *mcur, *m, *m0, *top; + int totlen, lenthisone; + int save_totlen; + u_short etype; + int off, resid; + int count, spinwait; + int i; + + totlen = inw(BASE + EP_W1_RX_STATUS); + off = 0; + top = 0; + + if (totlen & ERR_RX) { + ++sc->arpcom.ac_if.if_ierrors; + goto out; + } + save_totlen = totlen &= RX_BYTES_MASK; /* Lower 11 bits = RX bytes. */ + + m = sc->mb[sc->next_mb]; + sc->mb[sc->next_mb] = 0; + + if (m == 0) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == 0) + goto out; + } else { + /* Convert one of our saved mbuf's */ + sc->next_mb = (sc->next_mb + 1) % MAX_MBS; + m->m_data = m->m_pktdat; + m->m_flags = M_PKTHDR; + } + + top = m0 = m; /* We assign top so we can "goto out" */ +#define EROUND ((sizeof(struct ether_header) + 3) & ~3) +#define EOFF (EROUND - sizeof(struct ether_header)) + m0->m_data += EOFF; + /* Read what should be the header. */ + insw(BASE + EP_W1_RX_PIO_RD_1, + mtod(m0, caddr_t), sizeof(struct ether_header) / 2); + m->m_len = sizeof(struct ether_header); + totlen -= sizeof(struct ether_header); + /* + * mostly deal with trailer here. (untested) + * We do this in a couple of parts. First we check for a trailer, if + * we have one we convert the mbuf back to a regular mbuf and set the offset and + * subtract sizeof(struct ether_header) from the pktlen. + * After we've read the packet off the interface (all except for the trailer + * header, we then get a header mbuf, read the trailer into it, and fix up + * the mbuf pointer chain. + */ + eh = mtod(m, struct ether_header *); + eh->ether_type = etype = ntohs((u_short) eh->ether_type); + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER) { + m->m_data = m->m_dat; /* Convert back to regular mbuf. */ + m->m_flags = 0; /* This sucks but non-trailers are the norm */ + off = (etype - ETHERTYPE_TRAIL) * 512; + if (off >= ETHERMTU) { + m_freem(m); + return; /* sanity */ + } + totlen -= sizeof(struct ether_header); /* We don't read the trailer */ + m->m_data += 2 * sizeof(u_short); /* Get rid of type & len */ + } + while (totlen > 0) { + lenthisone = min(totlen, M_TRAILINGSPACE(m)); + if (lenthisone == 0) { /* no room in this one */ + mcur = m; + m = sc->mb[sc->next_mb]; + sc->mb[sc->next_mb] = 0; + if (!m) { + MGET(m, M_DONTWAIT, MT_DATA); + if (m == 0) + goto out; + } else { + timeout(epmbuffill, (caddr_t)sc, 0); + sc->next_mb = (sc->next_mb + 1) % MAX_MBS; + } + if (totlen >= MINCLSIZE) + MCLGET(m, M_DONTWAIT); + m->m_len = 0; + mcur->m_next = m; + lenthisone = min(totlen, M_TRAILINGSPACE(m)); + } + if (sc->bus32bit) { + insl(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, + lenthisone / 4); + m->m_len += (lenthisone & ~3); + if (lenthisone & 3) + insb(BASE + EP_W1_RX_PIO_RD_1, + mtod(m, caddr_t) + m->m_len, + lenthisone & 3); + m->m_len += (lenthisone & 3); + } else { + insw(BASE + EP_W1_RX_PIO_RD_1, mtod(m, caddr_t) + m->m_len, + lenthisone / 2); + m->m_len += lenthisone; + if (lenthisone & 1) + *(mtod(m, caddr_t) + m->m_len - 1) = inb(BASE + EP_W1_RX_PIO_RD_1); + } + totlen -= lenthisone; + } + if (off) { + top = sc->mb[sc->next_mb]; + sc->mb[sc->next_mb] = 0; + if (top == 0) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (top == 0) { + top = m0; + goto out; + } + } else { + /* Convert one of our saved mbuf's */ + sc->next_mb = (sc->next_mb + 1) % MAX_MBS; + top->m_data = top->m_pktdat; + top->m_flags = M_PKTHDR; + } + insw(BASE + EP_W1_RX_PIO_RD_1, mtod(top, caddr_t), + sizeof(struct ether_header)); + top->m_next = m0; + top->m_len = sizeof(struct ether_header); + /* XXX Accomodate for type and len from beginning of trailer */ + top->m_pkthdr.len = save_totlen - (2 * sizeof(u_short)); + } else { + top = m0; + top->m_pkthdr.len = save_totlen; + } + + top->m_pkthdr.rcvif = &sc->arpcom.ac_if; + outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); + while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS) + ; + ++sc->arpcom.ac_if.if_ipackets; +#if NBPFILTER > 0 + if (sc->bpf) { + bpf_mtap(sc->bpf, top); + + /* + * Note that the interface cannot be in promiscuous mode if + * there are no BPF listeners. And if we are in promiscuous + * mode, we have to check if this packet is really ours. + */ + if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && + (eh->ether_dhost[0] & 1) == 0 && + bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, + sizeof(eh->ether_dhost)) != 0 && + bcmp(eh->ether_dhost, etherbroadcastaddr, + sizeof(eh->ether_dhost)) != 0) { + m_freem(top); + return; + } + } +#endif + m_adj(top, sizeof(struct ether_header)); + ether_input(&sc->arpcom.ac_if, eh, top); + return; + +out: outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); + while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS) + ; + if (top) + m_freem(top); + +} + + +/* + * Look familiar? + */ +static int +epioctl(ifp, cmd, data) + register struct ifnet *ifp; + int cmd; + caddr_t data; +{ + register struct ifaddr *ifa = (struct ifaddr *) data; + struct ep_softc *sc = &ep_softc[ifp->if_unit]; + struct ifreq *ifr = (struct ifreq *) data; + int s, error = 0; + + switch (cmd) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + switch (ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + epinit(ifp->if_unit); /* before arpwhohas */ + ((struct arpcom *) ifp)->ac_ipaddr = IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *) ifp, &IA_SIN(ifa)->sin_addr); + break; +#endif +#ifdef NS + case AF_NS: + { + register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + + if (ns_nullhost(*ina)) + ina->x_host = + *(union ns_host *)(sc->arpcom.ac_enaddr); + else { + ifp->if_flags &= ~IFF_RUNNING; + bcopy((caddr_t) ina->x_host.c_host, + (caddr_t)sc->arpcom.ac_enaddr, + sizeof(sc->arpcom.ac_enaddr)); + } + epinit(ifp->if_unit); + break; + } +#endif + default: + epinit(ifp->if_unit); + break; + } + break; + case SIOCSIFFLAGS: + if ((ifp->if_flags & IFF_UP) == 0 && ifp->if_flags & IFF_RUNNING) { + ifp->if_flags &= ~IFF_RUNNING; + epstop(ifp->if_unit); + epmbufempty(sc); + break; + } + if (ifp->if_flags & IFF_UP && (ifp->if_flags & IFF_RUNNING) == 0) + epinit(ifp->if_unit); + break; +#ifdef notdef + case SIOCGHWADDR: + bcopy((caddr_t) sc->sc_addr, (caddr_t) &ifr->ifr_data, + sizeof(sc->sc_addr)); + break; +#endif + case SIOCSIFMTU: + + /* + * Set the interface MTU. + */ + if (ifr->ifr_mtu > ETHERMTU) { + error = EINVAL; + } else { + ifp->if_mtu = ifr->ifr_mtu; + } + break; + + default: + error = EINVAL; + } + return (error); +} + +void +epreset(unit) + int unit; +{ + int s = splimp(); + + epstop(unit); + epinit(unit); + splx(s); +} + +void +epwatchdog(unit) + int unit; +{ + struct ep_softc *sc = &ep_softc[unit]; + + log(LOG_ERR, "ep%d: watchdog\n", unit); + ++sc->arpcom.ac_if.if_oerrors; + + epreset(unit); +} + +void +epstop(unit) + int unit; +{ + struct ep_softc *sc = &ep_softc[unit]; + + outw(BASE + EP_COMMAND, RX_DISABLE); + outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK); + while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS) + ; + outw(BASE + EP_COMMAND, TX_DISABLE); + outw(BASE + EP_COMMAND, STOP_TRANSCEIVER); + outw(BASE + EP_COMMAND, RX_RESET); + outw(BASE + EP_COMMAND, TX_RESET); + outw(BASE + EP_COMMAND, C_INTR_LATCH); + outw(BASE + EP_COMMAND, SET_RD_0_MASK); + outw(BASE + EP_COMMAND, SET_INTR_MASK); + outw(BASE + EP_COMMAND, SET_RX_FILTER); +} + + +/* + * This is adapted straight from the book. There's probably a better way. + */ +static int +send_ID_sequence(port) + u_short port; +{ + char cx, al; + + cx = 0x0ff; + al = 0x0ff; + + outb(port, 0x0); + DELAY(1000); + outb(port, 0x0); + DELAY(1000); + +loop1: cx--; + outb(port, al); + if (!(al & 0x80)) { + al = al << 1; + goto loop1; + } + al = al << 1; + al ^= 0xcf; + if (cx) + goto loop1; + + return(1); +} + + +/* + * We get eeprom data from the id_port given an offset into the + * eeprom. Basically; after the ID_sequence is sent to all of + * the cards; they enter the ID_CMD state where they will accept + * command requests. 0x80-0xbf loads the eeprom data. We then + * read the port 16 times and with every read; the cards check + * for contention (ie: if one card writes a 0 bit and another + * writes a 1 bit then the host sees a 0. At the end of the cycle; + * each card compares the data on the bus; if there is a difference + * then that card goes into ID_WAIT state again). In the meantime; + * one bit of data is returned in the AX register which is conveniently + * returned to us by inb(). Hence; we read 16 times getting one + * bit of data with each read. + */ +static u_short +get_eeprom_data(id_port, offset) + int id_port; + int offset; +{ + int i, data = 0; + outb(id_port, 0x80 + offset); + DELAY(1000); + for (i = 0; i < 16; i++) + data = (data << 1) | (inw(id_port) & 1); + return (data); +} + +static int +is_eeprom_busy(is) + struct isa_device *is; +{ + int i = 0, j; + register struct ep_softc *sc = &ep_softc[is->id_unit]; + + while (i++ < 100) { + j = inw(BASE + EP_W0_EEPROM_COMMAND); + if (j & EEPROM_BUSY) + DELAY(100); + else + break; + } + if (i >= 100) { + printf("\nep%d: eeprom failed to come ready.\n", is->id_unit); + return (1); + } + if (j & EEPROM_TST_MODE) { + printf("\nep%d: 3c509 in test mode. Erase pencil mark!\n", is->id_unit); + return (1); + } + return (0); +} + +void +epmbuffill(sp) + caddr_t sp; +{ + struct ep_softc *sc = (struct ep_softc *)sp; + int s, i; + + s = splimp(); + i = sc->last_mb; + do { + if(sc->mb[i] == NULL) + MGET(sc->mb[i], M_DONTWAIT, MT_DATA); + if(sc->mb[i] == NULL) + break; + i = (i + 1) % MAX_MBS; + } while (i != sc->next_mb); + sc->last_mb = i; + splx(s); +} + +static void +epmbufempty(sc) + struct ep_softc *sc; +{ + int s, i; + + s = splimp(); + for (i = 0; i<MAX_MBS; i++) { + if (sc->mb[i]) { + m_freem(sc->mb[i]); + sc->mb[i] = NULL; + } + } + sc->last_mb = sc->next_mb = 0; + untimeout(epmbuffill, sc); + splx(s); +} + +#endif /* NEP > 0 */ diff --git a/sys/i386/isa/if_epreg.h b/sys/i386/isa/if_epreg.h new file mode 100644 index 0000000..f0b4cd9 --- /dev/null +++ b/sys/i386/isa/if_epreg.h @@ -0,0 +1,295 @@ +/* + * Copyright (c) 1993 Herb Peyerl (hpeyerl@novatel.ca) + * 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. The name of the author may not be used to endorse or promote products + * derived from this software withough specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * $Id: if_epreg.h,v 1.1 1993/12/14 04:26:47 hpeyerl Exp $ + */ +/************************************************************************** + * * + * These define the EEPROM data structure. They are used in the probe + * function to verify the existance of the adapter after having sent + * the ID_Sequence. + * + * There are others but only the ones we use are defined here. + * + **************************************************************************/ + +#define EEPROM_NODE_ADDR_0 0x0 /* Word */ +#define EEPROM_NODE_ADDR_1 0x1 /* Word */ +#define EEPROM_NODE_ADDR_2 0x2 /* Word */ +#define EEPROM_PROD_ID 0x3 /* 0x9[0-f]50 */ +#define EEPROM_MFG_ID 0x7 /* 0x6d50 */ +#define EEPROM_ADDR_CFG 0x8 /* Base addr */ +#define EEPROM_RESOURCE_CFG 0x9 /* IRQ. Bits 12-15 */ + +/************************************************************************** + * * + * These are the registers for the 3Com 3c509 and their bit patterns when * + * applicable. They have been taken out the the "EtherLink III Parallel * + * Tasking EISA and ISA Technical Reference" "Beta Draft 10/30/92" manual * + * from 3com. * + * * + **************************************************************************/ + +#define EP_COMMAND 0x0e /* Write. BASE+0x0e is always a command reg. */ +#define EP_STATUS 0x0e /* Read. BASE+0x0e is always status reg. */ +#define EP_WINDOW 0x0f /* Read. BASE+0x0f is always window reg. */ +/* + * Window 0 registers. Setup. + */ + /* Write */ +#define EP_W0_EEPROM_DATA 0x0c +#define EP_W0_EEPROM_COMMAND 0x0a +#define EP_W0_RESOURCE_CFG 0x08 +#define EP_W0_ADDRESS_CFG 0x06 +#define EP_W0_CONFIG_CTRL 0x04 + /* Read */ +#define EP_W0_PRODUCT_ID 0x02 +#define EP_W0_MFG_ID 0x00 + +/* + * Window 1 registers. Operating Set. + */ + /* Write */ +#define EP_W1_TX_PIO_WR_2 0x02 +#define EP_W1_TX_PIO_WR_1 0x00 + /* Read */ +#define EP_W1_FREE_TX 0x0c +#define EP_W1_TX_STATUS 0x0b /* byte */ +#define EP_W1_TIMER 0x0a /* byte */ +#define EP_W1_RX_STATUS 0x08 +#define EP_W1_RX_PIO_RD_2 0x02 +#define EP_W1_RX_PIO_RD_1 0x00 + +/* + * Window 2 registers. Station Address Setup/Read + */ + /* Read/Write */ +#define EP_W2_ADDR_5 0x05 +#define EP_W2_ADDR_4 0x04 +#define EP_W2_ADDR_3 0x03 +#define EP_W2_ADDR_2 0x02 +#define EP_W2_ADDR_1 0x01 +#define EP_W2_ADDR_0 0x00 + +/* + * Window 3 registers. FIFO Management. + */ + /* Read */ +#define EP_W3_FREE_TX 0x0c +#define EP_W3_FREE_RX 0x0a + +/* + * Window 4 registers. Diagnostics. + */ + /* Read/Write */ +#define EP_W4_MEDIA_TYPE 0x0a +#define EP_W4_CTRLR_STATUS 0x08 +#define EP_W4_NET_DIAG 0x06 +#define EP_W4_FIFO_DIAG 0x04 +#define EP_W4_HOST_DIAG 0x02 +#define EP_W4_TX_DIAG 0x00 + +/* + * Window 5 Registers. Results and Internal status. + */ + /* Read */ +#define EP_W5_READ_0_MASK 0x0c +#define EP_W5_INTR_MASK 0x0a +#define EP_W5_RX_FILTER 0x08 +#define EP_W5_RX_EARLY_THRESH 0x06 +#define EP_W5_TX_AVAIL_THRESH 0x02 +#define EP_W5_TX_START_THRESH 0x00 + +/* + * Window 6 registers. Statistics. + */ + /* Read/Write */ +#define TX_TOTAL_OK 0x0c +#define RX_TOTAL_OK 0x0a +#define TX_DEFERRALS 0x08 +#define RX_FRAMES_OK 0x07 +#define TX_FRAMES_OK 0x06 +#define RX_OVERRUNS 0x05 +#define TX_COLLISIONS 0x04 +#define TX_AFTER_1_COLLISION 0x03 +#define TX_AFTER_X_COLLISIONS 0x02 +#define TX_NO_SQE 0x01 +#define TX_CD_LOST 0x00 + +/**************************************** + * + * Register definitions. + * + ****************************************/ + +/* + * Command register. All windows. + * + * 16 bit register. + * 15-11: 5-bit code for command to be executed. + * 10-0: 11-bit arg if any. For commands with no args; + * this can be set to anything. + */ +#define GLOBAL_RESET (u_short) 0x0000 /* Wait at least 1ms after issuing */ +#define WINDOW_SELECT (u_short) (0x1<<11) +#define START_TRANSCEIVER (u_short) (0x2<<11) /* Read ADDR_CFG reg to determine + whether this is needed. If so; + wait 800 uSec before using trans- + ceiver. */ +#define RX_DISABLE (u_short) (0x3<<11) /* state disabled on power-up */ +#define RX_ENABLE (u_short) (0x4<<11) +#define RX_RESET (u_short) (0x5<<11) +#define RX_DISCARD_TOP_PACK (u_short) (0x8<<11) +#define TX_ENABLE (u_short) (0x9<<11) +#define TX_DISABLE (u_short) (0xa<<11) +#define TX_RESET (u_short) (0xb<<11) +#define REQ_INTR (u_short) (0xc<<11) + /* + * The following C_* acknowledge the various interrupts. + * Some of them don't do anything. See the manual. + */ +#define ACK_INTR (u_short) (0x6800) +# define C_INTR_LATCH (u_short) (ACK_INTR|0x1) +# define C_CARD_FAILURE (u_short) (ACK_INTR|0x2) +# define C_TX_COMPLETE (u_short) (ACK_INTR|0x4) +# define C_TX_AVAIL (u_short) (ACK_INTR|0x8) +# define C_RX_COMPLETE (u_short) (ACK_INTR|0x10) +# define C_RX_EARLY (u_short) (ACK_INTR|0x20) +# define C_INT_RQD (u_short) (ACK_INTR|0x40) +# define C_UPD_STATS (u_short) (ACK_INTR|0x80) +#define SET_INTR_MASK (u_short) (0xe<<11) +#define SET_RD_0_MASK (u_short) (0xf<<11) +#define SET_RX_FILTER (u_short) (0x10<<11) +# define FIL_INDIVIDUAL (u_short) (0x1) +# define FIL_GROUP (u_short) (0x2) +# define FIL_BRDCST (u_short) (0x4) +# define FIL_ALL (u_short) (0x8) +#define SET_RX_EARLY_THRESH (u_short) (0x11<<11) +#define SET_TX_AVAIL_THRESH (u_short) (0x12<<11) +#define SET_TX_START_THRESH (u_short) (0x13<<11) +#define STATS_ENABLE (u_short) (0x15<<11) +#define STATS_DISABLE (u_short) (0x16<<11) +#define STOP_TRANSCEIVER (u_short) (0x17<<11) + +/* + * Status register. All windows. + * + * 15-13: Window number(0-7). + * 12: Command_in_progress. + * 11: reserved. + * 10: reserved. + * 9: reserved. + * 8: reserved. + * 7: Update Statistics. + * 6: Interrupt Requested. + * 5: RX Early. + * 4: RX Complete. + * 3: TX Available. + * 2: TX Complete. + * 1: Adapter Failure. + * 0: Interrupt Latch. + */ +#define S_INTR_LATCH (u_short) (0x1) +#define S_CARD_FAILURE (u_short) (0x2) +#define S_TX_COMPLETE (u_short) (0x4) +#define S_TX_AVAIL (u_short) (0x8) +#define S_RX_COMPLETE (u_short) (0x10) +#define S_RX_EARLY (u_short) (0x20) +#define S_INT_RQD (u_short) (0x40) +#define S_UPD_STATS (u_short) (0x80) +#define S_COMMAND_IN_PROGRESS (u_short) (0x1000) + +/* + * FIFO Registers. RX Status. + * + * 15: Incomplete or FIFO empty. + * 14: 1: Error in RX Packet 0: Incomplete or no error. + * 13-11: Type of error. + * 1000 = Overrun. + * 1011 = Run Packet Error. + * 1100 = Alignment Error. + * 1101 = CRC Error. + * 1001 = Oversize Packet Error (>1514 bytes) + * 0010 = Dribble Bits. + * (all other error codes, no errors.) + * + * 10-0: RX Bytes (0-1514) + */ +#define ERR_INCOMPLETE (u_short) (0x8000) +#define ERR_RX (u_short) (0x4000) +#define ERR_RX_PACKET (u_short) (0x2000) +#define ERR_OVERRUN (u_short) (0x1000) +#define ERR_RUNT (u_short) (0x1300) +#define ERR_ALIGNMENT (u_short) (0x1400) +#define ERR_CRC (u_short) (0x1500) +#define ERR_OVERSIZE (u_short) (0x1100) +#define ERR_DRIBBLE (u_short) (0x200) + +/* + * TX Status + * + * Reports the transmit status of a completed transmission. Writing this + * register pops the transmit completion stack. + * + * Window 1/Port 0x0b. + * + * 7: Complete + * 6: Interrupt on successful transmission requested. + * 5: Jabber Error (TP Only, TX Reset required. ) + * 4: Underrun (TX Reset required. ) + * 3: Maximum Collisions. + * 2: TX Status Overflow. + * 1-0: Undefined. + * + */ +#define TXS_COMPLETE 0x80 +#define TXS_INTR_REQ 0x40 +#define TXS_JABBER 0x20 +#define TXS_UNDERRUN 0x10 +#define TXS_MAX_COLLISION 0x8 +#define TXS_STATUS_OVERFLOW 0x4 + +/* + * Misc defines for various things. + */ +#define TAG_ADAPTER_0 0xd0 +#define ACTIVATE_ADAPTER_TO_CONFIG 0xff +#define ENABLE_DRQ_IRQ 0x0001 +#define MFG_ID 0x6d50 +#define PROD_ID 0x9150 +#define BASE sc->ep_io_addr +#define GO_WINDOW(x) outw(BASE+EP_COMMAND, WINDOW_SELECT|x) +#define AUI 0x1 +#define BNC 0x2 +#define UTP 0x4 +#define IS_AUI (1<<13) +#define IS_BNC (1<<12) +#define IS_UTP (1<<9) +#define EEPROM_BUSY (1<<15) +#define EEPROM_TST_MODE (1<<14) +#define READ_EEPROM (1<<7) +#define ETHER_ADDR_LEN 6 +#define ETHER_MAX 1536 +#define ENABLE_UTP 0xc0 +#define DISABLE_UTP 0x0 +#define RX_BYTES_MASK (u_short) (0x07ff) diff --git a/sys/i386/isa/if_ie.c b/sys/i386/isa/if_ie.c new file mode 100644 index 0000000..9693804 --- /dev/null +++ b/sys/i386/isa/if_ie.c @@ -0,0 +1,1953 @@ +/*- + * Copyright (c) 1992, 1993, University of Vermont and State + * Agricultural College. + * Copyright (c) 1992, 1993, Garrett A. Wollman. + * + * Portions: + * Copyright (c) 1990, 1991, William F. Jolitz + * Copyright (c) 1990, The Regents of the University of California + * + * 3Com 3C507 support: + * Copyright (c) 1993, 1994, Charles M. Hannum + * + * 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 + * Vermont and State Agricultural College and Garrett A. Wollman, + * by William F. Jolitz, by the University of California, + * Berkeley, by Larwence Berkeley Laboratory, by Charles M. Hannum, + * and their contributors. + * 4. Neither the names of the Universities nor the names of the authors + * 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 UNIVERSITY OR AUTHORS 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. + * + * $Id: if_ie.c,v 1.10 1994/08/24 22:32:43 ats Exp $ + */ + +/* + * Intel 82586 Ethernet chip + * Register, bit, and structure definitions. + * + * Written by GAW with reference to the Clarkson Packet Driver code for this + * chip written by Russ Nelson and others. + * + * BPF support code stolen directly from hpdev/if_le.c, supplied with + * tcpdump. + */ + +/* + * The i82586 is a very versatile chip, found in many implementations. + * Programming this chip is mostly the same, but certain details differ + * from card to card. This driver is written so that different cards + * can be automatically detected at run-time. Currently, only the + * AT&T EN100/StarLAN 10 series are supported. + */ + +/* +Mode of operation: + +We run the 82586 in a standard Ethernet mode. We keep NFRAMES received +frame descriptors around for the receiver to use, and NBUFFS associated +receive buffer descriptors, both in a circular list. Whenever a frame is +received, we rotate both lists as necessary. (The 586 treats both lists +as a simple queue.) We also keep a transmit command around so that packets +can be sent off quickly. + +We configure the adapter in AL-LOC = 1 mode, which means that the +Ethernet/802.3 MAC header is placed at the beginning of the receive buffer +rather than being split off into various fields in the RFD. This also +means that we must include this header in the transmit buffer as well. + +By convention, all transmit commands, and only transmit commands, shall +have the I (IE_CMD_INTR) bit set in the command. This way, when an +interrupt arrives at ieintr(), it is immediately possible to tell +what precisely caused it. ANY OTHER command-sending routines should +run at splimp(), and should post an acknowledgement to every interrupt +they generate. + +The 82586 has a 24-bit address space internally, and the adaptor's +memory is located at the top of this region. However, the value we are +given in configuration is normally the *bottom* of the adaptor RAM. So, +we must go through a few gyrations to come up with a kernel virtual address +which represents the actual beginning of the 586 address space. First, +we autosize the RAM by running through several possible sizes and trying +to initialize the adapter under the assumption that the selected size +is correct. Then, knowing the correct RAM size, we set up our pointers +in ie_softc[unit]. `iomem' represents the computed base of the 586 +address space. `iomembot' represents the actual configured base +of adapter RAM. Finally, `iosize' represents the calculated size +of 586 RAM. Then, when laying out commands, we use the interval +[iomembot, iomembot + iosize); to make 24-pointers, we subtract +iomem, and to make 16-pointers, we subtract iomem and and with 0xffff. + +*/ + +#include "ie.h" +#if NIE > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/errno.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> + +#include "bpfilter.h" + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#endif + +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif + +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/ic/i82586.h> +#include <i386/isa/if_iereg.h> +#include <i386/isa/icu.h> +#include <i386/isa/if_ie507.h> +#include <i386/isa/elink.h> + +#include <vm/vm.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#include <net/bpfdesc.h> +#endif + +#if (NBPFILTER > 0) || defined(MULTICAST) +#define FILTER +static struct mbuf *last_not_for_us; +#endif + +#ifdef DEBUG +#define IED_RINT 1 +#define IED_TINT 2 +#define IED_RNR 4 +#define IED_CNA 8 +#define IED_READFRAME 16 +int ie_debug = IED_RNR; +#endif + +#ifndef ETHERMINLEN +#define ETHERMINLEN 60 +#endif + +#define IE_BUF_LEN 1512 /* length of transmit buffer */ + +/* Forward declaration */ +struct ie_softc; + +static int ieprobe(struct isa_device *dvp); +static int ieattach(struct isa_device *dvp); +static void ieinit(int unit); +static int ieioctl(struct ifnet *ifp, int command, caddr_t data); +static void iestart(struct ifnet *ifp); +static void el_reset_586(int unit); +static void el_chan_attn(int unit); +static void sl_reset_586(int unit); +static void sl_chan_attn(int unit); +static void iereset(int unit); +static void ie_readframe(int unit, struct ie_softc *ie, int bufno); +static void ie_drop_packet_buffer(int unit, struct ie_softc *ie); +static void sl_read_ether(int unit, unsigned char addr[6]); +static void find_ie_mem_size(int unit); +static int command_and_wait(int unit, int command, void volatile *pcmd, int); +static int ierint(int unit, struct ie_softc *ie); +static int ietint(int unit, struct ie_softc *ie); +static int iernr(int unit, struct ie_softc *ie); +static void start_receiver(int unit); +static int ieget(int, struct ie_softc *, struct mbuf **, + struct ether_header *, int *); +static caddr_t setup_rfa(caddr_t ptr, struct ie_softc *ie); +static int mc_setup(int, caddr_t, volatile struct ie_sys_ctl_block *); +#ifdef MULTICAST +static void ie_mc_reset(int unit); +#endif + +#ifdef DEBUG +void print_rbd(volatile struct ie_recv_buf_desc *rbd); + +int in_ierint = 0; +int in_ietint = 0; +#endif + +/* + * This tells the autoconf code how to set us up. + */ +struct isa_driver iedriver = { + ieprobe, ieattach, "ie", +}; + +enum ie_hardware { + IE_STARLAN10, + IE_EN100, + IE_SLFIBER, + IE_3C507, + IE_UNKNOWN +}; + +const char *ie_hardware_names[] = { + "StarLAN 10", + "EN100", + "StarLAN Fiber", + "3C507", + "Unknown" +}; + +/* +sizeof(iscp) == 1+1+2+4 == 8 +sizeof(scb) == 2+2+2+2+2+2+2+2 == 16 +NFRAMES * sizeof(rfd) == NFRAMES*(2+2+2+2+6+6+2+2) == NFRAMES*24 == 384 +sizeof(xmit_cmd) == 2+2+2+2+6+2 == 18 +sizeof(transmit buffer) == 1512 +sizeof(transmit buffer desc) == 8 +----- +1946 + +NBUFFS * sizeof(rbd) == NBUFFS*(2+2+4+2+2) == NBUFFS*12 +NBUFFS * IE_RBUF_SIZE == NBUFFS*256 + +NBUFFS should be (16384 - 1946) / (256 + 12) == 14438 / 268 == 53 + +With NBUFFS == 48, this leaves us 1574 bytes for another command or +more buffers. Another transmit command would be 18+8+1512 == 1538 +---just barely fits! + +Obviously all these would have to be reduced for smaller memory sizes. +With a larger memory, it would be possible to roughly double the number of +both transmit and receive buffers. +*/ + +#define NFRAMES 16 /* number of frames to allow for receive */ +#define NBUFFS 48 /* number of buffers to allocate */ +#define IE_RBUF_SIZE 256 /* size of each buffer, MUST BE POWER OF TWO */ + +/* + * Ethernet status, per interface. + */ +struct ie_softc { + struct arpcom arpcom; + void (*ie_reset_586)(int); + void (*ie_chan_attn)(int); + enum ie_hardware hard_type; + int hard_vers; + + u_short port; + caddr_t iomem; + caddr_t iomembot; + unsigned iosize; + + int want_mcsetup; + int promisc; + volatile struct ie_int_sys_conf_ptr *iscp; + volatile struct ie_sys_ctl_block *scb; + volatile struct ie_recv_frame_desc *rframes[NFRAMES]; + volatile struct ie_recv_buf_desc *rbuffs[NBUFFS]; + volatile char *cbuffs[NBUFFS]; + int rfhead, rftail, rbhead, rbtail; + + volatile struct ie_xmit_cmd *xmit_cmds[2]; + volatile struct ie_xmit_buf *xmit_buffs[2]; + int xmit_count; + u_char *xmit_cbuffs[2]; + + struct ie_en_addr mcast_addrs[MAXMCAST + 1]; + int mcast_count; + +#if NBPFILTER > 0 + caddr_t ie_bpf; +#endif + +} ie_softc[NIE]; + +#define MK_24(base, ptr) ((caddr_t)((u_long)ptr - (u_long)base)) +#define MK_16(base, ptr) ((u_short)(u_long)MK_24(base, ptr)) + +#define PORT ie_softc[unit].port +#define MEM ie_softc[unit].iomem + +static int sl_probe(struct isa_device *); +static int el_probe(struct isa_device *); + +/* This routine written by Charles Martin Hannum. */ +int ieprobe(dvp) + struct isa_device *dvp; +{ + int ret; + + ret = sl_probe(dvp); + if(!ret) ret = el_probe(dvp); + return(ret); +} + +static int sl_probe(dvp) + struct isa_device *dvp; +{ + int unit = dvp->id_unit; + u_char c; + + ie_softc[unit].port = dvp->id_iobase; + ie_softc[unit].iomembot = dvp->id_maddr; + ie_softc[unit].iomem = 0; + + c = inb(PORT + IEATT_REVISION); + switch(SL_BOARD(c)) { + case SL10_BOARD: + ie_softc[unit].hard_type = IE_STARLAN10; + ie_softc[unit].ie_reset_586 = sl_reset_586; + ie_softc[unit].ie_chan_attn = sl_chan_attn; + break; + case EN100_BOARD: + ie_softc[unit].hard_type = IE_EN100; + ie_softc[unit].ie_reset_586 = sl_reset_586; + ie_softc[unit].ie_chan_attn = sl_chan_attn; + break; + case SLFIBER_BOARD: + ie_softc[unit].hard_type = IE_SLFIBER; + ie_softc[unit].ie_reset_586 = sl_reset_586; + ie_softc[unit].ie_chan_attn = sl_chan_attn; + break; + + /* + * Anything else is not recognized or cannot be used. + */ + default: + return 0; + } + + ie_softc[unit].hard_vers = SL_REV(c); + + /* + * Divine memory size on-board the card. Ususally 16k. + */ + find_ie_mem_size(unit); + + if(!ie_softc[unit].iosize) { + return 0; + } + + dvp->id_msize = ie_softc[unit].iosize; + + switch(ie_softc[unit].hard_type) { + case IE_EN100: + case IE_STARLAN10: + case IE_SLFIBER: + sl_read_ether(unit, ie_softc[unit].arpcom.ac_enaddr); + break; + + default: + printf("ie%d: unknown AT&T board type code %d\n", unit, + ie_softc[unit].hard_type); + return 0; + } + + return 1; +} + +/* This routine written by Charles Martin Hannum. */ +static int el_probe(dvp) + struct isa_device *dvp; +{ + struct ie_softc *sc = &ie_softc[dvp->id_unit]; + u_char c; + int i; + u_char signature[] = "*3COM*"; + int unit = dvp->id_unit; + + sc->port = dvp->id_iobase; + sc->iomembot = dvp->id_maddr; + + /* Need this for part of the probe. */ + sc->ie_reset_586 = el_reset_586; + sc->ie_chan_attn = el_chan_attn; + + /* Reset and put card in CONFIG state without changing address. */ + elink_reset(); + outb(ELINK_ID_PORT, 0x00); + elink_idseq(ELINK_507_POLY); + elink_idseq(ELINK_507_POLY); + outb(ELINK_ID_PORT, 0xff); + + c = inb(PORT + IE507_MADDR); + if(c & 0x20) { + printf("ie%d: can't map 3C507 RAM in high memory\n", unit); + return 0; + } + + /* go to RUN state */ + outb(ELINK_ID_PORT, 0x00); + elink_idseq(ELINK_507_POLY); + outb(ELINK_ID_PORT, 0x00); + + outb(PORT + IE507_CTRL, EL_CTRL_NRST); + + for (i = 0; i < 6; i++) + if (inb(PORT + i) != signature[i]) + return 0; + + c = inb(PORT + IE507_IRQ) & 0x0f; + + if (dvp->id_irq != (1 << c)) { + printf("ie%d: kernel configured irq %d doesn't match board configured irq %d\n", + unit, ffs(dvp->id_irq) - 1, c); + return 0; + } + + c = (inb(PORT + IE507_MADDR) & 0x1c) + 0xc0; + + if (kvtop(dvp->id_maddr) != ((int)c << 12)) { + printf("ie%d: kernel configured maddr %x doesn't match board configured maddr %x\n", + unit, kvtop(dvp->id_maddr),(int)c << 12); + return 0; + } + + outb(PORT + IE507_CTRL, EL_CTRL_NORMAL); + + sc->hard_type = IE_3C507; + sc->hard_vers = 0; /* 3C507 has no version number. */ + + /* + * Divine memory size on-board the card. + */ + find_ie_mem_size(dvp->id_unit); + + if (!sc->iosize) { + printf("ie%d: can't find shared memory\n", unit); + outb(PORT + IE507_CTRL, EL_CTRL_NRST); + return 0; + } + + if(!dvp->id_msize) + dvp->id_msize = sc->iosize; + else if (dvp->id_msize != sc->iosize) { + printf("ie%d: kernel configured msize %d doesn't match board configured msize %d\n", + unit, dvp->id_msize, sc->iosize); + outb(PORT + IE507_CTRL, EL_CTRL_NRST); + return 0; + } + + sl_read_ether(unit, ie_softc[unit].arpcom.ac_enaddr); + + /* Clear the interrupt latch just in case. */ + outb(PORT + IE507_ICTRL, 1); + + return 16; +} + +/* + * Taken almost exactly from Bill's if_is.c, then modified beyond recognition. + */ +int +ieattach(dvp) + struct isa_device *dvp; +{ + int unit = dvp->id_unit; + struct ie_softc *ie = &ie_softc[unit]; + struct ifnet *ifp = &ie->arpcom.ac_if; + + ifp->if_unit = unit; + ifp->if_name = iedriver.name; + ifp->if_mtu = ETHERMTU; + printf("<%s R%d> ethernet address %s\n", + ie_hardware_names[ie_softc[unit].hard_type], + ie_softc[unit].hard_vers + 1, + ether_sprintf(ie->arpcom.ac_enaddr)); + + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS; +#ifdef MULTICAST + ifp->if_flags |= IFF_MULTICAST; +#endif /* MULTICAST */ + + ifp->if_init = ieinit; + ifp->if_output = ether_output; + ifp->if_start = iestart; + ifp->if_ioctl = ieioctl; + ifp->if_reset = iereset; + ifp->if_type = IFT_ETHER; + ifp->if_addrlen = 6; + ifp->if_hdrlen = 14; + +#if NBPFILTER > 0 + bpfattach(&ie_softc[unit].ie_bpf, ifp, DLT_EN10MB, + sizeof(struct ether_header)); +#endif + + if_attach(ifp); + { + struct ifaddr *ifa = ifp->if_addrlist; + struct sockaddr_dl *sdl; + while(ifa && ifa->ifa_addr && ifa->ifa_addr->sa_family != AF_LINK) + ifa = ifa->ifa_next; + + if(!ifa || !ifa->ifa_addr) return 1; + + /* Provide our ether address to the higher layers */ + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = 6; + sdl->sdl_slen = 0; + bcopy(ie->arpcom.ac_enaddr, LLADDR(sdl), 6); + return 1; + } +} + +/* + * What to do upon receipt of an interrupt. + */ +int ieintr(unit) + int unit; +{ + register struct ie_softc *ie = &ie_softc[unit]; + register u_short status; + + status = ie->scb->ie_status; + + /* This if statement written by Charles Martin Hannum. */ + if ((status & IE_ST_WHENCE) == 0) { + /* Clear the interrupt latch on the 3C507. */ + if (ie->hard_type == IE_3C507 && + (inb(PORT + IE507_CTRL) & EL_CTRL_INTL)) + outb(PORT + IE507_ICTRL, 1); + } + +loop: + if(status & (IE_ST_RECV | IE_ST_RNR)) { +#ifdef DEBUG + in_ierint++; + if(ie_debug & IED_RINT) + printf("ie%d: rint\n", unit); +#endif + ierint(unit, ie); +#ifdef DEBUG + in_ierint--; +#endif + } + + if(status & IE_ST_DONE) { +#ifdef DEBUG + in_ietint++; + if(ie_debug & IED_TINT) + printf("ie%d: tint\n", unit); +#endif + ietint(unit, ie); +#ifdef DEBUG + in_ietint--; +#endif + } + + if(status & IE_ST_RNR) { +#ifdef DEBUG + if(ie_debug & IED_RNR) + printf("ie%d: rnr\n", unit); +#endif + iernr(unit, ie); + } + +#ifdef DEBUG + if((status & IE_ST_ALLDONE) + && (ie_debug & IED_CNA)) + printf("ie%d: cna\n", unit); +#endif + + /* Don't ack interrupts which we didn't receive */ + ie_ack(ie->scb, IE_ST_WHENCE & status, unit, ie->ie_chan_attn); + + if((status = ie->scb->ie_status) & IE_ST_WHENCE) + goto loop; + + /* This comment and if statement written by Charles Martin Hannum. */ + /* Clear the interrupt latch on the 3C507. */ + if (ie->hard_type == IE_3C507) + outb(PORT + IE507_ICTRL, 1); + + return unit; +} + +/* + * Process a received-frame interrupt. + */ +static int ierint(unit, ie) + int unit; + struct ie_softc *ie; +{ + int i, status; + static int timesthru = 1024; + + i = ie->rfhead; + while(1) { + status = ie->rframes[i]->ie_fd_status; + + if((status & IE_FD_COMPLETE) && (status & IE_FD_OK)) { + ie->arpcom.ac_if.if_ipackets++; + if(!--timesthru) { + ie->arpcom.ac_if.if_ierrors += ie->scb->ie_err_crc + ie->scb->ie_err_align + + ie->scb->ie_err_resource + ie->scb->ie_err_overrun; + ie->scb->ie_err_crc = 0; + ie->scb->ie_err_align = 0; + ie->scb->ie_err_resource = 0; + ie->scb->ie_err_overrun = 0; + timesthru = 1024; + } + ie_readframe(unit, ie, i); + } else { + if(status & IE_FD_RNR) { + if(!(ie->scb->ie_status & IE_RU_READY)) { + ie->rframes[0]->ie_fd_next = MK_16(MEM, ie->rbuffs[0]); + ie->scb->ie_recv_list = MK_16(MEM, ie->rframes[0]); + command_and_wait(unit, IE_RU_START, 0, 0); + } + } + break; + } + i = (i + 1) % NFRAMES; + } + return 0; +} + +/* + * Process a command-complete interrupt. These are only generated by + * the transmission of frames. This routine is deceptively simple, since + * most of the real work is done by iestart(). + */ +static int ietint(unit, ie) + int unit; + struct ie_softc *ie; +{ + int status; + int i; + + ie->arpcom.ac_if.if_timer = 0; + ie->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + + for(i = 0; i < ie->xmit_count; i++) { + status = ie->xmit_cmds[i]->ie_xmit_status; + + if(status & IE_XS_LATECOLL) { + printf("ie%d: late collision\n", unit); + ie->arpcom.ac_if.if_collisions++; + ie->arpcom.ac_if.if_oerrors++; + } else if(status & IE_XS_NOCARRIER) { + printf("ie%d: no carrier\n", unit); + ie->arpcom.ac_if.if_oerrors++; + } else if(status & IE_XS_LOSTCTS) { + printf("ie%d: lost CTS\n", unit); + ie->arpcom.ac_if.if_oerrors++; + } else if(status & IE_XS_UNDERRUN) { + printf("ie%d: DMA underrun\n", unit); + ie->arpcom.ac_if.if_oerrors++; + } else if(status & IE_XS_EXCMAX) { + printf("ie%d: too many collisions\n", unit); + ie->arpcom.ac_if.if_collisions += 16; + ie->arpcom.ac_if.if_oerrors++; + } else { + ie->arpcom.ac_if.if_opackets++; + ie->arpcom.ac_if.if_collisions += status & IE_XS_MAXCOLL; + } + } + ie->xmit_count = 0; + + /* + * If multicast addresses were added or deleted while we were transmitting, + * ie_mc_reset() set the want_mcsetup flag indicating that we should do it. + */ + if(ie->want_mcsetup) { + mc_setup(unit, (caddr_t)ie->xmit_cbuffs[0], ie->scb); + ie->want_mcsetup = 0; + } + + /* Wish I knew why this seems to be necessary... */ + ie->xmit_cmds[0]->ie_xmit_status |= IE_STAT_COMPL; + + iestart(&ie->arpcom.ac_if); + return 0; /* shouldn't be necessary */ +} + +/* + * Process a receiver-not-ready interrupt. I believe that we get these + * when there aren't enough buffers to go around. For now (FIXME), we + * just restart the receiver, and hope everything's ok. + */ +static int iernr(unit, ie) + int unit; + struct ie_softc *ie; +{ +#ifdef doesnt_work + setup_rfa((caddr_t)ie->rframes[0], ie); + + ie->scb->ie_recv_list = MK_16(MEM, ie_softc[unit].rframes[0]); + command_and_wait(unit, IE_RU_START, 0, 0); +#else + /* This doesn't work either, but it doesn't hang either. */ + command_and_wait(unit, IE_RU_DISABLE, 0, 0); /* just in case */ + setup_rfa((caddr_t)ie->rframes[0], ie); /* ignore cast-qual */ + + ie->scb->ie_recv_list = MK_16(MEM, ie_softc[unit].rframes[0]); + command_and_wait(unit, IE_RU_START, 0, 0); /* was ENABLE */ + +#endif + ie_ack(ie->scb, IE_ST_WHENCE, unit, ie->ie_chan_attn); + + ie->arpcom.ac_if.if_ierrors++; + return 0; +} + +#ifdef FILTER +/* + * Compare two Ether/802 addresses for equality, inlined and + * unrolled for speed. I'd love to have an inline assembler + * version of this... + */ +static inline int ether_equal(u_char *one, u_char *two) { + if(one[0] != two[0]) return 0; + if(one[1] != two[1]) return 0; + if(one[2] != two[2]) return 0; + if(one[3] != two[3]) return 0; + if(one[4] != two[4]) return 0; + if(one[5] != two[5]) return 0; + return 1; +} + +/* + * Check for a valid address. to_bpf is filled in with one of the following: + * 0 -> BPF doesn't get this packet + * 1 -> BPF does get this packet + * 2 -> BPF does get this packet, but we don't + * Return value is true if the packet is for us, and false otherwise. + * + * This routine is a mess, but it's also critical that it be as fast + * as possible. It could be made cleaner if we can assume that the + * only client which will fiddle with IFF_PROMISC is BPF. This is + * probably a good assumption, but we do not make it here. (Yet.) + */ +static inline int check_eh(struct ie_softc *ie, + struct ether_header *eh, + int *to_bpf) { + int i; + + switch(ie->promisc) { + case IFF_ALLMULTI: + /* + * Receiving all multicasts, but no unicasts except those destined for us. + */ +#if NBPFILTER > 0 + *to_bpf = (ie->ie_bpf != 0); /* BPF gets this packet if anybody cares */ +#endif + if(eh->ether_dhost[0] & 1) { + return 1; + } + if(ether_equal(eh->ether_dhost, ie->arpcom.ac_enaddr)) return 1; + return 0; + + case IFF_PROMISC: + /* + * Receiving all packets. These need to be passed on to BPF. + */ +#if NBPFILTER > 0 + *to_bpf = (ie->ie_bpf != 0); +#endif + /* If for us, accept and hand up to BPF */ + if(ether_equal(eh->ether_dhost, ie->arpcom.ac_enaddr)) return 1; + +#if NBPFILTER > 0 + if(*to_bpf) *to_bpf = 2; /* we don't need to see it */ +#endif + +#ifdef MULTICAST + /* + * Not a multicast, so BPF wants to see it but we don't. + */ + if(!(eh->ether_dhost[0] & 1)) return 1; + + /* + * If it's one of our multicast groups, accept it and pass it + * up. + */ + for(i = 0; i < ie->mcast_count; i++) { + if(ether_equal(eh->ether_dhost, (u_char *)&ie->mcast_addrs[i])) { +#if NBPFILTER > 0 + if(*to_bpf) *to_bpf = 1; +#endif + return 1; + } + } +#endif /* MULTICAST */ + return 1; + + case IFF_ALLMULTI | IFF_PROMISC: + /* + * Acting as a multicast router, and BPF running at the same time. + * Whew! (Hope this is a fast machine...) + */ +#if NBPFILTER > 0 + *to_bpf = (ie->ie_bpf != 0); +#endif + /* We want to see multicasts. */ + if(eh->ether_dhost[0] & 1) return 1; + + /* We want to see our own packets */ + if(ether_equal(eh->ether_dhost, ie->arpcom.ac_enaddr)) return 1; + + /* Anything else goes to BPF but nothing else. */ +#if NBPFILTER > 0 + if(*to_bpf) *to_bpf = 2; +#endif + return 1; + + default: + /* + * Only accept unicast packets destined for us, or multicasts + * for groups that we belong to. For now, we assume that the + * '586 will only return packets that we asked it for. This + * isn't strictly true (it uses hashing for the multicast filter), + * but it will do in this case, and we want to get out of here + * as quickly as possible. + */ +#if NBPFILTER > 0 + *to_bpf = (ie->ie_bpf != 0); +#endif + return 1; + } + return 0; +} +#endif /* FILTER */ + +/* + * We want to isolate the bits that have meaning... This assumes that + * IE_RBUF_SIZE is an even power of two. If somehow the act_len exceeds + * the size of the buffer, then we are screwed anyway. + */ +static inline int ie_buflen(struct ie_softc *ie, int head) { + return (ie->rbuffs[head]->ie_rbd_actual + & (IE_RBUF_SIZE | (IE_RBUF_SIZE - 1))); +} + +static inline int ie_packet_len(int unit, struct ie_softc *ie) { + int i; + int head = ie->rbhead; + int acc = 0; + + do { + if(!(ie->rbuffs[ie->rbhead]->ie_rbd_actual & IE_RBD_USED)) { +#ifdef DEBUG + print_rbd(ie->rbuffs[ie->rbhead]); +#endif + log(LOG_ERR, "ie%d: receive descriptors out of sync at %d\n", + unit, ie->rbhead); + iereset(unit); + return -1; + } + + i = ie->rbuffs[head]->ie_rbd_actual & IE_RBD_LAST; + + acc += ie_buflen(ie, head); + head = (head + 1) % NBUFFS; + } while(!i); + + return acc; +} + +/* + * Read data off the interface, and turn it into an mbuf chain. + * + * This code is DRAMATICALLY different from the previous version; this + * version tries to allocate the entire mbuf chain up front, given the + * length of the data available. This enables us to allocate mbuf + * clusters in many situations where before we would have had a long + * chain of partially-full mbufs. This should help to speed up the + * operation considerably. (Provided that it works, of course.) + */ +static inline int ieget(unit, ie, mp, ehp, to_bpf) + int unit; + struct ie_softc *ie; + struct mbuf **mp; + struct ether_header *ehp; + int *to_bpf; +{ + struct mbuf *m, *top, **mymp; + int i; + int offset; + int totlen, resid; + int thismboff; + int head; + + totlen = ie_packet_len(unit, ie); + if(totlen <= 0) return -1; + + i = ie->rbhead; + + /* + * Snarf the Ethernet header. + */ + bcopy((caddr_t)ie->cbuffs[i], (caddr_t)ehp, sizeof *ehp); + /* ignore cast-qual warning here */ + + /* + * As quickly as possible, check if this packet is for us. + * If not, don't waste a single cycle copying the rest of the + * packet in. + * This is only a consideration when FILTER is defined; i.e., when + * we are either running BPF or doing multicasting. + */ +#ifdef FILTER + if(!check_eh(ie, ehp, to_bpf)) { + ie_drop_packet_buffer(unit, ie); + ie->arpcom.ac_if.if_ierrors--; /* just this case, it's not an error */ + return -1; + } +#endif + totlen -= (offset = sizeof *ehp); + + MGETHDR(*mp, M_DONTWAIT, MT_DATA); + if(!*mp) { + ie_drop_packet_buffer(unit, ie); + return -1; + } + + m = *mp; + m->m_pkthdr.rcvif = &ie->arpcom.ac_if; + m->m_len = MHLEN; + resid = m->m_pkthdr.len = totlen; + top = 0; + mymp = ⊤ + + /* + * This loop goes through and allocates mbufs for all the data we will + * be copying in. It does not actually do the copying yet. + */ + do { /* while(resid > 0) */ + /* + * Try to allocate an mbuf to hold the data that we have. If we + * already allocated one, just get another one and stick it on the + * end (eventually). If we don't already have one, try to allocate + * an mbuf cluster big enough to hold the whole packet, if we think it's + * reasonable, or a single mbuf which may or may not be big enough. + * Got that? + */ + if(top) { + MGET(m, M_DONTWAIT, MT_DATA); + if(!m) { + m_freem(top); + ie_drop_packet_buffer(unit, ie); + return -1; + } + m->m_len = MLEN; + } + + if(resid >= MINCLSIZE) { + MCLGET(m, M_DONTWAIT); + if(m->m_flags & M_EXT) + m->m_len = min(resid, MCLBYTES); + } else { + if(resid < m->m_len) { + if(!top && resid + max_linkhdr <= m->m_len) + m->m_data += max_linkhdr; + m->m_len = resid; + } + } + resid -= m->m_len; + *mymp = m; + mymp = &m->m_next; + } while(resid > 0); + + resid = totlen; + m = top; + thismboff = 0; + head = ie->rbhead; + + /* + * Now we take the mbuf chain (hopefully only one mbuf most of the + * time) and stuff the data into it. There are no possible failures + * at or after this point. + */ + while(resid > 0) { /* while there's stuff left */ + int thislen = ie_buflen(ie, head) - offset; + + /* + * If too much data for the current mbuf, then fill the current one + * up, go to the next one, and try again. + */ + if(thislen > m->m_len - thismboff) { + int newlen = m->m_len - thismboff; + bcopy((caddr_t)(ie->cbuffs[head] + offset), + mtod(m, caddr_t) + thismboff, (unsigned)newlen); + /* ignore cast-qual warning */ + m = m->m_next; + thismboff = 0; /* new mbuf, so no offset */ + offset += newlen; /* we are now this far into the packet */ + resid -= newlen; /* so there is this much left to get */ + continue; + } + + /* + * If there is more than enough space in the mbuf to hold the + * contents of this buffer, copy everything in, advance pointers, + * and so on. + */ + if(thislen < m->m_len - thismboff) { + bcopy((caddr_t)(ie->cbuffs[head] + offset), /* ignore warning */ + mtod(m, caddr_t) + thismboff, (unsigned)thislen); + thismboff += thislen; /* we are this far into the mbuf */ + resid -= thislen; /* and this much is left */ + goto nextbuf; + } + + /* + * Otherwise, there is exactly enough space to put this buffer's + * contents into the current mbuf. Do the combination of the above + * actions. + */ + bcopy((caddr_t)(ie->cbuffs[head] + offset), /* ignore warning */ + mtod(m, caddr_t) + thismboff, (unsigned)thislen); + m = m->m_next; + thismboff = 0; /* new mbuf, start at the beginning */ + resid -= thislen; /* and we are this far through */ + + /* + * Advance all the pointers. We can get here from either of the + * last two cases, but never the first. + */ +nextbuf: + offset = 0; + ie->rbuffs[head]->ie_rbd_actual = 0; + ie->rbuffs[head]->ie_rbd_length |= IE_RBD_LAST; + ie->rbhead = head = (head + 1) % NBUFFS; + ie->rbuffs[ie->rbtail]->ie_rbd_length &= ~IE_RBD_LAST; + ie->rbtail = (ie->rbtail + 1) % NBUFFS; + } + + /* + * Unless something changed strangely while we were doing the copy, + * we have now copied everything in from the shared memory. + * This means that we are done. + */ + return 0; +} + +/* + * Read frame NUM from unit UNIT (pre-cached as IE). + * + * This routine reads the RFD at NUM, and copies in the buffers from + * the list of RBD, then rotates the RBD and RFD lists so that the receiver + * doesn't start complaining. Trailers are DROPPED---there's no point + * in wasting time on confusing code to deal with them. Hopefully, + * this machine will never ARP for trailers anyway. + */ +static void ie_readframe(unit, ie, num) + int unit; + struct ie_softc *ie; + int num; /* frame number to read */ +{ + struct ie_recv_frame_desc rfd; + struct mbuf *m = 0; + struct ether_header eh; +#if NBPFILTER > 0 + int bpf_gets_it = 0; +#endif + + bcopy((caddr_t)(ie->rframes[num]), &rfd, sizeof(struct ie_recv_frame_desc)); + + /* Immediately advance the RFD list, since we we have copied ours now. */ + ie->rframes[num]->ie_fd_status = 0; + ie->rframes[num]->ie_fd_last |= IE_FD_LAST; + ie->rframes[ie->rftail]->ie_fd_last &= ~IE_FD_LAST; + ie->rftail = (ie->rftail + 1) % NFRAMES; + ie->rfhead = (ie->rfhead + 1) % NFRAMES; + + if(rfd.ie_fd_status & IE_FD_OK) { + if( +#if NBPFILTER > 0 + ieget(unit, ie, &m, &eh, &bpf_gets_it) +#else + ieget(unit, ie, &m, &eh, (int *)0) +#endif + ) { + ie->arpcom.ac_if.if_ierrors++; /* this counts as an error */ + return; + } + } + +#ifdef DEBUG + if(ie_debug & IED_READFRAME) { + printf("ie%d: frame from ether %s type %x\n", unit, + ether_sprintf(eh.ether_shost), (unsigned)eh.ether_type); + } + if(ntohs(eh.ether_type) > ETHERTYPE_TRAIL + && ntohs(eh.ether_type) < (ETHERTYPE_TRAIL + ETHERTYPE_NTRAILER)) + printf("received trailer!\n"); +#endif + + if(!m) return; + +#ifdef FILTER + if(last_not_for_us) { + m_freem(last_not_for_us); + last_not_for_us = 0; + } + +#if NBPFILTER > 0 + /* + * Check for a BPF filter; if so, hand it up. + * Note that we have to stick an extra mbuf up front, because + * bpf_mtap expects to have the ether header at the front. + * It doesn't matter that this results in an ill-formatted mbuf chain, + * since BPF just looks at the data. (It doesn't try to free the mbuf, + * tho' it will make a copy for tcpdump.) + */ + if(bpf_gets_it) { + struct mbuf m0; + m0.m_len = sizeof eh; + m0.m_data = (caddr_t)&eh; + m0.m_next = m; + + /* Pass it up */ + bpf_mtap(ie->ie_bpf, &m0); + } + /* + * A signal passed up from the filtering code indicating that the + * packet is intended for BPF but not for the protocol machinery. + * We can save a few cycles by not handing it off to them. + */ + if(bpf_gets_it == 2) { + last_not_for_us = m; + return; + } +#endif /* NBPFILTER > 0 */ + /* + * In here there used to be code to check destination addresses upon + * receipt of a packet. We have deleted that code, and replaced it + * with code to check the address much earlier in the cycle, before + * copying the data in; this saves us valuable cycles when operating + * as a multicast router or when using BPF. + */ +#endif /* FILTER */ + + eh.ether_type = ntohs(eh.ether_type); + + /* + * Finally pass this packet up to higher layers. + */ + ether_input(&ie->arpcom.ac_if, &eh, m); +} + +static void ie_drop_packet_buffer(int unit, struct ie_softc *ie) { + int i; + + do { + /* + * This means we are somehow out of sync. So, we reset the + * adapter. + */ + if(!(ie->rbuffs[ie->rbhead]->ie_rbd_actual & IE_RBD_USED)) { +#ifdef DEBUG + print_rbd(ie->rbuffs[ie->rbhead]); +#endif + log(LOG_ERR, "ie%d: receive descriptors out of sync at %d\n", + unit, ie->rbhead); + iereset(unit); + return; + } + + i = ie->rbuffs[ie->rbhead]->ie_rbd_actual & IE_RBD_LAST; + + ie->rbuffs[ie->rbhead]->ie_rbd_length |= IE_RBD_LAST; + ie->rbuffs[ie->rbhead]->ie_rbd_actual = 0; + ie->rbhead = (ie->rbhead + 1) % NBUFFS; + ie->rbuffs[ie->rbtail]->ie_rbd_length &= ~IE_RBD_LAST; + ie->rbtail = (ie->rbtail + 1) % NBUFFS; + } while(!i); +} + + +/* + * Start transmission on an interface. + */ +static void +iestart(ifp) + struct ifnet *ifp; +{ + struct ie_softc *ie = &ie_softc[ifp->if_unit]; + struct mbuf *m0, *m; + unsigned char *buffer; + u_short len; + /* This is not really volatile, in this routine, but it makes gcc happy. */ + volatile u_short *bptr = &ie->scb->ie_command_list; + + if(!(ifp->if_flags & IFF_RUNNING)) + return; + if(ifp->if_flags & IFF_OACTIVE) + return; + + do { + IF_DEQUEUE(&ie->arpcom.ac_if.if_snd, m); + if(!m) + break; + + buffer = ie->xmit_cbuffs[ie->xmit_count]; + len = 0; + + for(m0 = m; m && len < IE_BUF_LEN; m = m->m_next) { + bcopy(mtod(m, caddr_t), buffer, m->m_len); + buffer += m->m_len; + len += m->m_len; + } + + m_freem(m0); + len = max(len, ETHERMINLEN); + +#if NBPFILTER > 0 + /* + * See if bpf is listening on this interface, let it see the packet + * before we commit it to the wire. + */ + if(ie->ie_bpf) + bpf_tap(ie->ie_bpf, ie->xmit_cbuffs[ie->xmit_count], len); +#endif + + ie->xmit_buffs[ie->xmit_count]->ie_xmit_flags = IE_XMIT_LAST | len; + ie->xmit_buffs[ie->xmit_count]->ie_xmit_next = 0xffff; + ie->xmit_buffs[ie->xmit_count]->ie_xmit_buf = + MK_24(ie->iomem, ie->xmit_cbuffs[ie->xmit_count]); + + ie->xmit_cmds[ie->xmit_count]->com.ie_cmd_cmd = IE_CMD_XMIT; + ie->xmit_cmds[ie->xmit_count]->ie_xmit_status = 0; + ie->xmit_cmds[ie->xmit_count]->ie_xmit_desc = + MK_16(ie->iomem, ie->xmit_buffs[ie->xmit_count]); + + *bptr = MK_16(ie->iomem, ie->xmit_cmds[ie->xmit_count]); + bptr = &ie->xmit_cmds[ie->xmit_count]->com.ie_cmd_link; + ie->xmit_count++; + } while(ie->xmit_count < 2); + + /* + * If we queued up anything for transmission, send it. + */ + if(ie->xmit_count) { + ie->xmit_cmds[ie->xmit_count - 1]->com.ie_cmd_cmd |= + IE_CMD_LAST | IE_CMD_INTR; + + /* + * By passing the command pointer as a null, we tell + * command_and_wait() to pretend that this isn't an action + * command. I wish I understood what was happening here. + */ + command_and_wait(ifp->if_unit, IE_CU_START, 0, 0); + ifp->if_flags |= IFF_OACTIVE; + } + + return; +} + +/* + * Check to see if there's an 82586 out there. + */ +int check_ie_present(unit, where, size) + int unit; + caddr_t where; + unsigned size; +{ + volatile struct ie_sys_conf_ptr *scp; + volatile struct ie_int_sys_conf_ptr *iscp; + volatile struct ie_sys_ctl_block *scb; + u_long realbase; + int s; + + s = splimp(); + + realbase = (u_long)where + size - (1 << 24); + + scp = (volatile struct ie_sys_conf_ptr *)(realbase + IE_SCP_ADDR); + bzero((char *)scp, sizeof *scp); /* ignore cast-qual */ + + /* + * First we put the ISCP at the bottom of memory; this tests to make + * sure that our idea of the size of memory is the same as the controller's. + * This is NOT where the ISCP will be in normal operation. + */ + iscp = (volatile struct ie_int_sys_conf_ptr *)where; + bzero((char *)iscp, sizeof *iscp); /* ignore cast-qual */ + + scb = (volatile struct ie_sys_ctl_block *)where; + bzero((char *)scb, sizeof *scb); /* ignore cast-qual */ + + scp->ie_bus_use = 0; /* 16-bit */ + scp->ie_iscp_ptr = (caddr_t)((volatile caddr_t)iscp - /* ignore cast-qual */ + (volatile caddr_t)realbase); + + iscp->ie_busy = 1; + iscp->ie_scb_offset = MK_16(realbase, scb) + 256; + + (*ie_softc[unit].ie_reset_586)(unit); + (*ie_softc[unit].ie_chan_attn)(unit); + + DELAY(100); /* wait a while... */ + + if(iscp->ie_busy) { + splx(s); + return 0; + } + + /* + * Now relocate the ISCP to its real home, and reset the controller + * again. + */ + iscp = (void *)Align((caddr_t)(realbase + IE_SCP_ADDR - + sizeof(struct ie_int_sys_conf_ptr))); + bzero((char *)iscp, sizeof *iscp); /* ignore cast-qual */ + + scp->ie_iscp_ptr = (caddr_t)((caddr_t)iscp - (caddr_t)realbase); + /* ignore cast-qual */ + + iscp->ie_busy = 1; + iscp->ie_scb_offset = MK_16(realbase, scb); + + (*ie_softc[unit].ie_reset_586)(unit); + (*ie_softc[unit].ie_chan_attn)(unit); + + DELAY(100); + + if(iscp->ie_busy) { + splx(s); + return 0; + } + + ie_softc[unit].iosize = size; + ie_softc[unit].iomem = (caddr_t)realbase; + + ie_softc[unit].iscp = iscp; + ie_softc[unit].scb = scb; + + /* + * Acknowledge any interrupts we may have caused... + */ + ie_ack(scb, IE_ST_WHENCE, unit, ie_softc[unit].ie_chan_attn); + splx(s); + + return 1; +} + +/* + * Divine the memory size of ie board UNIT. + * Better hope there's nothing important hiding just below the ie card... + */ +static void find_ie_mem_size(unit) + int unit; +{ + unsigned size; + + ie_softc[unit].iosize = 0; + + for(size = 65536; size >= 16384; size -= 16384) { + if(check_ie_present(unit, ie_softc[unit].iomembot, size)) { + return; + } + } + + return; +} + +void el_reset_586(unit) + int unit; +{ + outb(PORT + IE507_CTRL, EL_CTRL_RESET); + DELAY(100); + outb(PORT + IE507_CTRL, EL_CTRL_NORMAL); + DELAY(100); +} + +void sl_reset_586(unit) + int unit; +{ + outb(PORT + IEATT_RESET, 0); +} + +void el_chan_attn(unit) + int unit; +{ + outb(PORT + IE507_ATTN, 1); +} + +void sl_chan_attn(unit) + int unit; +{ + outb(PORT + IEATT_ATTN, 0); +} + +void sl_read_ether(unit, addr) + int unit; + unsigned char addr[6]; +{ + int i; + + for(i = 0; i < 6; i++) + addr[i] = inb(PORT + i); +} + + +static void +iereset(unit) + int unit; +{ + int s = splimp(); + + if(unit >= NIE) { + splx(s); + return; + } + + printf("ie%d: reset\n", unit); + ie_softc[unit].arpcom.ac_if.if_flags &= ~IFF_UP; + ieioctl(&ie_softc[unit].arpcom.ac_if, SIOCSIFFLAGS, 0); + + /* + * Stop i82586 dead in its tracks. + */ + if(command_and_wait(unit, IE_RU_ABORT | IE_CU_ABORT, 0, 0)) + printf("ie%d: abort commands timed out\n", unit); + + if(command_and_wait(unit, IE_RU_DISABLE | IE_CU_STOP, 0, 0)) + printf("ie%d: disable commands timed out\n", unit); + +#ifdef notdef + if(!check_ie_present(unit, ie_softc[unit].iomembot, ie_softc[unit].iosize)) + panic("ie disappeared!\n"); +#endif + + ie_softc[unit].arpcom.ac_if.if_flags |= IFF_UP; + ieioctl(&ie_softc[unit].arpcom.ac_if, SIOCSIFFLAGS, 0); + + splx(s); + return; +} + +/* + * This is called if we time out. + */ +static void +chan_attn_timeout(rock) + caddr_t rock; +{ + *(int *)rock = 1; +} + +/* + * Send a command to the controller and wait for it to either + * complete or be accepted, depending on the command. If the + * command pointer is null, then pretend that the command is + * not an action command. If the command pointer is not null, + * and the command is an action command, wait for + * ((volatile struct ie_cmd_common *)pcmd)->ie_cmd_status & MASK + * to become true. + */ +static int command_and_wait(unit, cmd, pcmd, mask) + int unit; + int cmd; + volatile void *pcmd; + int mask; +{ + volatile struct ie_cmd_common *cc = pcmd; + volatile int timedout = 0; + extern int hz; + + ie_softc[unit].scb->ie_command = (u_short)cmd; + + if(IE_ACTION_COMMAND(cmd) && pcmd) { + (*ie_softc[unit].ie_chan_attn)(unit); + + /* + * According to the packet driver, the minimum timeout should be + * .369 seconds, which we round up to .37. + */ + timeout(chan_attn_timeout, (caddr_t)&timedout, 37 * hz / 100); + /* ignore cast-qual */ + + /* + * Now spin-lock waiting for status. This is not a very nice + * thing to do, but I haven't figured out how, or indeed if, we + * can put the process waiting for action to sleep. (We may + * be getting called through some other timeout running in the + * kernel.) + */ + while(1) { + if((cc->ie_cmd_status & mask) || timedout) + break; + } + + untimeout(chan_attn_timeout, (caddr_t)&timedout); + /* ignore cast-qual */ + + return timedout; + } else { + + /* + * Otherwise, just wait for the command to be accepted. + */ + (*ie_softc[unit].ie_chan_attn)(unit); + + while(ie_softc[unit].scb->ie_command) + ; /* spin lock */ + + return 0; + } +} + +/* + * Run the time-domain reflectometer... + */ +static void run_tdr(unit, cmd) + int unit; + struct ie_tdr_cmd *cmd; +{ + int result; + + cmd->com.ie_cmd_status = 0; + cmd->com.ie_cmd_cmd = IE_CMD_TDR | IE_CMD_LAST; + cmd->com.ie_cmd_link = 0xffff; + cmd->ie_tdr_time = 0; + + ie_softc[unit].scb->ie_command_list = MK_16(MEM, cmd); + cmd->ie_tdr_time = 0; + + if(command_and_wait(unit, IE_CU_START, cmd, IE_STAT_COMPL)) + result = 0x2000; + else + result = cmd->ie_tdr_time; + + ie_ack(ie_softc[unit].scb, IE_ST_WHENCE, unit, + ie_softc[unit].ie_chan_attn); + + if(result & IE_TDR_SUCCESS) + return; + + if(result & IE_TDR_XCVR) { + printf("ie%d: transceiver problem\n", unit); + } else if(result & IE_TDR_OPEN) { + printf("ie%d: TDR detected an open %d clocks away\n", unit, + result & IE_TDR_TIME); + } else if(result & IE_TDR_SHORT) { + printf("ie%d: TDR detected a short %d clocks away\n", unit, + result & IE_TDR_TIME); + } else { + printf("ie%d: TDR returned unknown status %x\n", result); + } +} + +static void start_receiver(unit) + int unit; +{ + int s = splimp(); + + ie_softc[unit].scb->ie_recv_list = MK_16(MEM, ie_softc[unit].rframes[0]); + command_and_wait(unit, IE_RU_START, 0, 0); + + ie_ack(ie_softc[unit].scb, IE_ST_WHENCE, unit, ie_softc[unit].ie_chan_attn); + + splx(s); +} + +/* + * Here is a helper routine for iernr() and ieinit(). This sets up + * the RFA. + */ +static caddr_t setup_rfa(caddr_t ptr, struct ie_softc *ie) { + volatile struct ie_recv_frame_desc *rfd = (void *)ptr; + volatile struct ie_recv_buf_desc *rbd; + int i; + int unit = ie - &ie_softc[0]; + + /* First lay them out */ + for(i = 0; i < NFRAMES; i++) { + ie->rframes[i] = rfd; + bzero((char *)rfd, sizeof *rfd); /* ignore cast-qual */ + rfd++; + } + + ptr = (caddr_t)Align((caddr_t)rfd); /* ignore cast-qual */ + + /* Now link them together */ + for(i = 0; i < NFRAMES; i++) { + ie->rframes[i]->ie_fd_next = + MK_16(MEM, ie->rframes[(i + 1) % NFRAMES]); + } + + /* Finally, set the EOL bit on the last one. */ + ie->rframes[NFRAMES - 1]->ie_fd_last |= IE_FD_LAST; + + /* + * Now lay out some buffers for the incoming frames. Note that + * we set aside a bit of slop in each buffer, to make sure that + * we have enough space to hold a single frame in every buffer. + */ + rbd = (void *)ptr; + + for(i = 0; i < NBUFFS; i++) { + ie->rbuffs[i] = rbd; + bzero((char *)rbd, sizeof *rbd); /* ignore cast-qual */ + ptr = (caddr_t)Align(ptr + sizeof *rbd); + rbd->ie_rbd_length = IE_RBUF_SIZE; + rbd->ie_rbd_buffer = MK_24(MEM, ptr); + ie->cbuffs[i] = (void *)ptr; + ptr += IE_RBUF_SIZE; + rbd = (void *)ptr; + } + + /* Now link them together */ + for(i = 0; i < NBUFFS; i++) { + ie->rbuffs[i]->ie_rbd_next = MK_16(MEM, ie->rbuffs[(i + 1) % NBUFFS]); + } + + /* Tag EOF on the last one */ + ie->rbuffs[NBUFFS - 1]->ie_rbd_length |= IE_RBD_LAST; + + /* We use the head and tail pointers on receive to keep track of + * the order in which RFDs and RBDs are used. */ + ie->rfhead = 0; + ie->rftail = NFRAMES - 1; + ie->rbhead = 0; + ie->rbtail = NBUFFS - 1; + + ie->scb->ie_recv_list = MK_16(MEM, ie->rframes[0]); + ie->rframes[0]->ie_fd_buf_desc = MK_16(MEM, ie->rbuffs[0]); + + ptr = Align(ptr); + return ptr; +} + +/* + * Run the multicast setup command. + * Call at splimp(). + */ +static int mc_setup(int unit, caddr_t ptr, + volatile struct ie_sys_ctl_block *scb) { + struct ie_softc *ie = &ie_softc[unit]; + volatile struct ie_mcast_cmd *cmd = (void *)ptr; + + cmd->com.ie_cmd_status = 0; + cmd->com.ie_cmd_cmd = IE_CMD_MCAST | IE_CMD_LAST; + cmd->com.ie_cmd_link = 0xffff; + + /* ignore cast-qual */ + bcopy((caddr_t)ie->mcast_addrs, (caddr_t)cmd->ie_mcast_addrs, + ie->mcast_count * sizeof *ie->mcast_addrs); + + cmd->ie_mcast_bytes = ie->mcast_count * 6; /* grrr... */ + + scb->ie_command_list = MK_16(MEM, cmd); + if(command_and_wait(unit, IE_CU_START, cmd, IE_STAT_COMPL) + || !(cmd->com.ie_cmd_status & IE_STAT_OK)) { + printf("ie%d: multicast address setup command failed\n", unit); + return 0; + } + return 1; +} + +/* + * This routine takes the environment generated by check_ie_present() + * and adds to it all the other structures we need to operate the adapter. + * This includes executing the CONFIGURE, IA-SETUP, and MC-SETUP commands, + * starting the receiver unit, and clearing interrupts. + * + * THIS ROUTINE MUST BE CALLED AT splimp() OR HIGHER. + */ +static void +ieinit(unit) + int unit; +{ + struct ie_softc *ie = &ie_softc[unit]; + volatile struct ie_sys_ctl_block *scb = ie->scb; + caddr_t ptr; + + ptr = (caddr_t)Align((caddr_t)scb + sizeof *scb); /* ignore cast-qual */ + + /* + * Send the configure command first. + */ + { + volatile struct ie_config_cmd *cmd = (void *)ptr; + + ie_setup_config(cmd, ie->promisc, ie->hard_type == IE_STARLAN10); + cmd->com.ie_cmd_status = 0; + cmd->com.ie_cmd_cmd = IE_CMD_CONFIG | IE_CMD_LAST; + cmd->com.ie_cmd_link = 0xffff; + + scb->ie_command_list = MK_16(MEM, cmd); + + if(command_and_wait(unit, IE_CU_START, cmd, IE_STAT_COMPL) + || !(cmd->com.ie_cmd_status & IE_STAT_OK)) { + printf("ie%d: configure command failed\n", unit); + return; + } + } + /* + * Now send the Individual Address Setup command. + */ + { + volatile struct ie_iasetup_cmd *cmd = (void *)ptr; + + cmd->com.ie_cmd_status = 0; + cmd->com.ie_cmd_cmd = IE_CMD_IASETUP | IE_CMD_LAST; + cmd->com.ie_cmd_link = 0xffff; + + bcopy((char *)ie_softc[unit].arpcom.ac_enaddr, (char *)&cmd->ie_address, + sizeof cmd->ie_address); /* ignore cast-qual */ + + scb->ie_command_list = MK_16(MEM, cmd); + if(command_and_wait(unit, IE_CU_START, cmd, IE_STAT_COMPL) + || !(cmd->com.ie_cmd_status & IE_STAT_OK)) { + printf("ie%d: individual address setup command failed\n", unit); + return; + } + } + + /* + * Now run the time-domain reflectometer. + */ + run_tdr(unit, (void *)ptr); + + /* + * Acknowledge any interrupts we have generated thus far. + */ + ie_ack(ie->scb, IE_ST_WHENCE, unit, ie->ie_chan_attn); + + /* + * Set up the RFA. + */ + ptr = setup_rfa(ptr, ie); + + /* + * Finally, the transmit command and buffer are the last little bit of work. + */ + ie->xmit_cmds[0] = (void *)ptr; + ptr += sizeof *ie->xmit_cmds[0]; + ptr = Align(ptr); + ie->xmit_buffs[0] = (void *)ptr; + ptr += sizeof *ie->xmit_buffs[0]; + ptr = Align(ptr); + + /* Second transmit command */ + ie->xmit_cmds[1] = (void *)ptr; + ptr += sizeof *ie->xmit_cmds[1]; + ptr = Align(ptr); + ie->xmit_buffs[1] = (void *)ptr; + ptr += sizeof *ie->xmit_buffs[1]; + ptr = Align(ptr); + + /* Both transmit buffers */ + ie->xmit_cbuffs[0] = (void *)ptr; + ptr += IE_BUF_LEN; + ptr = Align(ptr); + ie->xmit_cbuffs[1] = (void *)ptr; + + bzero((caddr_t)ie->xmit_cmds[0], sizeof *ie->xmit_cmds[0]); /* ignore */ + bzero((caddr_t)ie->xmit_buffs[0], sizeof *ie->xmit_buffs[0]); /* cast-qual */ + bzero((caddr_t)ie->xmit_cmds[1], sizeof *ie->xmit_cmds[0]); /* warnings */ + bzero((caddr_t)ie->xmit_buffs[1], sizeof *ie->xmit_buffs[0]); /* here */ + + /* + * This must be coordinated with iestart() and ietint(). + */ + ie->xmit_cmds[0]->ie_xmit_status = IE_STAT_COMPL; + + ie->arpcom.ac_if.if_flags |= IFF_RUNNING; /* tell higher levels that we are here */ + start_receiver(unit); + return; +} + +static void ie_stop(unit) + int unit; +{ + command_and_wait(unit, IE_RU_DISABLE, 0, 0); +} + +static int +ieioctl(ifp, command, data) + struct ifnet *ifp; + int command; + caddr_t data; +{ + struct ifaddr *ifa = (struct ifaddr *)data; + struct ie_softc *ie = &ie_softc[ifp->if_unit]; + struct ifreq *ifr = (struct ifreq *) data; + int s, error = 0; + + s = splimp(); + + switch(command) { + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + + switch(ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + ieinit(ifp->if_unit); + ((struct arpcom *)ifp)->ac_ipaddr = + IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); + break; +#endif /* INET */ + +#ifdef NS + /* This magic copied from if_is.c; I don't use XNS, so I have no + * way of telling if this actually works or not. + */ + case AF_NS: + { + struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + + if(ns_nullhost(*ina)) { + ina->x_host = *(union ns_host *)(ie->arpcom.ac_enaddr); + } else { + ifp->if_flags &= ~IFF_RUNNING; + bcopy((caddr_t)ina->x_host.c_host, + (caddr_t)ie->arpcom.ac_enaddr, + sizeof ie->arpcom.ac_enaddr); + } + + ieinit(ifp->if_unit); + } + break; +#endif /* NS */ + + default: + ieinit(ifp->if_unit); + break; + } + break; + + case SIOCSIFFLAGS: + /* + * Note that this device doesn't have an "all multicast" mode, so we + * must turn on promiscuous mode and do the filtering manually. + */ + if((ifp->if_flags & IFF_UP) == 0 && + (ifp->if_flags & IFF_RUNNING)) { + ifp->if_flags &= ~IFF_RUNNING; + ie_stop(ifp->if_unit); + } else if((ifp->if_flags & IFF_UP) && + (ifp->if_flags & IFF_RUNNING) == 0) { + ie_softc[ifp->if_unit].promisc = + ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI); + ieinit(ifp->if_unit); + } else if(ie_softc[ifp->if_unit].promisc ^ + (ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI))) { + ie_softc[ifp->if_unit].promisc = + ifp->if_flags & (IFF_PROMISC | IFF_ALLMULTI); + ieinit(ifp->if_unit); + } + break; + +#ifdef MULTICAST + case SIOCADDMULTI: + case SIOCDELMULTI: + /* + * Update multicast listeners + */ + error = ((command == SIOCADDMULTI) + ? ether_addmulti(ifr, &ie->arpcom) + : ether_delmulti(ifr, &ie->arpcom)); + + if(error == ENETRESET) { + /* reset multicast filtering */ + ie_mc_reset(ifp->if_unit); + error = 0; + } + break; +#endif /* MULTICAST */ + + case SIOCSIFMTU: + /* + * Set the interface MTU. + */ + if (ifr->ifr_mtu > ETHERMTU) { + error = EINVAL; + } else { + ifp->if_mtu = ifr->ifr_mtu; + } + break; + + default: + error = EINVAL; + } + + splx(s); + return error; +} + +#ifdef MULTICAST +static void ie_mc_reset(int unit) { + struct ie_softc *ie = &ie_softc[unit]; + struct ether_multi *enm; + struct ether_multistep step; + + /* + * Step through the list of addresses. + */ + ie->mcast_count = 0; + ETHER_FIRST_MULTI(step, &ie->arpcom, enm); + while(enm) { + if(ie->mcast_count >= MAXMCAST + || bcmp(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) { + ie->arpcom.ac_if.if_flags |= IFF_ALLMULTI; + ieioctl(&ie->arpcom.ac_if, SIOCSIFFLAGS, (void *)0); + goto setflag; + } + + bcopy(enm->enm_addrlo, &(ie->mcast_addrs[ie->mcast_count]), 6); + ie->mcast_count++; + ETHER_NEXT_MULTI(step, enm); + } + +setflag: + ie->want_mcsetup = 1; +} + +#endif + +#ifdef DEBUG +void print_rbd(volatile struct ie_recv_buf_desc *rbd) { + printf("RBD at %08lx:\n" + "actual %04x, next %04x, buffer %08x\n" + "length %04x, mbz %04x\n", + (unsigned long)rbd, + rbd->ie_rbd_actual, rbd->ie_rbd_next, rbd->ie_rbd_buffer, + rbd->ie_rbd_length, rbd->mbz); +} +#endif /* DEBUG */ +#endif /* NIE > 0 */ + diff --git a/sys/i386/isa/if_ie507.h b/sys/i386/isa/if_ie507.h new file mode 100644 index 0000000..4bf87fc --- /dev/null +++ b/sys/i386/isa/if_ie507.h @@ -0,0 +1,19 @@ +/* + * $Id: if_ie507.h,v 1.1 1994/05/25 20:06:49 ats Exp $ + * Definitions for 3C507 + */ + +#define IE507_CTRL 6 /* control port */ +#define IE507_ICTRL 10 /* interrupt control */ +#define IE507_ATTN 11 /* any write here sends a chan attn */ +#define IE507_MADDR 14 /* shared memory configuration */ +#define IE507_IRQ 15 /* IRQ configuration */ + +#define EL_CTRL_BNK1 0x01 /* register bank 1 */ +#define EL_CTRL_IEN 0x04 /* interrupt enable */ +#define EL_CTRL_INTL 0x08 /* interrupt active latch */ +#define EL_CTRL_16BIT 0x10 /* bus width; clear = 8-bit, set = 16-bit */ +#define EL_CTRL_LOOP 0x20 /* loopback mode */ +#define EL_CTRL_NRST 0x80 /* turn off to reset */ +#define EL_CTRL_RESET (EL_CTRL_LOOP) +#define EL_CTRL_NORMAL (EL_CTRL_NRST | EL_CTRL_IEN | EL_CTRL_BNK1) diff --git a/sys/i386/isa/if_iereg.h b/sys/i386/isa/if_iereg.h new file mode 100644 index 0000000..3588b84 --- /dev/null +++ b/sys/i386/isa/if_iereg.h @@ -0,0 +1,24 @@ +/* + * $Id$ + * definitions for AT&T StarLAN 10 etc... + */ + +#define IEATT_RESET 0 /* any write here resets the 586 */ +#define IEATT_ATTN 1 /* any write here sends a Chan attn */ +#define IEATT_REVISION 6 /* read here to figure out this board */ +#define IEATT_ATTRIB 7 /* more information about this board */ + +#define SL_BOARD(x) ((x) & 0x0f) +#define SL_REV(x) ((x) >> 4) + +#define SL1_BOARD 0 +#define SL10_BOARD 1 +#define EN100_BOARD 2 +#define SLFIBER_BOARD 3 + +#define SL_ATTR_WIDTH 0x04 /* bus width: clear -> 8-bit */ +#define SL_ATTR_SPEED 0x08 /* medium speed: clear -> 10 Mbps */ +#define SL_ATTR_CODING 0x10 /* encoding: clear -> Manchester */ +#define SL_ATTR_HBW 0x20 /* host bus width: clear -> 16-bit */ +#define SL_ATTR_TYPE 0x40 /* medium type: clear -> Ethernet */ +#define SL_ATTR_BOOTROM 0x80 /* set -> boot ROM present */ diff --git a/sys/i386/isa/if_is.c b/sys/i386/isa/if_is.c new file mode 100644 index 0000000..b2a6e87 --- /dev/null +++ b/sys/i386/isa/if_is.c @@ -0,0 +1,1161 @@ +/* + * Isolan AT 4141-0 Ethernet driver + * Isolink 4110 + * + * By Paul Richards + * + * Copyright (C) 1993, Paul Richards. This software may be used, modified, + * copied, distributed, and sold, in both source and binary form provided + * that the above copyright and these terms are retained. Under no + * circumstances is the author responsible for the proper functioning + * of this software, nor does the author assume any responsibility + * for damages incurred with its use. + * + * $Id: if_is.c,v 1.25 1994/08/08 13:33:16 davidg Exp $ + */ + +/* TODO + +1) Add working multicast support +2) Use better allocation of memory to card +3) Advertise for more packets until all transmit buffers are full +4) Add more of the timers/counters e.g. arpcom.opackets etc. +*/ + +#include "is.h" +#if NIS > 0 + +#include "bpfilter.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/if_ether.h> +#endif + +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif + +#if NBPFILTER > 0 +#include <net/bpf.h> +#include <net/bpfdesc.h> +#endif + +#include <i386/isa/isa_device.h> +#include <i386/isa/if_isreg.h> +#include <i386/isa/icu.h> + +#include <vm/vm.h> + +#define ETHER_MIN_LEN 64 +#define ETHER_MAX_LEN 1518 +#define ETHER_ADDR_LEN 6 + +char *card_type[] = {"Unknown", + "BICC Isolan", + "NE2100"}; + +char *ic_type[] = {"Unknown", + "Am7990 LANCE", + "Am79960 PCnet_ISA"}; + + +struct is_softc { + struct arpcom arpcom; /* Ethernet common part */ + int iobase; + int rap; + int rdp; + int ic_type; /* Am 7990 or Am79960 */ + int card_type; + int is_debug; + struct init_block *init_block; /* Lance initialisation block */ + struct mds *rd; + struct mds *td; + unsigned char *rbuf; + unsigned char *tbuf; + int last_rd; + int last_td; + int no_td; + caddr_t bpf; /* BPF "magic cookie" */ + +} is_softc[NIS] ; + + +/* Function prototypes */ +static int is_probe(struct isa_device *); +static int is_attach(struct isa_device *); +static void is_watchdog(int); +static int is_ioctl(struct ifnet *, int, caddr_t); +static void is_init(int); +static void is_start(struct ifnet *); +static void istint(int); +static void recv_print(int, int); +static void xmit_print(int, int); + + + +static inline void is_rint(int unit); +static inline void isread(struct is_softc*, unsigned char*, int); + +struct mbuf *isget(); + +struct isa_driver isdriver = { + is_probe, + is_attach, + "is" +}; + +void +iswrcsr(unit,port,val) + int unit; + u_short port; + u_short val; +{ + outw(is_softc[unit].rap,port); + outw(is_softc[unit].rdp,val); +} + +u_short isrdcsr(unit,port) + int unit; + u_short port; +{ + outw(is_softc[unit].rap,port); + return(inw(is_softc[unit].rdp)); +} + +int +is_probe(isa_dev) + struct isa_device *isa_dev; +{ + int unit = isa_dev->id_unit ; + int nports; + +int i; + is_softc[unit].iobase = isa_dev->id_iobase; + + /* + * It's impossible to do a non-invasive probe of the + * LANCE and PCnet_ISA. The LANCE requires setting the + * STOP bit to access the registers and the PCnet_ISA + * address port resets to an unknown state!! + */ + + /* + * Check for BICC cards first since for the NE2100 and + * PCnet-ISA cards this write will hit the Address PROM. + */ + +#ifdef DEBUG + printf("Dumping io space for is%d starting at %x\n",unit,is_softc[unit].iobase); + for (i=0; i< 32; i++) + printf(" %x ",inb(is_softc[unit].iobase+i)); + printf("\n"); +#endif /* DEBUG*/ + + if (nports = bicc_probe(unit)) + return (nports); + if (nports = ne2100_probe(unit)) + return (nports); + + + return (0); +} + +int +ne2100_probe(unit) + int unit; +{ +struct is_softc *is = &is_softc[unit]; +int i; + + is->rap = is->iobase + NE2100_RAP; + is->rdp = is->iobase + NE2100_RDP; + + if (is->ic_type = lance_probe(unit)) { + is->card_type = NE2100; + /* + * Extract the physical MAC address from ROM + */ + for(i=0;i<ETHER_ADDR_LEN;i++) + is->arpcom.ac_enaddr[i]=inb(is->iobase+i); + + /* + * Return number of I/O ports used by card + */ + return (24); + } + return (0); +} + + +int +bicc_probe(unit) + int unit; +{ +struct is_softc *is = &is_softc[unit]; +int i; + + is->rap = is->iobase + BICC_RAP; + is->rdp = is->iobase + BICC_RDP; + + if (is->ic_type = lance_probe(unit)) { + is->card_type = BICC; + + /* + * Extract the physical ethernet address from ROM + */ + + for(i=0;i<ETHER_ADDR_LEN;i++) + is->arpcom.ac_enaddr[i]=inb(is->iobase+(i*2)); + + /* + * Return number of I/O ports used by card + */ + return (16); + } + return (0); +} + + +/* + * Determine which, if any, of the LANCE or + * PCnet-ISA are present on the card. + */ + +int +lance_probe(unit) + int unit; +{ +int type=0; + + /* + * Have to reset the LANCE to get any + * stable information from it. + */ + + iswrcsr(unit,0,STOP); + DELAY(100); + + if (isrdcsr(unit,0) != STOP) + /* + * This either isn't a LANCE + * or there's a major problem. + */ + return(0); + + /* + * Depending on which controller it is, CSR3 will have + * different settable bits. Write to them all and see which ones + * get set. + */ + + iswrcsr(unit,3, LANCE_MASK); + + if (isrdcsr(unit,3) == LANCE_MASK) + type = LANCE; + + if (isrdcsr(unit,3) == PCnet_ISA_MASK) + type = PCnet_ISA; + + return (type); +} + +/* + * Reset of interface. + */ +static void +is_reset(int unit) +{ + int s; + struct is_softc *is = &is_softc[unit]; + + if (unit >= NIS) + return; + printf("is%d: reset\n", unit); + is_init(unit); +} + +/* + * Interface exists: make available by filling in network interface + * record. System will initialize the interface when it is ready + * to accept packets. We get the ethernet address here. + */ +int +is_attach(isa_dev) + struct isa_device *isa_dev; +{ + int unit = isa_dev->id_unit; + struct is_softc *is = &is_softc[unit]; + struct ifnet *ifp = &is->arpcom.ac_if; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + + ifp->if_unit = unit; + ifp->if_name = isdriver.name ; + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS; + ifp->if_init = is_init; + ifp->if_output = ether_output; + ifp->if_start = is_start; + ifp->if_ioctl = is_ioctl; + ifp->if_reset = is_reset; + ifp->if_watchdog = is_watchdog; + + /* + * XXX -- not sure this is right place to do this + * Allocate memory for use by Lance + * Memory allocated for: + * initialisation block, + * ring descriptors, + * transmit and receive buffers. + */ + + /* + * XXX - hopefully have better way to get dma'able memory later, + * this code assumes that the physical memory address returned + * from malloc will be below 16Mb. The Lance's address registers + * are only 16 bits wide! + */ + +#define MAXMEM ((NRBUF+NTBUF)*(BUFSIZE) + (NRBUF+NTBUF)*sizeof(struct mds) \ + + sizeof(struct init_block) + 8) + is->init_block = (struct init_block *)malloc(MAXMEM,M_TEMP,M_NOWAIT); + if (!is->init_block) { + printf("is%d : Couldn't allocate memory for card\n",unit); + } + /* + * XXX -- should take corrective action if not + * quadword alilgned, the 8 byte slew factor in MAXMEM + * allows for this. + */ + + if ((u_long)is->init_block & 0x3) + printf("is%d: memory allocated not quadword aligned\n"); + + /* Set up DMA */ + isa_dmacascade(isa_dev->id_drq); + + if_attach(ifp); + + /* + * Search down the ifa address list looking + * for the AF_LINK type entry + */ + + ifa = ifp->if_addrlist; + while ((ifa != 0) && (ifa->ifa_addr != 0) && + (ifa->ifa_addr->sa_family != AF_LINK)) + ifa = ifa->ifa_next; + + /* + * If we find an AF_LINK type entry, we will fill + * in the hardware address for this interface. + */ + + if ((ifa != 0) && (ifa->ifa_addr != 0)) { + + /* + * Fill in the link level address for this interface + */ + + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = ETHER_ADDR_LEN; + sdl->sdl_slen = 0; + bcopy(is->arpcom.ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); + } + + printf ("is%d: address %s\n", unit, + ether_sprintf(is->arpcom.ac_enaddr)) ; + printf("%s, %s\n",ic_type[is->ic_type],card_type[is->card_type]); + +#if NBPFILTER > 0 + bpfattach(&is->bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); +#endif + return 1; +} + +static void +is_watchdog(unit) + int unit; +{ + log(LOG_ERR, "is%d: device timeout\n", unit); + is_reset(unit); +} + + +/* Lance initialisation block set up */ +void +init_mem(unit) + int unit; +{ + int i; + void *temp; + struct is_softc *is = &is_softc[unit]; + + /* + * At this point we assume that the + * memory allocated to the Lance is + * quadword aligned. If it isn't + * then the initialisation is going + * fail later on. + */ + + + /* + * Set up lance initialisation block + */ + + temp = (void *)is->init_block; + temp += sizeof(struct init_block); + is->rd = (struct mds *) temp; + is->td = (struct mds *) (temp + (NRBUF*sizeof(struct mds))); + temp += (NRBUF+NTBUF) * sizeof(struct mds); + + is->init_block->mode = 0; + for (i=0; i<ETHER_ADDR_LEN; i++) + is->init_block->padr[i] = is->arpcom.ac_enaddr[i]; + for (i = 0; i < 8; ++i) + is->init_block->ladrf[i] = MULTI_INIT_ADDR; + is->init_block->rdra = kvtop(is->rd); + is->init_block->rlen = ((kvtop(is->rd) >> 16) & 0xff) | (RLEN<<13); + is->init_block->tdra = kvtop(is->td); + is->init_block->tlen = ((kvtop(is->td) >> 16) & 0xff) | (TLEN<<13); + + + /* + * Set up receive ring descriptors + */ + + is->rbuf = (unsigned char *)temp; + for (i=0; i<NRBUF; i++) { + (is->rd+i)->addr = kvtop(temp); + (is->rd+i)->flags= ((kvtop(temp) >> 16) & 0xff) | OWN; + (is->rd+i)->bcnt = -BUFSIZE; + (is->rd+i)->mcnt = 0; + temp += BUFSIZE; + } + + /* + * Set up transmit ring descriptors + */ + + is->tbuf = (unsigned char *)temp; + for (i=0; i<NTBUF; i++) { + (is->td+i)->addr = kvtop(temp); + (is->td+i)->flags= ((kvtop(temp) >> 16) & 0xff); + (is->td+i)->bcnt = 0; + (is->td+i)->mcnt = 0; + temp += BUFSIZE; + } + +} + +/* + * Initialization of interface; set up initialization block + * and transmit/receive descriptor rings. + */ + +static void +is_init(unit) + int unit; +{ + register struct is_softc *is = &is_softc[unit]; + struct ifnet *ifp = &is->arpcom.ac_if; + int s; + register i; + + /* Address not known */ + if (ifp->if_addrlist == (struct ifaddr *)0) return; + + s = splimp(); + + /* + * Lance must be stopped + * to access registers. + */ + + iswrcsr(unit,0,STOP); + + is->last_rd = is->last_td = is->no_td = 0; + + /* Set up lance's memory area */ + init_mem(unit); + + /* No byte swapping etc */ + iswrcsr(unit,3,0); + + /* Give lance the physical address of its memory area */ + iswrcsr(unit,1,kvtop(is->init_block)); + iswrcsr(unit,2,(kvtop(is->init_block) >> 16) & 0xff); + + /* OK, let's try and initialise the Lance */ + iswrcsr(unit,0,INIT); + + /* Wait for initialisation to finish */ + for(i=0; i<1000; i++){ + if (isrdcsr(unit,0)&IDON) + break; + } + if (isrdcsr(unit,0)&IDON) { + /* Start lance */ + iswrcsr(unit,0,STRT|IDON|INEA); + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + + is_start(ifp); + } + else + printf("is%d: card failed to initialise\n", unit); + + (void) splx(s); +} + +/* + * Setup output on interface. + * Get another datagram to send off of the interface queue, + * and map it to the interface before starting the output. + * called only at splimp or interrupt level. + */ +static void +is_start(ifp) + struct ifnet *ifp; +{ + int unit = ifp->if_unit; + register struct is_softc *is = &is_softc[unit]; + struct mbuf *m0, *m; + unsigned char *buffer; + u_short len; + int i; + struct mds *cdm; + + + if ((is->arpcom.ac_if.if_flags & IFF_RUNNING) == 0) + return; + + do { + cdm = (is->td + is->last_td); + if (cdm->flags&OWN) + return; + + IF_DEQUEUE(&is->arpcom.ac_if.if_snd, m); + + if (m == 0) + return; + + /* + * Copy the mbuf chain into the transmit buffer + */ + + buffer = is->tbuf+(BUFSIZE*is->last_td); + len=0; + for (m0=m; m != 0; m=m->m_next) { + bcopy(mtod(m,caddr_t),buffer,m->m_len); + buffer += m->m_len; + len += m->m_len; + } +#if NBPFILTER > 0 + if (is->bpf) { + u_short etype; + int off, datasize, resid; + struct ether_header *eh; + struct trailer_header { + u_short ether_type; + u_short ether_residual; + } trailer_header; + char ether_packet[ETHER_MAX_LEN]; + char *ep; + + ep = ether_packet; + + /* + * We handle trailers below: + * Copy ether header first, then residual data, + * then data. Put all this in a temporary buffer + * 'ether_packet' and send off to bpf. Since the + * system has generated this packet, we assume + * that all of the offsets in the packet are + * correct; if they're not, the system will almost + * certainly crash in m_copydata. + * We make no assumptions about how the data is + * arranged in the mbuf chain (i.e. how much + * data is in each mbuf, if mbuf clusters are + * used, etc.), which is why we use m_copydata + * to get the ether header rather than assume + * that this is located in the first mbuf. + */ + /* copy ether header */ + m_copydata(m0, 0, sizeof(struct ether_header), ep); + eh = (struct ether_header *) ep; + ep += sizeof(struct ether_header); + etype = ntohs(eh->ether_type); + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) { + datasize = ((etype - ETHERTYPE_TRAIL) << 9); + off = datasize + sizeof(struct ether_header); + + /* copy trailer_header into a data structure */ + m_copydata(m0, off, sizeof(struct trailer_header), + (caddr_t)&trailer_header.ether_type); + + /* copy residual data */ + resid = trailer_header.ether_residual - + sizeof(struct trailer_header); + resid = ntohs(resid); + m_copydata(m0, off+sizeof(struct trailer_header), + resid, ep); + ep += resid; + + /* copy data */ + m_copydata(m0, sizeof(struct ether_header), + datasize, ep); + ep += datasize; + + /* restore original ether packet type */ + eh->ether_type = trailer_header.ether_type; + + bpf_tap(is->bpf, ether_packet, ep - ether_packet); + } else + bpf_mtap(is->bpf, m0); + } +#endif + + + m_freem(m0); + len = max(len,ETHER_MIN_LEN); + + /* + * Init transmit registers, and set transmit start flag. + */ + + cdm->flags |= (OWN|STP|ENP); + cdm->bcnt = -len; + cdm->mcnt = 0; +#ifdef ISDEBUG + if (is->is_debug) + xmit_print(unit,is->last_td); +#endif + + iswrcsr(unit,0,TDMD|INEA); + if (++is->last_td >= NTBUF) + is->last_td=0; + }while(++is->no_td < NTBUF); + is->no_td = NTBUF; + is->arpcom.ac_if.if_flags |= IFF_OACTIVE; +#ifdef ISDEBUG + if (is->is_debug) + printf("no_td = %x, last_td = %x\n",is->no_td, is->last_td); +#endif +} + + +/* + * Controller interrupt. + */ +void +isintr(unit) + int unit; +{ + register struct is_softc *is = &is_softc[unit]; + u_short isr; + + while((isr=isrdcsr(unit,0))&INTR) { + if (isr&ERR) { + if (isr&BABL){ + printf("is%d: BABL\n",unit); + is->arpcom.ac_if.if_oerrors++; + } + if (isr&CERR) { + printf("is%d: CERR\n",unit); + is->arpcom.ac_if.if_collisions++; + } + if (isr&MISS) { + printf("is%d: MISS\n",unit); + is->arpcom.ac_if.if_ierrors++; + } + if (isr&MERR) + printf("is%d: MERR\n",unit); + iswrcsr(unit,0,BABL|CERR|MISS|MERR|INEA); + } + if (!(isr&RXON)) { + printf("is%d: !(isr&RXON)\n", unit); + is->arpcom.ac_if.if_ierrors++; + is_reset(unit); + return; + } + if (!(isr&TXON)) { + printf("is%d: !(isr&TXON)\n", unit); + is->arpcom.ac_if.if_oerrors++; + is_reset(unit); + return; + } + + if (isr&RINT) { + /* reset watchdog timer */ + is->arpcom.ac_if.if_timer = 0; + is_rint(unit); + } + if (isr&TINT) { + /* reset watchdog timer */ + is->arpcom.ac_if.if_timer = 0; + iswrcsr(unit,0,TINT|INEA); + istint(unit); + } + } +} + +static void +istint(unit) + int unit; +{ + struct is_softc *is = &is_softc[unit]; + register struct ifnet *ifp = &is->arpcom.ac_if; + int i,loopcount=0; + struct mds *cdm; + + is->arpcom.ac_if.if_opackets++; + do { + if ((i=is->last_td - is->no_td) < 0) + i+=NTBUF; + cdm = (is->td+i); +#ifdef ISDEBUG + if (is->is_debug) + printf("Trans cdm = %x\n",cdm); +#endif + if (cdm->flags&OWN) { + if (loopcount) + break; + return; + } + loopcount++; + is->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + }while(--is->no_td > 0); + is_start(ifp); + +} + +#define NEXTRDS \ + if (++rmd == NRBUF) rmd=0, cdm=is->rd; else ++cdm + +/* only called from one place, so may as well integrate */ +static inline void is_rint(int unit) +{ + register struct is_softc *is=&is_softc[unit]; + register int rmd = is->last_rd; + struct mds *cdm = (is->rd + rmd); + + /* Out of sync with hardware, should never happen */ + + if (cdm->flags & OWN) { + printf("is%d: error: out of sync\n",unit); + iswrcsr(unit,0,RINT|INEA); + return; + } + + /* Process all buffers with valid data */ + while (!(cdm->flags&OWN)) { + /* Clear interrupt to avoid race condition */ + iswrcsr(unit,0,RINT|INEA); + if (cdm->flags&ERR) { + if (cdm->flags&FRAM) + printf("is%d: FRAM\n",unit); + if (cdm->flags&OFLO) + printf("is%d: OFLO\n",unit); + if (cdm->flags&CRC) + printf("is%d: CRC\n",unit); + if (cdm->flags&RBUFF) + printf("is%d: RBUFF\n",unit); + }else + if (cdm->flags&(STP|ENP) != (STP|ENP)) { + do { + iswrcsr(unit,0,RINT|INEA); + cdm->mcnt = 0; + cdm->flags |= OWN; + NEXTRDS; + }while (!(cdm->flags&(OWN|ERR|STP|ENP))); + is->last_rd = rmd; + printf("is%d: Chained buffer\n",unit); + if ((cdm->flags & (OWN|ERR|STP|ENP)) != ENP) { + is_reset(unit); + return; + } + }else + { +#ifdef ISDEBUG + if (is->is_debug) + recv_print(unit,is->last_rd); +#endif + isread(is,is->rbuf+(BUFSIZE*rmd),(int)cdm->mcnt); + is->arpcom.ac_if.if_ipackets++; + } + + cdm->flags |= OWN; + cdm->mcnt = 0; + NEXTRDS; +#ifdef ISDEBUG + if (is->is_debug) + printf("is->last_rd = %x, cdm = %x\n",is->last_rd,cdm); +#endif + } /* while */ + is->last_rd = rmd; +} /* is_rint */ + + +/* + * Pass a packet to the higher levels. + * We deal with the trailer protocol here. + */ +static inline void +isread(struct is_softc *is, unsigned char *buf, int len) +{ + register struct ether_header *eh; + struct mbuf *m; + int off, resid; + register struct ifqueue *inq; + + /* + * Deal with trailer protocol: if type is trailer type + * get true type from first 16-bit word past data. + * Remember that type was trailer by setting off. + */ + eh = (struct ether_header *)buf; + eh->ether_type = ntohs((u_short)eh->ether_type); + len = len - sizeof(struct ether_header) - 4; +#define nedataaddr(eh, off, type) ((type)(((caddr_t)((eh)+1)+(off)))) + if (eh->ether_type >= ETHERTYPE_TRAIL && + eh->ether_type < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) { + off = (eh->ether_type - ETHERTYPE_TRAIL) * 512; + if (off >= ETHERMTU) return; /* sanity */ + eh->ether_type = ntohs(*nedataaddr(eh, off, u_short *)); + resid = ntohs(*(nedataaddr(eh, off+2, u_short *))); + if (off + resid > len) return; /* sanity */ + len = off + resid; + } else off = 0; + + if (len == 0) return; + + /* + * Pull packet off interface. Off is nonzero if packet + * has trailing header; neget will then force this header + * information to be at the front, but we still have to drop + * the type and length which are at the front of any trailer data. + */ + m = isget(buf, len, off, &is->arpcom.ac_if); + if (m == 0) return; +#if NBPFILTER > 0 + /* + * Check if there's a BPF listener on this interface. + * If so, hand off the raw packet to bpf. + */ + if (is->bpf) { + bpf_mtap(is->bpf, m); + + /* + * Note that the interface cannot be in promiscuous mode if + * there are no BPF listeners. And if we are in promiscuous + * mode, we have to check if this packet is really ours. + * + * XXX This test does not support multicasts. + */ + if ((is->arpcom.ac_if.if_flags & IFF_PROMISC) && + bcmp(eh->ether_dhost, is->arpcom.ac_enaddr, + sizeof(eh->ether_dhost)) != 0 && + bcmp(eh->ether_dhost, etherbroadcastaddr, + sizeof(eh->ether_dhost)) != 0) { + + m_freem(m); + return; + } + } +#endif + + + ether_input(&is->arpcom.ac_if, eh, m); +} + +/* + * Supporting routines + */ + +/* + * Pull read data off a interface. + * Len is length of data, with local net header stripped. + * Off is non-zero if a trailer protocol was used, and + * gives the offset of the trailer information. + * We copy the trailer information and then all the normal + * data into mbufs. When full cluster sized units are present + * we copy into clusters. + */ +struct mbuf * +isget(buf, totlen, off0, ifp) + caddr_t buf; + int totlen, off0; + struct ifnet *ifp; +{ + struct mbuf *top, **mp, *m, *p; + int off = off0, len; + register caddr_t cp = buf; + char *epkt; + + buf += sizeof(struct ether_header); + cp = buf; + epkt = cp + totlen; + + + if (off) { + cp += off + 2 * sizeof(u_short); + totlen -= 2 * sizeof(u_short); + } + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == 0) + return (0); + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = totlen; + m->m_len = MHLEN; + top = 0; + mp = ⊤ + while (totlen > 0) { + if (top) { + MGET(m, M_DONTWAIT, MT_DATA); + if (m == 0) { + m_freem(top); + return (0); + } + m->m_len = MLEN; + } + len = min(totlen, epkt - cp); + if (len >= MINCLSIZE) { + MCLGET(m, M_DONTWAIT); + if (m->m_flags & M_EXT) + m->m_len = len = min(len, MCLBYTES); + else + len = m->m_len; + } else { + /* + * Place initial small packet/header at end of mbuf. + */ + if (len < m->m_len) { + if (top == 0 && len + max_linkhdr <= m->m_len) + m->m_data += max_linkhdr; + m->m_len = len; + } else + len = m->m_len; + } + bcopy(cp, mtod(m, caddr_t), (unsigned)len); + cp += len; + *mp = m; + mp = &m->m_next; + totlen -= len; + if (cp == epkt) + cp = buf; + } + return (top); +} + + +/* + * Process an ioctl request. + */ +int +is_ioctl(ifp, cmd, data) + register struct ifnet *ifp; + int cmd; + caddr_t data; +{ + register struct ifaddr *ifa = (struct ifaddr *)data; + int unit = ifp->if_unit; + struct is_softc *is = &is_softc[unit]; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + + s = splimp(); + + switch (cmd) { + + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + + switch (ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + is_init(ifp->if_unit); /* before arpwhohas */ + /* + * See if another station has *our* IP address. + * i.e.: There is an address conflict! If a + * conflict exists, a message is sent to the + * console. + */ + ((struct arpcom *)ifp)->ac_ipaddr = + IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); + break; +#endif +#ifdef NS + /* + * XXX - This code is probably wrong + */ + case AF_NS: + { + register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + + if (ns_nullhost(*ina)) + ina->x_host = + *(union ns_host *)(is->arpcom.ac_enaddr); + else { + /* + * + */ + bcopy((caddr_t)ina->x_host.c_host, + (caddr_t)is->arpcom.ac_enaddr, + sizeof(is->arpcom.ac_enaddr)); + } + /* + * Set new address + */ + is_init(ifp->if_unit); + break; + } +#endif + default: + is_init(ifp->if_unit); + break; + } + break; + + case SIOCSIFFLAGS: + /* + * If interface is marked down and it is running, then stop it + */ + if ((ifp->if_flags & IFF_UP) == 0 && + ifp->if_flags & IFF_RUNNING) { + iswrcsr(unit,0,STOP); + ifp->if_flags &= ~IFF_RUNNING; + } else { + /* + * If interface is marked up and it is stopped, then start it + */ + if ((ifp->if_flags & IFF_UP) && + (ifp->if_flags & IFF_RUNNING) == 0) + is_init(ifp->if_unit); + } +#ifdef ISDEBUG + if (ifp->if_flags & IFF_DEBUG) + is->is_debug = 1; + else + is->is_debug = 0; +#endif +#if NBPFILTER > 0 + if (ifp->if_flags & IFF_PROMISC) { + /* + * Set promiscuous mode on interface. + * XXX - for multicasts to work, we would need to + * write 1's in all bits of multicast + * hashing array. For now we assume that + * this was done in is_init(). + */ + is->init_block->mode = PROM; + } else + /* + * XXX - for multicasts to work, we would need to + * rewrite the multicast hashing array with the + * proper hash (would have been destroyed above). + */ + { /* Don't know about this */}; +#endif + break; + +#ifdef notdef + case SIOCGHWADDR: + bcopy((caddr_t)is->arpcom.ac_enaddr, (caddr_t) &ifr->ifr_data, + sizeof(is->arpcom.ac_enaddr)); + break; +#endif + + case SIOCSIFMTU: + + /* + * Set the interface MTU. + */ + if (ifr->ifr_mtu > ETHERMTU) { + error = EINVAL; + } else { + ifp->if_mtu = ifr->ifr_mtu; + } + break; + + default: + error = EINVAL; + } + (void) splx(s); + return (error); +} + +#ifdef ISDEBUG +void +recv_print(unit,no) + int unit,no; +{ + register struct is_softc *is=&is_softc[unit]; + struct mds *rmd; + int len,i,printed=0; + + rmd = (is->rd+no); + len = rmd->mcnt; + printf("is%d: Receive buffer %d, len = %d\n",unit,no,len); + printf("is%d: Status %x\n",unit,isrdcsr(unit,0)); + for (i=0; i<len; i++) { + if (!printed) { + printed=1; + printf("is%d: data: ", unit); + } + printf("%x ",*(is->rbuf+(BUFSIZE*no)+i)); + } + if (printed) + printf("\n"); +} + +void +xmit_print(unit,no) + int unit,no; +{ + register struct is_softc *is=&is_softc[unit]; + struct mds *rmd; + int i, printed=0; + u_short len; + + rmd = (is->td+no); + len = -(rmd->bcnt); + printf("is%d: Transmit buffer %d, len = %d\n",unit,no,len); + printf("is%d: Status %x\n",unit,isrdcsr(unit,0)); + printf("is%d: addr %x, flags %x, bcnt %x, mcnt %x\n", + unit,rmd->addr,rmd->flags,rmd->bcnt,rmd->mcnt); + for (i=0; i<len; i++) { + if (!printed) { + printed = 1; + printf("is%d: data: ", unit); + } + printf("%x ",*(is->tbuf+(BUFSIZE*no)+i)); + } + if (printed) + printf("\n"); +} +#endif /* ISDEBUG */ + +#endif /* NIS > 0 */ diff --git a/sys/i386/isa/if_isreg.h b/sys/i386/isa/if_isreg.h new file mode 100644 index 0000000..b4b4ece --- /dev/null +++ b/sys/i386/isa/if_isreg.h @@ -0,0 +1,129 @@ +/* + * Isolan AT 4141-0 Ethernet driver header file + * Isolink 4110 + * + * By Paul Richards + * + * Copyright (C) 1993, Paul Richards. This software may be used, modified, + * copied, distributed, and sold, in both source and binary form provided + * that the above copyright and these terms are retained. Under no + * circumstances is the author responsible for the proper functioning + * of this software, nor does the author assume any responsibility + * for damages incurred with its use. + * + * $Id: if_isreg.h,v 1.3 1993/10/24 04:27:00 paul Exp $ + */ + +/* + * Initialize multicast address hashing registers to accept + * all multicasts (only used when in promiscuous mode) + */ +#if NBPFILTER > 0 +#define MULTI_INIT_ADDR 0xff +#else +#define MULTI_INIT_ADDR 0 +#endif + +/* Declarations specific to this driver */ +#define NTBUF 2 +#define TLEN 1 +#define NRBUF 8 +#define RLEN 3 +#define BUFSIZE 1518 +#define BICC_RDP 0xc +#define BICC_RAP 0xe +#define NE2100_RDP 0x10 +#define NE2100_RAP 0x12 + +/* Board types */ +#define BICC 1 +#define NE2100 2 + +/* Am7990 or Am79960 */ +#define LANCE 1 +#define LANCE_MASK 0x07 +#define PCnet_ISA 2 +#define PCnet_ISA_MASK 0x0 + + +/* Control and status register 0 flags */ + +#define ERR 0x8000 +#define BABL 0x4000 +#define CERR 0x2000 +#define MISS 0x1000 +#define MERR 0x0800 +#define RINT 0x0400 +#define TINT 0x0200 +#define IDON 0x0100 +#define INTR 0x0080 +#define INEA 0x0040 +#define RXON 0x0020 +#define TXON 0x0010 +#define TDMD 0x0008 +#define STOP 0x0004 +#define STRT 0x0002 +#define INIT 0x0001 + +/* Coontrol and status register 3 flags */ + +#define BSWP 0x0004 +#define ACON 0x0002 +#define BCON 0x0001 + +/* Initialisation block (must be on word boundary) */ + +struct init_block { + u_short mode; /* Mode register */ + u_char padr[6]; /* Ethernet address */ + u_char ladrf[8]; /* Logical address filter (multicast) */ + u_short rdra; /* Low order pointer to receive ring */ + u_short rlen; /* High order pointer and no. rings */ + u_short tdra; /* Low order pointer to transmit ring */ + u_short tlen; /* High order pointer and no rings */ + }; + +/* Mode settings */ + +#define PROM 0x8000 /* Promiscuous */ +#define INTL 0x0040 /* Internal loopback */ +#define DRTY 0x0020 /* Disable retry */ +#define COLL 0x0010 /* Force collision */ +#define DTCR 0x0008 /* Disable transmit crc */ +#define LOOP 0x0004 /* Loop back */ +#define DTX 0x0002 /* Disable transmitter */ +#define DRX 0x0001 /* Disable receiver */ + +/* Message descriptor structure */ + +struct mds { + u_short addr; + u_short flags; + u_short bcnt; + u_short mcnt; + }; + +/* Receive ring status flags */ + +#define OWN 0x8000 /* Owner bit, 0=host, 1=Lance */ +#define MDERR 0x4000 /* Error */ +#define FRAM 0x2000 /* Framing error error */ +#define OFLO 0x1000 /* Silo overflow */ +#define CRC 0x0800 /* CRC error */ +#define RBUFF 0x0400 /* Buffer error */ +#define STP 0x0200 /* Start of packet */ +#define ENP 0x0100 /* End of packet */ + +/* Transmit ring flags */ + +#define MORE 0x1000 /* More than 1 retry */ +#define ONE 0x0800 /* One retry */ +#define DEF 0x0400 /* Deferred transmit */ + +/* Transmit errors */ + +#define TBUFF 0x8000 /* Buffer error */ +#define UFLO 0x4000 /* Silo underflow */ +#define LCOL 0x1000 /* Late collision */ +#define LCAR 0x0800 /* Loss of carrier */ +#define RTRY 0x0400 /* Tried 16 times */ diff --git a/sys/i386/isa/if_le.c b/sys/i386/isa/if_le.c new file mode 100644 index 0000000..bf5fa4c --- /dev/null +++ b/sys/i386/isa/if_le.c @@ -0,0 +1,2167 @@ +/*- + * Copyright (c) 1994 Matt Thomas (thomas@lkg.dec.com) + * 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. The name of the author may not be used to endorse or promote products + * derived from this software withough specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * $Id: if_le.c,v 1.3 1994/08/20 03:48:39 davidg Exp $ + * + */ + +/* + * DEC EtherWORKS 2 Ethernet Controllers + * DEC EtherWORKS 3 Ethernet Controllers + * + * Written by Matt Thomas + * BPF support code stolen directly from if_ec.c + * + * This driver supports the DEPCA, DE100, DE101, DE200, DE201, + * DE2002, DE203, DE204, DE205, and DE422 cards. + */ + +#include "le.h" +#if NLE > 0 + +#include "param.h" +#include "systm.h" +#include "mbuf.h" +#include "protosw.h" +#include "socket.h" +#include "ioctl.h" +#include "errno.h" +#include "malloc.h" +#include "syslog.h" + +#include "net/if.h" +#include "net/if_types.h" +#include "net/if_dl.h" +#include "net/route.h" + +#include "bpfilter.h" + +#ifdef INET +#include "netinet/in.h" +#include "netinet/in_systm.h" +#include "netinet/in_var.h" +#include "netinet/ip.h" +#include "netinet/if_ether.h" +#endif + +#ifdef NS +#include "netns/ns.h" +#include "netns/ns_if.h" +#endif + +#include "i386/isa/isa.h" +#include "i386/isa/isa_device.h" +#include "i386/isa/icu.h" + +#include "vm/vm.h" + +#if NBPFILTER > 0 +#include "net/bpf.h" +#include "net/bpfdesc.h" +#endif + +/* Forward declarations */ +typedef struct le_softc le_softc_t; +typedef struct le_board le_board_t; + +typedef u_short le_mcbits_t; +#define LE_MC_NBPW_LOG2 4 +#define LE_MC_NBPW (1 << LE_MC_NBPW_LOG2) + +#if !defined(LE_NOLEMAC) +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Start of DEC EtherWORKS III (LEMAC) dependent structures + * + */ +#include "i386/isa/ic/lemac.h" /* Include LEMAC definitions */ + +static int lemac_probe(le_softc_t *sc, const le_board_t *bd, int *msize); + +struct le_lemac_info { + u_int lemac__lastpage; /* last 2K page */ + u_int lemac__memmode; /* Are we in 2K, 32K, or 64K mode */ + u_int lemac__membase; /* Physical address of start of RAM */ + u_int lemac__txctl; /* Transmit Control Byte */ + u_int lemac__txmax; /* Maximum # of outstanding transmits */ + le_mcbits_t lemac__mctbl[LEMAC_MCTBL_SIZE/sizeof(le_mcbits_t)]; + /* local copy of multicast table */ + u_char lemac__eeprom[LEMAC_EEP_SIZE]; /* local copy eeprom */ + char lemac__prodname[LEMAC_EEP_PRDNMSZ+1]; /* prodname name */ +#define lemac_lastpage le_un.un_lemac.lemac__lastpage +#define lemac_memmode le_un.un_lemac.lemac__memmode +#define lemac_membase le_un.un_lemac.lemac__membase +#define lemac_txctl le_un.un_lemac.lemac__txctl +#define lemac_txmax le_un.un_lemac.lemac__txmax +#define lemac_mctbl le_un.un_lemac.lemac__mctbl +#define lemac_eeprom le_un.un_lemac.lemac__eeprom +#define lemac_prodname le_un.un_lemac.lemac__prodname +}; +#endif /* !defined(LE_NOLEMAC) */ + +#if !defined(LE_NOLANCE) +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Start of DEC EtherWORKS II (LANCE) dependent structures + * + */ + +#include "i386/isa/ic/am7990.h" + +#ifndef LN_DOSTATS +#define LN_DOSTATS 1 +#endif + +static int depca_probe(le_softc_t *sc, const le_board_t *bd, int *msize); + +typedef struct lance_descinfo lance_descinfo_t; +typedef struct lance_ring lance_ring_t; + +typedef unsigned lance_addr_t; + +struct lance_descinfo { + caddr_t di_addr; /* address of descriptor */ + lance_addr_t di_bufaddr; /* LANCE address of buffer owned by descriptor */ + unsigned di_buflen; /* size of buffer owned by descriptor */ + struct mbuf *di_mbuf; /* mbuf being transmitted/received */ +}; + +struct lance_ring { + lance_descinfo_t *ri_first; /* Pointer to first descriptor in ring */ + lance_descinfo_t *ri_last; /* Pointer to last + 1 descriptor in ring */ + lance_descinfo_t *ri_nextin; /* Pointer to next one to be given to HOST */ + lance_descinfo_t *ri_nextout; /* Pointer to next one to be given to LANCE */ + unsigned ri_max; /* Size of Ring - 1 */ + unsigned ri_free; /* Number of free rings entires (owned by HOST) */ + lance_addr_t ri_heap; /* Start of RAM for this ring */ + lance_addr_t ri_heapend; /* End + 1 of RAM for this ring */ + lance_addr_t ri_outptr; /* Pointer to first output byte */ + unsigned ri_outsize; /* Space remaining for output */ +}; + +struct le_lance_info { + unsigned lance__csr1; /* LANCE Address of init block (low 16) */ + unsigned lance__csr2; /* LANCE Address of init block (high 8) */ + unsigned lance__csr3; /* Copy of CSR3 */ + unsigned lance__rap; /* IO Port Offset of RAP */ + unsigned lance__rdp; /* IO Port Offset of RDP */ + unsigned lance__ramoffset; /* Offset to valid LANCE RAM */ + unsigned lance__ramsize; /* Amount of RAM shared by LANCE */ + unsigned lance__rxbufsize; /* Size of a receive buffer */ + ln_initb_t lance__initb; /* local copy of LANCE initblock */ + ln_initb_t *lance__raminitb; /* copy to board's LANCE initblock (debugging) */ + ln_desc_t *lance__ramdesc; /* copy to board's LANCE descriptors (debugging) */ + lance_ring_t lance__rxinfo; /* Receive ring information */ + lance_ring_t lance__txinfo; /* Transmit ring information */ +#define lance_csr1 le_un.un_lance.lance__csr1 +#define lance_csr2 le_un.un_lance.lance__csr2 +#define lance_csr3 le_un.un_lance.lance__csr3 +#define lance_rap le_un.un_lance.lance__rap +#define lance_rdp le_un.un_lance.lance__rdp +#define lance_ramoffset le_un.un_lance.lance__ramoffset +#define lance_ramsize le_un.un_lance.lance__ramsize +#define lance_rxbufsize le_un.un_lance.lance__rxbufsize +#define lance_initb le_un.un_lance.lance__initb +#define lance_raminitb le_un.un_lance.lance__raminitb +#define lance_ramdesc le_un.un_lance.lance__ramdesc +#define lance_rxinfo le_un.un_lance.lance__rxinfo +#define lance_txinfo le_un.un_lance.lance__txinfo +}; +#endif /* !defined(LE_NOLANCE) */ + +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Start of Common Code + * + */ + +static void (*le_intrvec[NLE])(le_softc_t *sc); + +/* + * Ethernet status, per interface. + */ +struct le_softc { + struct arpcom le_ac; /* Common Ethernet/ARP Structure */ + caddr_t le_membase; /* Starting memory address (virtual) */ + unsigned le_iobase; /* Starting I/O base address */ + unsigned le_irq; /* Interrupt Request Value */ + unsigned le_flags; /* local copy of if_flags */ +#define LE_BRDCSTONLY 0x01000000 /* If only broadcast is enabled */ + u_int le_mcmask; /* bit mask for CRC-32 for multicast hash */ + le_mcbits_t *le_mctbl; /* pointer to multicast table */ + const char *le_prodname; /* product name DE20x-xx */ +#if NBPFILTER > 0 + caddr_t le_bpf; /* BPF context */ +#endif + u_char le_hwaddr[6]; /* local copy of hwaddr */ + unsigned le_scast_drops; /* singlecast drops */ + unsigned le_mcast_drops; /* multicast drops */ + unsigned le_bcast_drops; /* broadcast drops */ + union { +#if !defined(LE_NOLEMAC) + struct le_lemac_info un_lemac; /* LEMAC specific information */ +#endif +#if !defined(LE_NOLANCE) + struct le_lance_info un_lance; /* Am7990 specific information */ +#endif + } le_un; +}; + +static int le_probe(struct isa_device *dvp); +static int le_attach(struct isa_device *dvp); +static int le_ioctl(struct ifnet *ifp, int command, caddr_t data); +extern int le_intr(int unit); +static void le_input(le_softc_t *sc, caddr_t seg1, size_t total_len, + size_t len2, caddr_t seg2); +static void le_multi_filter(le_softc_t *sc); +static void le_multi_op(le_softc_t *sc, const u_char *mca, int oper_flg); +static int le_read_macaddr(le_softc_t *sc, int ioreg, int skippat); + +#define LE_CRC32_POLY 0xEDB88320UL /* CRC-32 Poly -- Little Endian */ + +struct le_board { + int (*bd_probe)(le_softc_t *sc, const le_board_t *bd, int *msize); +}; + + +le_softc_t le_softc[NLE]; + +const le_board_t le_boards[] = { +#if !defined(LE_NOLEMAC) + { lemac_probe }, /* DE20[345] */ +#endif +#if !defined(LE_NOLANCE) + { depca_probe }, /* DE{20[012],422} */ +#endif + { NULL } /* Must Be Last! */ +}; + +/* + * This tells the autoconf code how to set us up. + */ +struct isa_driver ledriver = { + le_probe, le_attach, "le", +}; + +unsigned le_intrs[NLE]; + +#define LE_ADDREQUAL(a1, a2) \ + (((u_short *)a1)[0] == ((u_short *)a2)[0] \ + || ((u_short *)a1)[1] == ((u_short *)a2)[1] \ + || ((u_short *)a1)[2] == ((u_short *)a2)[2]) +#define LE_ADDRBRDCST(a1) \ + (((u_short *)a1)[0] == 0xFFFFU \ + || ((u_short *)a1)[1] == 0xFFFFU \ + || ((u_short *)a1)[2] == 0xFFFFU) + +#define LE_INL(sc, reg) \ +({ u_long data; \ + __asm __volatile("inl %1, %0": "=a" (data): "d" ((u_short)((sc)->le_iobase + (reg)))); \ + data; }) + + +#define LE_OUTL(sc, reg, data) \ + ({__asm __volatile("outl %0, %1"::"a" ((u_long)(data)), "d" ((u_short)((sc)->le_iobase + (reg))));}) + +#define LE_INW(sc, reg) \ +({ u_short data; \ + __asm __volatile("inw %1, %0": "=a" (data): "d" ((u_short)((sc)->le_iobase + (reg)))); \ + data; }) + + +#define LE_OUTW(sc, reg, data) \ + ({__asm __volatile("outw %0, %1"::"a" ((u_short)(data)), "d" ((u_short)((sc)->le_iobase + (reg))));}) + +#define LE_INB(sc, reg) \ +({ u_char data; \ + __asm __volatile("inb %1, %0": "=a" (data): "d" ((u_short)((sc)->le_iobase + (reg)))); \ + data; }) + + +#define LE_OUTB(sc, reg, data) \ + ({__asm __volatile("outb %0, %1"::"a" ((u_char)(data)), "d" ((u_short)((sc)->le_iobase + (reg))));}) + +#define LE_IFP(sc) (&(sc)->le_ac.ac_if) + +#define MEMCPY(to, from, len) bcopy(from, to, len) +#define MEMSET(where, what, howmuch) bzero(where, howmuch) +#define MEMCMP(l, r, len) bcmp(l, r, len) + +static int +le_probe( + struct isa_device *dvp) +{ + le_softc_t *sc = &le_softc[dvp->id_unit]; + const le_board_t *bd; + int iospace; + + if (dvp->id_unit >= NLE) { + printf("%s%d not configured -- too many devices\n", + ledriver.name, dvp->id_unit); + return 0; + } + + sc->le_iobase = dvp->id_iobase; + sc->le_membase = (u_char *) dvp->id_maddr; + sc->le_irq = dvp->id_irq; + LE_IFP(sc)->if_name = ledriver.name; + LE_IFP(sc)->if_unit = dvp->id_unit; + + /* + * Find and Initialize board.. + */ + + sc->le_flags &= ~(IFF_UP|IFF_ALLMULTI); + + for (bd = le_boards; bd->bd_probe != NULL; bd++) { + if ((iospace = (*bd->bd_probe)(sc, bd, &dvp->id_msize)) != 0) { + return iospace; + } + } + printf("%s%d: no board found at 0x%x\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit, dvp->id_iobase); + return 0; +} + +static int +le_attach( + struct isa_device *dvp) +{ + le_softc_t *sc = &le_softc[dvp->id_unit]; + struct ifnet *ifp = LE_IFP(sc); + struct ifaddr *ifa = ifp->if_addrlist; + + ifp->if_mtu = ETHERMTU; + printf("%s%d: %s ethernet address %s\n", + ifp->if_name, ifp->if_unit, + sc->le_prodname, + ether_sprintf(sc->le_ac.ac_enaddr)); + + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS; +#ifdef MULTICAST + ifp->if_flags |= IFF_MULTICAST; +#endif /* MULTICAST */ + + ifp->if_output = ether_output; + ifp->if_ioctl = le_ioctl; + ifp->if_type = IFT_ETHER; + ifp->if_addrlen = 6; + ifp->if_hdrlen = 14; + +#if NBPFILTER > 0 + bpfattach(&sc->le_bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); +#endif + + if_attach(ifp); + + while (ifa && ifa->ifa_addr && ifa->ifa_addr->sa_family != AF_LINK) + ifa = ifa->ifa_next; + + if (ifa != NULL && ifa->ifa_addr != NULL) { + struct sockaddr_dl *sdl; + /* + * Provide our ether address to the higher layers + */ + sdl = (struct sockaddr_dl *) ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = 6; + sdl->sdl_slen = 0; + MEMCPY(LLADDR(sdl), sc->le_ac.ac_enaddr, 6); + } + return 1; +} + +int +le_intr( + int unit) +{ + int s = splimp(); + + le_intrs[unit]++; + (*le_intrvec[unit])(&le_softc[unit]); + + splx(s); + return unit; +} + +#define LE_XTRA 0 + +static void +le_input( + le_softc_t *sc, + caddr_t seg1, + size_t total_len, + size_t len1, + caddr_t seg2) +{ + struct ether_header eh; + struct mbuf *m; + + if (total_len - sizeof(eh) > ETHERMTU + || total_len - sizeof(eh) < ETHERMIN) { + LE_IFP(sc)->if_ierrors++; + return; + } + MEMCPY(&eh, seg1, sizeof(eh)); + eh.ether_type = ntohs(eh.ether_type); + +#if NBPFILTER > 0 + if (sc->le_bpf != NULL && seg2 == NULL) { + bpf_tap(sc->le_bpf, seg1, total_len); + /* + * If this is single cast but not to us + * drop it! + */ + if ((eh.ether_dhost[0] & 1) == 0) { + if (!LE_ADDREQUAL(eh.ether_dhost, sc->le_ac.ac_enaddr)) { + sc->le_scast_drops++; + return; + } + } else if ((sc->le_flags & IFF_MULTICAST) == 0) { + sc->le_mcast_drops++; + return; + } else if (sc->le_flags & LE_BRDCSTONLY) { + if (!LE_ADDRBRDCST(eh.ether_dhost)) { + sc->le_bcast_drops++; + return; + } + } + } +#endif + seg1 += sizeof(eh); total_len -= sizeof(eh); len1 -= sizeof(eh); + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + LE_IFP(sc)->if_ierrors++; + return; + } + m->m_pkthdr.len = total_len; + m->m_pkthdr.rcvif = LE_IFP(sc); + if (total_len + LE_XTRA > MHLEN /* >= MINCLSIZE */) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + LE_IFP(sc)->if_ierrors++; + return; + } + } else if (total_len + LE_XTRA > MHLEN && MINCLSIZE == (MHLEN+MLEN)) { + MGET(m->m_next, M_DONTWAIT, MT_DATA); + if (m->m_next == NULL) { + m_free(m); + LE_IFP(sc)->if_ierrors++; + return; + } + m->m_next->m_len = total_len - MHLEN - LE_XTRA; + len1 = total_len = MHLEN - LE_XTRA; + MEMCPY(mtod(m->m_next, caddr_t), &seg1[MHLEN-LE_XTRA], m->m_next->m_len); + } else if (total_len + LE_XTRA > MHLEN) { + panic("le_input: pkt of unknown length"); + } + m->m_data += LE_XTRA; + m->m_len = total_len; + MEMCPY(mtod(m, caddr_t), seg1, len1); + if (seg2 != NULL) + MEMCPY(mtod(m, caddr_t) + len1, seg2, total_len - len1); +#if NBPFILTER > 0 + if (sc->le_bpf != NULL && seg2 != NULL) { + bpf_mtap(sc->le_bpf, m); + /* + * If this is single cast but not to us + * drop it! + */ + if ((eh.ether_dhost[0] & 1) == 0) { + if (!LE_ADDREQUAL(eh.ether_dhost, sc->le_ac.ac_enaddr)) { + sc->le_scast_drops++; + m_freem(m); + return; + } + } else if ((sc->le_flags & IFF_MULTICAST) == 0) { + sc->le_mcast_drops++; + m_freem(m); + return; + } else if (sc->le_flags & LE_BRDCSTONLY) { + if (!LE_ADDRBRDCST(eh.ether_dhost)) { + sc->le_bcast_drops++; + m_freem(m); + return; + } + } + } +#endif + ether_input(LE_IFP(sc), &eh, m); +} + +static int +le_ioctl( + struct ifnet *ifp, + int cmd, + caddr_t data) +{ + le_softc_t *sc = &le_softc[ifp->if_unit]; + int s, error = 0; + + if ((sc->le_flags & IFF_UP) == 0) + return EIO; + + s = splimp(); + + switch (cmd) { + case SIOCSIFADDR: { + struct ifaddr *ifa = (struct ifaddr *)data; + + ifp->if_flags |= IFF_UP; + switch(ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: { + (*ifp->if_init)(ifp->if_unit); + ((struct arpcom *)ifp)->ac_ipaddr = IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); + break; + } +#endif /* INET */ + +#ifdef NS + /* This magic copied from if_is.c; I don't use XNS, + * so I have no way of telling if this actually + * works or not. + */ + case AF_NS: { + struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + if (ns_nullhost(*ina)) { + ina->x_host = *(union ns_host *)(sc->le_ac.ac_enaddr); + } else { + ifp->if_flags &= ~IFF_RUNNING; + bcopy((caddr_t)ina->x_host.c_host, + (caddr_t)sc->le_ac.ac_enaddr, + sizeof sc->le_ac.ac_enaddr); + } + + (*ifp->if_init)(ifp->if_unit); + break; + } +#endif /* NS */ + + default: { + (*ifp->if_init)(ifp->if_unit); + break; + } + } + break; + } + + case SIOCSIFFLAGS: { + (*ifp->if_init)(ifp->if_unit); + break; + } + +#ifdef MULTICAST + case SIOCADDMULTI: + case SIOCDELMULTI: { + /* + * Update multicast listeners + */ + if (cmd == SIOCADDMULTI) + error = ether_addmulti((struct ifreq *)data, &sc->le_ac); + else + error = ether_delmulti((struct ifreq *)data, &sc->le_ac); + + if (error == ENETRESET) { + /* reset multicast filtering */ + (*ifp->if_init)(ifp->if_unit); + error = 0; + } + break; + } + +#endif /* MULTICAST */ + + default: { + error = EINVAL; + } + } + + splx(s); + return error; +} + +/* + * This is the standard method of reading the DEC Address ROMS. + * I don't understand it but it does work. + */ +static int +le_read_macaddr( + le_softc_t *sc, + int ioreg, + int skippat) +{ + int cksum, rom_cksum; + + if (!skippat) { + int idx, idx2, found, octet; + static u_char testpat[] = { 0xFF, 0, 0x55, 0xAA, 0xFF, 0, 0x55, 0xAA }; + idx2 = found = 0; + + for (idx = 0; idx < 32; idx++) { + octet = LE_INB(sc, ioreg); + + if (octet == testpat[idx2]) { + if (++idx2 == sizeof testpat) { + ++found; + break; + } + } else { + idx2 = 0; + } + } + + if (!found) + return -1; + } + + cksum = 0; + sc->le_hwaddr[0] = LE_INB(sc, ioreg); + sc->le_hwaddr[1] = LE_INB(sc, ioreg); + + cksum = *(u_short *) &sc->le_hwaddr[0]; + + sc->le_hwaddr[2] = LE_INB(sc, ioreg); + sc->le_hwaddr[3] = LE_INB(sc, ioreg); + cksum *= 2; + if (cksum > 65535) cksum -= 65535; + cksum += *(u_short *) &sc->le_hwaddr[2]; + if (cksum > 65535) cksum -= 65535; + + sc->le_hwaddr[4] = LE_INB(sc, ioreg); + sc->le_hwaddr[5] = LE_INB(sc, ioreg); + cksum *= 2; + if (cksum > 65535) cksum -= 65535; + cksum += *(u_short *) &sc->le_hwaddr[4]; + if (cksum >= 65535) cksum -= 65535; + + rom_cksum = LE_INB(sc, ioreg); + rom_cksum |= LE_INB(sc, ioreg) << 8; + + if (cksum != rom_cksum) + return -1; + return 0; +} + +static void +le_multi_filter( + le_softc_t *sc) +{ +#ifdef MULTICAST + struct ether_multistep step; + struct ether_multi *enm; +#endif +#ifdef ISO + extern char all_es_snpa[]; +#endif + + MEMSET(sc->le_mctbl, 0, (sc->le_mcmask + 1) / 8); + + if (LE_IFP(sc)->if_flags & IFF_ALLMULTI) { + sc->le_flags |= IFF_MULTICAST|IFF_ALLMULTI; + return; + } + sc->le_flags &= ~IFF_MULTICAST; + if (sc->le_ac.ac_ipaddr.s_addr != 0) { + le_multi_op(sc, etherbroadcastaddr, TRUE); + sc->le_flags |= LE_BRDCSTONLY|IFF_MULTICAST; + } +#ifdef ISO + le_multi_op(sc, all_es_snpa, TRUE); +#endif + +#ifdef MULTICAST + ETHER_FIRST_MULTI(step, &sc->le_ac, enm); + if (enm != NULL) + sc->le_flags |= IFF_MULTICAST; + while (enm != NULL) { + if (MEMCMP(enm->enm_addrlo, enm->enm_addrhi, 6) != 0) { + sc->le_flags |= IFF_ALLMULTI; + return; + } + le_multi_op(sc, enm->enm_addrlo, TRUE); + ETHER_NEXT_MULTI(step, enm); + sc->le_flags &= ~LE_BRDCSTONLY; + } + sc->le_flags &= ~IFF_ALLMULTI; +#endif +} + +static void +le_multi_op( + le_softc_t *sc, + const u_char *mca, + int enable) +{ + u_int idx, bit, data, crc = 0xFFFFFFFFUL; + +#ifdef __alpha + for (data = *(__unaligned u_long *) mca, bit = 0; bit < 48; bit++, data >>= +1) + crc = (crc >> 1) ^ (((crc ^ data) & 1) ? LE_CRC32_POLY : 0); +#else + for (idx = 0; idx < 6; idx++) + for (data = *mca++, bit = 0; bit < 8; bit++, data >>= 1) + crc = (crc >> 1) ^ (((crc ^ data) & 1) ? LE_CRC32_POLY : 0); +#endif + /* + * The following two line convert the N bit index into a longword index + * and a longword mask. + */ + crc &= sc->le_mcmask; + bit = 1 << (crc & (LE_MC_NBPW -1)); + idx = crc >> (LE_MC_NBPW_LOG2); + + /* + * Set or clear hash filter bit in our table. + */ + if (enable) { + sc->le_mctbl[idx] |= bit; /* Set Bit */ + } else { + sc->le_mctbl[idx] &= ~bit; /* Clear Bit */ + } +} + +#if !defined(LE_NOLEMAC) +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Start of DEC EtherWORKS III (LEMAC) dependent code + * + */ + +#define LEMAC_INTR_ENABLE(sc) \ + LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) | LEMAC_IC_ALL) + +#define LEMAC_INTR_DISABLE(sc) \ + LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) & ~LEMAC_IC_ALL) + +#define LEMAC_64K_MODE(mbase) (((mbase) >= 0x0A) && ((mbase) <= 0x0F)) +#define LEMAC_32K_MODE(mbase) (((mbase) >= 0x14) && ((mbase) <= 0x1F)) +#define LEMAC_2K_MODE(mbase) ( (mbase) >= 0x40) + +static void lemac_init(int unit); +static void lemac_start(struct ifnet *ifp); +#if __FreeBSD__ > 1 +static void lemac_reset(int unit); +#else +static void lemac_reset(int unit, int dummy); +#endif +static void lemac_intr(le_softc_t *sc); +static void lemac_rne_intr(le_softc_t *sc); +static void lemac_tne_intr(le_softc_t *sc); +static void lemac_txd_intr(le_softc_t *sc, unsigned cs_value); +static void lemac_rxd_intr(le_softc_t *sc, unsigned cs_value); +static int lemac_read_eeprom(le_softc_t *sc); +static void lemac_init_adapmem(le_softc_t *sc); + +static const le_mcbits_t lemac_allmulti_mctbl[16] = { + 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, + 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, + 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, + 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, +}; +/* + * An IRQ mapping table. Less space than switch statement. + */ +static const int lemac_irqs[] = { IRQ5, IRQ10, IRQ11, IRQ15 }; + +/* + * Some tuning/monitoring variables. + */ +unsigned lemac_deftxmax = 16; /* see lemac_max above */ +unsigned lemac_txnospc = 0; /* total # of tranmit starvations */ + +unsigned lemac_tne_intrs = 0; /* total # of tranmit done intrs */ +unsigned lemac_rne_intrs = 0; /* total # of receive done intrs */ +unsigned lemac_txd_intrs = 0; /* total # of tranmit error intrs */ +unsigned lemac_rxd_intrs = 0; /* total # of receive error intrs */ + + +static int +lemac_probe( + le_softc_t *sc, + const le_board_t *bd, + int *msize) +{ + int irq, portval, cksum; + long membase; + + LE_OUTB(sc, LEMAC_REG_IOP, LEMAC_IOP_EEINIT); + DELAY(LEMAC_EEP_DELAY); + + /* + * Read Ethernet address if card is present. + */ + if (le_read_macaddr(sc, LEMAC_REG_APD, 0) < 0) + return 0; + + MEMCPY(sc->le_ac.ac_enaddr, sc->le_hwaddr, 6); + /* + * Clear interrupts and set IRQ. + */ + + portval = LE_INB(sc, LEMAC_REG_IC) & LEMAC_IC_IRQMSK; + irq = lemac_irqs[portval >> 5]; + LE_OUTB(sc, LEMAC_REG_IC, portval); + + /* + * Make sure settings match. + */ + + if (irq != sc->le_irq) { + printf("%s%d: lemac configuration error: expected IRQ 0x%x actual 0x%x\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit, sc->le_irq, irq); + return 0; + } + + /* + * Try to reset the unit + */ + sc->lemac_memmode = 2; +#if __FreeBSD__ > 1 + lemac_reset(LE_IFP(sc)->if_unit); +#else + lemac_reset(LE_IFP(sc)->if_unit, 0); +#endif + if ((sc->le_flags & IFF_UP) == 0) + return 0; + + /* + * Check for correct memory base configuration. + */ + if (vtophys(sc->le_membase) != sc->lemac_membase) { + printf("%s%d: lemac configuration error: expected iomem 0x%x actual 0x%x\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit, + vtophys(sc->le_membase), sc->lemac_membase); + return 0; + } + + LE_IFP(sc)->if_init = lemac_init; + LE_IFP(sc)->if_start = lemac_start; + LE_IFP(sc)->if_reset = lemac_reset; + sc->le_prodname = sc->lemac_prodname; + sc->le_mctbl = sc->lemac_mctbl; + sc->le_mcmask = (1 << LEMAC_MCTBL_BITS) - 1; + sc->lemac_txmax = lemac_deftxmax; + *msize = 2048; + le_intrvec[LE_IFP(sc)->if_unit] = lemac_intr; + + return LEMAC_IOSPACE; +} + +/* + * Do a hard reset of the board; + */ +static void +#if __FreeBSD__ > 1 +lemac_reset(int unit) +#else +lemac_reset(int unit, int dummy) +#endif +{ + le_softc_t *sc = &le_softc[unit]; + int portval, cksum; + + /* + * Initialize board.. + */ + + sc->le_flags &= IFF_UP; + LE_IFP(sc)->if_flags &= ~IFF_OACTIVE; + LEMAC_INTR_DISABLE(sc); + + LE_OUTB(sc, LEMAC_REG_IOP, LEMAC_IOP_EEINIT); + DELAY(LEMAC_EEP_DELAY); + + /* Disable Interrupts */ + /* LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) & ICR_IRQ_SEL); */ + + /* + * Read EEPROM information. NOTE - the placement of this function + * is important because functions hereafter may rely on information + * read from the EEPROM. + */ + if ((cksum = lemac_read_eeprom(sc)) != LEMAC_EEP_CKSUM) { + printf("%s%d: reset: EEPROM checksum failed (0x%x)\n", + LE_IFP(sc)->if_unit, LE_IFP(sc)->if_unit, cksum); + return; + } + + /* + * Force to 2K mode if not already configured. + */ + + portval = LE_INB(sc, LEMAC_REG_MBR); + if (!LEMAC_2K_MODE(portval)) { + if (LEMAC_64K_MODE(portval)) { + portval = (((portval * 2) & 0xF) << 4); + sc->lemac_memmode = 64; + } else if (LEMAC_32K_MODE(portval)) { + portval = ((portval & 0xF) << 4); + sc->lemac_memmode = 32; + } + LE_OUTB(sc, LEMAC_REG_MBR, portval); + } + sc->lemac_membase = portval * (2 * 1024) + (512 * 1024); + + /* + * Initialize Free Memory Queue, Init mcast table with broadcast. + */ + + lemac_init_adapmem(sc); + sc->le_flags |= IFF_UP; + return; +} + +static void +lemac_init( + int unit) +{ + le_softc_t *sc = &le_softc[unit]; + int regval, s; + + if ((sc->le_flags & IFF_UP) == 0) + return; + + s = splimp(); + + /* + * If the interface has the up flag + */ + if (LE_IFP(sc)->if_flags & IFF_UP) { + int saved_cs = LE_INB(sc, LEMAC_REG_CS); + LE_OUTB(sc, LEMAC_REG_CS, saved_cs | (LEMAC_CS_TXD | LEMAC_CS_RXD)); + LE_OUTB(sc, LEMAC_REG_PA0, sc->le_ac.ac_enaddr[0]); + LE_OUTB(sc, LEMAC_REG_PA1, sc->le_ac.ac_enaddr[1]); + LE_OUTB(sc, LEMAC_REG_PA2, sc->le_ac.ac_enaddr[2]); + LE_OUTB(sc, LEMAC_REG_PA3, sc->le_ac.ac_enaddr[3]); + LE_OUTB(sc, LEMAC_REG_PA4, sc->le_ac.ac_enaddr[4]); + LE_OUTB(sc, LEMAC_REG_PA5, sc->le_ac.ac_enaddr[5]); + + LE_OUTB(sc, LEMAC_REG_IC, LE_INB(sc, LEMAC_REG_IC) | LEMAC_IC_IE); + + if (LE_IFP(sc)->if_flags & IFF_PROMISC) { + LE_OUTB(sc, LEMAC_REG_CS, LEMAC_CS_MCE | LEMAC_CS_PME); + } else { + LEMAC_INTR_DISABLE(sc); + le_multi_filter(sc); + LE_OUTB(sc, LEMAC_REG_MPN, 0); + if ((sc->le_flags | LE_IFP(sc)->if_flags) & IFF_ALLMULTI) { + MEMCPY(&sc->le_membase[LEMAC_MCTBL_OFF], lemac_allmulti_mctbl, sizeof(lemac_allmulti_mctbl)); + } else { + MEMCPY(&sc->le_membase[LEMAC_MCTBL_OFF], sc->lemac_mctbl, sizeof(sc->lemac_mctbl)); + } + LE_OUTB(sc, LEMAC_REG_CS, LEMAC_CS_MCE); + } + + LE_OUTB(sc, LEMAC_REG_CTL, LE_INB(sc, LEMAC_REG_CTL) ^ LEMAC_CTL_LED); + + LEMAC_INTR_ENABLE(sc); + LE_IFP(sc)->if_flags |= IFF_RUNNING; + } else { + LE_OUTB(sc, LEMAC_REG_CS, LEMAC_CS_RXD|LEMAC_CS_TXD); + + LEMAC_INTR_DISABLE(sc); + LE_IFP(sc)->if_flags &= ~IFF_RUNNING; + } + splx(s); +} + +/* + * What to do upon receipt of an interrupt. + */ +static void +lemac_intr( + le_softc_t *sc) +{ + int cs_value; + + LEMAC_INTR_DISABLE(sc); /* Mask interrupts */ + + /* + * Determine cause of interrupt. Receive events take + * priority over Transmit. + */ + + cs_value = LE_INB(sc, LEMAC_REG_CS); + + /* + * Check for Receive Queue not being empty. + * Check for Transmit Done Queue not being empty. + */ + + if (cs_value & LEMAC_CS_RNE) + lemac_rne_intr(sc); + if (cs_value & LEMAC_CS_TNE) + lemac_tne_intr(sc); + + /* + * Check for Transmitter Disabled. + * Check for Receiver Disabled. + */ + + if (cs_value & LEMAC_CS_TXD) + lemac_txd_intr(sc, cs_value); + if (cs_value & LEMAC_CS_RXD) + lemac_rxd_intr(sc, cs_value); + + /* + * Toggle LED and unmask interrupts. + */ + + LE_OUTB(sc, LEMAC_REG_CTL, LE_INB(sc, LEMAC_REG_CTL) ^ LEMAC_CTL_LED); + LEMAC_INTR_ENABLE(sc); /* Unmask interrupts */ +} + +static void +lemac_rne_intr( + le_softc_t *sc) +{ + int rxcount, rxlen, rxpg; + struct mbuf *m; + u_char *rxptr; + + lemac_rne_intrs++; + rxcount = LE_INB(sc, LEMAC_REG_RQC); + while (rxcount--) { + rxpg = LE_INB(sc, LEMAC_REG_RQ); + LE_OUTB(sc, LEMAC_REG_MPN, rxpg); + + rxptr = sc->le_membase; + LE_IFP(sc)->if_ipackets++; + if (*rxptr & LEMAC_RX_OK) { + + /* + * Get receive length - subtract out checksum. + */ + + rxlen = ((*(u_int *)rxptr >> 8) & 0x7FF) - 4; + le_input(sc, rxptr + sizeof(u_int), rxlen, rxlen, NULL); + } else { /* end if (*rxptr & LEMAC_RX_OK) */ + LE_IFP(sc)->if_ierrors++; + } +next: + LE_OUTB(sc, LEMAC_REG_FMQ, rxpg); /* Return this page to Free Memory Queue */ + } /* end while (recv_count--) */ + + return; +} + +static void +lemac_rxd_intr( + le_softc_t *sc, + unsigned cs_value) +{ + /* + * Handle CS_RXD (Receiver disabled) here. + * + * Check Free Memory Queue Count. If not equal to zero + * then just turn Receiver back on. If it is equal to + * zero then check to see if transmitter is disabled. + * Process transmit TXD loop once more. If all else + * fails then do software init (0xC0 to EEPROM Init) + * and rebuild Free Memory Queue. + */ + + lemac_rxd_intrs++; + + /* + * Re-enable Receiver. + */ + + cs_value &= ~LEMAC_CS_RXD; + LE_OUTB(sc, LEMAC_REG_CS, cs_value); + + if (LE_INB(sc, LEMAC_REG_FMC) > 0) + return; + + if (cs_value & LEMAC_CS_TXD) + lemac_txd_intr(sc, cs_value); + + if ((LE_INB(sc, LEMAC_REG_CS) & LEMAC_CS_RXD) == 0) + return; + + printf("%s%d: fatal RXD error, attempting recovery\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit); + +#if __FreeBSD__ > 1 + lemac_reset(LE_IFP(sc)->if_unit); +#else + lemac_reset(LE_IFP(sc)->if_unit, 0); +#endif + if (sc->le_flags & IFF_UP) { + lemac_init(LE_IFP(sc)->if_unit); + return; + } + + /* + * Error during initializion. Mark card as disabled. + */ + printf("%s%d: recovery failed -- board disabled\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit); + return; +} + +static void +lemac_start( + struct ifnet *ifp) +{ + le_softc_t *sc = (le_softc_t *) ifp; + struct ifqueue *ifq = &ifp->if_snd; + + if ((ifp->if_flags & IFF_RUNNING) == 0) + return; + + LEMAC_INTR_DISABLE(sc); + + while (ifq->ifq_head != NULL) { + struct mbuf *m; + int tx_pg; + u_int txhdr, txoff; + + if (LE_INB(sc, LEMAC_REG_TQC) >= sc->lemac_txmax) { + ifp->if_flags |= IFF_OACTIVE; + break; + } + + tx_pg = LE_INB(sc, LEMAC_REG_FMQ); /* get free memory page */ + /* + * Check for good transmit page. + */ + if (tx_pg == 0 || tx_pg > sc->lemac_lastpage) { + lemac_txnospc++; + ifp->if_flags |= IFF_OACTIVE; + break; + } + + IF_DEQUEUE(ifq, m); + LE_OUTB(sc, LEMAC_REG_MPN, tx_pg); /* Shift 2K window. */ + + /* + * The first four bytes of each transmit buffer are for + * control information. The first byte is the control + * byte, then the length (why not word aligned??), then + * the off to the buffer. + */ + + txoff = (mtod(m, u_int) & (sizeof(u_long) - 1)) + LEMAC_TX_HDRSZ; + txhdr = sc->lemac_txctl | (m->m_pkthdr.len << 8) | (txoff << 24); + *(u_int *) sc->le_membase = txhdr; + + /* + * Copy the packet to the board + */ + + m_copydata(m, 0, m->m_pkthdr.len, sc->le_membase + txoff); + + LE_OUTB(sc, LEMAC_REG_TQ, tx_pg); /* tell chip to transmit this packet */ + m_freem(m); /* free the mbuf */ + } + LEMAC_INTR_ENABLE(sc); +} + +static void +lemac_tne_intr( + le_softc_t *sc) +{ + int txsts, txcount = LE_INB(sc, LEMAC_REG_TDC); + + lemac_tne_intrs++; + while (txcount--) { + txsts = LE_INB(sc, LEMAC_REG_TDQ); + LE_IFP(sc)->if_opackets++; /* another one done */ + if ((txsts & LEMAC_TDQ_COL) != LEMAC_TDQ_NOCOL) + LE_IFP(sc)->if_collisions++; + } + LE_IFP(sc)->if_flags &= ~IFF_OACTIVE; + lemac_start(LE_IFP(sc)); +} + +static void +lemac_txd_intr( + le_softc_t *sc, + unsigned cs_value) +{ + /* + * Read transmit status, remove transmit buffer from + * transmit queue and place on free memory queue, + * then reset transmitter. + * Increment appropriate counters. + */ + + lemac_txd_intrs++; + LE_IFP(sc)->if_oerrors++; + if (LE_INB(sc, LEMAC_REG_TS) & LEMAC_TS_ECL) + LE_IFP(sc)->if_collisions++; + LE_IFP(sc)->if_flags &= ~IFF_OACTIVE; + + LE_OUTB(sc, LEMAC_REG_FMQ, LE_INB(sc, LEMAC_REG_TQ)); + /* Get Page number and write it back out */ + + LE_OUTB(sc, LEMAC_REG_CS, cs_value & ~LEMAC_CS_TXD); + /* Turn back on transmitter */ + return; +} + +static int +lemac_read_eeprom( + le_softc_t *sc) +{ + int word_off, cksum; + + u_char *ep; + + cksum = 0; + ep = sc->lemac_eeprom; + for (word_off = 0; word_off < LEMAC_EEP_SIZE / 2; word_off++) { + LE_OUTB(sc, LEMAC_REG_PI1, word_off); + LE_OUTB(sc, LEMAC_REG_IOP, LEMAC_IOP_EEREAD); + + DELAY(LEMAC_EEP_DELAY); + + *ep = LE_INB(sc, LEMAC_REG_EE1); cksum += *ep++; + *ep = LE_INB(sc, LEMAC_REG_EE2); cksum += *ep++; + } + + /* + * Set up Transmit Control Byte for use later during transmit. + */ + + sc->lemac_txctl |= LEMAC_TX_FLAGS; + + if ((sc->lemac_eeprom[LEMAC_EEP_SWFLAGS] & LEMAC_EEP_SW_SQE) == 0) + sc->lemac_txctl &= ~LEMAC_TX_SQE; + + if (sc->lemac_eeprom[LEMAC_EEP_SWFLAGS] & LEMAC_EEP_SW_LAB) + sc->lemac_txctl |= LEMAC_TX_LAB; + + MEMCPY(sc->lemac_prodname, &sc->lemac_eeprom[LEMAC_EEP_PRDNM], LEMAC_EEP_PRDNMSZ); + sc->lemac_prodname[LEMAC_EEP_PRDNMSZ] = '\0'; + + return cksum % 256; +} + +static void +lemac_init_adapmem( + le_softc_t *sc) +{ + int pg, conf; + + conf = LE_INB(sc, LEMAC_REG_CNF); + + if ((sc->lemac_eeprom[LEMAC_EEP_SETUP] & LEMAC_EEP_ST_DRAM) == 0) { + sc->lemac_lastpage = 63; + conf &= ~LEMAC_CNF_DRAM; + } else { + sc->lemac_lastpage = 127; + conf |= LEMAC_CNF_DRAM; + } + + LE_OUTB(sc, LEMAC_REG_CNF, conf); + + for (pg = 1; pg <= sc->lemac_lastpage; pg++) + LE_OUTB(sc, LEMAC_REG_FMQ, pg); + + return; +} +#endif /* !defined(LE_NOLEMAC) */ + +#if !defined(LE_NOLANCE) +/* + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * + * Start of DEPCA (DE200/DE201/DE202/DE422 etal) support. + * + */ +static void depca_intr(le_softc_t *sc); +static int lance_init_adapmem(le_softc_t *sc); +static int lance_init_ring(le_softc_t *sc, ln_ring_t *rp, lance_ring_t *ri, + unsigned ndescs, unsigned bufoffset, + unsigned descoffset); +static void lance_init(int unit); +#if __FreeBSD__ > 1 +static void lance_reset(int unit); +#else +static void lance_reset(int unit, int dummy); +#endif +static void lance_intr(le_softc_t *sc); +static int lance_rx_intr(le_softc_t *sc); +static void lance_start(struct ifnet *ifp); +static int lance_tx_intr(le_softc_t *sc); + +#define LN_BUFSIZE /* 380 */ 304 /* 1520 / 4 */ +#define LN_TXDESC_RATIO 2048 +#define LN_DESC_MAX 128 + +#if LN_DOSTATS +struct { + unsigned lance_rx_misses; + unsigned lance_rx_badcrc; + unsigned lance_rx_badalign; + unsigned lance_rx_badframe; + unsigned lance_rx_buferror; + unsigned lance_tx_deferred; + unsigned lance_tx_single_collisions; + unsigned lance_tx_multiple_collisions; + unsigned lance_tx_excessive_collisions; + unsigned lance_tx_late_collisions; + + unsigned lance_memory_errors; + unsigned lance_inits; + unsigned lance_tx_intrs; + unsigned lance_tx_nospc[2]; + unsigned lance_tx_drains[2]; + unsigned lance_tx_orphaned; + unsigned lance_tx_adoptions; + unsigned lance_tx_emptied; + unsigned lance_tx_deftxint; + unsigned lance_tx_buferror; + unsigned lance_high_txoutptr; + unsigned lance_low_txheapsize; + unsigned lance_low_txfree; + unsigned lance_tx_intr_hidescs; + /* unsigned lance_tx_intr_descs[LN_DESC_MAX]; */ + + unsigned lance_rx_intrs; + unsigned lance_rx_badsop; + unsigned lance_rx_contig; + unsigned lance_rx_noncontig; + unsigned lance_rx_intr_hidescs; + unsigned lance_rx_ndescs[4096 / LN_BUFSIZE]; + /* unsigned lance_rx_intr_descs[LN_DESC_MAX]; */ +} lance_stats; + +#define LN_STAT(stat) (lance_stats.lance_ ## stat) +#define LN_MINSTAT(stat, val) (LN_STAT(stat > (val)) ? LN_STAT(stat = (val)) : 0) +#define LN_MAXSTAT(stat, val) (LN_STAT(stat < (val)) ? LN_STAT(stat = (val)) : 0) + +#else +#define LN_STAT(stat) 0 +#define LN_MINSTAT(stat, val) 0 +#define LN_MAXSTAT(stat, val) 0 +#endif + +#define LN_SELCSR(sc, csrno) (LE_OUTW(sc, sc->lance_rap, csrno)) +#define LN_INQCSR(sc) (LE_INW(sc, sc->lance_rap)) + +#define LN_WRCSR(sc, val) (LE_OUTW(sc, sc->lance_rdp, val)) +#define LN_RDCSR(sc) (LE_INW(sc, sc->lance_rdp)) + + +#define LN_ZERO(sc, vaddr, len) bzero(vaddr, len) +#define LN_COPYTO(sc, from, to, len) bcopy(from, to, len) + +#define LN_SETFLAG(sc, vaddr, val) \ + (((volatile u_char *) vaddr)[3] = (val)) + +#define LN_PUTDESC(sc, desc, vaddr) \ + (((volatile u_short *) vaddr)[0] = ((u_short *) desc)[0], \ + ((volatile u_short *) vaddr)[2] = ((u_short *) desc)[2], \ + ((volatile u_short *) vaddr)[1] = ((u_short *) desc)[1]) + +/* + * Only get the descriptor flags and length/status. All else + * read-only. + */ +#define LN_GETDESC(sc, desc, vaddr) \ + (((u_short *) desc)[1] = ((volatile u_short *) vaddr)[1], \ + ((u_short *) desc)[3] = ((volatile u_short *) vaddr)[3]) + + +/* + * These definitions are specific to the DEC "DEPCA-style" NICs. + * (DEPCA, DE10x, DE20[012], DE422) + * + */ +#define DEPCA_REG_NICSR 0 /* (RW;16) NI Control / Status */ +#define DEPCA_REG_RDP 4 /* (RW:16) LANCE RDP (data) register */ +#define DEPCA_REG_RAP 6 /* (RW:16) LANCE RAP (address) register */ +#define DEPCA_REG_ADDRROM 12 /* (R : 8) DEPCA Ethernet Address ROM */ +#define DEPCA_IOSPACE 16 /* DEPCAs use 16 bytes of IO space */ + +#define DEPCA_NICSR_LED 0x0001 /* Light the LED on the back of the DEPCA */ +#define DEPCA_NICSR_ENABINTR 0x0002 /* Enable Interrupts */ +#define DEPCA_NICSR_MASKINTR 0x0004 /* Mask Interrupts */ +#define DEPCA_NICSR_AAC 0x0008 /* Address Counter Clear */ +#define DEPCA_NICSR_REMOTEBOOT 0x0010 /* Remote Boot Enabled (ignored) */ +#define DEPCA_NICSR_32KRAM 0x0020 /* DEPCA LANCE RAM size 64K (C) / 32K (S) */ +#define DEPCA_NICSR_LOW32K 0x0040 /* Bank Select (A15 = !This Bit) */ +#define DEPCA_NICSR_SHE 0x0080 /* Shared RAM Enabled (ie hide ROM) */ +#define DEPCA_NICSR_BOOTTMO 0x0100 /* Remote Boot Timeout (ignored) */ + +#define DEPCA_RDNICSR(sc) (LE_INW(sc, DEPCA_REG_NICSR)) +#define DEPCA_WRNICSR(sc, val) (LE_OUTW(sc, DEPCA_REG_NICSR, val)) + +#define DEPCA_IDSTR_OFFSET 0xC006 /* ID String Offset */ + +#define DEPCA_REG_EISAID 0x80 +#define DEPCA_EISAID_MASK 0xf0ffffff +#define DEPCA_EISAID_DE422 0x2042A310 + +typedef enum { + DEPCA_CLASSIC, + DEPCA_DE100, DEPCA_DE101, + DEPCA_EE100, + DEPCA_DE200, DEPCA_DE201, DEPCA_DE202, + DEPCA_DE422, + DEPCA_UNKNOWN +} depca_t; + +static const char *depca_signatures[] = { + "DEPCA", + "DE100", "DE101", + "EE100", + "DE200", "DE201", "DE202", + "DE422", + NULL +}; + +static int +depca_probe( + le_softc_t *sc, + const le_board_t *bd, + int *msize) +{ + unsigned nicsr, idx, idstr_offset = DEPCA_IDSTR_OFFSET; + + /* + * Find out how memory we are dealing with. Adjust + * the ID string offset approriately if we are at + * 32K. Make sure the ROM is enabled. + */ + nicsr = DEPCA_RDNICSR(sc); + nicsr &= ~(DEPCA_NICSR_SHE|DEPCA_NICSR_LED|DEPCA_NICSR_ENABINTR); + + if (nicsr & DEPCA_NICSR_32KRAM) { + /* + * Make we are going to read the upper + * 32K so we do read the ROM. + */ + sc->lance_ramsize = 32 * 1024; + nicsr &= ~DEPCA_NICSR_LOW32K; + sc->lance_ramoffset = 32 * 1024; + idstr_offset -= sc->lance_ramsize; + } else { + sc->lance_ramsize = 64 * 1024; + sc->lance_ramoffset = 0; + } + DEPCA_WRNICSR(sc, nicsr); + + sc->le_prodname = NULL; + for (idx = 0; depca_signatures[idx] != NULL; idx++) { + if (bcmp(depca_signatures[idx], sc->le_membase + idstr_offset, 5) == 0) { + sc->le_prodname = depca_signatures[idx]; + break; + } + } + + if (sc->le_prodname == NULL) { + /* + * Try to get the EISA device if it's a DE422. + */ + if (sc->le_iobase > 0x1000 && (sc->le_iobase & 0x0F00) == 0x0C00 + && (LE_INL(sc, DEPCA_REG_EISAID) & DEPCA_EISAID_MASK) + == DEPCA_EISAID_DE422) { + sc->le_prodname = "DE422"; + } else { + return 0; + } + } + /* + * Try to read the address ROM. + * Stop the LANCE, reset the Address ROM Counter (AAC), + * read the NICSR to "clock" in the reset, and then + * re-enable the Address ROM Counter. Now read the + * address ROM. + */ + sc->lance_rdp = DEPCA_REG_RDP; + sc->lance_rap = DEPCA_REG_RAP; + sc->lance_csr3 = LN_CSR3_ALE; + sc->le_mctbl = sc->lance_initb.ln_multi_mask; + sc->le_mcmask = LN_MC_MASK; + LN_SELCSR(sc, LN_CSR0); + LN_WRCSR(sc, LN_CSR0_STOP); + + if (idx < DEPCA_DE200) { + DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) & ~DEPCA_NICSR_AAC); + DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) | DEPCA_NICSR_AAC); + } + + if (le_read_macaddr(sc, DEPCA_REG_ADDRROM, 0) < 0) + return 0; + + MEMCPY(sc->le_ac.ac_enaddr, sc->le_hwaddr, 6); + /* + * Renable shared RAM. + */ + DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) | DEPCA_NICSR_SHE); + + le_intrvec[LE_IFP(sc)->if_unit] = depca_intr; + if (!lance_init_adapmem(sc)) + return 0; + + DEPCA_WRNICSR(sc, DEPCA_NICSR_SHE | DEPCA_NICSR_ENABINTR); +#if __FreeBSD__ > 1 + lance_reset(LE_IFP(sc)->if_unit); +#else + lance_reset(LE_IFP(sc)->if_unit, 0); +#endif + + LN_STAT(low_txfree = sc->lance_txinfo.ri_max); + LN_STAT(low_txheapsize = 0xFFFFFFFF); + LE_IFP(sc)->if_reset = lance_reset; + LE_IFP(sc)->if_init = lance_init; + LE_IFP(sc)->if_start = lance_start; + *msize = sc->lance_ramsize; + return DEPCA_IOSPACE; +} + +static void +depca_intr( + le_softc_t *sc) +{ + DEPCA_WRNICSR(sc, DEPCA_RDNICSR(sc) ^ DEPCA_NICSR_LED); + lance_intr(sc); +} + +/* + * Here's as good a place to describe our paritioning of the + * LANCE shared RAM space. (NOTE: this driver does not yet support + * the concept of a LANCE being able to DMA). + * + * First is the 24 (00:23) bytes for LANCE Initialization Block + * Next are the recieve descriptors. The number is calculated from + * how many LN_BUFSIZE buffers we can allocate (this number must + * be a power of 2). Next are the transmit descriptors. The amount + * of transmit descriptors is derived from the size of the RAM + * divided by 1K. Now come the receive buffers (one for each receive + * descriptor). Finally is the transmit heap. (no fixed buffers are + * allocated so as to make the most use of the limited space). + */ +static int +lance_init_adapmem( + le_softc_t *sc) +{ + lance_addr_t rxbufoffset; + lance_addr_t rxdescoffset, txdescoffset; + unsigned rxdescs, txdescs; + + /* + * First calculate how many descriptors we heap. + * Note this assumes the ramsize is a power of two. + */ + sc->lance_rxbufsize = LN_BUFSIZE; + rxdescs = 1; + while (rxdescs * sc->lance_rxbufsize < sc->lance_ramsize) + rxdescs *= 2; + rxdescs /= 2; + if (rxdescs > LN_DESC_MAX) { + sc->lance_rxbufsize *= rxdescs / LN_DESC_MAX; + rxdescs = LN_DESC_MAX; + } + txdescs = sc->lance_ramsize / LN_TXDESC_RATIO; + if (txdescs > LN_DESC_MAX) + txdescs = LN_DESC_MAX; + + /* + * Now calculate where everything goes in memory + */ + rxdescoffset = sizeof(ln_initb_t); + txdescoffset = rxdescoffset + sizeof(ln_desc_t) * rxdescs; + rxbufoffset = txdescoffset + sizeof(ln_desc_t) * txdescs; + + sc->le_mctbl = (le_mcbits_t *) sc->lance_initb.ln_multi_mask; + /* + * Remember these for debugging. + */ + sc->lance_raminitb = (ln_initb_t *) sc->le_membase; + sc->lance_ramdesc = (ln_desc_t *) (sc->le_membase + rxdescoffset); + + /* + * Initialize the rings. + */ + if (!lance_init_ring(sc, &sc->lance_initb.ln_rxring, &sc->lance_rxinfo, + rxdescs, rxbufoffset, rxdescoffset)) + return 0; + sc->lance_rxinfo.ri_heap = rxbufoffset; + sc->lance_rxinfo.ri_heapend = rxbufoffset + sc->lance_rxbufsize * rxdescs; + + if (!lance_init_ring(sc, &sc->lance_initb.ln_txring, &sc->lance_txinfo, + txdescs, 0, txdescoffset)) + return 0; + sc->lance_txinfo.ri_heap = sc->lance_rxinfo.ri_heapend; + sc->lance_txinfo.ri_heapend = sc->lance_ramsize; + + /* + * Set CSR1 and CSR2 to the address of the init block (which + * for us is always 0. + */ + sc->lance_csr1 = LN_ADDR_LO(0 + sc->lance_ramoffset); + sc->lance_csr2 = LN_ADDR_HI(0 + sc->lance_ramoffset); + return 1; +} + +static int +lance_init_ring( + le_softc_t *sc, + ln_ring_t *rp, + lance_ring_t *ri, + unsigned ndescs, + lance_addr_t bufoffset, + lance_addr_t descoffset) +{ + lance_descinfo_t *di; + + /* + * Initialize the ring pointer in the LANCE InitBlock + */ + rp->r_addr_lo = LN_ADDR_LO(descoffset + sc->lance_ramoffset); + rp->r_addr_hi = LN_ADDR_HI(descoffset + sc->lance_ramoffset); + rp->r_log2_size = ffs(ndescs) - 1; + + /* + * Allocate the ring entry descriptors and initialize + * our ring information data structure. All these are + * our copies and do not live in the LANCE RAM. + */ + ri->ri_first = (lance_descinfo_t *) malloc(ndescs * sizeof(*di), M_DEVBUF, M_NOWAIT); + if (ri->ri_first == NULL) { + printf("lance_init_ring: malloc(%d) failed\n", ndescs * sizeof(*di)); + return 0; + } + ri->ri_free = ri->ri_max = ndescs; + ri->ri_last = ri->ri_first + ri->ri_max; + for (di = ri->ri_first; di < ri->ri_last; di++) { + di->di_addr = sc->le_membase + descoffset; + di->di_mbuf = NULL; + if (bufoffset) { + di->di_bufaddr = bufoffset; + di->di_buflen = sc->lance_rxbufsize; + bufoffset += sc->lance_rxbufsize; + } + descoffset += sizeof(ln_desc_t); + } + return 1; +} + +static void +lance_dumpcsrs( + le_softc_t *sc, + const char *id) +{ + printf("%s%d: %s: nicsr=%04x", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit, + id, DEPCA_RDNICSR(sc)); + LN_SELCSR(sc, LN_CSR0); printf(" csr0=%04x", LN_RDCSR(sc)); + LN_SELCSR(sc, LN_CSR1); printf(" csr1=%04x", LN_RDCSR(sc)); + LN_SELCSR(sc, LN_CSR2); printf(" csr2=%04x", LN_RDCSR(sc)); + LN_SELCSR(sc, LN_CSR3); printf(" csr3=%04x\n", LN_RDCSR(sc)); + LN_SELCSR(sc, LN_CSR0); +} + +static void +#if __FreeBSD__ > 1 +lance_reset(int unit) +#else +lance_reset(int unit, int dummy) +#endif +{ + le_softc_t *sc = &le_softc[unit]; + register int cnt, csr; + + /* lance_dumpcsrs(sc, "lance_reset: start"); */ + + LN_WRCSR(sc, LN_RDCSR(sc) & ~LN_CSR0_ENABINTR); + LN_WRCSR(sc, LN_CSR0_STOP); + DELAY(100); + + sc->le_flags &= ~IFF_UP; + LE_IFP(sc)->if_flags &= ~(IFF_UP|IFF_RUNNING); + + le_multi_filter(sc); /* initialize the multicast table */ + if ((sc->le_flags | LE_IFP(sc)->if_flags) & IFF_ALLMULTI) { + sc->lance_initb.ln_multi_mask[0] = 0xFFFFU; + sc->lance_initb.ln_multi_mask[1] = 0xFFFFU; + sc->lance_initb.ln_multi_mask[2] = 0xFFFFU; + sc->lance_initb.ln_multi_mask[3] = 0xFFFFU; + } + sc->lance_initb.ln_physaddr[0] = ((u_short *) sc->le_ac.ac_enaddr)[0]; + sc->lance_initb.ln_physaddr[1] = ((u_short *) sc->le_ac.ac_enaddr)[1]; + sc->lance_initb.ln_physaddr[2] = ((u_short *) sc->le_ac.ac_enaddr)[2]; + if (LE_IFP(sc)->if_flags & IFF_PROMISC) { + sc->lance_initb.ln_mode |= LN_MODE_PROMISC; + } else { + sc->lance_initb.ln_mode &= ~LN_MODE_PROMISC; + } + /* + * We force the init block to be at the start + * of the LANCE's RAM buffer. + */ + LN_COPYTO(sc, &sc->lance_initb, sc->le_membase, sizeof(sc->lance_initb)); + LN_SELCSR(sc, LN_CSR1); LN_WRCSR(sc, sc->lance_csr1); + LN_SELCSR(sc, LN_CSR2); LN_WRCSR(sc, sc->lance_csr2); + LN_SELCSR(sc, LN_CSR3); LN_WRCSR(sc, sc->lance_csr3); + + /* lance_dumpcsrs(sc, "lance_reset: preinit"); */ + + /* + * clear INITDONE and INIT the chip + */ + LN_SELCSR(sc, LN_CSR0); + LN_WRCSR(sc, LN_CSR0_INIT|LN_CSR0_INITDONE); + + csr = 0; + cnt = 100; + while (cnt-- > 0) { + if (((csr = LN_RDCSR(sc)) & LN_CSR0_INITDONE) != 0) + break; + DELAY(10000); + } + + if ((csr & LN_CSR0_INITDONE) == 0) { /* make sure we got out okay */ + lance_dumpcsrs(sc, "lance_reset: reset failure"); + } else { + /* lance_dumpcsrs(sc, "lance_reset: end"); */ + LE_IFP(sc)->if_flags |= IFF_UP; + sc->le_flags |= IFF_UP; + } +} + +static void +lance_init( + int unit) +{ + le_softc_t *sc = &le_softc[unit]; + lance_ring_t *ri; + lance_descinfo_t *di; + ln_desc_t desc; + + LN_STAT(inits++); + if (LE_IFP(sc)->if_flags & IFF_RUNNING) { +#if __FreeBSD__ > 1 + lance_reset(unit); +#else + lance_reset(unit, 0); +#endif + lance_tx_intr(sc); + /* + * If we were running, requeue any pending transmits. + */ + ri = &sc->lance_txinfo; + di = ri->ri_nextout; + while (ri->ri_free < ri->ri_max) { + if (--di == ri->ri_first) + di = ri->ri_nextout - 1; + if (di->di_mbuf == NULL) + break; + IF_PREPEND(&LE_IFP(sc)->if_snd, di->di_mbuf); + di->di_mbuf = NULL; + ri->ri_free++; + } + } else { +#if __FreeBSD__ > 1 + lance_reset(unit); +#else + lance_reset(unit, 0); +#endif + } + + /* + * Reset the transmit ring. Make sure we own all the buffers. + * Also reset the transmit heap. + */ + LE_IFP(sc)->if_flags &= ~IFF_OACTIVE; + ri = &sc->lance_txinfo; + for (di = ri->ri_first; di < ri->ri_last; di++) { + if (di->di_mbuf != NULL) { + m_freem(di->di_mbuf); + di->di_mbuf = NULL; + } + desc.d_flag = 0; + desc.d_addr_lo = LN_ADDR_LO(ri->ri_heap + sc->lance_ramoffset); + desc.d_addr_hi = LN_ADDR_HI(ri->ri_heap + sc->lance_ramoffset); + desc.d_buflen = 0; + LN_PUTDESC(sc, &desc, di->di_addr); + } + ri->ri_nextin = ri->ri_nextout = ri->ri_first; + ri->ri_free = ri->ri_max; + ri->ri_outptr = ri->ri_heap; + ri->ri_outsize = ri->ri_heapend - ri->ri_heap; + + ri = &sc->lance_rxinfo; + desc.d_flag = LN_DFLAG_OWNER; + desc.d_buflen = 0 - sc->lance_rxbufsize; + for (di = ri->ri_first; di < ri->ri_last; di++) { + desc.d_addr_lo = LN_ADDR_LO(di->di_bufaddr + sc->lance_ramoffset); + desc.d_addr_hi = LN_ADDR_HI(di->di_bufaddr + sc->lance_ramoffset); + LN_PUTDESC(sc, &desc, di->di_addr); + } + ri->ri_nextin = ri->ri_nextout = ri->ri_first; + ri->ri_outptr = ri->ri_heap; + ri->ri_outsize = ri->ri_heapend - ri->ri_heap; + ri->ri_free = 0; + + if (LE_IFP(sc)->if_flags & IFF_UP) { + LE_IFP(sc)->if_flags |= IFF_RUNNING; + LN_WRCSR(sc, LN_CSR0_START|LN_CSR0_INITDONE|LN_CSR0_ENABINTR); + /* lance_dumpcsrs(sc, "lance_init: up"); */ + lance_start(LE_IFP(sc)); + } else { + /* lance_dumpcsrs(sc, "lance_init: down"); */ + LE_IFP(sc)->if_flags &= ~IFF_RUNNING; + } +} + +static void +lance_intr( + le_softc_t *sc) +{ + unsigned oldcsr; + + oldcsr = LN_RDCSR(sc); + oldcsr &= ~LN_CSR0_ENABINTR; + LN_WRCSR(sc, oldcsr); + LN_WRCSR(sc, LN_CSR0_ENABINTR); + + if (oldcsr & LN_CSR0_ERRSUM) { + if (oldcsr & LN_CSR0_MISS) { + /* + * LN_CSR0_MISS is signaled when the LANCE receiver + * loses a packet because it doesn't own a receive + * descriptor. Rev. D LANCE chips, which are no + * longer used, require a chip reset as described + * below. + */ + LN_STAT(rx_misses++); + } + if (oldcsr & LN_CSR0_MEMERROR) { + LN_STAT(memory_errors++); + if (oldcsr & (LN_CSR0_RXON|LN_CSR0_TXON)) { + lance_init(LE_IFP(sc)->if_unit); + return; + } + } + } + + if ((oldcsr & LN_CSR0_RXINT) && lance_rx_intr(sc)) { + lance_init(LE_IFP(sc)->if_unit); + return; + } + + if (oldcsr & LN_CSR0_TXINT) { + if (lance_tx_intr(sc)) + lance_start(LE_IFP(sc)); + } + + if (oldcsr == (LN_CSR0_PENDINTR|LN_CSR0_RXON|LN_CSR0_TXON)) + printf("%s%d: lance_intr: stray interrupt\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit); +} + +static int +lance_rx_intr( + le_softc_t *sc) +{ + lance_ring_t *ri = &sc->lance_rxinfo; + lance_descinfo_t *eop; + ln_desc_t desc; + int ndescs, total_len, rxdescs; + + LN_STAT(rx_intrs++); + + for (rxdescs = 0;;) { + /* + * Now to try to find the end of this packet chain. + */ + for (ndescs = 1, eop = ri->ri_nextin;; ndescs++) { + /* + * If we don't own this descriptor, the packet ain't + * all here so return because we are done. + */ + LN_GETDESC(sc, &desc, eop->di_addr); + if (desc.d_flag & LN_DFLAG_OWNER) + return 0; + /* + * In case we have missed a packet and gotten the + * LANCE confused, make sure we are pointing at the + * start of a packet. If we aren't, something is really + * strange so reinit the LANCE. + */ + if (desc.d_flag & LN_DFLAG_RxBUFERROR) { + LN_STAT(rx_buferror++); + return 1; + } + if ((desc.d_flag & LN_DFLAG_SOP) && eop != ri->ri_nextin) { + LN_STAT(rx_badsop++); + return 1; + } + if (desc.d_flag & LN_DFLAG_EOP) + break; + if (++eop == ri->ri_last) + eop = ri->ri_first; + } + + total_len = (desc.d_status & LN_DSTS_RxLENMASK) - 4; + if ((desc.d_flag & LN_DFLAG_RxERRSUM) == 0) { + /* + * Valid Packet -- If the SOP is less than or equal to the EOP + * or the length is less than the receive buffer size, then the + * packet is contiguous in memory and can be copied in one shot. + * Otherwise we need to copy two segments to get the entire + * packet. + */ + if (ri->ri_nextin <= eop || total_len <= ri->ri_heapend - ri->ri_nextin->di_bufaddr) { + le_input(sc, sc->le_membase + ri->ri_nextin->di_bufaddr, + total_len, total_len, NULL); + LN_STAT(rx_contig++); + } else { + le_input(sc, sc->le_membase + ri->ri_nextin->di_bufaddr, + total_len, + ri->ri_heapend - ri->ri_nextin->di_bufaddr, + sc->le_membase + ri->ri_first->di_bufaddr); + LN_STAT(rx_noncontig++); + } + } else { + /* + * If the packet is bad, increment the + * counters. + */ + LE_IFP(sc)->if_ierrors++; + if (desc.d_flag & LN_DFLAG_RxBADCRC) + LN_STAT(rx_badcrc++); + if (desc.d_flag & LN_DFLAG_RxOVERFLOW) + LN_STAT(rx_badalign++); + if (desc.d_flag & LN_DFLAG_RxFRAMING) + LN_STAT(rx_badframe++); + } + LE_IFP(sc)->if_ipackets++; + LN_STAT(rx_ndescs[ndescs-1]++); + rxdescs += ndescs; + while (ndescs-- > 0) { + LN_SETFLAG(sc, ri->ri_nextin->di_addr, LN_DFLAG_OWNER); + if (++ri->ri_nextin == ri->ri_last) + ri->ri_nextin = ri->ri_first; + } + } + /* LN_STAT(rx_intr_descs[rxdescs]++); */ + LN_MAXSTAT(rx_intr_hidescs, rxdescs); + + return 0; +} + +static void +lance_start( + struct ifnet *ifp) +{ + le_softc_t *sc = (le_softc_t *) ifp; + struct ifqueue *ifq = &ifp->if_snd; + lance_ring_t *ri = &sc->lance_txinfo; + lance_descinfo_t *di; + ln_desc_t desc; + unsigned len, slop; + struct mbuf *m, *m0; + caddr_t bp; + + if ((ifp->if_flags & IFF_RUNNING) == 0) + return; + + for (;;) { + IF_DEQUEUE(ifq, m); + if (m == NULL) + break; + + /* + * Make the packet meets the minimum size for Ethernet. + * The slop is so that we also use an even number of longwards. + */ + len = ETHERMIN + sizeof(struct ether_header); + if (m->m_pkthdr.len > len) + len = m->m_pkthdr.len; + + slop = (8 - len) & 3; + /* + * If there are no free ring entries (there must be always + * one owned by the host), or there's not enough space for + * this packet, or this packet would wrap around the end + * of LANCE RAM then wait for the transmits to empty for + * space and ring entries to become available. + */ + if (ri->ri_free == 1 || len + slop > ri->ri_outsize) { + /* + * Try to see if we can free up anything off the transit ring. + */ + if (lance_tx_intr(sc) > 0) { + LN_STAT(tx_drains[0]++); + IF_PREPEND(ifq, m); + continue; + } + LN_STAT(tx_nospc[0]++); + break; + } + + if (len + slop > ri->ri_heapend - ri->ri_outptr) { + /* + * Since the packet won't fit in the end of the transmit + * heap, see if there is space at the beginning of the transmit + * heap. If not, try again when there is space. + */ + LN_STAT(tx_orphaned++); + slop += ri->ri_heapend - ri->ri_outptr; + if (len + slop > ri->ri_outsize) { + LN_STAT(tx_nospc[1]++); + break; + } + /* + * Point to the beginning of the heap + */ + ri->ri_outptr = ri->ri_heap; + LN_STAT(tx_adoptions++); + } + + /* + * Initialize the descriptor (saving the buffer address, + * buffer length, and mbuf) and write the packet out + * to the board. + */ + di = ri->ri_nextout; + di->di_bufaddr = ri->ri_outptr; + di->di_buflen = len + slop; + di->di_mbuf = m; + bp = sc->le_membase + di->di_bufaddr; + for (m0 = m; m0 != NULL; m0 = m0->m_next) { + LN_COPYTO(sc, mtod(m0, caddr_t), bp, m0->m_len); + bp += m0->m_len; + } + /* + * Zero out the remainder if needed (< ETHERMIN). + */ + if (m->m_pkthdr.len < len) + LN_ZERO(sc, bp, len - m->m_pkthdr.len); + + /* + * Finally, copy out the descriptor and tell the + * LANCE to transmit!. + */ + desc.d_buflen = 0 - len; + desc.d_addr_lo = LN_ADDR_LO(di->di_bufaddr + sc->lance_ramoffset); + desc.d_addr_hi = LN_ADDR_HI(di->di_bufaddr + sc->lance_ramoffset); + desc.d_flag = LN_DFLAG_SOP|LN_DFLAG_EOP|LN_DFLAG_OWNER; + LN_PUTDESC(sc, &desc, di->di_addr); + LN_WRCSR(sc, LN_CSR0_TXDEMAND|LN_CSR0_ENABINTR); + + /* + * Do our bookkeeping with our transmit heap. + * (if we wrap, point back to the beginning). + */ + ri->ri_outptr += di->di_buflen; + ri->ri_outsize -= di->di_buflen; + LN_MAXSTAT(high_txoutptr, ri->ri_outptr); + LN_MINSTAT(low_txheapsize, ri->ri_outsize); + + if (ri->ri_outptr == ri->ri_heapend) + ri->ri_outptr = ri->ri_heap; + + ri->ri_free--; + if (++ri->ri_nextout == ri->ri_last) + ri->ri_nextout = ri->ri_first; + LN_MINSTAT(low_txfree, ri->ri_free); + } + if (m != NULL) { + ifp->if_flags |= IFF_OACTIVE; + IF_PREPEND(ifq, m); + } +} + +static int +lance_tx_intr( + le_softc_t *sc) +{ + lance_ring_t *ri = &sc->lance_txinfo; + unsigned xmits; + + LN_STAT(tx_intrs++); + for (xmits = 0; ri->ri_free < ri->ri_max; ) { + ln_desc_t desc; + + LN_GETDESC(sc, &desc, ri->ri_nextin->di_addr); + if (desc.d_flag & LN_DFLAG_OWNER) + break; + + if (desc.d_flag & (LN_DFLAG_TxONECOLL|LN_DFLAG_TxMULTCOLL)) + LE_IFP(sc)->if_collisions++; + if (desc.d_flag & LN_DFLAG_TxDEFERRED) + LN_STAT(tx_deferred++); + if (desc.d_flag & LN_DFLAG_TxONECOLL) + LN_STAT(tx_single_collisions++); + if (desc.d_flag & LN_DFLAG_TxMULTCOLL) + LN_STAT(tx_multiple_collisions++); + + if (desc.d_flag & LN_DFLAG_TxERRSUM) { + if (desc.d_status & (LN_DSTS_TxUNDERFLOW|LN_DSTS_TxBUFERROR| + LN_DSTS_TxEXCCOLL|LN_DSTS_TxLATECOLL)) { + if (desc.d_status & LN_DSTS_TxEXCCOLL) { + unsigned tdr; + LN_STAT(tx_excessive_collisions++); + if ((tdr = (desc.d_status & LN_DSTS_TxTDRMASK)) > 0) { + tdr *= 100; + printf("%s%d: lance: warning: excessive collisions: TDR %dns (%d-%dm)\n", + LE_IFP(sc)->if_name, LE_IFP(sc)->if_unit, + tdr, (tdr*99)/1000, (tdr*117)/1000); + } + } + if (desc.d_status & LN_DSTS_TxBUFERROR) + LN_STAT(tx_buferror++); + LE_IFP(sc)->if_oerrors++; + if ((desc.d_status & LN_DSTS_TxLATECOLL) == 0) { + lance_init(LE_IFP(sc)->if_unit); + return 0; + } else { + LN_STAT(tx_late_collisions++); + } + } + } + m_freem(ri->ri_nextin->di_mbuf); + ri->ri_nextin->di_mbuf = NULL; + LE_IFP(sc)->if_opackets++; + ri->ri_free++; + ri->ri_outsize += ri->ri_nextin->di_buflen; + if (++ri->ri_nextin == ri->ri_last) + ri->ri_nextin = ri->ri_first; + LE_IFP(sc)->if_flags &= ~IFF_OACTIVE; + xmits++; + } + if (ri->ri_free == ri->ri_max) + LN_STAT(tx_emptied++); + /* LN_STAT(tx_intr_descs[xmits]++); */ + LN_MAXSTAT(tx_intr_hidescs, xmits); + return xmits; +} +#endif /* !defined(LE_NOLANCE) */ +#endif /* NLE > 0 */ diff --git a/sys/i386/isa/if_ze.c b/sys/i386/isa/if_ze.c new file mode 100644 index 0000000..6ffb96c --- /dev/null +++ b/sys/i386/isa/if_ze.c @@ -0,0 +1,1951 @@ +/*- + * TODO: + * [1] integrate into current if_ed.c + * [2] parse tuples to find out where to map the shared memory buffer, + * and what to write into the configuration register + * [3] move pcic-specific code into a separate module. + * + * Device driver for IBM PCMCIA Credit Card Adapter for Ethernet, + * if_ze.c + * + * Based on the Device driver for National Semiconductor DS8390 ethernet + * adapters by David Greenman. Modifications for PCMCIA by Keith Moore. + * Adapted for FreeBSD 1.1.5 by Jordan Hubbard. + * + * Currently supports only the IBM Credit Card Adapter for Ethernet, but + * could probably work with other PCMCIA cards also, if it were modified + * to get the locations of the PCMCIA configuration option register (COR) + * by parsing the configuration tuples, rather than by hard-coding in + * the value expected by IBM's card. + * + * Sources for data on the PCMCIA/IBM CCAE specific portions of the driver: + * + * [1] _Local Area Network Credit Card Adapters Technical Reference_, + * IBM Corp., SC30-3585-00, part # 33G9243. + * [2] "pre-alpha" PCMCIA support code for Linux by Barry Jaspan. + * [3] Intel 82536SL PC Card Interface Controller Data Sheet, Intel + * Order Number 290423-002 + * [4] National Semiconductor DP83902A ST-NIC (tm) Serial Network + * Interface Controller for Twisted Pair data sheet. + * + * + * Copyright (C) 1993, David Greenman. This software may be used, modified, + * copied, distributed, and sold, in both source and binary form provided + * that the above copyright and these terms are retained. Under no + * circumstances is the author responsible for the proper functioning + * of this software, nor does the author assume any responsibility + * for damages incurred with its use. + */ + +#include "ze.h" +#if NZE > 0 +#include "bpfilter.h" + +#include "param.h" +#include "systm.h" +#include "errno.h" +#include "ioctl.h" +#include "mbuf.h" +#include "socket.h" +#include "syslog.h" + +#include "net/if.h" +#include "net/if_dl.h" +#include "net/if_types.h" +#include "net/netisr.h" + +#ifdef INET +#include "netinet/in.h" +#include "netinet/in_systm.h" +#include "netinet/in_var.h" +#include "netinet/ip.h" +#include "netinet/if_ether.h" +#endif + +#ifdef NS +#include "netns/ns.h" +#include "netns/ns_if.h" +#endif + +#if NBPFILTER > 0 +#include "net/bpf.h" +#include "net/bpfdesc.h" +#endif + +#include "i386/isa/isa.h" +#include "i386/isa/isa_device.h" +#include "i386/isa/icu.h" +#include "i386/isa/if_zereg.h" + +#include "i386/include/pio.h" + + + +/***************************************************************************** + * pcmcia controller chip (PCIC) support * + * (eventually, move this to a separate file) * + *****************************************************************************/ +#include "ic/i82365.h" + +/* + * Each PCIC chip (82365SL or clone) can handle two card slots, and there + * can be up to four PCICs in a system. (On some machines, not all of the + * address lines are decoded, so a card may appear to be in more than one + * slot.) + */ +#define MAXSLOT 8 + +/* + * To access a register on the PCIC for a particular slot, you + * first write the correct OFFSET value for that slot in the + * INDEX register for the PCIC controller. You then read or write + * the value from or to the DATA register for that controller. + * + * The first pair of chips shares I/O addresss for DATA and INDEX, + * as does the second pair. (To the programmer, it looks like each + * pair is a single chip.) The i/o port addresses are hard-wired + * into the PCIC; so the following addresses should be valid for + * any machine that uses this chip. + */ + +#define PCIC_INDEX_0 0x3E0 /* index reg, chips 0 and 1 */ +#define PCIC_DATA_0 0x3E1 /* data register, chips 0 and 1 */ +#define PCIC_INDEX_1 0x3E2 /* index reg, chips 1 and 2 */ +#define PCIC_DATA_1 0x3E3 /* data register, chips 1 and 2 */ + +/* + * Given a slot number, calculate the INDEX and DATA registers + * to talk to that slot. OFFSET is added to the register number + * to address the registers for a particular slot. + */ +#define INDEX(slot) ((slot) < 4 ? PCIC_INDEX_0 : PCIC_INDEX_1) +#define DATA(slot) ((slot) < 4 ? PCIC_DATA_0 : PCIC_DATA_1) +#define OFFSET(slot) ((slot) % 4 * 0x40) + +/* + * There are 5 sets (windows) of memory mapping registers on the PCIC chip + * for each slot, numbered 0..4. + * + * They start at 10/50 hex within the chip's register space (not system + * I/O space), and are eight addresses apart. These are actually pairs of + * 8-bit-wide registers (low byte first, then high byte) since the + * address fields are actually 12 bits long. The upper bits are used + * for other things like 8/16-bit select and wait states. + * + * Memory mapping registers include start/stop addresses to define the + * region to be mapped (in terms of system memory addresses), and + * an offset register to allow for translation from system space + * to card space. The lower 12 bits aren't included in these, so memory is + * mapped in 4K chunks. + */ +#define MEM_START_ADDR(window) (((window) * 0x08) + 0x10) +#define MEM_STOP_ADDR(window) (((window) * 0x08) + 0x12) +#define MEM_OFFSET(window) (((window) * 0x08) + 0x14) +/* + * this bit gets set in the address window enable register (PCIC_ADDRWINE) + * to enable a particular address window. + */ +#define MEM_ENABLE_BIT(window) ((1) << (window)) + +/* + * There are two i/o port addressing windows. I/O ports cannot be + * relocated within system i/o space (unless the card doesn't decode + * all of the address bits); unlike card memory, there is no address + * translation offset. + */ +#define IO_START_ADDR(window) ((window) ? PCIC_IO1_STL : PCIC_IO0_STL) +#define IO_STOP_ADDR(window) ((window) ? PCIC_IO1_SPL : PCIC_IO0_SPL) +#define IO_ENABLE_BIT(window) ((window) ? PCIC_IO1_EN : PCIC_IO0_EN) +#define IO_CS16_BIT(window) ((window) ? PCIC_IO1_CS16 : PCIC_IO0_CS16) + +/* + * read a byte from a pcic register for a particular slot + */ +static inline unsigned char +pcic_getb (int slot, int reg) +{ + outb (INDEX(slot), OFFSET (slot) + reg); + return inb (DATA (slot)); +} + +/* + * write a byte to a pcic register for a particular slot + */ +static inline void +pcic_putb (int slot, int reg, unsigned char val) +{ + outb (INDEX(slot), OFFSET (slot) + reg); + outb (DATA (slot), val); +} + +/* + * read a word from a pcic register for a particular slot + */ +static inline unsigned short +pcic_getw (int slot, int reg) +{ + return pcic_getb (slot, reg) | (pcic_getb (slot, reg+1) << 8); +} + +/* + * write a word to a pcic register at a particular slot + */ +static inline void +pcic_putw (int slot, int reg, unsigned short val) +{ + pcic_putb (slot, reg, val & 0xff); + pcic_putb (slot, reg + 1, (val >> 8) & 0xff); +} + +static void +pcic_print_regs (int slot) +{ + int i, j; + + for (i = 0; i < 0x40; i += 16) { + for (j = 0; j < 16; ++j) + printf ("%02x ", pcic_getb (slot, i + j)); + printf ("\n"); + } +} + +/* + * map a portion of the card's memory space into system memory + * space. + * + * slot = # of the slot the card is plugged into + * window = which pcic memory map registers to use (0..4) + * sys_addr = base system PHYSICAL memory address where we want it. must + * be on an appropriate boundary (lower 12 bits are zero). + * card_addr = the base address of the card's memory to correspond + * to sys_addr + * length = length of the segment to map (may be rounded up as necessary) + * type = which card memory space to map (attribute or shared) + * width = 1 for byte-wide mapping; 2 for word (16-bit) mapping. + */ + +enum memtype { COMMON, ATTRIBUTE }; + +static void +pcic_map_memory (int slot, int window, unsigned long sys_addr, + unsigned long card_addr, unsigned long length, + enum memtype type, int width) +{ + unsigned short offset; + unsigned short mem_start_addr; + unsigned short mem_stop_addr; + + sys_addr >>= 12; + card_addr >>= 12; + length >>= 12; + /* + * compute an offset for the chip such that + * (sys_addr + offset) = card_addr + * but the arithmetic is done modulo 2^14 + */ + offset = (card_addr - sys_addr) & 0x3FFF; + /* + * now OR in the bit for "attribute memory" if necessary + */ + if (type == ATTRIBUTE) { + offset |= (PCIC_REG << 8); + /* REG == "region active" pin on card */ + } + /* + * okay, set up the chip memory mapping registers, and turn + * on the enable bit for this window. + * if we are doing 16-bit wide accesses (width == 2), + * turn on the appropriate bit. + * + * XXX for now, we set all of the wait state bits to zero. + * Not really sure how they should be set. + */ + mem_start_addr = sys_addr & 0xFFF; + if (width == 2) + mem_start_addr |= (PCIC_DATA16 << 8); + mem_stop_addr = (sys_addr + length) & 0xFFF; + + pcic_putw (slot, MEM_START_ADDR(window), mem_start_addr); + pcic_putw (slot, MEM_STOP_ADDR(window), mem_stop_addr); + pcic_putw (slot, MEM_OFFSET(window), offset); + /* + * Assert the bit (PCIC_MEMCS16) that says to decode all of + * the address lines. + */ + pcic_putb (slot, PCIC_ADDRWINE, + pcic_getb (slot, PCIC_ADDRWINE) | + MEM_ENABLE_BIT(window) | PCIC_MEMCS16); +} + +static void +pcic_unmap_memory (int slot, int window) +{ + /* + * seems like we need to turn off the enable bit first, after which + * we can clear the registers out just to be sure. + */ + pcic_putb (slot, PCIC_ADDRWINE, + pcic_getb (slot, PCIC_ADDRWINE) & ~MEM_ENABLE_BIT(window)); + pcic_putw (slot, MEM_START_ADDR(window), 0); + pcic_putw (slot, MEM_STOP_ADDR(window), 0); + pcic_putw (slot, MEM_OFFSET(window), 0); +} + +/* + * map a range of addresses into system i/o space + * (no translation of i/o addresses is possible) + * + * 'width' is: + * + 0 to tell the PCIC to generate the ISA IOCS16* signal from + * the PCMCIA IOIS16* signal. + * + 1 to select 8-bit width + * + 2 to select 16-bit width + */ + +static void +pcic_map_io (int slot, int window, unsigned short base, unsigned short length, + unsigned short width) +{ + unsigned char x; + + pcic_putw (slot, IO_START_ADDR(window), base); + pcic_putw (slot, IO_STOP_ADDR(window), base+length-1); + /* + * select the bits that determine whether + * an i/o operation is 8 or 16 bits wide + */ + x = pcic_getb (slot, PCIC_IOCTL); + switch (width) { + case 0: /* PCMCIA card decides */ + if (window) + x = (x & 0xf0) | PCIC_IO1_CS16; + else + x = (x & 0x0f) | PCIC_IO0_CS16; + break; + case 1: /* 8 bits wide */ + break; + case 2: /* 16 bits wide */ + if (window) + x = (x & 0xf0) | PCIC_IO1_16BIT; + else + x = (x & 0x0f) | PCIC_IO0_16BIT; + break; + } + pcic_putb (slot, PCIC_IOCTL, x); + pcic_putb (slot, PCIC_ADDRWINE, + pcic_getb (slot, PCIC_ADDRWINE) | IO_ENABLE_BIT(window)); +} + +#ifdef TEST +static void +pcic_unmap_io (int slot, int window) +{ + pcic_putb (slot, PCIC_ADDRWINE, + pcic_getb (slot, PCIC_ADDRWINE) & ~IO_ENABLE_BIT(window)); + pcic_putw (slot, IO_START_ADDR(window), 0); + pcic_putw (slot, IO_STOP_ADDR(window), 0); +} +#endif /* TEST */ + +/* + * tell the PCIC which irq we want to use. only the following are legal: + * 3, 4, 5, 7, 9, 10, 11, 12, 14, 15 + * + * NB: 'irq' is an interrupt NUMBER, not a MASK as in struct isa_device. + */ + +static void +pcic_map_irq (int slot, int irq) +{ + if (irq < 3 || irq == 6 || irq == 8 || irq == 13 || irq > 15) { + printf ("ze: pcic_map_irq (slot %d): illegal irq %d\n", slot, irq); + return; + } + pcic_putb (slot, PCIC_INT_GEN, + pcic_getb (slot, PCIC_INT_GEN) | (irq & 0x0F)); +} + +static void +pcic_power_on (int slot) +{ + pcic_putb (slot, PCIC_POWER, + pcic_getb (slot, PCIC_POWER) | PCIC_DISRST | PCIC_PCPWRE); + DELAY (50000); + pcic_putb (slot, PCIC_POWER, + pcic_getb (slot, PCIC_POWER) | PCIC_OUTENA); +} + +static void +pcic_reset (int slot) +{ + /* assert RESET (by clearing a bit!), wait a bit, and de-assert it */ + pcic_putb (slot, PCIC_INT_GEN, + pcic_getb (slot, PCIC_INT_GEN) & ~PCIC_CARDRESET); + DELAY (50000); + pcic_putb (slot, PCIC_INT_GEN, + pcic_getb (slot, PCIC_INT_GEN) | PCIC_CARDRESET); +} + + +/***************************************************************************** + * Driver for Ethernet Adapter * + *****************************************************************************/ +/* + * ze_softc: per line info and status + */ +struct ze_softc { + struct arpcom arpcom; /* ethernet common */ + + char *type_str; /* pointer to type string */ + char *mau; /* type of media access unit */ +#if 0 + u_char vendor; /* interface vendor */ + u_char type; /* interface type code */ +#endif + +#if 0 + u_short vector; /* interrupt vector */ +#endif + u_short nic_addr; /* NIC (DS8390) I/O bus address */ + + caddr_t smem_start; /* shared memory start address */ + caddr_t smem_end; /* shared memory end address */ + u_long smem_size; /* total shared memory size */ + caddr_t smem_ring; /* start of RX ring-buffer (in smem) */ + + caddr_t bpf; /* BPF "magic cookie" */ + + u_char memwidth; /* width of access to card mem 8 or 16 */ + u_char xmit_busy; /* transmitter is busy */ + u_char txb_cnt; /* Number of transmit buffers */ + u_char txb_next; /* Pointer to next buffer ready to xmit */ + u_short txb_next_len; /* next xmit buffer length */ + u_char data_buffered; /* data has been buffered in interface memory */ + u_char tx_page_start; /* first page of TX buffer area */ + + u_char rec_page_start; /* first page of RX ring-buffer */ + u_char rec_page_stop; /* last page of RX ring-buffer */ + u_char next_packet; /* pointer to next unread RX packet */ +} ze_softc[NZE]; + +int ze_attach(), ze_ioctl(), ze_probe(); +void ze_init(), ze_start(), ze_stop(), ze_intr(); +void ze_reset(), ze_watchdog(), ze_get_packet(); + +static inline void ze_rint(); +static inline void ze_xmit(); +static inline char *ze_ring_copy(); + +extern int ether_output(); + +struct isa_driver zedriver = { + ze_probe, + ze_attach, + "ze" +}; + +#define ETHER_MIN_LEN 64 +#define ETHER_MAX_LEN 1518 +#define ETHER_ADDR_LEN 6 +#define ETHER_HDR_SIZE 14 + +static unsigned char enet_addr[6]; +static unsigned char card_info[256]; + +#define CARD_INFO "IBM Corp.~Ethernet~0933495" + +/* + * scan the card information structure looking for the version/product info + * tuple. when we find it, compare it to the string we are looking for. + * return 1 if we find it, 0 otherwise. + */ + +static int +ze_check_cis (unsigned char *scratch) +{ + int i,j,k; + + card_info[0] = '\0'; + i = 0; + while (scratch[i] != 0xff && i < 1024) { + unsigned char link = scratch[i+2]; + +#if 0 + printf ("[%02x] %02x ", i, link); + for (j = 4; j < 2 * link + 4 && j < 32; j += 2) + printf ("%02x ", scratch[j + i]); + printf ("\n"); +#endif + if (scratch[i] == 0x15) { + /* + * level 1 version/product info + * copy to card_info, translating '\0' to '~' + */ + k = 0; + for (j = i+8; scratch[j] != 0xff; j += 2) + card_info[k++] = scratch[j] == '\0' ? '~' : scratch[j]; + card_info[k++] = '\0'; + return (memcmp (card_info, CARD_INFO, sizeof(CARD_INFO)-1) == 0); + } + i += 4 + 2 * link; + } + return 0; +} + +/* + * Probe each slot looking for an IBM Credit Card Adapter for Ethernet + * For each card that we find, map its card information structure + * into system memory at 'scratch' and see whether it's one of ours. + * Return the slot number if we find a card, or -1 otherwise. + * + * Side effects: + * + On success, leaves CIS mapped into memory at 'scratch'; + * caller must free it. + * + On success, leaves ethernet address in enet_addr. + * + Leaves product/vendor id of last card probed in 'card_info' + */ + +static int +ze_find_adapter (unsigned char *scratch) +{ + int slot; + + for (slot = 0; slot < MAXSLOT; ++slot) { + /* + * see if there's a PCMCIA controller here + * Intel PCMCIA controllers use 0x82 and 0x83 + * IBM clone chips use 0x88 and 0x89, apparently + */ + unsigned char idbyte = pcic_getb (slot, PCIC_ID_REV); + + if (idbyte != 0x82 && idbyte != 0x83 && + idbyte != 0x88 && idbyte != 0x89) { +#if 0 + printf ("ibmccae: pcic slot %d: wierd id/rev code 0x%02x\n", + slot, idbyte); +#endif + continue; + } + if ((pcic_getb (slot, PCIC_STATUS) & PCIC_CD) != PCIC_CD) { + printf ("ze: slot %d: no card in slot\n", slot); + /* no card in slot */ + continue; + } + pcic_power_on (slot); + pcic_reset (slot); + /* + * map the card's attribute memory and examine its + * card information structure tuples for something + * we recognize. + */ + pcic_map_memory (slot, 0, kvtop (scratch), 0L, + 0xFFFL, ATTRIBUTE, 1); + + if ((ze_check_cis (scratch)) > 0) { + /* found it */ + printf ("ze: found card in slot %d\n", slot); + return slot; + } + else + printf ("ze: pcmcia slot %d: %s\n", slot, card_info); + pcic_unmap_memory (slot, 0); + } + return -1; +} + + +/* + * macros to handle casting unsigned long to (char *) so we can + * read/write into physical memory space. + */ + +#define PEEK(addr) (*((unsigned char *)(addr))) +#define POKE(addr,val) do { PEEK(addr) = (val); } while (0) + +/* + * Determine if the device is present + * + * on entry: + * a pointer to an isa_device struct + * on exit: + * NULL if device not found + * or # of i/o addresses used (if found) + */ +int +ze_probe(isa_dev) + struct isa_device *isa_dev; +{ + struct ze_softc *sc = &ze_softc[isa_dev->id_unit]; + int i, x; + u_int memsize; + u_char iptr, memwidth, sum, tmp; + int slot; + + if ((slot = ze_find_adapter (isa_dev->id_maddr)) < 0) + return NULL; + + /* + * okay, we found a card, so set it up + */ + /* + * Inhibit 16 bit memory delay. + * POINTETH.SYS apparently does this, for what reason I don't know. + */ + pcic_putb (slot, PCIC_CDGC, + pcic_getb (slot, PCIC_CDGC) | PCIC_16_DL_INH); + /* + * things to map + * (1) card's EEPROM is already mapped by the find_adapter routine + * but we still need to get the card's ethernet address. + * after that we unmap that part of attribute memory. + * (2) card configuration registers need to be mapped in so we + * can set the configuration and socket # registers. + * (3) shared memory packet buffer + * (4) i/o ports + * (5) IRQ + */ + /* + * Sigh. Location of the ethernet address isn't documented in [1]. + * It was derived by doing a hex dump of all of attribute memory + * and looking for the IBM vendor prefix. + */ + enet_addr[0] = PEEK(isa_dev->id_maddr+0xff0); + enet_addr[1] = PEEK(isa_dev->id_maddr+0xff2); + enet_addr[2] = PEEK(isa_dev->id_maddr+0xff4); + enet_addr[3] = PEEK(isa_dev->id_maddr+0xff6); + enet_addr[4] = PEEK(isa_dev->id_maddr+0xff8); + enet_addr[5] = PEEK(isa_dev->id_maddr+0xffa); + pcic_unmap_memory (slot, 0); + + /* + * (2) map card configuration registers. these are offset + * in card memory space by 0x20000. normally we could get + * this offset from the card information structure, but I'm + * too lazy and am not quite sure if I understand the CIS anyway. + * + * XXX IF YOU'RE TRYING TO PORT THIS DRIVER FOR A DIFFERENT + * PCMCIA CARD, the most likely thing to change is the constant + * 0x20000 in the next statement. Oh yes, also change the + * card id string that we probe for. + */ + pcic_map_memory (slot, 0, kvtop (isa_dev->id_maddr), 0x20000, 8L, + ATTRIBUTE, 1); + POKE(isa_dev->id_maddr, 0x80); /* reset the card (how long?) */ + DELAY (10000); + /* + * Set the configuration index. According to [1], the adapter won't + * respond to any i/o signals until we do this; it uses the + * Memory Only interface (whatever that is; it's not documented). + * Also turn on "level" (not pulse) interrupts. + * + * XXX probably should init the socket and copy register also, + * so that we can deal with multiple instances of the same card. + */ + POKE(isa_dev->id_maddr, 0x41); + pcic_unmap_memory (slot, 0); + + /* + * (3) now map in the shared memory buffer. This has to be mapped + * as words, not bytes, and on a 16k boundary. The offset value + * was derived by installing IBM's POINTETH.SYS under DOS and + * looking at the PCIC registers; it's not documented in IBM's + * tech ref manual ([1]). + */ + pcic_map_memory (slot, 0, kvtop (isa_dev->id_maddr), 0x4000L, 0x4000L, + COMMON, 2); + + /* + * (4) map i/o ports. + * + * XXX is it possible that the config file leaves this unspecified, + * in which case we have to pick one? + * + * At least one PCMCIA device driver I'v seen maps a block + * of 32 consecutive i/o ports as two windows of 16 ports each. + * Maybe some other pcic chips are restricted to 16-port windows; + * the 82365SL doesn't seem to have that problem. But since + * we have an extra window anyway... + */ +#ifdef SHARED_MEMORY + pcic_map_io (slot, 0, isa_dev->id_iobase, 32, 1); +#else + pcic_map_io (slot, 0, isa_dev->id_iobase, 16, 1); + pcic_map_io (slot, 1, isa_dev->id_iobase+16, 16, 2); +#endif /* SHARED_MEMORY */ + + /* + * (5) configure the card for the desired interrupt + * + * XXX is it possible that the config file leaves this unspecified? + */ + pcic_map_irq (slot, ffs (isa_dev->id_irq) - 1); + + /* tell the PCIC that this is an I/O card (not memory) */ + pcic_putb (slot, PCIC_INT_GEN, + pcic_getb (slot, PCIC_INT_GEN) | PCIC_CARDTYPE); + +#if 0 + /* tell the PCIC to use level-mode interrupts */ + /* XXX this register may not be present on all controllers */ + pcic_putb (slot, PCIC_GLO_CTRL, + pcic_getb (slot, PCIC_GLO_CTRL) | PCIC_LVL_MODE); +#endif + +#if 0 + pcic_print_regs (slot); +#endif + /* + * Setup i/o addresses + */ + sc->nic_addr = isa_dev->id_iobase; +#if 0 + sc->vector = isa_dev->id_irq; +#endif + sc->smem_start = (caddr_t)isa_dev->id_maddr; + +#if 0 + sc->vendor = ZE_VENDOR_IBM; + sc->type = xxx; +#endif + + /* reset card to force it into a known state */ + tmp = inb (isa_dev->id_iobase + ZE_RESET); + DELAY(5000); + outb (isa_dev->id_iobase + ZE_RESET, tmp); + DELAY(5000); + + /* + * query MAM bit in misc register for 10base2 + */ + tmp = inb (isa_dev->id_iobase + ZE_MISC); + sc->mau = tmp & 0x09 ? "10base2" : "10baseT"; + + /* set width/size */ + sc->type_str = "IBM PCMCIA"; + memsize = 16*1024; + sc->memwidth = 16; + + /* allocate 1 xmit buffer */ + sc->smem_ring = sc->smem_start + (ZE_PAGE_SIZE * ZE_TXBUF_SIZE); + sc->txb_cnt = 1; + sc->rec_page_start = ZE_TXBUF_SIZE + ZE_PAGE_OFFSET; + sc->smem_size = memsize; + sc->smem_end = sc->smem_start + memsize; + sc->rec_page_stop = memsize / ZE_PAGE_SIZE + ZE_PAGE_OFFSET; + sc->tx_page_start = ZE_PAGE_OFFSET; + + /* get station address */ + for (i = 0; i < ETHER_ADDR_LEN; ++i) + sc->arpcom.ac_enaddr[i] = enet_addr[i]; + + isa_dev->id_msize = memsize; + return 32; +} + +/* + * Install interface into kernel networking data structures + */ +int +ze_attach(isa_dev) + struct isa_device *isa_dev; +{ + struct ze_softc *sc = &ze_softc[isa_dev->id_unit]; + struct ifnet *ifp = &sc->arpcom.ac_if; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + + /* + * Set interface to stopped condition (reset) + */ + ze_stop(isa_dev->id_unit); + + /* + * Initialize ifnet structure + */ + ifp->if_unit = isa_dev->id_unit; + ifp->if_name = "ze" ; + ifp->if_mtu = ETHERMTU; + ifp->if_init = ze_init; + ifp->if_output = ether_output; + ifp->if_start = ze_start; + ifp->if_ioctl = ze_ioctl; + ifp->if_reset = ze_reset; + ifp->if_watchdog = ze_watchdog; + + /* + * Set default state for LLC0 flag (used to disable the tranceiver + * for AUI operation), based on compile-time config option. + */ + if (isa_dev->id_flags & ZE_FLAGS_DISABLE_TRANCEIVER) + ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS + | IFF_LLC0); + else + ifp->if_flags = (IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS); + + /* + * Attach the interface + */ + if_attach(ifp); + + /* + * Search down the ifa address list looking for the AF_LINK type entry + */ + ifa = ifp->if_addrlist; + while ((ifa != 0) && (ifa->ifa_addr != 0) && + (ifa->ifa_addr->sa_family != AF_LINK)) + ifa = ifa->ifa_next; + /* + * If we find an AF_LINK type entry we fill in the hardware address. + * This is useful for netstat(1) to keep track of which interface + * is which. + */ + if ((ifa != 0) && (ifa->ifa_addr != 0)) { + /* + * Fill in the link-level address for this interface + */ + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + sdl->sdl_type = IFT_ETHER; + sdl->sdl_alen = ETHER_ADDR_LEN; + sdl->sdl_slen = 0; + bcopy(sc->arpcom.ac_enaddr, LLADDR(sdl), ETHER_ADDR_LEN); + } + + /* + * Print additional info when attached + */ + printf("ze%d: address %s, type %s (%dbit)%s, MAU %s\n", + isa_dev->id_unit, + ether_sprintf(sc->arpcom.ac_enaddr), sc->type_str, + sc->memwidth, + (ifp->if_flags & IFF_LLC0 ? " [tranceiver disabled]" : ""), + sc->mau); + + /* + * If BPF is in the kernel, call the attach for it + */ +#if NBPFILTER > 0 + bpfattach(&sc->bpf, ifp, DLT_EN10MB, sizeof(struct ether_header)); +#endif + return 1; +} + +/* + * Reset interface. + */ +void +ze_reset(unit) + int unit; +{ + int s; + + s = splnet(); + + /* + * Stop interface and re-initialize. + */ + ze_stop(unit); + ze_init(unit); + + (void) splx(s); +} + +/* + * Take interface offline. + */ +void +ze_stop(unit) + int unit; +{ + struct ze_softc *sc = &ze_softc[unit]; + int n = 5000; + + /* + * Stop everything on the interface, and select page 0 registers. + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_STP); + + /* + * Wait for interface to enter stopped state, but limit # of checks + * to 'n' (about 5ms). It shouldn't even take 5us on modern + * DS8390's, but just in case it's an old one. + */ + while (((inb(sc->nic_addr + ZE_P0_ISR) & ZE_ISR_RST) == 0) && --n); + +} + +/* + * Device timeout/watchdog routine. Entered if the device neglects to + * generate an interrupt after a transmit has been started on it. + */ +void +ze_watchdog(unit) + int unit; +{ +#if 1 + struct ze_softc *sc = &ze_softc[unit]; + u_char isr, imr; + u_short imask; + + /* select page zero */ + outb (sc->nic_addr + ZE_P0_CR, + (inb (sc->nic_addr + ZE_P0_CR) & 0x3f) | ZE_CR_PAGE_0); + + /* read interrupt status register */ + isr = inb (sc->nic_addr + ZE_P0_ISR) & 0xff; + + /* select page two */ + outb (sc->nic_addr + ZE_P0_CR, + (inb (sc->nic_addr + ZE_P0_CR) & 0x3f) | ZE_CR_PAGE_2); + + /* read interrupt mask register */ + imr = inb (sc->nic_addr + ZE_P2_IMR) & 0xff; + + imask = inb(IO_ICU2) << 8 | inb(IO_ICU1); + + log (LOG_ERR, "ze%d: device timeout, isr=%02x, imr=%02x, imask=%04x\n", + unit, isr, imr, imask); +#else + log(LOG_ERR, "ze%d: device timeout\n", unit); +#endif + + ze_reset(unit); +} + +/* + * Initialize device. + */ +void +ze_init(unit) + int unit; +{ + struct ze_softc *sc = &ze_softc[unit]; + struct ifnet *ifp = &sc->arpcom.ac_if; + int i, s; + u_char command; + + + /* address not known */ + if (ifp->if_addrlist == (struct ifaddr *)0) return; + + /* + * Initialize the NIC in the exact order outlined in the NS manual. + * This init procedure is "mandatory"...don't change what or when + * things happen. + */ + s = splnet(); + + /* reset transmitter flags */ + sc->data_buffered = 0; + sc->xmit_busy = 0; + sc->arpcom.ac_if.if_timer = 0; + + sc->txb_next = 0; + + /* This variable is used below - don't move this assignment */ + sc->next_packet = sc->rec_page_start + 1; + + /* + * Set interface for page 0, Remote DMA complete, Stopped + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_STP); + + if (sc->memwidth == 16) { + /* + * Set FIFO threshold to 8, No auto-init Remote DMA, + * byte order=80x86, word-wide DMA xfers + */ + outb(sc->nic_addr + ZE_P0_DCR, ZE_DCR_FT1|ZE_DCR_WTS); + } else { + /* + * Same as above, but byte-wide DMA xfers + */ + outb(sc->nic_addr + ZE_P0_DCR, ZE_DCR_FT1); + } + + /* + * Clear Remote Byte Count Registers + */ + outb(sc->nic_addr + ZE_P0_RBCR0, 0); + outb(sc->nic_addr + ZE_P0_RBCR1, 0); + + /* + * Enable reception of broadcast packets + */ + outb(sc->nic_addr + ZE_P0_RCR, ZE_RCR_AB); + + /* + * Place NIC in internal loopback mode + */ + outb(sc->nic_addr + ZE_P0_TCR, ZE_TCR_LB0); + + /* + * Initialize transmit/receive (ring-buffer) Page Start + */ + outb(sc->nic_addr + ZE_P0_TPSR, sc->tx_page_start); + outb(sc->nic_addr + ZE_P0_PSTART, sc->rec_page_start); + + /* + * Initialize Receiver (ring-buffer) Page Stop and Boundry + */ + outb(sc->nic_addr + ZE_P0_PSTOP, sc->rec_page_stop); + outb(sc->nic_addr + ZE_P0_BNRY, sc->rec_page_start); + + /* + * Clear all interrupts. A '1' in each bit position clears the + * corresponding flag. + */ + outb(sc->nic_addr + ZE_P0_ISR, 0xff); + + /* + * Enable the following interrupts: receive/transmit complete, + * receive/transmit error, and Receiver OverWrite. + * + * Counter overflow and Remote DMA complete are *not* enabled. + */ + outb(sc->nic_addr + ZE_P0_IMR, + ZE_IMR_PRXE|ZE_IMR_PTXE|ZE_IMR_RXEE|ZE_IMR_TXEE|ZE_IMR_OVWE); + + /* + * Program Command Register for page 1 + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_PAGE_1|ZE_CR_RD2|ZE_CR_STP); + + /* + * Copy out our station address + */ + for (i = 0; i < ETHER_ADDR_LEN; ++i) + outb(sc->nic_addr + ZE_P1_PAR0 + i, sc->arpcom.ac_enaddr[i]); + +#if NBPFILTER > 0 + /* + * Initialize multicast address hashing registers to accept + * all multicasts (only used when in promiscuous mode) + */ + for (i = 0; i < 8; ++i) + outb(sc->nic_addr + ZE_P1_MAR0 + i, 0xff); +#endif + + /* + * Set Current Page pointer to next_packet (initialized above) + */ + outb(sc->nic_addr + ZE_P1_CURR, sc->next_packet); + + /* + * Set Command Register for page 0, Remote DMA complete, + * and interface Start. + */ + outb(sc->nic_addr + ZE_P1_CR, ZE_CR_RD2|ZE_CR_STA); + + /* + * Take interface out of loopback + */ + outb(sc->nic_addr + ZE_P0_TCR, 0); + +#if 0 + /* + * If this is a 3Com board, the tranceiver must be software enabled + * (there is no settable hardware default). + */ + if (sc->vendor == ZE_VENDOR_3COM) { + if (ifp->if_flags & IFF_LLC0) { + outb(sc->asic_addr + ZE_3COM_CR, 0); + } else { + outb(sc->asic_addr + ZE_3COM_CR, ZE_3COM_CR_XSEL); + } + } +#endif + + /* + * Set 'running' flag, and clear output active flag. + */ + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + + /* + * ...and attempt to start output + */ + ze_start(ifp); + + (void) splx(s); +} + +/* + * This routine actually starts the transmission on the interface + */ +static inline void +ze_xmit(ifp) + struct ifnet *ifp; +{ + struct ze_softc *sc = &ze_softc[ifp->if_unit]; + u_short len = sc->txb_next_len; + + /* + * Set NIC for page 0 register access + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_STA); + + /* + * Set TX buffer start page + */ + outb(sc->nic_addr + ZE_P0_TPSR, sc->tx_page_start + + sc->txb_next * ZE_TXBUF_SIZE); + + /* + * Set TX length + */ + outb(sc->nic_addr + ZE_P0_TBCR0, len & 0xff); + outb(sc->nic_addr + ZE_P0_TBCR1, len >> 8); + + /* + * Set page 0, Remote DMA complete, Transmit Packet, and *Start* + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_TXP|ZE_CR_STA); + + sc->xmit_busy = 1; + sc->data_buffered = 0; + + /* + * Switch buffers if we are doing double-buffered transmits + */ + if ((sc->txb_next == 0) && (sc->txb_cnt > 1)) + sc->txb_next = 1; + else + sc->txb_next = 0; + + /* + * Set a timer just in case we never hear from the board again + */ + ifp->if_timer = 2; +} + +/* + * Start output on interface. + * We make two assumptions here: + * 1) that the current priority is set to splnet _before_ this code + * is called *and* is returned to the appropriate priority after + * return + * 2) that the IFF_OACTIVE flag is checked before this code is called + * (i.e. that the output part of the interface is idle) + */ +void +ze_start(ifp) + struct ifnet *ifp; +{ + struct ze_softc *sc = &ze_softc[ifp->if_unit]; + struct mbuf *m0, *m; + caddr_t buffer; + int len; + u_char laar_tmp; + +outloop: + /* + * See if there is room to send more data (i.e. one or both of the + * buffers is empty). + */ + if (sc->data_buffered) + if (sc->xmit_busy) { + /* + * No room. Indicate this to the outside world + * and exit. + */ + ifp->if_flags |= IFF_OACTIVE; + return; + } else { + /* + * Data is buffered, but we're not transmitting, so + * start the xmit on the buffered data. + * Note that ze_xmit() resets the data_buffered flag + * before returning. + */ + ze_xmit(ifp); + } + + IF_DEQUEUE(&sc->arpcom.ac_if.if_snd, m); + if (m == NULL) { + /* + * The following isn't pretty; we are using the !OACTIVE flag to + * indicate to the outside world that we can accept an additional + * packet rather than that the transmitter is _actually_ + * active. Indeed, the transmitter may be active, but if we haven't + * filled the secondary buffer with data then we still want to + * accept more. + * Note that it isn't necessary to test the data_buffered flag - + * we wouldn't have tried to de-queue the packet in the first place + * if it was set. + */ + ifp->if_flags &= ~IFF_OACTIVE; + return; + } + + /* + * Copy the mbuf chain into the transmit buffer + */ +#if 0 + /* + * Enable 16bit access to shared memory on WD/SMC boards + */ + if (sc->memwidth == 16) + if (sc->vendor == ZE_VENDOR_WD_SMC) { + laar_tmp = inb(sc->asic_addr + ZE_WD_LAAR); + outb(sc->asic_addr + ZE_WD_LAAR, laar_tmp | ZE_WD_LAAR_M16EN); + } +#endif + + buffer = sc->smem_start + (sc->txb_next * ZE_TXBUF_SIZE * ZE_PAGE_SIZE); + len = 0; + for (m0 = m; m != 0; m = m->m_next) { + bcopy(mtod(m, caddr_t), buffer, m->m_len); + buffer += m->m_len; + len += m->m_len; + } + +#if 0 + /* + * Restore previous shared mem access type + */ + if (sc->memwidth == 16) + if (sc->vendor == ZE_VENDOR_WD_SMC) { + outb(sc->asic_addr + ZE_WD_LAAR, laar_tmp); + } +#endif + + sc->txb_next_len = MAX(len, ETHER_MIN_LEN); + + if (sc->txb_cnt > 1) + /* + * only set 'buffered' flag if doing multiple buffers + */ + sc->data_buffered = 1; + + if (sc->xmit_busy == 0) + ze_xmit(ifp); + /* + * If there is BPF support in the configuration, tap off here. + * The following has support for converting trailer packets + * back to normal. + */ +#if NBPFILTER > 0 + if (sc->bpf) { + u_short etype; + int off, datasize, resid; + struct ether_header *eh; + struct trailer_header { + u_short ether_type; + u_short ether_residual; + } trailer_header; + char ether_packet[ETHER_MAX_LEN]; + char *ep; + + ep = ether_packet; + + /* + * We handle trailers below: + * Copy ether header first, then residual data, + * then data. Put all this in a temporary buffer + * 'ether_packet' and send off to bpf. Since the + * system has generated this packet, we assume + * that all of the offsets in the packet are + * correct; if they're not, the system will almost + * certainly crash in m_copydata. + * We make no assumptions about how the data is + * arranged in the mbuf chain (i.e. how much + * data is in each mbuf, if mbuf clusters are + * used, etc.), which is why we use m_copydata + * to get the ether header rather than assume + * that this is located in the first mbuf. + */ + /* copy ether header */ + m_copydata(m0, 0, sizeof(struct ether_header), ep); + eh = (struct ether_header *) ep; + ep += sizeof(struct ether_header); + etype = ntohs(eh->ether_type); + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) { + datasize = ((etype - ETHERTYPE_TRAIL) << 9); + off = datasize + sizeof(struct ether_header); + + /* copy trailer_header into a data structure */ + m_copydata(m0, off, sizeof(struct trailer_header), + &trailer_header.ether_type); + + /* copy residual data */ + m_copydata(m0, off+sizeof(struct trailer_header), + resid = ntohs(trailer_header.ether_residual) - + sizeof(struct trailer_header), ep); + ep += resid; + + /* copy data */ + m_copydata(m0, sizeof(struct ether_header), + datasize, ep); + ep += datasize; + + /* restore original ether packet type */ + eh->ether_type = trailer_header.ether_type; + + bpf_tap(sc->bpf, ether_packet, ep - ether_packet); + } else + bpf_mtap(sc->bpf, m0); + } +#endif + + m_freem(m0); + + /* + * If we are doing double-buffering, a buffer might be free to + * fill with another packet, so loop back to the top. + */ + if (sc->txb_cnt > 1) + goto outloop; + else { + ifp->if_flags |= IFF_OACTIVE; + return; + } +} + +/* + * Ethernet interface receiver interrupt. + */ +static inline void /* only called from one place, so may as well integrate */ +ze_rint(unit) + int unit; +{ + register struct ze_softc *sc = &ze_softc[unit]; + u_char boundry, current; + u_short len; + struct ze_ring *packet_ptr; + + /* + * Set NIC to page 1 registers to get 'current' pointer + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_PAGE_1|ZE_CR_RD2|ZE_CR_STA); + + /* + * 'sc->next_packet' is the logical beginning of the ring-buffer - i.e. + * it points to where new data has been buffered. The 'CURR' + * (current) register points to the logical end of the ring-buffer + * - i.e. it points to where additional new data will be added. + * We loop here until the logical beginning equals the logical + * end (or in other words, until the ring-buffer is empty). + */ + while (sc->next_packet != inb(sc->nic_addr + ZE_P1_CURR)) { + + /* get pointer to this buffer header structure */ + packet_ptr = (struct ze_ring *)(sc->smem_ring + + (sc->next_packet - sc->rec_page_start) * ZE_PAGE_SIZE); + + /* + * The byte count includes the FCS - Frame Check Sequence (a + * 32 bit CRC). + */ + len = packet_ptr->count; + if ((len >= ETHER_MIN_LEN) && (len <= ETHER_MAX_LEN)) { + /* + * Go get packet. len - 4 removes CRC from length. + * (packet_ptr + 1) points to data just after the packet ring + * header (+4 bytes) + */ + ze_get_packet(sc, (caddr_t)(packet_ptr + 1), len - 4); + ++sc->arpcom.ac_if.if_ipackets; + } else { + /* + * Really BAD...probably indicates that the ring pointers + * are corrupted. Also seen on early rev chips under + * high load - the byte order of the length gets switched. + */ + log(LOG_ERR, + "ze%d: shared memory corrupt - invalid packet length %d\n", + unit, len); + ze_reset(unit); + return; + } + + /* + * Update next packet pointer + */ + sc->next_packet = packet_ptr->next_packet; + + /* + * Update NIC boundry pointer - being careful to keep it + * one buffer behind. (as recommended by NS databook) + */ + boundry = sc->next_packet - 1; + if (boundry < sc->rec_page_start) + boundry = sc->rec_page_stop - 1; + + /* + * Set NIC to page 0 registers to update boundry register + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_STA); + + outb(sc->nic_addr + ZE_P0_BNRY, boundry); + + /* + * Set NIC to page 1 registers before looping to top (prepare to + * get 'CURR' current pointer) + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_PAGE_1|ZE_CR_RD2|ZE_CR_STA); + } +} + +/* + * Ethernet interface interrupt processor + */ +void +zeintr(unit) + int unit; +{ + struct ze_softc *sc = &ze_softc[unit]; + u_char isr; + + /* + * Set NIC to page 0 registers + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_STA); + + /* + * loop until there are no more new interrupts + */ + while (isr = inb(sc->nic_addr + ZE_P0_ISR)) { + + /* + * reset all the bits that we are 'acknowleging' + * by writing a '1' to each bit position that was set + * (writing a '1' *clears* the bit) + */ + outb(sc->nic_addr + ZE_P0_ISR, isr); + + /* + * Transmit error. If a TX completed with an error, we end up + * throwing the packet away. Really the only error that is + * possible is excessive collisions, and in this case it is + * best to allow the automatic mechanisms of TCP to backoff + * the flow. Of course, with UDP we're screwed, but this is + * expected when a network is heavily loaded. + */ + if (isr & ZE_ISR_TXE) { + u_char tsr = inb(sc->nic_addr + ZE_P0_TSR); + u_char ncr = inb(sc->nic_addr + ZE_P0_NCR); + + /* + * Excessive collisions (16) + */ + if ((tsr & ZE_TSR_ABT) && (ncr == 0)) { + /* + * When collisions total 16, the P0_NCR will + * indicate 0, and the TSR_ABT is set. + */ + sc->arpcom.ac_if.if_collisions += 16; + } else + sc->arpcom.ac_if.if_collisions += ncr; + + /* + * update output errors counter + */ + ++sc->arpcom.ac_if.if_oerrors; + + /* + * reset tx busy and output active flags + */ + sc->xmit_busy = 0; + sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + + /* + * clear watchdog timer + */ + sc->arpcom.ac_if.if_timer = 0; + } + + + /* + * Receiver Error. One or more of: CRC error, frame alignment error + * FIFO overrun, or missed packet. + */ + if (isr & ZE_ISR_RXE) { + ++sc->arpcom.ac_if.if_ierrors; +#ifdef ZE_DEBUG +#if 0 + printf("ze%d: receive error %x\n", unit, + inb(sc->nic_addr + ZE_P0_RSR)); +#else + printf("ze%d: receive error %b\n", unit, + inb(sc->nic_addr + ZE_P0_RSR), + "\20\8DEF\7REC DISAB\6PHY/MC\5MISSED\4OVR\3ALIGN\2FCS\1RCVD"); +#endif +#endif + } + + /* + * Overwrite warning. In order to make sure that a lockup + * of the local DMA hasn't occurred, we reset and + * re-init the NIC. The NSC manual suggests only a + * partial reset/re-init is necessary - but some + * chips seem to want more. The DMA lockup has been + * seen only with early rev chips - Methinks this + * bug was fixed in later revs. -DG + */ + if (isr & ZE_ISR_OVW) { + ++sc->arpcom.ac_if.if_ierrors; +#if 0 + /* sigh. this happens too often on our net */ + log(LOG_WARNING, + "ze%d: warning - receiver ring buffer overrun\n", + unit); +#endif + /* + * Stop/reset/re-init NIC + */ + ze_reset(unit); + } + + /* + * Transmission completed normally. + */ + if (isr & ZE_ISR_PTX) { + + /* + * reset tx busy and output active flags + */ + sc->xmit_busy = 0; + sc->arpcom.ac_if.if_flags &= ~IFF_OACTIVE; + + /* + * clear watchdog timer + */ + sc->arpcom.ac_if.if_timer = 0; + + /* + * Update total number of successfully transmitted + * packets. + */ + ++sc->arpcom.ac_if.if_opackets; + + /* + * Add in total number of collisions on last + * transmission. + */ + sc->arpcom.ac_if.if_collisions += inb(sc->nic_addr + + ZE_P0_TBCR0); + } + + /* + * Receive Completion. Go and get the packet. + * XXX - Doing this on an error is dubious because there + * shouldn't be any data to get (we've configured the + * interface to not accept packets with errors). + */ + if (isr & (ZE_ISR_PRX|ZE_ISR_RXE)) { +#if 0 + /* + * Enable access to shared memory on WD/SMC boards + */ + if (sc->memwidth == 16) + if (sc->vendor == ZE_VENDOR_WD_SMC) { + outb(sc->asic_addr + ZE_WD_LAAR, + inb(sc->asic_addr + ZE_WD_LAAR) + | ZE_WD_LAAR_M16EN); + } +#endif + ze_rint (unit); + +#if 0 + /* + * Disable access to shared memory + */ + if (sc->memwidth == 16) + if (sc->vendor == ZE_VENDOR_WD_SMC) { + outb(sc->asic_addr + ZE_WD_LAAR, + inb(sc->asic_addr + ZE_WD_LAAR) + & ~ZE_WD_LAAR_M16EN); + } +#endif + } + + /* + * If it looks like the transmitter can take more data, + * attempt to start output on the interface. If data is + * already buffered and ready to go, send it first. + */ + if ((sc->arpcom.ac_if.if_flags & IFF_OACTIVE) == 0) { + if (sc->data_buffered) + ze_xmit(&sc->arpcom.ac_if); + ze_start(&sc->arpcom.ac_if); + } + + /* + * return NIC CR to standard state: page 0, remote DMA complete, + * start (toggling the TXP bit off, even if was just set + * in the transmit routine, is *okay* - it is 'edge' + * triggered from low to high) + */ + outb(sc->nic_addr + ZE_P0_CR, ZE_CR_RD2|ZE_CR_STA); + + /* + * If the Network Talley Counters overflow, read them to + * reset them. It appears that old 8390's won't + * clear the ISR flag otherwise - resulting in an + * infinite loop. + */ + if (isr & ZE_ISR_CNT) { + (void) inb(sc->nic_addr + ZE_P0_CNTR0); + (void) inb(sc->nic_addr + ZE_P0_CNTR1); + (void) inb(sc->nic_addr + ZE_P0_CNTR2); + } + } +} + +/* + * Process an ioctl request. This code needs some work - it looks + * pretty ugly. + */ +int +ze_ioctl(ifp, command, data) + register struct ifnet *ifp; + int command; + caddr_t data; +{ + register struct ifaddr *ifa = (struct ifaddr *)data; + struct ze_softc *sc = &ze_softc[ifp->if_unit]; + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + + s = splnet(); + + switch (command) { + + case SIOCSIFADDR: + ifp->if_flags |= IFF_UP; + + switch (ifa->ifa_addr->sa_family) { +#ifdef INET + case AF_INET: + ze_init(ifp->if_unit); /* before arpwhohas */ + /* + * See if another station has *our* IP address. + * i.e.: There is an address conflict! If a + * conflict exists, a message is sent to the + * console. + */ + ((struct arpcom *)ifp)->ac_ipaddr = + IA_SIN(ifa)->sin_addr; + arpwhohas((struct arpcom *)ifp, &IA_SIN(ifa)->sin_addr); + break; +#endif +#ifdef NS + /* + * XXX - This code is probably wrong + */ + case AF_NS: + { + register struct ns_addr *ina = &(IA_SNS(ifa)->sns_addr); + + if (ns_nullhost(*ina)) + ina->x_host = + *(union ns_host *)(sc->arpcom.ac_enaddr); + else { + /* + * + */ + bcopy((caddr_t)ina->x_host.c_host, + (caddr_t)sc->arpcom.ac_enaddr, + sizeof(sc->arpcom.ac_enaddr)); + } + /* + * Set new address + */ + ze_init(ifp->if_unit); + break; + } +#endif + default: + ze_init(ifp->if_unit); + break; + } + break; + + case SIOCSIFFLAGS: + /* + * If interface is marked down and it is running, then stop it + */ + if (((ifp->if_flags & IFF_UP) == 0) && + (ifp->if_flags & IFF_RUNNING)) { + ze_stop(ifp->if_unit); + ifp->if_flags &= ~IFF_RUNNING; + } else { + /* + * If interface is marked up and it is stopped, then start it + */ + if ((ifp->if_flags & IFF_UP) && + ((ifp->if_flags & IFF_RUNNING) == 0)) + ze_init(ifp->if_unit); + } +#if NBPFILTER > 0 + if (ifp->if_flags & IFF_PROMISC) { + /* + * Set promiscuous mode on interface. + * XXX - for multicasts to work, we would need to + * write 1's in all bits of multicast + * hashing array. For now we assume that + * this was done in ze_init(). + */ + outb(sc->nic_addr + ZE_P0_RCR, + ZE_RCR_PRO|ZE_RCR_AM|ZE_RCR_AB); + } else { + /* + * XXX - for multicasts to work, we would need to + * rewrite the multicast hashing array with the + * proper hash (would have been destroyed above). + */ + outb(sc->nic_addr + ZE_P0_RCR, ZE_RCR_AB); + } +#endif +#if 0 + /* + * An unfortunate hack to provide the (required) software control + * of the tranceiver for 3Com boards. The LLC0 flag disables + * the tranceiver if set. + */ + if (sc->vendor == ZE_VENDOR_3COM) { + if (ifp->if_flags & IFF_LLC0) { + outb(sc->asic_addr + ZE_3COM_CR, 0); + } else { + outb(sc->asic_addr + ZE_3COM_CR, ZE_3COM_CR_XSEL); + } + } +#endif + + break; + + default: + error = EINVAL; + } + (void) splx(s); + return (error); +} + +/* + * Macro to calculate a new address within shared memory when given an offset + * from an address, taking into account ring-wrap. + */ +#define ringoffset(sc, start, off, type) \ + ((type)( ((caddr_t)(start)+(off) >= (sc)->smem_end) ? \ + (((caddr_t)(start)+(off))) - (sc)->smem_end \ + + (sc)->smem_ring: \ + ((caddr_t)(start)+(off)) )) + +/* + * Retreive packet from shared memory and send to the next level up via + * ether_input(). If there is a BPF listener, give a copy to BPF, too. + */ +void +ze_get_packet(sc, buf, len) + struct ze_softc *sc; + char *buf; + u_short len; +{ + struct ether_header *eh; + struct mbuf *m, *head = NULL, *ze_ring_to_mbuf(); + u_short off; + int resid; + u_short etype; + struct trailer_header { + u_short trail_type; + u_short trail_residual; + } trailer_header; + + /* Allocate a header mbuf */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + goto bad; + m->m_pkthdr.rcvif = &sc->arpcom.ac_if; + m->m_pkthdr.len = len; + m->m_len = 0; + head = m; + + eh = (struct ether_header *)buf; + + /* The following sillines is to make NFS happy */ +#define EROUND ((sizeof(struct ether_header) + 3) & ~3) +#define EOFF (EROUND - sizeof(struct ether_header)) + + /* + * The following assumes there is room for + * the ether header in the header mbuf + */ + head->m_data += EOFF; + bcopy(buf, mtod(head, caddr_t), sizeof(struct ether_header)); + buf += sizeof(struct ether_header); + head->m_len += sizeof(struct ether_header); + len -= sizeof(struct ether_header); + + etype = ntohs((u_short)eh->ether_type); + + /* + * Deal with trailer protocol: + * If trailer protocol, calculate the datasize as 'off', + * which is also the offset to the trailer header. + * Set resid to the amount of packet data following the + * trailer header. + * Finally, copy residual data into mbuf chain. + */ + if (etype >= ETHERTYPE_TRAIL && + etype < ETHERTYPE_TRAIL+ETHERTYPE_NTRAILER) { + + off = (etype - ETHERTYPE_TRAIL) << 9; + if ((off + sizeof(struct trailer_header)) > len) + goto bad; /* insanity */ + + eh->ether_type = *ringoffset(sc, buf, off, u_short *); + resid = ntohs(*ringoffset(sc, buf, off+2, u_short *)); + + if ((off + resid) > len) goto bad; /* insanity */ + + resid -= sizeof(struct trailer_header); + if (resid < 0) goto bad; /* insanity */ + + m = ze_ring_to_mbuf(sc, ringoffset(sc, buf, off+4, char *), head, resid); + if (m == NULL) goto bad; + + len = off; + head->m_pkthdr.len -= 4; /* subtract trailer header */ + } + + /* + * Pull packet off interface. Or if this was a trailer packet, + * the data portion is appended. + */ + m = ze_ring_to_mbuf(sc, buf, m, len); + if (m == NULL) goto bad; + +#if NBPFILTER > 0 + /* + * Check if there's a BPF listener on this interface. + * If so, hand off the raw packet to bpf. + */ + if (sc->bpf) { + bpf_mtap(sc->bpf, head); + + /* + * Note that the interface cannot be in promiscuous mode if + * there are no BPF listeners. And if we are in promiscuous + * mode, we have to check if this packet is really ours. + * + * XXX This test does not support multicasts. + */ + if ((sc->arpcom.ac_if.if_flags & IFF_PROMISC) && + bcmp(eh->ether_dhost, sc->arpcom.ac_enaddr, + sizeof(eh->ether_dhost)) != 0 && + bcmp(eh->ether_dhost, etherbroadcastaddr, + sizeof(eh->ether_dhost)) != 0) { + + m_freem(head); + return; + } + } +#endif + + /* + * Fix up data start offset in mbuf to point past ether header + */ + m_adj(head, sizeof(struct ether_header)); + + /* + * silly ether_input routine needs 'type' in host byte order + */ + eh->ether_type = ntohs(eh->ether_type); + + ether_input(&sc->arpcom.ac_if, eh, head); + return; + +bad: if (head) + m_freem(head); + return; +} + +/* + * Supporting routines + */ + +/* + * Given a source and destination address, copy 'amount' of a packet from + * the ring buffer into a linear destination buffer. Takes into account + * ring-wrap. + */ +static inline char * +ze_ring_copy(sc,src,dst,amount) + struct ze_softc *sc; + char *src; + char *dst; + u_short amount; +{ + u_short tmp_amount; + + /* does copy wrap to lower addr in ring buffer? */ + if (src + amount > sc->smem_end) { + tmp_amount = sc->smem_end - src; + bcopy(src,dst,tmp_amount); /* copy amount up to end of smem */ + amount -= tmp_amount; + src = sc->smem_ring; + dst += tmp_amount; + } + + bcopy(src, dst, amount); + + return(src + amount); +} + +/* + * Copy data from receive buffer to end of mbuf chain + * allocate additional mbufs as needed. return pointer + * to last mbuf in chain. + * sc = ze info (softc) + * src = pointer in ze ring buffer + * dst = pointer to last mbuf in mbuf chain to copy to + * amount = amount of data to copy + */ +struct mbuf * +ze_ring_to_mbuf(sc,src,dst,total_len) + struct ze_softc *sc; + char *src; + struct mbuf *dst; + u_short total_len; +{ + register struct mbuf *m = dst; + + while (total_len) { + register u_short amount = min(total_len, M_TRAILINGSPACE(m)); + + if (amount == 0) { /* no more data in this mbuf, alloc another */ + /* + * If there is enough data for an mbuf cluster, attempt + * to allocate one of those, otherwise, a regular + * mbuf will do. + * Note that a regular mbuf is always required, even if + * we get a cluster - getting a cluster does not + * allocate any mbufs, and one is needed to assign + * the cluster to. The mbuf that has a cluster + * extension can not be used to contain data - only + * the cluster can contain data. + */ + dst = m; + MGET(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (0); + + if (total_len >= MINCLSIZE) + MCLGET(m, M_DONTWAIT); + + m->m_len = 0; + dst->m_next = m; + amount = min(total_len, M_TRAILINGSPACE(m)); + } + + src = ze_ring_copy(sc, src, mtod(m, caddr_t) + m->m_len, amount); + + m->m_len += amount; + total_len -= amount; + + } + return (m); +} +#endif + diff --git a/sys/i386/isa/if_zereg.h b/sys/i386/isa/if_zereg.h new file mode 100644 index 0000000..3cd501f --- /dev/null +++ b/sys/i386/isa/if_zereg.h @@ -0,0 +1,859 @@ +/* + * National Semiconductor DS8390 NIC register definitions + * + * if_edreg.h,v + * Revision 1.1.2.1 1993/07/21 13:50:04 cgd + * from davidg: + * Added config file override for memory size and added flags to force + * 8bit or 16bit operation, and a flag to disable transmitter double buffering. + * See the updated "ed.relnotes" file for information about how to set + * the flags. + * This should be considered the first "production" release. It still + * needs a manual page, though. + * + * Revision 1.1 1993/07/03 12:21:07 cgd + * add support for David Greenman "ed" driver + * + * Revision 1.2 93/06/23 03:03:05 davidg + * added some additional definitions for the 83C584 bus interface + * chip (SMC/WD boards) + * + * Revision 1.1 93/06/23 03:01:07 davidg + * Initial revision + * + */ + +/* + * Page 0 register offsets + */ +#define ZE_P0_CR 0x00 /* Command Register */ + +#define ZE_P0_CLDA0 0x01 /* Current Local DMA Addr low (read) */ +#define ZE_P0_PSTART 0x01 /* Page Start register (write) */ + +#define ZE_P0_CLDA1 0x02 /* Current Local DMA Addr high (read) */ +#define ZE_P0_PSTOP 0x02 /* Page Stop register (write) */ + +#define ZE_P0_BNRY 0x03 /* Boundary Pointer */ + +#define ZE_P0_TSR 0x04 /* Transmit Status Register (read) */ +#define ZE_P0_TPSR 0x04 /* Transmit Page Start (write) */ + +#define ZE_P0_NCR 0x05 /* Number of Collisions Reg (read) */ +#define ZE_P0_TBCR0 0x05 /* Transmit Byte count, low (write) */ + +#define ZE_P0_FIFO 0x06 /* FIFO register (read) */ +#define ZE_P0_TBCR1 0x06 /* Transmit Byte count, high (write) */ + +#define ZE_P0_ISR 0x07 /* Interrupt Status Register */ + +#define ZE_P0_CRDA0 0x08 /* Current Remote DMA Addr low (read) */ +#define ZE_P0_RSAR0 0x08 /* Remote Start Address low (write) */ + +#define ZE_P0_CRDA1 0x09 /* Current Remote DMA Addr high (read) */ +#define ZE_P0_RSAR1 0x09 /* Remote Start Address high (write) */ + +#define ZE_P0_RBCR0 0x0a /* Remote Byte Count low (write) */ + +#define ZE_P0_RBCR1 0x0b /* Remote Byte Count high (write) */ + +#define ZE_P0_RSR 0x0c /* Receive Status (read) */ +#define ZE_P0_RCR 0x0c /* Receive Configuration Reg (write) */ + +#define ZE_P0_CNTR0 0x0d /* frame alignment error counter (read) */ +#define ZE_P0_TCR 0x0d /* Transmit Configuration Reg (write) */ + +#define ZE_P0_CNTR1 0x0e /* CRC error counter (read) */ +#define ZE_P0_DCR 0x0e /* Data Configuration Reg (write) */ + +#define ZE_P0_CNTR2 0x0f /* missed packet counter (read) */ +#define ZE_P0_IMR 0x0f /* Interrupt Mask Register (write) */ + +/* + * Page 1 register offsets + */ +#define ZE_P1_CR 0x00 /* Command Register */ +#define ZE_P1_PAR0 0x01 /* Physical Address Register 0 */ +#define ZE_P1_PAR1 0x02 /* Physical Address Register 1 */ +#define ZE_P1_PAR2 0x03 /* Physical Address Register 2 */ +#define ZE_P1_PAR3 0x04 /* Physical Address Register 3 */ +#define ZE_P1_PAR4 0x05 /* Physical Address Register 4 */ +#define ZE_P1_PAR5 0x06 /* Physical Address Register 5 */ +#define ZE_P1_CURR 0x07 /* Current RX ring-buffer page */ +#define ZE_P1_MAR0 0x08 /* Multicast Address Register 0 */ +#define ZE_P1_MAR1 0x09 /* Multicast Address Register 1 */ +#define ZE_P1_MAR2 0x0a /* Multicast Address Register 2 */ +#define ZE_P1_MAR3 0x0b /* Multicast Address Register 3 */ +#define ZE_P1_MAR4 0x0c /* Multicast Address Register 4 */ +#define ZE_P1_MAR5 0x0d /* Multicast Address Register 5 */ +#define ZE_P1_MAR6 0x0e /* Multicast Address Register 6 */ +#define ZE_P1_MAR7 0x0f /* Multicast Address Register 7 */ + +/* + * Page 2 register offsets + */ +#define ZE_P2_CR 0x00 /* Command Register */ +#define ZE_P2_PSTART 0x01 /* Page Start (read) */ +#define ZE_P2_CLDA0 0x01 /* Current Local DMA Addr 0 (write) */ +#define ZE_P2_PSTOP 0x02 /* Page Stop (read) */ +#define ZE_P2_CLDA1 0x02 /* Current Local DMA Addr 1 (write) */ +#define ZE_P2_RNPP 0x03 /* Remote Next Packet Pointer */ +#define ZE_P2_TPSR 0x04 /* Transmit Page Start (read) */ +#define ZE_P2_LNPP 0x05 /* Local Next Packet Pointer */ +#define ZE_P2_ACU 0x06 /* Address Counter Upper */ +#define ZE_P2_ACL 0x07 /* Address Counter Lower */ +#define ZE_P2_RCR 0x0c /* Receive Configuration Register (read) */ +#define ZE_P2_TCR 0x0d /* Transmit Configuration Register (read) */ +#define ZE_P2_DCR 0x0e /* Data Configuration Register (read) */ +#define ZE_P2_IMR 0x0f /* Interrupt Mask Register (read) */ + +/* + * Command Register (CR) definitions + */ + +/* + * STP: SToP. Software reset command. Takes the controller offline. No + * packets will be received or transmitted. Any reception or + * transmission in progress will continue to completion before + * entering reset state. To exit this state, the STP bit must + * reset and the STA bit must be set. The software reset has + * executed only when indicated by the RST bit in the ISR being + * set. + */ +#define ZE_CR_STP 0x01 + +/* + * STA: STArt. This bit is used to activate the NIC after either power-up, + * or when the NIC has been put in reset mode by software command + * or error. + */ +#define ZE_CR_STA 0x02 + +/* + * TXP: Transmit Packet. This bit must be set to indicate transmission of + * a packet. TXP is internally reset either after the transmission is + * completed or aborted. This bit should be set only after the Transmit + * Byte Count and Transmit Page Start register have been programmed. + */ +#define ZE_CR_TXP 0x04 + +/* + * RD0, RD1, RD2: Remote DMA Command. These three bits control the operation + * of the remote DMA channel. RD2 can be set to abort any remote DMA + * command in progress. The Remote Byte Count registers should be cleared + * when a remote DMA has been aborted. The Remote Start Addresses are not + * restored to the starting address if the remote DMA is aborted. + * + * RD2 RD1 RD0 function + * 0 0 0 not allowed + * 0 0 1 remote read + * 0 1 0 remote write + * 0 1 1 send packet + * 1 X X abort + */ +#define ZE_CR_RD0 0x08 +#define ZE_CR_RD1 0x10 +#define ZE_CR_RD2 0x20 + +/* + * PS0, PS1: Page Select. The two bits select which register set or 'page' to + * access. + * + * PS1 PS0 page + * 0 0 0 + * 0 1 1 + * 1 0 2 + * 1 1 reserved + */ +#define ZE_CR_PS0 0x40 +#define ZE_CR_PS1 0x80 +/* bit encoded aliases */ +#define ZE_CR_PAGE_0 0x00 /* (for consistency) */ +#define ZE_CR_PAGE_1 0x40 +#define ZE_CR_PAGE_2 0x80 + +/* + * Interrupt Status Register (ISR) definitions + */ + +/* + * PRX: Packet Received. Indicates packet received with no errors. + */ +#define ZE_ISR_PRX 0x01 + +/* + * PTX: Packet Transmitted. Indicates packet transmitted with no errors. + */ +#define ZE_ISR_PTX 0x02 + +/* + * RXE: Receive Error. Indicates that a packet was received with one or more + * the following errors: CRC error, frame alignment error, FIFO overrun, + * missed packet. + */ +#define ZE_ISR_RXE 0x04 + +/* + * TXE: Transmission Error. Indicates that an attempt to transmit a packet + * resulted in one or more of the following errors: excessive + * collisions, FIFO underrun. + */ +#define ZE_ISR_TXE 0x08 + +/* + * OVW: OverWrite. Indicates a receive ring-buffer overrun. Incoming network + * would exceed (has exceeded?) the boundry pointer, resulting in data + * that was previously received and not yet read from the buffer to be + * overwritten. + */ +#define ZE_ISR_OVW 0x10 + +/* + * CNT: Counter Overflow. Set when the MSB of one or more of the Network Talley + * Counters has been set. + */ +#define ZE_ISR_CNT 0x20 + +/* + * RDC: Remote Data Complete. Indicates that a Remote DMA operation has completed. + */ +#define ZE_ISR_RDC 0x40 + +/* + * RST: Reset status. Set when the NIC enters the reset state and cleared when a + * Start Command is issued to the CR. This bit is also set when a receive + * ring-buffer overrun (OverWrite) occurs and is cleared when one or more + * packets have been removed from the ring. This is a read-only bit. + */ +#define ZE_ISR_RST 0x80 + +/* + * Interrupt Mask Register (IMR) definitions + */ + +/* + * PRXE: Packet Received interrupt Enable. If set, a received packet will cause + * an interrupt. + */ +#define ZE_IMR_PRXE 0x01 + +/* + * PTXE: Packet Transmit interrupt Enable. If set, an interrupt is generated when + * a packet transmission completes. + */ +#define ZE_IMR_PTXE 0x02 + +/* + * RXEE: Receive Error interrupt Enable. If set, an interrupt will occur whenever a + * packet is received with an error. + */ +#define ZE_IMR_RXEE 0x04 + +/* + * TXEE: Transmit Error interrupt Enable. If set, an interrupt will occur whenever + * a transmission results in an error. + */ +#define ZE_IMR_TXEE 0x08 + +/* + * OVWE: OverWrite error interrupt Enable. If set, an interrupt is generated whenever + * the receive ring-buffer is overrun. i.e. when the boundry pointer is exceeded. + */ +#define ZE_IMR_OVWE 0x10 + +/* + * CNTE: Counter overflow interrupt Enable. If set, an interrupt is generated whenever + * the MSB of one or more of the Network Statistics counters has been set. + */ +#define ZE_IMR_CNTE 0x20 + +/* + * RDCE: Remote DMA Complete interrupt Enable. If set, an interrupt is generated + * when a remote DMA transfer has completed. + */ +#define ZE_IMR_RDCE 0x40 + +/* + * bit 7 is unused/reserved + */ + +/* + * Data Configuration Register (DCR) definitions + */ + +/* + * WTS: Word Transfer Select. WTS establishes byte or word transfers for + * both remote and local DMA transfers + */ +#define ZE_DCR_WTS 0x01 + +/* + * BOS: Byte Order Select. BOS sets the byte order for the host. + * Should be 0 for 80x86, and 1 for 68000 series processors + */ +#define ZE_DCR_BOS 0x02 + +/* + * LAS: Long Address Select. When LAS is 1, the contents of the remote + * DMA registers RSAR0 and RSAR1 are used to provide A16-A31 + */ +#define ZE_DCR_LAS 0x04 + +/* + * LS: Loopback Select. When 0, loopback mode is selected. Bits D1 and D2 + * of the TCR must also be programmed for loopback operation. + * When 1, normal operation is selected. + */ +#define ZE_DCR_LS 0x08 + +/* + * AR: Auto-initialize Remote. When 0, data must be removed from ring-buffer + * under program control. When 1, remote DMA is automatically initiated + * and the boundry pointer is automatically updated + */ +#define ZE_DCR_AR 0x10 + +/* + * FT0, FT1: Fifo Threshold select. + * FT1 FT0 Word-width Byte-width + * 0 0 1 word 2 bytes + * 0 1 2 words 4 bytes + * 1 0 4 words 8 bytes + * 1 1 8 words 12 bytes + * + * During transmission, the FIFO threshold indicates the number of bytes + * or words that the FIFO has filled from the local DMA before BREQ is + * asserted. The transmission threshold is 16 bytes minus the receiver + * threshold. + */ +#define ZE_DCR_FT0 0x20 +#define ZE_DCR_FT1 0x40 + +/* + * bit 7 (0x80) is unused/reserved + */ + +/* + * Transmit Configuration Register (TCR) definitions + */ + +/* + * CRC: Inhibit CRC. If 0, CRC will be appended by the transmitter, if 0, CRC + * is not appended by the transmitter. + */ +#define ZE_TCR_CRC 0x01 + +/* + * LB0, LB1: Loopback control. These two bits set the type of loopback that is + * to be performed. + * + * LB1 LB0 mode + * 0 0 0 - normal operation (DCR_LS = 0) + * 0 1 1 - internal loopback (DCR_LS = 0) + * 1 0 2 - external loopback (DCR_LS = 1) + * 1 1 3 - external loopback (DCR_LS = 0) + */ +#define ZE_TCR_LB0 0x02 +#define ZE_TCR_LB1 0x04 + +/* + * ATD: Auto Transmit Disable. Clear for normal operation. When set, allows + * another station to disable the NIC's transmitter by transmitting to + * a multicast address hashing to bit 62. Reception of a multicast address + * hashing to bit 63 enables the transmitter. + */ +#define ZE_TCR_ATD 0x08 + +/* + * OFST: Collision Offset enable. This bit when set modifies the backoff + * algorithm to allow prioritization of nodes. + */ +#define ZE_TCR_OFST 0x10 + +/* + * bits 5, 6, and 7 are unused/reserved + */ + +/* + * Transmit Status Register (TSR) definitions + */ + +/* + * PTX: Packet Transmitted. Indicates successful transmission of packet. + */ +#define ZE_TSR_PTX 0x01 + +/* + * bit 1 (0x02) is unused/reserved + */ + +/* + * COL: Transmit Collided. Indicates that the transmission collided at least + * once with another station on the network. + */ +#define ZE_TSR_COL 0x04 + +/* + * ABT: Transmit aborted. Indicates that the transmission was aborted due to + * excessive collisions. + */ +#define ZE_TSR_ABT 0x08 + +/* + * CRS: Carrier Sense Lost. Indicates that carrier was lost during the + * transmission of the packet. (Transmission is not aborted because + * of a loss of carrier) + */ +#define ZE_TSR_CRS 0x10 + +/* + * FU: FIFO Underrun. Indicates that the NIC wasn't able to access bus/ + * transmission memory before the FIFO emptied. Transmission of the + * packet was aborted. + */ +#define ZE_TSR_FU 0x20 + +/* + * CDH: CD Heartbeat. Indicates that the collision detection circuitry + * isn't working correctly during a collision heartbeat test. + */ +#define ZE_TSR_CDH 0x40 + +/* + * OWC: Out of Window Collision: Indicates that a collision occurred after + * a slot time (51.2us). The transmission is rescheduled just as in + * normal collisions. + */ +#define ZE_TSR_OWC 0x80 + +/* + * Receiver Configuration Register (RCR) definitions + */ + +/* + * SEP: Save Errored Packets. If 0, error packets are discarded. If set to 1, + * packets with CRC and frame errors are not discarded. + */ +#define ZE_RCR_SEP 0x01 + +/* + * AR: Accept Runt packet. If 0, packet with less than 64 byte are discarded. + * If set to 1, packets with less than 64 byte are not discarded. + */ +#define ZE_RCR_AR 0x02 + +/* + * AB: Accept Broadcast. If set, packets sent to the broadcast address will be + * accepted. + */ +#define ZE_RCR_AB 0x04 + +/* + * AM: Accept Multicast. If set, packets sent to a multicast address are checked + * for a match in the hashing array. If clear, multicast packets are ignored. + */ +#define ZE_RCR_AM 0x08 + +/* + * PRO: Promiscuous Physical. If set, all packets with a physical addresses are + * accepted. If clear, a physical destination address must match this + * station's address. Note: for full promiscuous mode, RCR_AB and RCR_AM + * must also be set. In addition, the multicast hashing array must be set + * to all 1's so that all multicast addresses are accepted. + */ +#define ZE_RCR_PRO 0x10 + +/* + * MON: Monitor Mode. If set, packets will be checked for good CRC and framing, + * but are not stored in the ring-buffer. If clear, packets are stored (normal + * operation). + */ +#define ZE_RCR_MON 0x20 + +/* + * bits 6 and 7 are unused/reserved. + */ + +/* + * Receiver Status Register (RSR) definitions + */ + +/* + * PRX: Packet Received without error. + */ +#define ZE_RSR_PRX 0x01 + +/* + * CRC: CRC error. Indicates that a packet has a CRC error. Also set for frame + * alignment errors. + */ +#define ZE_RSR_CRC 0x02 + +/* + * FAE: Frame Alignment Error. Indicates that the incoming packet did not end on + * a byte boundry and the CRC did not match at the last byte boundry. + */ +#define ZE_RSR_FAE 0x04 + +/* + * FO: FIFO Overrun. Indicates that the FIFO was not serviced (during local DMA) + * causing it to overrun. Reception of the packet is aborted. + */ +#define ZE_RSR_FO 0x08 + +/* + * MPA: Missed Packet. Indicates that the received packet couldn't be stored in + * the ring-buffer because of insufficient buffer space (exceeding the + * boundry pointer), or because the transfer to the ring-buffer was inhibited + * by RCR_MON - monitor mode. + */ +#define ZE_RSR_MPA 0x10 + +/* + * PHY: Physical address. If 0, the packet received was sent to a physical address. + * If 1, the packet was accepted because of a multicast/broadcast address + * match. + */ +#define ZE_RSR_PHY 0x20 + +/* + * DIS: Receiver Disabled. Set to indicate that the receiver has enetered monitor + * mode. Cleared when the receiver exits monitor mode. + */ +#define ZE_RSR_DIS 0x40 + +/* + * DFR: Deferring. Set to indicate a 'jabber' condition. The CRS and COL inputs + * are active, and the transceiver has set the CD line as a result of the + * jabber. + */ +#define ZE_RSR_DFR 0x80 + +/* + * receive ring discriptor + * + * The National Semiconductor DS8390 Network interface controller uses + * the following receive ring headers. The way this works is that the + * memory on the interface card is chopped up into 256 bytes blocks. + * A contiguous portion of those blocks are marked for receive packets + * by setting start and end block #'s in the NIC. For each packet that + * is put into the receive ring, one of these headers (4 bytes each) is + * tacked onto the front. + */ +struct ze_ring { + struct edr_status { /* received packet status */ + u_char rs_prx:1, /* packet received intack */ + rs_crc:1, /* crc error */ + rs_fae:1, /* frame alignment error */ + rs_fo:1, /* fifo overrun */ + rs_mpa:1, /* packet received intack */ + rs_phy:1, /* packet received intack */ + rs_dis:1, /* packet received intack */ + rs_dfr:1; /* packet received intack */ + } ze_rcv_status; /* received packet status */ + u_char next_packet; /* pointer to next packet */ + u_short count; /* bytes in packet (length + 4) */ +}; + +/* + * Common constants + */ +#define ZE_PAGE_SIZE 256 /* Size of RAM pages in bytes */ +#define ZE_TXBUF_SIZE 6 /* Size of TX buffer in pages */ +#define ZE_PAGE_OFFSET 0x40 /* mem buffer starts at 0x4000 */ + +/* + * Vendor types + */ +#define ZE_VENDOR_WD_SMC 0x00 /* Western Digital/SMC */ +#define ZE_VENDOR_3COM 0x01 /* 3Com */ + +/* + * Compile-time config flags + */ +/* + * this sets the default for enabling/disablng the tranceiver + */ +#define ZE_FLAGS_DISABLE_TRANCEIVER 0x01 + +/* + * This forces the board to be used in 8/16bit mode even if it + * autoconfigs differently + */ +#define ZE_FLAGS_FORCE_8BIT_MODE 0x02 +#define ZE_FLAGS_FORCE_16BIT_MODE 0x04 + +/* + * This disables the use of double transmit buffers. + */ +#define ZE_FLAGS_NO_DOUBLE_BUFFERING 0x08 + +/* + * definitions for IBM credit card adapter for ethernet + */ + +#define ZE_DATA_IO 0x10 +#define ZE_MISC 0x18 +#define ZE_RESET 0x1F + +#if 0 +/* + * Definitions for Western digital/SMC WD80x3 series ASIC + */ +/* + * Memory Select Register (MSR) + */ +#define ZE_WD_MSR 0 + +#define ZE_WD_MSR_ADDR 0x3f /* Memory decode bits 18-13 */ +#define ZE_WD_MSR_MENB 0x40 /* Memory enable */ +#define ZE_WD_MSR_RST 0x80 /* Reset board */ + +/* + * Interface Configuration Register (ICR) + */ +#define ZE_WD_ICR 1 + +#define ZE_WD_ICR_16BIT 0x01 /* 16-bit interface */ +#define ZE_WD_ICR_OAR 0x02 /* select register. 0=BIO 1=EAR */ +#define ZE_WD_ICR_IR2 0x04 /* high order bit of encoded IRQ */ +#define ZE_WD_ICR_MSZ 0x08 /* memory size (0=8k 1=32k) */ +#define ZE_WD_ICR_RLA 0x10 /* recall LAN address */ +#define ZE_WD_ICR_RX7 0x20 /* recall all but i/o and LAN address */ +#define ZE_WD_ICR_RIO 0x40 /* recall i/o address */ +#define ZE_WD_ICR_STO 0x80 /* store to non-volatile memory */ + +/* + * IO Address Register (IAR) + */ +#define ZE_WD_IAR 2 + +/* + * EEROM Address Register + */ +#define ZE_WD_EAR 3 + +/* + * Interrupt Request Register (IRR) + */ +#define ZE_WD_IRR 4 + +#define ZE_WD_IRR_0WS 0x01 /* use 0 wait-states on 8 bit bus */ +#define ZE_WD_IRR_OUT1 0x02 /* WD83C584 pin 1 output */ +#define ZE_WD_IRR_OUT2 0x04 /* WD83C584 pin 2 output */ +#define ZE_WD_IRR_OUT3 0x08 /* WD83C584 pin 3 output */ +#define ZE_WD_IRR_FLASH 0x10 /* Flash RAM is in the ROM socket */ + +/* + * The three bit of the encoded IRQ are decoded as follows: + * + * IR2 IR1 IR0 IRQ + * 0 0 0 2/9 + * 0 0 1 3 + * 0 1 0 5 + * 0 1 1 7 + * 1 0 0 10 + * 1 0 1 11 + * 1 1 0 15 + * 1 1 1 4 + */ +#define ZE_WD_IRR_IR0 0x20 /* bit 0 of encoded IRQ */ +#define ZE_WD_IRR_IR1 0x40 /* bit 1 of encoded IRQ */ +#define ZE_WD_IRR_IEN 0x80 /* Interrupt enable */ + +/* + * LA Address Register (LAAR) + */ +#define ZE_WD_LAAR 5 + +#define ZE_WD_LAAR_ADDRHI 0x1f /* bits 23-19 of RAM address */ +#define ZE_WD_LAAR_0WS16 0x20 /* enable 0 wait-states on 16 bit bus */ +#define ZE_WD_LAAR_L16EN 0x40 /* enable 16-bit operation */ +#define ZE_WD_LAAR_M16EN 0x80 /* enable 16-bit memory access */ + +/* i/o base offset to station address/card-ID PROM */ +#define ZE_WD_PROM 8 + +/* i/o base offset to CARD ID */ +#define ZE_WD_CARD_ID ZE_WD_PROM+6 + +#define ZE_TYPE_WD8003S 0x02 +#define ZE_TYPE_WD8003E 0x03 +#define ZE_TYPE_WD8013EBT 0x05 +#define ZE_TYPE_WD8013EB 0x27 +#define ZE_TYPE_WD8013EBP 0x2c +#define ZE_TYPE_WD8013EPC 0x29 + +/* Bit definitions in card ID */ +#define ZE_WD_REV_MASK 0x1f /* Revision mask */ +#define ZE_WD_SOFTCONFIG 0x20 /* Soft config */ +#define ZE_WD_LARGERAM 0x40 /* Large RAM */ +#define ZE_MICROCHANEL 0x80 /* Microchannel bus (vs. isa) */ + +/* + * Checksum total. All 8 bytes in station address PROM will add up to this + */ +#define ZE_WD_ROM_CHECKSUM_TOTAL 0xFF + +#define ZE_WD_NIC_OFFSET 0x10 /* I/O base offset to NIC */ +#define ZE_WD_ASIC_OFFSET 0 /* I/O base offset to ASIC */ +#define ZE_WD_IO_PORTS 32 /* # of i/o addresses used */ + +#define ZE_WD_PAGE_OFFSET 0 /* page offset for NIC access to mem */ + +/* + * Definitions for 3Com 3c503 + */ +#define ZE_3COM_NIC_OFFSET 0 +#define ZE_3COM_ASIC_OFFSET 0x400 /* offset to nic i/o regs */ + +/* + * XXX - The I/O address range is fragmented in the 3c503; this is the + * number of regs at iobase. + */ +#define ZE_3COM_IO_PORTS 16 /* # of i/o addresses used */ + +#define ZE_3COM_PAGE_OFFSET 0x20 /* memory starts in second bank */ + +/* + * Page Start Register. Must match PSTART in NIC + */ +#define ZE_3COM_PSTR 0 + +/* + * Page Stop Register. Must match PSTOP in NIC + */ +#define ZE_3COM_PSPR 1 + +/* + * Drq Timer Register. Determines number of bytes to be transfered during + * a DMA burst. + */ +#define ZE_3COM_DQTR 2 + +/* + * Base Configuration Register. Read-only register which contains the + * board-configured I/O base address of the adapter. Bit encoded. + */ +#define ZE_3COM_BCFR 3 + +#define ZE_3COM_BCFR_2E0 0x01 +#define ZE_3COM_BCFR_2A0 0x02 +#define ZE_3COM_BCFR_280 0x04 +#define ZE_3COM_BCFR_250 0x08 +#define ZE_3COM_BCFR_350 0x10 +#define ZE_3COM_BCFR_330 0x20 +#define ZE_3COM_BCFR_310 0x40 +#define ZE_3COM_BCFR_300 0x80 + +/* + * EPROM Configuration Register. Read-only register which contains the + * board-configured memory base address. Bit encoded. + */ +#define ZE_3COM_PCFR 4 + +#define ZE_3COM_PCFR_C8000 0x10 +#define ZE_3COM_PCFR_CC000 0x20 +#define ZE_3COM_PCFR_D8000 0x40 +#define ZE_3COM_PCFR_DC000 0x80 + +/* + * GA Configuration Register. Gate-Array Configuration Register. + */ +#define ZE_3COM_GACFR 5 + +/* + * mbs2 mbs1 mbs0 start address + * 0 0 0 0x0000 + * 0 0 1 0x2000 + * 0 1 0 0x4000 + * 0 1 1 0x6000 + * + * Note that with adapters with only 8K, the setting for 0x2000 must + * always be used. + */ +#define ZE_3COM_GACFR_MBS0 0x01 +#define ZE_3COM_GACFR_MBS1 0x02 +#define ZE_3COM_GACFR_MBS2 0x04 + +#define ZE_3COM_GACFR_RSEL 0x08 /* enable shared memory */ +#define ZE_3COM_GACFR_TEST 0x10 /* for GA testing */ +#define ZE_3COM_GACFR_OWS 0x20 /* select 0WS access to GA */ +#define ZE_3COM_GACFR_TCM 0x40 /* Mask DMA interrupts */ +#define ZE_3COM_GACFR_NIM 0x80 /* Mask NIC interrupts */ + +/* + * Control Register. Miscellaneous control functions. + */ +#define ZE_3COM_CR 6 + +#define ZE_3COM_CR_RST 0x01 /* Reset GA and NIC */ +#define ZE_3COM_CR_XSEL 0x02 /* Transceiver select. BNC=1(def) AUI=0 */ +#define ZE_3COM_CR_EALO 0x04 /* window EA PROM 0-15 to I/O base */ +#define ZE_3COM_CR_EAHI 0x08 /* window EA PROM 16-31 to I/O base */ +#define ZE_3COM_CR_SHARE 0x10 /* select interrupt sharing option */ +#define ZE_3COM_CR_DBSEL 0x20 /* Double buffer select */ +#define ZE_3COM_CR_DDIR 0x40 /* DMA direction select */ +#define ZE_3COM_CR_START 0x80 /* Start DMA controller */ + +/* + * Status Register. Miscellaneous status information. + */ +#define ZE_3COM_STREG 7 + +#define ZE_3COM_STREG_REV 0x07 /* GA revision */ +#define ZE_3COM_STREG_DIP 0x08 /* DMA in progress */ +#define ZE_3COM_STREG_DTC 0x10 /* DMA terminal count */ +#define ZE_3COM_STREG_OFLW 0x20 /* Overflow */ +#define ZE_3COM_STREG_UFLW 0x40 /* Underflow */ +#define ZE_3COM_STREG_DPRDY 0x80 /* Data port ready */ + +/* + * Interrupt/DMA Configuration Register + */ +#define ZE_3COM_IDCFR 8 + +#define ZE_3COM_IDCFR_DRQ0 0x01 /* DMA request 1 select */ +#define ZE_3COM_IDCFR_DRQ1 0x02 /* DMA request 2 select */ +#define ZE_3COM_IDCFR_DRQ2 0x04 /* DMA request 3 select */ +#define ZE_3COM_IDCFR_UNUSED 0x08 /* not used */ +#define ZE_3COM_IDCFR_IRQ2 0x10 /* Interrupt request 2 select */ +#define ZE_3COM_IDCFR_IRQ3 0x20 /* Interrupt request 3 select */ +#define ZE_3COM_IDCFR_IRQ4 0x40 /* Interrupt request 4 select */ +#define ZE_3COM_IDCFR_IRQ5 0x80 /* Interrupt request 5 select */ + +/* + * DMA Address Register MSB + */ +#define ZE_3COM_DAMSB 9 + +/* + * DMA Address Register LSB + */ +#define ZE_3COM_DALSB 0x0a + +/* + * Vector Pointer Register 2 + */ +#define ZE_3COM_VPTR2 0x0b + +/* + * Vector Pointer Register 1 + */ +#define ZE_3COM_VPTR1 0x0c + +/* + * Vector Pointer Register 0 + */ +#define ZE_3COM_VPTR0 0x0d + +/* + * Register File Access MSB + */ +#define ZE_3COM_RFMSB 0x0e + +/* + * Register File Access LSB + */ +#define ZE_3COM_RFLSB 0x0f +#endif diff --git a/sys/i386/isa/isa.c b/sys/i386/isa/isa.c new file mode 100644 index 0000000..5b5878e --- /dev/null +++ b/sys/i386/isa/isa.c @@ -0,0 +1,834 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * from: @(#)isa.c 7.2 (Berkeley) 5/13/91 + * $Id: isa.c,v 1.20 1994/08/13 03:50:07 wollman Exp $ + */ + +/* + * code to manage AT bus + * + * 92/08/18 Frank P. MacLachlan (fpm@crash.cts.com): + * Fixed uninitialized variable problem and added code to deal + * with DMA page boundaries in isa_dmarangecheck(). Fixed word + * mode DMA count compution and reorganized DMA setup code in + * isa_dmastart() + */ + +#include <sys/param.h> +#include <sys/systm.h> /* isn't it a joy */ +#include <sys/kernel.h> /* to have three of these */ +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/buf.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/malloc.h> +#include <sys/rlist.h> +#include <machine/segments.h> +#include <vm/vm.h> +#include <machine/spl.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/isa.h> +#include <i386/isa/icu.h> +#include <i386/isa/ic/i8237.h> +#include <i386/isa/ic/i8042.h> +#include "vector.h" + +/* +** Register definitions for DMA controller 1 (channels 0..3): +*/ +#define DMA1_CHN(c) (IO_DMA1 + 1*(2*(c))) /* addr reg for channel c */ +#define DMA1_SMSK (IO_DMA1 + 1*10) /* single mask register */ +#define DMA1_MODE (IO_DMA1 + 1*11) /* mode register */ +#define DMA1_FFC (IO_DMA1 + 1*12) /* clear first/last FF */ + +/* +** Register definitions for DMA controller 2 (channels 4..7): +*/ +#define DMA2_CHN(c) (IO_DMA2 + 2*(2*(c))) /* addr reg for channel c */ +#define DMA2_SMSK (IO_DMA2 + 2*10) /* single mask register */ +#define DMA2_MODE (IO_DMA2 + 2*11) /* mode register */ +#define DMA2_FFC (IO_DMA2 + 2*12) /* clear first/last FF */ + +/* + * Bits to specify the type and amount of conflict checking. + */ +#define CC_ATTACH (1 << 0) +#define CC_DRQ (1 << 1) +#define CC_IOADDR (1 << 2) +#define CC_IRQ (1 << 3) +#define CC_MEMADDR (1 << 4) + +/* + * XXX these defines should be in a central place. + */ +#define read_eflags() ({u_long ef; \ + __asm("pushfl; popl %0" : "=a" (ef)); \ + ef; }) +#define write_eflags(ef) __asm("pushl %0; popfl" : : "a" ((u_long)(ef))) + +u_long *intr_countp[ICU_LEN]; +inthand2_t *intr_handler[ICU_LEN]; +u_int intr_mask[ICU_LEN]; +int intr_unit[ICU_LEN]; + +static inthand_t *fastintr[ICU_LEN] = { + &IDTVEC(fastintr0), &IDTVEC(fastintr1), + &IDTVEC(fastintr2), &IDTVEC(fastintr3), + &IDTVEC(fastintr4), &IDTVEC(fastintr5), + &IDTVEC(fastintr6), &IDTVEC(fastintr7), + &IDTVEC(fastintr8), &IDTVEC(fastintr9), + &IDTVEC(fastintr10), &IDTVEC(fastintr11), + &IDTVEC(fastintr12), &IDTVEC(fastintr13), + &IDTVEC(fastintr14), &IDTVEC(fastintr15) +}; + +static inthand_t *slowintr[ICU_LEN] = { + &IDTVEC(intr0), &IDTVEC(intr1), &IDTVEC(intr2), &IDTVEC(intr3), + &IDTVEC(intr4), &IDTVEC(intr5), &IDTVEC(intr6), &IDTVEC(intr7), + &IDTVEC(intr8), &IDTVEC(intr9), &IDTVEC(intr10), &IDTVEC(intr11), + &IDTVEC(intr12), &IDTVEC(intr13), &IDTVEC(intr14), &IDTVEC(intr15) +}; + +static void config_isadev __P((struct isa_device *isdp, u_int *mp)); +static void conflict __P((struct isa_device *dvp, struct isa_device *tmpdvp, + int item, char const *whatnot, char const *reason, + char const *format)); +static int haveseen __P((struct isa_device *dvp, struct isa_device *tmpdvp, + u_int checkbits)); +static int haveseen_isadev __P((struct isa_device *dvp, u_int checkbits)); +static inthand2_t isa_strayintr; +static void register_imask __P((struct isa_device *dvp, u_int mask)); + +/* + * print a conflict message + */ +static void +conflict(dvp, tmpdvp, item, whatnot, reason, format) + struct isa_device *dvp; + struct isa_device *tmpdvp; + int item; + char const *whatnot; + char const *reason; + char const *format; +{ + printf("%s%d not %sed due to %s conflict with %s%d at ", + dvp->id_driver->name, dvp->id_unit, whatnot, reason, + tmpdvp->id_driver->name, tmpdvp->id_unit); + printf(format, item); + printf("\n"); +} + +/* + * Check to see if things are alread in use, like IRQ's, I/O addresses + * and Memory addresses. + */ +static int +haveseen(dvp, tmpdvp, checkbits) + struct isa_device *dvp; + struct isa_device *tmpdvp; + u_int checkbits; +{ + int status = 0; + + /* + * Only check against devices that have already been found + */ + if (tmpdvp->id_alive) { + char const *whatnot; + + whatnot = checkbits & CC_ATTACH ? "attach" : "probe"; + /* + * Check for I/O address conflict. We can only check the + * starting address of the device against the range of the + * device that has already been probed since we do not + * know how many I/O addresses this device uses. + */ + if (checkbits & CC_IOADDR && tmpdvp->id_alive != -1) { + if ((dvp->id_iobase >= tmpdvp->id_iobase) && + (dvp->id_iobase <= + (tmpdvp->id_iobase + tmpdvp->id_alive - 1))) { + conflict(dvp, tmpdvp, dvp->id_iobase, whatnot, + "I/O address", "0x%x"); + status = 1; + } + } + /* + * Check for Memory address conflict. We can check for + * range overlap, but it will not catch all cases since the + * driver may adjust the msize paramater during probe, for + * now we just check that the starting address does not + * fall within any allocated region. + * XXX could add a second check after the probe for overlap, + * since at that time we would know the full range. + * XXX KERNBASE is a hack, we should have vaddr in the table! + */ + if (checkbits & CC_MEMADDR && tmpdvp->id_maddr) { + if ((KERNBASE + dvp->id_maddr >= tmpdvp->id_maddr) && + (KERNBASE + dvp->id_maddr <= + (tmpdvp->id_maddr + tmpdvp->id_msize - 1))) { + conflict(dvp, tmpdvp, (int)dvp->id_maddr, + whatnot, "maddr", "0x%x"); + status = 1; + } + } + /* + * Check for IRQ conflicts. + */ + if (checkbits & CC_IRQ && tmpdvp->id_irq) { + if (tmpdvp->id_irq == dvp->id_irq) { + conflict(dvp, tmpdvp, ffs(dvp->id_irq) - 1, + whatnot, "irq", "%d"); + status = 1; + } + } + /* + * Check for DRQ conflicts. + */ + if (checkbits & CC_DRQ && tmpdvp->id_drq != -1) { + if (tmpdvp->id_drq == dvp->id_drq) { + conflict(dvp, tmpdvp, dvp->id_drq, whatnot, + "drq", "%d"); + status = 1; + } + } + } + return (status); +} + +/* + * Search through all the isa_devtab_* tables looking for anything that + * conflicts with the current device. + */ +static int +haveseen_isadev(dvp, checkbits) + struct isa_device *dvp; + u_int checkbits; +{ + struct isa_device *tmpdvp; + int status = 0; + + for (tmpdvp = isa_devtab_tty; tmpdvp->id_driver; tmpdvp++) + status |= haveseen(dvp, tmpdvp, checkbits); + for (tmpdvp = isa_devtab_bio; tmpdvp->id_driver; tmpdvp++) + status |= haveseen(dvp, tmpdvp, checkbits); + for (tmpdvp = isa_devtab_net; tmpdvp->id_driver; tmpdvp++) + status |= haveseen(dvp, tmpdvp, checkbits); + for (tmpdvp = isa_devtab_null; tmpdvp->id_driver; tmpdvp++) + status |= haveseen(dvp, tmpdvp, checkbits); + return(status); +} + +/* + * Configure all ISA devices + */ +void +isa_configure() { + struct isa_device *dvp; + + splhigh(); + enable_intr(); + INTREN(IRQ_SLAVE); + printf("Probing for devices on the ISA bus:\n"); + for (dvp = isa_devtab_tty; dvp->id_driver; dvp++) + config_isadev(dvp, &tty_imask); + for (dvp = isa_devtab_bio; dvp->id_driver; dvp++) + config_isadev(dvp, &bio_imask); + for (dvp = isa_devtab_net; dvp->id_driver; dvp++) + config_isadev(dvp, &net_imask); + for (dvp = isa_devtab_null; dvp->id_driver; dvp++) + config_isadev(dvp, (u_int *)NULL); + bio_imask |= SWI_CLOCK_MASK; + net_imask |= SWI_NET_MASK; + tty_imask |= SWI_TTY_MASK; + +/* + * XXX we should really add the tty device to net_imask when the line is + * switched to SLIPDISC, and then remove it when it is switched away from + * SLIPDISC. No need to block out ALL ttys during a splimp when only one + * of them is running slip. + * + * XXX actually, blocking all ttys during a splimp doesn't matter so much + * with sio because the serial interrupt layer doesn't use tty_imask. Only + * non-serial ttys suffer. It's more stupid that ALL 'net's are blocked + * during spltty. + */ +#include "sl.h" +#if NSL > 0 + net_imask |= tty_imask; + tty_imask = net_imask; +#endif + /* bio_imask |= tty_imask ; can some tty devices use buffers? */ +#ifdef DIAGNOSTIC + printf("bio_imask %x tty_imask %x net_imask %x\n", + bio_imask, tty_imask, net_imask); +#endif + /* + * Finish initializing intr_mask[]. Note that the partly + * constructed masks aren't actually used since we're at splhigh. + * For fully dynamic initialization, register_intr() and + * unregister_intr() will have to adjust the masks for _all_ + * interrupts and for tty_imask, etc. + */ + for (dvp = isa_devtab_tty; dvp->id_driver; dvp++) + register_imask(dvp, tty_imask); + for (dvp = isa_devtab_bio; dvp->id_driver; dvp++) + register_imask(dvp, bio_imask); + for (dvp = isa_devtab_net; dvp->id_driver; dvp++) + register_imask(dvp, net_imask); + for (dvp = isa_devtab_null; dvp->id_driver; dvp++) + register_imask(dvp, SWI_CLOCK_MASK); + splnone(); +} + +/* + * Configure an ISA device. + */ +static void +config_isadev(isdp, mp) + struct isa_device *isdp; + u_int *mp; +{ + u_int checkbits; + int id_alive; + struct isa_driver *dp = isdp->id_driver; + + checkbits = 0; +#ifndef ALLOW_CONFLICT_DRQ + checkbits |= CC_DRQ; +#endif +#ifndef ALLOW_CONFLICT_IOADDR + checkbits |= CC_IOADDR; +#endif +#ifndef ALLOW_CONFLICT_MEMADDR + checkbits |= CC_MEMADDR; +#endif + if (haveseen_isadev(isdp, checkbits)) + return; + if (isdp->id_maddr) { + isdp->id_maddr -= 0xa0000; /* XXX should be a define */ + isdp->id_maddr += atdevbase; + } + id_alive = (*dp->probe)(isdp); + if (id_alive) { + /* + * Only print the I/O address range if id_alive != -1 + * Right now this is a temporary fix just for the new + * NPX code so that if it finds a 486 that can use trap + * 16 it will not report I/O addresses. + * Rod Grimes 04/26/94 + */ + printf("%s%d", dp->name, isdp->id_unit); + if (id_alive != -1) { + printf(" at 0x%x", isdp->id_iobase); + if ((isdp->id_iobase + id_alive - 1) != + isdp->id_iobase) { + printf("-0x%x", + isdp->id_iobase + id_alive - 1); + } + } + if (isdp->id_irq) + printf(" irq %d", ffs(isdp->id_irq) - 1); + if (isdp->id_drq != -1) + printf(" drq %d", isdp->id_drq); + if (isdp->id_maddr) + printf(" maddr 0x%lx", kvtop(isdp->id_maddr)); + if (isdp->id_msize) + printf(" msize %d", isdp->id_msize); + if (isdp->id_flags) + printf(" flags 0x%x", isdp->id_flags); + if (isdp->id_iobase) { + if (isdp->id_iobase < 0x100) { + printf(" on motherboard\n"); + } else { + if (isdp->id_iobase >= 0x1000) { + printf (" on eisa\n"); + } else { + printf (" on isa\n"); + } + } + } + /* + * Check for conflicts again. The driver may have changed + * *dvp. We should weaken the early check since the + * driver may have been able to change *dvp to avoid + * conflicts if given a chance. We already skip the early + * check for IRQs and force a check for IRQs in the next + * group of checks. + */ + checkbits |= CC_IRQ; + if (haveseen_isadev(isdp, checkbits)) + return; + isdp->id_alive = id_alive; + (*dp->attach)(isdp); + if (isdp->id_irq) { + if (mp) + INTRMASK(*mp, isdp->id_irq); + register_intr(ffs(isdp->id_irq) - 1, isdp->id_id, + isdp->id_ri_flags, isdp->id_intr, + mp ? *mp : 0, isdp->id_unit); + INTREN(isdp->id_irq); + } + } else { + printf("%s%d not found", dp->name, isdp->id_unit); + if (isdp->id_iobase) { + printf(" at 0x%x", isdp->id_iobase); + } + printf("\n"); + } +} + +/* + * Fill in default interrupt table (in case of spuruious interrupt + * during configuration of kernel, setup interrupt control unit + */ +void +isa_defaultirq() +{ + int i; + + /* icu vectors */ + for (i = 0; i < ICU_LEN; i++) + unregister_intr(i, (inthand2_t *)NULL); + + /* initialize 8259's */ + outb(IO_ICU1, 0x11); /* reset; program device, four bytes */ + outb(IO_ICU1+1, NRSVIDT); /* starting at this vector index */ + outb(IO_ICU1+1, 1<<2); /* slave on line 2 */ +#ifdef AUTO_EOI_1 + outb(IO_ICU1+1, 2 | 1); /* auto EOI, 8086 mode */ +#else + outb(IO_ICU1+1, 1); /* 8086 mode */ +#endif + outb(IO_ICU1+1, 0xff); /* leave interrupts masked */ + outb(IO_ICU1, 0x0a); /* default to IRR on read */ + outb(IO_ICU1, 0xc0 | (3 - 1)); /* pri order 3-7, 0-2 (com2 first) */ + + outb(IO_ICU2, 0x11); /* reset; program device, four bytes */ + outb(IO_ICU2+1, NRSVIDT+8); /* staring at this vector index */ + outb(IO_ICU2+1,2); /* my slave id is 2 */ +#ifdef AUTO_EOI_2 + outb(IO_ICU2+1, 2 | 1); /* auto EOI, 8086 mode */ +#else + outb(IO_ICU2+1,1); /* 8086 mode */ +#endif + outb(IO_ICU2+1, 0xff); /* leave interrupts masked */ + outb(IO_ICU2, 0x0a); /* default to IRR on read */ +} + +/* region of physical memory known to be contiguous */ +vm_offset_t isaphysmem; +static caddr_t dma_bounce[8]; /* XXX */ +static char bounced[8]; /* XXX */ +#define MAXDMASZ 512 /* XXX */ + +/* high byte of address is stored in this port for i-th dma channel */ +static short dmapageport[8] = + { 0x87, 0x83, 0x81, 0x82, 0x8f, 0x8b, 0x89, 0x8a }; + +/* + * isa_dmacascade(): program 8237 DMA controller channel to accept + * external dma control by a board. + */ +void isa_dmacascade(unsigned chan) +{ + if (chan > 7) + panic("isa_dmacascade: impossible request"); + + /* set dma channel mode, and set dma channel mode */ + if ((chan & 4) == 0) { + outb(DMA1_MODE, DMA37MD_CASCADE | chan); + outb(DMA1_SMSK, chan); + } else { + outb(DMA2_MODE, DMA37MD_CASCADE | (chan & 3)); + outb(DMA2_SMSK, chan & 3); + } +} + +static int +isa_dmarangecheck(caddr_t va, unsigned length, unsigned chan); + +/* + * isa_dmastart(): program 8237 DMA controller channel, avoid page alignment + * problems by using a bounce buffer. + */ +void isa_dmastart(int flags, caddr_t addr, unsigned nbytes, unsigned chan) +{ vm_offset_t phys; + int waport; + caddr_t newaddr; + + if ( chan > 7 + || (chan < 4 && nbytes > (1<<16)) + || (chan >= 4 && (nbytes > (1<<17) || (u_int)addr & 1))) + panic("isa_dmastart: impossible request"); + + if (isa_dmarangecheck(addr, nbytes, chan)) { + if (dma_bounce[chan] == 0) + dma_bounce[chan] = + /*(caddr_t)malloc(MAXDMASZ, M_TEMP, M_WAITOK);*/ + (caddr_t) isaphysmem + NBPG*chan; + bounced[chan] = 1; + newaddr = dma_bounce[chan]; + *(int *) newaddr = 0; /* XXX */ + + /* copy bounce buffer on write */ + if (!(flags & B_READ)) + bcopy(addr, newaddr, nbytes); + addr = newaddr; + } + + /* translate to physical */ + phys = pmap_extract(pmap_kernel(), (vm_offset_t)addr); + + if ((chan & 4) == 0) { + /* + * Program one of DMA channels 0..3. These are + * byte mode channels. + */ + /* set dma channel mode, and reset address ff */ + if (flags & B_READ) + outb(DMA1_MODE, DMA37MD_SINGLE|DMA37MD_WRITE|chan); + else + outb(DMA1_MODE, DMA37MD_SINGLE|DMA37MD_READ|chan); + outb(DMA1_FFC, 0); + + /* send start address */ + waport = DMA1_CHN(chan); + outb(waport, phys); + outb(waport, phys>>8); + outb(dmapageport[chan], phys>>16); + + /* send count */ + outb(waport + 1, --nbytes); + outb(waport + 1, nbytes>>8); + + /* unmask channel */ + outb(DMA1_SMSK, chan); + } else { + /* + * Program one of DMA channels 4..7. These are + * word mode channels. + */ + /* set dma channel mode, and reset address ff */ + if (flags & B_READ) + outb(DMA2_MODE, DMA37MD_SINGLE|DMA37MD_WRITE|(chan&3)); + else + outb(DMA2_MODE, DMA37MD_SINGLE|DMA37MD_READ|(chan&3)); + outb(DMA2_FFC, 0); + + /* send start address */ + waport = DMA2_CHN(chan - 4); + outb(waport, phys>>1); + outb(waport, phys>>9); + outb(dmapageport[chan], phys>>16); + + /* send count */ + nbytes >>= 1; + outb(waport + 2, --nbytes); + outb(waport + 2, nbytes>>8); + + /* unmask channel */ + outb(DMA2_SMSK, chan & 3); + } +} + +void isa_dmadone(int flags, caddr_t addr, int nbytes, int chan) +{ + + /* copy bounce buffer on read */ + /*if ((flags & (B_PHYS|B_READ)) == (B_PHYS|B_READ))*/ + if (bounced[chan]) { + bcopy(dma_bounce[chan], addr, nbytes); + bounced[chan] = 0; + } +} + +/* + * Check for problems with the address range of a DMA transfer + * (non-contiguous physical pages, outside of bus address space, + * crossing DMA page boundaries). + * Return true if special handling needed. + */ + +static int +isa_dmarangecheck(caddr_t va, unsigned length, unsigned chan) { + vm_offset_t phys, priorpage = 0, endva; + u_int dma_pgmsk = (chan & 4) ? ~(128*1024-1) : ~(64*1024-1); + + endva = (vm_offset_t)round_page(va + length); + for (; va < (caddr_t) endva ; va += NBPG) { + phys = trunc_page(pmap_extract(pmap_kernel(), (vm_offset_t)va)); +#define ISARAM_END RAM_END + if (phys == 0) + panic("isa_dmacheck: no physical page present"); + if (phys >= ISARAM_END) + return (1); + if (priorpage) { + if (priorpage + NBPG != phys) + return (1); + /* check if crossing a DMA page boundary */ + if (((u_int)priorpage ^ (u_int)phys) & dma_pgmsk) + return (1); + } + priorpage = phys; + } + return (0); +} + +/* head of queue waiting for physmem to become available */ +struct buf isa_physmemq; + +/* blocked waiting for resource to become free for exclusive use */ +static isaphysmemflag; +/* if waited for and call requested when free (B_CALL) */ +static void (*isaphysmemunblock)(); /* needs to be a list */ + +/* + * Allocate contiguous physical memory for transfer, returning + * a *virtual* address to region. May block waiting for resource. + * (assumed to be called at splbio()) + */ +caddr_t +isa_allocphysmem(caddr_t va, unsigned length, void (*func)()) { + + isaphysmemunblock = func; + while (isaphysmemflag & B_BUSY) { + isaphysmemflag |= B_WANTED; + tsleep((caddr_t)&isaphysmemflag, PRIBIO, "isaphys", 0); + } + isaphysmemflag |= B_BUSY; + + return((caddr_t)isaphysmem); +} + +/* + * Free contiguous physical memory used for transfer. + * (assumed to be called at splbio()) + */ +void +isa_freephysmem(caddr_t va, unsigned length) { + + isaphysmemflag &= ~B_BUSY; + if (isaphysmemflag & B_WANTED) { + isaphysmemflag &= B_WANTED; + wakeup((caddr_t)&isaphysmemflag); + if (isaphysmemunblock) + (*isaphysmemunblock)(); + } +} + +#define NMI_PARITY (1 << 7) +#define NMI_IOCHAN (1 << 6) +#define ENMI_WATCHDOG (1 << 7) +#define ENMI_BUSTIMER (1 << 6) +#define ENMI_IOSTATUS (1 << 5) + +/* + * Handle a NMI, possibly a machine check. + * return true to panic system, false to ignore. + */ +int +isa_nmi(cd) + int cd; +{ + int isa_port = inb(0x61); + int eisa_port = inb(0x461); + if(isa_port & NMI_PARITY) { + panic("RAM parity error, likely hardware failure."); + } else if(isa_port & NMI_IOCHAN) { + panic("I/O channel check, likely hardware failure."); + } else if(eisa_port & ENMI_WATCHDOG) { + panic("EISA watchdog timer expired, likely hardware failure."); + } else if(eisa_port & ENMI_BUSTIMER) { + panic("EISA bus timeout, likely hardware failure."); + } else if(eisa_port & ENMI_IOSTATUS) { + panic("EISA I/O port status error."); + } else { + printf("\nNMI ISA %x, EISA %x\n", isa_port, eisa_port); + return(0); + } +} + +/* + * Caught a stray interrupt, notify + */ +static void +isa_strayintr(d) + int d; +{ + + /* DON'T BOTHER FOR NOW! */ + /* for some reason, we get bursts of intr #7, even if not enabled! */ + /* + * Well the reason you got bursts of intr #7 is because someone + * raised an interrupt line and dropped it before the 8259 could + * prioritize it. This is documented in the intel data book. This + * means you have BAD hardware! I have changed this so that only + * the first 5 get logged, then it quits logging them, and puts + * out a special message. rgrimes 3/25/1993 + */ + /* + * XXX TODO print a different message for #7 if it is for a + * glitch. Glitches can be distinguished from real #7's by + * testing that the in-service bit is _not_ set. The test + * must be done before sending an EOI so it can't be done if + * we are using AUTO_EOI_1. + */ + if (intrcnt[NR_DEVICES + d] <= 5) + log(LOG_ERR, "stray irq %d\n", d); + if (intrcnt[NR_DEVICES + d] == 5) + log(LOG_CRIT, + "too many stray irq %d's; not logging any more\n", d); +} + +/* + * find an ISA device in a given isa_devtab_* table, given + * the table to search, the expected id_driver entry, and the unit number. + * + * this function is defined in isa_device.h, and this location is debatable; + * i put it there because it's useless w/o, and directly operates on + * the other stuff in that file. + * + */ + +struct isa_device *find_isadev(table, driverp, unit) + struct isa_device *table; + struct isa_driver *driverp; + int unit; +{ + if (driverp == NULL) /* sanity check */ + return NULL; + + while ((table->id_driver != driverp) || (table->id_unit != unit)) { + if (table->id_driver == 0) + return NULL; + + table++; + } + + return table; +} + +/* + * Return nonzero if a (masked) irq is pending for a given device. + */ +int +isa_irq_pending(dvp) + struct isa_device *dvp; +{ + unsigned id_irq; + + id_irq = dvp->id_irq; + if (id_irq & 0xff) + return (inb(IO_ICU1) & id_irq); + return (inb(IO_ICU2) & (id_irq >> 8)); +} + +int +register_intr(intr, device_id, flags, handler, mask, unit) + int intr; + int device_id; + u_int flags; + inthand2_t *handler; + u_int mask; + int unit; +{ + char *cp; + u_long ef; + int id; + + if ((u_int)intr >= ICU_LEN || intr == 2 + || (u_int)device_id >= NR_DEVICES) + return (EINVAL); + if (intr_handler[intr] != isa_strayintr) + return (EBUSY); + ef = read_eflags(); + disable_intr(); + intr_countp[intr] = &intrcnt[device_id]; + intr_handler[intr] = handler; + intr_mask[intr] = mask | (1 << intr); + intr_unit[intr] = unit; + setidt(ICU_OFFSET + intr, + flags & RI_FAST ? fastintr[intr] : slowintr[intr], + SDT_SYS386IGT, SEL_KPL); + write_eflags(ef); + for (cp = intrnames, id = 0; id <= device_id; id++) + while (*cp++ != '\0') + ; + if (cp > eintrnames) + return (0); + if (intr < 10) { + cp[-3] = intr + '0'; + cp[-2] = ' '; + } else { + cp[-3] = '1'; + cp[-2] = intr - 10 + '0'; + } + return (0); +} + +static void +register_imask(dvp, mask) + struct isa_device *dvp; + u_int mask; +{ + if (dvp->id_alive && dvp->id_irq) { + int intr; + + intr = ffs(dvp->id_irq) - 1; + intr_mask[intr] = mask | (1 <<intr); + } +} + +int +unregister_intr(intr, handler) + int intr; + inthand2_t *handler; +{ + u_long ef; + + if ((u_int)intr >= ICU_LEN || handler != intr_handler[intr]) + return (EINVAL); + ef = read_eflags(); + disable_intr(); + intr_countp[intr] = &intrcnt[NR_DEVICES + intr]; + intr_handler[intr] = isa_strayintr; + intr_mask[intr] = HWI_MASK | SWI_MASK; + intr_unit[intr] = intr; + setidt(ICU_OFFSET + intr, slowintr[intr], SDT_SYS386IGT, SEL_KPL); + write_eflags(ef); + return (0); +} diff --git a/sys/i386/isa/isa.h b/sys/i386/isa/isa.h new file mode 100644 index 0000000..e2a26e7 --- /dev/null +++ b/sys/i386/isa/isa.h @@ -0,0 +1,181 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * from: @(#)isa.h 5.7 (Berkeley) 5/9/91 + * $Id: isa.h,v 1.4 1994/01/05 15:03:28 rgrimes Exp $ + */ + +#ifndef _I386_ISA_ISA_H_ +#define _I386_ISA_ISA_H_ 1 + +/* + * ISA Bus conventions + */ + +#ifndef LOCORE +#include <sys/cdefs.h> + +extern unsigned int atdevbase; /* offset in virtual memory of ISA io mem */ +unsigned char rtcin __P((int)); +#endif + + +/* + * Input / Output Port Assignments + */ + +#ifndef IO_BEGIN +#define IO_ISABEGIN 0x000 /* 0x000 - Beginning of I/O Registers */ + + /* CPU Board */ +#define IO_DMA1 0x000 /* 8237A DMA Controller #1 */ +#define IO_ICU1 0x020 /* 8259A Interrupt Controller #1 */ +#define IO_TIMER1 0x040 /* 8253 Timer #1 */ +#define IO_TIMER2 0x048 /* 8253 Timer #2 */ +#define IO_KBD 0x060 /* 8042 Keyboard */ +#define IO_PPI 0x061 /* Programmabel Peripheral Interface */ +#define IO_RTC 0x070 /* RTC */ +#define IO_NMI IO_RTC /* NMI Control */ +#define IO_DMAPG 0x080 /* DMA Page Registers */ +#define IO_ICU2 0x0A0 /* 8259A Interrupt Controller #2 */ +#define IO_DMA2 0x0C0 /* 8237A DMA Controller #2 */ +#define IO_NPX 0x0F0 /* Numeric Coprocessor */ + + /* Cards */ + /* 0x100 - 0x16F Open */ + +#define IO_WD2 0x170 /* Secondary Fixed Disk Controller */ + + /* 0x178 - 0x1EF Open */ + +#define IO_WD1 0x1f0 /* Primary Fixed Disk Controller */ +#define IO_GAME 0x200 /* Game Controller */ + + /* 0x208 - 0x277 Open */ + +#define IO_LPT2 0x278 /* Parallel Port #2 */ + + /* 0x280 - 0x2E7 Open */ + +#define IO_COM4 0x2e8 /* COM4 i/o address */ + + /* 0x2F0 - 0x2F7 Open */ + +#define IO_COM2 0x2f8 /* COM2 i/o address */ + /* 0x300 - 0x32F Open */ + +#define IO_BT0 0x330 /* bustek 742a default addr. */ +#define IO_AHA0 0x330 /* adaptec 1542 default addr. */ +#define IO_UHA0 0x330 /* ultrastore 14f default addr. */ +#define IO_BT1 0x334 /* bustek 742a default addr. */ +#define IO_AHA1 0x334 /* adaptec 1542 default addr. */ + /* 0x338 - 0x36F Open */ + +#define IO_FD2 0x370 /* secondary base i/o address */ +#define IO_LPT1 0x378 /* Parallel Port #1 */ + + /* 0x380 - 0x3AF Open */ + +#define IO_MDA 0x3B0 /* Monochome Adapter */ +#define IO_LPT3 0x3BC /* Monochome Adapter Printer Port */ +#define IO_VGA 0x3C0 /* E/VGA Ports */ +#define IO_CGA 0x3D0 /* CGA Ports */ + + /* 0x3E0 - 0x3E7 Open */ + +#define IO_COM3 0x3e8 /* COM3 i/o address */ +#define IO_FD1 0x3f0 /* primary base i/o address */ +#define IO_COM1 0x3f8 /* COM1 i/o address */ + +#define IO_ISAEND 0x3FF /* - 0x3FF End of I/O Registers */ +#endif IO_ISABEGIN + +/* + * Input / Output Port Sizes - these are from several sources, and tend + * to be the larger of what was found, ie COM ports can be 4, but some + * boards do not fully decode the address, thus 8 ports are used. + */ + +#ifndef IO_ISASIZES +#define IO_ISASIZES + +#define IO_COMSIZE 8 /* 8250, 16X50 com controllers (4?) */ +#define IO_CGASIZE 16 /* CGA controllers */ +#define IO_DMASIZE 16 /* 8237 DMA controllers */ +#define IO_DPGSIZE 32 /* 74LS612 DMA page reisters */ +#define IO_FDCSIZE 8 /* Nec765 floppy controllers */ +#define IO_WDCSIZE 8 /* WD compatible disk controllers */ +#define IO_GAMSIZE 16 /* AT compatible game controllers */ +#define IO_ICUSIZE 16 /* 8259A interrupt controllers */ +#define IO_KBDSIZE 16 /* 8042 Keyboard controllers */ +#define IO_LPTSIZE 8 /* LPT controllers, some use only 4 */ +#define IO_MDASIZE 16 /* Monochrome display controllers */ +#define IO_RTCSIZE 16 /* CMOS real time clock, NMI control */ +#define IO_TMRSIZE 16 /* 8253 programmable timers */ +#define IO_NPXSIZE 16 /* 80387/80487 NPX registers */ +#define IO_VGASIZE 16 /* VGA controllers */ + +#endif /* IO_ISASIZES */ + +/* + * Input / Output Memory Physical Addresses + */ + +#ifndef IOM_BEGIN +#define IOM_BEGIN 0x0a0000 /* Start of I/O Memory "hole" */ +#define IOM_END 0x100000 /* End of I/O Memory "hole" */ +#define IOM_SIZE (IOM_END - IOM_BEGIN) +#endif IOM_BEGIN + +/* + * RAM Physical Address Space (ignoring the above mentioned "hole") + */ + +#ifndef RAM_BEGIN +#define RAM_BEGIN 0x0000000 /* Start of RAM Memory */ +#define RAM_END 0x1000000 /* End of RAM Memory */ +#define RAM_SIZE (RAM_END - RAM_BEGIN) +#endif RAM_BEGIN + +/* + * Oddball Physical Memory Addresses + */ +#ifndef COMPAQ_RAMRELOC +#define COMPAQ_RAMRELOC 0x80c00000 /* Compaq RAM relocation/diag */ +#define COMPAQ_RAMSETUP 0x80c00002 /* Compaq RAM setup */ +#define WEITEK_FPU 0xC0000000 /* WTL 2167 */ +#define CYRIX_EMC 0xC0000000 /* Cyrix EMC */ +#endif COMPAQ_RAMRELOC +#endif /* _I386_ISA_ISA_H_ */ diff --git a/sys/i386/isa/isa_device.h b/sys/i386/isa/isa_device.h new file mode 100644 index 0000000..3cd86ef --- /dev/null +++ b/sys/i386/isa/isa_device.h @@ -0,0 +1,131 @@ +/*- + * 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. + * + * from: @(#)isa_device.h 7.1 (Berkeley) 5/9/91 + * $Id: isa_device.h,v 1.7 1994/08/20 03:48:41 davidg Exp $ + */ + +#ifndef _I386_ISA_ISA_DEVICE_H_ +#define _I386_ISA_ISA_DEVICE_H_ 1 + +/* + * ISA Bus Autoconfiguration + */ + +#define IDTVEC(name) __CONCAT(X,name) + +/* + * Type of the first (asm) part of an interrupt handler. + */ +typedef void inthand_t __P((u_int cs, u_int ef, u_int esp, u_int ss)); + +/* + * Usual type of the second (C) part of an interrupt handler. Some bogus + * ones need the arg to be the interrupt frame (and not a copy of it, which + * is all that is possible in C). + */ +typedef void inthand2_t __P((int unit)); + +/* + * Per device structure. + */ +struct isa_device { + int id_id; /* device id */ + struct isa_driver *id_driver; + short id_iobase; /* base i/o address */ + u_short id_irq; /* interrupt request */ + short id_drq; /* DMA request */ + caddr_t id_maddr; /* physical i/o memory address on bus (if any)*/ + int id_msize; /* size of i/o memory */ + inthand2_t *id_intr; /* interrupt interface routine */ + int id_unit; /* unit number */ + int id_flags; /* flags */ + int id_scsiid; /* scsi id if needed */ + int id_alive; /* device is present */ +#define RI_FAST 1 /* fast interrupt handler */ + u_int id_ri_flags; /* flags for register_intr() */ +}; + +/* + * Per-driver structure. + * + * Each device driver defines entries for a set of routines + * as well as an array of types which are acceptable to it. + * These are used at boot time by the configuration program. + */ +struct isa_driver { + int (*probe) __P((struct isa_device *idp)); + /* test whether device is present */ + int (*attach) __P((struct isa_device *idp)); + /* setup driver for a device */ + char *name; /* device name */ +}; + +extern char eintrnames[]; /* end of intrnames[] */ +extern u_long intrcnt[]; /* counts for for each device and stray */ +extern char intrnames[]; /* string table containing device names */ +extern u_long *intr_countp[]; /* indirectors into intrcnt[] */ +extern inthand2_t *intr_handler[]; /* C entry points of intr handlers */ +extern u_int intr_mask[]; /* sets of intrs masked during handling of 1 */ +extern int intr_unit[]; /* cookies to pass to intr handlers */ + +extern struct isa_device isa_devtab_bio[], isa_devtab_tty[], isa_devtab_net[], + isa_devtab_null[], isa_biotab_wdc[], isa_biotab_fdc[]; + +inthand_t + IDTVEC(fastintr0), IDTVEC(fastintr1), + IDTVEC(fastintr2), IDTVEC(fastintr3), + IDTVEC(fastintr4), IDTVEC(fastintr5), + IDTVEC(fastintr6), IDTVEC(fastintr7), + IDTVEC(fastintr8), IDTVEC(fastintr9), + IDTVEC(fastintr10), IDTVEC(fastintr11), + IDTVEC(fastintr12), IDTVEC(fastintr13), + IDTVEC(fastintr14), IDTVEC(fastintr15); +struct isa_device *find_isadev __P((struct isa_device *table, + struct isa_driver *driverp, int unit)); +inthand_t + IDTVEC(intr0), IDTVEC(intr1), IDTVEC(intr2), IDTVEC(intr3), + IDTVEC(intr4), IDTVEC(intr5), IDTVEC(intr6), IDTVEC(intr7), + IDTVEC(intr8), IDTVEC(intr9), IDTVEC(intr10), IDTVEC(intr11), + IDTVEC(intr12), IDTVEC(intr13), IDTVEC(intr14), IDTVEC(intr15); +void isa_configure __P((void)); +void isa_defaultirq __P((void)); +void isa_dmacascade __P((unsigned chan)); +void isa_dmadone __P((int, caddr_t, int, int)); +void isa_dmastart __P((int, caddr_t, unsigned, unsigned)); +int isa_irq_pending __P((struct isa_device *dvp)); +int isa_nmi __P((int cd)); +int register_intr __P((int intr, int device_id, u_int flags, + inthand2_t *handler, u_int mask, int unit)); +int unregister_intr __P((int intr, inthand2_t *handler)); + +#endif /* _I386_ISA_ISA_DEVICE_H_ */ diff --git a/sys/i386/isa/iso8859.font b/sys/i386/isa/iso8859.font new file mode 100644 index 0000000..89c9b29 --- /dev/null +++ b/sys/i386/isa/iso8859.font @@ -0,0 +1,1243 @@ +/* + * Copyright (C) 1992, 1993, 1994 Søren Schmidt + * + * This program is free software; you may redistribute it and/or + * modify it, provided that it retain the above copyright notice + * and the following disclaimer. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Søren Schmidt Email: sos@login.dkuug.dk + * Tritonvej 36 UUCP: ...uunet!dkuug!login!sos + * DK9210 Aalborg SO Phone: +45 9814 8076 + * + * from:@(#)iso8859.font 1.1 940105 + * $Id: iso8859.font,v 1.4 1993/10/28 06:15:05 rgrimes Exp $ + */ + +char font_8x8[256*8] = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x7E, 0x81, 0xA5, 0x81, 0xBD, 0x99, 0x81, 0x7E, +0x7E, 0xFF, 0xDB, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E, +0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, +0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, +0x38, 0x7C, 0x38, 0xFE, 0xFE, 0x7C, 0x38, 0x7C, +0x10, 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x7C, +0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, +0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, +0x00, 0x3C, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00, +0xFF, 0xC3, 0x99, 0xBD, 0xBD, 0x99, 0xC3, 0xFF, +0x0F, 0x07, 0x0F, 0x7D, 0xCC, 0xCC, 0xCC, 0x78, +0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, +0x3F, 0x33, 0x3F, 0x30, 0x30, 0x70, 0xF0, 0xE0, +0x7F, 0x63, 0x7F, 0x63, 0x63, 0x67, 0xE6, 0xC0, +0x99, 0x5A, 0x3C, 0xE7, 0xE7, 0x3C, 0x5A, 0x99, +0x80, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0x80, 0x00, +0x02, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x02, 0x00, +0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, +0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00, +0x7F, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x00, +0x3E, 0x63, 0x38, 0x6C, 0x6C, 0x38, 0xCC, 0x78, +0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x00, +0x18, 0x3C, 0x7E, 0x18, 0x7E, 0x3C, 0x18, 0xFF, +0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, +0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, +0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, +0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00, +0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00, +0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00, +0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x00, 0x00, +0x00, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x30, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x00, +0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, +0x30, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x30, 0x00, +0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, +0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, +0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, +0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, +0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, +0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60, +0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, +0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, +0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00, +0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, +0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00, +0x78, 0xCC, 0x0C, 0x38, 0x0C, 0xCC, 0x78, 0x00, +0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, +0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, +0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00, +0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, +0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00, +0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00, +0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, +0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x60, +0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, +0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00, +0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, +0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, +0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00, +0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00, +0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, +0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, +0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, +0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, +0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, +0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3E, 0x00, +0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, +0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, +0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, +0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, +0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, +0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, +0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, +0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, +0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, +0x78, 0xCC, 0xCC, 0xCC, 0xDC, 0x78, 0x1C, 0x00, +0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00, +0x78, 0xCC, 0xE0, 0x70, 0x1C, 0xCC, 0x78, 0x00, +0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, +0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, +0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, +0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, +0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00, +0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x78, 0x00, +0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00, +0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00, +0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, +0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, +0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0xDC, 0x00, +0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00, +0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, +0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, +0x38, 0x6C, 0x60, 0xF0, 0x60, 0x60, 0xF0, 0x00, +0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, +0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, +0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, +0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, +0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, +0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, +0x00, 0x00, 0xCC, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, +0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, +0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, +0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, +0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, +0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0xF0, 0x00, +0x00, 0x00, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x00, +0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00, +0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, +0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, +0x00, 0x00, 0xC6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00, +0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, +0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, +0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00, +0x1C, 0x30, 0x30, 0xE0, 0x30, 0x30, 0x1C, 0x00, +0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, +0xE0, 0x30, 0x30, 0x1C, 0x30, 0x30, 0xE0, 0x00, +0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x7E, 0xFF, 0x7E, 0x18, 0x00, +0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55, 0xAA, +0x44, 0x44, 0x44, 0x44, 0x1F, 0x04, 0x04, 0x04, +0x7C, 0x40, 0x40, 0x40, 0x1F, 0x10, 0x10, 0x10, +0x38, 0x44, 0x44, 0x38, 0x1E, 0x11, 0x14, 0x13, +0x40, 0x40, 0x40, 0x7C, 0x1F, 0x10, 0x10, 0x10, +0x38, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, +0x44, 0x64, 0x4C, 0x44, 0x10, 0x10, 0x10, 0x1F, +0x44, 0x44, 0x28, 0x10, 0x1F, 0x04, 0x04, 0x04, +0x18, 0x18, 0x18, 0x18, 0xF8, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, +0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, +0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x00, 0x0C, 0x30, 0x60, 0x18, 0x0C, 0x7E, 0x00, +0x00, 0x30, 0x0C, 0x06, 0x18, 0x30, 0x7E, 0x00, +0x00, 0x00, 0x03, 0x3E, 0x36, 0x36, 0x6C, 0x00, +0x00, 0x00, 0x04, 0x7E, 0x10, 0x7E, 0x40, 0x00, +0x00, 0x1C, 0x30, 0x30, 0x30, 0x30, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x30, 0x00, 0x30, 0x30, 0x78, 0x78, 0x30, 0x00, +0x00, 0x00, 0x10, 0x7C, 0xC0, 0xC0, 0x7C, 0x10, +0x00, 0x38, 0x60, 0x60, 0xF0, 0x60, 0xFC, 0x00, +0x00, 0xC3, 0x3C, 0x66, 0x66, 0x3C, 0xC3, 0x00, +0x00, 0xCC, 0xCC, 0x78, 0x30, 0xFC, 0x30, 0x00, +0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, +0x7E, 0xC0, 0x7C, 0xC6, 0x7C, 0x06, 0xFC, 0x00, +0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x7C, 0x82, 0xBA, 0xA2, 0xBA, 0x82, 0x7C, 0x00, +0x1C, 0x06, 0x1E, 0x22, 0x1F, 0x3F, 0x00, 0x00, +0x00, 0x33, 0x66, 0xCC, 0x66, 0x33, 0x00, 0x00, +0x00, 0xFE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x7C, 0x82, 0xBA, 0xB2, 0xAA, 0x82, 0x7C, 0x00, +0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x38, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x10, 0x7C, 0x10, 0x00, 0x7C, 0x00, 0x00, +0x1C, 0x36, 0x06, 0x18, 0x3E, 0x00, 0x00, 0x00, +0x1E, 0x02, 0x0E, 0x02, 0x1E, 0x00, 0x00, 0x00, +0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0xC0, +0x7E, 0xCA, 0xCA, 0x7E, 0x0A, 0x0A, 0x0A, 0x00, +0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x30, +0x06, 0x0E, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, +0x0E, 0x11, 0x11, 0x11, 0x0E, 0x1F, 0x00, 0x00, +0x00, 0xCC, 0x66, 0x33, 0x66, 0xCC, 0x00, 0x00, +0x60, 0xE0, 0x66, 0x6C, 0x33, 0x67, 0x0F, 0x03, +0x60, 0xE0, 0x66, 0x6C, 0x36, 0x6A, 0x04, 0x0E, +0xF0, 0x20, 0x96, 0x6C, 0x33, 0x67, 0x0F, 0x03, +0x30, 0x00, 0x30, 0x60, 0xC0, 0xCC, 0x78, 0x00, +0x18, 0x0C, 0x30, 0x78, 0xCC, 0xFC, 0xCC, 0x00, +0x60, 0xC0, 0x30, 0x78, 0xCC, 0xFC, 0xCC, 0x00, +0x78, 0x84, 0x30, 0x78, 0xCC, 0xFC, 0xCC, 0x00, +0x66, 0x98, 0x30, 0x78, 0xCC, 0xFC, 0xCC, 0x00, +0xCC, 0x00, 0x30, 0x78, 0xCC, 0xFC, 0xCC, 0x00, +0x30, 0x48, 0x30, 0x78, 0xCC, 0xFC, 0xCC, 0x00, +0x3E, 0x78, 0x98, 0x9C, 0xF8, 0x98, 0x9E, 0x00, +0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x1C, 0x30, +0x30, 0x18, 0xFE, 0x62, 0x78, 0x62, 0xFE, 0x00, +0x18, 0x30, 0xFE, 0x62, 0x78, 0x62, 0xFE, 0x00, +0x38, 0x44, 0xFE, 0x62, 0x78, 0x62, 0xFE, 0x00, +0x66, 0x00, 0xFE, 0x62, 0x78, 0x62, 0xFE, 0x00, +0x60, 0x30, 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, +0x18, 0x30, 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, +0x78, 0x84, 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, +0xCC, 0x00, 0x78, 0x30, 0x30, 0x30, 0x78, 0x00, +0x78, 0x6C, 0x66, 0xF6, 0x66, 0x6C, 0x78, 0x00, +0x66, 0x98, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0x00, +0x30, 0x18, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, +0x18, 0x30, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, +0x38, 0x44, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, +0x66, 0x98, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, +0xC6, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, +0x00, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00, +0x06, 0x7C, 0xCE, 0x9A, 0xB2, 0xE6, 0x78, 0xC0, +0x60, 0x30, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, +0x18, 0x30, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, +0x78, 0x84, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, +0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, +0x18, 0x30, 0xCC, 0xCC, 0x78, 0x30, 0x78, 0x00, +0x60, 0x78, 0x6C, 0x78, 0x60, 0x60, 0x60, 0x00, +0x78, 0xCC, 0xC4, 0xDC, 0xC6, 0xC6, 0xDC, 0xC0, +0x30, 0x18, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0x18, 0x30, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0x78, 0x84, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0x66, 0x98, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0xCC, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0x30, 0x48, 0x38, 0x0C, 0x7C, 0xCC, 0x76, 0x00, +0x00, 0x00, 0xEC, 0x32, 0x7E, 0xB0, 0x6E, 0x00, +0x00, 0x00, 0x3C, 0x66, 0xC0, 0x66, 0x1C, 0x30, +0x30, 0x18, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, +0x18, 0x30, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, +0x78, 0x84, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, +0xCC, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, +0x60, 0x30, 0x00, 0x70, 0x30, 0x30, 0x78, 0x00, +0x18, 0x30, 0x00, 0x70, 0x30, 0x30, 0x78, 0x00, +0x70, 0x88, 0x00, 0x70, 0x30, 0x30, 0x78, 0x00, +0xCC, 0x00, 0x00, 0x70, 0x30, 0x30, 0x78, 0x00, +0x6C, 0x38, 0x6C, 0x0C, 0x6C, 0xCC, 0x78, 0x00, +0x66, 0x98, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, +0x60, 0x30, 0x00, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, +0x18, 0x30, 0x00, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, +0x38, 0x44, 0x00, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, +0x66, 0x98, 0x00, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, +0xC6, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, +0x00, 0x00, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x00, +0x00, 0x00, 0x06, 0x7C, 0xDE, 0xF6, 0x7C, 0xC0, +0x60, 0x30, 0x00, 0xCC, 0xCC, 0xCC, 0x76, 0x00, +0x18, 0x30, 0x00, 0xCC, 0xCC, 0xCC, 0x76, 0x00, +0x30, 0x48, 0x00, 0xCC, 0xCC, 0xCC, 0x76, 0x00, +0xCC, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x76, 0x00, +0x18, 0x30, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, +0xE0, 0x78, 0x6C, 0x66, 0x6C, 0x78, 0xE0, 0x00, +0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8 +}; + +char font_8x14[256*14] = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x7E, 0x81, 0xA5, 0x81, 0x81, 0xBD, 0x99, 0x81, +0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xFF, +0xDB, 0xFF, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x7F, 0x7F, +0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x08, 0x1C, 0x3E, 0x7F, 0x3E, +0x1C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x3C, 0x3C, 0xE7, 0xE7, 0xE7, 0x18, 0x18, +0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, +0x7E, 0xFF, 0xFF, 0x7E, 0x18, 0x18, 0x3C, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, +0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, +0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x24, 0x42, 0x42, 0x24, 0x3C, +0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, +0xC3, 0xDB, 0xBD, 0xBD, 0xDB, 0xC3, 0xFF, 0xFF, +0xFF, 0xFF, 0x00, 0x00, 0x1F, 0x07, 0x0D, 0x19, +0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, +0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x0A, 0x09, 0x09, 0x09, 0x0A, 0x08, 0x38, +0x78, 0x78, 0x30, 0x00, 0x00, 0x00, 0x1F, 0x11, +0x1F, 0x11, 0x11, 0x11, 0x13, 0x37, 0x77, 0x72, +0x20, 0x00, 0x00, 0x00, 0x18, 0x18, 0xDB, 0x3C, +0xE7, 0x3C, 0xDB, 0x18, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x40, 0x60, 0x70, 0x7C, 0x7F, 0x7C, +0x70, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, +0x01, 0x03, 0x07, 0x1F, 0x7F, 0x1F, 0x07, 0x03, +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, +0x7E, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, +0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, +0x33, 0x33, 0x00, 0x33, 0x33, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, +0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x3E, +0x63, 0x30, 0x1C, 0x36, 0x63, 0x63, 0x36, 0x1C, +0x06, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, +0x18, 0x18, 0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, +0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x06, 0x7F, 0x06, 0x0C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x30, +0x7F, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x60, 0x60, +0x7F, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x24, 0x42, 0xFF, 0x42, 0x24, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, +0x18, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, +0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x36, 0x36, 0x7F, 0x36, +0x36, 0x36, 0x7F, 0x36, 0x36, 0x00, 0x00, 0x00, +0x08, 0x08, 0x3E, 0x63, 0x60, 0x60, 0x3E, 0x03, +0x03, 0x63, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00, +0x00, 0x61, 0x63, 0x06, 0x0C, 0x18, 0x30, 0x63, +0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x36, +0x36, 0x1C, 0x3B, 0x6E, 0x66, 0x66, 0x3B, 0x00, +0x00, 0x00, 0x00, 0x30, 0x30, 0x30, 0x60, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, +0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, +0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x66, 0x3C, 0x7E, 0x3C, 0x66, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, +0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x06, 0x0C, +0x18, 0x30, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3E, 0x63, 0x67, 0x6F, 0x7B, 0x73, +0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x1C, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, +0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, +0x03, 0x06, 0x0C, 0x18, 0x30, 0x63, 0x7F, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x03, 0x03, +0x1E, 0x03, 0x03, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x06, 0x0E, 0x1E, 0x36, 0x66, 0x7F, +0x06, 0x06, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, +0x7E, 0x60, 0x60, 0x60, 0x7E, 0x03, 0x03, 0x63, +0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x30, +0x60, 0x60, 0x7E, 0x63, 0x63, 0x63, 0x3E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7F, 0x63, 0x03, 0x06, +0x0C, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x3E, 0x63, +0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x3E, 0x63, 0x63, 0x63, 0x3F, 0x03, 0x03, 0x06, +0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, +0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, +0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x30, +0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, +0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x63, 0x06, +0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3E, 0x63, 0x63, 0x6F, 0x6F, 0x6F, +0x6E, 0x60, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x08, 0x1C, 0x36, 0x63, 0x63, 0x7F, 0x63, 0x63, +0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x33, +0x33, 0x33, 0x3E, 0x33, 0x33, 0x33, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1E, 0x33, 0x60, 0x60, +0x60, 0x60, 0x60, 0x33, 0x1E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0x36, 0x33, 0x33, 0x33, 0x33, +0x33, 0x36, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x7F, 0x33, 0x30, 0x34, 0x3C, 0x34, 0x30, 0x33, +0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x33, +0x30, 0x34, 0x3C, 0x34, 0x30, 0x30, 0x78, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1E, 0x33, 0x60, 0x60, +0x60, 0x6F, 0x63, 0x33, 0x1D, 0x00, 0x00, 0x00, +0x00, 0x00, 0x63, 0x63, 0x63, 0x63, 0x7F, 0x63, +0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, +0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x06, +0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00, +0x00, 0x00, 0x00, 0x00, 0x73, 0x33, 0x36, 0x36, +0x3C, 0x36, 0x36, 0x33, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, +0x30, 0x33, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, +0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x63, +0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x73, +0x7B, 0x7F, 0x6F, 0x67, 0x63, 0x63, 0x63, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1C, 0x36, 0x63, 0x63, +0x63, 0x63, 0x63, 0x36, 0x1C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7E, 0x33, 0x33, 0x33, 0x3E, 0x30, +0x30, 0x30, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, +0x1C, 0x36, 0x63, 0x63, 0x63, 0x63, 0x6B, 0x3E, +0x1C, 0x06, 0x03, 0x00, 0x00, 0x00, 0x7E, 0x33, +0x33, 0x33, 0x3E, 0x36, 0x33, 0x33, 0x73, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3E, 0x63, 0x63, 0x30, +0x1C, 0x06, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, +0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, +0x63, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x08, 0x00, +0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x63, 0x63, +0x63, 0x6B, 0x6B, 0x7F, 0x36, 0x00, 0x00, 0x00, +0x00, 0x00, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x36, +0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, +0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, +0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x63, +0x06, 0x0C, 0x18, 0x30, 0x60, 0x63, 0x7F, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, +0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, +0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, +0x3C, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, 0x63, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, +0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x30, +0x30, 0x3E, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, +0x63, 0x60, 0x60, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0E, 0x06, 0x06, 0x3E, 0x66, 0x66, +0x66, 0x66, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x3E, 0x63, 0x7F, 0x60, 0x63, +0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x33, +0x30, 0x7C, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, +0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, +0x00, 0x00, 0x70, 0x30, 0x30, 0x36, 0x3B, 0x33, +0x33, 0x33, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, +0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, +0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, +0x78, 0x00, 0x00, 0x00, 0x70, 0x30, 0x30, 0x33, +0x36, 0x3C, 0x36, 0x33, 0x73, 0x00, 0x00, 0x00, +0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, +0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x66, 0x7F, 0x6B, 0x6B, 0x6B, +0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x6E, 0x33, 0x33, 0x33, 0x33, 0x33, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, +0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x33, 0x33, +0x33, 0x3E, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x3B, 0x66, 0x66, 0x66, 0x3E, +0x06, 0x06, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x6E, 0x33, 0x30, 0x30, 0x30, 0x78, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, +0x63, 0x38, 0x0E, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x08, 0x18, 0x7E, 0x18, 0x18, +0x18, 0x1B, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, +0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x08, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, +0x63, 0x6B, 0x6B, 0x7F, 0x36, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x1C, +0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x63, 0x66, 0x66, 0x66, 0x3E, +0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x7F, 0x66, 0x0C, 0x18, 0x33, 0x7F, 0x00, +0x00, 0x00, 0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, +0x70, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, +0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x6E, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1C, +0x36, 0x63, 0x63, 0x7F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, +0x18, 0x00, 0x00, 0x00, 0x55, 0xAA, 0x55, 0xAA, +0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, +0x55, 0xAA, 0x00, 0x44, 0x44, 0x7C, 0x44, 0x44, +0x00, 0x1F, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, +0x00, 0x7C, 0x40, 0x78, 0x40, 0x40, 0x00, 0x1F, +0x10, 0x1E, 0x10, 0x10, 0x00, 0x00, 0x00, 0x38, +0x44, 0x40, 0x44, 0x38, 0x00, 0x1E, 0x11, 0x1E, +0x14, 0x13, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, +0x40, 0x7C, 0x00, 0x1F, 0x10, 0x1E, 0x10, 0x10, +0x00, 0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, +0x18, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x44, +0x64, 0x54, 0x4C, 0x44, 0x00, 0x10, 0x10, 0x10, +0x10, 0x1F, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, +0x28, 0x10, 0x00, 0x1F, 0x04, 0x04, 0x04, 0x04, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x0C, 0x18, +0x30, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x30, 0x18, 0x0C, 0x06, +0x0C, 0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0x3E, 0x76, 0x36, +0x36, 0x36, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x02, 0x04, 0x7E, 0x08, 0x10, 0x7E, 0x20, +0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x36, +0x30, 0x30, 0x7C, 0x30, 0x30, 0x73, 0x7E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x00, 0x18, 0x18, 0x3C, 0x3C, 0x3C, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, +0x08, 0x3E, 0x63, 0x60, 0x60, 0x63, 0x3E, 0x08, +0x08, 0x00, 0x00, 0x00, 0x1C, 0x36, 0x30, 0x30, +0x7C, 0x30, 0x30, 0x73, 0x7E, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x42, 0x3C, 0x66, 0x66, 0x66, +0x3C, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, 0x7E, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, +0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, +0x00, 0x00, 0x00, 0x3E, 0x63, 0x30, 0x1C, 0x36, +0x63, 0x63, 0x36, 0x1C, 0x06, 0x63, 0x3E, 0x00, +0x00, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x3E, 0x41, 0x5D, 0x51, 0x51, 0x5D, 0x41, +0x3E, 0x00, 0x00, 0x00, 0x00, 0x38, 0x04, 0x3C, +0x44, 0x3C, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x36, +0x6C, 0x36, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x03, +0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, +0x41, 0x5D, 0x55, 0x59, 0x55, 0x41, 0x3E, 0x00, +0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, +0x7E, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6C, 0x18, +0x30, 0x64, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x7C, 0x0C, 0x38, 0x0C, 0x6C, +0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, +0x3B, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x7F, 0xDB, +0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x1B, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0C, 0x0C, 0x06, 0x1C, 0x00, 0x30, +0x70, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x44, +0x44, 0x38, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x36, +0x1B, 0x36, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x60, 0xE0, 0x63, 0x66, 0x6C, 0x18, 0x33, +0x67, 0xCF, 0x1F, 0x03, 0x03, 0x00, 0x00, 0x60, +0xE0, 0x63, 0x66, 0x6C, 0x18, 0x30, 0x6E, 0xC3, +0x06, 0x0C, 0x1F, 0x00, 0x00, 0xF0, 0x30, 0x63, +0xF6, 0x6C, 0x18, 0x33, 0x67, 0xCF, 0x1F, 0x03, +0x03, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, +0x18, 0x30, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x60, 0x30, 0x18, 0x00, 0x1C, 0x36, 0x63, 0x63, +0x7F, 0x63, 0x63, 0x00, 0x00, 0x00, 0x03, 0x06, +0x0C, 0x00, 0x1C, 0x36, 0x63, 0x63, 0x7F, 0x63, +0x63, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, 0x00, +0x1C, 0x36, 0x63, 0x63, 0x7F, 0x63, 0x63, 0x00, +0x00, 0x00, 0x3B, 0x6E, 0x00, 0x08, 0x1C, 0x36, +0x63, 0x63, 0x7F, 0x63, 0x63, 0x00, 0x00, 0x00, +0x00, 0x63, 0x63, 0x08, 0x1C, 0x36, 0x63, 0x63, +0x7F, 0x63, 0x63, 0x00, 0x00, 0x00, 0x1C, 0x36, +0x1C, 0x00, 0x1C, 0x36, 0x63, 0x63, 0x7F, 0x63, +0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x36, +0x66, 0x66, 0x67, 0x7E, 0x66, 0x66, 0x67, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1E, 0x33, 0x60, 0x60, +0x60, 0x60, 0x60, 0x33, 0x1E, 0x0C, 0x06, 0x1C, +0x60, 0x30, 0x18, 0x00, 0x7F, 0x33, 0x30, 0x3E, +0x30, 0x33, 0x7F, 0x00, 0x00, 0x00, 0x06, 0x0C, +0x18, 0x00, 0x7F, 0x33, 0x30, 0x3E, 0x30, 0x33, +0x7F, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, 0x00, +0x7F, 0x33, 0x30, 0x3E, 0x30, 0x33, 0x7F, 0x00, +0x00, 0x00, 0x00, 0x63, 0x63, 0x00, 0x7F, 0x33, +0x30, 0x3E, 0x30, 0x33, 0x7F, 0x00, 0x00, 0x00, +0x60, 0x30, 0x18, 0x00, 0x3C, 0x18, 0x18, 0x18, +0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x06, 0x0C, +0x18, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, +0x3C, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x66, 0x00, +0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, +0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x3C, 0x18, +0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x36, 0x33, 0x33, 0x7B, 0x33, +0x33, 0x36, 0x3C, 0x00, 0x00, 0x00, 0x3B, 0x6E, +0x00, 0x63, 0x73, 0x7B, 0x7F, 0x6F, 0x67, 0x63, +0x63, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x00, +0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00, +0x00, 0x00, 0x03, 0x06, 0x0C, 0x00, 0x1C, 0x36, +0x63, 0x63, 0x63, 0x36, 0x1C, 0x00, 0x00, 0x00, +0x08, 0x1C, 0x36, 0x00, 0x1C, 0x36, 0x63, 0x63, +0x63, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x3B, +0x6E, 0x00, 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, +0x1C, 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x00, +0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x36, 0x1C, +0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00, +0x00, 0x03, 0x1E, 0x36, 0x67, 0x6F, 0x6B, 0x7B, +0x73, 0x36, 0x3C, 0x60, 0x00, 0x00, 0x60, 0x30, +0x18, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, +0x3E, 0x00, 0x00, 0x00, 0x03, 0x06, 0x0C, 0x00, +0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, +0x00, 0x00, 0x08, 0x1C, 0x36, 0x00, 0x63, 0x63, +0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x63, 0x63, 0x00, 0x63, 0x63, 0x63, 0x63, +0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x06, 0x0C, +0x18, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, +0x3C, 0x00, 0x00, 0x00, 0x00, 0x78, 0x30, 0x3E, +0x33, 0x33, 0x3E, 0x30, 0x30, 0x30, 0x78, 0x00, +0x00, 0x00, 0x00, 0x00, 0x1C, 0x36, 0x63, 0x66, +0x6C, 0x67, 0x63, 0x6B, 0x6E, 0x60, 0x60, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0x3C, 0x06, 0x3E, +0x66, 0x66, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x06, +0x0C, 0x18, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, +0x3B, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, +0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3B, 0x6E, 0x00, 0x3C, +0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00, 0x00, 0x00, +0x00, 0x00, 0x66, 0x66, 0x00, 0x3C, 0x06, 0x3E, +0x66, 0x66, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x1C, +0x36, 0x1C, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, +0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x76, 0x0B, 0x3B, 0x6E, 0x68, 0x37, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, +0x63, 0x60, 0x60, 0x63, 0x3E, 0x0C, 0x06, 0x1C, +0x00, 0x30, 0x18, 0x0C, 0x00, 0x3E, 0x63, 0x7F, +0x60, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x03, +0x06, 0x0C, 0x00, 0x3E, 0x63, 0x7F, 0x60, 0x63, +0x3E, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x36, +0x00, 0x3E, 0x63, 0x7F, 0x60, 0x63, 0x3E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x00, 0x3E, +0x63, 0x7F, 0x60, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, +0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x06, +0x0C, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, +0x3C, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x66, +0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, +0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x38, +0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x36, 0x1C, 0x36, 0x06, 0x3E, 0x66, +0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, +0x3B, 0x6E, 0x00, 0x6E, 0x33, 0x33, 0x33, 0x33, +0x33, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, +0x00, 0x3E, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, +0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x00, 0x3E, +0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, +0x00, 0x08, 0x1C, 0x36, 0x00, 0x3E, 0x63, 0x63, +0x63, 0x63, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, +0x3B, 0x6E, 0x00, 0x3E, 0x63, 0x63, 0x63, 0x63, +0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, +0x00, 0x3E, 0x63, 0x63, 0x63, 0x63, 0x3E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, +0x7E, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x03, 0x3E, 0x67, 0x6F, +0x7B, 0x73, 0x3E, 0x60, 0x00, 0x00, 0x00, 0x60, +0x30, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, +0x3B, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, +0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3B, 0x00, +0x00, 0x00, 0x00, 0x18, 0x3C, 0x66, 0x00, 0x66, +0x66, 0x66, 0x66, 0x66, 0x3B, 0x00, 0x00, 0x00, +0x00, 0x00, 0x66, 0x66, 0x00, 0x66, 0x66, 0x66, +0x66, 0x66, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x06, +0x0C, 0x18, 0x00, 0x63, 0x66, 0x66, 0x66, 0x3E, +0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x78, +0x30, 0x3E, 0x33, 0x33, 0x33, 0x33, 0x3E, 0x30, +0x78, 0x00, 0x00, 0x00, 0x66, 0x66, 0x00, 0x66, +0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00 +}; + +char font_8x16[256*16] = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0x81, 0xBD, +0x99, 0x81, 0x81, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7E, 0xFF, 0xDB, 0xFF, 0xFF, 0xC3, +0xE7, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x6C, 0xFE, 0xFE, 0xFE, +0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE, +0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0xE7, 0xE7, +0xE7, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, +0x7E, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, +0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, +0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, +0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0xBD, +0xBD, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0x00, 0x00, 0x1E, 0x0E, 0x1A, 0x32, 0x78, 0xCC, +0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, +0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x30, +0x30, 0x70, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7F, 0x63, 0x7F, 0x63, 0x63, 0x63, +0x63, 0x67, 0xE7, 0xE6, 0xC0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x18, 0x18, 0xDB, 0x3C, 0xE7, +0x3C, 0xDB, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFE, 0xF8, +0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, +0x00, 0x02, 0x06, 0x0E, 0x1E, 0x3E, 0xFE, 0x3E, +0x1E, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, +0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, +0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, +0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, +0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0xC6, +0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xFE, 0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, +0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0C, 0xFE, +0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xFE, +0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, +0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xFF, +0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7C, +0x7C, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x7C, +0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, +0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, +0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x7C, 0xC6, 0xC2, 0xC0, 0x7C, 0x06, +0x06, 0x86, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18, +0x30, 0x60, 0xC6, 0x86, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, +0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, +0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, +0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, +0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x0C, 0x18, +0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x66, 0xC3, 0xC3, 0xDB, 0xDB, +0xC3, 0xC3, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30, +0x60, 0xC0, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, +0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, +0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0xFC, 0x06, +0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFE, 0xC6, 0x06, 0x06, 0x0C, 0x18, +0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, +0x06, 0x06, 0x0C, 0x78, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, +0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, +0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, +0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, +0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, +0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, +0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, +0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, +0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, +0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, +0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, +0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, +0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, +0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE, +0xC6, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, +0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, +0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xE6, 0x66, 0x66, 0x6C, 0x78, 0x78, +0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, +0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC3, 0xE7, 0xFF, 0xFF, 0xDB, 0xC3, +0xC3, 0xC3, 0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, +0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, +0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xD6, 0xDE, 0x7C, 0x0C, 0x0E, 0x00, 0x00, +0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, +0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C, +0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFF, 0xDB, 0x99, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, +0xC3, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xDB, +0xDB, 0xFF, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC3, 0xC3, 0x66, 0x3C, 0x18, 0x18, +0x3C, 0x66, 0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC3, 0xC3, 0xC3, 0x66, 0x3C, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xFF, 0xC3, 0x86, 0x0C, 0x18, 0x30, +0x60, 0xC1, 0xC3, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, +0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0x70, 0x38, +0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, +0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, +0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, +0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, +0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, +0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, +0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00, +0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, +0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06, +0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00, +0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, +0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0xFF, 0xDB, +0xDB, 0xDB, 0xDB, 0xDB, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, +0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, +0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66, +0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x60, +0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30, +0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0xC3, +0xC3, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xC3, 0xC3, +0xDB, 0xDB, 0xFF, 0x66, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x66, 0x3C, +0x18, 0x3C, 0x66, 0xC3, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x18, +0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, +0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, +0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, +0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, +0xFF, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, +0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, +0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, +0x00, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88, 0x00, +0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, +0x00, 0xF8, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00, +0x3E, 0x20, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00, +0x00, 0x70, 0x88, 0x80, 0x80, 0x88, 0x70, 0x00, +0x3C, 0x22, 0x3C, 0x28, 0x24, 0x22, 0x00, 0x00, +0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0x00, +0x3E, 0x20, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00, +0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, +0x18, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x88, 0xC8, 0xA8, 0xA8, 0x98, 0x88, 0x00, +0x20, 0x20, 0x20, 0x20, 0x20, 0x3E, 0x00, 0x00, +0x00, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, +0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, +0x00, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, +0x18, 0x0C, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C, +0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x02, 0x7E, 0xEC, 0x6C, 0x6C, +0x6C, 0x6C, 0x6C, 0xCC, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x06, 0x0C, 0x7E, 0x18, 0x18, +0x7E, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, +0x60, 0x60, 0xE6, 0xFC, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, +0x3C, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x18, 0x7E, 0xC3, 0xC0, 0xC0, 0xC0, +0xC3, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, +0x60, 0x60, 0xE6, 0xFC, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x82, 0xC6, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0x7C, 0xC6, 0x82, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC3, 0x66, 0x3C, 0x18, 0xFF, 0x18, +0xFF, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, +0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, +0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0xC6, +0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, +0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0x82, 0xBA, 0xA2, 0xA2, +0xBA, 0x82, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x70, 0x18, 0x78, 0xC8, 0x78, 0x00, 0xF8, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x6C, 0xD8, +0x6C, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, +0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7C, 0xC6, 0x82, 0xBA, 0xAA, 0xB2, +0xAA, 0x82, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, +0x18, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, +0x00, 0x70, 0xD8, 0x30, 0x60, 0xC8, 0xF8, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0xF8, 0x18, 0x30, 0x18, 0xD8, 0x70, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, +0x66, 0x7E, 0x63, 0x60, 0xC0, 0x00, 0x00, 0x00, +0x00, 0x00, 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, +0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, +0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x18, 0x0C, 0x06, 0x3C, 0x00, 0x00, +0x00, 0x60, 0xE0, 0x60, 0x60, 0x60, 0xF0, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x70, 0xD8, 0x88, 0xD8, 0x70, 0x00, 0xF8, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0xD8, 0x6C, 0x36, +0x6C, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, +0x66, 0xCE, 0x96, 0x3E, 0x06, 0x06, 0x00, 0x00, +0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, +0x60, 0xCE, 0x9B, 0x06, 0x0C, 0x1F, 0x00, 0x00, +0x00, 0xF0, 0x30, 0x72, 0x36, 0xEC, 0x18, 0x30, +0x66, 0xCE, 0x96, 0x3E, 0x06, 0x06, 0x00, 0x00, +0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, +0xC0, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x60, 0x30, 0x18, 0x00, 0x38, 0x6C, 0xC6, 0xC6, +0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x18, 0x30, 0x00, 0x38, 0x6C, 0xC6, 0xC6, +0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x10, 0x38, 0x6C, 0x00, 0x38, 0x6C, 0xC6, 0xC6, +0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x76, 0xDC, 0x00, 0x38, 0x6C, 0xC6, 0xC6, +0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0xC6, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, +0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x38, 0x6C, 0x38, 0x00, 0x38, 0x6C, 0xC6, 0xC6, +0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3E, 0x6C, 0xCC, 0xCC, 0xFE, 0xCC, +0xCC, 0xCC, 0xCC, 0xCE, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, +0xC2, 0x66, 0x3C, 0x0C, 0x06, 0x7C, 0x00, 0x00, +0x30, 0x18, 0x0C, 0x00, 0xFE, 0x66, 0x60, 0x7C, +0x60, 0x60, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x18, 0x30, 0x60, 0x00, 0xFE, 0x66, 0x60, 0x7C, +0x60, 0x60, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x10, 0x38, 0x6C, 0x00, 0xFE, 0x66, 0x60, 0x7C, +0x60, 0x60, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x00, 0xC6, 0x00, 0xFE, 0x66, 0x60, 0x60, 0x7C, +0x60, 0x60, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, +0x30, 0x18, 0x0C, 0x00, 0x3C, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x18, 0x30, 0x00, 0x3C, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x18, 0x3C, 0x66, 0x00, 0x3C, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x66, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x78, 0x6C, 0x66, 0x66, 0x66, 0xF6, +0x66, 0x66, 0x6C, 0x78, 0x00, 0x00, 0x00, 0x00, +0x76, 0xDC, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, +0xCE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, +0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x18, 0x30, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x76, 0xDC, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0xC6, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x10, +0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, +0x02, 0x06, 0x7C, 0xCE, 0xCE, 0xDE, 0xD6, 0xD6, +0xF6, 0xE6, 0xE6, 0x7C, 0xC0, 0x80, 0x00, 0x00, +0x60, 0x30, 0x18, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x18, 0x30, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x10, 0x38, 0x6C, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x0C, 0x18, 0x30, 0x00, 0xC3, 0xC3, 0x66, 0x3C, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0xF0, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, +0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x38, 0x6C, 0xC6, 0xCE, 0xDC, 0xD8, +0xCC, 0xC6, 0xD6, 0xDC, 0xC0, 0x80, 0x00, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x10, 0x38, 0x6C, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x76, 0xDC, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xCC, 0x00, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x38, 0x6C, 0x38, 0x00, 0x78, 0x0C, 0x7C, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x3B, 0x1B, +0x7E, 0xD8, 0xDC, 0x77, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x60, +0x66, 0x3C, 0x0C, 0x06, 0x3C, 0x00, 0x00, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xFE, +0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x0C, 0x18, 0x30, 0x00, 0x7C, 0xC6, 0xFE, +0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xFE, +0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC6, 0x00, 0x00, 0x7C, 0xC6, 0xFE, +0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x06, 0x0C, 0x18, 0x00, 0x38, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x3C, 0x66, 0x00, 0x38, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x66, 0x00, 0x00, 0x38, 0x18, 0x18, +0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x6C, 0x38, 0x38, 0x6C, 0x0C, 0x3C, 0x6C, +0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x76, 0xDC, 0x00, 0xDC, 0x66, 0x66, +0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x30, 0x60, 0x00, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x76, 0xDC, 0x00, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xC6, 0x00, 0x00, 0x7C, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7E, +0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x02, 0x06, 0x7C, 0xCE, 0xDE, +0xD6, 0xF6, 0xE6, 0x7C, 0xC0, 0x80, 0x00, 0x00, +0x00, 0x60, 0x30, 0x18, 0x00, 0xCC, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x30, 0x60, 0x00, 0xCC, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x30, 0x78, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0xCC, 0x00, 0x00, 0xCC, 0xCC, 0xCC, +0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, +0x00, 0x18, 0x30, 0x60, 0x00, 0xC6, 0xC6, 0xC6, +0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00, +0x00, 0x00, 0x00, 0xF0, 0x60, 0x7C, 0x66, 0x66, +0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, +0x00, 0x00, 0x66, 0x66, 0x00, 0xEE, 0x66, 0x66, +0x66, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00 +}; diff --git a/sys/i386/isa/kbd.h b/sys/i386/isa/kbd.h new file mode 100644 index 0000000..b7b6009 --- /dev/null +++ b/sys/i386/isa/kbd.h @@ -0,0 +1,56 @@ +/* + * Keyboard definitions + * from: unknown origin, 386BSD 0.1 + * $Id: kbd.h,v 1.2 1993/10/16 13:46:04 rgrimes Exp $ + */ + +#ifndef _I386_ISA_KBD_H_ +#define _I386_ISA_KBD_H_ 1 + +/* Reference: IBM AT Technical Reference Manual, + * pp. 1-38 to 1-43, 4-3 to 4-22 + */ + +/* commands sent to KBCMDP */ + +#define KBC_CMDREAD 0x20 /* read kbd cntrl command byte */ +#define KBC_CMDWRITE 0x60 /* == LD_CMDBYTE in kd.h, write command */ +#define KBC_SELFTEST 0xAA /* perform self test, returns 55 when ok */ +#define KBC_IFTEST 0xAB /* perform interface test */ +#define KBC_DIAGDUMP 0xAC /* send 19 status bytes to system */ +#define KBC_DISKBD 0xAD /* disable keyboard */ +#define KBC_ENAKBD 0xAE /* enable keyboard */ +#define KBC_RDINP 0xC0 /* read input port */ +#define KBC_RDID 0xC4 /* read keyboard ID */ +#define KBC_RDOUTP 0xD0 /* read output port */ +#define KBC_WROUTP 0xD1 /* write output port */ +#define KBC_RDTINP 0xE0 /* read test inputs */ + +/* commands sent to KBDATAP */ +#define KBC_STSIND 0xED /* set keyboard status indicators */ +#define KBC_ECHO 0xEE /* reply with 0xEE */ +#define KBC_SETTPM 0xF3 /* Set typematic rate/delay */ +#define KBC_ENABLE 0xF4 /* Start scanning */ +#define KBC_SETDEFD 0xF5 /* =KBC_SETDEF, but disable scanning */ +#define KBC_SETDEF 0xF6 /* Set power on defaults */ +#define KBC_RESEND 0xFE /* system wants keyboard to resend last code */ +#define KBC_RESET 0xFF /* Reset the keyboard */ + +/* responses */ +#define KBR_OVERRUN 0x00 /* Keyboard flooded */ +#define KBR_STOK 0x55 /* Selftest ok response */ +#define KBR_IFOK 0x00 /* Interface test ok */ +#define KBR_IFCL_SA0 0x01 /* Clock Stuck-at-0 fault */ +#define KBR_IFCL_SA1 0x02 /* Clock Stuck-at-1 fault */ +#define KBR_IFDA_SA0 0x03 /* Data Stuck-at-0 fault */ +#define KBR_IFDA_SA1 0x04 /* Data Stuck-at-1 fault */ +#define KBR_RSTDONE 0xAA /* Keyboard reset (BAT) complete */ +#define KBR_E0 0xE0 /* Extended prefix */ +#define KBR_E1 0xE1 /* BREAK'S HIT :-( */ +#define KBR_ECHO 0xEE /* Echo response */ +#define KBR_F0 0xF0 /* Break code prefix */ +#define KBR_ACK 0xFA /* Keyboard did receive command */ +#define KBR_BATFAIL 0xFC /* BAT failed */ +#define KBR_DIAGFAIL 0xFD /* Diagnostic failed response */ +#define KBR_RESEND 0xFE /* Keyboard needs resend of command */ +#endif /* _I386_ISA_KBD_H_ */ diff --git a/sys/i386/isa/kbdtables.h b/sys/i386/isa/kbdtables.h new file mode 100644 index 0000000..a923c45 --- /dev/null +++ b/sys/i386/isa/kbdtables.h @@ -0,0 +1,859 @@ +/* + * Copyright (C) 1992, 1993, 1994 Søren Schmidt + * + * This program is free software; you may redistribute it and/or + * modify it, provided that it retain the above copyright notice + * and the following disclaimer. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Søren Schmidt Email: sos@login.dkuug.dk + * Tritonvej 36 UUCP: ...uunet!dkuug!login!sos + * DK9210 Aalborg SO Phone: +45 9814 8076 + * + * @(#)kbdtables.h 1.3 940123 + * $Id: kbdtables.h,v 1.11 1994/02/01 09:27:43 ache Exp $ + */ + +#define SET8 0x80 /* eight bit for emacs SET8-key */ + +#ifdef DKKEYMAP +keymap_t key_map = { 0x69, /* DK iso8859 keymap */ +/* alt + * scan cntrl alt alt cntrl + * code base shift cntrl shift alt shift cntrl shift spcl flgs + * --------------------------------------------------------------------------- + */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, 0x1B, 0x02, 0x00, +/* sc=02 */ '1', '!', NOP, NOP, '1', '!', NOP, NOP, 0x33, 0x00, +/* sc=03 */ '2', '"', 0x00, 0x00, '@', '"', 0x00, 0x00, 0x00, 0x00, +/* sc=04 */ '3', '#', NOP, NOP, 0x9E, '#', NOP, NOP, 0x33, 0x00, +/* sc=05 */ '4', 0xA4, NOP, NOP, '$', 0xA4, NOP, NOP, 0x33, 0x00, +/* sc=06 */ '5', '%', NOP, NOP, '5', '%', NOP, NOP, 0x33, 0x00, +/* sc=07 */ '6', '&', NOP, NOP, '6', '&', NOP, NOP, 0x33, 0x00, +/* sc=08 */ '7', '/', NOP, NOP, '{', '/', NOP, NOP, 0x33, 0x00, +/* sc=09 */ '8', '(', 0x1B, 0x1B, '[', '(', 0x1B, 0x1B, 0x00, 0x00, +/* sc=0a */ '9', ')', 0x1D, 0x1D, ']', ')', 0x1D, 0x1D, 0x00, 0x00, +/* sc=0b */ '0', '=', NOP, NOP, '}', '=', NOP, NOP, 0x33, 0x00, +/* sc=0c */ '+', '?', NOP, NOP, '+', '?', NOP, NOP, 0x33, 0x00, +/* sc=0d */ '\'', '`', NOP, NOP, '|', '`', NOP, NOP, 0x33, 0x00, +/* sc=0e */ 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x00, 0x00, +/* sc=0f */ 0x09, 0x08, NOP, NOP, 0x09, 0x08, NOP, NOP, 0x33, 0x00, +/* sc=10 */ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, 0x00, 0x01, +/* sc=11 */ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, 0x00, 0x01, +/* sc=12 */ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, 0x00, 0x01, +/* sc=13 */ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, 0x00, 0x01, +/* sc=14 */ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, 0x00, 0x01, +/* sc=15 */ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, 0x00, 0x01, +/* sc=16 */ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, 0x00, 0x01, +/* sc=17 */ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, 0x00, 0x01, +/* sc=18 */ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, 0x00, 0x01, +/* sc=19 */ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, 0x00, 0x01, +/* sc=1a */ 0xE5, 0xC5, NOP, NOP, 0x86, 0x8F, NOP, NOP, 0x33, 0x01, +/* sc=1b */ '"', '^', 0x1E, 0x1E, '~', '^', 0x1E, 0x1E, 0x00, 0x00, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, 0x00, 0x01, +/* sc=1f */ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, 0x00, 0x01, +/* sc=20 */ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, 0x00, 0x01, +/* sc=21 */ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, 0x00, 0x01, +/* sc=22 */ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, 0x00, 0x01, +/* sc=23 */ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, 0x00, 0x01, +/* sc=24 */ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, 0x00, 0x01, +/* sc=25 */ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, 0x00, 0x01, +/* sc=26 */ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, 0x00, 0x01, +/* sc=27 */ 0xE6, 0xC6, NOP, NOP, 0x91, 0x92, NOP, NOP, 0x33, 0x01, +/* sc=28 */ 0xF8, 0xD8, NOP, NOP, 0x9B, 0x9D, NOP, NOP, 0x33, 0x01, +/* sc=29 */ 0xBD, 0xA7, NOP, NOP, 0xBD, 0xA7, NOP, NOP, 0x33, 0x00, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '\'', '*', NOP, NOP, '\'', '*', NOP, NOP, 0x33, 0x00, +/* sc=2c */ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, 0x00, 0x01, +/* sc=2d */ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, 0x00, 0x01, +/* sc=2e */ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, 0x00, 0x01, +/* sc=2f */ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, 0x00, 0x01, +/* sc=30 */ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, 0x00, 0x01, +/* sc=31 */ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, 0x00, 0x01, +/* sc=32 */ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, 0x00, 0x01, +/* sc=33 */ ',', ';', NOP, NOP, ',', ';', NOP, NOP, 0x33, 0x00, +/* sc=34 */ '.', ':', NOP, NOP, '.', ':', NOP, NOP, 0x33, 0x00, +/* sc=35 */ '-', '_', 0x1F, 0x1F, '-', '_', 0x1F, 0x1F, 0x00, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', '*', '*', '*', '*', '*', '*', 0x00, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x00, 0x00, +/* sc=3a */ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, NLK, NLK, NLK, NLK, NLK, NLK, 0xFF, 0x00, +/* sc=46 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +/* sc=47 */ F(49), '7', '7', '7', '7', '7', '7', '7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', '8', '8', '8', '8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', '9', '9', '9', '9', 0x80, 0x02, +/* sc=4a */ F(52), '-', '-', '-', '-', '-', '-', '-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', '4', '4', '4', '4', 0x80, 0x02, +/* sc=4c */ NOP, '5', '5', '5', '5', '5', '5', '5', 0x80, 0x02, +/* sc=4d */ F(55), '6', '6', '6', '6', '6', '6', '6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', '+', '+', '+', '+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', '1', '1', '1', '1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', '2', '2', '2', '2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', '3', '3', '3', '3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', '0', '0', '0', '0', 0x80, 0x02, +/* sc=53 */ 0x7F, '.', 0x7F, 0x7F, 0x7F, 0x7F, RBT, 0x7F, 0x02, 0x02, +/* sc=54 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ '<', '>', 0x1C, 0x1C, '\\', '>', 0x1C, 0x1C, 0x00, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x00, 0x02, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', '/', '/', '/', '/', '/', '/', 0x00, 0x00, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), F(54), F(54), F(54), F(54), RBT, F(54), 0xFF, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +}; +#endif + +#ifdef UKKEYMAP +keymap_t key_map = { 0x69, /* uk iso8859 keymap */ +/* alt + * scan cntrl alt alt cntrl + * code base shift cntrl shift alt shift cntrl shift spcl flgs + * --------------------------------------------------------------------------- + */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, 0x1B, 0x02, 0x00, +/* sc=02 */ '1', '!', NOP, NOP, '`', '`', NOP, NOP, 0x33, 0x00, +/* sc=03 */ '2', '"', 0x00, 0x00, '@', '@', 0x00, 0x00, 0x00, 0x00, +/* sc=04 */ '3', 0xA3, NOP, NOP, '#', '#', NOP, NOP, 0x33, 0x00, +/* sc=05 */ '4', '$', NOP, NOP, '4', '$', NOP, NOP, 0x33, 0x00, +/* sc=06 */ '5', '%', NOP, NOP, '5', '%', NOP, NOP, 0x33, 0x00, +/* sc=07 */ '6', '^', 0x1E, 0x1E, '^', '^', 0x1E, 0x1E, 0x00, 0x00, +/* sc=08 */ '7', '&', NOP, NOP, '[', '[', 0x1B, 0x1B, 0x30, 0x00, +/* sc=09 */ '8', '*', NOP, NOP, '8', '*', NOP, NOP, 0x33, 0x00, +/* sc=0a */ '9', '(', NOP, NOP, ']', ']', 0x1D, 0x1D, 0x30, 0x00, +/* sc=0b */ '0', ')', NOP, NOP, '{', '{', NOP, NOP, 0x33, 0x00, +/* sc=0c */ '-', '_', 0x1F, 0x1F, '|', '|', 0x1F, 0x1F, 0x00, 0x00, +/* sc=0d */ '=', '+', NOP, NOP, '}', '}', NOP, NOP, 0x33, 0x00, +/* sc=0e */ 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00, +/* sc=0f */ 0x09, 0x08, NOP, NOP, 0x09, 0x08, NOP, NOP, 0x77, 0x00, +/* sc=10 */ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, 0x00, 0x01, +/* sc=11 */ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, 0x00, 0x01, +/* sc=12 */ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, 0x00, 0x01, +/* sc=13 */ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, 0x00, 0x01, +/* sc=14 */ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, 0x00, 0x01, +/* sc=15 */ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, 0x00, 0x01, +/* sc=16 */ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, 0x00, 0x01, +/* sc=17 */ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, 0x00, 0x01, +/* sc=18 */ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, 0x00, 0x01, +/* sc=19 */ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, 0x00, 0x01, +/* sc=1a */ '[', '{', 0x1B, 0x1B, '[', '{', 0x1B, 0x1B, 0x00, 0x00, +/* sc=1b */ ']', '}', 0x1D, 0x1D, ']', '}', 0x1D, 0x1D, 0x00, 0x00, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, 0x00, 0x01, +/* sc=1f */ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, 0x00, 0x01, +/* sc=20 */ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, 0x00, 0x01, +/* sc=21 */ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, 0x00, 0x01, +/* sc=22 */ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, 0x00, 0x01, +/* sc=23 */ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, 0x00, 0x01, +/* sc=24 */ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, 0x00, 0x01, +/* sc=25 */ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, 0x00, 0x01, +/* sc=26 */ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, 0x00, 0x01, +/* sc=27 */ ';', ':', NOP, NOP, ';', ':', NOP, NOP, 0x33, 0x00, +/* sc=28 */ '\'', '@', 0x00, 0x00, '\'', '@', 0x00, 0x00, 0x00, 0x00, +/* sc=29 */ '\\', '|', 0x1C, 0x1C, '\\', '\\', 0x1C, 0x1C, 0x00, 0x00, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '#', '~', NOP, NOP, '~', '~', NOP, NOP, 0x33, 0x00, +/* sc=2c */ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, 0x00, 0x01, +/* sc=2d */ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, 0x00, 0x01, +/* sc=2e */ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, 0x00, 0x01, +/* sc=2f */ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, 0x00, 0x01, +/* sc=30 */ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, 0x00, 0x01, +/* sc=31 */ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, 0x00, 0x01, +/* sc=32 */ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, 0x00, 0x01, +/* sc=33 */ ',', '<', NOP, NOP, ',', '<', NOP, NOP, 0x33, 0x00, +/* sc=34 */ '.', '>', NOP, NOP, '.', '>', NOP, NOP, 0x33, 0x00, +/* sc=35 */ '/', '?', NOP, NOP, '/', '?', NOP, NOP, 0x33, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', 0x0A, 0x0A, '*', '*', 0x0A, 0x0A, 0x33, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x00, 0x00, +/* sc=3a */ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, 0x13, 0x13, NLK, NLK, 0x13, 0x13, 0xCC, 0x00, +/* sc=46 */ SLK, SLK, 0x7F, 0x7F, SLK, SLK, 0x7F, 0x7F, 0xCC, 0x00, +/* sc=47 */ F(49), '7', '7', '7', '7', '7', '7', '7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', '8', '8', '8', '8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', '9', '9', '9', '9', 0x80, 0x02, +/* sc=4a */ F(52), '-', 0x1F, 0x1F, '-', '-', '-', '-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', '4', '4', '4', '4', 0x80, 0x02, +/* sc=4c */ F(54), '5', '5', '5', '5', '5', '5', '5', 0x80, 0x02, +/* sc=4d */ F(55), '6', 0x1E, 0x1E, '6', '6', '6', '6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', '+', '+', '+', '+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', '1', '1', '1', '1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', '2', '2', '2', '2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', '3', '3', '3', '3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', '0', '0', '0', '0', 0x80, 0x02, +/* sc=53 */ 0x7F, '.', 0x7F, 0x7F, 0x7F, 0x7F, RBT, 0x7F, 0x02, 0x02, +/* sc=54 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ '\\', '|', 0x1C, 0x1C, '\\', '|', 0x1C, 0x1C, 0x00, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0xFF, 0x02, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', '/', '/', '/', '/', '/', '/', 0x00, 0x02, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), F(54), F(54), F(54), F(54), RBT, F(54), 0xFF, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +}; +#endif + +#ifdef GRKEYMAP +keymap_t key_map = { 0x69, /* german iso8859 keymap */ +/* alt + * scan cntrl alt alt cntrl + * code base shift cntrl shift alt shift cntrl shift spcl flgs + * --------------------------------------------------------------------------- + */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, 0x1B, 0x02, 0x00, +/* sc=02 */ '1', '!', NOP, NOP, '`', '`', NOP, NOP, 0x33, 0x00, +/* sc=03 */ '2', '"', 0x00, 0x00, '@', '@', 0x00, 0x00, 0x00, 0x00, +/* sc=04 */ '3', 0xA7, NOP, NOP, '#', '#', NOP, NOP, 0x33, 0x00, +/* sc=05 */ '4', '$', NOP, NOP, '4', '$', NOP, NOP, 0x33, 0x00, +/* sc=06 */ '5', '%', NOP, NOP, '5', '%', NOP, NOP, 0x33, 0x00, +/* sc=07 */ '6', '&', 0x1E, 0x1E, '^', '^', 0x1E, 0x1E, 0x00, 0x00, +/* sc=08 */ '7', '/', 0x1B, 0x1B, '[', '[', 0x1B, 0x1B, 0x00, 0x00, +/* sc=09 */ '8', '(', NOP, NOP, '8', '(', NOP, NOP, 0x33, 0x00, +/* sc=0a */ '9', ')', 0x1D, 0x1D, ']', ']', 0x1D, 0x1D, 0x00, 0x00, +/* sc=0b */ '0', '=', NOP, NOP, '{', '{', NOP, NOP, 0x33, 0x00, +/* sc=0c */ 0xDF, '?', NOP, NOP, '|', '|', NOP, NOP, 0x33, 0x00, +/* sc=0d */ 0x92, 0x93, NOP, NOP, '\'', '`', NOP, NOP, 0x33, 0x00, +/* sc=0e */ 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00, +/* sc=0f */ 0x09, 0x08, NOP, NOP, 0x09, 0x08, NOP, NOP, 0x77, 0x00, +/* sc=10 */ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, 0x00, 0x01, +/* sc=11 */ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, 0x00, 0x01, +/* sc=12 */ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, 0x00, 0x01, +/* sc=13 */ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, 0x00, 0x01, +/* sc=14 */ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, 0x00, 0x01, +/* sc=15 */ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, 0x00, 0x01, +/* sc=16 */ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, 0x00, 0x01, +/* sc=17 */ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, 0x00, 0x01, +/* sc=18 */ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, 0x00, 0x01, +/* sc=19 */ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, 0x00, 0x01, +/* sc=1a */ 0xFC, 0xDC, 0x1B, 0x1B, '[', '{', 0x1B, 0x1B, 0x00, 0x01, +/* sc=1b */ '+', '*', 0x1D, 0x1D, ']', '}', 0x1D, 0x1D, 0x00, 0x00, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, 0x00, 0x01, +/* sc=1f */ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, 0x00, 0x01, +/* sc=20 */ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, 0x00, 0x01, +/* sc=21 */ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, 0x00, 0x01, +/* sc=22 */ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, 0x00, 0x01, +/* sc=23 */ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, 0x00, 0x01, +/* sc=24 */ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, 0x00, 0x01, +/* sc=25 */ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, 0x00, 0x01, +/* sc=26 */ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, 0x00, 0x01, +/* sc=27 */ 0xF6, 0xD6, NOP, NOP, 0xF6, 0xD6, NOP, NOP, 0x33, 0x01, +/* sc=28 */ 0xE4, 0xC4, NOP, NOP, 0xE4, 0xC4, NOP, NOP, 0x33, 0x01, +/* sc=29 */ '<', '>', 0x1C, 0x1C, '\\', '|', 0x1C, 0x1C, 0x00, 0x00, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '#', '^', 0x1E, 0x1E, '`', '~', 0x1E, 0x1E, 0x00, 0x00, +/* sc=2c */ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, 0x00, 0x01, +/* sc=2d */ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, 0x00, 0x01, +/* sc=2e */ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, 0x00, 0x01, +/* sc=2f */ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, 0x00, 0x01, +/* sc=30 */ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, 0x00, 0x01, +/* sc=31 */ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, 0x00, 0x01, +/* sc=32 */ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, 0x00, 0x01, +/* sc=33 */ ',', ';', NOP, NOP, ',', ';', NOP, NOP, 0x33, 0x00, +/* sc=34 */ '.', ':', NOP, NOP, '.', ':', NOP, NOP, 0x33, 0x00, +/* sc=35 */ '-', '_', 0x1F, 0x1F, '-', '_', 0x1F, 0x1F, 0x00, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', 0x0A, 0x0A, '*', '*', 0x0A, 0x0A, 0x33, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x00, 0x00, +/* sc=3a */ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, 0x13, 0x13, NLK, NLK, 0x13, 0x13, 0xCC, 0x00, +/* sc=46 */ SLK, SLK, 0x7F, 0x7F, SLK, SLK, 0x7F, 0x7F, 0xCC, 0x00, +/* sc=47 */ F(49), '7', '7', '7', '7', '7', '7', '7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', '8', '8', '8', '8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', '9', '9', '9', '9', 0x80, 0x02, +/* sc=4a */ F(52), '-', 0x1F, 0x1F, '-', '-', '-', '-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', '4', '4', '4', '4', 0x80, 0x02, +/* sc=4c */ F(54), '5', '5', '5', '5', '5', '5', '5', 0x80, 0x02, +/* sc=4d */ F(55), '6', 0x1E, 0x1E, '6', '6', '6', '6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', '+', '+', '+', '+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', '1', '1', '1', '1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', '2', '2', '2', '2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', '3', '3', '3', '3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', '0', '0', '0', '0', 0x80, 0x02, +/* sc=53 */ 0x7F, '.', 0x7F, 0x7F, 0x7F, 0x7F, RBT, 0x7F, 0x02, 0x02, +/* sc=54 */ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0xFF, 0x02, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', '/', '/', '/', '/', '/', '/', 0x00, 0x02, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), F(54), F(54), F(54), F(54), RBT, F(54), 0xFF, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +}; +#endif + +#ifdef SWKEYMAP +keymap_t key_map = { 0x69, /* swedish iso8859 keymap */ +/* alt + * scan cntrl alt alt cntrl + * code base shift cntrl shift alt shift cntrl shift spcl flgs + * --------------------------------------------------------------------------- + */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, 0x1B, 0x02, 0x00, +/* sc=02 */ '1', '!', NOP, NOP, NOP, NOP, NOP, NOP, 0x3F, 0x00, +/* sc=03 */ '2', '"', NOP, NOP, '@', NOP, NOP, NOP, 0x37, 0x00, +/* sc=04 */ '3', '#', NOP, NOP, 0xA3, NOP, NOP, NOP, 0x37, 0x00, +/* sc=05 */ '4', '$', NOP, NOP, 0xA4, NOP, NOP, NOP, 0x37, 0x00, +/* sc=06 */ '5', '%', NOP, NOP, NOP, NOP, NOP, NOP, 0x3F, 0x00, +/* sc=07 */ '6', '&', NOP, NOP, NOP, NOP, NOP, NOP, 0x3F, 0x00, +/* sc=08 */ '7', '/', NOP, NOP, '{', NOP, NOP, NOP, 0x37, 0x00, +/* sc=09 */ '8', '(', NOP, NOP, '[', NOP, NOP, NOP, 0x37, 0x00, +/* sc=0a */ '9', ')', NOP, NOP, ']', NOP, NOP, NOP, 0x37, 0x00, +/* sc=0b */ '0', '=', NOP, NOP, '}', NOP, NOP, NOP, 0x37, 0x00, +/* sc=0c */ '+', '?', NOP, NOP, '\\', NOP, 0x1C, NOP, 0x35, 0x00, +/* sc=0d */ 0x180, '`', NOP, NOP, NOP, NOP, NOP, NOP, 0x3F, 0x00, +/* sc=0e */ 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00, +/* sc=0f */ 0x09, 0x08, NOP, NOP, 0x09, 0x08, NOP, NOP, 0x77, 0x00, +/* sc=10 */ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, 0x00, 0x01, +/* sc=11 */ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, 0x00, 0x01, +/* sc=12 */ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, 0x00, 0x01, +/* sc=13 */ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, 0x00, 0x01, +/* sc=14 */ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, 0x00, 0x01, +/* sc=15 */ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, 0x00, 0x01, +/* sc=16 */ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, 0x00, 0x01, +/* sc=17 */ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, 0x00, 0x01, +/* sc=18 */ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, 0x00, 0x01, +/* sc=19 */ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, 0x00, 0x01, +/* sc=1a */ 0xE5, 0xC5, NOP, NOP, '}', ']', NOP, NOP, 0x33, 0x01, +/* sc=1b */ 0xA8, '^', NOP, NOP, '~', NOP, NOP, NOP, 0x37, 0x00, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, 0x00, 0x01, +/* sc=1f */ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, 0x00, 0x01, +/* sc=20 */ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, 0x00, 0x01, +/* sc=21 */ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, 0x00, 0x01, +/* sc=22 */ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, 0x00, 0x01, +/* sc=23 */ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, 0x00, 0x01, +/* sc=24 */ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, 0x00, 0x01, +/* sc=25 */ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, 0x00, 0x01, +/* sc=26 */ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, 0x00, 0x01, +/* sc=27 */ 0xF6, 0xD6, NOP, NOP, '|', '\\', NOP, NOP, 0x33, 0x01, +/* sc=28 */ 0xE4, 0xC4, NOP, NOP, '{', '[', NOP, NOP, 0x33, 0x01, +/* sc=29 */ 0xA7, 0xBD, NOP, NOP, '\\', '|', NOP, NOP, 0x33, 0x00, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '\'', '*', NOP, NOP, NOP, NOP, NOP, NOP, 0x3F, 0x00, +/* sc=2c */ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, 0x00, 0x01, +/* sc=2d */ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, 0x00, 0x01, +/* sc=2e */ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, 0x00, 0x01, +/* sc=2f */ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, 0x00, 0x01, +/* sc=30 */ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, 0x00, 0x01, +/* sc=31 */ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, 0x00, 0x01, +/* sc=32 */ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, 0x00, 0x01, +/* sc=33 */ ',', ';', NOP, NOP, NOP, '<', NOP, NOP, 0x3B, 0x00, +/* sc=34 */ '.', ':', NOP, NOP, NOP, '>', NOP, NOP, 0x3B, 0x00, +/* sc=35 */ '-', '_', 0x1F, NOP, '/', '?', NOP, NOP, 0x13, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', 0x0A, 0x0A, '*', '*', 0x0A, 0x0A, 0x33, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x00, 0x00, +/* sc=3a */ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, 0x13, 0x13, NLK, NLK, 0x13, 0x13, 0xCC, 0x00, +/* sc=46 */ SLK, SLK, 0x7F, 0x7F, SLK, SLK, 0x7F, 0x7F, 0xCC, 0x00, +/* sc=47 */ F(49), '7', '7', '7', '7', '7', '7', '7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', '8', '8', '8', '8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', '9', '9', '9', '9', 0x80, 0x02, +/* sc=4a */ F(52), '-', 0x1F, 0x1F, '-', '-', '-', '-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', '4', '4', '4', '4', 0x80, 0x02, +/* sc=4c */ F(54), '5', '5', '5', '5', '5', '5', '5', 0x80, 0x02, +/* sc=4d */ F(55), '6', 0x1E, 0x1E, '6', '6', '6', '6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', '+', '+', '+', '+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', '1', '1', '1', '1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', '2', '2', '2', '2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', '3', '3', '3', '3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', '0', '0', '0', '0', 0x80, 0x02, +/* sc=53 */ 0x7F, '.', 0x7F, 0x7F, 0x7F, 0x7F, RBT, 0x7F, 0x02, 0x02, +/* sc=54 */ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ '<', '>', NOP, NOP, '|', NOP, NOP, NOP, 0x37, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0xFF, 0x02, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', '/', '/', '/', '/', '/', '/', 0x00, 0x02, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), F(54), F(54), F(54), F(54), RBT, F(54), 0xFF, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +}; +#endif + +#ifdef RUKEYMAP +keymap_t key_map = { 0xe9, /* keys number */ +/* alt + * scan cntrl alt alt cntrl + * code base shift cntrl shift alt shift cntrl shift spcl flgs + * ------------------------------------------------------------------------------------------- + */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, NOP, NOP, SET8|0x1B, SET8|0x1B, DBG, NOP, 0x33, 0x00, +/* sc=02 */ '1', '!', NOP, NOP, SET8|'1', SET8|'!', NOP, NOP, 0x33, 0x00, +/* sc=03 */ '2', '@', 0x00, 0x00, SET8|'2', SET8|'@', SET8|0x00, SET8|0x00, 0x00, 0x00, +/* sc=04 */ '3', '#', NOP, NOP, SET8|'3', SET8|'#', NOP, NOP, 0x33, 0x00, +/* sc=05 */ '4', '$', NOP, NOP, SET8|'4', SET8|'$', NOP, NOP, 0x33, 0x00, +/* sc=06 */ '5', '%', NOP, NOP, SET8|'5', SET8|'%', NOP, NOP, 0x33, 0x00, +/* sc=07 */ '6', '^', 0x1E, 0x1E, SET8|'6', SET8|'^', SET8|0x1E, SET8|0x1E, 0x00, 0x00, +/* sc=08 */ '7', '&', NOP, NOP, SET8|'7', SET8|'&', NOP, NOP, 0x33, 0x00, +/* sc=09 */ '8', '*', NOP, NOP, SET8|'8', SET8|'*', NOP, NOP, 0x33, 0x00, +/* sc=0a */ '9', '(', NOP, NOP, SET8|'9', SET8|'(', NOP, NOP, 0x33, 0x00, +/* sc=0b */ '0', ')', NOP, NOP, SET8|'0', SET8|')', NOP, NOP, 0x33, 0x00, +/* sc=0c */ '-', '_', 0x1F, 0x1F, SET8|'-', SET8|'_', SET8|0x1F, SET8|0x1F, 0x00, 0x00, +/* sc=0d */ '=', '+', NOP, NOP, SET8|'=', SET8|'+', NOP, NOP, 0x33, 0x00, +/* sc=0e */ 0x08, 0x08, 0x7F, 0x7F, SET8|0x08, SET8|0x08, SET8|0x7F, SET8|0x7F, 0x00, 0x00, +/* sc=0f */ 0x09, F(16), NOP, NOP, SET8|0x09, F(16), NOP, NOP, 0x77, 0x00, +/* sc=10 */ 'q', 'Q', 0x11, 0x11, SET8|'q', SET8|'Q', SET8|0x11, SET8|0x11, 0x00, 0x01, +/* sc=11 */ 'w', 'W', 0x17, 0x17, SET8|'w', SET8|'W', SET8|0x17, SET8|0x17, 0x00, 0x01, +/* sc=12 */ 'e', 'E', 0x05, 0x05, SET8|'e', SET8|'E', SET8|0x05, SET8|0x05, 0x00, 0x01, +/* sc=13 */ 'r', 'R', 0x12, 0x12, SET8|'r', SET8|'R', SET8|0x12, SET8|0x12, 0x00, 0x01, +/* sc=14 */ 't', 'T', 0x14, 0x14, SET8|'t', SET8|'T', SET8|0x14, SET8|0x14, 0x00, 0x01, +/* sc=15 */ 'y', 'Y', 0x19, 0x19, SET8|'y', SET8|'Y', SET8|0x19, SET8|0x19, 0x00, 0x01, +/* sc=16 */ 'u', 'U', 0x15, 0x15, SET8|'u', SET8|'U', SET8|0x15, SET8|0x15, 0x00, 0x01, +/* sc=17 */ 'i', 'I', 0x09, 0x09, SET8|'i', SET8|'I', SET8|0x09, SET8|0x09, 0x00, 0x01, +/* sc=18 */ 'o', 'O', 0x0F, 0x0F, SET8|'o', SET8|'O', SET8|0x0F, SET8|0x0F, 0x00, 0x01, +/* sc=19 */ 'p', 'P', 0x10, 0x10, SET8|'p', SET8|'P', SET8|0x10, SET8|0x10, 0x00, 0x01, +/* sc=1a */ '[', '{', 0x1B, 0x1B, SET8|'[', SET8|'{', SET8|0x1B, SET8|0x1B, 0x00, 0x00, +/* sc=1b */ ']', '}', 0x1D, 0x1D, SET8|']', SET8|'}', SET8|0x1D, SET8|0x1D, 0x00, 0x00, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, SET8|0x0D, SET8|0x0D, SET8|0x0A, SET8|0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 'a', 'A', 0x01, 0x01, SET8|'a', SET8|'A', SET8|0x01, SET8|0x01, 0x00, 0x01, +/* sc=1f */ 's', 'S', 0x13, 0x13, SET8|'s', SET8|'S', SET8|0x13, SET8|0x13, 0x00, 0x01, +/* sc=20 */ 'd', 'D', 0x04, 0x04, SET8|'d', SET8|'D', SET8|0x04, SET8|0x04, 0x00, 0x01, +/* sc=21 */ 'f', 'F', 0x06, 0x06, SET8|'f', SET8|'F', SET8|0x06, SET8|0x06, 0x00, 0x01, +/* sc=22 */ 'g', 'G', 0x07, 0x07, SET8|'g', SET8|'G', SET8|0x07, SET8|0x07, 0x00, 0x01, +/* sc=23 */ 'h', 'H', 0x08, 0x08, SET8|'h', SET8|'H', SET8|0x08, SET8|0x08, 0x00, 0x01, +/* sc=24 */ 'j', 'J', 0x0A, 0x0A, SET8|'j', SET8|'J', SET8|0x0A, SET8|0x0A, 0x00, 0x01, +/* sc=25 */ 'k', 'K', 0x0B, 0x0B, SET8|'k', SET8|'K', SET8|0x0B, SET8|0x0B, 0x00, 0x01, +/* sc=26 */ 'l', 'L', 0x0C, 0x0C, SET8|'l', SET8|'L', SET8|0x0C, SET8|0x0C, 0x00, 0x01, +/* sc=27 */ ';', ':', NOP, NOP, SET8|';', SET8|':', NOP, NOP, 0x33, 0x00, +/* sc=28 */ '\'', '"', NOP, NOP, SET8|'\'', SET8|'"', NOP, NOP, 0x33, 0x00, +/* sc=29 */ '`', '~', NOP, NOP, SET8|'`', SET8|'~', NOP, NOP, 0x33, 0x00, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '\\', '|', 0x1C, 0x1C, SET8|'\\', SET8|'|', SET8|0x1C, SET8|0x1C, 0x00, 0x00, +/* sc=2c */ 'z', 'Z', 0x1A, 0x1A, SET8|'z', SET8|'Z', SET8|0x1A, SET8|0x1A, 0x00, 0x01, +/* sc=2d */ 'x', 'X', 0x18, 0x18, SET8|'x', SET8|'X', SET8|0x18, SET8|0x18, 0x00, 0x01, +/* sc=2e */ 'c', 'C', 0x03, 0x03, SET8|'c', SET8|'C', SET8|0x03, SET8|0x03, 0x00, 0x01, +/* sc=2f */ 'v', 'V', 0x16, 0x16, SET8|'v', SET8|'V', SET8|0x16, SET8|0x16, 0x00, 0x01, +/* sc=30 */ 'b', 'B', 0x02, 0x02, SET8|'b', SET8|'B', SET8|0x02, SET8|0x02, 0x00, 0x01, +/* sc=31 */ 'n', 'N', 0x0E, 0x0E, SET8|'n', SET8|'N', SET8|0x0E, SET8|0x0E, 0x00, 0x01, +/* sc=32 */ 'm', 'M', 0x0D, 0x0D, SET8|'m', SET8|'M', SET8|0x0D, SET8|0x0D, 0x00, 0x01, +/* sc=33 */ ',', '<', NOP, NOP, SET8|',', SET8|'<', NOP, NOP, 0x33, 0x00, +/* sc=34 */ '.', '>', NOP, NOP, SET8|'.', SET8|'>', NOP, NOP, 0x33, 0x00, +/* sc=35 */ '/', '?', NOP, NOP, SET8|'/', SET8|'?', NOP, NOP, 0x33, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', 0x0A, 0x0A, SET8|'*', SET8|'*', SET8|0x0A, SET8|0x0A, 0x00, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', SET8|' ', SET8|' ', SET8|' ', SET8|' ', 0x00, 0x00, +/* sc=3a */ ALK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, NLK, NLK, NLK, NLK, NLK, NLK, 0xFF, 0x00, +/* sc=46 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +/* sc=47 */ F(49), '7', '7', '7', SET8|'7', SET8|'7', SET8|'7', SET8|'7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', SET8|'8', SET8|'8', SET8|'8', SET8|'8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', SET8|'9', SET8|'9', SET8|'9', SET8|'9', 0x80, 0x02, +/* sc=4a */ F(52), '-', '-', '-', SET8|'-', SET8|'-', SET8|'-', SET8|'-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', SET8|'4', SET8|'4', SET8|'4', SET8|'4', 0x80, 0x02, +/* sc=4c */ F(48), '5', '5', '5', SET8|'5', SET8|'5', SET8|'5', SET8|'5', 0x80, 0x02, +/* sc=4d */ F(55), '6', '6', '6', SET8|'6', SET8|'6', SET8|'6', SET8|'6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', SET8|'+', SET8|'+', SET8|'+', SET8|'+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', SET8|'1', SET8|'1', SET8|'1', SET8|'1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', SET8|'2', SET8|'2', SET8|'2', SET8|'2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', SET8|'3', SET8|'3', SET8|'3', SET8|'3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', SET8|'0', SET8|'0', SET8|'0', SET8|'0', 0x80, 0x02, +/* sc=53 */ F(54), '.', 0x7F, 0x7F, SET8|0x7F, SET8|0x7F, RBT, SET8|0x7F, 0x82, 0x02, +/* sc=54 */ ALK, ALK, ALK, ALK, ALK, ALK, ALK, ALK, 0xFF, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0A, 0x0A, SET8|0x0D, SET8|0x0D, SET8|0x0A, SET8|0x0A, 0x00, 0x00, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', NOP, NOP, SET8|'/', SET8|'/', NOP, NOP, 0x33, 0x00, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), 0x7F, 0x7F, SET8|0x7F, SET8|0x7F, RBT, SET8|0x7F, 0xC2, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +/* sc=69 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=6a */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=6b */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=6c */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=6d */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=6e */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=6f */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=70 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=71 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=72 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=73 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=74 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=75 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=76 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=77 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=78 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=79 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=7a */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=7b */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=7c */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=7d */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=7e */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=7f */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* extended (ALTGR LOCK keys) */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, NOP, NOP, SET8|0x1B, SET8|0x1B, DBG, NOP, 0x33, 0x00, +/* sc=02 */ '!', '1', NOP, NOP, SET8|'1', SET8|'!', NOP, NOP, 0x33, 0x00, +/* sc=03 */ '"', '2', 0x00, 0x00, SET8|'2', SET8|'@', SET8|0x00, SET8|0x00, 0x00, 0x00, +/* sc=04 */ '\'', '3', NOP, NOP, SET8|'3', SET8|'#', NOP, NOP, 0x33, 0x00, +/* sc=05 */ ';', '4', NOP, NOP, SET8|'4', SET8|'$', NOP, NOP, 0x33, 0x00, +/* sc=06 */ ':', '5', NOP, NOP, SET8|'5', SET8|'%', NOP, NOP, 0x33, 0x00, +/* sc=07 */ ',', '6', 0x1E, 0x1E, SET8|'6', SET8|'^', SET8|0x1E, SET8|0x1E, 0x00, 0x00, +/* sc=08 */ '.', '7', NOP, NOP, SET8|'7', SET8|'&', NOP, NOP, 0x33, 0x00, +/* sc=09 */ '*', '8', NOP, NOP, SET8|'8', SET8|'*', NOP, NOP, 0x33, 0x00, +/* sc=0a */ '(', '9', NOP, NOP, SET8|'9', SET8|'(', NOP, NOP, 0x33, 0x00, +/* sc=0b */ ')', '0', NOP, NOP, SET8|'0', SET8|')', NOP, NOP, 0x33, 0x00, +/* sc=0c */ '-', '_', 0x1F, 0x1F, SET8|'-', SET8|'_', SET8|0x1F, SET8|0x1F, 0x00, 0x00, +/* sc=0d */ '=', '+', NOP, NOP, SET8|'=', SET8|'+', NOP, NOP, 0x33, 0x00, +/* sc=0e */ 0x08, 0x08, 0x7F, 0x7F, SET8|0x08, SET8|0x08, SET8|0x7F, SET8|0x7F, 0x00, 0x00, +/* sc=0f */ 0x09, F(16), NOP, NOP, SET8|0x09, F(16), NOP, NOP, 0x77, 0x00, +/* sc=10 */ 0xca, 0xea, 0x11, 0x11, SET8|'q', SET8|'Q', SET8|0x11, SET8|0x11, 0x00, 0x01, +/* sc=11 */ 0xc3, 0xe3, 0x17, 0x17, SET8|'w', SET8|'W', SET8|0x17, SET8|0x17, 0x00, 0x01, +/* sc=12 */ 0xd5, 0xf5, 0x05, 0x05, SET8|'e', SET8|'E', SET8|0x05, SET8|0x05, 0x00, 0x01, +/* sc=13 */ 0xcb, 0xeb, 0x12, 0x12, SET8|'r', SET8|'R', SET8|0x12, SET8|0x12, 0x00, 0x01, +/* sc=14 */ 0xc5, 0xe5, 0x14, 0x14, SET8|'t', SET8|'T', SET8|0x14, SET8|0x14, 0x00, 0x01, +/* sc=15 */ 0xce, 0xee, 0x19, 0x19, SET8|'y', SET8|'Y', SET8|0x19, SET8|0x19, 0x00, 0x01, +/* sc=16 */ 0xc7, 0xe7, 0x15, 0x15, SET8|'u', SET8|'U', SET8|0x15, SET8|0x15, 0x00, 0x01, +/* sc=17 */ 0xdb, 0xfb, 0x09, 0x09, SET8|'i', SET8|'I', SET8|0x09, SET8|0x09, 0x00, 0x01, +/* sc=18 */ 0xdd, 0xfd, 0x0F, 0x0F, SET8|'o', SET8|'O', SET8|0x0F, SET8|0x0F, 0x00, 0x01, +/* sc=19 */ 0xda, 0xfa, 0x10, 0x10, SET8|'p', SET8|'P', SET8|0x10, SET8|0x10, 0x00, 0x01, +/* sc=1a */ 0xc8, 0xe8, 0x1B, 0x1B, SET8|'[', SET8|'{', SET8|0x1B, SET8|0x1B, 0x00, 0x01, +/* sc=1b */ 0xdf, 0xff, 0x1D, 0x1D, SET8|']', SET8|'}', SET8|0x1D, SET8|0x1D, 0x00, 0x01, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, SET8|0x0D, SET8|0x0D, SET8|0x0A, SET8|0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 0xc6, 0xe6, 0x01, 0x01, SET8|'a', SET8|'A', SET8|0x01, SET8|0x01, 0x00, 0x01, +/* sc=1f */ 0xd9, 0xf9, 0x13, 0x13, SET8|'s', SET8|'S', SET8|0x13, SET8|0x13, 0x00, 0x01, +/* sc=20 */ 0xd7, 0xf7, 0x04, 0x04, SET8|'d', SET8|'D', SET8|0x04, SET8|0x04, 0x00, 0x01, +/* sc=21 */ 0xc1, 0xe1, 0x06, 0x06, SET8|'f', SET8|'F', SET8|0x06, SET8|0x06, 0x00, 0x01, +/* sc=22 */ 0xd0, 0xf0, 0x07, 0x07, SET8|'g', SET8|'G', SET8|0x07, SET8|0x07, 0x00, 0x01, +/* sc=23 */ 0xd2, 0xf2, 0x08, 0x08, SET8|'h', SET8|'H', SET8|0x08, SET8|0x08, 0x00, 0x01, +/* sc=24 */ 0xcf, 0xef, 0x0A, 0x0A, SET8|'j', SET8|'J', SET8|0x0A, SET8|0x0A, 0x00, 0x01, +/* sc=25 */ 0xcc, 0xec, 0x0B, 0x0B, SET8|'k', SET8|'K', SET8|0x0B, SET8|0x0B, 0x00, 0x01, +/* sc=26 */ 0xc4, 0xe4, 0x0C, 0x0C, SET8|'l', SET8|'L', SET8|0x0C, SET8|0x0C, 0x00, 0x01, +/* sc=27 */ 0xd6, 0xf6, NOP, NOP, SET8|';', SET8|':', NOP, NOP, 0x33, 0x01, +/* sc=28 */ 0xdc, 0xfc, NOP, NOP, SET8|'\'', SET8|'"', NOP, NOP, 0x33, 0x01, +/* sc=29 */ 0xa3, 0xb3, NOP, NOP, SET8|'`', SET8|'~', NOP, NOP, 0x33, 0x01, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '\\', '|', 0x1C, 0x1C, SET8|'\\', SET8|'|', SET8|0x1C, SET8|0x1C, 0x00, 0x00, +/* sc=2c */ 0xd1, 0xf1, 0x1A, 0x1A, SET8|'z', SET8|'Z', SET8|0x1A, SET8|0x1A, 0x00, 0x01, +/* sc=2d */ 0xde, 0xfe, 0x18, 0x18, SET8|'x', SET8|'X', SET8|0x18, SET8|0x18, 0x00, 0x01, +/* sc=2e */ 0xd3, 0xf3, 0x03, 0x03, SET8|'c', SET8|'C', SET8|0x03, SET8|0x03, 0x00, 0x01, +/* sc=2f */ 0xcd, 0xed, 0x16, 0x16, SET8|'v', SET8|'V', SET8|0x16, SET8|0x16, 0x00, 0x01, +/* sc=30 */ 0xc9, 0xe9, 0x02, 0x02, SET8|'b', SET8|'B', SET8|0x02, SET8|0x02, 0x00, 0x01, +/* sc=31 */ 0xd4, 0xf4, 0x0E, 0x0E, SET8|'n', SET8|'N', SET8|0x0E, SET8|0x0E, 0x00, 0x01, +/* sc=32 */ 0xd8, 0xf8, 0x0D, 0x0D, SET8|'m', SET8|'M', SET8|0x0D, SET8|0x0D, 0x00, 0x01, +/* sc=33 */ 0xc2, 0xe2, NOP, NOP, SET8|',', SET8|'<', NOP, NOP, 0x33, 0x01, +/* sc=34 */ 0xc0, 0xe0, NOP, NOP, SET8|'.', SET8|'>', NOP, NOP, 0x33, 0x01, +/* sc=35 */ '/', '?', NOP, NOP, SET8|'/', SET8|'?', NOP, NOP, 0x33, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', 0x0A, 0x0A, SET8|'*', SET8|'*', SET8|0x0A, SET8|0x0A, 0x00, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', SET8|' ', SET8|' ', SET8|' ', SET8|' ', 0x00, 0x00, +/* sc=3a */ ALK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, NLK, NLK, NLK, NLK, NLK, NLK, 0xFF, 0x00, +/* sc=46 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +/* sc=47 */ F(49), '7', '7', '7', SET8|'7', SET8|'7', SET8|'7', SET8|'7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', SET8|'8', SET8|'8', SET8|'8', SET8|'8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', SET8|'9', SET8|'9', SET8|'9', SET8|'9', 0x80, 0x02, +/* sc=4a */ F(52), '-', '-', '-', SET8|'-', SET8|'-', SET8|'-', SET8|'-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', SET8|'4', SET8|'4', SET8|'4', SET8|'4', 0x80, 0x02, +/* sc=4c */ F(48), '5', '5', '5', SET8|'5', SET8|'5', SET8|'5', SET8|'5', 0x80, 0x02, +/* sc=4d */ F(55), '6', '6', '6', SET8|'6', SET8|'6', SET8|'6', SET8|'6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', SET8|'+', SET8|'+', SET8|'+', SET8|'+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', SET8|'1', SET8|'1', SET8|'1', SET8|'1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', SET8|'2', SET8|'2', SET8|'2', SET8|'2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', SET8|'3', SET8|'3', SET8|'3', SET8|'3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', SET8|'0', SET8|'0', SET8|'0', SET8|'0', 0x80, 0x02, +/* sc=53 */ F(54), '.', 0x7F, 0x7F, SET8|0x7F, SET8|0x7F, RBT, SET8|0x7F, 0x82, 0x02, +/* sc=54 */ ALK, ALK, ALK, ALK, ALK, ALK, ALK, ALK, 0xFF, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0A, 0x0A, SET8|0x0D, SET8|0x0D, SET8|0x0A, SET8|0x0A, 0x00, 0x00, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', NOP, NOP, SET8|'/', SET8|'/', NOP, NOP, 0x33, 0x00, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), 0x7F, 0x7F, SET8|0x7F, SET8|0x7F, RBT, SET8|0x7F, 0xC2, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +}; + +#endif + +#if !defined(DKKEYMAP) && !defined(UKKEYMAP) && !defined(GRKEYMAP) && !defined(SWKEYMAP) && !defined(RUKEYMAP) +keymap_t key_map = { 0x69, /* US iso8859 keymap */ +/* alt + * scan cntrl alt alt cntrl + * code base shift cntrl shift alt shift cntrl shift spcl flgs + * --------------------------------------------------------------------------- + */ +/* sc=00 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=01 */ 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B, DBG, 0x1B, 0x02, 0x00, +/* sc=02 */ '1', '!', NOP, NOP, '1', '!', NOP, NOP, 0x33, 0x00, +/* sc=03 */ '2', '@', 0x00, 0x00, '2', '@', 0x00, 0x00, 0x00, 0x00, +/* sc=04 */ '3', '#', NOP, NOP, '3', '#', NOP, NOP, 0x33, 0x00, +/* sc=05 */ '4', '$', NOP, NOP, '4', '$', NOP, NOP, 0x33, 0x00, +/* sc=06 */ '5', '%', NOP, NOP, '5', '%', NOP, NOP, 0x33, 0x00, +/* sc=07 */ '6', '^', 0x1E, 0x1E, '6', '^', 0x1E, 0x1E, 0x00, 0x00, +/* sc=08 */ '7', '&', NOP, NOP, '7', '&', NOP, NOP, 0x33, 0x00, +/* sc=09 */ '8', '*', NOP, NOP, '8', '*', NOP, NOP, 0x33, 0x00, +/* sc=0a */ '9', '(', NOP, NOP, '9', '(', NOP, NOP, 0x33, 0x00, +/* sc=0b */ '0', ')', NOP, NOP, '0', ')', NOP, NOP, 0x33, 0x00, +/* sc=0c */ '-', '_', 0x1F, 0x1F, '-', '_', 0x1F, 0x1F, 0x00, 0x00, +/* sc=0d */ '=', '+', NOP, NOP, '=', '+', NOP, NOP, 0x33, 0x00, +/* sc=0e */ 0x08, 0x08, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00, 0x00, +/* sc=0f */ 0x09, 0x08, NOP, NOP, 0x09, 0x08, NOP, NOP, 0x33, 0x00, +/* sc=10 */ 'q', 'Q', 0x11, 0x11, 'q', 'Q', 0x11, 0x11, 0x00, 0x01, +/* sc=11 */ 'w', 'W', 0x17, 0x17, 'w', 'W', 0x17, 0x17, 0x00, 0x01, +/* sc=12 */ 'e', 'E', 0x05, 0x05, 'e', 'E', 0x05, 0x05, 0x00, 0x01, +/* sc=13 */ 'r', 'R', 0x12, 0x12, 'r', 'R', 0x12, 0x12, 0x00, 0x01, +/* sc=14 */ 't', 'T', 0x14, 0x14, 't', 'T', 0x14, 0x14, 0x00, 0x01, +/* sc=15 */ 'y', 'Y', 0x19, 0x19, 'y', 'Y', 0x19, 0x19, 0x00, 0x01, +/* sc=16 */ 'u', 'U', 0x15, 0x15, 'u', 'U', 0x15, 0x15, 0x00, 0x01, +/* sc=17 */ 'i', 'I', 0x09, 0x09, 'i', 'I', 0x09, 0x09, 0x00, 0x01, +/* sc=18 */ 'o', 'O', 0x0F, 0x0F, 'o', 'O', 0x0F, 0x0F, 0x00, 0x01, +/* sc=19 */ 'p', 'P', 0x10, 0x10, 'p', 'P', 0x10, 0x10, 0x00, 0x01, +/* sc=1a */ '[', '{', 0x1B, 0x1B, '[', '{', 0x1B, 0x1B, 0x00, 0x00, +/* sc=1b */ ']', '}', 0x1D, 0x1D, ']', '}', 0x1D, 0x1D, 0x00, 0x00, +/* sc=1c */ 0x0D, 0x0D, 0x0A, 0x0A, 0x0D, 0x0D, 0x0A, 0x0A, 0x00, 0x00, +/* sc=1d */ LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, LCTR, 0xFF, 0x00, +/* sc=1e */ 'a', 'A', 0x01, 0x01, 'a', 'A', 0x01, 0x01, 0x00, 0x01, +/* sc=1f */ 's', 'S', 0x13, 0x13, 's', 'S', 0x13, 0x13, 0x00, 0x01, +/* sc=20 */ 'd', 'D', 0x04, 0x04, 'd', 'D', 0x04, 0x04, 0x00, 0x01, +/* sc=21 */ 'f', 'F', 0x06, 0x06, 'f', 'F', 0x06, 0x06, 0x00, 0x01, +/* sc=22 */ 'g', 'G', 0x07, 0x07, 'g', 'G', 0x07, 0x07, 0x00, 0x01, +/* sc=23 */ 'h', 'H', 0x08, 0x08, 'h', 'H', 0x08, 0x08, 0x00, 0x01, +/* sc=24 */ 'j', 'J', 0x0A, 0x0A, 'j', 'J', 0x0A, 0x0A, 0x00, 0x01, +/* sc=25 */ 'k', 'K', 0x0B, 0x0B, 'k', 'K', 0x0B, 0x0B, 0x00, 0x01, +/* sc=26 */ 'l', 'L', 0x0C, 0x0C, 'l', 'L', 0x0C, 0x0C, 0x00, 0x01, +/* sc=27 */ ';', ':', NOP, NOP, ';', ':', NOP, NOP, 0x33, 0x00, +/* sc=28 */ '\'', '"', NOP, NOP, '\'', '"', NOP, NOP, 0x33, 0x00, +/* sc=29 */ '`', '~', NOP, NOP, '`', '~', NOP, NOP, 0x33, 0x00, +/* sc=2a */ LSH, LSH, LSH, LSH, LSH, LSH, LSH, LSH, 0xFF, 0x00, +/* sc=2b */ '\\', '|', 0x1C, 0x1C, '\\', '|', 0x1C, 0x1C, 0x00, 0x00, +/* sc=2c */ 'z', 'Z', 0x1A, 0x1A, 'z', 'Z', 0x1A, 0x1A, 0x00, 0x01, +/* sc=2d */ 'x', 'X', 0x18, 0x18, 'x', 'X', 0x18, 0x18, 0x00, 0x01, +/* sc=2e */ 'c', 'C', 0x03, 0x03, 'c', 'C', 0x03, 0x03, 0x00, 0x01, +/* sc=2f */ 'v', 'V', 0x16, 0x16, 'v', 'V', 0x16, 0x16, 0x00, 0x01, +/* sc=30 */ 'b', 'B', 0x02, 0x02, 'b', 'B', 0x02, 0x02, 0x00, 0x01, +/* sc=31 */ 'n', 'N', 0x0E, 0x0E, 'n', 'N', 0x0E, 0x0E, 0x00, 0x01, +/* sc=32 */ 'm', 'M', 0x0D, 0x0D, 'm', 'M', 0x0D, 0x0D, 0x00, 0x01, +/* sc=33 */ ',', '<', NOP, NOP, ',', '<', NOP, NOP, 0x33, 0x00, +/* sc=34 */ '.', '>', NOP, NOP, '.', '>', NOP, NOP, 0x33, 0x00, +/* sc=35 */ '/', '?', NOP, NOP, '/', '?', NOP, NOP, 0x33, 0x00, +/* sc=36 */ RSH, RSH, RSH, RSH, RSH, RSH, RSH, RSH, 0xFF, 0x00, +/* sc=37 */ '*', '*', 0x0A, 0x0A, '*', '*', 0x0A, 0x0A, 0x33, 0x00, +/* sc=38 */ LALT, LALT, LALT, LALT, LALT, LALT, LALT, LALT, 0xFF, 0x00, +/* sc=39 */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0x00, 0x00, +/* sc=3a */ CLK, CLK, CLK, CLK, CLK, CLK, CLK, CLK, 0xFF, 0x00, +/* sc=3b */ F( 1), F(13), F(25), F(37), S( 1), S(11), S( 1), S(11), 0xFF, 0x00, +/* sc=3c */ F( 2), F(14), F(26), F(38), S( 2), S(12), S( 2), S(12), 0xFF, 0x00, +/* sc=3d */ F( 3), F(15), F(27), F(39), S( 3), S(13), S( 3), S(13), 0xFF, 0x00, +/* sc=3e */ F( 4), F(16), F(28), F(40), S( 4), S(14), S( 4), S(14), 0xFF, 0x00, +/* sc=3f */ F( 5), F(17), F(29), F(41), S( 5), S(15), S( 5), S(15), 0xFF, 0x00, +/* sc=40 */ F( 6), F(18), F(30), F(42), S( 6), S(16), S( 6), S(16), 0xFF, 0x00, +/* sc=41 */ F( 7), F(19), F(31), F(43), S( 7), S( 7), S( 7), S( 7), 0xFF, 0x00, +/* sc=42 */ F( 8), F(20), F(32), F(44), S( 8), S( 8), S( 8), S( 8), 0xFF, 0x00, +/* sc=43 */ F( 9), F(21), F(33), F(45), S( 9), S( 9), S( 9), S( 9), 0xFF, 0x00, +/* sc=44 */ F(10), F(22), F(34), F(46), S(10), S(10), S(10), S(10), 0xFF, 0x00, +/* sc=45 */ NLK, NLK, NLK, NLK, NLK, NLK, NLK, NLK, 0xFF, 0x00, +/* sc=46 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +/* sc=47 */ F(49), '7', '7', '7', '7', '7', '7', '7', 0x80, 0x02, +/* sc=48 */ F(50), '8', '8', '8', '8', '8', '8', '8', 0x80, 0x02, +/* sc=49 */ F(51), '9', '9', '9', '9', '9', '9', '9', 0x80, 0x02, +/* sc=4a */ F(52), '-', '-', '-', '-', '-', '-', '-', 0x80, 0x02, +/* sc=4b */ F(53), '4', '4', '4', '4', '4', '4', '4', 0x80, 0x02, +/* sc=4c */ NOP, '5', '5', '5', '5', '5', '5', '5', 0x80, 0x02, +/* sc=4d */ F(55), '6', '6', '6', '6', '6', '6', '6', 0x80, 0x02, +/* sc=4e */ F(56), '+', '+', '+', '+', '+', '+', '+', 0x80, 0x02, +/* sc=4f */ F(57), '1', '1', '1', '1', '1', '1', '1', 0x80, 0x02, +/* sc=50 */ F(58), '2', '2', '2', '2', '2', '2', '2', 0x80, 0x02, +/* sc=51 */ F(59), '3', '3', '3', '3', '3', '3', '3', 0x80, 0x02, +/* sc=52 */ F(60), '0', '0', '0', '0', '0', '0', '0', 0x80, 0x02, +/* sc=53 */ 0x7F, '.', 0x7F, 0x7F, 0x7F, 0x7F, RBT, 0x7F, 0x02, 0x02, +/* sc=54 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=55 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=56 */ NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=57 */ F(11), F(23), F(35), F(47), S(11), S(11), S(11), S(11), 0xFF, 0x00, +/* sc=58 */ F(12), F(24), F(36), F(48), S(12), S(12), S(12), S(12), 0xFF, 0x00, +/* sc=59 */ 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x00, 0x00, +/* sc=5a */ RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, RCTR, 0xFF, 0x00, +/* sc=5b */ '/', '/', '/', '/', '/', '/', '/', '/', 0x00, 0x00, +/* sc=5c */ NEXT, NOP, DBG, NOP, NOP, NOP, NOP, NOP, 0xFF, 0x00, +/* sc=5d */ RALT, RALT, RALT, RALT, RALT, RALT, RALT, RALT, 0xFF, 0x00, +/* sc=5e */ F(49), F(49), F(49), F(49), F(49), F(49), F(49), F(49), 0xFF, 0x00, +/* sc=5f */ F(50), F(50), F(50), F(50), F(50), F(50), F(50), F(50), 0xFF, 0x00, +/* sc=60 */ F(51), F(51), F(51), F(51), F(51), F(51), F(51), F(51), 0xFF, 0x00, +/* sc=61 */ F(53), F(53), F(53), F(53), F(53), F(53), F(53), F(53), 0xFF, 0x00, +/* sc=62 */ F(55), F(55), F(55), F(55), F(55), F(55), F(55), F(55), 0xFF, 0x00, +/* sc=63 */ F(57), F(57), F(57), F(57), F(57), F(57), F(57), F(57), 0xFF, 0x00, +/* sc=64 */ F(58), F(58), F(58), F(58), F(58), F(58), F(58), F(58), 0xFF, 0x00, +/* sc=65 */ F(59), F(59), F(59), F(59), F(59), F(59), F(59), F(59), 0xFF, 0x00, +/* sc=66 */ F(60), F(60), F(60), F(60), F(60), F(60), F(60), F(60), 0xFF, 0x00, +/* sc=67 */ F(54), F(54), F(54), F(54), F(54), F(54), RBT, F(54), 0xFF, 0x00, +/* sc=68 */ SLK, SLK, SLK, SLK, SLK, SLK, SLK, SLK, 0xFF, 0x00, +}; + +#endif + +fkeytab_t fkey_tab[60] = { +/* 00-03 */ {"\033[M", 3}, {"\033[N", 3}, {"\033[O", 3}, {"\033[P", 3}, +/* 04-07 */ {"\033[Q", 3}, {"\033[R", 3}, {"\033[S", 3}, {"\033[T", 3}, +/* 08-0B */ {"\033[U", 3}, {"\033[V", 3}, {"\033[W", 3}, {"\033[X", 3}, +/* 0C-0F */ {"\033[W", 3}, {"\033[X", 3}, {"\033[Y", 3}, {"\033[Z", 3}, +/* 10-13 */ {"\033[a", 3}, {"\033[b", 3}, {"\033[c", 3}, {"\033[d", 3}, +/* 14-17 */ {"\033[e", 3}, {"\033[f", 3}, {"\033[g", 3}, {"\033[h", 3}, +/* 18-1B */ {"\033[g", 3}, {"\033[h", 3}, {"\033[i", 3}, {"\033[j", 3}, +/* 1C-1F */ {"\033[k", 3}, {"\033[l", 3}, {"\033[m", 3}, {"\033[n", 3}, +/* 20-23 */ {"\033[o", 3}, {"\033[p", 3}, {"\033[q", 3}, {"\033[r", 3}, +/* 24-27 */ {"\033[g", 3}, {"\033[h", 3}, {"\033[i", 3}, {"\033[j", 3}, +/* 28-2B */ {"\033[k", 3}, {"\033[l", 3}, {"\033[m", 3}, {"\033[n", 3}, +/* 2C-2F */ {"\033[o", 3}, {"\033[p", 3}, {"\033[q", 3}, {"\033[r", 3}, +/* 30-33 */ {"\033[H", 3}, {"\033[A", 3}, {"\033[I", 3}, {"-" , 1}, +/* 34-37 */ {"\033[D", 3}, {"\177" , 1}, {"\033[C", 3}, {"+" , 1}, +/* 38-3B */ {"\033[F", 3}, {"\033[B", 3}, {"\033[G", 3}, {"\033[L", 3} +}; diff --git a/sys/i386/isa/lpt.c b/sys/i386/isa/lpt.c new file mode 100644 index 0000000..00080c3 --- /dev/null +++ b/sys/i386/isa/lpt.c @@ -0,0 +1,1040 @@ +/* + * Copyright (c) 1990 William F. Jolitz, TeleMuse + * 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 software is a component of "386BSD" developed by + * William F. Jolitz, TeleMuse. + * 4. Neither the name of the developer nor the name "386BSD" + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ + * AND IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS + * SOFTWARE SHOULD NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT. + * THE DEVELOPER URGES THAT USERS WHO REQUIRE A COMMERCIAL PRODUCT + * NOT MAKE USE OF THIS WORK. + * + * FOR USERS WHO WISH TO UNDERSTAND THE 386BSD SYSTEM DEVELOPED + * BY WILLIAM F. JOLITZ, WE RECOMMEND THE USER STUDY WRITTEN + * REFERENCES SUCH AS THE "PORTING UNIX TO THE 386" SERIES + * (BEGINNING JANUARY 1991 "DR. DOBBS JOURNAL", USA AND BEGINNING + * JUNE 1991 "UNIX MAGAZIN", GERMANY) BY WILLIAM F. JOLITZ AND + * LYNNE GREER JOLITZ, AS WELL AS OTHER BOOKS ON UNIX AND THE + * ON-LINE 386BSD USER MANUAL BEFORE USE. A BOOK DISCUSSING THE INTERNALS + * OF 386BSD ENTITLED "386BSD FROM THE INSIDE OUT" WILL BE AVAILABLE LATE 1992. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``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 DEVELOPER 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. + * + * from: unknown origin, 386BSD 0.1 + * $Id: lpt.c,v 1.15 1994/08/14 01:46:28 phk Exp $ + */ + +/* + * Device Driver for AT parallel printer port + * Written by William Jolitz 12/18/90 + */ + +/* + * Parallel port TCP/IP interfaces added. I looked at the driver from + * MACH but this is a complete rewrite, and btw. incompatible, and it + * should perform better too. I have never run the MACH driver though. + * + * This driver sends two bytes (0x08, 0x00) in front of each packet, + * to allow us to distinguish another format later. + * + * TODO: + * Make Linux/Crynwr compatible mode, use IF_LLC0 to enable. + * Make HDLC/PPP mode, use IF_LLC1 to enable. + * + * Connect the two computers using a Laplink parallel cable to use this + * feature: + * + * +----------------------------------------+ + * |A-name A-End B-End Descr. Port/Bit | + * +----------------------------------------+ + * |DATA0 2 15 Data 0/0x01 | + * |-ERROR 15 2 1/0x08 | + * +----------------------------------------+ + * |DATA1 3 13 Data 0/0x02 | + * |+SLCT 13 3 1/0x10 | + * +----------------------------------------+ + * |DATA2 4 12 Data 0/0x04 | + * |+PE 12 4 1/0x20 | + * +----------------------------------------+ + * |DATA3 5 10 Strobe 0/0x08 | + * |-ACK 10 5 1/0x40 | + * +----------------------------------------+ + * |DATA4 6 11 Data 0/0x10 | + * |BUSY 11 6 1/~0x80 | + * +----------------------------------------+ + * |GND 18-25 18-25 GND - | + * +----------------------------------------+ + * + * Expect transfer-rates up to 75 kbyte/sec. + * + * If GCC could correctly grok + * register int port asm("edx") + * the code would be cleaner + * + * Poul-Henning Kamp <phk@login.dkuug.dk> + */ + +#include "lpt.h" +#if NLPT > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/user.h> +#include <sys/buf.h> +#include <sys/kernel.h> +#include <sys/ioctl.h> +#include <sys/tty.h> +#include <sys/uio.h> +#include <sys/syslog.h> + +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/lptreg.h> + +#include <i386/include/lpt.h> + +#ifdef INET +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <net/if.h> +#include <net/if_types.h> +#include <net/netisr.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#endif /* INET */ + +#define LPINITRDY 4 /* wait up to 4 seconds for a ready */ +#define LPTOUTTIME 4 /* wait up to 4 seconds for a ready */ +#define LPPRI (PZERO+8) +#define BUFSIZE 1024 + +#ifdef INET +#ifndef LPMTU /* MTU for the lp# interfaces */ +#define LPMTU 1500 +#endif + +#ifndef LPMAXSPIN1 /* DELAY factor for the lp# interfaces */ +#define LPMAXSPIN1 8000 /* Spinning for remote intr to happen */ +#endif + +#ifndef LPMAXSPIN2 /* DELAY factor for the lp# interfaces */ +#define LPMAXSPIN2 500 /* Spinning for remote handshake to happen */ +#endif + +#ifndef LPMAXERRS /* Max errors before !RUNNING */ +#define LPMAXERRS 100 +#endif + +#define LPHDR 2 /* We send 0x08, 0x00 in front of packet */ +#endif /* INET */ + +/* BIOS printer list - used by BIOS probe*/ +#define BIOS_LPT_PORTS 0x408 +#define BIOS_PORTS (short *)(KERNBASE+BIOS_LPT_PORTS) +#define BIOS_MAX_LPT 4 + + +#ifndef DEBUG +#define lprintf (void) +#else +#define lprintf if (lptflag) printf +int lptflag = 1; +#endif + +#define LPTUNIT(s) ((s)&0x03) +#define LPTFLAGS(s) ((s)&0xfc) + +struct lpt_softc { + short sc_port; + short sc_state; + /* default case: negative prime, negative ack, handshake strobe, + prime once */ + u_char sc_control; + char sc_flags; +#define LP_POS_INIT 0x04 /* if we are a postive init signal */ +#define LP_POS_ACK 0x08 /* if we are a positive going ack */ +#define LP_NO_PRIME 0x10 /* don't prime the printer at all */ +#define LP_PRIMEOPEN 0x20 /* prime on every open */ +#define LP_AUTOLF 0x40 /* tell printer to do an automatic lf */ +#define LP_BYPASS 0x80 /* bypass printer ready checks */ + struct buf *sc_inbuf; + short sc_xfercnt ; + char sc_primed; + char *sc_cp ; + u_char sc_irq ; /* IRQ status of port */ +#define LP_HAS_IRQ 0x01 /* we have an irq available */ +#define LP_USE_IRQ 0x02 /* we are using our irq */ +#define LP_ENABLE_IRQ 0x04 /* enable IRQ on open */ + +#ifdef INET + struct ifnet sc_if; + u_char sc_ifbuf[LPMTU+LPHDR]; + int sc_iferrs; +#endif /* ENDIF */ + +} lpt_sc[NLPT] ; + +/* bits for state */ +#define OPEN (1<<0) /* device is open */ +#define ASLP (1<<1) /* awaiting draining of printer */ +#define ERROR (1<<2) /* error was received from printer */ +#define OBUSY (1<<3) /* printer is busy doing output */ +#define LPTOUT (1<<4) /* timeout while not selected */ +#define TOUT (1<<5) /* timeout while not selected */ +#define INIT (1<<6) /* waiting to initialize for open */ +#define INTERRUPTED (1<<7) /* write call was interrupted */ + + +/* status masks to interrogate printer status */ +#define RDY_MASK (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR) /* ready ? */ +#define LP_READY (LPS_SEL|LPS_NBSY|LPS_NERR) + +/* Printer Ready condition - from lpa.c */ +/* Only used in polling code */ +#define LPS_INVERT (LPS_NBSY | LPS_NACK | LPS_SEL | LPS_NERR) +#define LPS_MASK (LPS_NBSY | LPS_NACK | LPS_OUT | LPS_SEL | LPS_NERR) +#define NOT_READY(x) ((inb(x)^LPS_INVERT)&LPS_MASK) + +#define MAX_SLEEP (hz*5) /* Timeout while waiting for device ready */ +#define MAX_SPIN 20 /* Max delay for device ready in usecs */ + +static void lptout (struct lpt_softc * sc); +int lptprobe (struct isa_device *dvp); +int lptattach (struct isa_device *isdp); +void lptintr (int unit); + +#ifdef INET +/* Tables for the lp# interface */ +static unsigned char txmith[1024]; +#define txmitl (txmith+256) +#define trecvh (txmith+512) +#define trecvl (txmith+768) + +/* Functions for the lp# interface */ +static void lpattach(struct lpt_softc *,int); +static void lpinittables(); +static int lpioctl(struct ifnet *, int, caddr_t); +static int lpoutput(struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *); +static void lpintr(int); +#endif /* INET */ + +struct isa_driver lptdriver = { + lptprobe, lptattach, "lpt" +}; + + + +/* + * Internal routine to lptprobe to do port tests of one byte value + */ +static int +lpt_port_test(short port, u_char data, u_char mask) +{ + int temp, timeout; + + data = data & mask; + outb(port, data); + timeout = 10000; + do { + DELAY(10); + temp = inb(port) & mask; + } + while (temp != data && --timeout); + lprintf("Port 0x%x\tout=%x\tin=%x\ttout=%d\n", + port, data, temp, timeout); + return (temp == data); +} + +/* + * New lpt port probe Geoff Rehmet - Rhodes University - 14/2/94 + * Based partially on Rod Grimes' printer probe + * + * Logic: + * 1) If no port address was given, use the bios detected ports + * and autodetect what ports the printers are on. + * 2) Otherwise, probe the data port at the address given, + * using the method in Rod Grimes' port probe. + * (Much code ripped off directly from Rod's probe.) + * + * Comments from Rod's probe: + * Logic: + * 1) You should be able to write to and read back the same value + * to the data port. Do an alternating zeros, alternating ones, + * walking zero, and walking one test to check for stuck bits. + * + * 2) You should be able to write to and read back the same value + * to the control port lower 5 bits, the upper 3 bits are reserved + * per the IBM PC technical reference manauls and different boards + * do different things with them. Do an alternating zeros, alternating + * ones, walking zero, and walking one test to check for stuck bits. + * + * Some printers drag the strobe line down when the are powered off + * so this bit has been masked out of the control port test. + * + * XXX Some printers may not like a fast pulse on init or strobe, I + * don't know at this point, if that becomes a problem these bits + * should be turned off in the mask byte for the control port test. + * + * We are finally left with a mask of 0x14, due to some printers + * being adamant about holding other bits high ........ + * + * Before probing the control port, we write a 0 to the data port - + * If not, some printers chuck out garbage when the strobe line + * gets toggled. + * + * 3) Set the data and control ports to a value of 0 + * + * This probe routine has been tested on Epson Lx-800, HP LJ3P, + * Epson FX-1170 and C.Itoh 8510RM + * printers. + * Quick exit on fail added. + */ +int +lptprobe(struct isa_device *dvp) +{ + short port; + static short next_bios_lpt = 0; + int status; + u_char data; + u_char mask; + int i; + + /* + * Make sure there is some way for lptopen to see that + * the port is not configured + * This 0 will remain if the port isn't attached + */ + (lpt_sc + dvp->id_unit)->sc_port = 0; + + status = IO_LPTSIZE; + /* If port not specified, use bios list */ + if(dvp->id_iobase < 0) { /* port? */ + if((next_bios_lpt < BIOS_MAX_LPT) && + (*(BIOS_PORTS+next_bios_lpt) != 0) ) { + dvp->id_iobase = *(BIOS_PORTS+next_bios_lpt++); + goto end_probe; + } else + return (0); + } + + /* Port was explicitly specified */ + /* This allows probing of ports unknown to the BIOS */ + + port = dvp->id_iobase + lpt_data; + mask = 0xff; + data = 0x55; /* Alternating zeros */ + if (!lpt_port_test(port, data, mask)) + { status = 0 ; goto end_probe ; } + + data = 0xaa; /* Alternating ones */ + if (!lpt_port_test(port, data, mask)) + { status = 0 ; goto end_probe ; } + + for (i = 0; i < 8; i++) { /* Walking zero */ + data = ~(1 << i); + if (!lpt_port_test(port, data, mask)) + { status = 0 ; goto end_probe ; } + } + + for (i = 0; i < 8; i++) { /* Walking one */ + data = (1 << i); + if (!lpt_port_test(port, data, mask)) + { status = 0 ; goto end_probe ; } + } + +end_probe: + /* write 0's to control and data ports */ + outb(dvp->id_iobase+lpt_data, 0); + outb(dvp->id_iobase+lpt_control, 0); + + return (status); +} + +/* XXX Todo - try and detect if interrupt is working */ +int +lptattach(struct isa_device *isdp) +{ + struct lpt_softc *sc; + + sc = lpt_sc + isdp->id_unit; + sc->sc_port = isdp->id_iobase; + sc->sc_primed = 0; /* not primed yet */ + outb(sc->sc_port+lpt_control, LPC_NINIT); + + /* check if we can use interrupt */ + lprintf("oldirq %x\n", sc->sc_irq); + if(isdp->id_irq) { + sc->sc_irq = LP_HAS_IRQ | LP_USE_IRQ | LP_ENABLE_IRQ; + printf("lpt%d: Interrupt-driven port\n", isdp->id_unit); +#ifdef INET + lpattach(sc,isdp->id_unit); +#endif + } else { + sc->sc_irq = 0; + lprintf("lpt%d: Polled port\n", isdp->id_unit); + } + lprintf("irq %x\n", sc->sc_irq); + + return (1); +} + +/* + * lptopen -- reset the printer, then wait until it's selected and not busy. + */ + +int +lptopen(dev_t dev, int flag) +{ + struct lpt_softc *sc; + int s; + int trys, port; + u_int unit = LPTUNIT(minor(dev)); + + sc = lpt_sc + unit; + if ((unit >= NLPT) || (sc->sc_port == 0)) + return (ENXIO); + +#ifdef INET + if (sc->sc_if.if_flags & IFF_UP) + return(EBUSY); +#endif + if (sc->sc_state) { + lprintf("lp: still open\n") ; + lprintf("still open %x\n", sc->sc_state); + return(EBUSY); + } else sc->sc_state |= INIT; + + s = spltty(); + sc->sc_flags = LPTFLAGS(minor(dev)); + lprintf("lp flags 0x%x\n", sc->sc_flags); + port = sc->sc_port; + + /* set IRQ status according to ENABLE_IRQ flag */ + if(sc->sc_irq & LP_ENABLE_IRQ) + sc->sc_irq |= LP_USE_IRQ; + else + sc->sc_irq &= ~LP_USE_IRQ; + + + /* init printer */ + if((sc->sc_flags & LP_NO_PRIME) == 0) { + if((sc->sc_flags & LP_PRIMEOPEN) || sc->sc_primed == 0) { + outb(port+lpt_control, 0); + sc->sc_primed++; + DELAY(500); + } + } + outb(port+lpt_control, LPC_SEL|LPC_NINIT); + + /* wait till ready (printer running diagnostics) */ + trys = 0; + do { + /* ran out of waiting for the printer */ + if (trys++ >= LPINITRDY*4) { + splx(s); + sc->sc_state = 0; + lprintf ("status %x\n", inb(port+lpt_status) ); + return (EBUSY); + } + + /* wait 1/4 second, give up if we get a signal */ + if (tsleep ((caddr_t)sc, LPPRI|PCATCH, "lptinit", + hz/4) != EWOULDBLOCK) { + sc->sc_state = 0; + splx(s); + return (EBUSY); + } + + /* is printer online and ready for output */ + } while ((inb(port+lpt_status) & (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR)) != + (LPS_SEL|LPS_NBSY|LPS_NERR)); + + sc->sc_control = LPC_SEL|LPC_NINIT; + if(sc->sc_flags&LP_AUTOLF) + sc->sc_control |= LPC_AUTOL; + /* enable interrupt if interrupt-driven */ + if(sc->sc_irq & LP_USE_IRQ) + sc->sc_control |= LPC_ENA; + + outb(port+lpt_control, sc->sc_control); + + sc->sc_state = OPEN; + sc->sc_inbuf = geteblk(BUFSIZE); + sc->sc_xfercnt = 0; + splx(s); + + /* only use timeout if using interrupt */ + lprintf("irq %x\n", sc->sc_irq); + if(sc->sc_irq & LP_USE_IRQ) { + sc->sc_state |= TOUT; + timeout ((timeout_func_t)lptout, (caddr_t)sc, hz/2); + } + lprintf("opened.\n"); + return(0); +} + +static void +lptout (struct lpt_softc * sc) +{ int pl; + + lprintf ("T %x ", inb(sc->sc_port+lpt_status)); + if (sc->sc_state&OPEN) + timeout ((timeout_func_t)lptout, (caddr_t)sc, hz/2); + else sc->sc_state &= ~TOUT; + + if (sc->sc_state & ERROR) + sc->sc_state &= ~ERROR; + + /* + * Avoid possible hangs do to missed interrupts + */ + if (sc->sc_xfercnt) { + pl = spltty(); + lptintr(sc - lpt_sc); + splx(pl); + } else { + sc->sc_state &= ~OBUSY; + wakeup((caddr_t)sc); + } +} + +/* + * lptclose -- close the device, free the local line buffer. + * + * Check for interrupted write call added. + */ + +int +lptclose(dev_t dev, int flag) +{ + struct lpt_softc *sc = lpt_sc + LPTUNIT(minor(dev)); + int port = sc->sc_port; + + sc->sc_state &= ~OPEN; + + /* if the last write was interrupted, don't complete it */ + if((!(sc->sc_state & INTERRUPTED)) && (sc->sc_irq & LP_USE_IRQ)) + while ((inb(port+lpt_status) & (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR)) != + (LPS_SEL|LPS_NBSY|LPS_NERR) || sc->sc_xfercnt) + /* wait 1/4 second, give up if we get a signal */ + if (tsleep ((caddr_t)sc, LPPRI|PCATCH, + "lpclose", hz) != EWOULDBLOCK) + break; + + sc->sc_state = 0; + sc->sc_xfercnt = 0; + outb(sc->sc_port+lpt_control, LPC_NINIT); + brelse(sc->sc_inbuf); + lprintf("closed.\n"); + return(0); +} + +/* + * pushbytes() + * Workhorse for actually spinning and writing bytes to printer + * Derived from lpa.c + * Originally by ? + * + * This code is only used when we are polling the port + */ +static int +pushbytes(struct lpt_softc * sc) +{ + int spin, err, tic; + char ch; + int port = sc->sc_port; + + lprintf("p"); + /* loop for every character .. */ + while (sc->sc_xfercnt > 0) { + /* printer data */ + ch = *(sc->sc_cp); + sc->sc_cp++; + sc->sc_xfercnt--; + + /* + * Wait for printer ready. + * Loop 20 usecs testing BUSY bit, then sleep + * for exponentially increasing timeout. (vak) + */ + for (spin=0; NOT_READY(port+lpt_status) && spin<MAX_SPIN; ++spin) + DELAY(1); /* XXX delay is NOT this accurate! */ + if (spin >= MAX_SPIN) { + tic = 0; + while (NOT_READY(port+lpt_status)) { + /* + * Now sleep, every cycle a + * little longer .. + */ + tic = tic + tic + 1; + /* + * But no more than 10 seconds. (vak) + */ + if (tic > MAX_SLEEP) + tic = MAX_SLEEP; + err = tsleep((caddr_t)sc, LPPRI, + "lptpoll", tic); + if (err != EWOULDBLOCK) { + return (err); + } + } + } + + /* output data */ + outb(port+lpt_data, ch); + /* strobe */ + outb(port+lpt_control, sc->sc_control|LPC_STB); + outb(port+lpt_control, sc->sc_control); + + } + return(0); +} + +/* + * lptwrite --copy a line from user space to a local buffer, then call + * putc to get the chars moved to the output queue. + * + * Flagging of interrupted write added. + */ + +int +lptwrite(dev_t dev, struct uio * uio) +{ + register unsigned n; + int pl, err; + struct lpt_softc *sc = lpt_sc + LPTUNIT(minor(dev)); + + sc->sc_state &= ~INTERRUPTED; + while (n = min(BUFSIZE, uio->uio_resid)) { + sc->sc_cp = sc->sc_inbuf->b_un.b_addr ; + uiomove(sc->sc_cp, n, uio); + sc->sc_xfercnt = n ; + while ((sc->sc_xfercnt > 0)&&(sc->sc_irq & LP_USE_IRQ)) { + lprintf("i"); + /* if the printer is ready for a char, */ + /* give it one */ + if ((sc->sc_state & OBUSY) == 0){ + lprintf("\nC %d. ", sc->sc_xfercnt); + pl = spltty(); + lptintr(sc - lpt_sc); + (void) splx(pl); + } + lprintf("W "); + if (sc->sc_state & OBUSY) + if ((err = tsleep ((caddr_t)sc, + LPPRI|PCATCH, "lpwrite", 0))) { + sc->sc_state |= INTERRUPTED; + return(err); + } + } + /* check to see if we must do a polled write */ + if(!(sc->sc_irq & LP_USE_IRQ) && (sc->sc_xfercnt)) { + lprintf("p"); + if((err = pushbytes(sc))) + return(err); + } + } + return(0); +} + +/* + * lptintr -- handle printer interrupts which occur when the printer is + * ready to accept another char. + * + * do checking for interrupted write call. + */ + +void +lptintr(int unit) +{ + struct lpt_softc *sc = lpt_sc + unit; + int port = sc->sc_port, sts; + +#ifdef INET + if(sc->sc_if.if_flags & IFF_UP) { + lpintr(unit); + return; + } +#endif /* INET */ + + /* is printer online and ready for output */ + if (((sts=inb(port+lpt_status)) & RDY_MASK) == LP_READY) { + sc->sc_state = (sc->sc_state | OBUSY) & ~ERROR; + + if (sc->sc_xfercnt) { + /* send char */ + /*lprintf("%x ", *sc->sc_cp); */ + outb(port+lpt_data, *sc->sc_cp++) ; + outb(port+lpt_control, sc->sc_control|LPC_STB); + /* DELAY(X) */ + outb(port+lpt_control, sc->sc_control); + + /* any more data for printer */ + if(--(sc->sc_xfercnt) > 0) return; + } + + /* + * No more data waiting for printer. + * Wakeup is not done if write call was interrupted. + */ + sc->sc_state &= ~OBUSY; + if(!(sc->sc_state & INTERRUPTED)) + wakeup((caddr_t)sc); + lprintf("w "); + return; + } else { /* check for error */ + if(((sts & (LPS_NERR | LPS_OUT) ) != LPS_NERR) && + (sc->sc_state & OPEN)) + sc->sc_state |= ERROR; + } + lprintf("sts %x ", sts); +} + +int +lptioctl(dev_t dev, int cmd, caddr_t data, int flag) +{ + int error = 0; + struct lpt_softc *sc; + u_int unit = LPTUNIT(minor(dev)); + u_char old_sc_irq; /* old printer IRQ status */ + + sc = lpt_sc + unit; + + switch (cmd) { + case LPT_IRQ : + if(sc->sc_irq & LP_HAS_IRQ) { + /* + * NOTE: + * If the IRQ status is changed, + * this will only be visible on the + * next open. + * + * If interrupt status changes, + * this gets syslog'd. + */ + old_sc_irq = sc->sc_irq; + if(*(int*)data == 0) + sc->sc_irq &= (~LP_ENABLE_IRQ); + else + sc->sc_irq |= LP_ENABLE_IRQ; + if (old_sc_irq != sc->sc_irq ) + log(LOG_NOTICE, "lpt%c switched to %s mode\n", + (char)unit+'0', + (sc->sc_irq & LP_ENABLE_IRQ)? + "interrupt-driven":"polled"); + } else /* polled port */ + error = EOPNOTSUPP; + break; + default: + error = ENODEV; + } + + return(error); +} + +#ifdef INET + +static inline void +ob1(u_int port, u_char data) +{ + __asm __volatile(" + decl %%edx + outb %%al,%%dx + incl %%edx + " : : "a" (data), "d" (port)); +} + +static void +lpattach(struct lpt_softc *sc,int unit) +{ + struct ifnet *ifp = &sc->sc_if; + + ifp->if_name = "lp"; + ifp->if_unit = unit; + ifp->if_mtu = LPMTU; + ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST; + ifp->if_ioctl = lpioctl; + ifp->if_output = lpoutput; + ifp->if_type = IFT_SLIP; + ifp->if_hdrlen = 0; + ifp->if_addrlen = 0; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + if_attach(ifp); + printf("lp%d: TCP/IP interface, MTU=%d.\n",unit,LPMTU); +} +/* + * We don't want to calculate these nasties in our tight loop, so we + * precalculate them when we initialize. + */ +static void +lpinittables() +{ + int i; + + for(i=0;i<256;i++) { + txmith[i] = ((i & 0x80) >> 3) | ((i & 0x70) >> 4) | 0x08; + txmitl[i] = ((i & 0x08) << 1) | (i & 0x07); + trecvh[i] = ((~i) & 0x80) | ((i & 0x38) << 1); + trecvl[i] = (((~i) & 0x80) >> 4) | ((i & 0x38) >> 3); + } +} + +/* + * Process an ioctl request. + */ +/* ARGSUSED */ +static int +lpioctl(struct ifnet *ifp, int cmd, caddr_t data) +{ + struct proc *p = curproc; + struct lpt_softc *sc = lpt_sc + ifp->if_unit; + struct ifaddr *ifa = (struct ifaddr *)data; + struct ifreq *ifr = (struct ifreq *)data; + + int error = 0; + + switch (cmd) { + + case SIOCSIFFLAGS: + if (((ifp->if_flags & IFF_UP) == 0) && + (ifp->if_flags & IFF_RUNNING)) { + outb(sc->sc_port+2, 0x00); + ifp->if_flags &= ~IFF_RUNNING; + } + if (((ifp->if_flags & IFF_UP)) && + ((ifp->if_flags & IFF_RUNNING) == 0)) { + lpinittables(); + outb(sc->sc_port+2, 0x10); + ifp->if_flags |= IFF_RUNNING; + } + break; + + case SIOCAIFADDR: + case SIOCSIFADDR: + if (ifa->ifa_addr->sa_family != AF_INET) + error = EAFNOSUPPORT; + lpinittables(); + outb(sc->sc_port+2, 0x10); + ifp->if_flags |= IFF_RUNNING | IFF_UP; + break; + case SIOCSIFDSTADDR: + if (ifa->ifa_addr->sa_family != AF_INET) + error = EAFNOSUPPORT; + ifp->if_flags = IFF_POINTOPOINT; + break; + + case SIOCSIFMTU: + if ((error = suser(p->p_ucred, &p->p_acflag))) + return (error); + if(ifr->ifr_metric > LPMTU) + error = EINVAL; + sc->sc_if.if_mtu = ifr->ifr_metric; + break; + + case SIOCGIFMTU: + ifr->ifr_metric = sc->sc_if.if_mtu; + break; + + default: + lprintf("LP:ioctl%x\n",cmd); + error = EINVAL; + } + return (error); +} + +static void +lpintr(int unit) +{ + struct lpt_softc *sc = lpt_sc + unit; + int len,s,j; + register const int port = sc->sc_port+1; + + s = splimp(); + while((inb(port)&0x40)) { + { + u_long c, cl; + for(len=0;len<LPMTU+LPHDR;) { + __asm __volatile(" + inb %%dx,%%al + movzbl %%al,%0 + decl %%edx + movb $8,%%al + outb %%al,%%dx + incl %%edx + " : "=b" (cl) : "d" (port) : "a"); + { + j = LPMAXSPIN2; + while((inb(port)&0x40)) + if(!--j) goto err; + } + __asm __volatile(" + inb %%dx,%%al + movzbl %%al,%0 + decl %%edx + xorl %%eax,%%eax + outb %%al,%%dx + incl %%edx + " : "=b" (c) : "d" (port) : "a"); + sc->sc_ifbuf[len++] = trecvh[cl] | trecvl[c]; + { + j = LPMAXSPIN2; + while(!((cl=inb(port)) & 0x40)) { + if(cl != c && (((cl=inb(port))^0xb8)&0xf8) == (c&0xf8)) + goto end; + if(!--j) goto err; + } + } + } + } + end: + if(len <= LPHDR) + goto err; + + sc->sc_iferrs=0; + + if(IF_QFULL(&ipintrq)) { + lprintf("DROP"); + IF_DROP(&ipintrq); + goto done; + } + len -= LPHDR; + sc->sc_if.if_ipackets++; + sc->sc_if.if_ibytes += len; + { + u_char *p = sc->sc_ifbuf+LPHDR; + struct mbuf *top, *m, *m2; + MGETHDR(m, M_DONTWAIT, MT_DATA); + top = m; + m->m_len=0; + m->m_pkthdr.len = len; + m->m_pkthdr.rcvif = &sc->sc_if; + while(len > 0) { + m2 = m; + MGET(m, M_DONTWAIT, MT_DATA); + if(len > MINCLSIZE) { + MCLGET(m, M_DONTWAIT); + } + m->m_len=0; + m2->m_next = m; + j = min(len,M_TRAILINGSPACE(m)); + bcopy(p, mtod(m, caddr_t), j); + m->m_len=j; + len -= j; + p += j; + } + IF_ENQUEUE(&ipintrq, top); + schednetisr(NETISR_IP); + } + } + goto done; + err: + outb(sc->sc_port,0x00); + lprintf("R"); + sc->sc_if.if_ierrors++; + sc->sc_iferrs++; + if(sc->sc_iferrs > LPMAXERRS) { + /* We are not able to send receive anything for now, + * so stop wasting our time + */ + printf("lp%d: Too many errors, Going off-line.\n", unit); + outb(sc->sc_port+2, 0x00); + sc->sc_if.if_flags &= ~IFF_RUNNING; + sc->sc_iferrs=0; + } + done: + splx(s); + return; +} + + +static int +lpoutput(struct ifnet *ifp, struct mbuf *m, + struct sockaddr *dst, struct rtentry *rt) +{ + register int port = lpt_sc[ifp->if_unit].sc_port+1; + int s,i; + u_char *cp; + struct mbuf *mm=m; + + s=splimp(); + + /* Suspend (on laptops) or receive-errors might have taken us offline */ + outb(port+1, 0x10); + ifp->if_flags |= IFF_RUNNING; + + if(inb(port)&0x40) { + lprintf("&"); + lptintr(ifp->if_unit); + } + i = LPMAXSPIN1; + ob1(port,txmith[0x08]); + while(!(inb(port)&0x40)) if(!--i) goto end; + ob1(port,txmitl[0x08]); + while((inb(port)&0x40)) if(!--i) goto end; + + i = LPMAXSPIN2; + ob1(port,txmith[0x00]); + while(!(inb(port)&0x40)) if(!--i) goto end; + ob1(port,txmitl[0x00]); + while((inb(port)&0x40)) if(!--i) goto end; + + do { + cp = mtod(mm,u_char *); + for(;mm->m_len--;i=LPMAXSPIN2) { + ob1(port,txmith[*cp]); + while(!(inb(port)&0x40)) if(!--i) goto end; + ob1(port,txmitl[*cp++]); + while((inb(port)&0x40)) if(!--i) goto end; + } + } while ((mm = mm->m_next)); +end: + ob1(port,txmitl[cp[-1]] ^ 0x17); + if(i) { + ifp->if_opackets++; + ifp->if_obytes += m->m_pkthdr.len; + } else { + ifp->if_oerrors++; + lprintf("X"); + } + m_freem(m); + if((inb(port)&0x40)) { + lprintf("^"); + lptintr(ifp->if_unit); + } + (void)splx(s); + return 0; +} + +#endif /* INET */ + +#endif /* NLPT */ diff --git a/sys/i386/isa/lptreg.h b/sys/i386/isa/lptreg.h new file mode 100644 index 0000000..9e10ba9 --- /dev/null +++ b/sys/i386/isa/lptreg.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * form: @(#)lptreg.h 1.1 (Berkeley) 12/19/90 + * $Id$ + */ + +/* + * AT Parallel Port (for lineprinter) + * Interface port and bit definitions + * Written by William Jolitz 12/18/90 + * Copyright (C) William Jolitz 1990 + */ + +#define lpt_data 0 /* Data to/from printer (R/W) */ + +#define lpt_status 1 /* Status of printer (R) */ +#define LPS_NERR 0x08 /* printer no error */ +#define LPS_SEL 0x10 /* printer selected */ +#define LPS_OUT 0x20 /* printer out of paper */ +#define LPS_NACK 0x40 /* printer no ack of data */ +#define LPS_NBSY 0x80 /* printer no ack of data */ + +#define lpt_control 2 /* Control printer (R/W) */ +#define LPC_STB 0x01 /* strobe data to printer */ +#define LPC_AUTOL 0x02 /* automatic linefeed */ +#define LPC_NINIT 0x04 /* initialize printer */ +#define LPC_SEL 0x08 /* printer selected */ +#define LPC_ENA 0x10 /* printer out of paper */ diff --git a/sys/i386/isa/mcd.c b/sys/i386/isa/mcd.c new file mode 100644 index 0000000..1acd804 --- /dev/null +++ b/sys/i386/isa/mcd.c @@ -0,0 +1,1354 @@ +/* + * Copyright 1993 by Holger Veit (data part) + * Copyright 1993 by Brian Moore (audio part) + * Changes Copyright 1993 by Gary Clark II + * + * Rewrote probe routine to work on newer Mitsumi drives. + * Additional changes (C) 1994 by Jordan K. Hubbard + * + * 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 software was developed by Holger Veit and Brian Moore + * for use with "386BSD" and similar operating systems. + * "Similar operating systems" includes mainly non-profit oriented + * systems for research and education, including but not restricted to + * "NetBSD", "FreeBSD", "Mach" (by CMU). + * 4. Neither the name of the developer(s) nor the name "386BSD" + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER(S) ``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 DEVELOPER(S) 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. + * + * $Id: mcd.c,v 1.19 1994/08/27 13:15:25 ache Exp $ + */ +static char COPYRIGHT[] = "mcd-driver (C)1993 by H.Veit & B.Moore"; + +#include "mcd.h" +#if NMCD > 0 +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/buf.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/ioctl.h> +#include <sys/cdio.h> +#include <sys/errno.h> +#include <sys/dkbad.h> +#include <sys/disklabel.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/mcdreg.h> + +#define MIN_DELAY 10 + +/* user definable options */ +/*#define MCD_TO_WARNING_ON*/ /* define to get timeout messages */ +/*#define MCDMINI*/ /* define for a mini configuration for boot kernel */ +/*#define DEBUG*/ + +#ifdef MCDMINI +#define MCD_TRACE(fmt,a,b,c,d) +#ifdef MCD_TO_WARNING_ON +#undef MCD_TO_WARNING_ON +#endif +#else +#define MCD_TRACE(fmt,a,b,c,d) {if (mcd_data[unit].debug) {printf("mcd%d st=%02x: ",unit,mcd_data[unit].status); printf(fmt,a,b,c,d);}} +#endif + +#define mcd_part(dev) ((minor(dev)) & 7) +#define mcd_unit(dev) (((minor(dev)) & 0x38) >> 3) +#define mcd_phys(dev) (((minor(dev)) & 0x40) >> 6) +#define RAW_PART 3 + +/* flags */ +#define MCDOPEN 0x0001 /* device opened */ +#define MCDVALID 0x0002 /* parameters loaded */ +#define MCDINIT 0x0004 /* device is init'd */ +#define MCDWAIT 0x0008 /* waiting for something */ +#define MCDLABEL 0x0010 /* label is read */ +#define MCDPROBING 0x0020 /* probing */ +#define MCDREADRAW 0x0040 /* read raw mode (2352 bytes) */ +#define MCDVOLINFO 0x0080 /* already read volinfo */ +#define MCDTOC 0x0100 /* already read toc */ +#define MCDMBXBSY 0x0200 /* local mbx is busy */ + +/* status */ +#define MCDAUDIOBSY MCD_ST_AUDIOBSY /* playing audio */ +#define MCDDSKCHNG MCD_ST_DSKCHNG /* sensed change of disk */ +#define MCDDSKIN MCD_ST_DSKIN /* sensed disk in drive */ +#define MCDDOOROPEN MCD_ST_DOOROPEN /* sensed door open */ + +/* These are apparently the different states a mitsumi can get up to */ +#define MCDCDABSENT 0x0030 +#define MCDCDPRESENT 0x0020 +#define MCDSCLOSED 0x0080 +#define MCDSOPEN 0x00a0 + +/* toc */ +#define MCD_MAXTOCS 104 /* from the Linux driver */ +#define MCD_LASTPLUS1 170 /* special toc entry */ + +struct mcd_mbx { + short unit; + short port; + short retry; + short nblk; + int sz; + u_long skip; + struct buf *bp; + int p_offset; + short count; +}; + +struct mcd_data { + short config; + short flags; + short status; + int blksize; + u_long disksize; + int iobase; + struct disklabel dlabel; + int partflags[MAXPARTITIONS]; + int openflags; + struct mcd_volinfo volinfo; +#ifndef MCDMINI + struct mcd_qchninfo toc[MCD_MAXTOCS]; + short audio_status; + struct mcd_read2 lastpb; +#endif + short debug; + struct buf head; /* head of buf queue */ + struct mcd_mbx mbx; +} mcd_data[NMCD]; + +/* reader state machine */ +#define MCD_S_BEGIN 0 +#define MCD_S_BEGIN1 1 +#define MCD_S_WAITSTAT 2 +#define MCD_S_WAITMODE 3 +#define MCD_S_WAITREAD 4 + +/* prototypes */ +int mcdopen(dev_t dev); +int mcdclose(dev_t dev); +void mcdstrategy(struct buf *bp); +int mcdioctl(dev_t dev, int cmd, caddr_t addr, int flags); +int mcdsize(dev_t dev); +static void mcd_done(struct mcd_mbx *mbx); +static void mcd_start(int unit); +static int mcd_getdisklabel(int unit); +static void mcd_configure(struct mcd_data *cd); +static int mcd_get(int unit, char *buf, int nmax); +static void mcd_setflags(int unit,struct mcd_data *cd); +static int mcd_getstat(int unit,int sflg); +static int mcd_send(int unit, int cmd,int nretrys); +static int bcd2bin(bcd_t b); +static bcd_t bin2bcd(int b); +static void hsg2msf(int hsg, bcd_t *msf); +static int msf2hsg(bcd_t *msf); +static int mcd_volinfo(int unit); +static int mcd_waitrdy(int port,int dly); +static void mcd_doread(int state, struct mcd_mbx *mbxin); +#ifndef MCDMINI +static int mcd_setmode(int unit, int mode); +static int mcd_getqchan(int unit, struct mcd_qchninfo *q); +static int mcd_subchan(int unit, struct ioc_read_subchannel *sc); +static int mcd_toc_header(int unit, struct ioc_toc_header *th); +static int mcd_read_toc(int unit); +static int mcd_toc_entry(int unit, struct ioc_read_toc_entry *te); +static int mcd_stop(int unit); +static int mcd_playtracks(int unit, struct ioc_play_track *pt); +static int mcd_play(int unit, struct mcd_read2 *pb); +static int mcd_pause(int unit); +static int mcd_resume(int unit); +#endif + +extern int hz; +extern int mcd_probe(struct isa_device *dev); +extern int mcd_attach(struct isa_device *dev); +struct isa_driver mcddriver = { mcd_probe, mcd_attach, "mcd" }; + +#define mcd_put(port,byte) outb(port,byte) + +#define MCD_RETRYS 5 +#define MCD_RDRETRYS 8 + +#define MCDBLK 2048 /* for cooked mode */ +#define MCDRBLK 2352 /* for raw mode */ + +/* several delays */ +#define RDELAY_WAITSTAT 300 +#define RDELAY_WAITMODE 300 +#define RDELAY_WAITREAD 800 + +#define DELAY_STATUS 10000l /* 10000 * 1us */ +#define DELAY_GETREPLY 200000l /* 200000 * 2us */ +#define DELAY_SEEKREAD 20000l /* 20000 * 1us */ + +int mcd_attach(struct isa_device *dev) +{ + struct mcd_data *cd = mcd_data + dev->id_unit; + int i; + + cd->iobase = dev->id_iobase; + cd->flags |= MCDINIT; + cd->openflags = 0; + for (i=0; i<MAXPARTITIONS; i++) cd->partflags[i] = 0; + +#ifdef NOTYET + /* wire controller for interrupts and dma */ + mcd_configure(cd); +#endif + + return 1; +} + +int mcdopen(dev_t dev) +{ + int unit,part,phys; + struct mcd_data *cd; + + unit = mcd_unit(dev); + if (unit >= NMCD) + return ENXIO; + + cd = mcd_data + unit; + part = mcd_part(dev); + phys = mcd_phys(dev); + + /* not initialized*/ + if (!(cd->flags & MCDINIT)) + return ENXIO; + + /* invalidated in the meantime? mark all open part's invalid */ + if (!(cd->flags & MCDVALID) && cd->openflags) + return ENXIO; + + if (mcd_getstat(unit,1) < 0) + return ENXIO; + + /* XXX get a default disklabel */ + mcd_getdisklabel(unit); + + if (mcdsize(dev) < 0) { + printf("mcd%d: failed to get disk size\n",unit); + return ENXIO; + } else + cd->flags |= MCDVALID; + +MCD_TRACE("open: partition=%d, disksize = %d, blksize=%d\n", + part,cd->disksize,cd->blksize,0); + + if (part == RAW_PART || + (part < cd->dlabel.d_npartitions && + cd->dlabel.d_partitions[part].p_fstype != FS_UNUSED)) { + cd->partflags[part] |= MCDOPEN; + cd->openflags |= (1<<part); + if (part == RAW_PART && phys != 0) + cd->partflags[part] |= MCDREADRAW; + return 0; + } + + return ENXIO; +} + +int mcdclose(dev_t dev) +{ + int unit,part,phys; + struct mcd_data *cd; + + unit = mcd_unit(dev); + if (unit >= NMCD) + return ENXIO; + + cd = mcd_data + unit; + part = mcd_part(dev); + phys = mcd_phys(dev); + + if (!(cd->flags & MCDINIT)) + return ENXIO; + + mcd_getstat(unit,1); /* get status */ + + /* close channel */ + cd->partflags[part] &= ~(MCDOPEN|MCDREADRAW); + cd->openflags &= ~(1<<part); + MCD_TRACE("close: partition=%d\n",part,0,0,0); + + return 0; +} + +void +mcdstrategy(struct buf *bp) +{ + struct mcd_data *cd; + struct buf *qp; + int s; + + int unit = mcd_unit(bp->b_dev); + + cd = mcd_data + unit; + + /* test validity */ +/*MCD_TRACE("strategy: buf=0x%lx, unit=%ld, block#=%ld bcount=%ld\n", + bp,unit,bp->b_blkno,bp->b_bcount);*/ + if (unit >= NMCD || bp->b_blkno < 0) { + printf("mcdstrategy: unit = %d, blkno = %d, bcount = %d\n", + unit, bp->b_blkno, bp->b_bcount); + pg("mcd: mcdstratregy failure"); + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + goto bad; + } + + /* if device invalidated (e.g. media change, door open), error */ + if (!(cd->flags & MCDVALID)) { +MCD_TRACE("strategy: drive not valid\n",0,0,0,0); + bp->b_error = EIO; + goto bad; + } + + /* read only */ + if (!(bp->b_flags & B_READ)) { + bp->b_error = EROFS; + goto bad; + } + + /* no data to read */ + if (bp->b_bcount == 0) + goto done; + + /* for non raw access, check partition limits */ + if (mcd_part(bp->b_dev) != RAW_PART) { + if (!(cd->flags & MCDLABEL)) { + bp->b_error = EIO; + goto bad; + } + /* adjust transfer if necessary */ + if (bounds_check_with_label(bp,&cd->dlabel,1) <= 0) { + goto done; + } + } else { + bp->b_pblkno = bp->b_blkno; + bp->b_resid = 0; + } + + /* queue it */ + qp = &cd->head; + s = splbio(); + disksort(qp,bp); + splx(s); + + /* now check whether we can perform processing */ + mcd_start(unit); + return; + +bad: + bp->b_flags |= B_ERROR; +done: + bp->b_resid = bp->b_bcount; + biodone(bp); + return; +} + +static void mcd_start(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + struct buf *bp, *qp = &cd->head; + struct partition *p; + int part; + register s = splbio(); + + if (cd->flags & MCDMBXBSY) + return; + + if ((bp = qp->b_actf) != 0) { + /* block found to process, dequeue */ + /*MCD_TRACE("mcd_start: found block bp=0x%x\n",bp,0,0,0);*/ + qp->b_actf = bp->b_actf; + splx(s); + } else { + /* nothing to do */ + splx(s); + return; + } + + /* changed media? */ + if (!(cd->flags & MCDVALID)) { + MCD_TRACE("mcd_start: drive not valid\n",0,0,0,0); + return; + } + + p = cd->dlabel.d_partitions + mcd_part(bp->b_dev); + + cd->flags |= MCDMBXBSY; + cd->mbx.unit = unit; + cd->mbx.port = cd->iobase; + cd->mbx.retry = MCD_RETRYS; + cd->mbx.bp = bp; + cd->mbx.p_offset = p->p_offset; + + /* calling the read routine */ + mcd_doread(MCD_S_BEGIN,&(cd->mbx)); + /* triggers mcd_start, when successful finished */ + return; +} + +int mcdioctl(dev_t dev, int cmd, caddr_t addr, int flags) +{ + struct mcd_data *cd; + int unit,part; + + unit = mcd_unit(dev); + part = mcd_part(dev); + cd = mcd_data + unit; + +#ifdef MCDMINI + return ENOTTY; +#else + if (!(cd->flags & MCDVALID)) + return EIO; +MCD_TRACE("ioctl called 0x%x\n",cmd,0,0,0); + + switch (cmd) { + case DIOCSBAD: + return EINVAL; + case DIOCGDINFO: + case DIOCGPART: + case DIOCWDINFO: + case DIOCSDINFO: + case DIOCWLABEL: + return ENOTTY; + case CDIOCPLAYTRACKS: + return mcd_playtracks(unit, (struct ioc_play_track *) addr); + case CDIOCPLAYBLOCKS: + return mcd_play(unit, (struct mcd_read2 *) addr); + case CDIOCREADSUBCHANNEL: + return mcd_subchan(unit, (struct ioc_read_subchannel *) addr); + case CDIOREADTOCHEADER: + return mcd_toc_header(unit, (struct ioc_toc_header *) addr); + case CDIOREADTOCENTRYS: + return mcd_toc_entry(unit, (struct ioc_read_toc_entry *) addr); + case CDIOCSETPATCH: + case CDIOCGETVOL: + case CDIOCSETVOL: + case CDIOCSETMONO: + case CDIOCSETSTERIO: + case CDIOCSETMUTE: + case CDIOCSETLEFT: + case CDIOCSETRIGHT: + return EINVAL; + case CDIOCRESUME: + return mcd_resume(unit); + case CDIOCPAUSE: + return mcd_pause(unit); + case CDIOCSTART: + return EINVAL; + case CDIOCSTOP: + return mcd_stop(unit); + case CDIOCEJECT: + return EINVAL; + case CDIOCSETDEBUG: + cd->debug = 1; + return 0; + case CDIOCCLRDEBUG: + cd->debug = 0; + return 0; + case CDIOCRESET: + return EINVAL; + default: + return ENOTTY; + } + /*NOTREACHED*/ +#endif /*!MCDMINI*/ +} + +/* this could have been taken from scsi/cd.c, but it is not clear + * whether the scsi cd driver is linked in + */ +static int mcd_getdisklabel(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + + if (cd->flags & MCDLABEL) + return -1; + + bzero(&cd->dlabel,sizeof(struct disklabel)); + strncpy(cd->dlabel.d_typename,"Mitsumi CD ROM ",16); + strncpy(cd->dlabel.d_packname,"unknown ",16); + cd->dlabel.d_secsize = cd->blksize; + cd->dlabel.d_nsectors = 100; + cd->dlabel.d_ntracks = 1; + cd->dlabel.d_ncylinders = (cd->disksize/100)+1; + cd->dlabel.d_secpercyl = 100; + cd->dlabel.d_secperunit = cd->disksize; + cd->dlabel.d_rpm = 300; + cd->dlabel.d_interleave = 1; + cd->dlabel.d_flags = D_REMOVABLE; + cd->dlabel.d_npartitions= 1; + cd->dlabel.d_partitions[0].p_offset = 0; + cd->dlabel.d_partitions[0].p_size = cd->disksize; + cd->dlabel.d_partitions[0].p_fstype = 9; + cd->dlabel.d_magic = DISKMAGIC; + cd->dlabel.d_magic2 = DISKMAGIC; + cd->dlabel.d_checksum = dkcksum(&cd->dlabel); + + cd->flags |= MCDLABEL; + return 0; +} + +int mcdsize(dev_t dev) +{ + int size; + int unit = mcd_unit(dev); + struct mcd_data *cd = mcd_data + unit; + + if (mcd_volinfo(unit) >= 0) { + cd->blksize = MCDBLK; + size = msf2hsg(cd->volinfo.vol_msf); + cd->disksize = size * (MCDBLK/DEV_BSIZE); + return 0; + } + return -1; +} + +/*************************************************************** + * lower level of driver starts here + **************************************************************/ + +#ifdef NOTDEF +static char +irqs[] = { + 0x00,0x00,0x10,0x20,0x00,0x30,0x00,0x00, + 0x00,0x10,0x40,0x50,0x00,0x00,0x00,0x00 +}; + +static char +drqs[] = { + 0x00,0x01,0x00,0x03,0x00,0x05,0x06,0x07, +}; +#endif + +static void +mcd_configure(struct mcd_data *cd) +{ + outb(cd->iobase+mcd_config,cd->config); +} + +/* Wait for non-busy - return 0 on timeout */ +static int +twiddle_thumbs(int port, int unit, int count, char *whine) +{ + int i; + + for (i = 0; i < count; i++) { + if (!(inb(port+MCD_FLAGS) & MCD_ST_BUSY)) { + return 1; + } + } +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: timeout %s\n", unit, whine); +#endif + return 0; +} + +/* check to see if a Mitsumi CD-ROM is attached to the ISA bus */ + +int +mcd_probe(struct isa_device *dev) +{ + int port = dev->id_iobase; + int unit = dev->id_unit; + int i, j; + int status; + unsigned char stbytes[3]; + + mcd_data[unit].flags = MCDPROBING; + +#ifdef NOTDEF + /* get irq/drq configuration word */ + mcd_data[unit].config = irqs[dev->id_irq]; /* | drqs[dev->id_drq];*/ +#else + mcd_data[unit].config = 0; +#endif + + /* send a reset */ + outb(port+MCD_FLAGS, M_RESET); + + /* + * delay awhile by getting any pending garbage (old data) and + * throwing it away. + */ + for (i = 1000000; i != 0; i--) { + inb(port+MCD_FLAGS); + } + + /* Get status */ + outb(port+MCD_DATA, MCD_CMDGETSTAT); + if (!twiddle_thumbs(port, unit, 1000000, "getting status")) { + return 0; /* Timeout */ + } + status = inb(port+MCD_DATA); + if (status != MCDCDABSENT && status != MCDCDPRESENT && + status != MCDSOPEN && status != MCDSCLOSED) + return 0; /* Not actually a Mitsumi drive here */ + /* Get version information */ + outb(port+MCD_DATA, MCD_CMDCONTINFO); + for (j = 0; j < 3; j++) { + if (!twiddle_thumbs(port, unit, 3000, "getting version info")) { + return 0; + } + stbytes[j] = (inb(port+MCD_DATA) & 0xFF); + } + printf("mcd%d: version information is %x %c %x\n", unit, + stbytes[0], stbytes[1], stbytes[2]); + if (stbytes[1] >= 4) { + outb(port+MCD_CTRL, M_PICKLE); + printf("mcd%d: Adjusted for newer drive model\n", unit); + } + return 4; +} + + +static int +mcd_waitrdy(int port,int dly) +{ + int i; + + /* wait until xfer port senses data ready */ + for (i=0; i<dly; i+=MIN_DELAY) { + if ((inb(port+mcd_xfer) & MCD_ST_BUSY)==0) + return 0; + DELAY(MIN_DELAY); + } + return -1; +} + +static int +mcd_getreply(int unit,int dly) +{ + int i; + struct mcd_data *cd = mcd_data + unit; + int port = cd->iobase; + + /* wait data to become ready */ + if (mcd_waitrdy(port,dly)<0) { +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: timeout getreply\n",unit); +#endif + return -1; + } + + /* get the data */ + return inb(port+mcd_status) & 0xFF; +} + +static int +mcd_getstat(int unit,int sflg) +{ + int i; + struct mcd_data *cd = mcd_data + unit; + int port = cd->iobase; + + /* get the status */ + if (sflg) + outb(port+mcd_command, MCD_CMDGETSTAT); + i = mcd_getreply(unit,DELAY_GETREPLY); + if (i<0) return -1; + + cd->status = i; + + mcd_setflags(unit,cd); + return cd->status; +} + +static void +mcd_setflags(int unit, struct mcd_data *cd) +{ + /* check flags */ + if (cd->status & (MCDDSKCHNG|MCDDOOROPEN)) { + MCD_TRACE("getstat: sensed DSKCHNG or DOOROPEN\n",0,0,0,0); + cd->flags &= ~MCDVALID; + } + +#ifndef MCDMINI + if (cd->status & MCDAUDIOBSY) + cd->audio_status = CD_AS_PLAY_IN_PROGRESS; + else if (cd->audio_status == CD_AS_PLAY_IN_PROGRESS) + cd->audio_status = CD_AS_PLAY_COMPLETED; +#endif +} + +static int +mcd_get(int unit, char *buf, int nmax) +{ + int port = mcd_data[unit].iobase; + int i,k; + + for (i=0; i<nmax; i++) { + /* wait for data */ + if ((k = mcd_getreply(unit,DELAY_GETREPLY)) < 0) { +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: timeout mcd_get\n",unit); +#endif + return -1; + } + buf[i] = k; + } + return i; +} + +static int +mcd_send(int unit, int cmd,int nretrys) +{ + int i,k; + int port = mcd_data[unit].iobase; + +/*MCD_TRACE("mcd_send: command = 0x%x\n",cmd,0,0,0);*/ + for (i=0; i<nretrys; i++) { + outb(port+mcd_command, cmd); + if ((k=mcd_getstat(unit,0)) != -1) { + break; + } + } + if (i == nretrys) { + printf("mcd%d: mcd_send retry cnt exceeded\n",unit); + return -1; + } +/*MCD_TRACE("mcd_send: status = 0x%x\n",k,0,0,0);*/ + return 0; +} + +static int +bcd2bin(bcd_t b) +{ + return (b >> 4) * 10 + (b & 15); +} + +static bcd_t +bin2bcd(int b) +{ + return ((b / 10) << 4) | (b % 10); +} + +static void +hsg2msf(int hsg, bcd_t *msf) +{ + hsg += 150; + M_msf(msf) = bin2bcd(hsg / 4500); + hsg %= 4500; + S_msf(msf) = bin2bcd(hsg / 75); + F_msf(msf) = bin2bcd(hsg % 75); +} + +static int +msf2hsg(bcd_t *msf) +{ + return (bcd2bin(M_msf(msf)) * 60 + + bcd2bin(S_msf(msf))) * 75 + + bcd2bin(F_msf(msf)) - 150; +} + +static int +mcd_volinfo(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + int i; + +/*MCD_TRACE("mcd_volinfo: enter\n",0,0,0,0);*/ + + /* Get the status, in case the disc has been changed */ + if (mcd_getstat(unit, 1) < 0) return EIO; + + /* Just return if we already have it */ + if (cd->flags & MCDVOLINFO) return 0; + + /* send volume info command */ + if (mcd_send(unit,MCD_CMDGETVOLINFO,MCD_RETRYS) < 0) + return -1; + + /* get data */ + if (mcd_get(unit,(char*) &cd->volinfo,sizeof(struct mcd_volinfo)) < 0) { + printf("mcd%d: mcd_volinfo: error read data\n",unit); + return -1; + } + + if (cd->volinfo.trk_low != 0 || cd->volinfo.trk_high != 0) { + cd->flags |= MCDVOLINFO; /* volinfo is OK */ + return 0; + } + + return -1; +} + +void +mcdintr(unit) + int unit; +{ + int port = mcd_data[unit].iobase; + u_int i; + + MCD_TRACE("stray interrupt xfer=0x%x\n",inb(port+mcd_xfer),0,0,0); + + /* just read out status and ignore the rest */ + if ((inb(port+mcd_xfer)&0xFF) != 0xFF) { + i = inb(port+mcd_status); + } +} + +/* state machine to process read requests + * initialize with MCD_S_BEGIN: calculate sizes, and read status + * MCD_S_WAITSTAT: wait for status reply, set mode + * MCD_S_WAITMODE: waits for status reply from set mode, set read command + * MCD_S_WAITREAD: wait for read ready, read data + */ +static struct mcd_mbx *mbxsave; + +static void +mcd_doread(int state, struct mcd_mbx *mbxin) +{ + struct mcd_mbx *mbx = (state!=MCD_S_BEGIN) ? mbxsave : mbxin; + int unit = mbx->unit; + int port = mbx->port; + struct buf *bp = mbx->bp; + struct mcd_data *cd = mcd_data + unit; + + int rm,i,k; + struct mcd_read2 rbuf; + int blknum; + caddr_t addr; + +loop: + switch (state) { + case MCD_S_BEGIN: + mbx = mbxsave = mbxin; + + case MCD_S_BEGIN1: + /* get status */ + outb(port+mcd_command, MCD_CMDGETSTAT); + mbx->count = RDELAY_WAITSTAT; + timeout((timeout_func_t)mcd_doread, + (caddr_t)MCD_S_WAITSTAT,hz/100); /* XXX */ + return; + case MCD_S_WAITSTAT: + untimeout((timeout_func_t)mcd_doread,(caddr_t)MCD_S_WAITSTAT); + if (mbx->count-- >= 0) { + if (inb(port+mcd_xfer) & MCD_ST_BUSY) { + timeout((timeout_func_t)mcd_doread, + (caddr_t)MCD_S_WAITSTAT,hz/100); /* XXX */ + return; + } + mcd_setflags(unit,cd); + MCD_TRACE("got WAITSTAT delay=%d\n", + RDELAY_WAITSTAT-mbx->count,0,0,0); + /* reject, if audio active */ + if (cd->status & MCDAUDIOBSY) { + printf("mcd%d: audio is active\n",unit); + goto readerr; + } + + /* to check for raw/cooked mode */ + if (cd->flags & MCDREADRAW) { + rm = MCD_MD_RAW; + mbx->sz = MCDRBLK; + } else { + rm = MCD_MD_COOKED; + mbx->sz = cd->blksize; + } + + mbx->count = RDELAY_WAITMODE; + + mcd_put(port+mcd_command, MCD_CMDSETMODE); + mcd_put(port+mcd_command, rm); + timeout((timeout_func_t)mcd_doread, + (caddr_t)MCD_S_WAITMODE,hz/100); /* XXX */ + return; + } else { +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: timeout getstatus\n",unit); +#endif + goto readerr; + } + + case MCD_S_WAITMODE: + untimeout((timeout_func_t)mcd_doread,(caddr_t)MCD_S_WAITMODE); + if (mbx->count-- < 0) { +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: timeout set mode\n",unit); +#endif + goto readerr; + } + if (inb(port+mcd_xfer) & MCD_ST_BUSY) { + timeout((timeout_func_t)mcd_doread,(caddr_t)MCD_S_WAITMODE,hz/100); + return; + } + mcd_setflags(unit,cd); + MCD_TRACE("got WAITMODE delay=%d\n", + RDELAY_WAITMODE-mbx->count,0,0,0); + /* for first block */ + mbx->nblk = (bp->b_bcount + (mbx->sz-1)) / mbx->sz; + mbx->skip = 0; + +nextblock: + blknum = (bp->b_blkno / (mbx->sz/DEV_BSIZE)) + + mbx->p_offset + mbx->skip/mbx->sz; + + MCD_TRACE("mcd_doread: read blknum=%d for bp=0x%x\n", + blknum,bp,0,0); + + /* build parameter block */ + hsg2msf(blknum,rbuf.start_msf); + + /* send the read command */ + mcd_put(port+mcd_command,MCD_CMDREAD2); + mcd_put(port+mcd_command,rbuf.start_msf[0]); + mcd_put(port+mcd_command,rbuf.start_msf[1]); + mcd_put(port+mcd_command,rbuf.start_msf[2]); + mcd_put(port+mcd_command,0); + mcd_put(port+mcd_command,0); + mcd_put(port+mcd_command,1); + mbx->count = RDELAY_WAITREAD; + timeout((timeout_func_t)mcd_doread, + (caddr_t)MCD_S_WAITREAD,hz/100); /* XXX */ + return; + case MCD_S_WAITREAD: + untimeout((timeout_func_t)mcd_doread,(caddr_t)MCD_S_WAITREAD); + if (mbx->count-- > 0) { + k = inb(port+mcd_xfer); + if ((k & 2)==0) { + MCD_TRACE("got data delay=%d\n", + RDELAY_WAITREAD-mbx->count,0,0,0); + /* data is ready */ + addr = bp->b_un.b_addr + mbx->skip; + outb(port+mcd_ctl2,0x04); /* XXX */ + for (i=0; i<mbx->sz; i++) + *addr++ = inb(port+mcd_rdata); + outb(port+mcd_ctl2,0x0c); /* XXX */ + + if (--mbx->nblk > 0) { + mbx->skip += mbx->sz; + goto nextblock; + } + + /* return buffer */ + bp->b_resid = 0; + biodone(bp); + + cd->flags &= ~MCDMBXBSY; + mcd_start(mbx->unit); + return; + } + if ((k & 4)==0) + mcd_getstat(unit,0); + timeout((timeout_func_t)mcd_doread, + (caddr_t)MCD_S_WAITREAD,hz/100); /* XXX */ + return; + } else { +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: timeout read data\n",unit); +#endif + goto readerr; + } + } + +readerr: + if (mbx->retry-- > 0) { +#ifdef MCD_TO_WARNING_ON + printf("mcd%d: retrying\n",unit); +#endif + state = MCD_S_BEGIN1; + goto loop; + } + + /* invalidate the buffer */ + bp->b_flags |= B_ERROR; + bp->b_resid = bp->b_bcount; + biodone(bp); + mcd_start(mbx->unit); + return; + +#ifdef NOTDEF + printf("mcd%d: unit timeout, resetting\n",mbx->unit); + outb(mbx->port+mcd_reset,MCD_CMDRESET); + DELAY(300000); + (void)mcd_getstat(mbx->unit,1); + (void)mcd_getstat(mbx->unit,1); + /*cd->status &= ~MCDDSKCHNG; */ + cd->debug = 1; /* preventive set debug mode */ + +#endif + +} + +#ifndef MCDMINI +static int +mcd_setmode(int unit, int mode) +{ + struct mcd_data *cd = mcd_data + unit; + int port = cd->iobase; + int retry; + +#ifdef DEBUG + printf("mcd%d: setting mode to %d\n", unit, mode); +#endif + for(retry=0; retry<MCD_RETRYS; retry++) + { + outb(port+mcd_command, MCD_CMDSETMODE); + outb(port+mcd_command, mode); + if (mcd_getstat(unit, 0) != -1) return 0; + } + + return -1; +} + +static int +mcd_toc_header(int unit, struct ioc_toc_header *th) +{ + struct mcd_data *cd = mcd_data + unit; + + if (mcd_volinfo(unit) < 0) { + return ENXIO; + } + + th->len = msf2hsg(cd->volinfo.vol_msf); + th->starting_track = bcd2bin(cd->volinfo.trk_low); + th->ending_track = bcd2bin(cd->volinfo.trk_high); + + return 0; +} + +static int +mcd_read_toc(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + struct ioc_toc_header th; + struct mcd_qchninfo q; + int rc, trk, idx, retry; + + /* Only read TOC if needed */ + if (cd->flags & MCDTOC) { + return 0; + } + +#ifdef DEBUG + printf("mcd%d: reading toc header\n", unit); +#endif + if (mcd_toc_header(unit, &th) != 0) { + return ENXIO; + } + +#ifdef DEBUG + printf("mcd%d: stopping play\n", unit); +#endif + if ((rc=mcd_stop(unit)) != 0) { + return rc; + } + + /* try setting the mode twice */ + if (mcd_setmode(unit, MCD_MD_TOC) != 0) { + return EIO; + } + if (mcd_setmode(unit, MCD_MD_TOC) != 0) { + return EIO; + } + +#ifdef DEBUG + printf("mcd%d: get_toc reading qchannel info\n",unit); +#endif + for(trk=th.starting_track; trk<=th.ending_track; trk++) + cd->toc[trk].idx_no = 0; + trk = th.ending_track - th.starting_track + 1; + for(retry=0; retry<300 && trk>0; retry++) + { + if (mcd_getqchan(unit, &q) < 0) break; + idx = bcd2bin(q.idx_no); + if (idx>0 && idx < MCD_MAXTOCS && q.trk_no==0) { + if (cd->toc[idx].idx_no == 0) { + cd->toc[idx] = q; + trk--; + } + } + } + + if (mcd_setmode(unit, MCD_MD_COOKED) != 0) { + return EIO; + } + + if (trk != 0) { + return ENXIO; + } + + /* add a fake last+1 */ + idx = th.ending_track + 1; + cd->toc[idx].ctrl_adr = cd->toc[idx-1].ctrl_adr; + cd->toc[idx].trk_no = 0; + cd->toc[idx].idx_no = 0xAA; + cd->toc[idx].hd_pos_msf[0] = cd->volinfo.vol_msf[0]; + cd->toc[idx].hd_pos_msf[1] = cd->volinfo.vol_msf[1]; + cd->toc[idx].hd_pos_msf[2] = cd->volinfo.vol_msf[2]; + + cd->flags |= MCDTOC; + + return 0; +} + +static int +mcd_toc_entry(int unit, struct ioc_read_toc_entry *te) +{ + struct mcd_data *cd = mcd_data + unit; + struct ret_toc { + struct ioc_toc_header th; + struct cd_toc_entry rt; + } ret_toc; + struct ioc_toc_header th; + int rc, i; + + /* Make sure we have a valid toc */ + if ((rc=mcd_read_toc(unit)) != 0) { + return rc; + } + + /* find the toc to copy*/ + i = te->starting_track; + if (i == MCD_LASTPLUS1) { + i = bcd2bin(cd->volinfo.trk_high) + 1; + } + + /* verify starting track */ + if (i < bcd2bin(cd->volinfo.trk_low) || + i > bcd2bin(cd->volinfo.trk_high)+1) { + return EINVAL; + } + + /* do we have room */ + if (te->data_len < sizeof(struct ioc_toc_header) + + sizeof(struct cd_toc_entry)) { + return EINVAL; + } + + /* Copy the toc header */ + if (mcd_toc_header(unit, &th) < 0) { + return EIO; + } + ret_toc.th = th; + + /* copy the toc data */ + ret_toc.rt.control = cd->toc[i].ctrl_adr; + ret_toc.rt.addr_type = te->address_format; + ret_toc.rt.track = i; + if (te->address_format == CD_MSF_FORMAT) { + ret_toc.rt.addr.addr[1] = cd->toc[i].hd_pos_msf[0]; + ret_toc.rt.addr.addr[2] = cd->toc[i].hd_pos_msf[1]; + ret_toc.rt.addr.addr[3] = cd->toc[i].hd_pos_msf[2]; + } + + /* copy the data back */ + copyout(&ret_toc, te->data, sizeof(struct cd_toc_entry) + + sizeof(struct ioc_toc_header)); + + return 0; +} + +static int +mcd_stop(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + + if (mcd_send(unit, MCD_CMDSTOPAUDIO, MCD_RETRYS) < 0) { + return ENXIO; + } + cd->audio_status = CD_AS_PLAY_COMPLETED; + return 0; +} + +static int +mcd_getqchan(int unit, struct mcd_qchninfo *q) +{ + struct mcd_data *cd = mcd_data + unit; + + if (mcd_send(unit, MCD_CMDGETQCHN, MCD_RETRYS) < 0) { + return -1; + } + if (mcd_get(unit, (char *) q, sizeof(struct mcd_qchninfo)) < 0) { + return -1; + } +#ifdef DEBUG + if (cd->debug) { + printf("mcd%d: qchannel ctl=%d, t=%d, i=%d, ttm=%d:%d.%d dtm=%d:%d.%d\n", + unit, + q->ctrl_adr, q->trk_no, q->idx_no, + q->trk_size_msf[0], q->trk_size_msf[1], q->trk_size_msf[2], + q->trk_size_msf[0], q->trk_size_msf[1], q->trk_size_msf[2]); + } +#endif + return 0; +} + +static int +mcd_subchan(int unit, struct ioc_read_subchannel *sc) +{ + struct mcd_data *cd = mcd_data + unit; + struct mcd_qchninfo q; + struct cd_sub_channel_info data; + +#ifdef DEBUG + printf("mcd%d: subchan af=%d, df=%d\n", unit, + sc->address_format, + sc->data_format); +#endif + if (sc->address_format != CD_MSF_FORMAT) { + return EIO; + } + if (sc->data_format != CD_CURRENT_POSITION) { + return EIO; + } + if (mcd_getqchan(unit, &q) < 0) { + return EIO; + } + + data.header.audio_status = cd->audio_status; + data.what.position.data_format = CD_MSF_FORMAT; + data.what.position.track_number = bcd2bin(q.trk_no); + + if (copyout(&data, sc->data, sizeof(struct cd_sub_channel_info))!=0) { + return EFAULT; + } + return 0; +} + +static int +mcd_playtracks(int unit, struct ioc_play_track *pt) +{ + struct mcd_data *cd = mcd_data + unit; + struct mcd_read2 pb; + int a = pt->start_track; + int z = pt->end_track; + int rc; + + if ((rc = mcd_read_toc(unit)) != 0) { + return rc; + } +#ifdef DEBUG + printf("mcd%d: playtracks from %d:%d to %d:%d\n", unit, + a, pt->start_index, z, pt->end_index); +#endif + + if (a < cd->volinfo.trk_low || a > cd->volinfo.trk_high || a > z || + z < cd->volinfo.trk_low || z > cd->volinfo.trk_high) { + return EINVAL; + } + + pb.start_msf[0] = cd->toc[a].hd_pos_msf[0]; + pb.start_msf[1] = cd->toc[a].hd_pos_msf[1]; + pb.start_msf[2] = cd->toc[a].hd_pos_msf[2]; + pb.end_msf[0] = cd->toc[z+1].hd_pos_msf[0]; + pb.end_msf[1] = cd->toc[z+1].hd_pos_msf[1]; + pb.end_msf[2] = cd->toc[z+1].hd_pos_msf[2]; + + return mcd_play(unit, &pb); +} + +static int +mcd_play(int unit, struct mcd_read2 *pb) +{ + struct mcd_data *cd = mcd_data + unit; + int port = cd->iobase; + int retry, st; + + cd->lastpb = *pb; + for(retry=0; retry<MCD_RETRYS; retry++) { + outb(port+mcd_command, MCD_CMDREAD2); + outb(port+mcd_command, pb->start_msf[0]); + outb(port+mcd_command, pb->start_msf[1]); + outb(port+mcd_command, pb->start_msf[2]); + outb(port+mcd_command, pb->end_msf[0]); + outb(port+mcd_command, pb->end_msf[1]); + outb(port+mcd_command, pb->end_msf[2]); + if ((st=mcd_getstat(unit, 0)) != -1) { + break; + } + } + +#ifdef DEBUG + if (cd->debug) { + printf("mcd%d: mcd_play retry=%d, status=%d\n", unit, retry, st); + } +#endif + if (st == -1) { + return ENXIO; + } + cd->audio_status = CD_AS_PLAY_IN_PROGRESS; + return 0; +} + +static int +mcd_pause(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + struct mcd_qchninfo q; + int rc; + + /* Verify current status */ + if (cd->audio_status != CD_AS_PLAY_IN_PROGRESS) { +#ifdef DEBUG + printf("mcd%d: pause attempted when not playing\n", unit); +#endif + return EINVAL; + } + + /* Get the current position */ + if (mcd_getqchan(unit, &q) < 0) { + return EIO; + } + + /* Copy it into lastpb */ + cd->lastpb.start_msf[0] = q.hd_pos_msf[0]; + cd->lastpb.start_msf[1] = q.hd_pos_msf[1]; + cd->lastpb.start_msf[2] = q.hd_pos_msf[2]; + + /* Stop playing */ + if ((rc=mcd_stop(unit)) != 0) { + return rc; + } + + /* Set the proper status and exit */ + cd->audio_status = CD_AS_PLAY_PAUSED; + return 0; +} + +static int +mcd_resume(int unit) +{ + struct mcd_data *cd = mcd_data + unit; + + if (cd->audio_status != CD_AS_PLAY_PAUSED) { + return EINVAL; + } + return mcd_play(unit, &cd->lastpb); +} +#endif /*!MCDMINI*/ + +#endif /* NMCD > 0 */ diff --git a/sys/i386/isa/mcdreg.h b/sys/i386/isa/mcdreg.h new file mode 100644 index 0000000..0ce5de7 --- /dev/null +++ b/sys/i386/isa/mcdreg.h @@ -0,0 +1,159 @@ +/* + * Copyright 1993 by Holger Veit (data part) + * Copyright 1993 by Brian Moore (audio part) + * Changes Copyright 1993 by Gary Clark II + * 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 software was developed by Holger Veit and Brian Moore + * for use with "386BSD" and similar operating systems. + * "Similar operating systems" includes mainly non-profit oriented + * systems for research and education, including but not restricted to + * "NetBSD", "FreeBSD", "Mach" (by CMU). + * 4. Neither the name of the developer(s) nor the name "386BSD" + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER(S) ``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 DEVELOPER(S) 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. + * + * This file contains definitions for some cdrom control commands + * and status codes. This info was "inherited" from the DOS MTMCDE.SYS + * driver, and is thus not complete (and may even be wrong). Some day + * the manufacturer or anyone else might provide better documentation, + * so this file (and the driver) will then have a better quality. + * + * $Id: mcdreg.h,v 1.2 1994/01/16 23:34:17 jkh Exp $ + */ + +#ifndef MCD_H +#define MCD_H + +#ifdef __GNUC__ +#if __GNUC__ >= 2 +#pragma pack(1) +#endif +#endif + +typedef unsigned char bcd_t; +#define M_msf(msf) msf[0] +#define S_msf(msf) msf[1] +#define F_msf(msf) msf[2] + +/* io lines used */ +#define MCD_IO_BASE 0x300 + +#define mcd_command 0 +#define mcd_status 0 +#define mcd_rdata 0 + +#define mcd_reset 1 +#define mcd_xfer 1 +#define mcd_ctl2 2 /* XXX Is this right? */ +#define mcd_config 3 + +#define MCD_MASK_DMA 0x07 /* bits 2-0 = DMA channel */ +#define MCD_MASK_IRQ 0x70 /* bits 6-4 = INT number */ + /* 001 = int 2,9 */ + /* 010 = int 3 */ + /* 011 = int 5 */ + /* 100 = int 10 */ + /* 101 = int 11 */ +/* flags */ +#define STATUS_AVAIL 0xB +#define DATA_AVAIL 0xF + +/* New Flags */ +#define M_STATUS_AVAIL 0xFB +#define M_DATA_AVAIL 0xFD + +/* New Commands */ +#define M_RESET 0x00 +#define M_PICKLE 0x04 + +/* ports */ +#define MCD_DATA 0 +#define MCD_FLAGS 1 +#define MCD_CTRL 2 +#define CHANNEL 3 /* XXX ??? */ + +/* Status bits */ +#define MCD_ST_DOOROPEN 0x80 +#define MCD_ST_DSKIN 0x40 +#define MCD_ST_DSKCHNG 0x20 +#define MCD_ST_BUSY 0x04 +#define MCD_ST_AUDIOBSY 0x02 + +/* commands known by the controller */ +#define MCD_CMDRESET 0x00 +#define MCD_CMDGETVOLINFO 0x10 /* gets mcd_volinfo */ +#define MCD_CMDGETQCHN 0x20 /* gets mcd_qchninfo */ +#define MCD_CMDGETSTAT 0x40 /* gets a byte of status */ +#define MCD_CMDSETMODE 0x50 /* set transmission mode, needs byte */ +#define MCD_MD_RAW 0x60 +#define MCD_MD_COOKED 0x01 +#define MCD_MD_TOC 0x05 +#define MCD_CMDSTOPAUDIO 0x70 +#define MCD_CMDGETVOLUME 0x8E /* gets mcd_volume */ +#define MCD_CMDSETVOLUME 0xAE /* sets mcd_volume */ +#define MCD_CMDREAD1 0xB0 /* read n sectors */ +#define MCD_CMDREAD2 0xC0 /* read from-to */ +#define MCD_CMDCONTINFO 0xDC /* Get controller info */ +#define MCD_CMDEJECTDISK 0xF6 +#define MCD_CMDCLOSETRAY 0xF8 +#define MCD_CMDLOCKDRV 0xFE /* needs byte */ +#define MCD_LK_UNLOCK 0x00 +#define MCD_LK_LOCK 0x01 +#define MCD_LK_TEST 0x02 + +struct mcd_volinfo { + bcd_t trk_low; + bcd_t trk_high; + bcd_t vol_msf[3]; + bcd_t trk1_msf[3]; +}; + +struct mcd_qchninfo { + u_char ctrl_adr; + u_char trk_no; + u_char idx_no; + bcd_t trk_size_msf[3]; + u_char :8; + bcd_t hd_pos_msf[3]; +}; + +struct mcd_volume { + u_char v0l; + u_char v0rs; + u_char v0r; + u_char v0ls; +}; + +struct mcd_read1 { + bcd_t start_msf[3]; + u_char nsec[3]; +}; + +struct mcd_read2 { + bcd_t start_msf[3]; + bcd_t end_msf[3]; +}; +#endif /* MCD_H */ diff --git a/sys/i386/isa/mse.c b/sys/i386/isa/mse.c new file mode 100644 index 0000000..5ce5c03 --- /dev/null +++ b/sys/i386/isa/mse.c @@ -0,0 +1,501 @@ +/* + * Copyright 1992 by the University of Guelph + * + * Permission to use, copy and modify this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation. + * University of Guelph makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + * + * $Id: mse.c,v 1.5 1994/08/02 07:39:39 davidg Exp $ + */ +/* + * Driver for the Logitech and ATI Inport Bus mice for use with 386bsd and + * the X386 port, courtesy of + * Rick Macklem, rick@snowhite.cis.uoguelph.ca + * Caveats: The driver currently uses spltty(), but doesn't use any + * generic tty code. It could use splmse() (that only masks off the + * bus mouse interrupt, but that would require hacking in i386/isa/icu.s. + * (This may be worth the effort, since the Logitech generates 30/60 + * interrupts/sec continuously while it is open.) + * NB: The ATI has NOT been tested yet! + */ + +/* + * Modification history: + * + * Oct 19, 1992 -- E. Stark (stark@cs.sunysb.edu) + * fixes to make it work with Microsoft InPort busmouse + * + * Jan, 1993 -- E. Stark (stark@cs.sunysb.edu) + * added patches for new "select" interface + * + * May 4, 1993 -- E. Stark (stark@cs.sunysb.edu) + * changed position of some spl()'s in mseread + * + * October 8, 1993 -- E. Stark (stark@cs.sunysb.edu) + * limit maximum negative x/y value to -127 to work around XFree problem + * that causes spurious button pushes. + */ + +#include "mse.h" +#if NMSE > 0 +#include <sys/param.h> +#include <sys/proc.h> +#include <sys/user.h> +#include <sys/buf.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/ioctl.h> +#include <sys/tty.h> +#include <sys/uio.h> + +#include <i386/isa/isa_device.h> +#include <i386/isa/icu.h> + +static int mseprobe(struct isa_device *); +static int mseattach(struct isa_device *); +void mseintr(int); + +struct isa_driver msedriver = { + mseprobe, mseattach, "mse" +}; + +/* + * Software control structure for mouse. The sc_enablemouse(), + * sc_disablemouse() and sc_getmouse() routines must be called spl'd(). + */ +#define PROTOBYTES 5 +struct mse_softc { + int sc_flags; + int sc_mousetype; + struct selinfo sc_selp; + u_int sc_port; + void (*sc_enablemouse)(); + void (*sc_disablemouse)(); + void (*sc_getmouse)(); + int sc_deltax; + int sc_deltay; + int sc_obuttons; + int sc_buttons; + int sc_bytesread; + u_char sc_bytes[PROTOBYTES]; +} mse_sc[NMSE]; + +/* Flags */ +#define MSESC_OPEN 0x1 +#define MSESC_WANT 0x2 + +/* and Mouse Types */ +#define MSE_LOGITECH 0x1 +#define MSE_ATIINPORT 0x2 + +#define MSE_PORTA 0 +#define MSE_PORTB 1 +#define MSE_PORTC 2 +#define MSE_PORTD 3 + +#define MSE_UNIT(dev) (minor(dev) >> 1) +#define MSE_NBLOCKIO(dev) (minor(dev) & 0x1) + +/* + * Logitech bus mouse definitions + */ +#define MSE_SETUP 0x91 /* What does this mean? */ +#define MSE_HOLD 0x80 +#define MSE_RXLOW 0x00 +#define MSE_RXHIGH 0x20 +#define MSE_RYLOW 0x40 +#define MSE_RYHIGH 0x60 +#define MSE_DISINTR 0x10 +#define MSE_INTREN 0x00 + +static int mse_probelogi(); +static void mse_enablelogi(), mse_disablelogi(), mse_getlogi(); + +/* + * ATI Inport mouse definitions + */ +#define MSE_INPORT_RESET 0x80 +#define MSE_INPORT_STATUS 0x00 +#define MSE_INPORT_DX 0x01 +#define MSE_INPORT_DY 0x02 +#define MSE_INPORT_MODE 0x07 +#define MSE_INPORT_HOLD 0x20 +#define MSE_INPORT_INTREN 0x09 + +static int mse_probeati(); +static void mse_enableati(), mse_disableati(), mse_getati(); + +#define MSEPRI (PZERO + 3) + +/* + * Table of mouse types. + * Keep the Logitech last, since I haven't figured out how to probe it + * properly yet. (Someday I'll have the documentation.) + */ +struct mse_types { + int m_type; /* Type of bus mouse */ + int (*m_probe)(); /* Probe routine to test for it */ + void (*m_enable)(); /* Start routine */ + void (*m_disable)(); /* Disable interrupts routine */ + void (*m_get)(); /* and get mouse status */ +} mse_types[] = { + { MSE_ATIINPORT, mse_probeati, mse_enableati, mse_disableati, mse_getati }, + { MSE_LOGITECH, mse_probelogi, mse_enablelogi, mse_disablelogi, mse_getlogi }, + { 0, }, +}; + +int +mseprobe(idp) + register struct isa_device *idp; +{ + register struct mse_softc *sc = &mse_sc[idp->id_unit]; + register int i; + + /* + * Check for each mouse type in the table. + */ + i = 0; + while (mse_types[i].m_type) { + if ((*mse_types[i].m_probe)(idp)) { + sc->sc_mousetype = mse_types[i].m_type; + sc->sc_enablemouse = mse_types[i].m_enable; + sc->sc_disablemouse = mse_types[i].m_disable; + sc->sc_getmouse = mse_types[i].m_get; + return (1); + } + i++; + } + return (0); +} + +int +mseattach(idp) + struct isa_device *idp; +{ + struct mse_softc *sc = &mse_sc[idp->id_unit]; + + sc->sc_port = idp->id_iobase; + return (1); +} + +/* + * Exclusive open the mouse, initialize it and enable interrupts. + */ +int +mseopen(dev, flag) + dev_t dev; + int flag; +{ + register struct mse_softc *sc; + int s; + + if (MSE_UNIT(dev) >= NMSE) + return (ENXIO); + sc = &mse_sc[MSE_UNIT(dev)]; + if (sc->sc_flags & MSESC_OPEN) + return (EBUSY); + sc->sc_flags |= MSESC_OPEN; + sc->sc_obuttons = sc->sc_buttons = 0x7; + sc->sc_deltax = sc->sc_deltay = 0; + sc->sc_bytesread = PROTOBYTES; + + /* + * Initialize mouse interface and enable interrupts. + */ + s = spltty(); + (*sc->sc_enablemouse)(sc->sc_port); + splx(s); + return (0); +} + +/* + * mseclose: just turn off mouse innterrupts. + */ +int +mseclose(dev, flag) + dev_t dev; + int flag; +{ + struct mse_softc *sc = &mse_sc[MSE_UNIT(dev)]; + int s; + + s = spltty(); + (*sc->sc_disablemouse)(sc->sc_port); + sc->sc_flags &= ~MSESC_OPEN; + splx(s); + return(0); +} + +/* + * mseread: return mouse info using the MSC serial protocol, but without + * using bytes 4 and 5. + * (Yes this is cheesy, but it makes the X386 server happy, so...) + */ +int +mseread(dev, uio) + dev_t dev; + struct uio *uio; +{ + register struct mse_softc *sc = &mse_sc[MSE_UNIT(dev)]; + int xfer, s, error; + + /* + * If there are no protocol bytes to be read, set up a new protocol + * packet. + */ + s = spltty(); /* XXX Should be its own spl, but where is imlXX() */ + if (sc->sc_bytesread >= PROTOBYTES) { + while (sc->sc_deltax == 0 && sc->sc_deltay == 0 && + (sc->sc_obuttons ^ sc->sc_buttons) == 0) { + if (MSE_NBLOCKIO(dev)) { + splx(s); + return (0); + } + sc->sc_flags |= MSESC_WANT; + if (error = tsleep((caddr_t)sc, MSEPRI | PCATCH, + "mseread", 0)) { + splx(s); + return (error); + } + } + + /* + * Generate protocol bytes. + * For some reason X386 expects 5 bytes but never uses + * the fourth or fifth? + */ + sc->sc_bytes[0] = 0x80 | (sc->sc_buttons & ~0xf8); + if (sc->sc_deltax > 127) + sc->sc_deltax = 127; + if (sc->sc_deltax < -127) + sc->sc_deltax = -127; + sc->sc_deltay = -sc->sc_deltay; /* Otherwise mousey goes wrong way */ + if (sc->sc_deltay > 127) + sc->sc_deltay = 127; + if (sc->sc_deltay < -127) + sc->sc_deltay = -127; + sc->sc_bytes[1] = sc->sc_deltax; + sc->sc_bytes[2] = sc->sc_deltay; + sc->sc_bytes[3] = sc->sc_bytes[4] = 0; + sc->sc_obuttons = sc->sc_buttons; + sc->sc_deltax = sc->sc_deltay = 0; + sc->sc_bytesread = 0; + } + splx(s); + xfer = min(uio->uio_resid, PROTOBYTES - sc->sc_bytesread); + if (error = uiomove(&sc->sc_bytes[sc->sc_bytesread], xfer, uio)) + return (error); + sc->sc_bytesread += xfer; + return(0); +} + +/* + * mseselect: check for mouse input to be processed. + */ +int +mseselect(dev, rw, p) + dev_t dev; + int rw; + struct proc *p; +{ + register struct mse_softc *sc = &mse_sc[MSE_UNIT(dev)]; + int s; + + s = spltty(); + if (sc->sc_bytesread != PROTOBYTES || sc->sc_deltax != 0 || + sc->sc_deltay != 0 || (sc->sc_obuttons ^ sc->sc_buttons) != 0) { + splx(s); + return (1); + } + + /* + * Since this is an exclusive open device, any previous proc. + * pointer is trash now, so we can just assign it. + */ + selrecord(p, &sc->sc_selp); + splx(s); + return (0); +} + +/* + * mseintr: update mouse status. sc_deltax and sc_deltay are accumulative. + */ +void +mseintr(unit) + int unit; +{ + register struct mse_softc *sc = &mse_sc[unit]; + pid_t p; + +#ifdef DEBUG + static int mse_intrcnt = 0; + if((mse_intrcnt++ % 10000) == 0) + printf("mseintr\n"); +#endif /* DEBUG */ + if ((sc->sc_flags & MSESC_OPEN) == 0) + return; + + (*sc->sc_getmouse)(sc->sc_port, &sc->sc_deltax, &sc->sc_deltay, &sc->sc_buttons); + + /* + * If mouse state has changed, wake up anyone wanting to know. + */ + if (sc->sc_deltax != 0 || sc->sc_deltay != 0 || + (sc->sc_obuttons ^ sc->sc_buttons) != 0) { + if (sc->sc_flags & MSESC_WANT) { + sc->sc_flags &= ~MSESC_WANT; + wakeup((caddr_t)sc); + } + selwakeup(&sc->sc_selp); + } +} + +/* + * Routines for the Logitech mouse. + */ +/* + * Test for a Logitech bus mouse and return 1 if it is. + * (until I know how to use the signature port properly, just disable + * interrupts and return 1) + */ +static int +mse_probelogi(idp) + register struct isa_device *idp; +{ + + outb(idp->id_iobase + MSE_PORTB, 0x55); + if (inb(idp->id_iobase + MSE_PORTB) == 0x55) { + outb(idp->id_iobase + MSE_PORTB, 0xaa); + if (inb(idp->id_iobase + MSE_PORTB) == 0xaa) + return (1); + } + return (0); +} + +/* + * Initialize Logitech mouse and enable interrupts. + */ +static void +mse_enablelogi(port) + register u_int port; +{ + int dx, dy, but; + + outb(port + MSE_PORTD, MSE_SETUP); + mse_getlogi(port, &dx, &dy, &but); +} + +/* + * Disable interrupts for Logitech mouse. + */ +static void +mse_disablelogi(port) + register u_int port; +{ + + outb(port + MSE_PORTC, MSE_DISINTR); +} + +/* + * Get the current dx, dy and button up/down state. + */ +static void +mse_getlogi(port, dx, dy, but) + register u_int port; + int *dx; + int *dy; + int *but; +{ + register char x, y; + + outb(port + MSE_PORTC, MSE_HOLD | MSE_RXLOW); + x = inb(port + MSE_PORTA); + *but = (x >> 5) & 0x7; + x &= 0xf; + outb(port + MSE_PORTC, MSE_HOLD | MSE_RXHIGH); + x |= (inb(port + MSE_PORTA) << 4); + outb(port + MSE_PORTC, MSE_HOLD | MSE_RYLOW); + y = (inb(port + MSE_PORTA) & 0xf); + outb(port + MSE_PORTC, MSE_HOLD | MSE_RYHIGH); + y |= (inb(port + MSE_PORTA) << 4); + *dx += x; + *dy += y; + outb(port + MSE_PORTC, MSE_INTREN); +} + +/* + * Routines for the ATI Inport bus mouse. + */ +/* + * Test for a ATI Inport bus mouse and return 1 if it is. + * (do not enable interrupts) + */ +static int +mse_probeati(idp) + register struct isa_device *idp; +{ + int i; + + for (i = 0; i < 2; i++) + if (inb(idp->id_iobase + MSE_PORTC) == 0xde) + return (1); + return (0); +} + +/* + * Initialize ATI Inport mouse and enable interrupts. + */ +static void +mse_enableati(port) + register u_int port; +{ + + outb(port + MSE_PORTA, MSE_INPORT_RESET); + outb(port + MSE_PORTA, MSE_INPORT_MODE); + outb(port + MSE_PORTB, MSE_INPORT_INTREN); +} + +/* + * Disable interrupts for ATI Inport mouse. + */ +static void +mse_disableati(port) + register u_int port; +{ + + outb(port + MSE_PORTA, MSE_INPORT_MODE); + outb(port + MSE_PORTB, 0); +} + +/* + * Get current dx, dy and up/down button state. + */ +static void +mse_getati(port, dx, dy, but) + register u_int port; + int *dx; + int *dy; + int *but; +{ + register char byte; + + outb(port + MSE_PORTA, MSE_INPORT_MODE); + outb(port + MSE_PORTB, MSE_INPORT_HOLD); + outb(port + MSE_PORTA, MSE_INPORT_STATUS); + *but = ~(inb(port + MSE_PORTB) & 0x7); + outb(port + MSE_PORTA, MSE_INPORT_DX); + byte = inb(port + MSE_PORTB); + *dx += byte; + outb(port + MSE_PORTA, MSE_INPORT_DY); + byte = inb(port + MSE_PORTB); + *dy += byte; + outb(port + MSE_PORTA, MSE_INPORT_MODE); + outb(port + MSE_PORTB, MSE_INPORT_INTREN); +} +#endif /* NMSE */ diff --git a/sys/i386/isa/npx.c b/sys/i386/isa/npx.c new file mode 100644 index 0000000..f7f9b27 --- /dev/null +++ b/sys/i386/isa/npx.c @@ -0,0 +1,554 @@ +/*- + * Copyright (c) 1990 William Jolitz. + * 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. + * + * from: @(#)npx.c 7.2 (Berkeley) 5/12/91 + * $Id: npx.c,v 1.9 1994/06/11 05:17:15 davidg Exp $ + */ + +#include "npx.h" +#if NNPX > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/proc.h> +#include <machine/cpu.h> +#include <machine/pcb.h> +#include <machine/trap.h> +#include <sys/ioctl.h> +#include <machine/specialreg.h> +#include <i386/isa/icu.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/isa.h> + +/* + * 387 and 287 Numeric Coprocessor Extension (NPX) Driver. + */ + +#ifdef __GNUC__ + +#define disable_intr() __asm("cli") +#define enable_intr() __asm("sti") +#define fldcw(addr) __asm("fldcw %0" : : "m" (*addr)) +#define fnclex() __asm("fnclex") +#define fninit() __asm("fninit") +#define fnsave(addr) __asm("fnsave %0" : "=m" (*addr) : "0" (*addr)) +#define fnstcw(addr) __asm("fnstcw %0" : "=m" (*addr) : "0" (*addr)) +#define fnstsw(addr) __asm("fnstsw %0" : "=m" (*addr) : "0" (*addr)) +#define fp_divide_by_0() __asm("fldz; fld1; fdiv %st,%st(1); fwait") +#define frstor(addr) __asm("frstor %0" : : "m" (*addr)) +#define fwait() __asm("fwait") +#define read_eflags() ({u_long ef; \ + __asm("pushf; popl %0" : "=a" (ef)); \ + ef; }) +#define start_emulating() __asm("smsw %%ax; orb %0,%%al; lmsw %%ax" \ + : : "n" (CR0_TS) : "ax") +#define stop_emulating() __asm("clts") +#define write_eflags(ef) __asm("pushl %0; popf" : : "a" ((u_long) ef)) + +#else /* not __GNUC__ */ + +void disable_intr __P((void)); +void enable_intr __P((void)); +void fldcw __P((caddr_t addr)); +void fnclex __P((void)); +void fninit __P((void)); +void fnsave __P((caddr_t addr)); +void fnstcw __P((caddr_t addr)); +void fnstsw __P((caddr_t addr)); +void fp_divide_by_0 __P((void)); +void frstor __P((caddr_t addr)); +void fwait __P((void)); +u_long read_eflags __P((void)); +void start_emulating __P((void)); +void stop_emulating __P((void)); +void write_eflags __P((u_long ef)); + +#endif /* __GNUC__ */ + +typedef u_char bool_t; + +extern struct gate_descriptor idt[]; + +int npxdna __P((void)); +void npxexit __P((struct proc *p)); +void npxinit __P((u_int control)); +void npxintr __P((struct intrframe frame)); +void npxsave __P((struct save87 *addr)); +static int npxattach __P((struct isa_device *dvp)); +static int npxprobe __P((struct isa_device *dvp)); +static int npxprobe1 __P((struct isa_device *dvp)); + +struct isa_driver npxdriver = { + npxprobe, npxattach, "npx", +}; + +u_int npx0_imask; +struct proc *npxproc; + +static bool_t npx_ex16; +static bool_t npx_exists; +static struct gate_descriptor npx_idt_probeintr; +static int npx_intrno; +static volatile u_int npx_intrs_while_probing; +static bool_t npx_irq13; +static volatile u_int npx_traps_while_probing; + +/* + * Special interrupt handlers. Someday intr0-intr15 will be used to count + * interrupts. We'll still need a special exception 16 handler. The busy + * latch stuff in probintr() can be moved to npxprobe(). + */ +void probeintr(void); +asm +(" + .text +_probeintr: + ss + incl _npx_intrs_while_probing + pushl %eax + movb $0x20,%al # EOI (asm in strings loses cpp features) + outb %al,$0xa0 # IO_ICU2 + outb %al,$0x20 #IO_ICU1 + movb $0,%al + outb %al,$0xf0 # clear BUSY# latch + popl %eax + iret +"); + +void probetrap(void); +asm +(" + .text +_probetrap: + ss + incl _npx_traps_while_probing + fnclex + iret +"); + +/* + * Probe routine. Initialize cr0 to give correct behaviour for [f]wait + * whether the device exists or not (XXX should be elsewhere). Set flags + * to tell npxattach() what to do. Modify device struct if npx doesn't + * need to use interrupts. Return 1 if device exists. + */ +static int +npxprobe(dvp) + struct isa_device *dvp; +{ + int result; + u_long save_eflags; + u_char save_icu1_mask; + u_char save_icu2_mask; + struct gate_descriptor save_idt_npxintr; + struct gate_descriptor save_idt_npxtrap; + /* + * This routine is now just a wrapper for npxprobe1(), to install + * special npx interrupt and trap handlers, to enable npx interrupts + * and to disable other interrupts. Someday isa_configure() will + * install suitable handlers and run with interrupts enabled so we + * won't need to do so much here. + */ + npx_intrno = NRSVIDT + ffs(dvp->id_irq) - 1; + save_eflags = read_eflags(); + disable_intr(); + save_icu1_mask = inb(IO_ICU1 + 1); + save_icu2_mask = inb(IO_ICU2 + 1); + save_idt_npxintr = idt[npx_intrno]; + save_idt_npxtrap = idt[16]; + outb(IO_ICU1 + 1, ~(IRQ_SLAVE | dvp->id_irq)); + outb(IO_ICU2 + 1, ~(dvp->id_irq >> 8)); + setidt(16, probetrap, SDT_SYS386TGT, SEL_KPL); + setidt(npx_intrno, probeintr, SDT_SYS386IGT, SEL_KPL); + npx_idt_probeintr = idt[npx_intrno]; + enable_intr(); + result = npxprobe1(dvp); + disable_intr(); + outb(IO_ICU1 + 1, save_icu1_mask); + outb(IO_ICU2 + 1, save_icu2_mask); + idt[npx_intrno] = save_idt_npxintr; + idt[16] = save_idt_npxtrap; + write_eflags(save_eflags); + return (result); +} + +static int +npxprobe1(dvp) + struct isa_device *dvp; +{ + int control; + int status; +#ifdef lint + npxintr(); +#endif + /* + * Partially reset the coprocessor, if any. Some BIOS's don't reset + * it after a warm boot. + */ + outb(0xf1, 0); /* full reset on some systems, NOP on others */ + outb(0xf0, 0); /* clear BUSY# latch */ + /* + * Prepare to trap all ESC (i.e., NPX) instructions and all WAIT + * instructions. We must set the CR0_MP bit and use the CR0_TS + * bit to control the trap, because setting the CR0_EM bit does + * not cause WAIT instructions to trap. It's important to trap + * WAIT instructions - otherwise the "wait" variants of no-wait + * control instructions would degenerate to the "no-wait" variants + * after FP context switches but work correctly otherwise. It's + * particularly important to trap WAITs when there is no NPX - + * otherwise the "wait" variants would always degenerate. + * + * Try setting CR0_NE to get correct error reporting on 486DX's. + * Setting it should fail or do nothing on lesser processors. + */ + load_cr0(rcr0() | CR0_MP | CR0_NE); + /* + * But don't trap while we're probing. + */ + stop_emulating(); + /* + * Finish resetting the coprocessor, if any. If there is an error + * pending, then we may get a bogus IRQ13, but probeintr() will handle + * it OK. Bogus halts have never been observed, but we enabled + * IRQ13 and cleared the BUSY# latch early to handle them anyway. + */ + fninit(); + DELAY(1000); /* wait for any IRQ13 (fwait might hang) */ +#ifdef DIAGNOSTIC + if (npx_intrs_while_probing != 0) + printf("fninit caused %u bogus npx interrupt(s)\n", + npx_intrs_while_probing); + if (npx_traps_while_probing != 0) + printf("fninit caused %u bogus npx trap(s)\n", + npx_traps_while_probing); +#endif + /* + * Check for a status of mostly zero. + */ + status = 0x5a5a; + fnstsw(&status); + if ((status & 0xb8ff) == 0) { + /* + * Good, now check for a proper control word. + */ + control = 0x5a5a; + fnstcw(&control); + if ((control & 0x1f3f) == 0x033f) { + npx_exists = 1; + /* + * We have an npx, now divide by 0 to see if exception + * 16 works. + */ + control &= ~(1 << 2); /* enable divide by 0 trap */ + fldcw(&control); + npx_traps_while_probing = npx_intrs_while_probing = 0; + fp_divide_by_0(); + if (npx_traps_while_probing != 0) { + /* + * Good, exception 16 works. + */ + npx_ex16 = 1; + dvp->id_irq = 0; /* zap the interrupt */ + /* + * special return value to flag that we do not + * actually use any I/O registers + */ + return (-1); + } + if (npx_intrs_while_probing != 0) { + /* + * Bad, we are stuck with IRQ13. + */ + npx_irq13 = 1; + npx0_imask = dvp->id_irq; /* npxattach too late */ + return (IO_NPXSIZE); + } + /* + * Worse, even IRQ13 is broken. Use emulator. + */ + } + } + /* + * Probe failed, but we want to get to npxattach to initialize the + * emulator and say that it has been installed. XXX handle devices + * that aren't really devices better. + */ + dvp->id_irq = 0; + /* + * special return value to flag that we do not + * actually use any I/O registers + */ + return (-1); +} + +/* + * Attach routine - announce which it is, and wire into system + */ +int +npxattach(dvp) + struct isa_device *dvp; +{ + if (!npx_ex16 && !npx_irq13) { + if (npx_exists) { + printf("npx%d: Error reporting broken, using 387 emulator\n",dvp->id_unit); + npx_exists = 0; + } else { + printf("npx%d: 387 Emulator\n",dvp->id_unit); + } + } + npxinit(__INITIAL_NPXCW__); + return (1); /* XXX unused */ +} + +/* + * Initialize floating point unit. + */ +void +npxinit(control) + u_int control; +{ + struct save87 dummy; + + if (!npx_exists) + return; + /* + * fninit has the same h/w bugs as fnsave. Use the detoxified + * fnsave to throw away any junk in the fpu. fnsave initializes + * the fpu and sets npxproc = NULL as important side effects. + */ + npxsave(&dummy); + stop_emulating(); + fldcw(&control); + if (curpcb != NULL) + fnsave(&curpcb->pcb_savefpu); + start_emulating(); +} + +/* + * Free coprocessor (if we have it). + */ +void +npxexit(p) + struct proc *p; +{ + + if (p == npxproc) { + start_emulating(); + npxproc = NULL; + } +} + +/* + * Record the FPU state and reinitialize it all except for the control word. + * Then generate a SIGFPE. + * + * Reinitializing the state allows naive SIGFPE handlers to longjmp without + * doing any fixups. + * + * XXX there is currently no way to pass the full error state to signal + * handlers, and if this is a nested interrupt there is no way to pass even + * a status code! So there is no way to have a non-naive SIGFPE handler. At + * best a handler could do an fninit followed by an fldcw of a static value. + * fnclex would be of little use because it would leave junk on the FPU stack. + * Returning from the handler would be even less safe than usual because + * IRQ13 exception handling makes exceptions even less precise than usual. + */ +void +npxintr(frame) + struct intrframe frame; +{ + int code; + + if (npxproc == NULL || !npx_exists) { + /* XXX no %p in stand/printf.c. Cast to quiet gcc -Wall. */ + printf("npxintr: npxproc = %lx, curproc = %lx, npx_exists = %d\n", + (u_long) npxproc, (u_long) curproc, npx_exists); + panic("npxintr from nowhere"); + } + if (npxproc != curproc) { + printf("npxintr: npxproc = %lx, curproc = %lx, npx_exists = %d\n", + (u_long) npxproc, (u_long) curproc, npx_exists); + panic("npxintr from non-current process"); + } + /* + * Save state. This does an implied fninit. It had better not halt + * the cpu or we'll hang. + */ + outb(0xf0, 0); + fnsave(&curpcb->pcb_savefpu); + fwait(); + /* + * Restore control word (was clobbered by fnsave). + */ + fldcw(&curpcb->pcb_savefpu.sv_env.en_cw); + fwait(); + /* + * Remember the exception status word and tag word. The current + * (almost fninit'ed) fpu state is in the fpu and the exception + * state just saved will soon be junk. However, the implied fninit + * doesn't change the error pointers or register contents, and we + * preserved the control word and will copy the status and tag + * words, so the complete exception state can be recovered. + */ + curpcb->pcb_savefpu.sv_ex_sw = curpcb->pcb_savefpu.sv_env.en_sw; + curpcb->pcb_savefpu.sv_ex_tw = curpcb->pcb_savefpu.sv_env.en_tw; + + /* + * Pass exception to process. + */ + if (ISPL(frame.if_cs) == SEL_UPL) { + /* + * Interrupt is essentially a trap, so we can afford to call + * the SIGFPE handler (if any) as soon as the interrupt + * returns. + * + * XXX little or nothing is gained from this, and plenty is + * lost - the interrupt frame has to contain the trap frame + * (this is otherwise only necessary for the rescheduling trap + * in doreti, and the frame for that could easily be set up + * just before it is used). + */ + curproc->p_md.md_regs = (int *)&frame.if_es; +#ifdef notyet + /* + * Encode the appropriate code for detailed information on + * this exception. + */ + code = XXX_ENCODE(curpcb->pcb_savefpu.sv_ex_sw); +#else + code = 0; /* XXX */ +#endif + trapsignal(curproc, SIGFPE, code); + } else { + /* + * Nested interrupt. These losers occur when: + * o an IRQ13 is bogusly generated at a bogus time, e.g.: + * o immediately after an fnsave or frstor of an + * error state. + * o a couple of 386 instructions after + * "fstpl _memvar" causes a stack overflow. + * These are especially nasty when combined with a + * trace trap. + * o an IRQ13 occurs at the same time as another higher- + * priority interrupt. + * + * Treat them like a true async interrupt. + */ + psignal(npxproc, SIGFPE); + } +} + +/* + * Implement device not available (DNA) exception + * + * It would be better to switch FP context here (only). This would require + * saving the state in the proc table instead of in the pcb. + */ +int +npxdna() +{ + if (!npx_exists) + return (0); + if (npxproc != NULL) { + printf("npxdna: npxproc = %lx, curproc = %lx\n", + (u_long) npxproc, (u_long) curproc); + panic("npxdna"); + } + stop_emulating(); + /* + * Record new context early in case frstor causes an IRQ13. + */ + npxproc = curproc; + /* + * The following frstor may cause an IRQ13 when the state being + * restored has a pending error. The error will appear to have been + * triggered by the current (npx) user instruction even when that + * instruction is a no-wait instruction that should not trigger an + * error (e.g., fnclex). On at least one 486 system all of the + * no-wait instructions are broken the same as frstor, so our + * treatment does not amplify the breakage. On at least one + * 386/Cyrix 387 system, fnclex works correctly while frstor and + * fnsave are broken, so our treatment breaks fnclex if it is the + * first FPU instruction after a context switch. + */ + frstor(&curpcb->pcb_savefpu); + + return (1); +} + +/* + * Wrapper for fnsave instruction to handle h/w bugs. If there is an error + * pending, then fnsave generates a bogus IRQ13 on some systems. Force + * any IRQ13 to be handled immediately, and then ignore it. This routine is + * often called at splhigh so it must not use many system services. In + * particular, it's much easier to install a special handler than to + * guarantee that it's safe to use npxintr() and its supporting code. + */ +void +npxsave(addr) + struct save87 *addr; +{ + u_char icu1_mask; + u_char icu2_mask; + u_char old_icu1_mask; + u_char old_icu2_mask; + struct gate_descriptor save_idt_npxintr; + + disable_intr(); + old_icu1_mask = inb(IO_ICU1 + 1); + old_icu2_mask = inb(IO_ICU2 + 1); + save_idt_npxintr = idt[npx_intrno]; + outb(IO_ICU1 + 1, old_icu1_mask & ~(IRQ_SLAVE | npx0_imask)); + outb(IO_ICU2 + 1, old_icu2_mask & ~(npx0_imask >> 8)); + idt[npx_intrno] = npx_idt_probeintr; + enable_intr(); + stop_emulating(); + fnsave(addr); + fwait(); + start_emulating(); + npxproc = NULL; + disable_intr(); + icu1_mask = inb(IO_ICU1 + 1); /* masks may have changed */ + icu2_mask = inb(IO_ICU2 + 1); + outb(IO_ICU1 + 1, + (icu1_mask & ~npx0_imask) | (old_icu1_mask & npx0_imask)); + outb(IO_ICU2 + 1, + (icu2_mask & ~(npx0_imask >> 8)) + | (old_icu2_mask & (npx0_imask >> 8))); + idt[npx_intrno] = save_idt_npxintr; + enable_intr(); /* back to usual state */ +} + +#endif /* NNPX > 0 */ diff --git a/sys/i386/isa/pas.c b/sys/i386/isa/pas.c new file mode 100644 index 0000000..e0bbbc0 --- /dev/null +++ b/sys/i386/isa/pas.c @@ -0,0 +1,1011 @@ +/*- + * Copyright (C) 1993 Allen K. Briggs, Chris P. Caputo, + * Michael L. Finch, Bradley A. Grantham, and + * Lawrence A. Kesteloot + * Copyright (C) 1994 Poul-Henning Kamp + * 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 Alice Group. + * 4. The names of the Alice Group or any of its members may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ALICE GROUP ``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 ALICE GROUP 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. + * + * $Id: pas.c,v 1.8.2.1 1994/07/11 00:02:32 cgd Exp $ + * + */ + +/* Modified for use with the pc532 by Phil Nelson, Feb 94. + * Modified for use with MediaVision ProAudioSpectrum type adapters + * under FreeBSD by Poul-Henning Kamp, + */ + + +#include "pas.h" +#if NPAS > 0 +#include "types.h" +#include "param.h" +#include "systm.h" +#include "conf.h" +#include "file.h" +#include "buf.h" +#include "stat.h" +#include "uio.h" +#include "ioctl.h" +#include "cdio.h" +#include "errno.h" +#include "dkbad.h" +#include "disklabel.h" +#include "icu.h" +#include "i386/isa/isa.h" +#include "i386/isa/isa_device.h" +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> +#include "ic/ncr_5380.h" + +/* What we need to debug the driver */ + +#ifdef PAS_DEBUG +static int pas_show_scsi_cmd(struct scsi_xfer *xs); +#define HERE() printf("<%d>",__LINE__) +#define ARGH() printf("[%d]",__LINE__) +#else /* PAS_DEBUG */ +#define HERE() /**/ +#define ARGH() /**/ +#endif /* PAS_DEBUG */ + +#define SCSI_PHASE_DATA_OUT 0x0 +#define SCSI_PHASE_DATA_IN 0x1 +#define SCSI_PHASE_CMD 0x2 +#define SCSI_PHASE_STATUS 0x3 +#define SCSI_PHASE_UNSPEC1 0x4 +#define SCSI_PHASE_UNSPEC2 0x5 +#define SCSI_PHASE_MESSAGE_OUT 0x6 +#define SCSI_PHASE_MESSAGE_IN 0x7 + +#define SCSI_PHASE(x) ((x)&0x7) + +#define SCSI_RET_SUCCESS 0 +#define SCSI_RET_RETRY 1 +#define SCSI_RET_DEVICE_DOWN 2 +#define SCSI_RET_COMMAND_FAIL 3 + +/* Our per device (card) structure */ +static +struct pas_softc { + struct scsi_link sc_link; + int mv_unit; + u_short iobase; +#define sci_data(ptr) (ptr->iobase+0x0) +#define sci_icmd(ptr) (ptr->iobase+0x1) +#define sci_mode(ptr) (ptr->iobase+0x2) +#define sci_tcmd(ptr) (ptr->iobase+0x3) +#define sci_bus_csr(ptr) (ptr->iobase+0x2000) +#define sci_csr(ptr) (ptr->iobase+0x2001) +#define sci_idata(ptr) (ptr->iobase+0x2002) +#define sci_iack(ptr) (ptr->iobase+0x2003) +#define sci_pdmadata(ptr) (ptr->iobase+0x4000) +#define sci_pdmastat(ptr) (ptr->iobase+0x4001) +} s_pas[NPAS]; + +/* Our access to the 5380 chip */ +#define P_PAS struct pas_softc * +#define R_PAS(ptr,foo) inb(foo(ptr)) +#define W_PAS(ptr,foo,val) outb(foo(ptr),val) +#define M_PAS(ptr,foo,opr,arg) outb(foo(ptr),R_PAS(ptr,foo) opr arg) +#define PSEUDO_DMA 0 +#if PSEUDO_DMA +/* +static int sci_pdma_out(P_PAS, int, int, u_char *); +*/ +#define sci_pdma_out sci_data_out +static int sci_pdma_in(P_PAS, int, int, u_char *); + +#else /* PSEUDO_DMA */ +#define sci_pdma_out sci_data_out +#define sci_pdma_in sci_data_in +#endif /* PSEUDO_DMA */ + + + +static int pas_foo; +#define SCI_CLR_INTR(ptr) {pas_foo += R_PAS(ptr,sci_iack);} +#define SCI_PHASE_DISC 0 /* sort of ... */ +#define SCI_ACK(ptr,phase) {W_PAS(ptr,sci_tcmd,phase);} +#define SCSI_TIMEOUT_VAL 10000000 +#define WAIT_FOR_NOT_REQ(ptr) { \ + int scsi_timeout = SCSI_TIMEOUT_VAL; \ + while ( (R_PAS(ptr,sci_bus_csr) & SCI_BUS_REQ) && \ + (R_PAS(ptr,sci_bus_csr) & SCI_BUS_REQ) && \ + (R_PAS(ptr,sci_bus_csr) & SCI_BUS_REQ) && \ + (--scsi_timeout) ); \ + if (!scsi_timeout) { \ + printf("scsi timeout--WAIT_FOR_NOT_REQ-- pas.c:%d.\n", __LINE__); \ + goto scsi_timeout_error; \ + } \ +} +#define WAIT_FOR_REQ(ptr) { \ + int scsi_timeout = SCSI_TIMEOUT_VAL; \ + while ( ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_REQ) == 0) && \ + ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_REQ) == 0) && \ + ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_REQ) == 0) && \ + (--scsi_timeout) ); \ + if (!scsi_timeout) { \ + printf("scsi timeout--WAIT_FOR_REQ-- pas.c:%d.\n", __LINE__); \ + goto scsi_timeout_error; \ + } \ +} +#define WAIT_FOR_BSY(ptr) { \ + int scsi_timeout = SCSI_TIMEOUT_VAL; \ + while ( ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_BSY) == 0) && \ + ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_BSY) == 0) && \ + ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_BSY) == 0) && \ + (--scsi_timeout) ); \ + if (!scsi_timeout) { \ + printf("scsi timeout--WAIT_FOR_BSY-- pas.c:%d.\n", __LINE__); \ + goto scsi_timeout_error; \ + } \ +} + +static u_int32 pas_adapter_info(int adapter_number); +static void pas_minphys(struct buf *bp); +static int32 pas_scsi_cmd(struct scsi_xfer *xs); + +static int pas_reset_target(int adapter, int target); +static int pas_poll(int adapter, int timeout); +static int pas_send_cmd(struct scsi_xfer *xs); +static int scsi_req(P_PAS,int,int,u_char *,int, + u_char *, int , int *, int *); + +static int scsi_group0(int adapter, int id, int lun, + int opcode, int addr, int len, + int flags, caddr_t databuf, int datalen); + +static int sci_data_out(P_PAS, int, int, u_char *); +static int sci_data_in(P_PAS, int, int, u_char *); + +extern int pasprobe(struct isa_device *dev); +extern int pasattach(struct isa_device *dev); +struct isa_driver pasdriver = { pasprobe, pasattach, "pas" }; + + +static +struct scsi_adapter pas_adapter = { + pas_scsi_cmd, /* scsi_cmd() */ + pas_minphys, /* scsi_minphys() */ + 0, /* open_target_lu() */ + 0, /* close_target_lu() */ + pas_adapter_info, /* adapter_info() */ +"pas", /* name */ + { 0, 0 } /* spare[2] */ +}; + +static +struct scsi_device pas_dev = { + NULL, /* Use default error handler. */ + NULL, /* have a queue, served by this (?) */ + NULL, /* have no async handler. */ + NULL, /* Use default "done" routine. */ + "pas", + 0, + { 0, 0 } +}; + +static char *mv_type[] = { "?" "PAS","PAS+","CDPC","PAS16C","PAS16D" }; + + +int +pasprobe(struct isa_device *dev) +{ + int port = dev->id_iobase; + int base = port - 0x1c00; + int unit = dev->id_unit; + int i, j; + + /* Tell the PAS16 we want to talk to, where to listen */ + outb(0x9a01,0xbc + unit); + outb(0x9a01,base >> 2); + + /* Various magic */ + outb(base+0x4000,0x30); + outb(base+0x4001,0x01); + outb(base+0xbc00,0x01); + + /* Killer one */ + i = inb(base + 0x803); + if(i == 0xff) return 0; + + /* killer two */ + outb(base+0x803,i ^ 0xe0); + j = inb(base + 0x803); + outb(base+0x803,1); + if(i != j) return 0; + + /* killer three */ + if((0x03 & inb(base+0xec03)) != 0x03) return 0; + + /* killer four */ + for(i=0;i<4;i++) { + if(inb(port) != 0xff || inb(port+0x2000) != 0xff) + return 1; + } + return 0; +} + +static u_int32 +pas_adapter_info(int adapter_number) +{ + return 1; +} + +int +pasattach(struct isa_device *dev) +{ + int i,j; + struct pas_softc *ppas; + i = inb(dev->id_iobase - 0x1c00 + 0xFC00) >> 5; + if(i >= (sizeof mv_type / sizeof *mv_type)) + j=0; + else + j = i+1; + printf("pas%d: Type = %d <%s>\n",dev->id_unit,i,mv_type[j]); + ppas = s_pas + dev->id_unit; + ppas->sc_link.adapter_unit = dev->id_unit; + ppas->sc_link.adapter_targ = 7; + ppas->sc_link.adapter = &pas_adapter; + ppas->sc_link.device = &pas_dev; + ppas->iobase=dev->id_iobase; +#ifdef STILL_NO_INTR +/* As of yet we havn't bothered with interrupts, so don't bother */ + j = inb(dev->id_iobase - 0x1c00 + 0xf002); + switch (dev->id_irq) { + case IRQ2: i=1; break; + case IRQ3: i=2; break; + case IRQ4: i=3; break; + case IRQ5: i=4; break; + case IRQ6: i=5; break; + case IRQ7: i=6; break; + case IRQ10: i=7; break; + case IRQ11: i=8; break; + case IRQ12: i=9; break; + case IRQ14: i=10; break; + case IRQ15: i=11; break; + default: + printf("Intr %d unknown\n",dev->id_irq); + panic("brag!"); + } + printf("0xf002 irq%d code=%x, before %x,",dev->id_irq,i,j); + j &= 0x0f; + j |= (i << 4); + printf(" after= %x \n",j); + outb(dev->id_iobase - 0x1c00 + 0xf002,j); + outb(dev->id_iobase - 0x1c00 + 0x8003,0x4d); +#endif /* STILL_NO_INTR */ + scsi_attachdevs(&(ppas->sc_link)); + return 1; +} + +static void +pas_minphys(struct buf *bp) +{ +#define MIN_PHYS 65536 /*BARF!!!!*/ + if (bp->b_bcount > MIN_PHYS) { + printf("Uh-oh... pas_minphys setting bp->b_bcount = %x.\n", MIN_PHYS); + bp->b_bcount = MIN_PHYS; + } +#undef MIN_PHYS +} + +static int32 +pas_scsi_cmd(struct scsi_xfer *xs) +{ + int flags, s, r; + + flags = xs->flags; + if (xs->bp) flags |= (SCSI_NOSLEEP); + if ( flags & ITSDONE ) { + printf("Already done?"); + xs->flags &= ~ITSDONE; + } + if ( ! ( flags & INUSE ) ) { + printf("Not in use?"); + xs->flags |= INUSE; + } + + if ( flags & SCSI_RESET ) { + printf("flags & SCSIRESET.\n"); + if ( ! ( flags & SCSI_NOSLEEP ) ) { + s = splbio(); + pas_reset_target(xs->sc_link->adapter_unit, + xs->sc_link->target); + splx(s); + return(SUCCESSFULLY_QUEUED); + } else { + pas_reset_target(xs->sc_link->adapter_unit, xs->sc_link->target); + if (pas_poll(xs->sc_link->adapter_unit, xs->timeout)) { + return (HAD_ERROR); + } + return (COMPLETE); + } + } + /* + * OK. Now that that's over with, let's pack up that + * SCSI puppy and send it off. If we can, we'll just + * queue and go; otherwise, we'll wait for the command + * to finish. + if ( ! ( flags & SCSI_NOSLEEP ) ) { + s = splbio(); + pas_send_cmd(xs); + splx(s); + return(SUCCESSFULLY_QUEUED); + } + */ + + r = pas_send_cmd(xs); + xs->flags |= ITSDONE; + scsi_done(xs); + if (xs->flags&SCSI_NOMASK) { + return (r); + } + return SUCCESSFULLY_QUEUED; +} + +#ifdef PAS_DEBUG +static int +pas_show_scsi_cmd(struct scsi_xfer *xs) +{ + u_char *b = (u_char *) xs->cmd; + int i = 0; + + if ( ! ( xs->flags & SCSI_RESET ) ) { + printf("pas(%d:%d:%d) ", + xs->sc_link->adapter_unit, xs->sc_link->target, xs->sc_link->lun); + printf("%d@%x, %d@%x <",xs->cmdlen,xs->cmd,xs->datalen,xs->data); + while (i < xs->cmdlen) { + if (i) printf(","); + printf("%x",b[i++]); + } + printf(">\n"); + } else { + printf("pas(%d:%d:%d)-RESET-\n", + xs->sc_link->adapter_unit, xs->sc_link->target, + xs->sc_link->lun); + } + return 0; +} +#endif /* PAS_DEBUG */ + +/* + * Actual chip control. + */ + +extern void +pasintr(int adapter) +{ + struct pas_softc *ppas = s_pas + adapter; + printf ("pasintr\n"); + SCI_CLR_INTR(ppas); + W_PAS(ppas,sci_mode,0x00); +} + +#if PHK +extern int +scsi_irq_intr(void) +{ + +/* if (R_PAS(ptr,sci_csr) != SCI_CSR_PHASE_MATCH) + printf("scsi_irq_intr called (not just phase match -- " + "csr = 0x%x, bus_csr = 0x%x).\n", + R_PAS(ptr,sci_csr), R_PAS(ptr,sci_bus_csr)); + pas_intr(0); */ + return 1; +} +#endif + +static int +pas_reset_target(int adapter, int target) +{ + struct pas_softc *ppas = s_pas + adapter; + + W_PAS(ppas,sci_icmd,SCI_ICMD_TEST); + W_PAS(ppas,sci_icmd,SCI_ICMD_TEST | SCI_ICMD_RST); + DELAY(2500); + W_PAS(ppas,sci_icmd,0); + + W_PAS(ppas,sci_mode,0); + W_PAS(ppas,sci_tcmd,SCI_PHASE_DISC); + W_PAS(ppas,sci_sel_enb,0); + + SCI_CLR_INTR(ppas); + SCI_CLR_INTR(ppas); + return 0; +} + +static int +pas_poll(int adapter, int timeout) +{ + return 0; +} + +static int +pas_send_cmd(struct scsi_xfer *xs) +{ + P_PAS ptr = s_pas + xs->sc_link->adapter_unit; + int s,sent,ret; + int sense; + +#ifdef PAS_DEBUG + pas_show_scsi_cmd(xs); +#endif /* PAS_DEBUG */ + s = splbio(); + sense = scsi_req(ptr, + xs->sc_link->target, xs->sc_link->lun, + (u_char*)xs->cmd, xs->cmdlen, xs->data, xs->datalen, + &sent, &ret); + splx(s); +#ifdef PAS_DEBUG +HERE(); + printf("sent=%d,ret=%d,sense=%x ",sent,ret,sense); +#endif /* PAS_DEBUG */ + switch (sense) { + case 0x00: + xs->error = XS_NOERROR; + xs->resid = sent; +#ifdef PAS_DEBUG + printf("\n"); +#endif /* PAS_DEBUG */ + return (COMPLETE); + case 0x02: /* Check condition */ +#ifdef PAS_DEBUG + printf("check cond. targ= %d.\n", xs->sc_link->target); +#endif + spinwait(10); + s = splbio(); + scsi_group0(xs->sc_link->adapter_unit, + xs->sc_link->target, xs->sc_link->lun, + 0x3, 0x0, + sizeof(struct scsi_sense_data), + 0, (caddr_t) &(xs->sense), + sizeof(struct scsi_sense_data)); + splx(s); + xs->error = XS_SENSE; + return HAD_ERROR; + case 0x08: /* Busy */ +ARGH(); + xs->error = XS_BUSY; + return HAD_ERROR; + default: +ARGH(); + xs->error = XS_DRIVER_STUFFUP; + return HAD_ERROR; + } +} + +static int +select_target(P_PAS ptr, u_char myid, u_char tid, int with_atn) +{ + register u_char bid, icmd; + int ret = SCSI_RET_RETRY; + if ((R_PAS(ptr,sci_bus_csr) & (SCI_BUS_BSY|SCI_BUS_SEL)) && + (R_PAS(ptr,sci_bus_csr) & (SCI_BUS_BSY|SCI_BUS_SEL)) && + (R_PAS(ptr,sci_bus_csr) & (SCI_BUS_BSY|SCI_BUS_SEL))) + return ret; + + /* for our purposes.. */ + myid = 1 << myid; + tid = 1 << tid; + + W_PAS(ptr,sci_sel_enb,0); /* we don't want any interrupts. */ + W_PAS(ptr,sci_tcmd,0); /* get into a harmless state */ + W_PAS(ptr,sci_mode,0); /* get into a harmless state */ + + W_PAS(ptr,sci_odata,myid); + W_PAS(ptr,sci_mode,SCI_MODE_ARB); + + /* AIP might not set if BSY went true after we checked */ + for (bid = 0; bid < 20; bid++) /* 20usec circa */ + if (R_PAS(ptr,sci_icmd) & SCI_ICMD_AIP) + break; + if ((R_PAS(ptr,sci_icmd) & SCI_ICMD_AIP) == 0) { +ARGH(); + goto lost; + } + + spinwait(2 /* was 2 */); /* 2.2us arb delay */ + + if (R_PAS(ptr,sci_icmd) & SCI_ICMD_LST) { +printf ("lost 1\n"); + goto lost; + } + + M_PAS(ptr,sci_mode,&, ~SCI_MODE_PAR_CHK); + bid = R_PAS(ptr,sci_data); + + if ((bid & ~myid) > myid) { +printf ("lost 2\n"); + goto lost; + } + if (R_PAS(ptr,sci_icmd) & SCI_ICMD_LST) { +printf ("lost 3\n"); + goto lost; + } + + /* Won arbitration, enter selection phase now */ + icmd = R_PAS(ptr,sci_icmd) & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); + icmd |= (with_atn ? (SCI_ICMD_SEL|SCI_ICMD_ATN) : SCI_ICMD_SEL); + icmd |= SCI_ICMD_BSY; + W_PAS(ptr,sci_icmd,icmd); + + if (R_PAS(ptr,sci_icmd) & SCI_ICMD_LST) { +printf ("nosel\n"); + goto nosel; + } + + /* XXX a target that violates specs might still drive the bus XXX */ + /* XXX should put our id out, and after the delay check nothi XXX */ + /* XXX ng else is out there. XXX */ + + DELAY(0); + + W_PAS(ptr,sci_tcmd,0); + W_PAS(ptr,sci_odata,myid|tid); + W_PAS(ptr,sci_sel_enb,0); + + M_PAS(ptr,sci_mode, &, ~SCI_MODE_ARB); /* 2 deskew delays, too */ + W_PAS(ptr,sci_mode,0); + + icmd |= SCI_ICMD_DATA; + icmd &= ~(SCI_ICMD_BSY); + + W_PAS(ptr,sci_icmd,icmd); + + /* bus settle delay, 400ns */ + DELAY(2); /* too much (was 2) ? */ + +/* M_PAS(ptr,sci_mode, |, SCI_MODE_PAR_CHK); */ + + { + register int timeo = 2500;/* 250 msecs in 100 usecs chunks */ + while ((R_PAS(ptr,sci_bus_csr) & SCI_BUS_BSY) == 0) { + if (--timeo > 0) { + DELAY(100); + } else { +ARGH(); + goto nodev; + } + } + } + + icmd &= ~(SCI_ICMD_DATA|SCI_ICMD_SEL); + W_PAS(ptr,sci_icmd,icmd); +/* ptr->sci_sel_enb = myid;*/ /* looks like we should NOT have it */ + return SCSI_RET_SUCCESS; +nodev: + ret = SCSI_RET_DEVICE_DOWN; + W_PAS(ptr,sci_sel_enb,myid); +nosel: + icmd &= ~(SCI_ICMD_DATA|SCI_ICMD_SEL|SCI_ICMD_ATN); + W_PAS(ptr,sci_icmd,icmd); +lost: + W_PAS(ptr,sci_mode,0); + + return ret; +} + +static int +sci_data_out(P_PAS ptr, int phase, int count, u_char *data) +{ + register unsigned char icmd; + register int cnt=0; + +#ifdef PAS_DEBUG + printf("out%d@%x ",count,data); +#endif /* PAS_DEBUG */ + /* ..checks.. */ + + icmd = R_PAS(ptr,sci_icmd) & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); +loop: + if (SCI_CUR_PHASE(R_PAS(ptr,sci_bus_csr)) != phase) + return cnt; + + WAIT_FOR_REQ(ptr); + icmd |= SCI_ICMD_DATA; + W_PAS(ptr,sci_icmd,icmd); + W_PAS(ptr,sci_odata, *data++); + icmd |= SCI_ICMD_ACK; + W_PAS(ptr,sci_icmd,icmd); + + icmd &= ~(SCI_ICMD_DATA|SCI_ICMD_ACK); + WAIT_FOR_NOT_REQ(ptr); + W_PAS(ptr,sci_icmd,icmd); + ++cnt; + if (--count > 0) + goto loop; +scsi_timeout_error: + return cnt; +} + +static int +sci_data_in(P_PAS ptr, int phase, int count, u_char *data) +{ + register unsigned char icmd; + register int cnt=0; + +#ifdef PAS_DEBUG + printf("in%d@%x ",count,data); +#endif /* PAS_DEBUG */ + /* ..checks.. */ + + icmd = R_PAS(ptr,sci_icmd) & ~(SCI_ICMD_DIFF|SCI_ICMD_TEST); + +loop: + if (SCI_CUR_PHASE(R_PAS(ptr,sci_bus_csr)) != phase) + return cnt; + + WAIT_FOR_REQ(ptr); + *data++ = R_PAS(ptr,sci_data); + + icmd |= SCI_ICMD_ACK; + W_PAS(ptr,sci_icmd,icmd); + + icmd &= ~SCI_ICMD_ACK; + WAIT_FOR_NOT_REQ(ptr); + W_PAS(ptr,sci_icmd,icmd); + ++cnt; + if (--count > 0) + goto loop; + +scsi_timeout_error: + return cnt; +} + +#if PSEUDO_DMA + +static int +sci_pdma_in(P_PAS ptr, int phase, int count, u_char *data) +{ + register unsigned char icmd; + register int cnt=0; + +#ifdef PAS_DEBUG + printf("in%d@%x ",count,data); +#endif /* PAS_DEBUG */ + + WAIT_FOR_BSY(ptr); + M_PAS(ptr,sci_mode, |, SCI_MODE_DMA); + W_PAS(ptr,sci_dma_send, 0); + M_PAS(ptr,sci_icmd, |, SCI_ICMD_DATA); + +/* + while(R_PAS(ptr,sci_pdmastat) & 0x80) ; +*/ + + for(; cnt < count; cnt++) + *data++ = R_PAS(ptr,sci_pdmadata); + +scsi_timeout_error: + M_PAS(ptr,sci_mode, &, ~SCI_MODE_DMA); + M_PAS(ptr,sci_icmd, &, ~SCI_ICMD_DATA); + return cnt; +} +#endif /* PSEUDO_DMA */ + +static int +cmd_xfer(P_PAS ptr, int maxlen, u_char *data, u_char *status, u_char *msg) +{ + int xfer=0, phase; + +#ifdef PAS_DEBUG + printf("cmd_xfer called for 0x%x.\n", *data); +#endif /* PAS_DEBUG */ + + W_PAS(ptr,sci_icmd,0); + + while (1) { + + WAIT_FOR_REQ(ptr); + + phase = SCI_CUR_PHASE(R_PAS(ptr,sci_bus_csr)); + + switch (phase) { + case SCSI_PHASE_CMD: + SCI_ACK(ptr,SCSI_PHASE_CMD); + xfer += sci_data_out(ptr, SCSI_PHASE_CMD, maxlen, data); + return xfer; + case SCSI_PHASE_DATA_IN: + printf("Data in phase in cmd_xfer?\n"); + return 0; + case SCSI_PHASE_DATA_OUT: + printf("Data out phase in cmd_xfer?\n"); + return 0; + case SCSI_PHASE_STATUS: + SCI_ACK(ptr,SCSI_PHASE_STATUS); + printf("status in cmd_xfer.\n"); + sci_data_in(ptr, SCSI_PHASE_STATUS, 1, status); + break; + case SCSI_PHASE_MESSAGE_IN: + SCI_ACK(ptr,SCSI_PHASE_MESSAGE_IN); + printf("msgin in cmd_xfer.\n"); + sci_data_in(ptr, SCSI_PHASE_MESSAGE_IN, 1, msg); + break; + case SCSI_PHASE_MESSAGE_OUT: + SCI_ACK(ptr,SCSI_PHASE_MESSAGE_OUT); + sci_data_out(ptr, SCSI_PHASE_MESSAGE_OUT, 1, msg); + break; + default: + printf("Unexpected phase 0x%x in cmd_xfer()\n", phase); +scsi_timeout_error: + return xfer; + break; + } + } +} + +static int +data_xfer(P_PAS ptr, int maxlen, u_char *data, u_char *status, u_char *msg) +{ + int retlen = 0, xfer, phase; + + W_PAS(ptr,sci_icmd,0); + + *status = 0; + + while (1) { + + WAIT_FOR_REQ(ptr); + + phase = SCI_CUR_PHASE(R_PAS(ptr,sci_bus_csr)); + switch (phase) { + case SCSI_PHASE_CMD: + printf("Command phase in data_xfer().\n"); + return retlen; + case SCSI_PHASE_DATA_IN: + SCI_ACK(ptr,SCSI_PHASE_DATA_IN); + xfer = sci_pdma_in (ptr, SCSI_PHASE_DATA_IN, maxlen, data); + retlen += xfer; + maxlen -= xfer; + break; + case SCSI_PHASE_DATA_OUT: + SCI_ACK(ptr,SCSI_PHASE_DATA_OUT); + xfer = sci_pdma_out (ptr, SCSI_PHASE_DATA_OUT, maxlen, data); + retlen += xfer; + maxlen -= xfer; + break; + case SCSI_PHASE_STATUS: + SCI_ACK(ptr,SCSI_PHASE_STATUS); + sci_data_in(ptr, SCSI_PHASE_STATUS, 1, status); + break; + case SCSI_PHASE_MESSAGE_IN: + SCI_ACK(ptr,SCSI_PHASE_MESSAGE_IN); + sci_data_in(ptr, SCSI_PHASE_MESSAGE_IN, 1, msg); + if (*msg == 0) { + return retlen; + } else { + printf( "message 0x%x in data_xfer.\n", *msg); + } + break; + case SCSI_PHASE_MESSAGE_OUT: + SCI_ACK(ptr,SCSI_PHASE_MESSAGE_OUT); + sci_data_out(ptr, SCSI_PHASE_MESSAGE_OUT, 1, msg); + break; + default: + printf( "Unexpected phase 0x%x in data_xfer().\n", + phase); +scsi_timeout_error: + return retlen; + break; + } + } +} + +static int +scsi_req(P_PAS ptr, int target, int lun, u_char *cmd, int cmdlen, + u_char *databuf, int datalen, int *sent, int *ret) +{ +/* Returns 0 on success, -1 on internal error, or the status byte */ + int cmd_bytes_sent, r; + u_char stat, msg, c; + + *sent = 0; + if ( ( r = select_target(ptr, 7, target, 1) ) != SCSI_RET_SUCCESS) { + *ret = r; + SCI_CLR_INTR(ptr); + switch (r) { + case SCSI_RET_RETRY: +ARGH(); + return 0x08; + default: + printf("select_target(target %d, lun %d) failed(%d).\n", + target, lun, r); + case SCSI_RET_DEVICE_DOWN: + return -1; + } + } + + c = 0x80 | lun; + + if ((cmd_bytes_sent = cmd_xfer(ptr, cmdlen, cmd, &stat, &c)) + != cmdlen) { +ARGH(); + SCI_CLR_INTR(ptr); + *ret = SCSI_RET_COMMAND_FAIL; + printf("Data underrun sending CCB (%d bytes of %d, sent).\n", + cmd_bytes_sent, cmdlen); + return -1; + } + + *sent=data_xfer(ptr, datalen, databuf, &stat, &msg); + + *ret = 0; +#ifdef PAS_DEBUG + printf("scsi_req,stat=0x%x ",stat); +#endif /* PAS_DEBUG */ + return stat; +} + +static int +scsi_group0(int adapter, int id, int lun, + int opcode, int addr, int len, + int flags, caddr_t databuf, int datalen) +{ + P_PAS ptr = s_pas + adapter; + unsigned char cmd[6]; + int i,sent,ret; + + cmd[0] = opcode; /* Operation code */ + cmd[1] = (lun<<5) | ((addr>>16) & 0x1F); /* Lun & MSB of addr */ + cmd[2] = (addr >> 8) & 0xFF; /* addr */ + cmd[3] = addr & 0xFF; /* LSB of addr */ + cmd[4] = len; /* Allocation length */ + cmd[5] = flags; /* Link/Flag */ + + i = scsi_req(ptr, id, lun, cmd, 6, databuf, datalen, &sent, &ret); + + return i; +} + +/* pseudo-dma action */ + +#if 0 && PSEUDO_DMA + +static int pas_debug=1; +#define W1 *byte_data = *data++ +#define W4 *long_data = *((long*)data)++ + +int +sci_pdma_out(ptr, phase, count, data) + P_PAS ptr; + int phase; + int count; + u_char *data; +{ + register volatile long *long_data = sci_4byte_addr; + register volatile u_char *byte_data = sci_1byte_addr; + register int len = count, i; + +pas_debug=1; + + if (count < 128) + return sci_data_out(ptr, phase, count, data); + + WAIT_FOR_BSY(ptr); + M_PAS(ptr,sci_mode, |, SCI_MODE_DMA); + M_PAS(ptr,sci_icmd, |, SCI_ICMD_DATA); + W_PAS(ptr,sci_dma_send,0); + + while ( len >= 64 ) { + READY(1); W1; READY(1); W1; READY(1); W1; READY(1); W1; + READY(1); + W4;W4;W4; W4;W4;W4;W4; W4;W4;W4;W4; W4;W4;W4;W4; + len -= 64; + } + while (len) { + READY(1); + W1; + len--; + } + i = TIMEOUT; + while ( ((R_PAS(ptr,sci_csr) & (SCI_CSR_DREQ|SCI_CSR_PHASE_MATCH)) + == SCI_CSR_PHASE_MATCH) && --i); + if (!i) + printf("pas.c:%d: timeout waiting for SCI_CSR_DREQ.\n", __LINE__); + *byte_data = 0; +scsi_timeout_error: + M_PAS(ptr,sci_mode,&, ~SCI_MODE_DMA); + return count-len; +} + +#undef W1 +#undef W4 + +#define R4 *((long *)data)++ = *long_data +#define R1 *data++ = *byte_data + +int +sci_pdma_in(ptr, phase, count, data) + P_PAS ptr; + int phase; + int count; + u_char *data; +{ + register volatile long *long_data = sci_4byte_addr; + register volatile u_char *byte_data = sci_1byte_addr; + register int len = count, i; + +pas_debug=2; + if (count < 128) + return sci_data_in(ptr, phase, count, data); + +/* printf("Called sci_pdma_in(0x%x, 0x%x, %d, 0x%x.\n", ptr, phase, count, data); */ + + WAIT_FOR_BSY(ptr); + M_PAS(ptr,sci_mode, |, SCI_MODE_DMA); + M_PAS(ptr,sci_icmd, |, SCI_ICMD_DATA); + W_PAS(ptr,sci_irecv, 0); + + while (len >= 1024) { + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 128 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 256 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 384 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 512 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 640 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 768 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 896 */ + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /*1024 */ + len -= 1024; + } + while (len >= 128) { + READY(0); + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; + R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; R4;R4;R4;R4; /* 128 */ + len -= 128; + } + while (len) { + READY(0); + R1; + len--; + } +scsi_timeout_error: + M_PAS(ptr,sci_mode, &, ~SCI_MODE_DMA); + return count - len; +} +#undef R4 +#undef R1 +#endif +#endif /* NPAS */ diff --git a/sys/i386/isa/pcaudio.c b/sys/i386/isa/pcaudio.c new file mode 100644 index 0000000..33358d7 --- /dev/null +++ b/sys/i386/isa/pcaudio.c @@ -0,0 +1,404 @@ +/*- + * Copyright (c) 1994 Søren Schmidt + * 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. The name of the author may not be used to endorse or promote products + * derived from this software withough specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR 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. + * + * $Id: pcaudio.c,v 1.3 1994/05/20 12:24:15 sos Exp $ + */ + +#include "pca.h" +#if NPCA > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/uio.h> +#include <sys/ioctl.h> +#include <machine/cpufunc.h> +#include <machine/pio.h> +#include <machine/pcaudioio.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/timerreg.h> +#include <i386/isa/sound/ulaw.h> + +#define BUF_SIZE 8192 +#define SAMPLE_RATE 8000 +#define INTERRUPT_RATE 16000 + +static struct pca_status { + char open; /* device open */ + char queries; /* did others try opening */ + unsigned char *buf[2]; /* double buffering */ + unsigned char *buffer; /* current buffer ptr */ + unsigned in_use[2]; /* buffers fill */ + unsigned index; /* index in current buffer */ + unsigned counter; /* sample counter */ + unsigned scale; /* sample counter scale */ + unsigned sample_rate; /* sample rate */ + unsigned processed; /* samples processed */ + unsigned volume; /* volume for pc-speaker */ + char encoding; /* Ulaw, Alaw or linear */ + char current; /* current buffer */ + unsigned char oldval; /* old timer port value */ + char timer_on; /* is playback running */ +} pca_status; + +static char buffer1[BUF_SIZE]; +static char buffer2[BUF_SIZE]; +static char volume_table[256]; + +static int pca_sleep = 0; +static int pca_initialized = 0; + +void pcaintr(int regs); +int pcaprobe(struct isa_device *dvp); +int pcaattach(struct isa_device *dvp); +int pcaclose(dev_t dev, int flag); +int pcaopen(dev_t dev, int flag); +int pcawrite(dev_t dev, struct uio *uio, int flag); +int pcaioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p); + +struct isa_driver pcadriver = { + pcaprobe, pcaattach, "pca", +}; + + +inline void conv(const void *table, void *buff, unsigned long n) +{ + __asm__("1:\tmovb (%2), %3\n" + "\txlatb\n" + "\tmovb %3, (%2)\n" + "\tinc %2\n" + "\tdec %1\n" + "\tjnz 1b\n" + : + :"b" ((long)table), "c" (n), "D" ((long)buff), "a" ((char)n) + :"bx","cx","di","ax"); +} + + +static void +pca_volume(int volume) +{ + int i, j; + + for (i=0; i<256; i++) { + j = ((i-128)*volume)/100; + if (j<-128) + j = -128; + if (j>127) + j = 127; + volume_table[i] = (((255-(j + 128))/4)+1); + } +} + + +static void +pca_init() +{ + pca_status.open = 0; + pca_status.queries = 0; + pca_status.timer_on = 0; + pca_status.buf[0] = (unsigned char *)&buffer1[0]; + pca_status.buf[1] = (unsigned char *)&buffer2[0]; + pca_status.buffer = pca_status.buf[0]; + pca_status.in_use[0] = pca_status.in_use[1] = 0; + pca_status.current = 0; + pca_status.sample_rate = SAMPLE_RATE; + pca_status.scale = (pca_status.sample_rate << 8) / INTERRUPT_RATE; + pca_status.encoding = AUDIO_ENCODING_ULAW; + pca_status.volume = 100; + + pca_volume(pca_status.volume); +} + + +static int +pca_start(void) +{ + /* use the first buffer */ + pca_status.current = 0; + pca_status.index = 0; + pca_status.counter = 0; + pca_status.buffer = pca_status.buf[pca_status.current]; + pca_status.oldval = inb(IO_PPI) | 0x03; + /* acquire the timers */ + if (acquire_timer2(TIMER_LSB|TIMER_ONESHOT)) { + return -1; + } + if (acquire_timer0(INTERRUPT_RATE, pcaintr)) { + release_timer2(); + return -1; + } + pca_status.timer_on = 1; + return 0; +} + + +static void +pca_stop(void) +{ + /* release the timers */ + release_timer0(); + release_timer2(); + /* reset the buffer */ + pca_status.in_use[0] = pca_status.in_use[1] = 0; + pca_status.index = 0; + pca_status.counter = 0; + pca_status.current = 0; + pca_status.buffer = pca_status.buf[pca_status.current]; + pca_status.timer_on = 0; +} + + +static void +pca_pause() +{ + release_timer0(); + release_timer2(); + pca_status.timer_on = 0; +} + + +static void +pca_continue() +{ + pca_status.oldval = inb(IO_PPI) | 0x03; + acquire_timer2(TIMER_LSB|TIMER_ONESHOT); + acquire_timer0(INTERRUPT_RATE, pcaintr); + pca_status.timer_on = 1; +} + + +static void +pca_wait(void) +{ + while (pca_status.in_use[0] || pca_status.in_use[1]) { + pca_sleep = 1; + tsleep((caddr_t)&pca_sleep, PZERO|PCATCH, "pca_drain", 0); + } +} + + +int +pcaprobe(struct isa_device *dvp) +{ + return(-1); +} + + +int +pcaattach(struct isa_device *dvp) +{ + printf(" PCM audio driver\n", dvp->id_unit); + pca_init(); + return 1; +} + + +int +pcaopen(dev_t dev, int flag) +{ + /* audioctl device can always be opened */ + if (minor(dev) == 128) + return 0; + if (minor(dev) > 0) + return ENXIO; + + if (!pca_initialized) { + pca_init(); + pca_initialized = 1; + } + + /* audio device can only be open by one process */ + if (pca_status.open) { + pca_status.queries = 1; + return EBUSY; + } + pca_status.buffer = pca_status.buf[0]; + pca_status.in_use[0] = pca_status.in_use[1] = 0; + pca_status.timer_on = 0; + pca_status.open = 1; + pca_status.processed = 0; + return 0; +} + + +int +pcaclose(dev_t dev, int flag) +{ + /* audioctl device can always be closed */ + if (minor(dev) == 128) + return 0; + if (minor(dev) > 0) + return ENXIO; + /* audio device close drains all output and restores timers */ + pca_wait(); + pca_stop(); + pca_status.open = 0; + return 0; +} + + +int +pcawrite(dev_t dev, struct uio *uio, int flag) +{ + int count, which; + + /* only audio device can be written */ + if (minor(dev) > 0) + return ENXIO; + + while ((count = min(BUF_SIZE, uio->uio_resid)) > 0) { + which = pca_status.in_use[0] ? 1 : 0; + if (count && !pca_status.in_use[which]) { + uiomove(pca_status.buf[which], count, uio); + pca_status.processed += count; + switch (pca_status.encoding) { + case AUDIO_ENCODING_ULAW: + conv(ulaw_dsp, pca_status.buf[which], count); + break; + + case AUDIO_ENCODING_ALAW: + break; + + case AUDIO_ENCODING_RAW: + break; + } + pca_status.in_use[which] = count; + if (!pca_status.timer_on) + if (pca_start()) + return EBUSY; + } + if (pca_status.in_use[0] && pca_status.in_use[1]) { + pca_sleep = 1; + tsleep((caddr_t)&pca_sleep, PZERO|PCATCH, "pca_wait",0); + } + } + return 0; +} + + +int +pcaioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p) +{ +audio_info_t *auptr; + + switch(cmd) { + + case AUDIO_GETINFO: + auptr = (audio_info_t *)data; + auptr->play.sample_rate = pca_status.sample_rate; + auptr->play.channels = 1; + auptr->play.precision = 8; + auptr->play.encoding = pca_status.encoding; + + auptr->play.gain = pca_status.volume; + auptr->play.port = 0; + + auptr->play.samples = pca_status.processed; + auptr->play.eof = 0; + auptr->play.pause = !pca_status.timer_on; + auptr->play.error = 0; + auptr->play.waiting = pca_status.queries; + + auptr->play.open = pca_status.open; + auptr->play.active = pca_status.timer_on; + return 0; + + case AUDIO_SETINFO: + auptr = (audio_info_t *)data; + if (auptr->play.sample_rate != (unsigned int)~0) { + pca_status.sample_rate = auptr->play.sample_rate; + pca_status.scale = + (pca_status.sample_rate << 8) / INTERRUPT_RATE; + } + if (auptr->play.encoding != (unsigned int)~0) { + pca_status.encoding = auptr->play.encoding; + } + if (auptr->play.gain != (unsigned int)~0) { + pca_status.volume = auptr->play.gain; + pca_volume(pca_status.volume); + } + if (auptr->play.pause != (unsigned char)~0) { + if (auptr->play.pause) + pca_pause(); + else + pca_continue(); + } + + return 0; + + case AUDIO_DRAIN: + pca_wait(); + return 0; + + case AUDIO_FLUSH: + pca_stop(); + return 0; + + } + return ENXIO; +} + + +void +pcaintr(int regs) +{ + if (pca_status.index < pca_status.in_use[pca_status.current]) { +#if 1 + disable_intr(); + __asm__("outb %0,$0x61\n" + "andb $0xFE,%0\n" + "outb %0,$0x61" + : : "a" ((char)pca_status.oldval) ); + __asm__("xlatb\n" + "outb %0,$0x42" + : : "a" ((char)pca_status.buffer[pca_status.index]), + "b" ((long)volume_table) ); + enable_intr(); +#else + disable_intr(); + outb(IO_PPI, pca_status.oldval); + outb(IO_PPI, pca_status.oldval & 0xFE); + outb(TIMER_CNTR2, + volume_table[pca_status.buffer[pca_status.index]]); + enable_intr(); +#endif + pca_status.counter += pca_status.scale; + pca_status.index = (pca_status.counter >> 8); + } + else { + pca_status.index = pca_status.counter = 0; + pca_status.in_use[pca_status.current] = 0; + pca_status.current ^= 1; + pca_status.buffer = pca_status.buf[pca_status.current]; + if (pca_sleep) { + wakeup((caddr_t)&pca_sleep); + pca_sleep = 0; + } + } +} + +#endif diff --git a/sys/i386/isa/rtc.h b/sys/i386/isa/rtc.h new file mode 100644 index 0000000..9c0f501 --- /dev/null +++ b/sys/i386/isa/rtc.h @@ -0,0 +1,109 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * from: @(#)rtc.h 7.1 (Berkeley) 5/12/91 + * $Id: rtc.h,v 1.4 1993/12/18 01:12:47 ache Exp $ + */ + +#ifndef _I386_ISA_RTC_H_ +#define _I386_ISA_RTC_H_ 1 + +/* + * RTC Register locations + */ + +#define RTC_SEC 0x00 /* seconds */ +#define RTC_SECALRM 0x01 /* seconds alarm */ +#define RTC_MIN 0x02 /* minutes */ +#define RTC_MINALRM 0x03 /* minutes alarm */ +#define RTC_HRS 0x04 /* hours */ +#define RTC_HRSALRM 0x05 /* hours alarm */ +#define RTC_WDAY 0x06 /* week day */ +#define RTC_DAY 0x07 /* day of month */ +#define RTC_MONTH 0x08 /* month of year */ +#define RTC_YEAR 0x09 /* month of year */ + +#define RTC_STATUSA 0x0a /* status register A */ +#define RTCSA_TUP 0x80 /* time update, don't look now */ +#define RTCSA_DIVIDER 0x20 /* divider correct for 32768 Hz */ +#define RTCSA_8192 0x03 +#define RTCSA_4096 0x04 +#define RTCSA_2048 0x05 +#define RTCSA_1024 0x06 /* default for profiling */ +#define RTCSA_PROF RTCSA_1024 +#define RTC_PROFRATE 1024 +#define RTCSA_512 0x07 +#define RTCSA_256 0x08 +#define RTCSA_128 0x09 +#define RTCSA_NOPROF RTCSA_128 +#define RTC_NOPROFRATE 128 +#define RTCSA_64 0x0a +#define RTCSA_32 0x0b + +#define RTC_STATUSB 0x0b /* status register B */ +#define RTCSB_HALT 0x80 /* stop clock updates */ +#define RTCSB_PINTR 0x40 /* periodic clock interrupt */ +#define RTCSB_24HR 0x02 /* 24-hour mode */ + +#define RTC_INTR 0x0c /* status register C (R) interrupt source */ +#define RTCIR_UPDATE 0x10 /* update intr */ +#define RTCIR_ALARM 0x20 /* alarm intr */ +#define RTCIR_PERIOD 0x40 /* periodic intr */ +#define RTCIR_INT 0x80 /* interrupt output signal */ + +#define RTC_STATUSD 0x0d /* status register D (R) Lost Power */ +#define RTCSD_PWR 0x80 /* clock lost power */ + +#define RTC_DIAG 0x0e /* status register E - bios diagnostic */ +#define RTCDG_BITS "\020\010clock_battery\007ROM_cksum\006config_unit\005memory_size\004fixed_disk\003invalid_time" + +#define RTC_RESET 0x0f /* status register F - reset code byte */ +#define RTCRS_RST 0x00 /* normal reset */ +#define RTCRS_LOAD 0x04 /* load system */ + +#define RTC_FDISKETTE 0x10 /* diskette drive type in upper/lower nibble */ +#define RTCFDT_NONE 0 /* none present */ +#define RTCFDT_360K 0x10 /* 360K */ +#define RTCFDT_12M 0x20 /* 1.2M */ +#define RTCFDT_720K 0x30 /* 720K */ +#define RTCFDT_144M 0x40 /* 1.44M */ + +#define RTC_BASELO 0x15 /* low byte of basemem size */ +#define RTC_BASEHI 0x16 /* high byte of basemem size */ +#define RTC_EXTLO 0x17 /* low byte of extended mem size */ +#define RTC_EXTHI 0x18 /* low byte of extended mem size */ + +#define RTC_CENTURY 0x32 /* current century - please increment in Dec99*/ +#endif /* _I386_ISA_RTC_H_ */ diff --git a/sys/i386/isa/sio.c b/sys/i386/isa/sio.c new file mode 100644 index 0000000..99f913f --- /dev/null +++ b/sys/i386/isa/sio.c @@ -0,0 +1,1957 @@ +/*- + * 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. + * + * from: @(#)com.c 7.5 (Berkeley) 5/16/91 + * $Id: sio.c,v 1.50 1994/08/23 07:52:23 paul Exp $ + */ + +#include "sio.h" +#if NSIO > 0 +/* + * Serial driver, based on 386BSD-0.1 com driver. + * Mostly rewritten to use pseudo-DMA. + * Works for National Semiconductor NS8250-NS16550AF UARTs. + * COM driver, based on HP dca driver. + */ +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioctl.h> +#define TTYDEFCHARS /* XXX TK2.0 */ +#include <sys/tty.h> +#undef TTYDEFCHARS +#include <sys/proc.h> +#include <sys/user.h> +#include <sys/conf.h> +#include <sys/dkstat.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/syslog.h> + +#include <i386/isa/icu.h> /* XXX just to get at `imen' */ +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/sioreg.h> +#include <i386/isa/ic/ns16550.h> + +/* + * XXX temporary kludges for 2.0 (XXX TK2.0). + */ +#define TS_RTS_IFLOW 0 +#define TSA_CARR_ON(tp) ((void *)&(tp)->t_rawq) +#define TSA_OCOMPLETE(tp) ((void *)&(tp)->t_outq) +#define TSA_OLOWAT(tp) ((void *)&(tp)->t_outq) +void +termioschars(t) + struct termios *t; +{ + + bcopy(ttydefchars, t->c_cc, sizeof t->c_cc); +} + +#define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ +#define RB_I_HIGH_WATER (TTYHOG - 2 * RS_IBUFSIZE) +#define RS_IBUFSIZE 256 +#define TTY_BI TTY_FE /* XXX */ +#define TTY_OE TTY_PE /* XXX */ + +#define CALLOUT_MASK 0x80 +#define CONTROL_MASK 0x60 +#define CONTROL_INIT_STATE 0x20 +#define CONTROL_LOCK_STATE 0x40 +#define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) +#define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) +#define MINOR_TO_UNIT(mynor) ((mynor) & ~MINOR_MAGIC_MASK) + +#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) +#define COM_NOTAST4(dev) ((dev)->id_flags & 0x04) +#endif /* COM_MULTIPORT */ + +#define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) +#define COM_VERBOSE(dev) ((dev)->id_flags & 0x80) + +#define com_scr 7 /* scratch register for 16450-16550 (R/W) */ + +/* + * 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. + * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. + * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). + */ +#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_DTR_OFF 0x10 /* DTR held off */ +#define CS_ODONE 4 /* output completed */ +#define CS_RTS_IFLOW 8 /* use RTS input flow control */ + +static char const * const 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 u_char bool_t; /* boolean */ + +/* com device structure */ +struct com_s { + u_char state; /* miscellaneous flag bits */ + bool_t active_out; /* nonzero if the callout device is open */ + u_char cfcr_image; /* copy of value written to CFCR */ + u_char ftl; /* current rx fifo trigger level */ + u_char ftl_init; /* ftl_max for next open() */ + u_char ftl_max; /* maximum ftl for curent open() */ + bool_t hasfifo; /* nonzero for 16550 UARTs */ + u_char mcr_image; /* copy of value written to MCR */ +#ifdef COM_MULTIPORT + bool_t multiport; /* is this unit part of a multiport device? */ +#endif /* COM_MULTIPORT */ + int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ + u_int tx_fifo_size; + u_int wopeners; /* # processes waiting for DCD in open() */ + + /* + * 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 hotchar; /* ldisc-specific char to be handled ASAP */ + 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 */ + 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 */ + + /* Initial state. */ + struct termios it_in; /* should be in struct tty */ + struct termios it_out; + + /* Lock state. */ + struct termios lt_in; /* should be in struct tty */ + struct termios lt_out; + +#ifdef TIOCTIMESTAMP + bool_t do_timestamp; + struct timeval timestamp; +#endif + + 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]; + + /* + * Output buffer. Someday we should avoid copying. Twice. + */ + u_char obuf[256]; +}; + +/* + * The public functions in the com module ought to be declared in a com-driver + * system header. + */ + +/* Interrupt handling entry points. */ +void siointr __P((int unit)); +void siopoll __P((void)); + +/* Device switch entry points. */ +int sioopen __P((dev_t dev, int oflags, int devtype, + struct proc *p)); +int sioclose __P((dev_t dev, int fflag, int devtype, + struct proc *p)); +int sioread __P((dev_t dev, struct uio *uio, int ioflag)); +int siowrite __P((dev_t dev, struct uio *uio, int ioflag)); +int sioioctl __P((dev_t dev, int cmd, caddr_t data, + int fflag, struct proc *p)); +void siostop __P((struct tty *tp, int rw)); +#define sioreset noreset +int sioselect __P((dev_t dev, int rw, struct proc *p)); +#define siommap nommap +#define siostrategy nostrategy + +/* Console device entry points. */ +int siocngetc __P((dev_t dev)); +struct consdev; +void siocninit __P((struct consdev *cp)); +void siocnprobe __P((struct consdev *cp)); +void siocnputc __P((dev_t dev, int c)); + +static int sioattach __P((struct isa_device *dev)); +static timeout_t siodtrwakeup; +static void comflush __P((struct com_s *com)); +static void comhardclose __P((struct com_s *com)); +static void siointr1 __P((struct com_s *com)); +static void 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 comstart __P((struct tty *tp)); +static timeout_t comwakeup; +static int tiocm_xxx2mcr __P((int tiocm_xxx)); + +/* 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]) + +#ifdef TIOCTIMESTAMP +static struct timeval intr_timestamp; +#endif + +struct isa_driver siodriver = { + sioprobe, sioattach, "sio" +}; + +#ifdef COMCONSOLE +static int comconsole = COMCONSOLE; +#else +static int comconsole = -1; +#endif +static speed_t comdefaultrate = TTYDEF_SPEED; +static u_int com_events; /* input chars + weighted output completions */ +static int commajor; +#if 0 /* XXX TK2.0 */ +struct tty *sio_tty[NSIO]; +#else +struct tty sio_tty[NSIO]; +#endif +extern struct tty *constty; /* 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; + bool_t failures[10]; + int fn; + struct isa_device *idev; + Port_t iobase; + u_char mcr_image; + 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. + * XXX the gate enable is elsewhere for some multiports. + */ + 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; + } + + /* + * If the port is on a multiport card and has a master port, + * initialize the common interrupt control register in the + * master and prepare to leave MCR_IENABLE clear in the mcr. + * Otherwise, prepare to set MCR_IENABLE in the mcr. + * Point idev to the device struct giving the correct id_irq. + * This is the struct for the master device if there is one. + */ + idev = dev; + mcr_image = MCR_IENABLE; +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(dev)) { + idev = find_isadev(isa_devtab_tty, &siodriver, + COM_MPMASTER(dev)); + if (idev == NULL) { + printf("sio%d: master device %d not found\n", + dev->id_unit, COM_MPMASTER(dev)); + return (0); + } + if (idev->id_irq == 0) { + printf("sio%d: master device %d irq not configured\n", + dev->id_unit, COM_MPMASTER(dev)); + return (0); + } + if (!COM_NOTAST4(dev)) { + outb(idev->id_iobase + com_scr, 0x80); + mcr_image = 0; + } + } + else +#endif /* COM_MULTIPORT */ + if (idev->id_irq == 0) { + printf("sio%d: irq not configured\n", dev->id_unit); + return (0); + } + + bzero(failures, sizeof failures); + iobase = dev->id_iobase; + + /* + * 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(); + + /* + * XXX DELAY() reenables CPU interrupts. This is a problem for + * shared interrupts after the first device using one has been + * successfully probed - config_isadev() has enabled the interrupt + * in the ICU. + */ + outb(IO_ICU1 + 1, 0xff); + + /* + * Initialize the speed and the word size and wait long enough to + * drain the maximum of 16 bytes of junk in device output queues. + * The speed is undefined after a master reset and must be set + * before relying on anything related to output. There may be + * junk after a (very fast) soft reboot and (apparently) after + * master reset. + * XXX what about the UART bug avoided by waiting in comparam()? + * We don't want to to wait long enough to drain at 2 bps. + */ + outb(iobase + com_cfcr, CFCR_DLAB); + outb(iobase + com_dlbl, COMBRD(9600) & 0xff); + outb(iobase + com_dlbh, (u_int) COMBRD(9600) >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + DELAY((16 + 1) * 9600 / 10); + + /* + * Enable the interrupt gate and disable device interupts. This + * should leave the device driving the interrupt line low and + * guarantee an edge trigger if an interrupt can be generated. + */ + outb(iobase + com_mcr, mcr_image); + outb(iobase + com_ier, 0); + + /* + * Attempt to set loopback mode so that we can send a null byte + * without annoying any external device. + */ + outb(iobase + com_mcr, mcr_image | MCR_LOOPBACK); + + /* + * Attempt to generate an output interrupt. On 8250's, setting + * IER_ETXRDY generates an interrupt independent of the current + * setting and independent of whether the THR is empty. On 16450's, + * setting IER_ETXRDY generates an interrupt independent of the + * current setting. On 16550A's, setting IER_ETXRDY only + * generates an interrupt when IER_ETXRDY is not already set. + */ + outb(iobase + com_ier, IER_ETXRDY); + + /* + * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate + * an interrupt. They'd better generate one for actually doing + * output. Loopback may be broken on the same incompatibles but + * it's unlikely to do more than allow the null byte out. + */ + outb(iobase + com_data, 0); + DELAY((2 + 1) * 9600 / 10); + + /* + * Turn off loopback mode so that the interrupt gate works again + * (MCR_IENABLE was hidden). This should leave the device driving + * an interrupt line high. It doesn't matter if the interrupt + * line oscillates while we are not looking at it, since interrupts + * are disabled. + */ + outb(iobase + com_mcr, mcr_image); + + /* + * Check that + * 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. + */ + failures[0] = inb(iobase + com_cfcr) - CFCR_8BITS; + failures[1] = inb(iobase + com_ier) - IER_ETXRDY; + failures[2] = inb(iobase + com_mcr) - mcr_image; + if (idev->id_irq != 0) + failures[3] = isa_irq_pending(idev) ? 0 : 1; + failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; + failures[5] = isa_irq_pending(idev) ? 1 : 0; + failures[6] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; + + /* + * Turn off all device interrupts and check that they go off properly. + * Leave MCR_IENABLE alone. For ports without a master port, 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_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ + failures[7] = inb(iobase + com_ier); + failures[8] = isa_irq_pending(idev) ? 1 : 0; + failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; + + outb(IO_ICU1 + 1, imen); /* XXX */ + enable_intr(); + + result = IO_COMSIZE; + for (fn = 0; fn < sizeof failures; ++fn) + if (failures[fn]) { + outb(iobase + com_mcr, 0); + result = 0; + if (COM_VERBOSE(dev)) + printf("sio%d: probe test %d failed\n", + dev->id_unit, fn); + } + return (result); +} + +static int +sioattach(isdp) + struct isa_device *isdp; +{ + struct com_s *com; + static bool_t comwakeup_started = FALSE; + Port_t iobase; + int s; + int unit; + + isdp->id_ri_flags |= RI_FAST; + iobase = isdp->id_iobase; + unit = isdp->id_unit; + com = malloc(sizeof *com, M_TTYS, M_NOWAIT); + if (com == NULL) + return (0); + + /* + * 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, or 0 if the port has a master port. + * Keeping MCR_DTR and MCR_RTS off might stop the external + * device from sending before we are ready. + */ + bzero(com, sizeof *com); + com->cfcr_image = CFCR_8BITS; + com->dtr_wait = 3 * hz; + com->tx_fifo_size = 1; + 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->mcr_image = inb(com->modem_ctl_port); + com->line_status_port = iobase + com_lsr; + com->modem_status_port = iobase + com_msr; + + /* + * We don't use all the flags from <sys/ttydefaults.h> since they + * 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. + */ + com->it_in.c_iflag = 0; + com->it_in.c_oflag = 0; + com->it_in.c_cflag = TTYDEF_CFLAG; + com->it_in.c_lflag = 0; + if (unit == comconsole) { + com->it_in.c_iflag = TTYDEF_IFLAG; + com->it_in.c_oflag = TTYDEF_OFLAG; + com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; + com->it_in.c_lflag = TTYDEF_LFLAG; + com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; + } + termioschars(&com->it_in); + com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; + com->it_out = com->it_in; + + /* attempt to determine UART type */ + printf("sio%d: type", unit); +#ifdef COM_MULTIPORT + if (!COM_ISMULTIPORT(isdp)) +#endif + { + u_char scr; + u_char scr1; + u_char scr2; + + 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"); + goto determined_type; + } + } + outb(iobase + com_fifo, FIFO_ENABLE | FIFO_TRIGGER_14); + DELAY(100); + switch (inb(com->int_id_port) & 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: + printf(" 16550A"); + if (COM_NOFIFO(isdp)) + printf(" fifo disabled"); + else { + com->hasfifo = TRUE; + com->ftl_init = FIFO_TRIGGER_14; + com->tx_fifo_size = 16; + } + break; + } + outb(iobase + com_fifo, 0); +determined_type: ; + +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(isdp)) { + com->multiport = TRUE; + printf(" (multiport"); + if (unit == COM_MPMASTER(isdp)) + printf(" master"); + printf(")"); + } +#endif /* COM_MULTIPORT */ + printf("\n"); + +#ifdef KGDB + if (kgdb_dev == makedev(commajor, unit)) { + if (unit == comconsole) + kgdb_dev = -1; /* can't debug over console port */ + else { + int divisor; + + /* + * XXX now unfinished and broken. Need to do + * something more like a full open(). There's no + * suitable interrupt handler so don't enable device + * interrupts. Watch out for null tp's. + */ + outb(iobase + com_cfcr, CFCR_DLAB); + divisor = ttspeedtab(kgdb_rate, comspeedtab); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + outb(com->modem_status_port, + com->mcr_image |= MCR_DTR | MCR_RTS); + + if (kgdb_debug_init) { + /* + * Print prefix of device name, + * let kgdb_connect print the rest. + */ + printf("sio%d: ", unit); + kgdb_connect(1); + } else + printf("sio%d: kgdb enabled\n", unit); + } + } +#endif + + s = spltty(); + com_addr(unit) = com; + splx(s); + if (!comwakeup_started) { + comwakeup((void *)NULL); + 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; + Port_t iobase; + int mynor; + int s; + struct tty *tp; + int unit; + + mynor = minor(dev); + unit = MINOR_TO_UNIT(mynor); + if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) + return (ENXIO); + if (mynor & CONTROL_MASK) + return (0); +#if 0 /* XXX TK2.0 */ + tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); +#else + tp = com->tp = &sio_tty[unit]; +#endif + s = spltty(); + /* + * We jump to this label after all non-interrupted sleeps to pick + * up any changes of the device state. + */ +open_top: + while (com->state & CS_DTR_OFF) { + error = tsleep(&com->dtr_wait, TTIPRI | PCATCH, "siodtr", 0); + if (error != 0) + goto out; + } + if (tp->t_state & TS_ISOPEN) { + /* + * The device is open, so everything has been initialized. + * Handle conflicts. + */ + if (mynor & CALLOUT_MASK) { + if (!com->active_out) { + error = EBUSY; + goto out; + } + } else { + if (com->active_out) { + if (flag & O_NONBLOCK) { + error = EBUSY; + goto out; + } + error = tsleep(&com->active_out, + TTIPRI | PCATCH, "siobi", 0); + if (error != 0) + goto out; + goto open_top; + } + } + if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { + error = EBUSY; + goto out; + } + } else { + /* + * The device isn't open, so there are no conflicts. + * Initialize it. Initialization is done twice in many + * cases: to preempt sleeping callin opens if we are + * callout, and to complete a callin open after DCD rises. + */ + tp->t_oproc = comstart; + tp->t_param = comparam; + tp->t_dev = dev; + tp->t_termios = mynor & CALLOUT_MASK + ? com->it_out : com->it_in; + commctl(com, MCR_DTR | MCR_RTS, DMSET); + com->ftl_max = com->ftl_init; + ++com->wopeners; + error = comparam(tp, &tp->t_termios); + --com->wopeners; + if (error != 0) + goto out; + /* + * XXX we should goto open_top if comparam() slept. + */ + ttsetwater(tp); + iobase = com->iobase; + if (com->hasfifo) { + /* Drain fifo. */ + outb(iobase + com_fifo, + FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST + | com->ftl); + DELAY(100); + } + disable_intr(); + (void) inb(com->line_status_port); + (void) inb(com->data_port); + com->prev_modem_status = + com->last_modem_status = inb(com->modem_status_port); + outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS + | IER_EMSC); + enable_intr(); + /* + * Handle initial DCD. Callout devices get a fake initial + * DCD (trapdoor DCD). If we are callout, then any sleeping + * callin opens get woken up and resume sleeping on "siobi" + * instead of "siodcd". + */ + if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) + (*linesw[tp->t_line].l_modem)(tp, 1); + } + /* + * Wait for DCD if necessary. + */ + if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) + && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { + ++com->wopeners; + error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); + --com->wopeners; + if (error != 0) + goto out; + goto open_top; + } + error = (*linesw[tp->t_line].l_open)(dev, tp); + if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) + com->active_out = TRUE; +out: + splx(s); + if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) + comhardclose(com); + return (error); +} + +/*ARGSUSED*/ +int +sioclose(dev, flag, mode, p) + dev_t dev; + int flag; + int mode; + struct proc *p; +{ + struct com_s *com; + int mynor; + int s; + struct tty *tp; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (0); + com = com_addr(MINOR_TO_UNIT(mynor)); + tp = com->tp; + s = spltty(); + (*linesw[tp->t_line].l_close)(tp, flag); + siostop(tp, FREAD | FWRITE); + comhardclose(com); + ttyclose(tp); + splx(s); + return (0); +} + +static void +comhardclose(com) + struct com_s *com; +{ + Port_t iobase; + int s; + struct tty *tp; + int unit; + + unit = DEV_TO_UNIT(com->tp->t_dev); + iobase = com->iobase; + s = spltty(); +#ifdef TIOCTIMESTAMP + com->do_timestamp = 0; +#endif + outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); +#ifdef KGDB + /* do not disable interrupts or hang up if debugging */ + if (kgdb_dev != makedev(commajor, unit)) +#endif + { + outb(iobase + com_ier, 0); + tp = com->tp; + if (tp->t_cflag & HUPCL + /* + * XXX we will miss any carrier drop between here and the + * next open. Perhaps we should watch DCD even when the + * port is closed; it is not sufficient to check it at + * the next open because it might go up and down while + * we're not watching. + */ + || !com->active_out + && !(com->prev_modem_status & MSR_DCD) + && !(com->it_in.c_cflag & CLOCAL) + || !(tp->t_state & TS_ISOPEN)) { + commctl(com, MCR_RTS, DMSET); + if (com->dtr_wait != 0) { + timeout(siodtrwakeup, com, com->dtr_wait); + com->state |= CS_DTR_OFF; + } + } + } + com->active_out = FALSE; + wakeup(&com->active_out); + wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ + splx(s); +} + +int +sioread(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + int mynor; + struct tty *tp; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (ENODEV); + tp = com_addr(MINOR_TO_UNIT(mynor))->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 mynor; + struct tty *tp; + int unit; + + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (ENODEV); + unit = MINOR_TO_UNIT(mynor); + 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)); +} + +static void +siodtrwakeup(chan) + void *chan; +{ + struct com_s *com; + + com = (struct com_s *)chan; + com->state &= ~CS_DTR_OFF; + wakeup(&com->dtr_wait); +} + +#ifdef TIOCTIMESTAMP +/* Interrupt routine for timekeeping purposes */ +void +siointrts(unit) + int unit; +{ + microtime(&intr_timestamp); + siointr(unit); +} +#endif + +void +siointr(unit) + int unit; +{ +#ifndef COM_MULTIPORT + siointr1(com_addr(unit)); +#else /* COM_MULTIPORT */ + struct com_s *com; + bool_t possibly_more_intrs; + + /* + * Loop until there is no activity on any port. This is necessary + * to get an interrupt edge more than to avoid another interrupt. + * If the IRQ signal is just an OR of the IRQ signals from several + * devices, then the edge from one may be lost because another is + * on. + */ + do { + possibly_more_intrs = FALSE; + for (unit = 0; unit < NSIO; ++unit) { + com = com_addr(unit); + if (com != NULL + && (inb(com->int_id_port) & IIR_IMASK) + != IIR_NOPEND) { + siointr1(com); + possibly_more_intrs = TRUE; + } + } + } while (possibly_more_intrs); +#endif /* COM_MULTIPORT */ +} + +static void +siointr1(com) + struct com_s *com; +{ + u_char line_status; + u_char modem_status; + u_char *ioptr; + u_char recv_data; + +#ifdef TIOCTIMESTAMP + if (com->do_timestamp) + /* XXX a little bloat here... */ + com->timestamp = intr_timestamp; +#endif + 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? */ + if (!(line_status & LSR_RXRDY)) + recv_data = 0; + else + recv_data = inb(com->data_port); + ++com->bytes_in; + if (com->hotchar != 0 && recv_data == com->hotchar) + setsofttty(); +#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; +#if 0 /* for testing input latency vs efficiency */ +if (com->iptr - com->ibuf == 8) + setsofttty(); +#endif + 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); + /* XXX - move this out of isr */ + if (line_status & LSR_OE) + CE_RECORD(com, CE_OVERRUN); + } + + /* + * "& 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. + */ + com->last_modem_status = modem_status; + if (!(com->state & CS_CHECKMSR)) { + com_events += LOTS_OF_EVENTS; + com->state |= CS_CHECKMSR; + setsofttty(); + } + + /* 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)) { + ioptr = com->optr; + if (com->tx_fifo_size > 1) { + u_int ocount; + + ocount = com->obufend - ioptr; + if (ocount > com->tx_fifo_size) + ocount = com->tx_fifo_size; + com->bytes_out += ocount; + do + outb(com->data_port, *ioptr++); + while (--ocount != 0); + } else { + 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); + setsofttty(); /* handle at high level ASAP */ + } + } + + /* finished? */ +#ifndef COM_MULTIPORT + if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) +#endif /* COM_MULTIPORT */ + return; + } +} + +static int +tiocm_xxx2mcr(tiocm_xxx) + int tiocm_xxx; +{ + int mcr; + + mcr = 0; + if (tiocm_xxx & TIOCM_DTR) + mcr |= MCR_DTR; + if (tiocm_xxx & TIOCM_RTS) + mcr |= MCR_RTS; + return (mcr); +} + +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 mcr; + int msr; + int mynor; + int s; + int tiocm_xxx; + struct tty *tp; + + mynor = minor(dev); + com = com_addr(MINOR_TO_UNIT(mynor)); + if (mynor & CONTROL_MASK) { + struct termios *ct; + + switch (mynor & CONTROL_MASK) { + case CONTROL_INIT_STATE: + ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; + break; + case CONTROL_LOCK_STATE: + ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; + break; + default: + return (ENODEV); /* /dev/nodev */ + } + switch (cmd) { + case TIOCSETA: + error = suser(p->p_ucred, &p->p_acflag); + if (error) + return (error); + *ct = *(struct termios *)data; + return (0); + case TIOCGETA: + *(struct termios *)data = *ct; + return (0); + case TIOCGETD: + *(int *)data = TTYDISC; + return (0); + case TIOCGWINSZ: + bzero(data, sizeof(struct winsize)); + return (0); + default: + return (ENOTTY); + } + } + tp = com->tp; + if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { + int cc; + struct termios *dt = (struct termios *)data; + struct termios *lt = mynor & CALLOUT_MASK + ? &com->lt_out : &com->lt_in; + + dt->c_iflag = (tp->t_iflag & lt->c_iflag) + | (dt->c_iflag & ~lt->c_iflag); + dt->c_oflag = (tp->t_oflag & lt->c_oflag) + | (dt->c_oflag & ~lt->c_oflag); + dt->c_cflag = (tp->t_cflag & lt->c_cflag) + | (dt->c_cflag & ~lt->c_cflag); + dt->c_lflag = (tp->t_lflag & lt->c_lflag) + | (dt->c_lflag & ~lt->c_lflag); + for (cc = 0; cc < NCCS; ++cc) + if (lt->c_cc[cc] != 0) + dt->c_cc[cc] = tp->t_cc[cc]; + if (lt->c_ispeed != 0) + dt->c_ispeed = tp->t_ispeed; + if (lt->c_ospeed != 0) + dt->c_ospeed = tp->t_ospeed; + } + error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); + 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: + commctl(com, MCR_DTR, DMBIS); + break; + case TIOCCDTR: + commctl(com, MCR_DTR, DMBIC); + break; + case TIOCMSET: + commctl(com, tiocm_xxx2mcr(*(int *)data), DMSET); + break; + case TIOCMBIS: + commctl(com, tiocm_xxx2mcr(*(int *)data), DMBIS); + break; + case TIOCMBIC: + commctl(com, tiocm_xxx2mcr(*(int *)data), DMBIC); + break; + case TIOCMGET: + tiocm_xxx = TIOCM_LE; /* XXX - always enabled while open */ + mcr = com->mcr_image; + if (mcr & MCR_DTR) + tiocm_xxx |= TIOCM_DTR; + if (mcr & MCR_RTS) + tiocm_xxx |= TIOCM_RTS; + msr = com->prev_modem_status; + if (msr & MSR_CTS) + tiocm_xxx |= TIOCM_CTS; + if (msr & MSR_DCD) + tiocm_xxx |= TIOCM_CD; + if (msr & MSR_DSR) + tiocm_xxx |= TIOCM_DSR; + /* + * XXX - MSR_RI is naturally volatile, and we make MSR_TERI + * more volatile by reading the modem status a lot. Perhaps + * we should latch both bits until the status is read here. + */ + if (msr & (MSR_RI | MSR_TERI)) + tiocm_xxx |= TIOCM_RI; + *(int *)data = tiocm_xxx; + break; + case TIOCMSDTRWAIT: + /* must be root since the wait applies to following logins */ + error = suser(p->p_ucred, &p->p_acflag); + if (error != 0) { + splx(s); + return (EPERM); + } + com->dtr_wait = *(int *)data * 100 / hz; + break; + case TIOCMGDTRWAIT: + *(int *)data = com->dtr_wait; + break; +#ifdef TIOCTIMESTAMP + case TIOCTIMESTAMP: + com->do_timestamp = TRUE; + *(struct timeval *)data = com->timestamp; + break; +#endif + default: + splx(s); + return (ENOTTY); + } + splx(s); + return (0); +} + +/* cancel pending output */ +static void +comflush(com) + struct com_s *com; +{ + disable_intr(); + if (com->state & CS_ODONE) + com_events -= LOTS_OF_EVENTS; + com->state &= ~(CS_ODONE | CS_BUSY); + enable_intr(); + com->tp->t_state &= ~TS_BUSY; +} + +void +siopoll() +{ + int unit; + + if (com_events == 0) + return; +repeat: + for (unit = 0; unit < NSIO; ++unit) { + u_char *buf; + struct com_s *com; + u_char *ibuf; + int incc; + struct tty *tp; + + com = com_addr(unit); + if (com == NULL) + continue; + tp = com->tp; + if (tp == NULL) + continue; + + /* switch the role of the low-level input buffers */ + if (com->iptr == (ibuf = com->ibuf)) { + buf = NULL; /* not used, but compiler can't tell */ + incc = 0; + } else { + /* + * Prepare to reduce input latency for packet + * discplines with a end of packet character. + * XXX should be elsewhere. + */ + if (tp->t_line == SLIPDISC) + com->hotchar = 0xc0; + else if (tp->t_line == PPPDISC) + com->hotchar = 0x7e; + else + com->hotchar = 0; + 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. + */ + /* + * XXX this used not to look at CS_RTS_IFLOW. The + * change is to allow full control of MCR_RTS via + * ioctls after turning CS_RTS_IFLOW off. Check + * for races. We shouldn't allow the ioctls while + * CS_RTS_IFLOW is on. + */ + if ((com->state & CS_RTS_IFLOW) + && !(com->mcr_image & MCR_RTS) + && !(tp->t_state & TS_RTS_IFLOW)) + 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) + (*linesw[tp->t_line].l_modem) + (tp, com->prev_modem_status & MSR_DCD); + } + + /* XXX */ + if (TRUE) { + u_int delta; + int errnum; + u_long total; + + for (errnum = 0; errnum < CE_NTYPES; ++errnum) { + disable_intr(); + delta = com->delta_error_counts[errnum]; + com->delta_error_counts[errnum] = 0; + enable_intr(); + if (delta == 0 || !(tp->t_state & TS_ISOPEN)) + continue; + total = com->error_counts[errnum] += delta; + log(LOG_WARNING, + "sio%d: %u more %s%s (total %lu)\n", + unit, delta, error_desc[errnum], + delta == 1 ? "" : "s", total); + if (errnum == CE_OVERRUN && com->hasfifo + && com->ftl > FIFO_TRIGGER_1) { + static u_char ftl_in_bytes[] = + { 1, 4, 8, 14, }; + + com->ftl_init = FIFO_TRIGGER_8; +#define FIFO_TRIGGER_DELTA FIFO_TRIGGER_4 + com->ftl_max = + com->ftl -= FIFO_TRIGGER_DELTA; + outb(com->iobase + com_fifo, + FIFO_ENABLE | com->ftl); + log(LOG_WARNING, + "sio%d: reduced fifo trigger level to %d\n", + unit, + ftl_in_bytes[com->ftl + / FIFO_TRIGGER_DELTA]); + } + } + } + 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 + && tp->t_rawq.c_cc + incc >= RB_I_HIGH_WATER + && !(tp->t_state & TS_RTS_IFLOW) + /* + * 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_RTS_IFLOW; + 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] + += b_to_q((char *)buf, incc, &tp->t_rawq); + 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; +} + +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 (t->c_ispeed == 0) + t->c_ispeed = t->c_ospeed; + if (divisor < 0 || divisor > 0 && t->c_ispeed != t->c_ospeed) + return (EINVAL); + + /* parameters are OK, convert them to the com struct and the device */ + unit = DEV_TO_UNIT(tp->t_dev); + com = com_addr(unit); + iobase = com->iobase; + s = spltty(); + if (divisor == 0) + commctl(com, MCR_DTR, DMBIC); /* hang up line */ + else + commctl(com, MCR_DTR, DMBIS); + 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; + + if (com->hasfifo) { + /* + * Use a fifo trigger level low enough so that the input + * latency from the fifo is less than about 16 msec and + * the total latency is less than about 30 msec. These + * latencies are reasonable for humans. Serial comms + * protocols shouldn't expect anything better since modem + * latencies are larger. + */ + com->ftl = t->c_ospeed <= 4800 + ? FIFO_TRIGGER_1 : FIFO_TRIGGER_14; + if (com->ftl > com->ftl_max) + com->ftl = com->ftl_max; + outb(iobase + com_fifo, FIFO_ENABLE | com->ftl); + } + + /* + * 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 siointr1() might lose some receiver + * error bits, but that is acceptable here. + */ + disable_intr(); +retry: + com->state &= ~CS_TTGO; + enable_intr(); + while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) { + error = ttysleep(tp, TSA_OCOMPLETE(tp), TTIPRI | PCATCH, + "siotx", hz / 100); + 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 */ + + /* + * XXX - clearing CS_TTGO is not sufficient to stop further output, + * because siopoll() calls comstart() which usually sets it again + * because TS_TTSTOP is clear. Setting TS_TTSTOP would not be + * sufficient, for similar reasons. + */ + if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) + goto retry; + + if (divisor != 0) { + 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 10+ msec latency, while CTS flow has 50- usec latency. + */ + com->state &= ~CS_CTS_OFLOW; + com->state |= CS_ODEVREADY; + if (cflag & CCTS_OFLOW) { + com->state |= CS_CTS_OFLOW; + if (!(com->last_modem_status & MSR_CTS)) + com->state &= ~CS_ODEVREADY; + } + + /* + * Recover from fiddling with CS_TTGO. We used to call siointr1() + * unconditionally, but that defeated the careful discarding of + * stale input in sioopen(). + */ + if (com->state >= (CS_BUSY | CS_TTGO)) + siointr1(com); + + enable_intr(); + splx(s); + return (0); +} + +static void +comstart(tp) + struct tty *tp; +{ + struct com_s *com; + int s; + int unit; + + unit = DEV_TO_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_RTS_IFLOW) { + if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) + outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); + } else { + /* + * XXX don't raise MCR_RTS if CTS_RTS_IFLOW is off. Set it + * appropriately in comparam() if RTS-flow is being changed. + * Check for races. + */ + 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 0 /* XXX TK2.0 */ + if (tp->t_state & (TS_SO_OCOMPLETE | TS_SO_OLOWAT) || tp->t_wsel) + ttwwakeup(tp); +#else + if (tp->t_outq.c_cc <= tp->t_lowat) { + if (tp->t_state & TS_ASLEEP) { + tp->t_state &= ~TS_ASLEEP; + wakeup(TSA_OLOWAT(tp)); + } + selwakeup(&tp->t_wsel); + } +#endif + if (com->state & CS_BUSY) { + disable_intr(); + siointr1(com); + enable_intr(); + } else if (tp->t_outq.c_cc != 0) { + u_int ocount; + + tp->t_state |= TS_BUSY; + ocount = q_to_b(&tp->t_outq, com->obuf, sizeof com->obuf); + disable_intr(); + com->obufend = (com->optr = com->obuf) + ocount; + com->state |= CS_BUSY; + siointr1(com); /* fake interrupt to start output */ + enable_intr(); + } +out: + splx(s); +} + +void +siostop(tp, rw) + struct tty *tp; + int rw; +{ + struct com_s *com; + + com = com_addr(DEV_TO_UNIT(tp->t_dev)); + if (rw & FWRITE) + comflush(com); + disable_intr(); + if (rw & FREAD) { + com_events -= (com->iptr - com->ibuf); + com->iptr = com->ibuf; + } + if (tp->t_state & TS_TTSTOP) + com->state &= ~CS_TTGO; + else + com->state |= CS_TTGO; + enable_intr(); +} + +int +sioselect(dev, rw, p) + dev_t dev; + int rw; + struct proc *p; +{ + if (minor(dev) & CONTROL_MASK) + return (ENODEV); + return (ttselect(dev & ~MINOR_MAGIC_MASK, rw, p)); +} + +static void +commctl(com, bits, how) + struct com_s *com; + int bits; + int how; +{ + disable_intr(); + switch (how) { + case DMSET: + outb(com->modem_ctl_port, + com->mcr_image = bits | (com->mcr_image & MCR_IENABLE)); + break; + case DMBIS: + outb(com->modem_ctl_port, com->mcr_image |= bits); + break; + case DMBIC: + outb(com->modem_ctl_port, com->mcr_image &= ~bits); + break; + } + enable_intr(); +} + +static void +comwakeup(chan) + void *chan; +{ + int unit; + + timeout(comwakeup, (void *)NULL, hz / 100); + + if (com_events != 0) { + int s; + + s = splsofttty(); + siopoll(); + splx(s); + } + + /* recover from lost output interrupts */ + for (unit = 0; unit < NSIO; ++unit) { + struct com_s *com; + + com = com_addr(unit); + if (com != NULL && com->state >= (CS_BUSY | CS_TTGO)) { + disable_intr(); + siointr1(com); + enable_intr(); + } + } +} + +/* + * Following are all routines needed for SIO to act as console + */ +#include "i386/i386/cons.h" + +struct siocnstate { + u_char dlbl; + u_char dlbh; + u_char ier; + u_char cfcr; + u_char mcr; +}; + +static Port_t siocniobase; + +static void siocnclose __P((struct siocnstate *sp)); +static void siocnopen __P((struct siocnstate *sp)); +static void siocntxwait __P((void)); + +static void +siocntxwait() +{ + int timo; + + /* + * Wait for any pending transmission to finish. Required to avoid + * the UART lockup bug when the speed is changed, and for normal + * transmits. + */ + timo = 100000; + while ((inb(siocniobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY) && --timo != 0) + ; +} + +static void +siocnopen(sp) + struct siocnstate *sp; +{ + int divisor; + Port_t iobase; + + /* + * Save all the device control registers except the fifo register + * and set our default ones (cs8 -parenb speed=comdefaultrate). + * We can't save the fifo register since it is read-only. + */ + iobase = siocniobase; + sp->ier = inb(iobase + com_ier); + outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ + siocntxwait(); + sp->cfcr = inb(iobase + com_cfcr); + outb(iobase + com_cfcr, CFCR_DLAB); + sp->dlbl = inb(iobase + com_dlbl); + sp->dlbh = inb(iobase + com_dlbh); + divisor = ttspeedtab(comdefaultrate, comspeedtab); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + sp->mcr = inb(iobase + com_mcr); + outb(iobase + com_mcr, MCR_DTR | MCR_RTS); +} + +static void +siocnclose(sp) + struct siocnstate *sp; +{ + Port_t iobase; + + /* + * Restore the device control registers. + */ + siocntxwait(); + iobase = siocniobase; + outb(iobase + com_cfcr, CFCR_DLAB); + outb(iobase + com_dlbl, sp->dlbl); + outb(iobase + com_dlbh, sp->dlbh); + outb(iobase + com_cfcr, sp->cfcr); + /* + * XXX damp oscllations of MCR_DTR and MCR_RTS by not restoring them. + */ + outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); + outb(iobase + com_ier, sp->ier); +} + +void +siocnprobe(cp) + struct consdev *cp; +{ + int unit; + + /* locate the major number */ + /* XXX - should be elsewhere since KGDB uses it */ + for (commajor = 0; commajor < nchrdev; commajor++) + if (cdevsw[commajor].d_open == sioopen) + break; + + /* XXX: ick */ + unit = DEV_TO_UNIT(CONUNIT); + siocniobase = CONADDR; + + /* make sure hardware exists? XXX */ + + /* initialize required fields */ + cp->cn_dev = makedev(commajor, 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; +{ + /* + * XXX can delete more comconsole stuff now that i/o routines are + * fairly reentrant. + */ + comconsole = DEV_TO_UNIT(cp->cn_dev); +} + +int +siocngetc(dev) + dev_t dev; +{ + int c; + Port_t iobase; + int s; + struct siocnstate sp; + + iobase = siocniobase; + s = spltty(); + siocnopen(&sp); + while (!(inb(iobase + com_lsr) & LSR_RXRDY)) + ; + c = inb(iobase + com_data); + siocnclose(&sp); + splx(s); + return (c); +} + +void +siocnputc(dev, c) + dev_t dev; + int c; +{ + int s; + struct siocnstate sp; + + s = spltty(); + siocnopen(&sp); + siocntxwait(); + outb(siocniobase + com_data, c); + siocnclose(&sp); + splx(s); +} + +#endif /* NSIO > 0 */ diff --git a/sys/i386/isa/sioreg.h b/sys/i386/isa/sioreg.h new file mode 100644 index 0000000..4b0f1b6 --- /dev/null +++ b/sys/i386/isa/sioreg.h @@ -0,0 +1,114 @@ +/*- + * 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. + * + * from: @(#)comreg.h 7.2 (Berkeley) 5/9/91 + * $Id$ + */ + + +/* 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/i386/isa/sound/CHANGELOG b/sys/i386/isa/sound/CHANGELOG new file mode 100644 index 0000000..6a9bef1 --- /dev/null +++ b/sys/i386/isa/sound/CHANGELOG @@ -0,0 +1,75 @@ +Changelog for version 2.5 +------------------------- + +Since 2.5-beta2 +- Some fine tuning to the GUS v3.7 mixer code. +- Fixed speed limits for the plain SB (1.0 to 2.0). + +Since 2.5-beta +- Fixed OPL-3 detection with SB. Caused problems with PAS16. +- GUS v3.7 mixer support. + +Since 2.4 +- Mixer support for Sound Galaxy NX Pro (define __SGNXPRO__ on your local.h). +- Fixed truncated sound on /dev/dsp when the device is closed. +- Linear volume mode for GUS +- Pitch bends larger than +/- 2 octaves. +- MIDI recording for SB and SB Pro. (Untested). +- Some other fixes. +- SB16 MIDI and DSP drivers only initialized if SB16 actually installed. +- Implemented better detection for OPL-3. This should be usefull if you + have an old SB Pro (the non-OPL-3 one) or a SB 2.0 clone which has a OPL-3. +- SVR4.2 support by Ian Hartas. Initial ALPHA TEST version (untested). + +Since 2.3b +- Fixed bug which made it impossible to make long recordings to disk. + Recording was not restarted after a buffer overflow situation. +- Limited mixer support for GUS. +- Numerous improvements to the GUS driver by Andrew Robinson. Including + some click removal etc. + +Since 2.3 +- Fixed some minor bugs in the SB16 driver. + +Since 2.2b +- Full SB16 DSP support. 8/16 bit, mono/stereo +- The SCO and FreeBSD versions should be in sync now. There are some + problems with SB16 and GUS in the freebsd versions. + The DMA buffer allocation of the SCO version has been polished but + there could still be some problems. At least it hogs memory. + The DMA channel + configuration method used in the sco/System is a hack. +- Support for the MPU emulation of the SB16. +- Some big arrays are now allocated boot time. This makes the bss segment + smaller which makes it possible to use the full driver with + NetBSD. These arrays are not allocated if no suitable soundcard is available. +- Fixed a bug in the compute_and_set_volume in gus_wave.c +- Fixed the too fast mono playback problem of SB Pro and PAS16. + +Since 2.2 +- Stereo recording for SB Pro. Somehow it was missing and nobody + had noticed it earlier. +- Minor polishing. +- Interpreting of boot time arguments (sound=) for Linux. +- Breakup of sb_dsp.c. Parts of the code has been moved to + sb_mixer.c and sb_midi.c + +Since 2.1 +- Preliminary support for SB16. + - The SB16 mixer is supported in it's native mode. + - Digitized voice capability up to 44.1 kHz/8 bit/mono + (16 bit and stereo support coming in the next release). +- Fixed some bugs in the digitized voice driver for PAS16. +- Proper initialization of the SB emulation of latest PAS16 models. + +- Significantly improved /dev/dsp and /dev/audio support. + - Now supports half duplex mode. It's now possible to record and + playback without closing and reopening the device. + - It's possible to use smaller buffers than earlier. There is a new + ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &n) where n should be 1, 2 or 4. + This call instructs the driver to use smaller buffers. The default + buffer size (0.5 to 1.0 seconds) is divided by n. Should be called + immediately after opening the device. + +Since 2.0 +Just cosmetic changes. diff --git a/sys/i386/isa/sound/COPYING b/sys/i386/isa/sound/COPYING new file mode 100644 index 0000000..d1509c5 --- /dev/null +++ b/sys/i386/isa/sound/COPYING @@ -0,0 +1,25 @@ +/* + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ diff --git a/sys/i386/isa/sound/HOWTO_MIDI b/sys/i386/isa/sound/HOWTO_MIDI new file mode 100644 index 0000000..f0601e5 --- /dev/null +++ b/sys/i386/isa/sound/HOWTO_MIDI @@ -0,0 +1,51 @@ +The following file describes the procedure for adding modules to MIDI +Please READ the main documentation files for the driver first!!! + + + Example: We have a sound card with a MIDI chip & port on it + and, we call it the 'MYBLASTER' card: + + ************************************************************************** + + 0: Run 'configure'. Select the MIDI on CHIP support option. + + 1: Write a midi driver module; 'blast_midi.c' + (with functions for open,close,read,write,attach.) + + 1a: Write all functions except the 'attach' the way you want. + + For the 'attach' function, look at a model in the 'pro_midi.c' + file. Just dup it in the same fashion. For the 'generic_midi_operations' + structure which is required, see file 'dev_table.h'. + + 2: We called the 'attach' function: 'blast_attach'. + + Go to the file 'dev_table.h' and add your driver name and the function + pointer ( which is 'blast_attach' ) to the 'midi_supported' table. + + 3: You are almost set. Go and make a reference + to an 'exclude constant'; say EXLCUDE_BLAST_MIDI in your module + (refer to the 'pro_midi.c' file for model). Also make sure to + add the constant to the file 'sound_config.h' (for example, look + where the constant EXCLUDE_PRO_MIDI is in the file.) + + 4: Add the line + + #define ALL_EXTERNAL_TO_ME + + as the 1st line of your 'blast_midi.c' file. This happily, makes + you ignorant of everything else specific to the driver! :). + + 4a: And of course, don't forget to make a device :). Note that your + minor number should be = ( 15 + position of your driver in the + 'midi_supported' table in the 'dev_table.h' file ). + + Eg: Your driver is the second one in the table. viz midi_supported[1]. + Your device minor number should be ( 15 + 1 = 16 ). Then, make the + reference to your device as, say CMIDI_DEV_BLAST in the file + 'sound_config.h'. Also add this in 'soundcard.c' for the open, read, + write and close routines. See files for example using CMIDI_DEV_PRO + (which is the ProAudioSpectrum on chip MIDI). + + 5: You are all set. If ever you have problems, follow the same model + as the file 'pro_midi.c', except for substituting your own functions! diff --git a/sys/i386/isa/sound/README b/sys/i386/isa/sound/README new file mode 100644 index 0000000..efb0b11 --- /dev/null +++ b/sys/i386/isa/sound/README @@ -0,0 +1,17 @@ +CAUTION! + +This is a prototype version of the Linux Sound Driver for FreeBSD. + +The official and supported version is 1.0c. + +This version 'should work' but there may be some bugs and the programmers +API may change before the final version. + +There are some additional programs for GUS owners in the +gustest subdirectory of this directory, namely a module +(.MOD, .STM and .669) player and a patch file loader. +Additionally, there is a midithru program which allows +you to play the synth on the soundcard with a midi keyboard +(also usable for OPL-3 owners). + +Hannu & FreeBSD team. diff --git a/sys/i386/isa/sound/RELNOTES b/sys/i386/isa/sound/RELNOTES new file mode 100644 index 0000000..03d492d --- /dev/null +++ b/sys/i386/isa/sound/RELNOTES @@ -0,0 +1,38 @@ +Welcome to use the Linux sound driver for FreeBSD. This +driver supports the SoundBlaster, SB Pro, Pro Audio Spectrum 16, +AdLib and Gravis UltraSound sound cards. + +In addition there is rather limited support for MPU-401 +(and compatible) midi cards. Also, the OPL-3 synthesizer +of the SB Pro and PAS16 cards is now supported in the 4 OP +modes. + +Most of the features of the /dev/sequencer device file are +available just for GUS owners. + +The SoundBlaster 16 and SB 16 ASP cards are not supported, +though they may work in mono mode with speeds < 22 kHz. +The OPL-3 chicp of the SB 16 should work (without problems?). +Is there anybody willing to implement the SB 16 support +(have the SB 16 and the SDK for it)? + +Since this driver is a sound driver, it does not contain support +for SCSI/CD-ROM/Joystick -devices. + +Known bugs +---------- + +- It's not possible to open /dev/dsp (or /dev/audio) while the + /dev/sequencer is open for output and GUS is the only soundcard + installed. It's possible if /dev/dsp is opened before /dev/sequencer + but at this time the GUS is not available for access via /dev/sequencer. + This is a limitation of the driver. +- MPU-401 driver hangs the computer on boot if there is no MPU-401 installed. + It uses by default the I/O port 0x330, which is also used by the + Adaptec 1542 SCSI adapter. +- The /dev/sequencer playback to GUS sounds sometimes rather weird. Hitting + ^C and playing again should solve this problem. This is probably caused by + incompatibilities between the GUS and certain VLB motherboards. Try to avoid + switching between VTs while patches are being loaded to the GUS. +- There is a skeleton of the patch manager support. It doesn't work in + this version. diff --git a/sys/i386/isa/sound/RELNOTES.Linux b/sys/i386/isa/sound/RELNOTES.Linux new file mode 100644 index 0000000..ea57d0a --- /dev/null +++ b/sys/i386/isa/sound/RELNOTES.Linux @@ -0,0 +1,255 @@ +Release notes for the Linux Sound Driver 2.5 +-------------------------------------------- +There is also a version called 2.5-beta floating around the net. This +version contains some fixes after it. Mainly to the SB and GUS code. + +CAUTION! The SVR4.2 port has not been tested much. Backup your system + carefully before trying it. + +This is mainly a bug fix release. There are couple of new things such as +linear volume mode for GUS and MIDI recording for SB 2.0 and SB Pro. +Also this version supports the mixer of GUS v3.7. (Support for GUS MAX and +the 16-bit daughtercard is coming sooner or later). + +NOTE! The sound driver is a part of the Linux kernel distribution also. + Check that your kernel doesn't have more recent version than this + when installing a separately distributed sound driver. The + version number of this driver is defined in the makefile. + +This version contains a driver for the SB16 also. +The SB16 driver requires separate DMA channels for the 8 and 16 bit +modes. There should be a way to share the 8 bit DMA channels between +these modes but this feature is not supported yet. +The SB16 DSP support is by Joerg Schubert (jsb@sth.ruhr-uni-bochum.de). + +The SB16 driver has also the Midi input capability even at the same +time with the /dev/dsp. Also the WaveBlaster daughter board is supported. +No support for the ASP chip yet (the ASP chip can be installed but it's +not used by the driver). + +You will need the snd-util-2.5.tar.gz and snd-data-0.1.tar.Z +packages to use this driver. They should be in the same +ftp site or BBS from where you got this driver. For +example at nic.funet.fi:pub/OS/Linux/*. + +If you are looking for the installation instructions, please +look at $OS/Readme. + +This version supports the following soundcards: +GUS, SoundBlaster, SB Pro, SB16, Pro Audio Spectrum 16 and AdLib. +In addition there is rather limited support for MPU-401. +(and compatible) midi cards. Also the OPL-3 synthesizer +Most of the features of the /dev/sequencer device file are +available just for GUS owners. + +NOTE! There are separate driver for CD-ROMS supported by + some soundcards. The driver for CDU31A (Fusion 16) is + called cdu31a-0.6.diff.z. It will be contained in the + Linux version 0.99.12. The driver for the CD-ROM of SB Pro + is sbpcd0.4.tar.gz (these were the latest versions when I wrote + this). These files should be at least at sunsite.unc.edu. + Also the SCSI interface of the PAS16 should be supported by + Linux 0.99.13k and later. + + There is also a driver for joystick. Look for file joystick-0.5.tar.gz + (sunsite). + + +Compatibility with the earlier versions +--------------------------------------- + +In this version the ultrasound.h no longer includes the sys/soundcard.h +You have to change the gmod.c of the snd-util-2.0 package and to add an +include for it. + +IMPORTANT!!!!!!!!!!!!!!!!!!!!!! + +This version is not binary or source compatible with the version 1.0c. + +The ioctl() interface has changed completely since version 1.0c. All +programs using this driver must be at least recompiled. +The snd-util-2.0 package contains some utilities for this version. + +The version 1.0c and earlier used a 'nonportable' ioctl calling scheme +where the input argument was passed by value and the output value was +returned as the functional return. For example setting the speed of +/dev/dsp were done as the following: + + int actual_speed; + actual_speed = ioctl(fd, SOUND_PCM_WRITE_RATE, 44100); + +After version 1.99.0 this must be done as the following: + + int actual_speed = 44100; + ioctl(fd, SOUND_PCM_WRITE_RATE, &actual_speed); + +If you have an application written for the version 1.0, you should search +for the strings SNDCTL_ and SOUND_ and to check the parameters. +The following ioctl calls have changed: + + SNDCTL_SEQ_GETOUTCOUNT + SNDCTL_SEQ_GETINCOUNT + SNDCTL_SEQ_TESTMIDI + SNDCTL_DSP_SPEED + SNDCTL_DSP_STEREO + SNDCTL_DSP_GETBLKSIZE + SNDCTL_DSP_SAMPLESIZE + SOUND_PCM_WRITE_CHANNELS + SOUND_PCM_WRITE_FILTER + SOUND_PCM_READ_RATE + SOUND_PCM_READ_CHANNELS + SOUND_PCM_READ_BITS + SOUND_PCM_READ_FILTER + SOUND_PCM_WRITE_BITS + SOUND_PCM_WRITE_RATE + SOUND_MIXER_READ_* (several ones) + SOUND_MIXER_WRITE_* (several ones) + +Since the this version will support more than one synthesizer devices +at the same time, the ioctl(SNDCTL_FM_LOAD_INSTR) is obsolete. In addition +there is some new fields which must be initialized. Look at the sbiset.c in +the snd-util-2.0 package for further info. + +This version is almost 100% compatible with the alpha test version (1.99.9). The +difference is in the installation procedure. + +Using this driver with other operating systems than Linux +--------------------------------------------------------- + +This package contains just the Linux version. The version 2.3 +for SCO is available at nic.funet.fi:pub/OS/Linux/ALPHA/sound. +The version 2.3 doesn't work well with xxxxxBSD. Use the version +2.3 for them. + +/dev/sndstat +------------ + +The /dev/sndstat is now available in the SCO and BSD versions also. + +This is a new devicefile for debugging purposes. A better place for +it is in the /proc -directory but I was just too lazy to implement it +properly. The /dev/sndstat (major 14, minor 6) is a file which returns +info about the current configuration (see the example below). If you +send me a error/problem report, please include a printout from this +device to your message (cat /dev/sndstat). + +Note! This device file is currently present only in the Linux version + of this driver. + +------ cut here --- cat /dev/sndstat example -------- +Sound Driver:1.99.7 (Fri Jul 9 17:01:47 GMT 1993 root@lucifer.savolai.fi) +Config options: 0x00000d4b + +HW config: +Type 4: Gravis Ultrasound at 0x210 irq 15 drq 6 +Type 3: ProAudioSpectrum at 0x388 irq 10 drq 3 +Type 2: SoundBlaster at 0x220 irq 7 drq 1 +Type 1: AdLib at 0x388 irq 0 drq 0 + +PCM devices: +00: Gravis UltraSound +01: Pro Audio Spectrum +02: SoundBlaster 2.0 + +Synth devices: +00: Gravis UltraSound +01: Yamaha OPL-3 + +Midi devices: +00: Gravis UltraSound +01: Pro Audio Spectrum + +Mixer(s) installed +------ cut here ---- End of Example ----------- + +Known bugs/limitations +---------------------- + +- High speed recording of long audio samples (>20 second) to disk + is not possible. Everything works until next sync() which delays the + recording process too much. A delay longer than 0.1 to 0.3 seconds is + too much. +- The SB16 driver sometimes swaps the left and right channels together. +- Midi input doesn't work with SB and SB Pro (SB16 works). +- It's not possible to open /dev/dsp (or /dev/audio) while the + /dev/sequencer is open for output and GUS is the only soundcard + installed. It's possible if /dev/dsp is opened before /dev/sequencer + but at this time the GUS is not available for access via /dev/sequencer. + This is a limitation of the driver. +- MPU-401 driver hangs the computer on boot if there is no MPU-401 installed. + It uses by default the I/O port 0x330 whic is used by Adaptec 1542 SCSI + adapter. +- There are some problems in midi input with MPU-401 and the SB16 midi + (MPU-401 emulation). This makes it impossible to read long sysex dumps + using these devices. +- The /dev/sequencer playback to GUS sounds sometimes rather weird. Hitting + ^C and playing again should solve this problem. This is propably caused by + incompatibilities between GUS and certain VLB motherboards (like mine). + Try to avoid + switching between VTs while patches are being loaded to the GUS. + This problem disappears completely if you define GUS_PATCH_NO_DMA in the + local.h (after make config in linux). The drawback is that patch loading + without DMA takes several times longer than with DMA. +- There is a skeleton of the patch manager support. It don't work in + this version. + + +Future development +------------------ + +- Since this driver is no longer just the Linux Sound Driver, it's time + to give it a new name. I have planned to use name VoxWare. +- I'm writing a Hacker's guide to the VoxWare sound driver. Should + be ready within this(/next) year (alpha version). +- Completion of the ISC, SCO and BSD ports. Port to SVR4.2. +- I'm interested to implement/include support for new soundcards and + operating systems. + + Hint for the soundcard and OS manufacturers: + I'm collecting soundcards (high end ones) and SDKs for them. In + addition I'm collecting PC operating systems. I will be happy if + somebody sends me such items. In addition such kind of donation + makes it easier to change the VoxWare driver to support your + soundcard or operating system. However, please contact me before + sending anything. + +I will propably release some fix versions within this and next year. At +least when the non-Linux versions get ready. The next major release (3.0) +will be quite complete rewrite and released after about a year (end of 94 or +beginning of 95). + + +Contributors +------------ + +This driver contains code by several contributors. In addition several other +persons have given usefull suggestions. The following is a list of major +contributors. (I could have forgotten some names.) + + Craig Metz 1/2 of the PAS16 Mixer and PCM support + Rob Hooft Volume computation algorithm for the FM synth. + Mika Liljeberg uLaw encoding and decoding routines + Greg Lee Volume computation algorithm for the GUS and + lot's of valuable suggestions. + Andy Warner Initial ISC port + Jim Lowe Initial FreeBSD port + Anders Baekgaard Bughunting and valuable suggestions. + Joerg Schubert SB16 DSP support. + Andrew Robinson Improvements to the GUS driver + Megens SA MIDI recording for SB and SB Pro. + Mikael Nordqvist Linear volume support for GUS. + Ian Hartas SVR4.2 port + Markus Aroharju and + Risto Kankkunen Major contributions to the mixer support + of GUS v3.7. + Hunyue Yau Sound Galaxy NX Pro mixer support. + +Regards, + +Hannu Savolainen +hannu@voxware.pp.fi, Hannu.Savolainen@Helsinki.fi + +Snail mail: Hannu Savolainen + Pallaksentie 4 A 2 + 00970 Helsinki + Finland diff --git a/sys/i386/isa/sound/adlib_card.c b/sys/i386/isa/sound/adlib_card.c new file mode 100644 index 0000000..36c2f5e --- /dev/null +++ b/sys/i386/isa/sound/adlib_card.c @@ -0,0 +1,52 @@ +/* + * sound/adlib_card.c + * + * Detection routine for the AdLib card. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_YM3812) + +long +attach_adlib_card (long mem_start, struct address_info *hw_config) +{ + + if (opl3_detect (FM_MONO)) + { + mem_start = opl3_init (mem_start); + } + return mem_start; +} + +int +probe_adlib (struct address_info *hw_config) +{ + return opl3_detect (FM_MONO); +} + +#endif diff --git a/sys/i386/isa/sound/audio.c b/sys/i386/isa/sound/audio.c new file mode 100644 index 0000000..092a5e5 --- /dev/null +++ b/sys/i386/isa/sound/audio.c @@ -0,0 +1,357 @@ +/* + * sound/audio.c + * + * Device file manager for /dev/audio + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD +#ifndef EXCLUDE_AUDIO + +#include "ulaw.h" + +#define ON 1 +#define OFF 0 + +static int wr_buff_no[MAX_DSP_DEV]; /* != -1, if there is a + + * incomplete output block */ +static int wr_buff_size[MAX_DSP_DEV], wr_buff_ptr[MAX_DSP_DEV]; + +static int audio_mode[MAX_DSP_DEV]; + +#define AM_NONE 0 +#define AM_WRITE 1 +#define AM_READ 2 + +static char *wr_dma_buf[MAX_DSP_DEV]; + +int +audio_open (int dev, struct fileinfo *file) +{ + int ret; + int bits; + int dev_type = dev & 0x0f; + int mode = file->mode & O_ACCMODE; + + dev = dev >> 4; + + if (dev_type == SND_DEV_DSP16) + bits = 16; + else + bits = 8; + + if ((ret = DMAbuf_open (dev, mode)) < 0) + return ret; + + if (DMAbuf_ioctl (dev, SNDCTL_DSP_SAMPLESIZE, bits, 1) != bits) + { + audio_release (dev, file); + return RET_ERROR (ENXIO); + } + + wr_buff_no[dev] = -1; + audio_mode[dev] = AM_NONE; + + return ret; +} + +void +audio_release (int dev, struct fileinfo *file) +{ + int mode; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + + DMAbuf_release (dev, mode); +} + +#ifdef NO_INLINE_ASM +static void +translate_bytes (const unsigned char *table, unsigned char *buff, unsigned long n) +{ + unsigned long i; + + for (i = 0; i < n; ++i) + buff[i] = table[buff[i]]; +} + +#else +extern inline void +translate_bytes (const void *table, void *buff, unsigned long n) +{ + __asm__ ("cld\n" + "1:\tlodsb\n\t" + "xlatb\n\t" + "stosb\n\t" + "loop 1b\n\t": + :"b" ((long) table), "c" (n), "D" ((long) buff), "S" ((long) buff) + :"bx", "cx", "di", "si", "ax"); +} + +#endif + +int +audio_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int c, p, l; + int err; + int dev_type = dev & 0x0f; + + dev = dev >> 4; + + p = 0; + c = count; + + if (audio_mode[dev] == AM_READ) /* Direction changed */ + { + wr_buff_no[dev] = -1; + } + + audio_mode[dev] = AM_WRITE; + + if (!count) /* Flush output */ + { + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + return 0; + } + + while (c) + { /* Perform output blocking */ + if (wr_buff_no[dev] < 0) /* There is no incomplete buffers */ + { + if ((wr_buff_no[dev] = DMAbuf_getwrbuffer (dev, &wr_dma_buf[dev], &wr_buff_size[dev])) < 0) + return wr_buff_no[dev]; + wr_buff_ptr[dev] = 0; + } + + l = c; + if (l > (wr_buff_size[dev] - wr_buff_ptr[dev])) + l = (wr_buff_size[dev] - wr_buff_ptr[dev]); + + if (!dsp_devs[dev]->copy_from_user) + { /* No device specific copy routine */ + COPY_FROM_USER (&wr_dma_buf[dev][wr_buff_ptr[dev]], buf, p, l); + } + else + dsp_devs[dev]->copy_from_user (dev, + wr_dma_buf[dev], wr_buff_ptr[dev], buf, p, l); + + + /* Insert local processing here */ + + if (dev_type == SND_DEV_AUDIO) + { +#ifdef linux + /* This just allows interrupts while the conversion is running */ + __asm__ ("sti"); +#endif + translate_bytes (ulaw_dsp, (unsigned char *) &wr_dma_buf[dev][wr_buff_ptr[dev]], l); + } + + c -= l; + p += l; + wr_buff_ptr[dev] += l; + + if (wr_buff_ptr[dev] >= wr_buff_size[dev]) + { + if ((err = DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev])) < 0) + return err; + + wr_buff_no[dev] = -1; + } + + } + + return count; +} + +int +audio_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int c, p, l; + char *dmabuf; + int buff_no; + int dev_type = dev & 0x0f; + + dev = dev >> 4; + p = 0; + c = count; + + if (audio_mode[dev] == AM_WRITE) + { + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + } + + audio_mode[dev] = AM_READ; + + while (c) + { + if ((buff_no = DMAbuf_getrdbuffer (dev, &dmabuf, &l)) < 0) + return buff_no; + + if (l > c) + l = c; + + /* Insert any local processing here. */ + + if (dev_type == SND_DEV_AUDIO) + { +#ifdef linux + /* This just allows interrupts while the conversion is running */ + __asm__ ("sti"); +#endif + + translate_bytes (dsp_ulaw, (unsigned char *) dmabuf, l); + } + + COPY_TO_USER (buf, p, dmabuf, l); + + DMAbuf_rmchars (dev, buff_no, l); + + p += l; + c -= l; + } + + return count - c; +} + +int +audio_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + int dev_type = dev & 0x0f; + + dev = dev >> 4; + + switch (cmd) + { + case SNDCTL_DSP_SYNC: + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + return DMAbuf_ioctl (dev, cmd, arg, 0); + break; + + case SNDCTL_DSP_POST: + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + return 0; + break; + + case SNDCTL_DSP_RESET: + wr_buff_no[dev] = -1; + return DMAbuf_ioctl (dev, cmd, arg, 0); + break; + + default: + if (dev_type == SND_DEV_AUDIO) + return RET_ERROR (EIO); + + return DMAbuf_ioctl (dev, cmd, arg, 0); + } +} + +long +audio_init (long mem_start) +{ + return mem_start; +} + +#else +/* Stub versions */ + +int +audio_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +audio_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +audio_open (int dev, struct fileinfo *file) +{ + return RET_ERROR (ENXIO); +} + +void +audio_release (int dev, struct fileinfo *file) +{ +}; +int +audio_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + return RET_ERROR (EIO); +} + +int +audio_lseek (int dev, struct fileinfo *file, off_t offset, int orig) +{ + return RET_ERROR (EIO); +} + +long +audio_init (long mem_start) +{ + return mem_start; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/dev_table.c b/sys/i386/isa/sound/dev_table.c new file mode 100644 index 0000000..ccc32bc --- /dev/null +++ b/sys/i386/isa/sound/dev_table.c @@ -0,0 +1,218 @@ +/* + * sound/dev_table.c + * + * Device call tables. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#define _DEV_TABLE_C_ +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +long +sndtable_init (long mem_start) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + for (i = 0; i < (n - 1); i++) + if (supported_drivers[i].enabled) + if (supported_drivers[i].probe (&supported_drivers[i].config)) + { +#ifndef SHORT_BANNERS + printk ("snd%d", + supported_drivers[i].card_type); +#endif + + mem_start = supported_drivers[i].attach (mem_start, &supported_drivers[i].config); +#ifndef SHORT_BANNERS + printk (" at 0x%x irq %d drq %d\n", + supported_drivers[i].config.io_base, + supported_drivers[i].config.irq, + supported_drivers[i].config.dma); +#endif + } + else + supported_drivers[i].enabled = 0; /* Mark as not detected */ + return mem_start; +} + +int +sndtable_probe (int unit, struct address_info *hw_config) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + if (!unit) + return TRUE; + + for (i = 0; i < (n - 1); i++) + if (supported_drivers[i].card_type == unit) + { + supported_drivers[i].config.io_base = hw_config->io_base; + supported_drivers[i].config.irq = hw_config->irq; + supported_drivers[i].config.dma = hw_config->dma; + if (supported_drivers[i].probe (hw_config)) + return 1; + supported_drivers[i].enabled = 0; /* Mark as not detected */ + return 0; + } + + return FALSE; +} + +int +sndtable_init_card (int unit, struct address_info *hw_config) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + if (!unit) + { + if (sndtable_init (0) != 0) + panic ("snd: Invalid memory allocation\n"); + return TRUE; + } + + for (i = 0; i < (n - 1); i++) + if (supported_drivers[i].card_type == unit) + { + supported_drivers[i].config.io_base = hw_config->io_base; + supported_drivers[i].config.irq = hw_config->irq; + supported_drivers[i].config.dma = hw_config->dma; + + if (supported_drivers[i].attach (0, hw_config) != 0) + panic ("snd#: Invalid memory allocation\n"); + return TRUE; + } + + return FALSE; +} + +int +sndtable_get_cardcount (void) +{ + return num_dspdevs + num_mixers + num_synths + num_midis; +} + +#ifdef linux +void +sound_setup (char *str, int *ints) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + /* + * First disable all drivers + */ + + for (i = 0; i < n; i++) + supported_drivers[i].enabled = 0; + + if (ints[0] == 0 || ints[1] == 0) + return; + /* + * Then enable them one by time + */ + + for (i = 1; i <= ints[0]; i++) + { + int card_type, ioaddr, irq, dma, ptr, j; + unsigned int val; + + val = (unsigned int) ints[i]; + + card_type = (val & 0x0ff00000) >> 20; + + if (card_type > 127) + { + /* Add any future extensions here */ + return; + } + + ioaddr = (val & 0x000fff00) >> 8; + irq = (val & 0x000000f0) >> 4; + dma = (val & 0x0000000f); + + ptr = -1; + for (j = 0; j < n && ptr == -1; j++) + if (supported_drivers[j].card_type == card_type) + ptr = j; + + if (ptr == -1) + printk ("Sound: Invalid setup parameter 0x%08x\n", val); + else + { + supported_drivers[ptr].enabled = 1; + supported_drivers[ptr].config.io_base = ioaddr; + supported_drivers[ptr].config.irq = irq; + supported_drivers[ptr].config.dma = dma; + } + } +} + +#else +void +sound_chconf (int card_type, int ioaddr, int irq, int dma) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + int ptr, j; + + ptr = -1; + for (j = 0; j < n && ptr == -1; j++) + if (supported_drivers[j].card_type == card_type) + ptr = j; + + if (ptr != -1) + { + supported_drivers[ptr].enabled = 1; + if (ioaddr) + supported_drivers[ptr].config.io_base = ioaddr; + if (irq) + supported_drivers[ptr].config.irq = irq; + if (dma) + supported_drivers[ptr].config.dma = dma; + } +} + +#endif + +struct address_info * +sound_getconf (int card_type) +{ + int j, ptr; + int n = sizeof (supported_drivers) / sizeof (struct card_info); + + ptr = -1; + for (j = 0; j < n && ptr == -1; j++) + if (supported_drivers[j].card_type == card_type) + ptr = j; + + if (ptr == -1) + return (struct address_info *) NULL; + + return &supported_drivers[ptr].config; +} + +#endif diff --git a/sys/i386/isa/sound/dev_table.h b/sys/i386/isa/sound/dev_table.h new file mode 100644 index 0000000..a814d5b --- /dev/null +++ b/sys/i386/isa/sound/dev_table.h @@ -0,0 +1,273 @@ +/* + * dev_table.h + * + * Global definitions for device call tables + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifndef _DEV_TABLE_H_ +#define _DEV_TABLE_H_ + +/* + * NOTE! NOTE! NOTE! NOTE! + * + * If you modify this file, please check the dev_table.c also. + * + * NOTE! NOTE! NOTE! NOTE! + */ + +struct card_info { + int card_type; /* From soundcard.c */ + char *name; + long (*attach) (long mem_start, struct address_info *hw_config); + int (*probe) (struct address_info *hw_config); + struct address_info config; + int enabled; +}; + +/** UWM -- new MIDI structure here.. **/ + +struct generic_midi_info{ + char *name; /* Name of the MIDI device.. */ + long (*attach) (long mem_start); +}; + +struct audio_operations { + char name[32]; + int flags; +#define NOTHING_SPECIAL 0 +#define NEEDS_RESTART 1 + int (*open) (int dev, int mode); + void (*close) (int dev); + void (*output_block) (int dev, unsigned long buf, + int count, int intrflag, int dma_restart); + void (*start_input) (int dev, unsigned long buf, + int count, int intrflag, int dma_restart); + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg, int local); + int (*prepare_for_input) (int dev, int bufsize, int nbufs); + int (*prepare_for_output) (int dev, int bufsize, int nbufs); + void (*reset) (int dev); + void (*halt_xfer) (int dev); + int (*has_output_drained)(int dev); + void (*copy_from_user)(int dev, char *localbuf, int localoffs, + snd_rw_buf *userbuf, int useroffs, int len); +}; + +struct mixer_operations { + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg); +}; + +struct synth_operations { + struct synth_info *info; + int synth_type; + int synth_subtype; + + int (*open) (int dev, int mode); + void (*close) (int dev); + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg); + int (*kill_note) (int dev, int voice, int velocity); + int (*start_note) (int dev, int voice, int note, int velocity); + int (*set_instr) (int dev, int voice, int instr); + void (*reset) (int dev); + void (*hw_control) (int dev, unsigned char *event); + int (*load_patch) (int dev, int format, snd_rw_buf *addr, + int offs, int count, int pmgr_flag); + void (*aftertouch) (int dev, int voice, int pressure); + void (*controller) (int dev, int voice, int ctrl_num, int value); + void (*panning) (int dev, int voice, int value); + void (*volume_method) (int dev, int mode); + int (*pmgr_interface) (int dev, struct patmgr_info *info); +}; + +struct midi_operations { + struct midi_info info; + int (*open) (int dev, int mode, + void (*inputintr)(int dev, unsigned char data), + void (*outputintr)(int dev) + ); + void (*close) (int dev); + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg); + int (*putc) (int dev, unsigned char data); + int (*start_read) (int dev); + int (*end_read) (int dev); + void (*kick)(int dev); + int (*command) (int dev, unsigned char data); + int (*buffer_status) (int dev); +}; + +/** UWM -- new structure for MIDI **/ + +struct generic_midi_operations { + struct midi_info info; + int (*open) (int dev, int mode); + void (*close) (int dev); + int (*write) (int dev, snd_rw_buf *data); + int (*read) (int dev, snd_rw_buf *data); +}; + +#ifndef ALL_EXTERNAL_TO_ME + +#ifdef _MIDI_TABLE_C_ + +/** UWM **/ + struct generic_midi_operations * generic_midi_devs[MAX_MIDI_DEV] = {NULL}; + int num_generic_midis = 0, pro_midi_dev = 0; + + struct generic_midi_info midi_supported[] = { + +#ifndef EXCLUDE_PRO_MIDI + {"ProAudioSpectrum MV101",pro_midi_attach} +#endif + }; + + int num_midi_drivers = + sizeof (midi_supported) / sizeof(struct generic_midi_info); + +#endif + + +#ifdef _DEV_TABLE_C_ + struct audio_operations * dsp_devs[MAX_DSP_DEV] = {NULL}; int num_dspdevs = 0; + struct mixer_operations * mixer_devs[MAX_MIXER_DEV] = {NULL}; int num_mixers = 0; + struct synth_operations * synth_devs[MAX_SYNTH_DEV] = {NULL}; int num_synths = 0; + struct midi_operations * midi_devs[MAX_MIDI_DEV] = {NULL}; int num_midis = 0; + + +# ifndef EXCLUDE_MPU401 + int mpu401_dev = 0; +# endif + +/* + * Note! The detection order is significant. Don't change it. + */ + + struct card_info supported_drivers[] = { +#if !defined(EXCLUDE_MPU401) && !defined(EXCLUDE_MIDI) + {SNDCARD_MPU401,"Roland MPU-401", attach_mpu401, probe_mpu401, + {MPU_BASE, MPU_IRQ, 0}, SND_DEFAULT_ENABLE}, +#endif + +#ifndef EXCLUDE_PAS + {SNDCARD_PAS, "ProAudioSpectrum", attach_pas_card, probe_pas, + {PAS_BASE, PAS_IRQ, PAS_DMA}, SND_DEFAULT_ENABLE}, +#endif + +#ifndef EXCLUDE_SB + {SNDCARD_SB, "SoundBlaster", attach_sb_card, probe_sb, + {SBC_BASE, SBC_IRQ, SBC_DMA}, SND_DEFAULT_ENABLE}, +#endif + +#if !defined(EXCLUDE_SB) && !defined(EXCLUDE_SB16) && !defined(EXCLUDE_SBPRO) +#ifndef EXCLUDE_AUDIO + {SNDCARD_SB16, "SoundBlaster16", sb16_dsp_init, sb16_dsp_detect, + {SBC_BASE, SBC_IRQ, SB16_DMA}, SND_DEFAULT_ENABLE}, +#endif +#ifndef EXCLUDE_MIDI + {SNDCARD_SB16MIDI,"SB16 MPU-401", attach_sb16midi, probe_sb16midi, + {SB16MIDI_BASE, SBC_IRQ, 0}, SND_DEFAULT_ENABLE}, +#endif +#endif + +#ifndef EXCLUDE_GUS + {SNDCARD_GUS, "Gravis Ultrasound", attach_gus_card, probe_gus, + {GUS_BASE, GUS_IRQ, GUS_DMA}, SND_DEFAULT_ENABLE}, +#endif + +#ifndef EXCLUDE_YM3812 + {SNDCARD_ADLIB, "AdLib", attach_adlib_card, probe_adlib, + {FM_MONO, 0, 0}, SND_DEFAULT_ENABLE}, +#endif + {0, "*?*", NULL, 0} + }; + + int num_sound_drivers = + sizeof(supported_drivers) / sizeof (struct card_info); + + +# ifndef EXCLUDE_AUDIO + int sound_buffcounts[MAX_DSP_DEV] = {0}; + long sound_buffsizes[MAX_DSP_DEV] = {0}; + int sound_dsp_dmachan[MAX_DSP_DEV] = {0}; + int sound_dma_automode[MAX_DSP_DEV] = {0}; +# endif +#else + extern struct audio_operations * dsp_devs[MAX_DSP_DEV]; int num_dspdevs; + extern struct mixer_operations * mixer_devs[MAX_MIXER_DEV]; extern int num_mixers; + extern struct synth_operations * synth_devs[MAX_SYNTH_DEV]; extern int num_synths; + extern struct midi_operations * midi_devs[MAX_MIDI_DEV]; extern int num_midis; +# ifndef EXCLUDE_MPU401 + extern int mpu401_dev; +# endif + + extern struct card_info supported_drivers[]; + extern int num_sound_drivers; + +# ifndef EXCLUDE_AUDIO + extern int sound_buffcounts[MAX_DSP_DEV]; + extern long sound_buffsizes[MAX_DSP_DEV]; + extern int sound_dsp_dmachan[MAX_DSP_DEV]; + extern int sound_dma_automode[MAX_DSP_DEV]; +# endif + +#endif + +long sndtable_init(long mem_start); +int sndtable_get_cardcount (void); +long CMIDI_init(long mem_start); /* */ +struct address_info *sound_getconf(int card_type); +void sound_chconf(int card_type, int ioaddr, int irq, int dma); +#endif + +#endif + +/* If external to me.... :) */ + +#ifdef ALL_EXTERNAL_TO_ME + + extern struct audio_operations * dsp_devs[MAX_DSP_DEV]; int num_dspdevs; + extern struct mixer_operations * mixer_devs[MAX_MIXER_DEV]; extern int num_mixers; + extern struct synth_operations * synth_devs[MAX_SYNTH_DEV]; extern int num_synths; + extern struct midi_operations * midi_devs[MAX_MIDI_DEV]; extern int num_midis; + extern struct generic_midi_operations *generic_midi_devs[]; + extern int num_generic_midis, pro_midi_dev; + +#ifndef EXCLUDE_MPU401 + extern int mpu401_dev; +#endif + + extern struct generic_midi_info midi_supported[]; + extern struct card_info supported_drivers[]; + extern int num_sound_drivers; + extern int num_midi_drivers; +#ifndef EXCLUDE_AUDIO + extern int sound_buffcounts[MAX_DSP_DEV]; + extern long sound_buffsizes[MAX_DSP_DEV]; + extern int sound_dsp_dmachan[MAX_DSP_DEV]; + extern int sound_dma_automode[MAX_DSP_DEV]; +#endif + +#endif diff --git a/sys/i386/isa/sound/dmabuf.c b/sys/i386/isa/sound/dmabuf.c new file mode 100644 index 0000000..7452717 --- /dev/null +++ b/sys/i386/isa/sound/dmabuf.c @@ -0,0 +1,903 @@ +/* + * sound/dmabuf.c + * + * The DMA buffer manager for digitized voice applications + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "sound_calls.h" + +#if !defined(EXCLUDE_AUDIO) || !defined(EXCLUDE_GUS) + +#define MAX_SUB_BUFFERS (32*MAX_REALTIME_FACTOR) + +/* + * The DSP channel can be used either for input or output. Variable + * 'dma_mode' will be set when the program calls read or write first time + * after open. Current version doesn't support mode changes without closing + * and reopening the device. Support for this feature may be implemented in a + * future version of this driver. + */ + +#define DMODE_NONE 0 +#define DMODE_OUTPUT 1 +#define DMODE_INPUT 2 + +DEFINE_WAIT_QUEUES (dev_sleeper[MAX_DSP_DEV], dev_sleep_flag[MAX_DSP_DEV]); + +static int dma_mode[MAX_DSP_DEV] = +{0}; /* DMODE_INPUT, DMODE_OUTPUT or DMODE_NONE */ + +static volatile int dmabuf_interrupted[MAX_DSP_DEV] = +{0}; + +/* + * Pointers to raw buffers + */ + +char *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT] = +{ + {NULL}}; +unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT]; +int snd_raw_count[MAX_DSP_DEV]; + +/* + * Device state tables + */ + +static int dev_busy[MAX_DSP_DEV]; +static int dev_needs_restart[MAX_DSP_DEV]; +static int dev_modes[MAX_DSP_DEV]; +static int dev_active[MAX_DSP_DEV]; +static int dev_started[MAX_DSP_DEV]; +static int dev_qlen[MAX_DSP_DEV]; +static int dev_qhead[MAX_DSP_DEV]; +static int dev_qtail[MAX_DSP_DEV]; +static int dev_underrun[MAX_DSP_DEV]; +static int bufferalloc_done[MAX_DSP_DEV] = +{0}; + +/* + * Logical buffers for each devices + */ + +static int dev_nbufs[MAX_DSP_DEV]; /* # of logical buffers ( >= + + * sound_buffcounts[dev] */ +static int dev_counts[MAX_DSP_DEV][MAX_SUB_BUFFERS]; +static int dev_subdivision[MAX_DSP_DEV]; +static unsigned long dev_buf_phys[MAX_DSP_DEV][MAX_SUB_BUFFERS]; +static char *dev_buf[MAX_DSP_DEV][MAX_SUB_BUFFERS] = +{ + {NULL}}; +static int dev_buffsize[MAX_DSP_DEV]; + +static void +reorganize_buffers (int dev) +{ + /* + * This routine breaks the physical device buffers to logical ones. + */ + + unsigned i, p, n; + unsigned sr, nc, sz, bsz; + + sr = dsp_devs[dev]->ioctl (dev, SOUND_PCM_READ_RATE, 0, 1); + nc = dsp_devs[dev]->ioctl (dev, SOUND_PCM_READ_CHANNELS, 0, 1); + sz = dsp_devs[dev]->ioctl (dev, SOUND_PCM_READ_BITS, 0, 1); + + if (sr < 1 || nc < 1 || sz < 1) + { + printk ("SOUND: Invalid PCM parameters[%d] sr=%d, nc=%d, sz=%d\n", dev, sr, nc, sz); + sr = DSP_DEFAULT_SPEED; + nc = 1; + sz = 8; + } + + sz /= 8; /* Convert # of bits -> # of bytes */ + + sz = sr * nc * sz; + + /* + * Compute a buffer size not exeeding 1 second. + */ + + bsz = sound_buffsizes[dev]; + + while (bsz > sz) + bsz >>= 1; /* Divide by 2 */ + + if (sound_buffcounts[dev] == 1 && bsz == sound_buffsizes[dev]) + bsz >>= 1; /* Need at least 2 buffers */ + + if (dev_subdivision[dev] == 0) + dev_subdivision[dev] = 1; /* Default value */ + + bsz /= dev_subdivision[dev]; /* Use smaller buffers */ + + if (bsz == 0) + bsz = 4096; /* Just a sanity check */ + + while ((sound_buffsizes[dev] * sound_buffcounts[dev]) / bsz > MAX_SUB_BUFFERS) + bsz <<= 1; /* Too much buffers */ + + dev_buffsize[dev] = bsz; + n = 0; + + /* + * Now computing addresses for the logical buffers + */ + + for (i = 0; i < snd_raw_count[dev]; i++) + { + p = 0; + + while ((p + bsz) <= sound_buffsizes[dev]) + { + dev_buf[dev][n] = snd_raw_buf[dev][i] + p; + dev_buf_phys[dev][n] = snd_raw_buf_phys[dev][i] + p; + p += bsz; + n++; + } + } + + dev_nbufs[dev] = n; + + for (i = 0; i < dev_nbufs[dev]; i++) + { + dev_counts[dev][i] = 0; + } + + bufferalloc_done[dev] = 1; +} + +static void +dma_init_buffers (int dev) +{ + RESET_WAIT_QUEUE (dev_sleeper[dev], dev_sleep_flag[dev]); + dev_underrun[dev] = 0; + + dev_busy[dev] = 1; + + bufferalloc_done[dev] = 0; + + dev_active[dev] = dev_qlen[dev] = dev_qtail[dev] = dev_qhead[dev] = 0; + dev_needs_restart[dev] = dev_started[dev] = 0; + dma_mode[dev] = DMODE_NONE; +} + +int +DMAbuf_open (int dev, int mode) +{ + int retval; + + if (dev >= num_dspdevs) + { + printk ("PCM device %d not installed.\n", dev); + return RET_ERROR (ENXIO); + } + + if (dev_busy[dev]) + return RET_ERROR (EBUSY); + + if (!dsp_devs[dev]) + { + printk ("DSP device %d not initialized\n", dev); + return RET_ERROR (ENXIO); + } + +#ifdef USE_RUNTIME_DMAMEM + sound_dma_malloc (dev); +#endif + + if (snd_raw_buf[dev][0] == NULL) + return RET_ERROR (ENOSPC); /* Memory allocation failed during boot */ + + if ((retval = dsp_devs[dev]->open (dev, mode)) < 0) + return retval; + + dev_modes[dev] = mode; + dev_subdivision[dev] = 0; + + dma_init_buffers (dev); + dsp_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_BITS, 8, 1); + dsp_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_CHANNELS, 1, 1); + dsp_devs[dev]->ioctl (dev, SOUND_PCM_WRITE_RATE, DSP_DEFAULT_SPEED, 1); + + return 0; +} + +static void +dma_reset (int dev) +{ + int retval; + unsigned long flags; + + DISABLE_INTR (flags); + dsp_devs[dev]->reset (dev); + dsp_devs[dev]->close (dev); + + if ((retval = dsp_devs[dev]->open (dev, dev_modes[dev])) < 0) + printk ("Sound: Reset failed - Can't reopen device\n"); + RESTORE_INTR (flags); + + dma_init_buffers (dev); + reorganize_buffers (dev); +} + +static int +dma_sync (int dev) +{ + unsigned long flags; + + if (dma_mode[dev] == DMODE_OUTPUT) + { + DISABLE_INTR (flags); + + while ((!(PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev]) || + dmabuf_interrupted[dev])) + && dev_qlen[dev]) + { + DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], 10 * HZ); + if (TIMED_OUT (dev_sleeper[dev], dev_sleep_flag[dev])) + return dev_qlen[dev]; + } + RESTORE_INTR (flags); + + /* + * Some devices such as GUS have huge amount of on board RAM for the + * audio data. We have to wait util the device has finished playing. + */ + + DISABLE_INTR (flags); + if (dsp_devs[dev]->has_output_drained) /* Device has hidden buffers */ + { + while (!(PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev]) || + dmabuf_interrupted[dev]) + && !dsp_devs[dev]->has_output_drained (dev)) + { + DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], HZ / 4); + } + } + RESTORE_INTR (flags); + } + return dev_qlen[dev]; +} + +int +DMAbuf_release (int dev, int mode) +{ + + if (!(PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev]) || + dmabuf_interrupted[dev]) + && (dma_mode[dev] == DMODE_OUTPUT)) + { + dma_sync (dev); + } + +#ifdef USE_RUNTIME_DMAMEM + sound_dma_free (dev); +#endif + + dsp_devs[dev]->reset (dev); + + dsp_devs[dev]->close (dev); + + dma_mode[dev] = DMODE_NONE; + dev_busy[dev] = 0; + + return 0; +} + +int +DMAbuf_getrdbuffer (int dev, char **buf, int *len) +{ + unsigned long flags; + int err = EIO; + + DISABLE_INTR (flags); + if (!dev_qlen[dev]) + { + if (dev_needs_restart[dev]) + { + dma_reset (dev); + dev_needs_restart[dev] = 0; + } + + if (dma_mode[dev] == DMODE_OUTPUT) /* Was output -> direction change */ + { + dma_sync (dev); + dma_reset (dev); + dma_mode[dev] = DMODE_NONE; + } + + if (!bufferalloc_done[dev]) + reorganize_buffers (dev); + + if (!dma_mode[dev]) + { + int err; + + if ((err = dsp_devs[dev]->prepare_for_input (dev, + dev_buffsize[dev], dev_nbufs[dev])) < 0) + { + RESTORE_INTR (flags); + return err; + } + dma_mode[dev] = DMODE_INPUT; + } + + if (!dev_active[dev]) + { + dsp_devs[dev]->start_input (dev, dev_buf_phys[dev][dev_qtail[dev]], + dev_buffsize[dev], 0, + !sound_dma_automode[dev] || + !dev_started[dev]); + dev_active[dev] = 1; + dev_started[dev] = 1; + } + + /* Wait for the next block */ + DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], 2 * HZ); + if (TIMED_OUT (dev_sleeper[dev], dev_sleep_flag[dev])) + { + printk ("Sound: DMA timed out - IRQ/DRQ config error?\n"); + err = EIO; + SET_ABORT_FLAG (dev_sleeper[dev], dev_sleep_flag[dev]); + } + else + err = EINTR; + } + RESTORE_INTR (flags); + + if (!dev_qlen[dev]) + return RET_ERROR (err); + + *buf = &dev_buf[dev][dev_qhead[dev]][dev_counts[dev][dev_qhead[dev]]]; + *len = dev_buffsize[dev] - dev_counts[dev][dev_qhead[dev]]; + + return dev_qhead[dev]; +} + +int +DMAbuf_rmchars (int dev, int buff_no, int c) +{ + int p = dev_counts[dev][dev_qhead[dev]] + c; + + if (p >= dev_buffsize[dev]) + { /* This buffer is now empty */ + dev_counts[dev][dev_qhead[dev]] = 0; + dev_qlen[dev]--; + dev_qhead[dev] = (dev_qhead[dev] + 1) % dev_nbufs[dev]; + } + else + dev_counts[dev][dev_qhead[dev]] = p; + + return 0; +} + +int +DMAbuf_read (int dev, snd_rw_buf * user_buf, int count) +{ + char *dmabuf; + int buff_no, c, err; + + /* + * This routine returns at most 'count' bytes from the dsp input buffers. + * Returns negative value if there is an error. + */ + + if ((buff_no = DMAbuf_getrdbuffer (dev, &dmabuf, &c)) < 0) + return buff_no; + + if (c > count) + c = count; + + COPY_TO_USER (user_buf, 0, dmabuf, c); + + if ((err = DMAbuf_rmchars (dev, buff_no, c)) < 0) + return err; + return c; + +} + +int +DMAbuf_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SNDCTL_DSP_RESET: + dma_reset (dev); + return 0; + break; + + case SNDCTL_DSP_SYNC: + dma_sync (dev); + dma_reset (dev); + return 0; + break; + + case SNDCTL_DSP_GETBLKSIZE: + if (!bufferalloc_done[dev]) + reorganize_buffers (dev); + + return IOCTL_OUT (arg, dev_buffsize[dev]); + break; + + case SNDCTL_DSP_SUBDIVIDE: + { + int fact = IOCTL_IN (arg); + + if (fact == 0) + { + fact = dev_subdivision[dev]; + if (fact == 0) + fact = 1; + return IOCTL_OUT (arg, fact); + } + + if (dev_subdivision[dev] != 0) /* Too late to change */ + return RET_ERROR (EINVAL); + + if (fact > MAX_REALTIME_FACTOR) + return RET_ERROR (EINVAL); + + if (fact != 1 && fact != 2 && fact != 4 && fact != 8 && fact != 16) + return RET_ERROR (EINVAL); + + dev_subdivision[dev] = fact; + return IOCTL_OUT (arg, fact); + } + break; + + default: + return dsp_devs[dev]->ioctl (dev, cmd, arg, local); + } + + /* NOTREACHED */ + return RET_ERROR (EIO); +} + +int +DMAbuf_getwrbuffer (int dev, char **buf, int *size) +{ + unsigned long flags; + int err = EIO; + + if (dma_mode[dev] == DMODE_INPUT) /* Was input -> Direction change */ + { + dma_reset (dev); + dma_mode[dev] = DMODE_NONE; + } + else if (dev_needs_restart[dev]) /* Restart buffering */ + { + dma_sync (dev); + dma_reset (dev); + } + + dev_needs_restart[dev] = 0; + + if (!bufferalloc_done[dev]) + reorganize_buffers (dev); + + if (!dma_mode[dev]) + { + int err; + + dma_mode[dev] = DMODE_OUTPUT; + if ((err = dsp_devs[dev]->prepare_for_output (dev, + dev_buffsize[dev], dev_nbufs[dev])) < 0) + return err; + } + + + DISABLE_INTR (flags); + + RESET_WAIT_QUEUE (dev_sleeper[dev], dev_sleep_flag[dev]); + + if (dev_qlen[dev] == dev_nbufs[dev]) + { + if (!dev_active[dev]) + { + printk ("Soundcard warning: DMA not activated %d/%d\n", + dev_qlen[dev], dev_nbufs[dev]); + return RET_ERROR (EIO); + } + + /* Wait for free space */ + DO_SLEEP (dev_sleeper[dev], dev_sleep_flag[dev], 2 * HZ); + if (TIMED_OUT (dev_sleeper[dev], dev_sleep_flag[dev])) + { + printk ("Sound: DMA timed out - IRQ/DRQ config error?\n"); + err = EIO; + SET_ABORT_FLAG (dev_sleeper[dev], dev_sleep_flag[dev]); + } + else if (PROCESS_ABORTING (dev_sleeper[dev], dev_sleep_flag[dev])) + err = EINTR; + } + RESTORE_INTR (flags); + + if (dev_qlen[dev] == dev_nbufs[dev]) + return RET_ERROR (err); /* We have got signal (?) */ + + *buf = dev_buf[dev][dev_qtail[dev]]; + *size = dev_buffsize[dev]; + dev_counts[dev][dev_qtail[dev]] = 0; + + return dev_qtail[dev]; +} + +int +DMAbuf_start_output (int dev, int buff_no, int l) +{ + if (buff_no != dev_qtail[dev]) + printk ("Soundcard warning: DMA buffers out of sync %d != %d\n", buff_no, dev_qtail[dev]); + + dev_qlen[dev]++; + + dev_counts[dev][dev_qtail[dev]] = l; + + dev_needs_restart[dev] = (l != dev_buffsize[dev]) && + (sound_dma_automode[dev] || dsp_devs[dev]->flags & NEEDS_RESTART); + + dev_qtail[dev] = (dev_qtail[dev] + 1) % dev_nbufs[dev]; + + if (!dev_active[dev]) + { + dev_active[dev] = 1; + dsp_devs[dev]->output_block (dev, dev_buf_phys[dev][dev_qhead[dev]], + dev_counts[dev][dev_qhead[dev]], 0, + !sound_dma_automode[dev] || !dev_started[dev]); + dev_started[dev] = 1; + } + + return 0; +} + +int +DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode) +{ + int chan = sound_dsp_dmachan[dev]; + unsigned long flags; + + /* + * This function is not as portable as it should be. + */ + + /* + * The count must be one less than the actual size. This is handled by + * set_dma_addr() + */ + + if (sound_dma_automode[dev]) + { /* Auto restart mode. Transfer the whole + * buffer */ +#ifdef linux + DISABLE_INTR (flags); + disable_dma (chan); + clear_dma_ff (chan); + set_dma_mode (chan, dma_mode | DMA_AUTOINIT); + set_dma_addr (chan, snd_raw_buf_phys[dev][0]); + set_dma_count (chan, sound_buffsizes[dev]); + enable_dma (chan); + RESTORE_INTR (flags); +#else /* linux */ + +#ifdef __386BSD__ + printk ("sound: Invalid DMA mode for device %d\n", dev); + + isa_dmastart ((dma_mode == DMA_MODE_READ) ? B_READ : B_WRITE, + (caddr_t)snd_raw_buf_phys[dev][0], + sound_buffsizes[dev], + chan); +#else /* __386BSD__ */ +#if defined(ISC) || defined(SCO) || defined(SVR42) +#ifndef DMAMODE_AUTO + printk ("sound: Invalid DMA mode for device %d\n", dev); +#endif /* DMAMODE_AUTO */ + dma_param (chan, ((dma_mode == DMA_MODE_READ) ? DMA_Rdmode : DMA_Wrmode) +#ifdef DMAMODE_AUTO + | DMAMODE_AUTO +#endif /* DMAMODE_AUTO */ + , + snd_raw_buf_phys[dev][0], count); + dma_enable (chan); +#else /* SYSV */ +#error This routine is not valid for this OS. +#endif /* SYSV */ +#endif /* __386BSD__ */ + +#endif /* linux */ + } + else + { +#ifdef linux + DISABLE_INTR (flags); + disable_dma (chan); + clear_dma_ff (chan); + set_dma_mode (chan, dma_mode); + set_dma_addr (chan, physaddr); + set_dma_count (chan, count); + enable_dma (chan); + RESTORE_INTR (flags); +#else /* linux */ +#ifdef __386BSD__ + isa_dmastart ((dma_mode == DMA_MODE_READ) ? B_READ : B_WRITE, + (caddr_t)physaddr, + count, + chan); +#else /* __386BSD__ */ + +#if defined(ISC) || defined(SCO) || defined(SVR42) + dma_param (chan, ((dma_mode == DMA_MODE_READ) ? DMA_Rdmode : DMA_Wrmode), + physaddr, count); + dma_enable (chan); +#else /* SYSV */ +#error This routine is not valid for this OS. +#endif /* SYSV */ +#endif /* __386BSD__ */ + +#endif /* linux */ + } + + return count; +} + +long +DMAbuf_init (long mem_start) +{ + int i; + + /* + * In this version the DMA buffer allocation is done by sound_mem_init() + * which is called by init/main.c + */ + + for (i = 0; i < MAX_DSP_DEV; i++) + { + dev_qlen[i] = 0; + dev_qhead[i] = 0; + dev_qtail[i] = 0; + dev_active[i] = 0; + dev_busy[i] = 0; + bufferalloc_done[i] = 0; + } + + return mem_start; +} + +void +DMAbuf_outputintr (int dev, int underrun_flag) +{ + unsigned long flags; + + dev_qlen[dev]--; + dev_qhead[dev] = (dev_qhead[dev] + 1) % dev_nbufs[dev]; + dev_active[dev] = 0; + + if (dev_qlen[dev]) + { + dsp_devs[dev]->output_block (dev, dev_buf_phys[dev][dev_qhead[dev]], + dev_counts[dev][dev_qhead[dev]], 1, + !sound_dma_automode[dev]); + dev_active[dev] = 1; + } + else if (underrun_flag) + { + dev_underrun[dev]++; + dsp_devs[dev]->halt_xfer (dev); + dev_needs_restart[dev] = (sound_dma_automode[dev] || + dsp_devs[dev]->flags & NEEDS_RESTART); + } + + DISABLE_INTR (flags); + if (SOMEONE_WAITING (dev_sleeper[dev], dev_sleep_flag[dev])) + { + WAKE_UP (dev_sleeper[dev], dev_sleep_flag[dev]); + } + RESTORE_INTR (flags); +} + +void +DMAbuf_inputintr (int dev) +{ + unsigned long flags; + + if (!dev_busy[dev]) + { + dsp_devs[dev]->close (dev); + } + else if (dev_qlen[dev] == (dev_nbufs[dev] - 1)) + { + printk ("Sound: Recording overrun\n"); + dev_underrun[dev]++; + dsp_devs[dev]->halt_xfer (dev); + dev_active[dev] = 0; + dev_needs_restart[dev] = sound_dma_automode[dev]; + } + else + { + dev_qlen[dev]++; + dev_qtail[dev] = (dev_qtail[dev] + 1) % dev_nbufs[dev]; + + dsp_devs[dev]->start_input (dev, dev_buf_phys[dev][dev_qtail[dev]], + dev_buffsize[dev], 1, + !sound_dma_automode[dev]); + dev_active[dev] = 1; + } + + DISABLE_INTR (flags); + if (SOMEONE_WAITING (dev_sleeper[dev], dev_sleep_flag[dev])) + { + WAKE_UP (dev_sleeper[dev], dev_sleep_flag[dev]); + } + RESTORE_INTR (flags); +} + +int +DMAbuf_open_dma (int dev) +{ + unsigned long flags; + int chan = sound_dsp_dmachan[dev]; + + if (ALLOC_DMA_CHN (chan)) + { + printk ("Unable to grab DMA%d for the audio driver\n", chan); + return 0; + } + + DISABLE_INTR (flags); +#ifdef linux + disable_dma (chan); + clear_dma_ff (chan); +#endif + RESTORE_INTR (flags); + + return 1; +} + +void +DMAbuf_close_dma (int dev) +{ + int chan = sound_dsp_dmachan[dev]; + + DMAbuf_reset_dma (chan); + RELEASE_DMA_CHN (chan); +} + +void +DMAbuf_reset_dma (int chan) +{ +} + +/* + * The sound_mem_init() is called by mem_init() immediately after mem_map is + * initialized and before free_page_list is created. + * + * This routine allocates DMA buffers at the end of available physical memory ( + * <16M) and marks pages reserved at mem_map. + */ + +#else +/* Stub versions if audio services not included */ + +int +DMAbuf_open (int dev, int mode) +{ + return RET_ERROR (ENXIO); +} + +int +DMAbuf_release (int dev, int mode) +{ + return 0; +} + +int +DMAbuf_read (int dev, snd_rw_buf * user_buf, int count) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_getwrbuffer (int dev, char **buf, int *size) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_getrdbuffer (int dev, char **buf, int *len) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_rmchars (int dev, int buff_no, int c) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_start_output (int dev, int buff_no, int l) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + return RET_ERROR (EIO); +} + +long +DMAbuf_init (long mem_start) +{ + return mem_start; +} + +int +DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_open_dma (int chan) +{ + return RET_ERROR (ENXIO); +} + +void +DMAbuf_close_dma (int chan) +{ + return; +} + +void +DMAbuf_reset_dma (int chan) +{ + return; +} + +void +DMAbuf_inputintr (int dev) +{ + return; +} + +void +DMAbuf_outputintr (int dev, int underrun_flag) +{ + return; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/finetune.h b/sys/i386/isa/sound/finetune.h new file mode 100644 index 0000000..59e76fe --- /dev/null +++ b/sys/i386/isa/sound/finetune.h @@ -0,0 +1,50 @@ +#ifdef SEQUENCER_C +/* + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + + unsigned short finetune_table[128] = + { +/* 0 */ 9439, 9447, 9456, 9464, 9473, 9481, 9490, 9499, +/* 8 */ 9507, 9516, 9524, 9533, 9542, 9550, 9559, 9567, +/* 16 */ 9576, 9585, 9593, 9602, 9611, 9619, 9628, 9637, +/* 24 */ 9645, 9654, 9663, 9672, 9680, 9689, 9698, 9707, +/* 32 */ 9715, 9724, 9733, 9742, 9750, 9759, 9768, 9777, +/* 40 */ 9786, 9795, 9803, 9812, 9821, 9830, 9839, 9848, +/* 48 */ 9857, 9866, 9874, 9883, 9892, 9901, 9910, 9919, +/* 56 */ 9928, 9937, 9946, 9955, 9964, 9973, 9982, 9991, +/* 64 */ 10000, 10009, 10018, 10027, 10036, 10045, 10054, 10063, +/* 72 */ 10072, 10082, 10091, 10100, 10109, 10118, 10127, 10136, +/* 80 */ 10145, 10155, 10164, 10173, 10182, 10191, 10201, 10210, +/* 88 */ 10219, 10228, 10237, 10247, 10256, 10265, 10274, 10284, +/* 96 */ 10293, 10302, 10312, 10321, 10330, 10340, 10349, 10358, +/* 104 */ 10368, 10377, 10386, 10396, 10405, 10415, 10424, 10433, +/* 112 */ 10443, 10452, 10462, 10471, 10481, 10490, 10499, 10509, +/* 120 */ 10518, 10528, 10537, 10547, 10556, 10566, 10576, 10585 + }; +#else + extern unsigned short finetune_table[128]; +#endif diff --git a/sys/i386/isa/sound/gus_card.c b/sys/i386/isa/sound/gus_card.c new file mode 100644 index 0000000..f3e8119 --- /dev/null +++ b/sys/i386/isa/sound/gus_card.c @@ -0,0 +1,143 @@ +/* + * sound/gus_card.c + * + * Detection routine for the Gravis Ultrasound. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_GUS) + +#include "gus_hw.h" + +void gusintr (int); + +int gus_base, gus_irq, gus_dma; + +long +attach_gus_card (long mem_start, struct address_info *hw_config) +{ + int io_addr; + + snd_set_irq_handler (hw_config->irq, gusintr); + + if (gus_wave_detect (hw_config->io_base)) /* Try first the default */ + { + mem_start = gus_wave_init (mem_start, hw_config->irq, hw_config->dma); +#ifndef EXCLUDE_MIDI + mem_start = gus_midi_init (mem_start); +#endif + return mem_start; + } + +#ifndef EXCLUDE_GUS_IODETECT + + /* + * Look at the possible base addresses (0x2X0, X=1, 2, 3, 4, 5, 6) + */ + + for (io_addr = 0x210; io_addr <= 0x260; io_addr += 0x10) + if (io_addr != hw_config->io_base) /* Already tested */ + if (gus_wave_detect (io_addr)) + { + printk (" WARNING! GUS found at %x, config was %x ", io_addr, hw_config->io_base); + mem_start = gus_wave_init (mem_start, hw_config->irq, hw_config->dma); +#ifndef EXCLUDE_MIDI + mem_start = gus_midi_init (mem_start); +#endif + return mem_start; + } + +#endif + + return mem_start; /* Not detected */ +} + +int +probe_gus (struct address_info *hw_config) +{ + int io_addr; + + if (gus_wave_detect (hw_config->io_base)) + return 1; + +#ifndef EXCLUDE_GUS_IODETECT + + /* + * Look at the possible base addresses (0x2X0, X=1, 2, 3, 4, 5, 6) + */ + + for (io_addr = 0x210; io_addr <= 0x260; io_addr += 0x10) + if (io_addr != hw_config->io_base) /* Already tested */ + if (gus_wave_detect (io_addr)) + return 1; + +#endif + + return 0; +} + +void +gusintr (int unit) +{ + unsigned char src; + +#ifdef linux + sti (); +#endif + + while (1) + { + if (!(src = INB (u_IrqStatus))) + return; + + if (src & DMA_TC_IRQ) + { + guswave_dma_irq (); + } + + if (src & (MIDI_TX_IRQ | MIDI_RX_IRQ)) + { +#ifndef EXCLUDE_MIDI + gus_midi_interrupt (0); +#endif + } + + if (src & (GF1_TIMER1_IRQ | GF1_TIMER2_IRQ)) + { + printk ("T"); + gus_write8 (0x45, 0); /* Timer control */ + } + + if (src & (WAVETABLE_IRQ | ENVELOPE_IRQ)) + { + gus_voice_irq (); + } + } +} + +#endif diff --git a/sys/i386/isa/sound/gus_hw.h b/sys/i386/isa/sound/gus_hw.h new file mode 100644 index 0000000..b3dc32f --- /dev/null +++ b/sys/i386/isa/sound/gus_hw.h @@ -0,0 +1,52 @@ +/* + * $Id$ + */ +/* + * I/O addresses + */ + +#define u_Base (gus_base + 0x000) +#define u_Mixer u_Base +#define u_Status (gus_base + 0x006) +#define u_TimerControl (gus_base + 0x008) +#define u_TimerData (gus_base + 0x009) +#define u_IRQDMAControl (gus_base + 0x00b) +#define u_MidiControl (gus_base + 0x100) +#define MIDI_RESET 0x03 +#define MIDI_ENABLE_XMIT 0x20 +#define MIDI_ENABLE_RCV 0x80 +#define u_MidiStatus u_MidiControl +#define MIDI_RCV_FULL 0x01 +#define MIDI_XMIT_EMPTY 0x02 +#define MIDI_FRAME_ERR 0x10 +#define MIDI_OVERRUN 0x20 +#define MIDI_IRQ_PEND 0x80 +#define u_MidiData (gus_base + 0x101) +#define u_Voice (gus_base + 0x102) +#define u_Command (gus_base + 0x103) +#define u_DataLo (gus_base + 0x104) +#define u_DataHi (gus_base + 0x105) +#define u_MixData (gus_base + 0x106) /* Rev. 3.7+ mixing */ +#define u_MixSelect (gus_base + 0x506) /* registers. */ +#define u_IrqStatus u_Status +# define MIDI_TX_IRQ 0x01 /* pending MIDI xmit IRQ */ +# define MIDI_RX_IRQ 0x02 /* pending MIDI recv IRQ */ +# define GF1_TIMER1_IRQ 0x04 /* general purpose timer */ +# define GF1_TIMER2_IRQ 0x08 /* general purpose timer */ +# define WAVETABLE_IRQ 0x20 /* pending wavetable IRQ */ +# define ENVELOPE_IRQ 0x40 /* pending volume envelope IRQ */ +# define DMA_TC_IRQ 0x80 /* pending dma tc IRQ */ + +#define ICS2101 1 +# define ICS_MIXDEVS 6 +# define DEV_MIC 0 +# define DEV_LINE 1 +# define DEV_CD 2 +# define DEV_GF1 3 +# define DEV_UNUSED 4 +# define DEV_VOL 5 + +# define CHN_LEFT 0 +# define CHN_RIGHT 1 +#define CS4231 2 +#define u_DRAMIO (gus_base + 0x107) diff --git a/sys/i386/isa/sound/gus_linearvol.h b/sys/i386/isa/sound/gus_linearvol.h new file mode 100644 index 0000000..6e41f09 --- /dev/null +++ b/sys/i386/isa/sound/gus_linearvol.h @@ -0,0 +1,21 @@ +/* + * $Id$ + */ +static unsigned short gus_linearvol[128] = { + 0x0000, 0x08ff, 0x09ff, 0x0a80, 0x0aff, 0x0b40, 0x0b80, 0x0bc0, + 0x0bff, 0x0c20, 0x0c40, 0x0c60, 0x0c80, 0x0ca0, 0x0cc0, 0x0ce0, + 0x0cff, 0x0d10, 0x0d20, 0x0d30, 0x0d40, 0x0d50, 0x0d60, 0x0d70, + 0x0d80, 0x0d90, 0x0da0, 0x0db0, 0x0dc0, 0x0dd0, 0x0de0, 0x0df0, + 0x0dff, 0x0e08, 0x0e10, 0x0e18, 0x0e20, 0x0e28, 0x0e30, 0x0e38, + 0x0e40, 0x0e48, 0x0e50, 0x0e58, 0x0e60, 0x0e68, 0x0e70, 0x0e78, + 0x0e80, 0x0e88, 0x0e90, 0x0e98, 0x0ea0, 0x0ea8, 0x0eb0, 0x0eb8, + 0x0ec0, 0x0ec8, 0x0ed0, 0x0ed8, 0x0ee0, 0x0ee8, 0x0ef0, 0x0ef8, + 0x0eff, 0x0f04, 0x0f08, 0x0f0c, 0x0f10, 0x0f14, 0x0f18, 0x0f1c, + 0x0f20, 0x0f24, 0x0f28, 0x0f2c, 0x0f30, 0x0f34, 0x0f38, 0x0f3c, + 0x0f40, 0x0f44, 0x0f48, 0x0f4c, 0x0f50, 0x0f54, 0x0f58, 0x0f5c, + 0x0f60, 0x0f64, 0x0f68, 0x0f6c, 0x0f70, 0x0f74, 0x0f78, 0x0f7c, + 0x0f80, 0x0f84, 0x0f88, 0x0f8c, 0x0f90, 0x0f94, 0x0f98, 0x0f9c, + 0x0fa0, 0x0fa4, 0x0fa8, 0x0fac, 0x0fb0, 0x0fb4, 0x0fb8, 0x0fbc, + 0x0fc0, 0x0fc4, 0x0fc8, 0x0fcc, 0x0fd0, 0x0fd4, 0x0fd8, 0x0fdc, + 0x0fe0, 0x0fe4, 0x0fe8, 0x0fec, 0x0ff0, 0x0ff4, 0x0ff8, 0x0ffc +}; diff --git a/sys/i386/isa/sound/gus_midi.c b/sys/i386/isa/sound/gus_midi.c new file mode 100644 index 0000000..15931cc --- /dev/null +++ b/sys/i386/isa/sound/gus_midi.c @@ -0,0 +1,284 @@ +/* + * sound/gus2_midi.c + * + * The low level driver for the GUS Midi Interface. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "gus_hw.h" + +#if !defined(EXCLUDE_GUS) && !defined(EXCLUDE_MIDI) + +static int midi_busy = 0, input_opened = 0; +static int my_dev; +static int output_used = 0; +static volatile unsigned char gus_midi_control; + +static void (*midi_input_intr) (int dev, unsigned char data); + +static unsigned char tmp_queue[256]; +static volatile int qlen; +static volatile unsigned char qhead, qtail; +extern int gus_base, gus_irq, gus_dma; + +#define GUS_MIDI_STATUS() INB(u_MidiStatus) + +static int +gus_midi_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) +) +{ + + if (midi_busy) + { + printk ("GUS: Midi busy\n"); + return RET_ERROR (EBUSY); + } + + OUTB (MIDI_RESET, u_MidiControl); + gus_delay (); + + gus_midi_control = 0; + input_opened = 0; + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + { + gus_midi_control |= MIDI_ENABLE_RCV; + input_opened = 1; + } + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + { + gus_midi_control |= MIDI_ENABLE_XMIT; + } + + OUTB (gus_midi_control, u_MidiControl); /* Enable */ + + midi_busy = 1; + qlen = qhead = qtail = output_used = 0; + midi_input_intr = input; + + return 0; +} + +static int +dump_to_midi (unsigned char midi_byte) +{ + unsigned long flags; + int ok = 0; + + output_used = 1; + + DISABLE_INTR (flags); + + if (GUS_MIDI_STATUS () & MIDI_XMIT_EMPTY) + { + ok = 1; + OUTB (midi_byte, u_MidiData); + } + else + { + /* Enable Midi xmit interrupts (again) */ + gus_midi_control |= MIDI_ENABLE_XMIT; + OUTB (gus_midi_control, u_MidiControl); + } + + RESTORE_INTR (flags); + return ok; +} + +static void +gus_midi_close (int dev) +{ + /* Reset FIFO pointers, disable intrs */ + + OUTB (MIDI_RESET, u_MidiControl); + midi_busy = 0; +} + +static int +gus_midi_out (int dev, unsigned char midi_byte) +{ + + unsigned long flags; + + /* + * Drain the local queue first + */ + + DISABLE_INTR (flags); + + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + + /* + * Output the byte if the local queue is empty. + */ + + if (!qlen) + if (dump_to_midi (midi_byte)) + return 1; /* OK */ + + /* + * Put to the local queue + */ + + if (qlen >= 256) + return 0; /* Local queue full */ + + DISABLE_INTR (flags); + + tmp_queue[qtail] = midi_byte; + qlen++; + qtail++; + + RESTORE_INTR (flags); + + return 1; +} + +static int +gus_midi_start_read (int dev) +{ + return 0; +} + +static int +gus_midi_end_read (int dev) +{ + return 0; +} + +static int +gus_midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +gus_midi_kick (int dev) +{ +} + +static int +gus_midi_buffer_status (int dev) +{ + unsigned long flags; + + if (!output_used) + return 0; + + DISABLE_INTR (flags); + + if (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + + return (qlen > 0) | !(GUS_MIDI_STATUS () & MIDI_XMIT_EMPTY); +} + +static struct midi_operations gus_midi_operations = +{ + {"Gravis UltraSound", 0, 0, SNDCARD_GUS}, + gus_midi_open, + gus_midi_close, + gus_midi_ioctl, + gus_midi_out, + gus_midi_start_read, + gus_midi_end_read, + gus_midi_kick, + NULL, /* command */ + gus_midi_buffer_status +}; + +long +gus_midi_init (long mem_start) +{ + OUTB (MIDI_RESET, u_MidiControl); + + my_dev = num_midis; + midi_devs[num_midis++] = &gus_midi_operations; + return mem_start; +} + +void +gus_midi_interrupt (int dummy) +{ + unsigned char stat, data; + unsigned long flags; + + DISABLE_INTR (flags); + + stat = GUS_MIDI_STATUS (); + + if (stat & MIDI_RCV_FULL) + { + data = INB (u_MidiData); + if (input_opened) + midi_input_intr (my_dev, data); + } + + if (stat & MIDI_XMIT_EMPTY) + { + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + if (!qlen) + { + /* Disable Midi output interrupts, since no data in the buffer */ + gus_midi_control &= ~MIDI_ENABLE_XMIT; + OUTB (gus_midi_control, u_MidiControl); + } + } + + if (stat & MIDI_FRAME_ERR) + printk ("Midi framing error\n"); + if (stat & MIDI_OVERRUN && input_opened) + printk ("GUS: Midi input overrun\n"); + + RESTORE_INTR (flags); +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/gus_vol.c b/sys/i386/isa/sound/gus_vol.c new file mode 100644 index 0000000..0ec1417 --- /dev/null +++ b/sys/i386/isa/sound/gus_vol.c @@ -0,0 +1,148 @@ +/* + * gus_vol.c - Compute volume for GUS. + * + * Greg Lee 1993. + * $Id$ + */ +#include "sound_config.h" +#ifndef EXCLUDE_GUS +#include "gus_linearvol.h" + +#define GUS_VOLUME gus_wave_volume + + +extern int gus_wave_volume; + +/* + * Calculate gus volume from note velocity, main volume, expression, and + * intrinsic patch volume given in patch library. Expression is multiplied + * in, so it emphasizes differences in note velocity, while main volume is + * added in -- I don't know whether this is right, but it seems reasonable to + * me. (In the previous stage, main volume controller messages were changed + * to expression controller messages, if they were found to be used for + * dynamic volume adjustments, so here, main volume can be assumed to be + * constant throughout a song.) + * + * Intrinsic patch volume is added in, but if over 64 is also multiplied in, so + * we can give a big boost to very weak voices like nylon guitar and the + * basses. The normal value is 64. Strings are assigned lower values. + */ +unsigned short +gus_adagio_vol (int vel, int mainv, int xpn, int voicev) +{ + int i, m, n, x; + + + /* + * A voice volume of 64 is considered neutral, so adjust the main volume if + * something other than this neutral value was assigned in the patch + * library. + */ + x = 256 + 6 * (voicev - 64); + + /* + * Boost expression by voice volume above neutral. + */ + if (voicev > 65) + xpn += voicev - 64; + xpn += (voicev - 64) / 2; + + /* + * Combine multiplicative and level components. + */ + x = vel * xpn * 6 + (voicev / 4) * x; + +#ifdef GUS_VOLUME + /* + * Further adjustment by installation-specific master volume control + * (default 60). + */ + x = (x * GUS_VOLUME * GUS_VOLUME) / 10000; +#endif + +#ifdef GUS_USE_CHN_MAIN_VOLUME + /* + * Experimental support for the channel main volume + */ + + mainv = (mainv / 2) + 64; /* Scale to 64 to 127 */ + x = (x * mainv * mainv) / 16384; +#endif + + if (x < 2) + return (0); + else if (x >= 65535) + return ((15 << 8) | 255); + + /* + * Convert to gus's logarithmic form with 4 bit exponent i and 8 bit + * mantissa m. + */ + n = x; + i = 7; + if (n < 128) + { + while (i > 0 && n < (1 << i)) + i--; + } + else + while (n > 255) + { + n >>= 1; + i++; + } + /* + * Mantissa is part of linear volume not expressed in exponent. (This is + * not quite like real logs -- I wonder if it's right.) + */ + m = x - (1 << i); + + /* + * Adjust mantissa to 8 bits. + */ + if (m > 0) + { + if (i > 8) + m >>= i - 8; + else if (i < 8) + m <<= 8 - i; + } + + return ((i << 8) + m); +} + +/* + * Volume-values are interpreted as linear values. Volume is based on the + * value supplied with SEQ_START_NOTE(), channel main volume (if compiled in) + * and the volume set by the mixer-device (default 60%). + */ + +unsigned short +gus_linear_vol (int vol, int mainvol) +{ + int mixer_mainvol; + + if (vol <= 0) + vol = 0; + else if (vol >= 127) + vol = 127; + +#ifdef GUS_VOLUME + mixer_mainvol = GUS_VOLUME; +#else + mixer_mainvol = 100; +#endif + +#ifdef GUS_USE_CHN_MAIN_VOLUME + if (mainvol <= 0) + mainvol = 0; + else if (mainvol >= 127) + mainvol = 127; +#else + mainvol = 128; +#endif + + return gus_linearvol[(((vol * mainvol) / 128) * mixer_mainvol) / 100]; +} + +#endif diff --git a/sys/i386/isa/sound/gus_wave.c b/sys/i386/isa/sound/gus_wave.c new file mode 100644 index 0000000..621286f --- /dev/null +++ b/sys/i386/isa/sound/gus_wave.c @@ -0,0 +1,3576 @@ +/* + * sound/gus_wave.c + * + * Driver for the Gravis UltraSound wave table synth. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" +#ifdef __FreeBSD__ +#include <machine/ultrasound.h> +#else +#include "ultrasound.h" +#endif +#include "gus_hw.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_GUS) + +#define MAX_SAMPLE 128 +#define MAX_PATCH 256 + +struct voice_info + { + unsigned long orig_freq; + unsigned long current_freq; + unsigned long mode; + int bender; + int bender_range; + int panning; + int midi_volume; + unsigned int initial_volume; + unsigned int current_volume; + int loop_irq_mode, loop_irq_parm; +#define LMODE_FINISH 1 +#define LMODE_PCM 2 +#define LMODE_PCM_STOP 3 + int volume_irq_mode, volume_irq_parm; +#define VMODE_HALT 1 +#define VMODE_ENVELOPE 2 +#define VMODE_START_NOTE 3 + + int env_phase; + unsigned char env_rate[6]; + unsigned char env_offset[6]; + + /* + * Volume computation parameters for gus_adagio_vol() + */ + int main_vol, expression_vol, patch_vol; + + /* Variables for "Ultraclick" removal */ + int dev_pending, note_pending, volume_pending, sample_pending; + char kill_pending; + long offset_pending; + + }; + +extern int gus_base; +extern int gus_irq, gus_dma; +extern char *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern int snd_raw_count[MAX_DSP_DEV]; +static long gus_mem_size = 0; +static long free_mem_ptr = 0; +static int gus_busy = 0; +static int nr_voices = 0; +static int gus_devnum = 0; +static int volume_base, volume_scale, volume_method; +static int gus_line_vol = 100, gus_mic_vol = 0; +static int gus_recmask = SOUND_MASK_MIC; +static int recording_active = 0; + +int gus_wave_volume = 60; +int gus_pcm_volume = 80; +static unsigned char mix_image = 0x00; + +/* + * Current version of this driver doesn't allow synth and PCM functions + * at the same time. The active_device specifies the active driver + */ +static int active_device = 0; + +#define GUS_DEV_WAVE 1 /* + * * * Wave table synth */ +#define GUS_DEV_PCM_DONE 2 /* + * * * PCM device, transfer done */ +#define GUS_DEV_PCM_CONTINUE 3 /* + * * * PCM device, transfer the + * second * * * chn */ + +static int gus_sampling_speed; +static int gus_sampling_channels; +static int gus_sampling_bits; + +DEFINE_WAIT_QUEUE (dram_sleeper, dram_sleep_flag); + +/* + * Variables and buffers for PCM output + */ +#define MAX_PCM_BUFFERS (32*MAX_REALTIME_FACTOR) /* + * * * Don't + * * * change + * + */ + +static int pcm_bsize, /* + * Current blocksize + */ + pcm_nblk, /* + * Current # of blocks + */ + pcm_banksize; /* + + + * * * * # bytes allocated for channels */ +static int pcm_datasize[MAX_PCM_BUFFERS]; /* + + + * * * * Actual # of bytes + * in blk * */ +static volatile int pcm_head, pcm_tail, pcm_qlen; /* + + + * * * * DRAM queue + * */ +static volatile int pcm_active; +static int pcm_opened = 0; +static int pcm_current_dev; +static int pcm_current_block; +static unsigned long pcm_current_buf; +static int pcm_current_count; +static int pcm_current_intrflag; + +struct voice_info voices[32]; + +static int freq_div_table[] = +{ + 44100, /* + * 14 + */ + 41160, /* + * 15 + */ + 38587, /* + * 16 + */ + 36317, /* + * 17 + */ + 34300, /* + * 18 + */ + 32494, /* + * 19 + */ + 30870, /* + * 20 + */ + 29400, /* + * 21 + */ + 28063, /* + * 22 + */ + 26843, /* + * 23 + */ + 25725, /* + * 24 + */ + 24696, /* + * 25 + */ + 23746, /* + * 26 + */ + 22866, /* + * 27 + */ + 22050, /* + * 28 + */ + 21289, /* + * 29 + */ + 20580, /* + * 30 + */ + 19916, /* + * 31 + */ + 19293 /* + * 32 + */ +}; + +static struct patch_info *samples; +static long sample_ptrs[MAX_SAMPLE + 1]; +static int sample_map[32]; +static int free_sample; + + +static int patch_table[MAX_PATCH]; +static int patch_map[32]; + +static struct synth_info gus_info = +{"Gravis UltraSound", 0, SYNTH_TYPE_SAMPLE, SAMPLE_TYPE_GUS, 0, 16, 0, MAX_PATCH}; + +static void gus_poke (long addr, unsigned char data); +static void compute_and_set_volume (int voice, int volume, int ramp_time); +extern unsigned short gus_adagio_vol (int vel, int mainv, int xpn, int voicev); +extern unsigned short gus_linear_vol (int vol, int mainvol); +static void compute_volume (int voice, int volume); +static void do_volume_irq (int voice); +static void set_input_volumes (void); + +#define INSTANT_RAMP -1 /* + * * * Dont use ramping */ +#define FAST_RAMP 0 /* + * * * Fastest possible ramp */ + +static void +reset_sample_memory (void) +{ + int i; + + for (i = 0; i <= MAX_SAMPLE; i++) + sample_ptrs[i] = -1; + for (i = 0; i < 32; i++) + sample_map[i] = -1; + for (i = 0; i < 32; i++) + patch_map[i] = -1; + + gus_poke (0, 0); /* + * Put silence here + */ + gus_poke (1, 0); + + free_mem_ptr = 2; + free_sample = 0; + + for (i = 0; i < MAX_PATCH; i++) + patch_table[i] = -1; +} + +void +gus_delay (void) +{ + int i; + + for (i = 0; i < 7; i++) + INB (u_DRAMIO); +} + +static void +gus_poke (long addr, unsigned char data) +{ + unsigned long flags; + + DISABLE_INTR (flags); + OUTB (0x43, u_Command); + OUTB (addr & 0xff, u_DataLo); + OUTB ((addr >> 8) & 0xff, u_DataHi); + + OUTB (0x44, u_Command); + OUTB ((addr >> 16) & 0xff, u_DataHi); + OUTB (data, u_DRAMIO); + RESTORE_INTR (flags); +} + +static unsigned char +gus_peek (long addr) +{ + unsigned long flags; + unsigned char tmp; + + DISABLE_INTR (flags); + OUTB (0x43, u_Command); + OUTB (addr & 0xff, u_DataLo); + OUTB ((addr >> 8) & 0xff, u_DataHi); + + OUTB (0x44, u_Command); + OUTB ((addr >> 16) & 0xff, u_DataHi); + tmp = INB (u_DRAMIO); + RESTORE_INTR (flags); + + return tmp; +} + +void +gus_write8 (int reg, unsigned int data) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + OUTB (reg, u_Command); + OUTB ((unsigned char) (data & 0xff), u_DataHi); + + RESTORE_INTR (flags); +} + +unsigned char +gus_read8 (int reg) +{ + unsigned long flags; + unsigned char val; + + DISABLE_INTR (flags); + OUTB (reg | 0x80, u_Command); + val = INB (u_DataHi); + RESTORE_INTR (flags); + + return val; +} + +unsigned char +gus_look8 (int reg) +{ + unsigned long flags; + unsigned char val; + + DISABLE_INTR (flags); + OUTB (reg, u_Command); + val = INB (u_DataHi); + RESTORE_INTR (flags); + + return val; +} + +void +gus_write16 (int reg, unsigned int data) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + OUTB (reg, u_Command); + + OUTB ((unsigned char) (data & 0xff), u_DataLo); + OUTB ((unsigned char) ((data >> 8) & 0xff), u_DataHi); + + RESTORE_INTR (flags); +} + +unsigned short +gus_read16 (int reg) +{ + unsigned long flags; + unsigned char hi, lo; + + DISABLE_INTR (flags); + + OUTB (reg | 0x80, u_Command); + + lo = INB (u_DataLo); + hi = INB (u_DataHi); + + RESTORE_INTR (flags); + + return ((hi << 8) & 0xff00) | lo; +} + +void +gus_write_addr (int reg, unsigned long address, int is16bit) +{ + unsigned long hold_address; + + if (is16bit) + { + /* + * Special processing required for 16 bit patches + */ + + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + + gus_write16 (reg, (unsigned short) ((address >> 7) & 0xffff)); + gus_write16 (reg + 1, (unsigned short) ((address << 9) & 0xffff)); + /* Could writing twice fix problems with GUS_VOICE_POS() ? Lets try... */ + gus_delay (); + gus_write16 (reg, (unsigned short) ((address >> 7) & 0xffff)); + gus_write16 (reg + 1, (unsigned short) ((address << 9) & 0xffff)); +} + +static void +gus_select_voice (int voice) +{ + if (voice < 0 || voice > 31) + return; + + OUTB (voice, u_Voice); +} + +static void +gus_select_max_voices (int nvoices) +{ + if (nvoices < 14) + nvoices = 14; + if (nvoices > 32) + nvoices = 32; + + nr_voices = nvoices; + + gus_write8 (0x0e, (nvoices - 1) | 0xc0); +} + +static void +gus_voice_on (unsigned int mode) +{ + gus_write8 (0x00, (unsigned char) (mode & 0xfc)); + gus_delay (); + gus_write8 (0x00, (unsigned char) (mode & 0xfc)); +} + +static void +gus_voice_off (void) +{ + gus_write8 (0x00, gus_read8 (0x00) | 0x03); +} + +static void +gus_voice_mode (unsigned int m) +{ + unsigned char mode = (unsigned char) (m & 0xff); + + gus_write8 (0x00, (gus_read8 (0x00) & 0x03) | (mode & 0xfc)); /* + * Don't + * start + * or + * stop + * * + * voice + */ + gus_delay (); + gus_write8 (0x00, (gus_read8 (0x00) & 0x03) | (mode & 0xfc)); +} + +static void +gus_voice_freq (unsigned long freq) +{ + unsigned long divisor = freq_div_table[nr_voices - 14]; + unsigned short fc; + + fc = (unsigned short) (((freq << 9) + (divisor >> 1)) / divisor); + fc = fc << 1; + + gus_write16 (0x01, fc); +} + +static void +gus_voice_volume (unsigned int vol) +{ + gus_write8 (0x0d, 0x03); /* + * Stop ramp before setting volume + */ + gus_write16 (0x09, (unsigned short) (vol << 4)); +} + +static void +gus_voice_balance (unsigned int balance) +{ + gus_write8 (0x0c, (unsigned char) (balance & 0xff)); +} + +static void +gus_ramp_range (unsigned int low, unsigned int high) +{ + gus_write8 (0x07, (unsigned char) ((low >> 4) & 0xff)); + gus_write8 (0x08, (unsigned char) ((high >> 4) & 0xff)); +} + +static void +gus_ramp_rate (unsigned int scale, unsigned int rate) +{ + gus_write8 (0x06, (unsigned char) (((scale & 0x03) << 6) | (rate & 0x3f))); +} + +static void +gus_rampon (unsigned int m) +{ + unsigned char mode = (unsigned char) (m & 0xff); + + gus_write8 (0x0d, mode & 0xfc); + gus_delay (); + gus_write8 (0x0d, mode & 0xfc); +} + +static void +gus_ramp_mode (unsigned int m) +{ + unsigned char mode = (unsigned char) (m & 0xff); + + gus_write8 (0x0d, (gus_read8 (0x0d) & 0x03) | (mode & 0xfc)); /* + * Don't + * start + * or + * stop + * * + * ramping + */ + gus_delay (); + gus_write8 (0x0d, (gus_read8 (0x0d) & 0x03) | (mode & 0xfc)); +} + +static void +gus_rampoff (void) +{ + gus_write8 (0x0d, 0x03); +} + +static void +gus_set_voice_pos (int voice, long position) +{ + int sample_no; + + if ((sample_no = sample_map[voice]) != -1) + if (position < samples[sample_no].len) + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + voices[voice].offset_pending = position; + else + gus_write_addr (0x0a, sample_ptrs[sample_no] + position, + samples[sample_no].mode & WAVE_16_BITS); +} + +static void +gus_voice_init (int voice) +{ + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_volume (0); + gus_write_addr (0x0a, 0, 0); /* + * Set current position to 0 + */ + gus_write8 (0x00, 0x03); /* + * Voice off + */ + gus_write8 (0x0d, 0x03); /* + * Ramping off + */ + RESTORE_INTR (flags); + +} + +static void +gus_voice_init2 (int voice) +{ + voices[voice].panning = 0; + voices[voice].mode = 0; + voices[voice].orig_freq = 20000; + voices[voice].current_freq = 20000; + voices[voice].bender = 0; + voices[voice].bender_range = 200; + voices[voice].initial_volume = 0; + voices[voice].current_volume = 0; + voices[voice].loop_irq_mode = 0; + voices[voice].loop_irq_parm = 0; + voices[voice].volume_irq_mode = 0; + voices[voice].volume_irq_parm = 0; + voices[voice].env_phase = 0; + voices[voice].main_vol = 127; + voices[voice].patch_vol = 127; + voices[voice].expression_vol = 127; + voices[voice].sample_pending = -1; +} + +static void +step_envelope (int voice) +{ + unsigned vol, prev_vol, phase; + unsigned char rate; + long int flags; + + if (voices[voice].mode & WAVE_SUSTAIN_ON && voices[voice].env_phase == 2) + { + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_rampoff (); + RESTORE_INTR (flags); + return; /* + * Sustain + */ + } + + if (voices[voice].env_phase >= 5) + { + /* + * Shoot the voice off + */ + + gus_voice_init (voice); + return; + } + + prev_vol = voices[voice].current_volume; + phase = ++voices[voice].env_phase; + compute_volume (voice, voices[voice].midi_volume); + vol = voices[voice].initial_volume * voices[voice].env_offset[phase] / 255; + rate = voices[voice].env_rate[phase]; + + DISABLE_INTR (flags); + gus_select_voice (voice); + + gus_voice_volume (prev_vol); + + + gus_write8 (0x06, rate); /* + * Ramping rate + */ + + voices[voice].volume_irq_mode = VMODE_ENVELOPE; + + if (((vol - prev_vol) / 64) == 0) /* + * No significant volume change + */ + { + RESTORE_INTR (flags); + step_envelope (voice); /* + * Continue with the next phase + */ + return; + } + + if (vol > prev_vol) + { + if (vol >= (4096 - 64)) + vol = 4096 - 65; + gus_ramp_range (0, vol); + gus_rampon (0x20); /* + * Increasing, irq + */ + } + else + { + if (vol <= 64) + vol = 65; + gus_ramp_range (vol, 4030); + gus_rampon (0x60); /* + * Decreasing, irq + */ + } + voices[voice].current_volume = vol; + RESTORE_INTR (flags); +} + +static void +init_envelope (int voice) +{ + voices[voice].env_phase = -1; + voices[voice].current_volume = 64; + + step_envelope (voice); +} + +static void +start_release (int voice, long int flags) +{ + if (gus_read8 (0x00) & 0x03) + return; /* + * Voice already stopped + */ + + voices[voice].env_phase = 2; /* + * Will be incremented by step_envelope + */ + + voices[voice].current_volume = + voices[voice].initial_volume = + gus_read16 (0x09) >> 4; /* + * Get current volume + */ + + voices[voice].mode &= ~WAVE_SUSTAIN_ON; + gus_rampoff (); + RESTORE_INTR (flags); + step_envelope (voice); +} + +static void +gus_voice_fade (int voice) +{ + int instr_no = sample_map[voice], is16bits; + long int flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + + if (instr_no < 0 || instr_no > MAX_SAMPLE) + { + gus_write8 (0x00, 0x03); /* + * Hard stop + */ + RESTORE_INTR (flags); + return; + } + + is16bits = (samples[instr_no].mode & WAVE_16_BITS) ? 1 : 0; /* + * 8 or 16 + * bit + * samples + */ + + if (voices[voice].mode & WAVE_ENVELOPES) + { + start_release (voice, flags); + return; + } + + /* + * Ramp the volume down but not too quickly. + */ + if ((gus_read16 (0x09) >> 4) < 100) /* + * Get current volume + */ + { + gus_voice_off (); + gus_rampoff (); + gus_voice_init (voice); + return; + } + + gus_ramp_range (65, 4030); + gus_ramp_rate (2, 4); + gus_rampon (0x40 | 0x20); /* + * Down, once, irq + */ + voices[voice].volume_irq_mode = VMODE_HALT; + RESTORE_INTR (flags); +} + +static void +gus_reset (void) +{ + int i; + + gus_select_max_voices (24); + volume_base = 3071; + volume_scale = 4; + volume_method = VOL_METHOD_ADAGIO; + + for (i = 0; i < 32; i++) + { + gus_voice_init (i); /* + * Turn voice off + */ + gus_voice_init2 (i); + } + + INB (u_Status); /* + * Touch the status register + */ + + gus_look8 (0x41); /* + * Clear any pending DMA IRQs + */ + gus_look8 (0x49); /* + * Clear any pending sample IRQs + */ + gus_read8 (0x0f); /* + * Clear pending IRQs + */ + +} + +static void +gus_initialize (void) +{ + unsigned long flags; + register unsigned char dma_image, irq_image, tmp; + + static unsigned char gus_irq_map[16] = + {0, 0, 1, 3, 0, 2, 0, 4, 0, 0, 0, 5, 6, 0, 0, 7}; + + static unsigned char gus_dma_map[8] = + {0, 1, 0, 2, 0, 3, 4, 5}; + + DISABLE_INTR (flags); + + gus_write8 (0x4c, 0); /* + * Reset GF1 + */ + gus_delay (); + gus_delay (); + + gus_write8 (0x4c, 1); /* + * Release Reset + */ + gus_delay (); + gus_delay (); + + /* + * Clear all interrupts + */ + + gus_write8 (0x41, 0); /* + * DMA control + */ + gus_write8 (0x45, 0); /* + * Timer control + */ + gus_write8 (0x49, 0); /* + * Sample control + */ + + gus_select_max_voices (24); + + INB (u_Status); /* + * Touch the status register + */ + + gus_look8 (0x41); /* + * Clear any pending DMA IRQs + */ + gus_look8 (0x49); /* + * Clear any pending sample IRQs + */ + gus_read8 (0x0f); /* + * Clear pending IRQs + */ + + gus_reset (); /* + * Resets all voices + */ + + gus_look8 (0x41); /* + * Clear any pending DMA IRQs + */ + gus_look8 (0x49); /* + * Clear any pending sample IRQs + */ + gus_read8 (0x0f); /* + * Clear pending IRQs + */ + + gus_write8 (0x4c, 7); /* + * Master reset | DAC enable | IRQ enable + */ + + /* + * Set up for Digital ASIC + */ + + OUTB (0x05, gus_base + 0x0f); + + mix_image |= 0x02; /* + * Disable line out + */ + OUTB (mix_image, u_Mixer); + + OUTB (0x00, u_IRQDMAControl); + + OUTB (0x00, gus_base + 0x0f); + + /* + * Now set up the DMA and IRQ interface + * + * The GUS supports two IRQs and two DMAs. + * + * Just one DMA channel is used. This prevents simultaneous ADC and DAC. + * Adding this support requires significant changes to the dmabuf.c, dsp.c + * and audio.c also. + */ + + irq_image = 0; + tmp = gus_irq_map[gus_irq]; + if (!tmp) + printk ("Warning! GUS IRQ not selected\n"); + irq_image |= tmp; + irq_image |= 0x40; /* + * Combine IRQ1 (GF1) and IRQ2 (Midi) + */ + + dma_image = 0x40; /* + * Combine DMA1 (DRAM) and IRQ2 (ADC) + */ + tmp = gus_dma_map[gus_dma]; + if (!tmp) + printk ("Warning! GUS DMA not selected\n"); + dma_image |= tmp; + + /* + * For some reason the IRQ and DMA addresses must be written twice + */ + + /* + * Doing it first time + */ + + OUTB (mix_image, u_Mixer); /* + * Select DMA control + */ + OUTB (dma_image | 0x80, u_IRQDMAControl); /* + * Set DMA address + */ + + OUTB (mix_image | 0x40, u_Mixer); /* + * Select IRQ control + */ + OUTB (irq_image, u_IRQDMAControl); /* + * Set IRQ address + */ + + /* + * Doing it second time + */ + + OUTB (mix_image, u_Mixer); /* + * Select DMA control + */ + OUTB (dma_image, u_IRQDMAControl); /* + * Set DMA address + */ + + OUTB (mix_image | 0x40, u_Mixer); /* + * Select IRQ control + */ + OUTB (irq_image, u_IRQDMAControl); /* + * Set IRQ address + */ + + gus_select_voice (0); /* + * This disables writes to IRQ/DMA reg + */ + + mix_image &= ~0x02; /* + * Enable line out + */ + mix_image |= 0x08; /* + * Enable IRQ + */ + OUTB (mix_image, u_Mixer); /* + * Turn mixer channels on + * Note! Mic in is left off. + */ + + gus_select_voice (0); /* + * This disables writes to IRQ/DMA reg + */ + + gusintr (0); /* + * Serve pending interrupts + */ + RESTORE_INTR (flags); +} + +int +gus_wave_detect (int baseaddr) +{ + unsigned long i; + unsigned long loc; + + gus_base = baseaddr; + + gus_write8 (0x4c, 0); /* Reset GF1 */ + gus_delay (); + gus_delay (); + + gus_write8 (0x4c, 1); /* Release Reset */ + gus_delay (); + gus_delay (); + + /* See if there is first block there.... */ + gus_poke (0L, 0xaa); + if (gus_peek (0L) != 0xaa) + return (0); + + /* Now zero it out so that I can check for mirroring .. */ + gus_poke (0L, 0x00); + for (i = 1L; i < 1024L; i++) + { + int n, failed; + + /* check for mirroring ... */ + if (gus_peek (0L) != 0) + break; + loc = i << 10; + + for (n = loc - 1, failed = 0; n <= loc; n++) + { + gus_poke (loc, 0xaa); + if (gus_peek (loc) != 0xaa) + failed = 1; + + gus_poke (loc, 0x55); + if (gus_peek (loc) != 0x55) + failed = 1; + } + + if (failed) + break; + } + gus_mem_size = i << 10; + return 1; +} + +static int +guswave_ioctl (int dev, + unsigned int cmd, unsigned int arg) +{ + + switch (cmd) + { + case SNDCTL_SYNTH_INFO: + gus_info.nr_voices = nr_voices; + IOCTL_TO_USER ((char *) arg, 0, &gus_info, sizeof (gus_info)); + return 0; + break; + + case SNDCTL_SEQ_RESETSAMPLES: + reset_sample_memory (); + return 0; + break; + + case SNDCTL_SEQ_PERCMODE: + return 0; + break; + + case SNDCTL_SYNTH_MEMAVL: + return gus_mem_size - free_mem_ptr - 32; + + default: + return RET_ERROR (EINVAL); + } +} + +static int +guswave_set_instr (int dev, int voice, int instr_no) +{ + int sample_no; + + if (instr_no < 0 || instr_no > MAX_PATCH) + return RET_ERROR (EINVAL); + + if (voice < 0 || voice > 31) + return RET_ERROR (EINVAL); + + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + { + voices[voice].sample_pending = instr_no; + return 0; + } + + sample_no = patch_table[instr_no]; + patch_map[voice] = -1; + + if (sample_no < 0) + { + printk ("GUS: Undefined patch %d for voice %d\n", instr_no, voice); + return RET_ERROR (EINVAL);/* + * Patch not defined + */ + } + + if (sample_ptrs[sample_no] == -1) /* + * Sample not loaded + */ + { + printk ("GUS: Sample #%d not loaded for patch %d (voice %d)\n", sample_no, instr_no, voice); + return RET_ERROR (EINVAL); + } + + sample_map[voice] = sample_no; + patch_map[voice] = instr_no; + return 0; +} + +static int +#ifdef FUTURE_VERSION +guswave_kill_note (int dev, int voice, int note, int velocity) +#else +guswave_kill_note (int dev, int voice, int velocity) +#endif +{ + unsigned long flags; + + DISABLE_INTR (flags); + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + { + voices[voice].kill_pending = 1; + RESTORE_INTR (flags); + } + else + { + RESTORE_INTR (flags); + gus_voice_fade (voice); + } + + return 0; +} + +static void +guswave_aftertouch (int dev, int voice, int pressure) +{ + short lo_limit, hi_limit; + unsigned long flags; + + return; /* + * Currently disabled + */ + + if (voice < 0 || voice > 31) + return; + + if (voices[voice].mode & WAVE_ENVELOPES && voices[voice].env_phase != 2) + return; /* + * Don't mix with envelopes + */ + + if (pressure < 32) + { + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_rampoff (); + compute_and_set_volume (voice, 255, 0); /* + * Back to original volume + */ + RESTORE_INTR (flags); + return; + } + + hi_limit = voices[voice].current_volume; + lo_limit = hi_limit * 99 / 100; + if (lo_limit < 65) + lo_limit = 65; + + DISABLE_INTR (flags); + gus_select_voice (voice); + if (hi_limit > (4095 - 65)) + { + hi_limit = 4095 - 65; + gus_voice_volume (hi_limit); + } + gus_ramp_range (lo_limit, hi_limit); + gus_ramp_rate (3, 8); + gus_rampon (0x58); /* + * Bidirectional, Down, Loop + */ + RESTORE_INTR (flags); +} + +static void +guswave_panning (int dev, int voice, int value) +{ + if (voice >= 0 || voice < 32) + voices[voice].panning = value; +} + +static void +guswave_volume_method (int dev, int mode) +{ + if (mode == VOL_METHOD_LINEAR || mode == VOL_METHOD_ADAGIO) + volume_method = mode; +} + +static void +compute_volume (int voice, int volume) +{ + if (volume < 128) + voices[voice].midi_volume = volume; + + switch (volume_method) + { + case VOL_METHOD_ADAGIO: + voices[voice].initial_volume = + gus_adagio_vol (voices[voice].midi_volume, voices[voice].main_vol, + voices[voice].expression_vol, + voices[voice].patch_vol); + break; + + case VOL_METHOD_LINEAR: /* Totally ignores patch-volume and expression */ + voices[voice].initial_volume = + gus_linear_vol (volume, voices[voice].main_vol); + break; + + default: + voices[voice].initial_volume = volume_base + + (voices[voice].midi_volume * volume_scale); + } + + if (voices[voice].initial_volume > 4030) + voices[voice].initial_volume = 4030; +} + +static void +compute_and_set_volume (int voice, int volume, int ramp_time) +{ + int current, target, rate; + unsigned long flags; + + compute_volume (voice, volume); + voices[voice].current_volume = voices[voice].initial_volume; + + DISABLE_INTR (flags); + /* + * CAUTION! Interrupts disabled. Enable them before returning + */ + + gus_select_voice (voice); + + current = gus_read16 (0x09) >> 4; + target = voices[voice].initial_volume; + + if (ramp_time == INSTANT_RAMP) + { + gus_rampoff (); + gus_voice_volume (target); + RESTORE_INTR (flags); + return; + } + + if (ramp_time == FAST_RAMP) + rate = 63; + else + rate = 16; + gus_ramp_rate (0, rate); + + if ((target - current) / 64 == 0) /* + * Too close + */ + { + gus_rampoff (); + gus_voice_volume (target); + RESTORE_INTR (flags); + return; + } + + if (target > current) + { + if (target > (4095 - 65)) + target = 4095 - 65; + gus_ramp_range (current, target); + gus_rampon (0x00); /* + * Ramp up, once, no irq + */ + } + else + { + if (target < 65) + target = 65; + + gus_ramp_range (target, current); + gus_rampon (0x40); /* + * Ramp down, once, no irq + */ + } + RESTORE_INTR (flags); +} + +static void +dynamic_volume_change (int voice) +{ + unsigned char status; + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + status = gus_read8 (0x00); /* + * Voice status + */ + RESTORE_INTR (flags); + + if (status & 0x03) + return; /* + * Voice not started + */ + + if (!(voices[voice].mode & WAVE_ENVELOPES)) + { + compute_and_set_volume (voice, voices[voice].midi_volume, 1); + return; + } + + /* + * Voice is running and has envelopes. + */ + + DISABLE_INTR (flags); + gus_select_voice (voice); + status = gus_read8 (0x0d); /* + * Ramping status + */ + RESTORE_INTR (flags); + + if (status & 0x03) /* + * Sustain phase? + */ + { + compute_and_set_volume (voice, voices[voice].midi_volume, 1); + return; + } + + if (voices[voice].env_phase < 0) + return; + + compute_volume (voice, voices[voice].midi_volume); + +#if 0 /* + * * * Is this really required */ + voices[voice].current_volume = + gus_read16 (0x09) >> 4; /* + * Get current volume + */ + + voices[voice].env_phase--; + step_envelope (voice); +#endif +} + +static void +guswave_controller (int dev, int voice, int ctrl_num, int value) +{ + unsigned long flags; + unsigned long freq; + + if (voice < 0 || voice > 31) + return; + + switch (ctrl_num) + { + case CTRL_PITCH_BENDER: + voices[voice].bender = value; + + if (voices[voice].volume_irq_mode != VMODE_START_NOTE) + { + freq = compute_finetune (voices[voice].orig_freq, value, voices[voice].bender_range); + voices[voice].current_freq = freq; + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_freq (freq); + RESTORE_INTR (flags); + } + break; + + case CTRL_PITCH_BENDER_RANGE: + voices[voice].bender_range = value; + break; +#ifdef FUTURE_VERSION + case CTL_EXPRESSION: + value /= 128; +#endif + case CTRL_EXPRESSION: + if (volume_method == VOL_METHOD_ADAGIO) + { + voices[voice].expression_vol = value; + if (voices[voice].volume_irq_mode != VMODE_START_NOTE) + dynamic_volume_change (voice); + } + break; + +#ifdef FUTURE_VERSION + case CTL_PAN: + voices[voice].panning = (value * 2) - 128; + break; + + case CTL_MAIN_VOLUME: + value = (value * 100) / 16383; +#endif + + case CTRL_MAIN_VOLUME: + voices[voice].main_vol = value; + if (voices[voice].volume_irq_mode != VMODE_START_NOTE) + dynamic_volume_change (voice); + break; + + default: /* + * Ignore + */ + break; + } +} + +static int +guswave_start_note2 (int dev, int voice, int note_num, int volume) +{ + int sample, best_sample, best_delta, delta_freq; + int is16bits, samplep, patch, pan; + unsigned long note_freq, base_note, freq, flags; + unsigned char mode = 0; + + if (voice < 0 || voice > 31) + { + printk ("GUS: Invalid voice\n"); + return RET_ERROR (EINVAL); + } + + if (note_num == 255) + { + if (voices[voice].mode & WAVE_ENVELOPES) + { + voices[voice].midi_volume = volume; + dynamic_volume_change (voice); + return 0; + } + + compute_and_set_volume (voice, volume, 1); + return 0; + } + + if ((patch = patch_map[voice]) == -1) + { + return RET_ERROR (EINVAL); + } + + if ((samplep = patch_table[patch]) == -1) + { + return RET_ERROR (EINVAL); + } + + note_freq = note_to_freq (note_num); + + /* + * Find a sample within a patch so that the note_freq is between low_note + * and high_note. + */ + sample = -1; + + best_sample = samplep; + best_delta = 1000000; + while (samplep >= 0 && sample == -1) + { + delta_freq = note_freq - samples[samplep].base_note; + if (delta_freq < 0) + delta_freq = -delta_freq; + if (delta_freq < best_delta) + { + best_sample = samplep; + best_delta = delta_freq; + } + if (samples[samplep].low_note <= note_freq && note_freq <= samples[samplep].high_note) + sample = samplep; + else + samplep = samples[samplep].key; /* + * Follow link + */ + } + if (sample == -1) + sample = best_sample; + + if (sample == -1) + { + printk ("GUS: Patch %d not defined for note %d\n", patch, note_num); + return 0; /* + * Should play default patch ??? + */ + } + + is16bits = (samples[sample].mode & WAVE_16_BITS) ? 1 : 0; /* + * 8 or 16 + * bit + * samples + */ + voices[voice].mode = samples[sample].mode; + voices[voice].patch_vol = samples[sample].volume; + + if (voices[voice].mode & WAVE_ENVELOPES) + { + int i; + + for (i = 0; i < 6; i++) + { + voices[voice].env_rate[i] = samples[sample].env_rate[i]; + voices[voice].env_offset[i] = samples[sample].env_offset[i]; + } + } + + sample_map[voice] = sample; + + base_note = samples[sample].base_note / 100; /* + * To avoid overflows + */ + note_freq /= 100; + + freq = samples[sample].base_freq * note_freq / base_note; + + voices[voice].orig_freq = freq; + + /* + * Since the pitch bender may have been set before playing the note, we + * have to calculate the bending now. + */ + + freq = compute_finetune (voices[voice].orig_freq, voices[voice].bender, voices[voice].bender_range); + voices[voice].current_freq = freq; + + pan = (samples[sample].panning + voices[voice].panning) / 32; + pan += 7; + if (pan < 0) + pan = 0; + if (pan > 15) + pan = 15; + + if (samples[sample].mode & WAVE_16_BITS) + { + mode |= 0x04; /* + * 16 bits + */ + if ((sample_ptrs[sample] >> 18) != + ((sample_ptrs[sample] + samples[sample].len) >> 18)) + printk ("GUS: Sample address error\n"); + } + + /************************************************************************* + * CAUTION! Interrupts disabled. Don't return before enabling + *************************************************************************/ + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_off (); /* + * It may still be running + */ + gus_rampoff (); + + RESTORE_INTR (flags); + + if (voices[voice].mode & WAVE_ENVELOPES) + { + compute_volume (voice, volume); + init_envelope (voice); + } + else + compute_and_set_volume (voice, volume, 0); + + DISABLE_INTR (flags); + gus_select_voice (voice); + + if (samples[sample].mode & WAVE_LOOP_BACK) + gus_write_addr (0x0a, sample_ptrs[sample] + samples[sample].len - + voices[voice].offset_pending, is16bits); /* Sample + * start=end */ + else + gus_write_addr (0x0a, sample_ptrs[sample] + voices[voice].offset_pending, + is16bits); /* Sample start=begin */ + + if (samples[sample].mode & WAVE_LOOPING) + { + mode |= 0x08; /* + * Looping on + */ + + if (samples[sample].mode & WAVE_BIDIR_LOOP) + mode |= 0x10; /* + * Bidirectional looping on + */ + + if (samples[sample].mode & WAVE_LOOP_BACK) + { + gus_write_addr (0x0a, + sample_ptrs[sample] + samples[sample].loop_end - + voices[voice].offset_pending, is16bits); + mode |= 0x40; + } + + gus_write_addr (0x02, sample_ptrs[sample] + samples[sample].loop_start, is16bits); /* + * Loop + * start + * location + */ + gus_write_addr (0x04, sample_ptrs[sample] + samples[sample].loop_end, is16bits); /* + * Loop + * end + * location + */ + } + else + { + mode |= 0x20; /* + * Loop irq at the end + */ + voices[voice].loop_irq_mode = LMODE_FINISH; /* + * Ramp it down at + * the * end + */ + voices[voice].loop_irq_parm = 1; + gus_write_addr (0x02, sample_ptrs[sample], is16bits); /* + * Loop start + * location + */ + gus_write_addr (0x04, sample_ptrs[sample] + samples[sample].len - 1, is16bits); /* + * Loop + * end + * location + */ + } + gus_voice_freq (freq); + gus_voice_balance (pan); + gus_voice_on (mode); + RESTORE_INTR (flags); + + return 0; +} + +/* + * * New guswave_start_note by Andrew J. Robinson attempts to minimize + * clicking * when the note playing on the voice is changed. It uses volume + * ramping. */ + +static int +guswave_start_note (int dev, int voice, int note_num, int volume) +{ + long int flags; + int mode; + int ret_val = 0; + + DISABLE_INTR (flags); + if (note_num == 255) + { + if (voices[voice].volume_irq_mode == VMODE_START_NOTE) + voices[voice].volume_pending = volume; + else + { + RESTORE_INTR (flags); + ret_val = guswave_start_note2 (dev, voice, note_num, volume); + } + } + else + { + gus_select_voice (voice); + mode = gus_read8 (0x00); + if (mode & 0x20) + gus_write8 (0x00, mode & 0xdf); /* No interrupt! */ + + voices[voice].offset_pending = 0; + voices[voice].kill_pending = 0; + voices[voice].volume_irq_mode = 0; + voices[voice].loop_irq_mode = 0; + + if (voices[voice].sample_pending >= 0) + { + RESTORE_INTR (flags); + guswave_set_instr (voices[voice].dev_pending, voice, + voices[voice].sample_pending); + voices[voice].sample_pending = -1; + DISABLE_INTR (flags); + } + + if ((mode & 0x01) || ((gus_read16 (0x09) >> 4) < 2065)) + { + ret_val = guswave_start_note2 (dev, voice, note_num, volume); + } + else + { + voices[voice].dev_pending = dev; + voices[voice].note_pending = note_num; + voices[voice].volume_pending = volume; + voices[voice].volume_irq_mode = VMODE_START_NOTE; + + gus_rampoff (); + gus_ramp_range (2000, 4065); + gus_ramp_rate (0, 63);/* Fastest possible rate */ + gus_rampon (0x20 | 0x40); /* Ramp down, once, irq */ + RESTORE_INTR (flags); + } + } + return ret_val; +} + +static void +guswave_reset (int dev) +{ + int i; + + for (i = 0; i < 32; i++) + { + gus_voice_init (i); + gus_voice_init2 (i); + } +} + +static int +guswave_open (int dev, int mode) +{ + int err; + + if (gus_busy) + return RET_ERROR (EBUSY); + + gus_initialize (); + + if ((err = DMAbuf_open_dma (gus_devnum))) + return err; + + RESET_WAIT_QUEUE (dram_sleeper, dram_sleep_flag); + gus_busy = 1; + active_device = GUS_DEV_WAVE; + + gus_reset (); + + return 0; +} + +static void +guswave_close (int dev) +{ + gus_busy = 0; + active_device = 0; + gus_reset (); + + DMAbuf_close_dma (gus_devnum); +} + +static int +guswave_load_patch (int dev, int format, snd_rw_buf * addr, + int offs, int count, int pmgr_flag) +{ + struct patch_info patch; + int instr; + long sizeof_patch; + + unsigned long blk_size, blk_end, left, src_offs, target; + + sizeof_patch = (long) &patch.data[0] - (long) &patch; /* + * Size of + * the header + * * info + */ + + if (format != GUS_PATCH) + { + printk ("GUS Error: Invalid patch format (key) 0x%x\n", format); + return RET_ERROR (EINVAL); + } + + if (count < sizeof_patch) + { + printk ("GUS Error: Patch header too short\n"); + return RET_ERROR (EINVAL); + } + + count -= sizeof_patch; + + if (free_sample >= MAX_SAMPLE) + { + printk ("GUS: Sample table full\n"); + return RET_ERROR (ENOSPC); + } + + /* + * Copy the header from user space but ignore the first bytes which have + * been transferred already. + */ + + COPY_FROM_USER (&((char *) &patch)[offs], addr, offs, sizeof_patch - offs); + + instr = patch.instr_no; + + if (instr < 0 || instr > MAX_PATCH) + { + printk ("GUS: Invalid patch number %d\n", instr); + return RET_ERROR (EINVAL); + } + + if (count < patch.len) + { + printk ("GUS Warning: Patch record too short (%d<%d)\n", + count, (int) patch.len); + patch.len = count; + } + + if (patch.len <= 0 || patch.len > gus_mem_size) + { + printk ("GUS: Invalid sample length %d\n", (int) patch.len); + return RET_ERROR (EINVAL); + } + + if (patch.mode & WAVE_LOOPING) + { + if (patch.loop_start < 0 || patch.loop_start >= patch.len) + { + printk ("GUS: Invalid loop start\n"); + return RET_ERROR (EINVAL); + } + + if (patch.loop_end < patch.loop_start || patch.loop_end > patch.len) + { + printk ("GUS: Invalid loop end\n"); + return RET_ERROR (EINVAL); + } + } + + free_mem_ptr = (free_mem_ptr + 31) & ~31; /* + * Alignment 32 bytes + */ + +#define GUS_BANK_SIZE (256*1024) + + if (patch.mode & WAVE_16_BITS) + { + /* + * 16 bit samples must fit one 256k bank. + */ + if (patch.len >= GUS_BANK_SIZE) + { + printk ("GUS: Sample (16 bit) too long %d\n", (int) patch.len); + return RET_ERROR (ENOSPC); + } + + if ((free_mem_ptr / GUS_BANK_SIZE) != + ((free_mem_ptr + patch.len) / GUS_BANK_SIZE)) + { + unsigned long tmp_mem = /* + * Align to 256K*N + */ + ((free_mem_ptr / GUS_BANK_SIZE) + 1) * GUS_BANK_SIZE; + + if ((tmp_mem + patch.len) > gus_mem_size) + return RET_ERROR (ENOSPC); + + free_mem_ptr = tmp_mem; /* + * This leaves unusable memory + */ + } + } + + if ((free_mem_ptr + patch.len) > gus_mem_size) + return RET_ERROR (ENOSPC); + + sample_ptrs[free_sample] = free_mem_ptr; + + /* + * Tremolo is not possible with envelopes + */ + + if (patch.mode & WAVE_ENVELOPES) + patch.mode &= ~WAVE_TREMOLO; + + memcpy ((char *) &samples[free_sample], &patch, sizeof_patch); + + /* + * Link this_one sample to the list of samples for patch 'instr'. + */ + + samples[free_sample].key = patch_table[instr]; + patch_table[instr] = free_sample; + + /* + * Use DMA to transfer the wave data to the DRAM + */ + + left = patch.len; + src_offs = 0; + target = free_mem_ptr; + + while (left) /* + * Not all moved + */ + { + blk_size = sound_buffsizes[gus_devnum]; + if (blk_size > left) + blk_size = left; + + /* + * DMA cannot cross 256k bank boundaries. Check for that. + */ + blk_end = target + blk_size; + + if ((target >> 18) != (blk_end >> 18)) + { /* + * Have to split the block + */ + + blk_end &= ~(256 * 1024 - 1); + blk_size = blk_end - target; + } + +#if defined(GUS_NO_DMA) || defined(GUS_PATCH_NO_DMA) + /* + * For some reason the DMA is not possible. We have to use PIO. + */ + { + long i; + unsigned char data; + + for (i = 0; i < blk_size; i++) + { + GET_BYTE_FROM_USER (data, addr, sizeof_patch + i); + if (patch.mode & WAVE_UNSIGNED) + + if (!(patch.mode & WAVE_16_BITS) || (i & 0x01)) + data ^= 0x80; /* + * Convert to signed + */ + gus_poke (target + i, data); + } + } +#else /* + * * * GUS_NO_DMA */ + { + unsigned long address, hold_address; + unsigned char dma_command; + unsigned long flags; + + /* + * OK, move now. First in and then out. + */ + + COPY_FROM_USER (snd_raw_buf[gus_devnum][0], + addr, sizeof_patch + src_offs, + blk_size); + + DISABLE_INTR (flags); /******** INTERRUPTS DISABLED NOW ********/ + gus_write8 (0x41, 0); /* + * Disable GF1 DMA + */ + DMAbuf_start_dma (gus_devnum, snd_raw_buf_phys[gus_devnum][0], + blk_size, DMA_MODE_WRITE); + + /* + * Set the DRAM address for the wave data + */ + + address = target; + + if (sound_dsp_dmachan[gus_devnum] > 3) + { + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + + gus_write16 (0x42, (address >> 4) & 0xffff); /* + * DRAM DMA address + */ + + /* + * Start the DMA transfer + */ + + dma_command = 0x21; /* + * IRQ enable, DMA start + */ + if (patch.mode & WAVE_UNSIGNED) + dma_command |= 0x80; /* + * Invert MSB + */ + if (patch.mode & WAVE_16_BITS) + dma_command |= 0x40; /* + * 16 bit _DATA_ + */ + if (sound_dsp_dmachan[gus_devnum] > 3) + dma_command |= 0x04; /* + * 16 bit DMA channel + */ + + gus_write8 (0x41, dma_command); /* + * Let's go luteet (=bugs) + */ + + /* + * Sleep here until the DRAM DMA done interrupt is served + */ + active_device = GUS_DEV_WAVE; + + DO_SLEEP (dram_sleeper, dram_sleep_flag, HZ); + if (TIMED_OUT (dram_sleeper, dram_sleep_flag)) + printk ("GUS: DMA Transfer timed out\n"); + RESTORE_INTR (flags); + } +#endif /* + * * * GUS_NO_DMA */ + + /* + * Now the next part + */ + + left -= blk_size; + src_offs += blk_size; + target += blk_size; + + gus_write8 (0x41, 0); /* + * Stop DMA + */ + } + + free_mem_ptr += patch.len; + + if (!pmgr_flag) + pmgr_inform (dev, PM_E_PATCH_LOADED, instr, free_sample, 0, 0); + free_sample++; + return 0; +} + +static void +guswave_hw_control (int dev, unsigned char *event) +{ + int voice, cmd; + unsigned short p1, p2; + unsigned long plong, flags; + + cmd = event[2]; + voice = event[3]; + p1 = *(unsigned short *) &event[4]; + p2 = *(unsigned short *) &event[6]; + plong = *(unsigned long *) &event[4]; + + if ((voices[voice].volume_irq_mode == VMODE_START_NOTE) && + (cmd != _GUS_VOICESAMPLE) && (cmd != _GUS_VOICE_POS)) + do_volume_irq (voice); + + switch (cmd) + { + + case _GUS_NUMVOICES: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_select_max_voices (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICESAMPLE: + guswave_set_instr (dev, voice, p1); + break; + + case _GUS_VOICEON: + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* + * Disable intr + */ + gus_voice_on (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEOFF: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_off (); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEFADE: + gus_voice_fade (voice); + break; + + case _GUS_VOICEMODE: + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* + * Disable intr + */ + gus_voice_mode (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEBALA: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_balance (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEFREQ: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_freq (plong); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEVOL: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_volume (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEVOL2: /* + * Just update the voice value + */ + voices[voice].initial_volume = + voices[voice].current_volume = p1; + break; + + case _GUS_RAMPRANGE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* + * NO-NO + */ + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_ramp_range (p1, p2); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPRATE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* + * NO-NO + */ + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_ramp_rate (p1, p2); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPMODE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* + * NO-NO + */ + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* + * Disable intr + */ + gus_ramp_mode (p1); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPON: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* + * NO-NO + */ + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* + * Disable intr + */ + gus_rampon (p1); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPOFF: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* + * NO-NO + */ + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_rampoff (); + RESTORE_INTR (flags); + break; + + case _GUS_VOLUME_SCALE: + volume_base = p1; + volume_scale = p2; + break; + + case _GUS_VOICE_POS: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_set_voice_pos (voice, plong); + RESTORE_INTR (flags); + break; + + default:; + } +} + +static int +gus_sampling_set_speed (int speed) +{ + if (speed <= 0) + return gus_sampling_speed; + + if (speed > 44100) + speed = 44100; + + gus_sampling_speed = speed; + return speed; +} + +static int +gus_sampling_set_channels (int channels) +{ + if (!channels) + return gus_sampling_channels; + if (channels > 2) + channels = 2; + if (channels < 1) + channels = 1; + gus_sampling_channels = channels; + return channels; +} + +static int +gus_sampling_set_bits (int bits) +{ + if (!bits) + return gus_sampling_bits; + + if (bits != 8 && bits != 16) + bits = 8; + + gus_sampling_bits = bits; + return bits; +} + +static int +gus_sampling_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return gus_sampling_set_speed (arg); + return IOCTL_OUT (arg, gus_sampling_set_speed (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return gus_sampling_speed; + return IOCTL_OUT (arg, gus_sampling_speed); + break; + + case SNDCTL_DSP_STEREO: + if (local) + return gus_sampling_set_channels (arg + 1) - 1; + return IOCTL_OUT (arg, gus_sampling_set_channels (IOCTL_IN (arg) + 1) - 1); + break; + + case SOUND_PCM_WRITE_CHANNELS: + if (local) + return gus_sampling_set_channels (arg); + return IOCTL_OUT (arg, gus_sampling_set_channels (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return gus_sampling_channels; + return IOCTL_OUT (arg, gus_sampling_channels); + break; + + case SNDCTL_DSP_SAMPLESIZE: + if (local) + return gus_sampling_set_bits (arg); + return IOCTL_OUT (arg, gus_sampling_set_bits (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_BITS: + if (local) + return gus_sampling_bits; + return IOCTL_OUT (arg, gus_sampling_bits); + + case SOUND_PCM_WRITE_FILTER: /* + * NOT YET IMPLEMENTED + */ + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + break; + + case SOUND_PCM_READ_FILTER: + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + break; + + default: + return RET_ERROR (EINVAL); + } + return RET_ERROR (EINVAL); +} + +static void +gus_sampling_reset (int dev) +{ +} + +static int +gus_sampling_open (int dev, int mode) +{ +#ifdef GUS_NO_DMA + printk ("GUS: DMA mode not enabled. Device not supported\n"); + return RET_ERROR (ENXIO); +#endif + + if (gus_busy) + return RET_ERROR (EBUSY); + + gus_initialize (); + + gus_busy = 1; + active_device = 0; + + gus_reset (); + reset_sample_memory (); + gus_select_max_voices (14); + + pcm_active = 0; + pcm_opened = 1; + if (mode & OPEN_READ) + { + recording_active = 1; + set_input_volumes (); + } + + return 0; +} + +static void +gus_sampling_close (int dev) +{ + gus_reset (); + gus_busy = 0; + pcm_opened = 0; + active_device = 0; + + if (recording_active) + set_input_volumes (); + + recording_active = 0; +} + +static void +gus_sampling_update_volume (void) +{ + unsigned long flags; + int voice; + + DISABLE_INTR (flags); + if (pcm_active && pcm_opened) + for (voice = 0; voice < gus_sampling_channels; voice++) + { + gus_select_voice (voice); + gus_rampoff (); + gus_voice_volume (1530 + (25 * gus_pcm_volume)); + gus_ramp_range (65, 1530 + (25 * gus_pcm_volume)); + } + RESTORE_INTR (flags); +} + +static void +play_next_pcm_block (void) +{ + unsigned long flags; + int speed = gus_sampling_speed; + int this_one, is16bits, chn; + unsigned long dram_loc; + unsigned char mode[2], ramp_mode[2]; + + if (!pcm_qlen) + return; + + this_one = pcm_head; + + for (chn = 0; chn < gus_sampling_channels; chn++) + { + mode[chn] = 0x00; + ramp_mode[chn] = 0x03; /* + * Ramping and rollover off + */ + + if (chn == 0) + { + mode[chn] |= 0x20; /* + * Loop irq + */ + voices[chn].loop_irq_mode = LMODE_PCM; + } + + if (gus_sampling_bits != 8) + { + is16bits = 1; + mode[chn] |= 0x04; /* + * 16 bit data + */ + } + else + is16bits = 0; + + dram_loc = this_one * pcm_bsize; + dram_loc += chn * pcm_banksize; + + if (this_one == (pcm_nblk - 1)) /* + * Last of the DRAM buffers + */ + { + mode[chn] |= 0x08; /* + * Enable loop + */ + ramp_mode[chn] = 0x03;/* + * Disable rollover + */ + } + else + { + if (chn == 0) + ramp_mode[chn] = 0x04; /* + * Enable rollover bit + */ + } + + DISABLE_INTR (flags); + gus_select_voice (chn); + gus_voice_freq (speed); + + if (gus_sampling_channels == 1) + gus_voice_balance (7); /* + * mono + */ + else if (chn == 0) + gus_voice_balance (0); /* + * left + */ + else + gus_voice_balance (15); /* + * right + */ + + if (!pcm_active) /* + * Voice not started yet + */ + { + /* + * The playback was not started yet (or there has been a pause). + * Start the voice (again) and ask for a rollover irq at the end of + * this_one block. If this_one one is last of the buffers, use just + * the normal loop with irq. + */ + + gus_voice_off (); /* + * It could already be running + */ + gus_rampoff (); + gus_voice_volume (1530 + (25 * gus_pcm_volume)); + gus_ramp_range (65, 1530 + (25 * gus_pcm_volume)); + + gus_write_addr (0x0a, dram_loc, is16bits); /* + * Starting position + */ + gus_write_addr (0x02, chn * pcm_banksize, is16bits); /* + * Loop start + * location + */ + + if (chn != 0) + gus_write_addr (0x04, pcm_banksize + (pcm_bsize * pcm_nblk), + is16bits); /* + * Loop end location + */ + } + + if (chn == 0) + gus_write_addr (0x04, dram_loc + pcm_datasize[this_one], is16bits); /* + * Loop + * end + * location + */ + else + mode[chn] |= 0x08; /* + * Enable loop + */ + + if (pcm_datasize[this_one] != pcm_bsize) + { + /* + * Incomplete block. Possibly the last one. + */ + if (chn == 0) + { + mode[chn] &= ~0x08; /* + * Disable loop + */ + mode[chn] |= 0x20;/* + * Enable loop IRQ + */ + voices[0].loop_irq_mode = LMODE_PCM_STOP; + ramp_mode[chn] = 0x03; /* + * No rollover bit + */ + } + else + { + gus_write_addr (0x04, dram_loc + pcm_datasize[this_one], is16bits); /* + * Loop + * end + * location + */ + mode[chn] &= ~0x08; /* + * Disable loop + */ + } + } + + RESTORE_INTR (flags); + } + + for (chn = 0; chn < gus_sampling_channels; chn++) + { + DISABLE_INTR (flags); + gus_select_voice (chn); + gus_write8 (0x0d, ramp_mode[chn]); + gus_voice_on (mode[chn]); + RESTORE_INTR (flags); + } + + pcm_active = 1; +} + +static void +gus_transfer_output_block (int dev, unsigned long buf, + int total_count, int intrflag, int chn) +{ + /* + * This routine transfers one block of audio data to the DRAM. In mono mode + * it's called just once. When in stereo mode, this_one routine is called + * once for both channels. + * + * The left/mono channel data is transferred to the beginning of dram and the + * right data to the area pointed by gus_page_size. + */ + + int this_one, count; + unsigned long flags; + unsigned char dma_command; + unsigned long address, hold_address; + + DISABLE_INTR (flags); + + count = total_count / gus_sampling_channels; + + if (chn == 0) + { + if (pcm_qlen >= pcm_nblk) + printk ("GUS Warning: PCM buffers out of sync\n"); + + this_one = pcm_current_block = pcm_tail; + pcm_qlen++; + pcm_tail = (pcm_tail + 1) % pcm_nblk; + pcm_datasize[this_one] = count; + } + else + this_one = pcm_current_block; + + gus_write8 (0x41, 0); /* + * Disable GF1 DMA + */ + DMAbuf_start_dma (dev, buf + (chn * count), count, DMA_MODE_WRITE); + + address = this_one * pcm_bsize; + address += chn * pcm_banksize; + + if (sound_dsp_dmachan[dev] > 3) + { + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + + gus_write16 (0x42, (address >> 4) & 0xffff); /* + * DRAM DMA address + */ + + dma_command = 0x21; /* + * IRQ enable, DMA start + */ + + if (gus_sampling_bits != 8) + dma_command |= 0x40; /* + * 16 bit _DATA_ + */ + else + dma_command |= 0x80; /* + * Invert MSB + */ + + if (sound_dsp_dmachan[dev] > 3) + dma_command |= 0x04; /* + * 16 bit DMA channel + */ + + gus_write8 (0x41, dma_command); /* + * Kick on + */ + + if (chn == (gus_sampling_channels - 1)) /* + * Last channel + */ + { + /* + * Last (right or mono) channel data + */ + active_device = GUS_DEV_PCM_DONE; + if (!pcm_active && (pcm_qlen > 2 || count < pcm_bsize)) + { + play_next_pcm_block (); + } + } + else /* + * * * Left channel data. The right channel + * is * * * transferred after DMA interrupt */ + active_device = GUS_DEV_PCM_CONTINUE; + + RESTORE_INTR (flags); +} + +static void +gus_sampling_output_block (int dev, unsigned long buf, int total_count, + int intrflag, int restart_dma) +{ + pcm_current_buf = buf; + pcm_current_count = total_count; + pcm_current_intrflag = intrflag; + pcm_current_dev = dev; + gus_transfer_output_block (dev, buf, total_count, intrflag, 0); +} + +static void +gus_sampling_start_input (int dev, unsigned long buf, int count, + int intrflag, int restart_dma) +{ + unsigned long flags; + unsigned char mode; + + DISABLE_INTR (flags); + + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + + mode = 0xa0; /* + * DMA IRQ enable, invert MSB + */ + + if (sound_dsp_dmachan[dev] > 3) + mode |= 0x04; /* + * 16 bit DMA channel + */ + if (gus_sampling_channels > 1) + mode |= 0x02; /* + * Stereo + */ + mode |= 0x01; /* + * DMA enable + */ + + gus_write8 (0x49, mode); + + RESTORE_INTR (flags); +} + +static int +gus_sampling_prepare_for_input (int dev, int bsize, int bcount) +{ + unsigned int rate; + + rate = (9878400 / (gus_sampling_speed + 2)) / 16; + + gus_write8 (0x48, rate & 0xff); /* + * Set sampling frequency + */ + + if (gus_sampling_bits != 8) + { + printk ("GUS Error: 16 bit recording not supported\n"); + return RET_ERROR (EINVAL); + } + + return 0; +} + +static int +gus_sampling_prepare_for_output (int dev, int bsize, int bcount) +{ + int i; + + long mem_ptr, mem_size; + + mem_ptr = 0; + mem_size = gus_mem_size / gus_sampling_channels; + + if (mem_size > (256 * 1024)) + mem_size = 256 * 1024; + + pcm_bsize = bsize / gus_sampling_channels; + pcm_head = pcm_tail = pcm_qlen = 0; + + pcm_nblk = MAX_PCM_BUFFERS; + if ((pcm_bsize * pcm_nblk) > mem_size) + pcm_nblk = mem_size / pcm_bsize; + + for (i = 0; i < pcm_nblk; i++) + pcm_datasize[i] = 0; + + pcm_banksize = pcm_nblk * pcm_bsize; + + if (gus_sampling_bits != 8 && pcm_banksize == (256 * 1024)) + pcm_nblk--; + + return 0; +} + +static int +gus_has_output_drained (int dev) +{ + return !pcm_qlen; +} + +static void +gus_copy_from_user (int dev, char *localbuf, int localoffs, + snd_rw_buf * userbuf, int useroffs, int len) +{ + if (gus_sampling_channels == 1) + { + COPY_FROM_USER (&localbuf[localoffs], userbuf, useroffs, len); + } + else if (gus_sampling_bits == 8) + { + int in_left = useroffs; + int in_right = useroffs + 1; + char *out_left, *out_right; + int i; + + len /= 2; + localoffs /= 2; + out_left = &localbuf[localoffs]; + out_right = out_left + pcm_bsize; + + for (i = 0; i < len; i++) + { + GET_BYTE_FROM_USER (*out_left++, userbuf, in_left); + in_left += 2; + GET_BYTE_FROM_USER (*out_right++, userbuf, in_right); + in_right += 2; + } + } + else + { + int in_left = useroffs; + int in_right = useroffs + 1; + short *out_left, *out_right; + int i; + + len /= 4; + localoffs /= 4; + + out_left = (short *) &localbuf[localoffs]; + out_right = out_left + (pcm_bsize / 2); + + for (i = 0; i < len; i++) + { + GET_SHORT_FROM_USER (*out_left++, (short *) userbuf, in_left); + in_left += 2; + GET_SHORT_FROM_USER (*out_right++, (short *) userbuf, in_right); + in_right += 2; + } + } +} + +static struct audio_operations gus_sampling_operations = +{ + "Gravis UltraSound", + NEEDS_RESTART, + gus_sampling_open, + gus_sampling_close, + gus_sampling_output_block, + gus_sampling_start_input, + gus_sampling_ioctl, + gus_sampling_prepare_for_input, + gus_sampling_prepare_for_output, + gus_sampling_reset, + gus_sampling_reset, + gus_has_output_drained, + gus_copy_from_user +}; + +#ifdef FUTURE_VERSION +static void +guswave_bender (int dev, int voice, int value) +{ + int freq; + unsigned long flags; + + voices[voice].bender = value - 8192; + freq = compute_finetune (voices[voice].orig_freq, value, voices[voice].bender_range); + voices[voice].current_freq = freq; + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_freq (freq); + RESTORE_INTR (flags); +} + +#endif + +static int +guswave_patchmgr (int dev, struct patmgr_info *rec) +{ + int i, n; + + switch (rec->command) + { + case PM_GET_DEVTYPE: + rec->parm1 = PMTYPE_WAVE; + return 0; + break; + + case PM_GET_NRPGM: + rec->parm1 = MAX_PATCH; + return 0; + break; + + case PM_GET_PGMMAP: + rec->parm1 = MAX_PATCH; + + for (i = 0; i < MAX_PATCH; i++) + { + int ptr = patch_table[i]; + + rec->data.data8[i] = 0; + + while (ptr >= 0 && ptr < free_sample) + { + rec->data.data8[i]++; + ptr = samples[ptr].key; /* + * Follow link + */ + } + } + return 0; + break; + + case PM_GET_PGM_PATCHES: + { + int ptr = patch_table[rec->parm1]; + + n = 0; + + while (ptr >= 0 && ptr < free_sample) + { + rec->data.data32[n++] = ptr; + ptr = samples[ptr].key; /* + * Follow link + */ + } + } + rec->parm1 = n; + return 0; + break; + + case PM_GET_PATCH: + { + int ptr = rec->parm1; + struct patch_info *pat; + + if (ptr < 0 || ptr >= free_sample) + return RET_ERROR (EINVAL); + + memcpy (rec->data.data8, (char *) &samples[ptr], + sizeof (struct patch_info)); + + pat = (struct patch_info *) rec->data.data8; + + pat->key = GUS_PATCH; /* + * Restore patch type + */ + rec->parm1 = sample_ptrs[ptr]; /* + * DRAM address + */ + rec->parm2 = sizeof (struct patch_info); + } + return 0; + break; + + case PM_SET_PATCH: + { + int ptr = rec->parm1; + struct patch_info *pat; + + if (ptr < 0 || ptr >= free_sample) + return RET_ERROR (EINVAL); + + pat = (struct patch_info *) rec->data.data8; + + if (pat->len > samples[ptr].len) /* + * Cannot expand sample + */ + return RET_ERROR (EINVAL); + + pat->key = samples[ptr].key; /* + * Ensure the link is correct + */ + + memcpy ((char *) &samples[ptr], rec->data.data8, + sizeof (struct patch_info)); + + pat->key = GUS_PATCH; + } + return 0; + break; + + case PM_READ_PATCH: /* + * Returns a block of wave data from the DRAM + */ + { + int sample = rec->parm1; + int n; + long offs = rec->parm2; + int l = rec->parm3; + + if (sample < 0 || sample >= free_sample) + return RET_ERROR (EINVAL); + + if (offs < 0 || offs >= samples[sample].len) + return RET_ERROR (EINVAL); /* + * Invalid offset + */ + + n = samples[sample].len - offs; /* + * Nr of bytes left + */ + + if (l > n) + l = n; + + if (l > sizeof (rec->data.data8)) + l = sizeof (rec->data.data8); + + if (l <= 0) + return RET_ERROR (EINVAL); /* + * Was there a bug? + */ + + offs += sample_ptrs[sample]; /* + * Begin offsess + offset to DRAM + */ + + for (n = 0; n < l; n++) + rec->data.data8[n] = gus_peek (offs++); + rec->parm1 = n; /* + * Nr of bytes copied + */ + } + return 0; + break; + + case PM_WRITE_PATCH: /* + * Writes a block of wave data to the DRAM + */ + { + int sample = rec->parm1; + int n; + long offs = rec->parm2; + int l = rec->parm3; + + if (sample < 0 || sample >= free_sample) + return RET_ERROR (EINVAL); + + if (offs < 0 || offs >= samples[sample].len) + return RET_ERROR (EINVAL); /* + * Invalid offset + */ + + n = samples[sample].len - offs; /* + * Nr of bytes left + */ + + if (l > n) + l = n; + + if (l > sizeof (rec->data.data8)) + l = sizeof (rec->data.data8); + + if (l <= 0) + return RET_ERROR (EINVAL); /* + * Was there a bug? + */ + + offs += sample_ptrs[sample]; /* + * Begin offsess + offset to DRAM + */ + + for (n = 0; n < l; n++) + gus_poke (offs++, rec->data.data8[n]); + rec->parm1 = n; /* + * Nr of bytes copied + */ + } + return 0; + break; + + default: + return RET_ERROR (EINVAL); + } +} + +static struct synth_operations guswave_operations = +{ + &gus_info, +#ifdef FUTURE_VERSION + 0, +#endif + SYNTH_TYPE_SAMPLE, + SAMPLE_TYPE_GUS, + guswave_open, + guswave_close, + guswave_ioctl, + guswave_kill_note, + guswave_start_note, + guswave_set_instr, + guswave_reset, + guswave_hw_control, + guswave_load_patch, + guswave_aftertouch, + guswave_controller, + guswave_panning, + guswave_volume_method, + guswave_patchmgr, +#ifdef FUTURE_VERSION + guswave_bender +#endif +}; + +static void +set_input_volumes (void) +{ + unsigned long flags; + unsigned char mask = 0xff & ~0x06; /* Just line out enabled */ + + DISABLE_INTR (flags); + + /* + * Enable channels having vol > 10% + * Note! bit 0x01 means line in DISABLED while 0x04 means + * mic in ENABLED. + */ + if (gus_line_vol > 10) + mask &= ~0x01; + if (gus_mic_vol > 10) + mask |= 0x04; + + if (recording_active) + { + /* + * Disable channel, if not selected for recording + */ + if (!(gus_recmask & SOUND_MASK_LINE)) + mask |= 0x01; + if (!(gus_recmask & SOUND_MASK_MIC)) + mask &= ~0x04; + } + + mix_image &= ~0x07; + mix_image |= mask & 0x07; + OUTB (mix_image, u_Mixer); + + RESTORE_INTR (flags); +} + +int +gus_default_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) +{ +#define MIX_DEVS (SOUND_MASK_MIC|SOUND_MASK_LINE| \ + SOUND_MASK_SYNTH|SOUND_MASK_PCM) + if (((cmd >> 8) & 0xff) == 'M') + { + if (cmd & IOC_IN) + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + gus_recmask = IOCTL_IN (arg) & MIX_DEVS; + if (!(gus_recmask & (SOUND_MASK_MIC | SOUND_MASK_LINE))) + gus_recmask = SOUND_MASK_MIC; + /* Note! Input volumes are updated during next open for recording */ + return IOCTL_OUT (arg, gus_recmask); + break; + + case SOUND_MIXER_MIC: + { + int vol = IOCTL_IN (arg) & 0xff; + + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + gus_mic_vol = vol; + set_input_volumes (); + return IOCTL_OUT (arg, vol | (vol << 8)); + } + break; + + case SOUND_MIXER_LINE: + { + int vol = IOCTL_IN (arg) & 0xff; + + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + gus_line_vol = vol; + set_input_volumes (); + return IOCTL_OUT (arg, vol | (vol << 8)); + } + break; + + case SOUND_MIXER_PCM: + gus_pcm_volume = IOCTL_IN (arg) & 0xff; + if (gus_pcm_volume < 0) + gus_pcm_volume = 0; + if (gus_pcm_volume > 100) + gus_pcm_volume = 100; + gus_sampling_update_volume (); + return IOCTL_OUT (arg, gus_pcm_volume | (gus_pcm_volume << 8)); + break; + + case SOUND_MIXER_SYNTH: + { + int voice; + + gus_wave_volume = IOCTL_IN (arg) & 0xff; + + if (gus_wave_volume < 0) + gus_wave_volume = 0; + if (gus_wave_volume > 100) + gus_wave_volume = 100; + + if (active_device == GUS_DEV_WAVE) + for (voice = 0; voice < nr_voices; voice++) + dynamic_volume_change (voice); /* + * Apply the new + * volume + */ + + return IOCTL_OUT (arg, gus_wave_volume | (gus_wave_volume << 8)); + } + break; + + default: + return RET_ERROR (EINVAL); + } + else + switch (cmd & 0xff) /* + * Return parameters + */ + { + + case SOUND_MIXER_RECSRC: + return IOCTL_OUT (arg, gus_recmask); + break; + + case SOUND_MIXER_DEVMASK: + return IOCTL_OUT (arg, MIX_DEVS); + break; + + case SOUND_MIXER_STEREODEVS: + return IOCTL_OUT (arg, 0); + break; + + case SOUND_MIXER_RECMASK: + return IOCTL_OUT (arg, SOUND_MASK_MIC | SOUND_MASK_LINE); + break; + + case SOUND_MIXER_CAPS: + return IOCTL_OUT (arg, 0); + break; + + case SOUND_MIXER_MIC: + return IOCTL_OUT (arg, gus_mic_vol | (gus_mic_vol << 8)); + break; + + case SOUND_MIXER_LINE: + return IOCTL_OUT (arg, gus_line_vol | (gus_line_vol << 8)); + break; + + case SOUND_MIXER_PCM: + return IOCTL_OUT (arg, gus_pcm_volume | (gus_pcm_volume << 8)); + break; + + case SOUND_MIXER_SYNTH: + return IOCTL_OUT (arg, gus_wave_volume | (gus_wave_volume << 8)); + break; + + default: + return RET_ERROR (EINVAL); + } + } + else + return RET_ERROR (EINVAL); +} + +static struct mixer_operations gus_mixer_operations = +{ + gus_default_mixer_ioctl +}; + +static long +gus_default_mixer_init (long mem_start) +{ + if (num_mixers < MAX_MIXER_DEV) /* + * Don't install if there is another + * mixer + */ + mixer_devs[num_mixers++] = &gus_mixer_operations; + + return mem_start; +} + +long +gus_wave_init (long mem_start, int irq, int dma) +{ + unsigned long flags; + unsigned char val; + char *model_num = "2.4"; + int gus_type = 0x24; /* 2.4 */ + int mixer_type = 0; + + /* + * Try to identify the GUS model. + * + * Versions < 3.6 don't have the digital ASIC. Try to probe it first. + */ + + DISABLE_INTR (flags); + OUTB (0x20, gus_base + 0x0f); + val = INB (gus_base + 0x0f); + RESTORE_INTR (flags); + + if (val != 0xff && (val & 0x06)) /* Should be 0x02? */ + { + /* + * It has the digital ASIC so the card is at least v3.4. + * Next try to detect the true model. + */ + + val = INB (u_MixSelect); + + /* + * Value 255 means pre-3.7 which don't have mixer. + * Values 5 thru 9 mean v3.7 which has a ICS2101 mixer. + * 10 and above is GUS MAX which has the CS4231 codec/mixer. + * + * Sorry. No GUS max support yet but it should be available + * soon after the SDK for GUS MAX is available. + */ + + if (val == 255 || val < 5) + { + model_num = "3.4"; + gus_type = 0x34; + } + else if (val < 10) + { + model_num = "3.7"; + gus_type = 0x37; + mixer_type = ICS2101; + } + else + { + model_num = "MAX"; + gus_type = 0x40; + mixer_type = CS4231; + } + } + else + { + /* + * ASIC not detected so the card must be 2.2 or 2.4. + * There could still be the 16-bit/mixer daughter card. + * It has the same codec/mixer than MAX. + * At this time there is no support for it but it will appear soon. + */ + } + + +#ifdef __FreeBSD__ + printk ("snd4: <Gravis UltraSound %s (%dk)>", model_num, (int) gus_mem_size / 1024); +#else + printk (" <Gravis UltraSound %s (%dk)>", model_num, (int) gus_mem_size / 1024); +#endif + +#ifndef SCO + sprintf (gus_info.name, "Gravis UltraSound %s (%dk)", model_num, (int) gus_mem_size / 1024); +#endif + + if (irq < 0 || irq > 15) + { + printk ("ERROR! Invalid IRQ#%d. GUS Disabled", irq); + return mem_start; + } + + if (dma < 0 || dma > 7) + { + printk ("ERROR! Invalid DMA#%d. GUS Disabled", dma); + return mem_start; + } + + gus_irq = irq; + gus_dma = dma; + + if (num_synths >= MAX_SYNTH_DEV) + printk ("GUS Error: Too many synthesizers\n"); + else + synth_devs[num_synths++] = &guswave_operations; + + PERMANENT_MALLOC (struct patch_info *, samples, + (MAX_SAMPLE + 1) * sizeof (*samples), mem_start); + + reset_sample_memory (); + + gus_initialize (); + + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[gus_devnum = num_dspdevs++] = &gus_sampling_operations; + sound_dsp_dmachan[gus_devnum] = dma; + sound_buffcounts[gus_devnum] = DSP_BUFFCOUNT; + sound_buffsizes[gus_devnum] = DSP_BUFFSIZE; + sound_dma_automode[gus_devnum] = 0; + } + else + printk ("GUS: Too many PCM devices available\n"); + + /* + * Mixer dependent initialization. + */ + + switch (mixer_type) + { + case ICS2101: + gus_line_vol=gus_mic_vol=gus_wave_volume = gus_pcm_volume = 100; + return ics2101_mixer_init (mem_start); + + case CS4231: + /* Available soon */ + default: + return gus_default_mixer_init (mem_start); + } + + return mem_start; +} + +static void +do_loop_irq (int voice) +{ + unsigned char tmp; + int mode, parm; + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + + tmp = gus_read8 (0x00); + tmp &= ~0x20; /* + * Disable wave IRQ for this_one voice + */ + gus_write8 (0x00, tmp); + + mode = voices[voice].loop_irq_mode; + voices[voice].loop_irq_mode = 0; + parm = voices[voice].loop_irq_parm; + + switch (mode) + { + + case LMODE_FINISH: /* + * Final loop finished, shoot volume down + */ + + if ((gus_read16 (0x09) >> 4) < 100) /* + * Get current volume + */ + { + gus_voice_off (); + gus_rampoff (); + gus_voice_init (voice); + break; + } + gus_ramp_range (65, 4065); + gus_ramp_rate (0, 63); /* + * Fastest possible rate + */ + gus_rampon (0x20 | 0x40); /* + * Ramp down, once, irq + */ + voices[voice].volume_irq_mode = VMODE_HALT; + break; + + case LMODE_PCM_STOP: + pcm_active = 0; /* + * Requires extensive processing + */ + case LMODE_PCM: + { + int orig_qlen = pcm_qlen; + + pcm_qlen--; + pcm_head = (pcm_head + 1) % pcm_nblk; + if (pcm_qlen) + { + play_next_pcm_block (); + } + else + { /* + * Out of data. Just stop the voice + */ + gus_voice_off (); + gus_rampoff (); + pcm_active = 0; + } + + if (orig_qlen == pcm_nblk) + { + DMAbuf_outputintr (gus_devnum, 0); + } + } + break; + + default:; + } + RESTORE_INTR (flags); +} + +static void +do_volume_irq (int voice) +{ + unsigned char tmp; + int mode, parm; + unsigned long flags; + + DISABLE_INTR (flags); + + gus_select_voice (voice); + + tmp = gus_read8 (0x0d); + tmp &= ~0x20; /* + * Disable volume ramp IRQ + */ + gus_write8 (0x0d, tmp); + + mode = voices[voice].volume_irq_mode; + voices[voice].volume_irq_mode = 0; + parm = voices[voice].volume_irq_parm; + + switch (mode) + { + case VMODE_HALT: /* + * Decay phase finished + */ + RESTORE_INTR (flags); + gus_voice_init (voice); + break; + + case VMODE_ENVELOPE: + gus_rampoff (); + RESTORE_INTR (flags); + step_envelope (voice); + break; + + case VMODE_START_NOTE: + RESTORE_INTR (flags); + guswave_start_note2 (voices[voice].dev_pending, voice, + voices[voice].note_pending, voices[voice].volume_pending); + if (voices[voice].kill_pending) + guswave_kill_note (voices[voice].dev_pending, voice, 0); + if (voices[voice].sample_pending >= 0) + { + guswave_set_instr (voices[voice].dev_pending, voice, + voices[voice].sample_pending); + voices[voice].sample_pending = -1; + } + break; + + default:; + } +} + +void +gus_voice_irq (void) +{ + unsigned long wave_ignore = 0, volume_ignore = 0; + unsigned long voice_bit; + + unsigned char src, voice; + + while (1) + { + src = gus_read8 (0x0f); /* + * Get source info + */ + voice = src & 0x1f; + src &= 0xc0; + + if (src == (0x80 | 0x40)) + return; /* + * No interrupt + */ + + voice_bit = 1 << voice; + + if (!(src & 0x80)) /* + * Wave IRQ pending + */ + if (!(wave_ignore & voice_bit) && voice < nr_voices) /* + * Not done + * yet + */ + { + wave_ignore |= voice_bit; + do_loop_irq (voice); + } + + if (!(src & 0x40)) /* + * Volume IRQ pending + */ + if (!(volume_ignore & voice_bit) && voice < nr_voices) /* + * Not done + * yet + */ + { + volume_ignore |= voice_bit; + do_volume_irq (voice); + } + } +} + +void +guswave_dma_irq (void) +{ + unsigned char status; + + status = gus_look8 (0x41); /* + * Get DMA IRQ Status + */ + if (status & 0x40) /* + * DMA Irq pending + */ + switch (active_device) + { + case GUS_DEV_WAVE: + if (SOMEONE_WAITING (dram_sleeper, dram_sleep_flag)) + WAKE_UP (dram_sleeper, dram_sleep_flag); + break; + + case GUS_DEV_PCM_CONTINUE: + gus_transfer_output_block (pcm_current_dev, pcm_current_buf, + pcm_current_count, + pcm_current_intrflag, 1); + break; + + case GUS_DEV_PCM_DONE: + if (pcm_qlen < pcm_nblk) + { + DMAbuf_outputintr (gus_devnum, pcm_qlen == 0); + } + break; + + default:; + } + + status = gus_look8 (0x49); /* + * Get Sampling IRQ Status + */ + if (status & 0x40) /* + * Sampling Irq pending + */ + { + DMAbuf_inputintr (gus_devnum); + } + +} + +#endif diff --git a/sys/i386/isa/sound/gustest/Makefile b/sys/i386/isa/sound/gustest/Makefile new file mode 100644 index 0000000..d161e5b --- /dev/null +++ b/sys/i386/isa/sound/gustest/Makefile @@ -0,0 +1,16 @@ +all: gustest gusload gmod midithru + +gustest: gustest.c + $(CC) -o gustest gustest.c -lm + +gusload: gusload.c + $(CC) -o gusload gusload.c + +gmod: gmod.c + $(CC) -o gmod gmod.c + +midithru: midithru.c + $(CC) -o midithru midithru.c + +clean: + rm -f gusload gustest gmod midithru *.o diff --git a/sys/i386/isa/sound/gustest/Readme b/sys/i386/isa/sound/gustest/Readme new file mode 100644 index 0000000..7640bf8 --- /dev/null +++ b/sys/i386/isa/sound/gustest/Readme @@ -0,0 +1,67 @@ +The programs in this directory are more or less incompletely implemented. +I have used them for debugging purposes while developing the driver. + +Files in this directory: + + +../ultrasound.h (sys/ultrasound.h) + This file contains some macros which are similar than + the procedures provided by GUSUNIT.PAS. See gustest.c + for more information. + INSTALL THIS FILE TO YOUR /usr/include/sys !!!!!!!!!!!! + +gusload.c This program can be used to load patches (samples) to + the DRAM of GUS. It understands the format used in the + .pat files shipped with GUS. + + Usage: gusload pgm# patchfile. + or gusload reset #Removes all patches from memory. + + You should load just the patches you will need to play + a Midi file, since the memory capacity of GUS is rather + limited (256k-1M). + + Example: + + gusload 0 acpiano.pat + gusload 1 britepno.pat + gusload 19 church.pat + + This program is not required if the adagio package is + used. It can do the patch uploading itself. + +gmod.c This is a simple module player which demonstrates + programming with GUS. It doesn't try to interpret + most of the effect commands. In fact this program + may interpret the modules incorrectly since I am + not a module player expert. + This version plays .MOD, .STM and .669 modules. + +midithru.c This program reads messages from the Midi interface + and plays the notes with an internal synthesizer + (FM or GUS). The program accepts one argument, the + synthesizer device number. In addition to the note on + and note off messages it interprets also program changes + and channel pressure messages. + If you need an example on programming the /dev/sequencer, + this is a good one. The voice allocation algorithm is + not good so don't look at it. + + NOTE! This program is useful with gmod. Jus load + a module with gmod. Wait until the module has + finished or hit ^C. Now you can play the samples + with the midithru program. + + NOTE2! You need a Midi keyboard to use this program. In + addition the Midi interface of GUS is not supported + yet which means you need also PAS16 or MPU-401. + +pmtest.c +gpatinfo.c ******* For information only ******* + These programs demonstrate the patch manager interface + which will be included to some later driver version. + This interface is not complete in version 1.99.9. + Using pmtest will hang your system sooner or later. + +Hannu Savolainen +hsavolai@cs.helsinki.fi diff --git a/sys/i386/isa/sound/gustest/gmidi.h b/sys/i386/isa/sound/gustest/gmidi.h new file mode 100644 index 0000000..106cfa2 --- /dev/null +++ b/sys/i386/isa/sound/gustest/gmidi.h @@ -0,0 +1,134 @@ +/* + * $Id$ + */ + char patch_names[][9] = + { + /* 0 */ "acpiano", + /* 1 */ "britepno", + /* 2 */ "synpiano", + /* 3 */ "honktonk", + /* 4 */ "epiano1", + /* 5 */ "epiano2", + /* 6 */ "hrpschrd", + /* 7 */ "clavinet", + /* 8 */ "celeste", + /* 9 */ "glocken", + /* 10 */ "musicbox", + /* 11 */ "vibes", + /* 12 */ "marimba", + /* 13 */ "xylophon", + /* 14 */ "tubebell", + /* 15 */ "santur", + /* 16 */ "homeorg", + /* 17 */ "percorg", + /* 18 */ "rockorg", + /* 19 */ "church", + /* 20 */ "reedorg", + /* 21 */ "accordn", + /* 22 */ "harmonca", + /* 23 */ "concrtna", + /* 24 */ "nyguitar", + /* 25 */ "acguitar", + /* 26 */ "jazzgtr", + /* 27 */ "cleangtr", + /* 28 */ "mutegtr", + /* 29 */ "odguitar", + /* 30 */ "distgtr", + /* 31 */ "gtrharm", + /* 32 */ "acbass", + /* 33 */ "fngrbass", + /* 34 */ "pickbass", + /* 35 */ "fretless", + /* 36 */ "slapbas1", + /* 37 */ "slapbas2", + /* 38 */ "synbass1", + /* 39 */ "synbass2", + /* 40 */ "violin", + /* 41 */ "viola", + /* 42 */ "cello", + /* 43 */ "contraba", + /* 44 */ "marcato", + /* 45 */ "pizzcato", + /* 46 */ "harp", + /* 47 */ "timpani", + /* 48 */ "marcato", + /* 49 */ "slowstr", + /* 50 */ "synstr1", + /* 51 */ "synstr2", + /* 52 */ "choir", + /* 53 */ "doo", + /* 54 */ "voices", + /* 55 */ "orchhit", + /* 56 */ "trumpet", + /* 57 */ "trombone", + /* 58 */ "tuba", + /* 59 */ "mutetrum", + /* 60 */ "frenchrn", + /* 61 */ "hitbrass", + /* 62 */ "synbras1", + /* 63 */ "synbras2", + /* 64 */ "sprnosax", + /* 65 */ "altosax", + /* 66 */ "tenorsax", + /* 67 */ "barisax", + /* 68 */ "oboe", + /* 69 */ "englhorn", + /* 70 */ "bassoon", + /* 71 */ "clarinet", + /* 72 */ "piccolo", + /* 73 */ "flute", + /* 74 */ "recorder", + /* 75 */ "woodflut", + /* 76 */ "bottle", + /* 77 */ "shakazul", + /* 78 */ "whistle", + /* 79 */ "ocarina", + /* 80 */ "sqrwave", + /* 81 */ "sawwave", + /* 82 */ "calliope", + /* 83 */ "chiflead", + /* 84 */ "voxlead", + /* 85 */ "voxlead", + /* 86 */ "lead5th", + /* 87 */ "basslead", + /* 88 */ "fantasia", + /* 89 */ "warmpad", + /* 90 */ "polysyn", + /* 91 */ "ghostie", + /* 92 */ "bowglass", + /* 93 */ "metalpad", + /* 94 */ "halopad", + /* 95 */ "sweeper", + /* 96 */ "aurora", + /* 97 */ "soundtrk", + /* 98 */ "crystal", + /* 99 */ "atmosphr", + /* 100 */ "freshair", + /* 101 */ "unicorn", + /* 102 */ "sweeper", + /* 103 */ "startrak", + /* 104 */ "sitar", + /* 105 */ "banjo", + /* 106 */ "shamisen", + /* 107 */ "koto", + /* 108 */ "kalimba", + /* 109 */ "bagpipes", + /* 110 */ "fiddle", + /* 111 */ "Shannai", + /* 112 */ "carillon", + /* 113 */ "agogo", + /* 114 */ "steeldrm", + /* 115 */ "woodblk", + /* 116 */ "taiko", + /* 117 */ "toms", + /* 118 */ "syntom", + /* 119 */ "revcym", + /* 120 */ "fx-fret", + /* 121 */ "fx-blow", + /* 122 */ "seashore", + /* 123 */ "jungle", + /* 124 */ "telephon", + /* 125 */ "helicptr", + /* 126 */ "applause", + /* 127 */ "ringwhsl" + }; diff --git a/sys/i386/isa/sound/gustest/gmod.c b/sys/i386/isa/sound/gustest/gmod.c new file mode 100644 index 0000000..2988783 --- /dev/null +++ b/sys/i386/isa/sound/gustest/gmod.c @@ -0,0 +1,1589 @@ +/* + * gmod.c - Module player for GUS and Linux. + * (C) Hannu Savolainen, 1993 + * + * NOTE! This program doesn't try to be a complete module player. + * It's just a too I used while developing the driver. In + * addition it can be used as an example on programming + * the LInux Sound Driver with GUS. + * $Id$ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <machine/ultrasound.h> +#include <fcntl.h> +#include <math.h> +#include <string.h> + +#define CMD_ARPEG 0x00 +#define CMD_SLIDEUP 0x01 +#define CMD_SLIDEDOWN 0x02 +#define CMD_SLIDETO 0x03 +#define SLIDE_SIZE 8 +#define CMD_VOLSLIDE 0x0a +#define CMD_JUMP 0x0b +#define CMD_VOLUME 0x0c +#define CMD_BREAK 0x0d +#define CMD_SPEED 0x0f +#define CMD_NOP 0xfe +#define CMD_NONOTE 0xff + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define MAX_TRACK 8 +#define MAX_PATTERN 128 +#define MAX_POSITION 128 + +struct note_info + { + unsigned char note; + unsigned char vol; + unsigned char sample; + unsigned char command; + short parm1, parm2; + }; + +struct voice_info + { + int sample; + int note; + int volume; + int pitchbender; + + /* Pitch sliding */ + + int slide_pitch; + int slide_goal; + int slide_rate; + + int volslide; + }; + +typedef struct note_info pattern[MAX_TRACK][64]; +int pattern_len[MAX_POSITION]; +int pattern_tempo[MAX_POSITION]; +pattern *pattern_table[MAX_PATTERN]; + +struct voice_info voices[MAX_TRACK]; + +int nr_channels, nr_patterns, songlength; +int tune[MAX_POSITION]; +double tick_duration; + +int period_table[] = +{ + 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, + 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, + 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 +}; + +SEQ_DEFINEBUF (2048); + +int seqfd; +int sample_ok[128], sample_vol[128]; +int tmp, gus_dev; +double this_time, next_time; +int ticks_per_division; +double clock_rate; /* HZ */ + +/* + * The function seqbuf_dump() must always be provided + */ + +void play_module (char *name); +int load_module (char *name); +int play_note (int channel, struct note_info *pat); +void lets_play_voice (int channel, struct voice_info *v); + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +void +init_voices () +{ + int i; + + for (i = 0; i < MAX_TRACK; i++) + { + voices[i].sample = 0; + voices[i].note = 0; + voices[i].volume = 64; + + voices[i].slide_pitch = 0; + voices[i].slide_goal = 0; + voices[i].slide_rate = 0; + voices[i].pitchbender = 0; + + voices[i].volslide = 0; + } +} + +int +main (int argc, char *argv[]) +{ + int i, n, j; + struct synth_info info; + + if ((seqfd = open ("/dev/sequencer", O_WRONLY, 0)) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Gravis Ultrasound not detected\n"); + exit (-1); + } + + GUS_NUMVOICES (gus_dev, 14); + + for (i = 1; i < argc; i++) + { + for (j = 0; j < MAX_PATTERN; j++) + pattern_table[j] = NULL; + + if (load_module (argv[i])) + { + tick_duration = 100.0 / clock_rate; + play_module (argv[i]); + } + + } + + SEQ_DUMPBUF (); + close (seqfd); + + exit (0); +} + +unsigned short +intelize (unsigned short v) +{ + return ((v & 0xff) << 8) | ((v >> 8) & 0xff); +} + +unsigned long +intelize4 (unsigned long v) +{ + return + (((v >> 16) & 0xff) << 8) | (((v >> 16) >> 8) & 0xff) | + (((v & 0xff) << 8) | ((v >> 8) & 0xff) << 16); +} + +int +load_stm_module (int mod_fd, char *name) +{ + + struct sample_header + { + char name[12]; + unsigned char instr_disk; + unsigned short reserved1; + unsigned short length; /* In bytes */ + unsigned short loop_start; + unsigned short loop_end; + unsigned char volume; + unsigned char reserved2; + unsigned short C2_speed; + unsigned short reserved3; + + }; + + int i, total_mem; + int sample_ptr; + + int position; + + unsigned char *tune_ptr; /* array 0-127 */ + + char header[1105], sname[21]; + + int nr_samples; /* 16 or 32 samples (or 64 or ???) */ + int slen, npat; + + fprintf (stderr, "Loading .STM module: %s\n", name); + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + strncpy (sname, header, 20); + + fprintf (stderr, "\nModule: %s - ", sname); + + if (header[28] != 0x1a) + { + fprintf (stderr, "Not a STM module\n"); + close (mod_fd); + return 0; + } + + npat = header[33]; + slen = 0; + tune_ptr = &header[48 + (31 * 32)]; + + for (i = 0; i < 64; i++) + { + tune[i] = tune_ptr[i]; + if (tune[i] < npat) + slen = i; + } + + fprintf (stderr, "Song lenght %d, %d patterns.\n", slen, npat); + + nr_samples = 31; + + sample_ptr = 48 + (31 * 32) + 64 + (npat * 1024); /* Location where the + * first sample is + * stored */ + total_mem = 0; + + for (i = 0; i < 32; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end, base_freq; + unsigned short loop_flags = 0; + + struct sample_header *sample; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[48 + (i * 32)]; + + len = sample->length; + loop_start = sample->loop_start; + loop_end = sample->loop_end; + base_freq = sample->C2_speed; + + if (strlen (sample->name) > 21) + { + fprintf (stderr, "\nInvalid name for sample #%d\n", i); + close (mod_fd); + return 0; + } + + if (len > 0) + { + int x; + + if (loop_end > len) + loop_end = 1; + else if (loop_end < loop_start) + { + loop_start = 0; + loop_end = 0; + } + else + loop_flags = WAVE_LOOPING; + + total_mem += len; + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_freq = base_freq; + patch->base_note = 261630; /* Mid C */ + patch->low_note = 0; + patch->high_note = 0x7fffffff; + patch->volume = 120; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if ((x = read (mod_fd, patch->data, len)) != len) + { + fprintf (stderr, "Short file (sample at %d (%d!=%d)\n", sample_ptr, x, len); + close (mod_fd); + free (patch); + return 0; + } + + fprintf (stderr, "Sample %02d: %05d, %05d, %05d, %07d %s\n", + i, + len, + loop_start, + loop_end, + base_freq, + sample->name); + + if (write (seqfd, patch, sizeof (*patch) + len) == -1) + { + perror ("ioctl /dev/sequencer"); + exit (-1); + } + else + sample_ok[i] = 1; + + free (patch); + } + } + + nr_patterns = slen; + songlength = slen; + nr_channels = 4; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[64][4][4]; + int pat, channel, x; + + int pp = 1104 + (position * 1024); + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if ((x = read (mod_fd, patterns, 1024)) != 1024) + { + fprintf (stderr, "Short file (pattern at %d), %d!=%d\n", pp, x, 1024); + close (mod_fd); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + + for (channel = 0; channel < 4; channel++) + { + unsigned char *p; + + unsigned vol, note, octave, sample, effect, params; + + p = &patterns[pat][channel][0]; + + if (p[0] < 251) + { + note = p[0] & 15; + octave = p[0] / 16; + + note = 48 + octave * 12 + note; + + sample = p[1] / 8; + vol = (p[1] & 7) + (p[2] / 2); + effect = p[2] & 0xF; + params = p[3]; + } + else + { + note = 0; + octave = 0; + + sample = 0; + vol = 0; + effect = CMD_NONOTE; + params = 0; + } + + (*pattern_table[position])[channel][pat].note = note; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + + } + + } + + close (mod_fd); + return 1; +} + +int +load_669_module (int mod_fd, char *name) +{ + struct sample_header + { + char name[13]; + unsigned long length; /* In bytes */ + unsigned long loop_start; + unsigned long loop_end; + }; + + int i, total_mem; + int sample_ptr; + + int position; + + unsigned char *tune_ptr, *len_ptr, *tempo_ptr; /* array 0-127 */ + + char header[1084]; + char msg[110]; + + int nr_samples; /* 16 or 32 samples */ + int slen, npat; + + clock_rate = 25.0; + + fprintf (stderr, "Loading .669 module: %s\n", name); + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + if (*(unsigned short *) &header[0] != 0x6669) + { + fprintf (stderr, "Not a 669 file\n"); + close (mod_fd); + return 0; + } + + strncpy (msg, &header[2], 108); + + for (i = 0; i < strlen (msg); i++) + if ((msg[i] >= ' ' && msg[i] <= 'z') || msg[i] == '\n') + printf ("%c", msg[i]); + printf ("\n"); + + npat = header[0x6f]; + + tune_ptr = &header[0x71]; + + for (slen = 0; slen < 128 && tune_ptr[slen] != 0xff; slen++); + slen--; + + for (i = 0; i < slen; i++) + tune[i] = tune_ptr[i]; + + len_ptr = &header[0x171]; + for (i = 0; i < slen; i++) + pattern_len[i] = len_ptr[i] - 1; + + tempo_ptr = &header[0xf1]; + for (i = 0; i < slen; i++) + pattern_tempo[i] = tempo_ptr[i]; + + nr_samples = header[0x6e]; + + fprintf (stderr, "Song lenght %d, %d patterns, %d samples.\n", slen, npat, nr_samples); + + sample_ptr = 0x1f1 + (nr_samples * 0x19) + (npat * 0x600); /* Location where the + * first sample is + * stored */ + total_mem = 0; + + for (i = 0; i < 64; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end; + unsigned short loop_flags = 0; + + struct sample_header *sample; + char sname[14]; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[0x1f1 + (i * 0x19)]; + + len = *(unsigned long *) &sample->name[13]; + loop_start = *(unsigned long *) &sample->name[17]; + loop_end = *(unsigned long *) &sample->name[21]; + if (loop_end > len) + loop_end = 1; + else if (loop_end == len) + loop_end--; + + if (loop_end < loop_start) + { + loop_start = 0; + loop_end = 0; + } + + strncpy (sname, sample->name, 13); + + if (len > 0 && len < 200000) + { + total_mem += len; + + fprintf (stderr, "Sample %02d: %05d, %05d, %05d %s\n", + i, + len, + loop_start, + loop_end, + sname); + + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + if (loop_end == 0) + loop_end = 1; + if (loop_end >= len) + loop_end = 1; + + if (loop_end > 1) loop_flags = WAVE_LOOPING; + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = WAVE_UNSIGNED | loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_freq = 8448; + patch->base_note = 261630; + patch->low_note = 1000; + patch->high_note = 0x7fffffff; + patch->volume = 120; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + fprintf (stderr, "Seek failed\n"); + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if (read (mod_fd, patch->data, len) != len) + { + fprintf (stderr, "Short file (sample at %d)\n", sample_ptr); + close (mod_fd); + free (patch); + return 0; + } + + if (write (seqfd, patch, sizeof (*patch) + len) == -1) + { + perror ("ioctl /dev/sequencer"); + /* exit (-1); */ + } + else + sample_ok[i] = 1; + + free (patch); + } + } + + nr_patterns = slen; + songlength = slen; + nr_channels = 8; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[0x600]; + int pat, channel, x; + + int pp = 0x1f1 + (nr_samples * 0x19) + (position * 0x600); + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if ((x = read (mod_fd, patterns, 1024)) != 1024) + { + fprintf (stderr, "Short file (pattern at %d) %d!=1024\n", pp, x); + close (mod_fd); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + + for (channel = 0; channel < 8; channel++) + { + unsigned char *p; + + unsigned vol, period, sample, effect, params; + + p = &patterns[pat * 24 + channel * 3]; + + if (p[0] >= 0xfe || + (p[0] == 0xff && p[1] == 0xff && p[2] == 0xff) || + (p[0] == 0 && p[1] == 0 && p[2] == 0) || + *(int *) p == -1) + { + period = 0; + effect = CMD_NONOTE; + sample = 0; + vol = 0; + params = 0; + + if (p[0] == 0) + { + effect = CMD_BREAK; + params = -2; + } + } + else + { + period = (p[0] >> 2) + 48; + effect = (p[2] >> 4); + params = p[2] & 0x0f; + vol = p[1] & 0x0f; + + if (p[2] == 0xfe) + { + effect = CMD_VOLUME; + params = vol; + } + else if (p[2] == 0xff) + { + effect = CMD_NOP; + } + else + switch (effect) + { + case 0: /* a - Portamento up */ + effect = CMD_SLIDEUP; + break; + + case 1: /* b - Portamento down */ + effect = CMD_SLIDEDOWN; + break; + + case 2: /* c - Port to note */ + effect = CMD_SLIDETO; + break; + + case 3: /* d - Frequency adjust */ + effect = CMD_NOP; /* To be implemented */ + break; + + case 4: /* e - Frequency vibrato */ + effect = CMD_NOP; /* To be implemented */ + break; + + case 5: /* f - Set tempo */ + effect = CMD_SPEED; + break; + + default: + effect = CMD_NOP; + } + + sample = (((p[0] << 4) & 0x30) | ((p[1] >> 4) & 0x0f)) + 1; + } + + (*pattern_table[position])[channel][pat].note = period; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + + } + + } + + close (mod_fd); + return 1; +} + +int +load_mmd0_module (int mod_fd, char *name) +{ + + struct sample_header + { + unsigned short loop_start; + unsigned short loop_end; + unsigned char midich; + unsigned char midipreset; + unsigned char volume; + unsigned char strans; + }; + + int i, total_mem; + int sample_ptr; + + int position; + + unsigned char *tune_ptr; /* array 0-127 */ + + char header[1105]; + + int nr_samples; /* 16 or 32 samples (or 64 or ???) */ + int slen, npat; + + fprintf (stderr, "Loading .MED module: %s\n", name); + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + if (strncmp (header, "MMD0", 4)) + { + fprintf (stderr, "Not a MED module\n"); + close (mod_fd); + return 0; + } + + printf ("Module len %d\n", intelize4 (*(long *) &header[4])); + printf ("Song info %d\n", intelize4 (*(long *) &header[8])); + printf ("Song len %d\n", intelize4 (*(long *) &header[12])); + printf ("Blockarr %x\n", intelize4 (*(long *) &header[16])); + printf ("Blockarr len %d\n", intelize4 (*(long *) &header[20])); + printf ("Sample array %x\n", intelize4 (*(long *) &header[24])); + printf ("Sample array len %d\n", intelize4 (*(long *) &header[28])); + printf ("Exp data %x\n", intelize4 (*(long *) &header[32])); + printf ("Exp size %d\n", intelize4 (*(long *) &header[36])); + printf ("Pstate %d\n", intelize (*(long *) &header[40])); + printf ("Pblock %d\n", intelize (*(long *) &header[42])); + + return 0; + + npat = header[33]; + slen = 0; + tune_ptr = &header[48 + (31 * 32)]; + + for (i = 0; i < 64; i++) + { + tune[i] = tune_ptr[i]; + if (tune[i] < npat) + slen = i; + } + + fprintf (stderr, "Song lenght %d, %d patterns.\n", slen, npat); + + nr_samples = 31; + + sample_ptr = 48 + (31 * 32) + 64 + (npat * 1024); /* Location where the + * first sample is + * stored */ + total_mem = 0; + + for (i = 0; i < 32; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end, base_freq; + unsigned short loop_flags = 0; + + struct sample_header *sample; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[48 + (i * 32)]; + + /* + * len = sample->length; loop_start = sample->loop_start; loop_end = + * sample->loop_end; base_freq = sample->C2_speed; + * + * if (strlen (sample->name) > 21) { fprintf (stderr, "\nInvalid name for + * sample #%d\n", i); close (mod_fd); return 0; } + */ + if (len > 0) + { + int x; + + if (loop_end > len) + loop_end = 1; + + if (loop_end < loop_start) + { + loop_start = 0; + loop_end = 0; + } + + if (loop_end > 2) loop_flags = WAVE_LOOPING; + + total_mem += len; + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_freq = base_freq; + patch->base_note = 261630; /* Mid C */ + patch->low_note = 0; + patch->high_note = 0x7fffffff; + patch->volume = 120; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if ((x = read (mod_fd, patch->data, len)) != len) + { + fprintf (stderr, "Short file (sample at %d (%d!=%d)\n", sample_ptr, x, len); + close (mod_fd); + free (patch); + return 0; + } + /* + * fprintf (stderr, "Sample %02d: %05d, %05d, %05d, %07d %s\n", i, + * len, loop_start, loop_end, base_freq, sample->name); + */ + if (write (seqfd, patch, sizeof (*patch) + len) == -1) + { + perror ("ioctl /dev/sequencer"); + exit (-1); + } + else + sample_ok[i] = 1; + + free (patch); + } + } + + nr_patterns = slen; + songlength = slen; + nr_channels = 4; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[64][4][4]; + int pat, channel, x; + + int pp = 1104 + (position * 1024); + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if ((x = read (mod_fd, patterns, 1024)) != 1024) + { + fprintf (stderr, "Short file (pattern at %d), %d!=%d\n", pp, x, 1024); + close (mod_fd); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + + for (channel = 0; channel < 4; channel++) + { + unsigned char *p; + + unsigned vol, note, octave, sample, effect, params; + + p = &patterns[pat][channel][0]; + + if (p[0] < 251) + { + note = p[0] & 15; + octave = p[0] / 16; + + note = 48 + octave * 12 + note; + + sample = p[1] / 8; + vol = (p[1] & 7) + (p[2] / 2); + effect = p[2] & 0xF; + params = p[3]; + } + else + { + note = 0; + octave = 0; + + sample = 0; + vol = 0; + effect = CMD_NONOTE; + params = 0; + } + + (*pattern_table[position])[channel][pat].note = note; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + + } + + } + + close (mod_fd); + return 1; +} + +int +load_module (char *name) +{ + + struct sample_header + { + char name[22]; + unsigned short length; /* In words */ + + unsigned char finetune; + unsigned char volume; + + unsigned short repeat_point; /* In words */ + unsigned short repeat_length; /* In words */ + }; + + int i, mod_fd, total_mem; + int sample_ptr, pattern_loc; + + int position; + + unsigned char *tune_ptr; /* array 0-127 */ + + char header[1084]; + + int nr_samples; /* 16 or 32 samples */ + int slen, npat; + char mname[23]; + + ioctl (seqfd, SNDCTL_SEQ_SYNC, 0); + ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev); + + clock_rate = 50.0; + + for (i = 0; i < MAX_POSITION; i++) + pattern_len[i] = 64; + + for (i = 0; i < MAX_POSITION; i++) + pattern_tempo[i] = 0; + + if ((mod_fd = open (name, O_RDONLY, 0)) == -1) + { + perror (name); + return 0; + } + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + if (lseek (mod_fd, 0, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if (header[28] == 0x1a) + return load_stm_module (mod_fd, name); + + if (*(unsigned short *) &header[0] == 0x6669) + return load_669_module (mod_fd, name); + + if (!strncmp (header, "MMD0", 4)) + return load_mmd0_module (mod_fd, name); + + fprintf (stderr, "Loading .MOD module: %s\n", name); + + strncpy (mname, header, 22); + fprintf (stderr, "\nModule: %s - ", mname); + + if (!strncmp (&header[1080], "M.K.", 4) || !strncmp (&header[1080], "FLT8", 4)) + { + fprintf (stderr, "31 samples\n"); + nr_samples = 31; + } + else + { + fprintf (stderr, "15 samples\n"); + nr_samples = 15; + } + + if (nr_samples == 31) + { + sample_ptr = pattern_loc = 1084; + slen = header[950]; + tune_ptr = (unsigned char *) &header[952]; + } + else + { + sample_ptr = pattern_loc = 600; + slen = header[470]; + tune_ptr = (unsigned char *) &header[472]; + } + + npat = 0; + for (i = 0; i < 128; i++) + { + tune[i] = tune_ptr[i]; + + if (tune_ptr[i] > npat) + npat = tune_ptr[i]; + } + npat++; + + fprintf (stderr, "Song lenght %d, %d patterns.\n", slen, npat); + + sample_ptr += (npat * 1024); /* Location where the first sample is stored */ + total_mem = 0; + + for (i = 0; i < 32; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end; + unsigned short loop_flags = 0; + char pname[22]; + + struct sample_header *sample; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[20 + (i * 30)]; + + len = intelize (sample->length) * 2; + loop_start = intelize (sample->repeat_point) * 2; + loop_end = loop_start + (intelize (sample->repeat_length) * 2); + + if (loop_start > len) + loop_start = 0; + if (loop_end > len) + loop_end = len; + + if (loop_end <= loop_start) + loop_end = loop_start + 1; + + if (loop_end > 2 && loop_end > loop_start) + loop_flags = WAVE_LOOPING; + + strncpy (pname, sample->name, 20); + + if (len > 0) + { + fprintf (stderr, "Sample %02d: L%05d, S%05d, E%05d V%02d %s\n", + i, + len, + loop_start, + loop_end, + sample->volume, + pname); + + total_mem += len; + + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_note = 261630; /* Middle C */ + patch->base_freq = 8448; + patch->low_note = 0; + patch->high_note = 20000000; + patch->volume = 120; + patch->panning = 0; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if (read (mod_fd, patch->data, len) != len) + { + fprintf (stderr, "Short file (sample) %d\n", sample_ptr); + close (mod_fd); + free (patch); + return 0; + } + + SEQ_WRPATCH (patch, sizeof (*patch) + len); + + sample_ok[i] = 1; + if (sample->volume == 0) sample->volume = 64; + sample_vol[i] = sample->volume; + + free (patch); + } + } + + nr_patterns = npat; + songlength = slen; + nr_channels = 4; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[64][4][4]; + int pat, channel; + + int pp = pattern_loc + (position * 1024); + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if (read (mod_fd, patterns, 1024) != 1024) + { + fprintf (stderr, "Short file (pattern %d) %d\n", tune[position], pp); + close (mod_fd); + return 0; + } + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + for (channel = 0; channel < 4; channel++) + { + unsigned short tmp; + unsigned char *p; + + unsigned period, sample, effect, params, note, vol; + + p = &patterns[pat][channel][0]; + + tmp = (p[0] << 8) | p[1]; + sample = (tmp >> 8) & 0x10; + period = + MIN (tmp & 0xFFF, 1023); + tmp = (p[2] << 8) | p[3]; + sample |= tmp >> 12; + effect = (tmp >> 8) & 0xF; + params = tmp & 0xFF; + + note = 0; + + if (period) + { + /* + * Convert period to a Midi note number + */ + + for (note = 0; note < 37 && period != period_table[note]; note++); + if (note >= 37) + note = 0; + + note += 48; + } + + vol = 64; + + if (sample) + if (effect == 0xc) + { + vol = params; + } + else + vol = sample_vol[sample - 1]; + + vol *= 2; + if (vol>64)vol--; + + (*pattern_table[position])[channel][pat].note = note; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + } + } + + close (mod_fd); + return 1; +} + +int +panning (int ch) +{ + static int panning_tab[] = + {-110, 110, 110, -110}; + + return panning_tab[ch % 4]; +} + +void +set_speed (int parm) +{ + if (!parm) + parm = 1; + + if (parm < 32) + { + ticks_per_division = parm; + } + else + { + tick_duration = (60.0 / parm) * 10.0; + } + +} + +void +play_module (char *name) +{ + int i, position, jump_to_pos; + + init_voices (); + + SEQ_START_TIMER (); +#if 1 + for (i=0;i<32;i++) + { + SEQ_EXPRESSION(gus_dev, i, 127); + SEQ_MAIN_VOLUME(gus_dev, i, 100); + } +#endif + next_time = 0.0; + + set_speed (6); + + for (position = 0; position < songlength; position++) + { + int tick, pattern, channel, pos, go_to; + + pos = tune[position]; + if (pattern_tempo[position]) + set_speed (pattern_tempo[position]); + + jump_to_pos = -1; + for (pattern = 0; pattern < pattern_len[position] && jump_to_pos == -1; pattern++) + { + this_time = 0.0; + + for (channel = 0; channel < nr_channels; channel++) + { + if ((go_to = play_note (channel, &(*pattern_table[pos])[channel][pattern])) != -1) + jump_to_pos = go_to; + + } + + next_time += tick_duration; + + for (tick = 1; tick < ticks_per_division; tick++) + { + for (channel = 0; channel < nr_channels; channel++) + lets_play_voice (channel, &voices[channel]); + next_time += tick_duration; + } + + } + + if (jump_to_pos >= 0) + position = jump_to_pos; + } + + SEQ_WAIT_TIME ((int) next_time + 200); /* Wait extra 2 secs */ + + for (i = 0; i < nr_channels; i++) + SEQ_STOP_NOTE (gus_dev, i, 0, 127); + SEQ_DUMPBUF (); + + for (i = 0; i < nr_patterns; i++) + free (pattern_table[i]); +} + +void +sync_time () +{ + if (next_time > this_time) + { + SEQ_WAIT_TIME ((long) next_time); + this_time = next_time; + } +} + +void +set_volslide (int channel, struct note_info *pat) +{ + int n; + + voices[channel].volslide = 0; + + if ((n = (pat->parm1 & 0xf0) >> 4)) + voices[channel].volslide = n; + else + voices[channel].volslide = pat->parm1 & 0xf; +} + +void +set_slideto (int channel, struct note_info *pat) +{ + int size, rate, dir, range = 200; + + rate = pat->parm1; + size = voices[channel].note - pat->note; + if (!size) + return; + + if (size < 0) + { + size *= -1; + dir = -1; + } + else + dir = 1; + + if (size > 2) + { + range = size * 100; + rate = rate * size / 200; + } + + rate = pat->parm1 * dir / 30; + if (!rate) + rate = 1; + + voices[channel].slide_pitch = 1; + voices[channel].slide_goal = (dir * 8192 * 200 * 2 / size) / range; + voices[channel].pitchbender = 0; + voices[channel].slide_rate = rate; + SEQ_BENDER_RANGE (gus_dev, channel, range); +} + +int +play_note (int channel, struct note_info *pat) +{ + int jump = -1; + int sample; + + if (pat->sample == 0x3f) + pat->sample = 0; + + if (pat->command == CMD_NONOTE) + return -1; /* Undefined */ + + sample = pat->sample; + + if (sample && !pat->note) + { + pat->note = voices[channel].note; + } + + if (sample) + voices[channel].sample = sample; + else + sample = voices[channel].sample; + + sample--; + + if (pat->note && pat->command != 3) /* Have a note -> play */ + { + if (sample < 0) + sample = voices[channel].sample - 1; + + if (!sample_ok[sample]) + sample = voices[channel].sample - 1; + + if (sample < 0) + sample = 0; + + if (sample_ok[sample]) + { + sync_time (); + + if (pat->vol > 127) pat->vol=127; + SEQ_SET_PATCH (gus_dev, channel, sample); + SEQ_PANNING (gus_dev, channel, panning (channel)); + SEQ_PITCHBEND (gus_dev, channel, 0); + SEQ_START_NOTE (gus_dev, channel, pat->note, pat->vol); + + voices[channel].volume = pat->vol; + voices[channel].note = pat->note; + voices[channel].slide_pitch = 0; + } + else + SEQ_STOP_NOTE (gus_dev, channel, pat->note, pat->vol); + } + + switch (pat->command) + { + + case CMD_NOP:; + break; + + case CMD_JUMP: + jump = pat->parm1; + break; + + case CMD_BREAK: + jump = -2; + break; + + case CMD_SPEED: + set_speed (pat->parm1); + break; + + case CMD_SLIDEUP: + voices[channel].slide_pitch = 1; + voices[channel].slide_goal = 8191; + voices[channel].pitchbender = 0; + voices[channel].slide_rate = pat->parm1 * SLIDE_SIZE; + SEQ_BENDER_RANGE (gus_dev, channel, 200); + break; + + case CMD_SLIDEDOWN: + voices[channel].slide_pitch = 1; + voices[channel].slide_goal = -8192; + voices[channel].pitchbender = 0; + voices[channel].slide_rate = -pat->parm1 * SLIDE_SIZE; + SEQ_BENDER_RANGE (gus_dev, channel, 200); + break; + + case CMD_SLIDETO: + set_slideto (channel, pat); + break; + + case CMD_VOLUME: + { + int vol = pat->parm1*2; + if (vol>127) vol=127; + if (pat->note && pat->command != 3) + break; + SEQ_START_NOTE (gus_dev, channel, 255, vol); + } + break; + + case CMD_ARPEG: + break; + + case 0x0e: + /* printf ("Cmd 0xE%02x\n", pat->parm1); */ + break; + + case CMD_VOLSLIDE: + set_slideto (channel, pat); + break; + + default: + /* printf ("Command %x %02x\n", pat->command, pat->parm1); */ + } + + return jump; +} + +void +lets_play_voice (int channel, struct voice_info *v) +{ + if (v->slide_pitch) + { + v->pitchbender += v->slide_rate; + if (v->slide_goal < 0) + { + if (v->pitchbender <= v->slide_goal) + { + v->pitchbender = v->slide_goal; + v->slide_pitch = 0; /* Stop */ + } + } + else + { + if (v->pitchbender >= v->slide_goal) + { + v->pitchbender = v->slide_goal; + v->slide_pitch = 0; /* Stop */ + } + } + + sync_time (); + SEQ_PITCHBEND (gus_dev, channel, v->pitchbender); + } + + if (v->volslide) + { + v->volume += v->volslide; + sync_time (); + + if (v->volume > 127) v->volume = 127; + SEQ_START_NOTE (gus_dev, channel, 255, v->volume); + } +} diff --git a/sys/i386/isa/sound/gustest/gpatinfo.c b/sys/i386/isa/sound/gustest/gpatinfo.c new file mode 100644 index 0000000..17dcb12 --- /dev/null +++ b/sys/i386/isa/sound/gustest/gpatinfo.c @@ -0,0 +1,176 @@ +/* + * gpatinfo.c: This program demonstrates the patch management + * interface of the GUS driver. + * + * NOTE! The patch manager interface is highly device dependent, + * currently incompletely implemented prototype and + * will change before final implementation. + * + * $Id$ + */ + +#include <stdio.h> +#include <machine/ultrasound.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <fcntl.h> +#include "gmidi.h" + +#define GUS_DEV gus_dev + +#define patch_access(cmd, rec) \ + rec.command = cmd;\ + rec.device = gus_dev;\ + if (ioctl(seqfd, SNDCTL_PMGR_IFACE, &rec)==-1)\ + {\ + perror("/dev/sequencer(SNDCTL_PMGR_IFACE/" #cmd ")");\ + exit(-1);\ + } + +SEQ_DEFINEBUF (2048); + +int seqfd; + +int gus_dev = -1; + +/* + * The function seqbuf_dump() must always be provided + */ + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +int +main (int argc, char *argv[]) +{ + int i, j, n; + struct synth_info info; + struct patch_info *patch; + struct patmgr_info mgr, mgr2, mgr3; + + if ((seqfd = open ("/dev/sequencer", O_WRONLY, 0)) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + +/* + * First locate the GUS device + */ + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Error: Gravis Ultrasound not detected\n"); + exit (-1); + } + + printf("Gravis UltraSound device = %d\n", gus_dev); + + /* + * Get type of the Patch Manager interface of the GUS device + */ + + patch_access(PM_GET_DEVTYPE, mgr); + printf("Patch manager type: %d\n", mgr.parm1); + + if (mgr.parm1 != PMTYPE_WAVE) + { + fprintf(stderr, "Hups, this program seems to be obsolete\n"); + exit(-1); + } + + /* + * The GUS driver supports up to 256 different midi program numbers but + * this limit can be changed before compiling the driver. The following + * call returns the value compiled to the driver. + */ + + patch_access(PM_GET_PGMMAP, mgr); + printf("Device supports %d midi programs.\n", mgr.parm1); + + /* + * Each program can be undefined or it may have one or more patches. + * A patch consists of header and the waveform data. If there is more + * than one patch in a program, the right one is selected by checking the + * note number when the program is played. + * + * The following call reads an array indexed by program number. Each + * element defines the number of patches defined for the corresponding + * program. + */ + printf("Loaded programs:\n"); + + for (i=0;i<mgr.parm1;i++) + if (mgr.data.data8[i]) + { + printf("%03d: %2d patches\n", i, mgr.data.data8[i]); + + /* + * Next get the magic keys of the patches associated with this program. + * This key can be used to access the patc data. + */ + mgr2.parm1=i; + patch_access(PM_GET_PGM_PATCHES, mgr2); + for (j = 0;j<mgr2.parm1;j++) + { + printf("\tPatch %d: %3d ", j, mgr2.data.data32[j]); + + /* + * The last step is to read the patch header (without wave data). + * The header is returned in the mgr3.data. The field parm1 returns + * address of the wave data in tge GUS DRAM. Parm2 returns + * size of the struct patch_info in the kernel. + * + * There is also the PM_SET_PATCH call which allows modification of the + * header data. The only limitation is that the sample len cannot be + * increased. + */ + mgr3.parm1 = mgr2.data.data32[j]; + patch_access(PM_GET_PATCH, mgr3); + patch = (struct patch_info *)&mgr3.data; /* Pointer to the patch hdr */ + + printf("DRAM ptr = %7d, sample len =%6d bytes.\n", + mgr3.parm1, patch->len); + + } + } + + i = gus_dev; + + if (ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &i)==-1) exit(-1); + printf("%d bytes of DRAM available for wave data\n", i); + + + exit(0); +} diff --git a/sys/i386/isa/sound/gustest/gusload.c b/sys/i386/isa/sound/gustest/gusload.c new file mode 100644 index 0000000..1ed9a3b --- /dev/null +++ b/sys/i386/isa/sound/gustest/gusload.c @@ -0,0 +1,352 @@ +/* + * $Id$ + */ +/* + * patutil.c - A sample program which loads patches to the Gravis + * Ultrasound + * + */ + +#ifndef PATCH_PATH +#define PATCH_PATH "/D/ultrasnd/midi" +#endif + +#include <stdio.h> +#include <machine/ultrasound.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <fcntl.h> +#include "gmidi.h" + +struct pat_header + { + char magic[12]; + char version[10]; + char description[60]; + unsigned char instruments; + char voices; + char channels; + unsigned short nr_waveforms; + unsigned short master_volume; + unsigned long data_size; + }; + +struct sample_header + { + char name[7]; + unsigned char fractions; + long len; + long loop_start; + long loop_end; + unsigned short base_freq; + long low_note; + long high_note; + long base_note; + short detune; + unsigned char panning; + + unsigned char envelope_rate[6]; + unsigned char envelope_offset[6]; + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + char modes; + + short scale_frequency; + unsigned short scale_factor; + }; + +#define GUS_DEV gus_dev + +SEQ_DEFINEBUF (2048); + +int seqfd; + +int gus_dev = -1; + +struct patch_info *patch; + +/* + * The function seqbuf_dump() must always be provided + */ + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +int +main (int argc, char *argv[]) +{ + int i, n, patfd, pgm, print_only = 0; + struct synth_info info; + struct pat_header header; + struct sample_header sample; + char buf[256]; + char name[256]; + long offset; + + if ((seqfd = open ("/dev/sequencer", O_WRONLY, 0)) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Error: Gravis Ultrasound not detected\n"); + exit (-1); + } + + if (argc == 2) + { + if (!strcmp (argv[1], "reset")) + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + perror ("Sample reset"); + exit (0); + } + + if (argc != 3) + { + fprintf (stderr, "Usage: %s pgm# patchfile\n", argv[0]); + fprintf (stderr, " or : %s pgm# GM\n", argv[0]); + fprintf (stderr, " or : %s pgm# -l\n", argv[0]); + fprintf (stderr, " or : %s reset\n", argv[0]); + fprintf (stderr, " or : %s -l patchfile\n", argv[0]); + exit (-1); + } + + pgm = atoi (argv[1]); + strcpy (name, argv[2]); + + if (strcmp (name, "GM") == 0 || strcmp(name, "-l")==0) + { + if (strcmp (name, "-l") == 0) print_only = 1; + if (pgm < 0 || pgm > 127) + { + fprintf (stderr, "pgm# must be between 0 and 127\n"); + exit (-1); + } + + sprintf (name, PATCH_PATH "/%s.pat", patch_names[pgm]); + + if (!print_only) + fprintf (stderr, "Loading program %d from %s\n", pgm, name); + } + else if (strcmp (argv[1], "-l") == 0) + print_only = 1; + + if ((patfd = open (name, O_RDONLY, 0)) == -1) + { + perror (name); + exit (-1); + } + + if (read (patfd, buf, 0xef) != 0xef) + { + fprintf (stderr, "%s: Short file\n", name); + exit (-1); + } + + memcpy ((char *) &header, buf, sizeof (header)); + + if (strncmp (header.magic, "GF1PATCH110", 12)) + { + fprintf (stderr, "%s: Not a patch file\n", name); + exit (-1); + } + + if (strncmp (header.version, "ID#000002", 10)) + { + fprintf (stderr, "%s: Incompatible patch file version\n", name); + exit (-1); + } + + header.nr_waveforms = *(unsigned short *) &buf[85]; + header.master_volume = *(unsigned short *) &buf[87]; + + if (print_only) + { + printf ("Patch file: %s contains %d samples\n\n", name, header.nr_waveforms); + printf ("Master volume: %d\n", header.master_volume); + } + + offset = 0xef; + + for (i = 0; i < header.nr_waveforms; i++) + { + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + exit (-1); + } + + if (read (patfd, &buf, sizeof (sample)) != sizeof (sample)) + { + fprintf (stderr, "%s: Short file\n", name); + exit (-1); + } + + memcpy ((char *) &sample, buf, sizeof (sample)); + + /* + * Since some fields of the patch record are not 32bit aligned, we must + * handle them specially. + */ + sample.low_note = *(long *) &buf[22]; + sample.high_note = *(long *) &buf[26]; + sample.base_note = *(long *) &buf[30]; + sample.detune = *(short *) &buf[34]; + sample.panning = (unsigned char) buf[36]; + + memcpy (sample.envelope_rate, &buf[37], 6); + memcpy (sample.envelope_offset, &buf[43], 6); + + sample.tremolo_sweep = (unsigned char) buf[49]; + sample.tremolo_rate = (unsigned char) buf[50]; + sample.tremolo_depth = (unsigned char) buf[51]; + + sample.vibrato_sweep = (unsigned char) buf[52]; + sample.vibrato_rate = (unsigned char) buf[53]; + sample.vibrato_depth = (unsigned char) buf[54]; + sample.modes = (unsigned char) buf[55]; + sample.scale_frequency = *(short *) &buf[56]; + sample.scale_factor = *(unsigned short *) &buf[58]; + + if (print_only) + { + printf("\nSample: %03d / %s\n", i, sample.name); + printf ("Len: %d, Loop start: %d, Loop end: %d\n", sample.len, sample.loop_start, sample.loop_end); + printf ("Flags: "); + if (sample.modes & WAVE_16_BITS) + printf ("16 bit "); + if (sample.modes & WAVE_UNSIGNED) + printf ("unsigned "); + if (sample.modes & WAVE_LOOP_BACK) + printf("reverse "); + if (sample.modes & WAVE_BIDIR_LOOP) + printf("bidir "); + if (sample.modes & WAVE_LOOPING) + printf ("looping "); else printf("one_shot" ); + if (sample.modes & WAVE_SUSTAIN_ON) + printf ("sustain "); + if (sample.modes & WAVE_ENVELOPES) + printf ("enveloped "); + printf ("\n"); + + if (sample.modes & WAVE_ENVELOPES) + { + int i; + + printf ("Envelope info: "); + for (i = 0; i < 6; i++) + { + printf ("%d/%d ", sample.envelope_rate[i], + sample.envelope_offset[i]); + } + printf ("\n"); + } + + printf("Tremolo: sweep=%d, rate=%d, depth=%d\n", + sample.tremolo_sweep, + sample.tremolo_rate, + sample.tremolo_depth); + + printf("Vibrato: sweep=%d, rate=%d, depth=%d\n", + sample.vibrato_sweep, + sample.vibrato_rate, + sample.vibrato_depth); + } + + offset = offset + 96; + patch = (struct patch_info *) malloc (sizeof (*patch) + sample.len); + + patch->key = GUS_PATCH; + patch->device_no = GUS_DEV; + patch->instr_no = pgm; + patch->mode = sample.modes | WAVE_TREMOLO | + WAVE_VIBRATO | WAVE_SCALE; + patch->len = sample.len; + patch->loop_start = sample.loop_start; + patch->loop_end = sample.loop_end; + patch->base_note = sample.base_note; + patch->high_note = sample.high_note; + patch->low_note = sample.low_note; + patch->base_freq = sample.base_freq; + patch->detuning = sample.detune; + patch->panning = (sample.panning - 7) * 16; + + memcpy (patch->env_rate, sample.envelope_rate, 6); + memcpy (patch->env_offset, sample.envelope_offset, 6); + + patch->tremolo_sweep = sample.tremolo_sweep; + patch->tremolo_rate = sample.tremolo_rate; + patch->tremolo_depth = sample.tremolo_depth; + + patch->vibrato_sweep = sample.vibrato_sweep; + patch->vibrato_rate = sample.vibrato_rate; + patch->vibrato_depth = sample.vibrato_depth; + + patch->scale_frequency = sample.scale_frequency; + patch->scale_factor = sample.scale_factor; + + patch->volume = header.master_volume; + + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + exit (-1); + } + + if (!print_only) + { + if (read (patfd, patch->data, sample.len) != sample.len) + { + fprintf (stderr, "%s: Short file\n", name); + exit (-1); + } + + SEQ_WRPATCH (patch, sizeof (*patch) + sample.len); + } + + offset = offset + sample.len; + } + + exit (0); +} diff --git a/sys/i386/isa/sound/gustest/midithru.c b/sys/i386/isa/sound/gustest/midithru.c new file mode 100644 index 0000000..c4fab7f --- /dev/null +++ b/sys/i386/isa/sound/gustest/midithru.c @@ -0,0 +1,328 @@ +/* + * $Id$ + */ +#include <stdio.h> +#include <machine/soundcard.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/errno.h> + +SEQ_DEFINEBUF (2048); +SEQ_PM_DEFINES; + +int seqfd, dev = 0; +unsigned char buf[100]; +int bufp; + +/* LRU list for free operators */ + +unsigned char free_list[256]; +int fhead=0, ftail=0, flen=0; + +/* LRU list for still playing notes */ + +unsigned char note_list[256]; +int nhead=0, ntail=0, nlen=0; +unsigned char oper_note[32]; + +int pgm = 0; +int num_voices; +int bender = 0; /* Initially off */ + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +void +stop_note(int note, int velocity) +{ + int i, op; + + op=255; + + for (i=0;i<num_voices && op==255;i++) + { + if (oper_note[i]== note) op=i; + } + + if (op==255) + { + fprintf(stderr, "Note %d off, note not started\n", note); + fprintf(stderr, "%d, %d\n", flen, nlen); + return; /* Has already been killed ??? */ + } + + SEQ_STOP_NOTE(dev, op, note, velocity); + SEQ_DUMPBUF(); + + oper_note[op] = 255; + + free_list[ftail]=op; + flen++; + ftail = (ftail+1) % num_voices; + + for (i=0;i<16;i++) + if (note_list[i] == op) note_list[i] = 255; + + while (nlen && note_list[nhead] == 255) + { + nlen--; + /* printf("Remove from note queue %d, len %d\n", nhead, nlen); */ + nhead = (nhead+1) % 256; + } +} + +void +kill_one_note() +{ + int oldest; + + if (!nlen) {fprintf(stderr, "Free list empty but no notes playing\n");return;} /* No notes playing */ + + oldest = note_list[nhead]; + nlen--; + nhead = (nhead+1) % 256; + + fprintf(stderr, "Killing oper %d, note %d\n", oldest, oper_note[oldest]); + + if (oldest== 255) return; /* Was already stopped. Why? */ + + stop_note(oper_note[oldest], 127); +} + +void +start_note(int note, int velocity) +{ + int free; + + if (!flen) kill_one_note(); + + if (!flen) {printf("** no free voices\n");return;} /* Panic??? */ + + free = free_list[fhead]; + flen--; + fhead = (fhead+1) % num_voices; + + note_list[ntail] = free; + + if (nlen>255) + { +#if 0 + fprintf(stderr, "Note list overflow %d, %d, %d\n", + nlen, nhead, ntail); +#endif + nlen=0; /* Overflow -> hard reset */ + } + nlen++; + ntail = (ntail+1) % 256; + + oper_note[free] = note; + + SEQ_SET_PATCH(dev, free, pgm); + SEQ_PITCHBEND(dev, free, bender); + SEQ_START_NOTE(dev, free, note, velocity); + SEQ_DUMPBUF(); +} + +void +channel_pressure(int ch, int pressure) +{ + int i; + + for (i=0;i<num_voices;i++) + { + if (oper_note[i] != 255) + { +#if 1 + SEQ_CHN_PRESSURE(dev, i, pressure); +#else + SEQ_EXPRESSION(dev, i, pressure); +#endif + SEQ_DUMPBUF(); + } + } +} + +void +pitch_bender(int ch, int value) +{ + int i; + + value -= 8192; + + bender = value; + + for (i=0;i<num_voices;i++) + { + if (oper_note[i] != 255) + { + bender = value; + SEQ_PITCHBEND(dev, i, value); + SEQ_DUMPBUF(); + } + } +} + +void +do_buf() +{ + int ch = buf[0] & 0x0f; + int value; + + switch (buf[0] & 0xf0) + { + case 0x90: /* Note on */ + if (bufp < 3) break; + /* printf("Note on %d %d %d\n", ch, buf[1], buf[2]); */ + if (buf[2]) + start_note(buf[1], buf[2]); + else + stop_note(buf[1], buf[2]); + bufp=1; + break; + + case 0xb0: /* Control change */ + if (bufp < 3) break; + /* printf("Control change %d %d %d\n", ch, buf[1], buf[2]); */ + bufp=1; + break; + + case 0x80: /* Note off */ + if (bufp < 3) break; + /* printf("Note off %d %d %d\n", ch, buf[1], buf[2]); */ + stop_note(buf[1], buf[2]); + bufp=1; + break; + + case 0xe0: /* Pitch bender */ + if (bufp < 3) break; + value = ((buf[2] & 0x7f) << 7) | (buf[1] & 0x7f); + /* printf("Pitch bender %d %d\n", ch, value >> 7); */ + pitch_bender(ch, value); + bufp=1; + break; + + case 0xc0: /* Pgm change */ + if (bufp < 2) break; + /* printf("Pgm change %d %d\n", ch, buf[1]); */ + pgm = buf[1]; + if (PM_LOAD_PATCH(dev, 0, pgm) < 0) + if (errno != ESRCH) /* No such process */ + perror("PM_LOAD_PATCH"); + bufp=0; + break; + + case 0xd0: /* Channel pressure */ + if (bufp < 2) break; + /* printf("Channel pressure %d %d\n", ch, buf[1]); */ + channel_pressure(ch, buf[1]); + bufp=1; + break; + + default: + bufp=0; + } +} + +int +main (int argc, char *argv[]) +{ + int i, n, max_voice = 999; + + struct synth_info info; + + unsigned char ev[4], *p; + + if (argc >= 2) dev = atoi(argv[1]); + + for (i=0;i<16;i++) oper_note[i] = 255; + + if ((seqfd = open ("/dev/sequencer", O_RDWR, 0)) == -1) + { + perror ("open /dev/sequencer"); + exit (-1); + } + + if (argc >= 3) + { + int d = dev; + ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &d); + } + + info.device = dev; + + if (ioctl(seqfd, SNDCTL_SYNTH_INFO, &info)==-1) + { + perror ("info /dev/sequencer"); + exit (-1); + } + + num_voices = info.nr_voices; + if (num_voices>max_voice)num_voices = max_voice; + fprintf(stderr, "Output to synth device %d (%s)\n", dev, info.name); + fprintf(stderr, "%d voices available\n", num_voices); + + for (i=0;i<num_voices;i++) + { + flen++; + free_list[fhead] = i; + fhead = (fhead+1) % num_voices; + } + + bufp = 0; + if (PM_LOAD_PATCH(dev, 0, 0) < 0) /* Load the default instrument */ + if (errno != ESRCH) /* No such process */ + perror("PM_LOAD_PATCH"); + + while (1) + { + if ((n = read (seqfd, ev, sizeof (ev))) == -1) + { + perror ("read /dev/sequencer"); + exit (-1); + } + + for (i = 0; i <= (n / 4); i++) + { + p = &ev[i * 4]; + + if (p[0] == SEQ_MIDIPUTC && p[2] == 0 /* Midi if# == 0 */) + { +/* printf("%02x ", p[1]);fflush(stdout); */ + if (p[1] & 0x80) /* Status */ + { + if (bufp) + do_buf (); + buf[0] = p[1]; + bufp = 1; + } + else if (bufp) + { + buf[bufp++] = p[1]; + if ((buf[0] & 0xf0) == 0x90 || (buf[0] & 0xf0) == 0x80 || (buf[0] & 0xf0) == 0xb0 || + (buf[0] & 0xf0) == 0xe0) + { + if (bufp == 3) + do_buf (); + } + else + if ((buf[0] & 0xf0) == 0xc0 || (buf[0] & 0xf0) == 0xd0) + { + if (bufp == 2) do_buf(); + } + } + } + } + } + + exit (0); +} diff --git a/sys/i386/isa/sound/gustest/pmtest.c b/sys/i386/isa/sound/gustest/pmtest.c new file mode 100644 index 0000000..0520545 --- /dev/null +++ b/sys/i386/isa/sound/gustest/pmtest.c @@ -0,0 +1,412 @@ +/* + * $Id$ + */ +/* + * CAUTION! This program is just an incompletely implemented version + * of the patch manager daemon for GUS. Using this program + * with the driver version 1.99.9 will hang your system + * completely (sooner or later). + * + * This program is for information only. The final + * implementation of the patch manager will not be + * compatible with this one. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <machine/ultrasound.h> +#include <strings.h> +#include <sys/errno.h> +#include "gmidi.h" + +#ifndef PATCH_PATH +#define PATCH_PATH "/D/ultrasnd/midi" +#endif + +char loadmap[256] = +{0}; /* 1 if the patch is already loaded */ + +struct pat_header + { + char magic[12]; + char version[10]; + char description[60]; + unsigned char instruments; + char voices; + char channels; + unsigned short nr_waveforms; + unsigned short master_volume; + unsigned long data_size; + }; + +struct sample_header + { + char name[7]; + unsigned char fractions; + long len; + long loop_start; + long loop_end; + unsigned short base_freq; + long low_note; + long high_note; + long base_note; + short detune; + unsigned char panning; + + unsigned char envelope_rate[6]; + unsigned char envelope_offset[6]; + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + char modes; + + short scale_frequency; + unsigned short scale_factor; + }; +int seqfd = 0, gus_dev = -1; + +struct patch_info *patch; + +int +do_load_patch (struct patmgr_info *rec) +{ + int i, patfd, pgm, print_only = 0; + struct pat_header header; + struct sample_header sample; + char buf[256]; + char name[256]; + long offset; + + pgm = rec->data.data8[0]; + + if (loadmap[pgm]) + return 0; /* Already loaded */ + + sprintf (name, PATCH_PATH "/%s.pat", patch_names[pgm]); + + if ((patfd = open (name, O_RDONLY, 0)) == -1) + { + perror (name); + return errno; + } + + if (read (patfd, buf, 0xef) != 0xef) + { + fprintf (stderr, "%s: Short file\n", name); + return EIO; + } + + memcpy ((char *) &header, buf, sizeof (header)); + + if (strncmp (header.magic, "GF1PATCH110", 12)) + { + fprintf (stderr, "%s: Not a patch file\n", name); + return EINVAL; + } + + if (strncmp (header.version, "ID#000002", 10)) + { + fprintf (stderr, "%s: Incompatible patch file version\n", name); + return EINVAL; + } + + header.nr_waveforms = *(unsigned short *) &buf[85]; + header.master_volume = *(unsigned short *) &buf[87]; + + printf ("GUS: Loading: %s\n", name); + + offset = 0xef; + + for (i = 0; i < header.nr_waveforms; i++) + { + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + return errno; + } + + if (read (patfd, &buf, sizeof (sample)) != sizeof (sample)) + { + fprintf (stderr, "%s: Short file\n", name); + return EIO; + } + + memcpy ((char *) &sample, buf, sizeof (sample)); + + /* + * Since some fields of the patch record are not 32bit aligned, we must + * handle them specially. + */ + sample.low_note = *(long *) &buf[22]; + sample.high_note = *(long *) &buf[26]; + sample.base_note = *(long *) &buf[30]; + sample.detune = *(short *) &buf[34]; + sample.panning = (unsigned char) buf[36]; + + memcpy (sample.envelope_rate, &buf[37], 6); + memcpy (sample.envelope_offset, &buf[43], 6); + + sample.tremolo_sweep = (unsigned char) buf[49]; + sample.tremolo_rate = (unsigned char) buf[50]; + sample.tremolo_depth = (unsigned char) buf[51]; + + sample.vibrato_sweep = (unsigned char) buf[52]; + sample.vibrato_rate = (unsigned char) buf[53]; + sample.vibrato_depth = (unsigned char) buf[54]; + sample.modes = (unsigned char) buf[55]; + sample.scale_frequency = *(short *) &buf[56]; + sample.scale_factor = *(unsigned short *) &buf[58]; + + if (print_only) + { + printf ("\nSample: %03d / %s\n", i, sample.name); + printf ("Len: %d, Loop start: %d, Loop end: %d\n", sample.len, sample.loop_start, sample.loop_end); + printf ("Flags: "); + if (sample.modes & WAVE_16_BITS) + printf ("16 bit "); + if (sample.modes & WAVE_UNSIGNED) + printf ("unsigned "); + if (sample.modes & WAVE_LOOP_BACK) + printf ("reverse "); + if (sample.modes & WAVE_BIDIR_LOOP) + printf ("bidir "); + if (sample.modes & WAVE_LOOPING) + printf ("looping "); + else + printf ("one_shot"); + if (sample.modes & WAVE_SUSTAIN_ON) + printf ("sustain "); + if (sample.modes & WAVE_ENVELOPES) + printf ("enveloped "); + printf ("\n"); + + if (sample.modes & WAVE_ENVELOPES) + { + int i; + + printf ("Envelope info: "); + for (i = 0; i < 6; i++) + { + printf ("%d/%d ", sample.envelope_rate[i], + sample.envelope_offset[i]); + } + printf ("\n"); + } + + printf ("Tremolo: sweep=%d, rate=%d, depth=%d\n", + sample.tremolo_sweep, + sample.tremolo_rate, + sample.tremolo_depth); + + printf ("Vibrato: sweep=%d, rate=%d, depth=%d\n", + sample.vibrato_sweep, + sample.vibrato_rate, + sample.vibrato_depth); + } + + offset = offset + 96; + patch = (struct patch_info *) malloc (sizeof (*patch) + sample.len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = pgm; + patch->mode = sample.modes | WAVE_TREMOLO | + WAVE_VIBRATO | WAVE_SCALE; + patch->len = sample.len; + patch->loop_start = sample.loop_start; + patch->loop_end = sample.loop_end; + patch->base_note = sample.base_note; + patch->high_note = sample.high_note; + patch->low_note = sample.low_note; + patch->base_freq = sample.base_freq; + patch->detuning = sample.detune; + patch->panning = (sample.panning - 7) * 16; + + memcpy (patch->env_rate, sample.envelope_rate, 6); + memcpy (patch->env_offset, sample.envelope_offset, 6); + + patch->tremolo_sweep = sample.tremolo_sweep; + patch->tremolo_rate = sample.tremolo_rate; + patch->tremolo_depth = sample.tremolo_depth; + + patch->vibrato_sweep = sample.vibrato_sweep; + patch->vibrato_rate = sample.vibrato_rate; + patch->vibrato_depth = sample.vibrato_depth; + + patch->scale_frequency = sample.scale_frequency; + patch->scale_factor = sample.scale_factor; + + patch->volume = header.master_volume; + + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + return errno; + } + + if (!print_only) + { + if (read (patfd, patch->data, sample.len) != sample.len) + { + fprintf (stderr, "%s: Short file\n", name); + return EIO; + } + + if (write (seqfd, patch, sizeof (*patch) + sample.len) == -1) + { + perror ("/dev/pmgr0"); + return errno; + } + } + + offset = offset + sample.len; + } + + loadmap[pgm] = 1; + return 0; +} + +int +main (int argc, char *argv[]) +{ + struct patmgr_info inf; + int err, i, n; + struct synth_info info; + + if ((seqfd = open ("/dev/patmgr0", O_RDWR, 0)) == -1) + { + fprintf (stderr, "Cannot open\n"); + perror ("/dev/patmgr0"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("NRSYNTH: /dev/patmgr0"); + exit (-1); + } + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("SYNTH_INFO: /dev/patmgr0"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Error: Gravis Ultrasound not detected\n"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + perror ("Sample reset"); + + for (i = 0; i < 256; i++) + loadmap[i] = 0; + + while (1) + { + if (read (seqfd, (char *) &inf, sizeof (inf)) != sizeof (inf)) + { + perror ("Read"); + exit (-1); + } + + if (inf.key == PM_K_EVENT) + switch (inf.command) + { + case PM_E_OPENED: + printf ("Opened\n"); + break; + + case PM_E_CLOSED: + printf ("Closed\n"); + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + perror ("Sample reset"); + for (i = 0; i < 256; i++) + loadmap[i] = 0; + break; + + case PM_E_PATCH_RESET: + printf ("Patch reset called\n"); + for (i = 0; i < 256; i++) + loadmap[i] = 0; + break; + + case PM_E_PATCH_LOADED: + printf ("Patch loaded by client\n"); + break; + + default: + printf ("Unknown event %d\n", inf.command); + inf.key = PM_ERROR; + inf.parm1 = EINVAL; + } + else if (inf.key == PM_K_COMMAND) + switch (inf.command) + { + case _PM_LOAD_PATCH: + if ((err = do_load_patch (&inf))) + if (err == ENOSPC) + { + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + { + perror ("Sample reset"); + return errno; + } + + for (i = 0; i < 256; i++) + loadmap[i] = 0; + err = do_load_patch (&inf); + } + + if (err) + { + inf.key = PM_ERROR; + inf.parm1 = err; + printf("Error = %d\n", err); + } + else + { + inf.key = PM_K_COMMAND; + inf.parm1 = 0; + } + break; + + default: + printf ("Unknown command %d\n", inf.command); + inf.key = PM_ERROR; + inf.parm1 = EINVAL; + } + else + { + printf ("Unknown event %d/%d\n", inf.key, inf.command); + inf.key = PM_ERROR; + inf.parm1 = EINVAL; + } + + if (write (seqfd, (char *) &inf, sizeof (inf)) != sizeof (inf)) + { + perror ("write"); + exit (-1); + } + } + + exit (0); +} diff --git a/sys/i386/isa/sound/ics2101.c b/sys/i386/isa/sound/ics2101.c new file mode 100644 index 0000000..c06fec0 --- /dev/null +++ b/sys/i386/isa/sound/ics2101.c @@ -0,0 +1,266 @@ +/* + * sound/ics2101.c + * + * Driver for the ICS2101 mixer of GUS v3.7. + * + * Copyright by Hannu Savolainen 1994 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_GUS) + +#ifdef __FreeBSD__ +#include <machine/ultrasound.h> +#else +#include "ultrasound.h" +#endif +#include "gus_hw.h" + +#define MIX_DEVS (SOUND_MASK_MIC|SOUND_MASK_LINE| \ + SOUND_MASK_SYNTH| \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +extern int gus_base; +static int volumes[ICS_MIXDEVS]; +static int left_fix[ICS_MIXDEVS] = +{1, 1, 1, 2, 1, 2}; +static int right_fix[ICS_MIXDEVS] = +{2, 2, 2, 1, 2, 1}; + +static int +scale_vol(int vol) +{ +#if 1 +/* + * Experimental volume scaling by Risto Kankkunen. + * This should give smoother volume response than just + * a plain multiplication. + */ + int e; + + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + vol = (31 * vol + 50) / 100; + e = 0; + if (vol) { + while (vol < 16) { + vol <<= 1; + e--; + } + vol -= 16; + e += 7; + } + return ((e << 4) + vol); +#else + return ((vol*127)+50)/100; +#endif +} + +static void +write_mix (int dev, int chn, int vol) +{ + int *selector; + unsigned long flags; + int ctrl_addr = dev << 3; + int attn_addr = dev << 3; + + vol=scale_vol(vol); + + if (chn == CHN_LEFT) + { + selector = left_fix; + ctrl_addr |= 0x00; + attn_addr |= 0x02; + } + else + { + selector = right_fix; + ctrl_addr |= 0x01; + attn_addr |= 0x03; + } + + DISABLE_INTR (flags); + OUTB (ctrl_addr, u_MixSelect); + OUTB (selector[dev], u_MixData); + OUTB (attn_addr, u_MixSelect); + OUTB ((unsigned char) vol, u_MixData); + RESTORE_INTR (flags); +} + +static int +set_volumes (int dev, int vol) +{ + int left = vol & 0x00ff; + int right = (vol >> 8) & 0x00ff; + + if (left < 0) + left = 0; + if (left > 100) + left = 100; + if (right < 0) + right = 0; + if (right > 100) + right = 100; + + write_mix (dev, CHN_LEFT, left); + write_mix (dev, CHN_RIGHT, right); + + vol = left + (right << 8); + volumes[dev] = vol; + return vol; +} + +static int +ics2101_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) +{ + if (((cmd >> 8) & 0xff) == 'M') + { + if (cmd & IOC_IN) + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + return gus_default_mixer_ioctl (dev, cmd, arg); + break; + + case SOUND_MIXER_MIC: + return IOCTL_OUT (arg, set_volumes (DEV_MIC, IOCTL_IN (arg))); + break; + + case SOUND_MIXER_CD: + return IOCTL_OUT (arg, set_volumes (DEV_CD, IOCTL_IN (arg))); + break; + + case SOUND_MIXER_LINE: + return IOCTL_OUT (arg, set_volumes (DEV_LINE, IOCTL_IN (arg))); + break; + + case SOUND_MIXER_SYNTH: + return IOCTL_OUT (arg, set_volumes (DEV_GF1, IOCTL_IN (arg))); + break; + + case SOUND_MIXER_VOLUME: + return IOCTL_OUT (arg, set_volumes (DEV_VOL, IOCTL_IN (arg))); + break; + + default: + return RET_ERROR (EINVAL); + } + else + switch (cmd & 0xff) /* + * Return parameters + */ + { + + case SOUND_MIXER_RECSRC: + return gus_default_mixer_ioctl (dev, cmd, arg); + break; + + case SOUND_MIXER_DEVMASK: + return IOCTL_OUT (arg, MIX_DEVS); + break; + + case SOUND_MIXER_STEREODEVS: + return IOCTL_OUT (arg, SOUND_MASK_LINE | SOUND_MASK_CD | + SOUND_MASK_SYNTH | SOUND_MASK_VOLUME| + SOUND_MASK_MIC); + break; + + case SOUND_MIXER_RECMASK: + return IOCTL_OUT (arg, SOUND_MASK_MIC | SOUND_MASK_LINE); + break; + + case SOUND_MIXER_CAPS: + return IOCTL_OUT (arg, 0); + break; + + case SOUND_MIXER_MIC: + return IOCTL_OUT (arg, volumes[DEV_MIC]); + break; + + case SOUND_MIXER_LINE: + return IOCTL_OUT (arg, volumes[DEV_LINE]); + break; + + case SOUND_MIXER_CD: + return IOCTL_OUT (arg, volumes[DEV_CD]); + break; + + case SOUND_MIXER_VOLUME: + return IOCTL_OUT (arg, volumes[DEV_VOL]); + break; + + case SOUND_MIXER_SYNTH: + return IOCTL_OUT (arg, volumes[DEV_GF1]); + break; + + default: + return RET_ERROR (EINVAL); + } + } + + return RET_ERROR (EINVAL); +} + +static struct mixer_operations ics2101_mixer_operations = +{ + ics2101_mixer_ioctl +}; + +long +ics2101_mixer_init (long mem_start) +{ + int i; + + if (num_mixers < MAX_MIXER_DEV) + { + mixer_devs[num_mixers++] = &ics2101_mixer_operations; + + /* + * Some GUS v3.7 cards had some channels flipped. Disable + * the flipping feature if the model id is other than 5. + */ + + if (INB (u_MixSelect) != 5) + { + for (i = 0; i < ICS_MIXDEVS; i++) + left_fix[i] = 1; + for (i = 0; i < ICS_MIXDEVS; i++) + right_fix[i] = 2; + } + + set_volumes (DEV_GF1, 0x5a5a); + set_volumes (DEV_CD, 0x5a5a); + set_volumes (DEV_MIC, 0x0000); + set_volumes (DEV_LINE, 0x5a5a); + set_volumes (DEV_VOL, 0x5a5a); + set_volumes (DEV_UNUSED, 0x0000); + } + + return mem_start; +} + +#endif diff --git a/sys/i386/isa/sound/local.h b/sys/i386/isa/sound/local.h new file mode 100644 index 0000000..a5e7e54 --- /dev/null +++ b/sys/i386/isa/sound/local.h @@ -0,0 +1,18 @@ +/* for FreeBSD */ +/* + * $Id$ + */ +#include "snd.h" + +#if NSND > 0 +#define KERNEL_SOUNDCARD +#endif + +#define DSP_BUFFSIZE 65536 +#define NO_AUTODMA /* still */ +#define SELECTED_SOUND_OPTIONS 0xffffffff +#define SOUND_VERSION_STRING "2.5" +#define SOUND_CONFIG_DATE "Sat Apr 23 07:45:17 MSD 1994" +#define SOUND_CONFIG_BY "ache" +#define SOUND_CONFIG_HOST "dream.demos.su" +#define SOUND_CONFIG_DOMAIN "" diff --git a/sys/i386/isa/sound/midi.c b/sys/i386/isa/sound/midi.c new file mode 100644 index 0000000..a11a4df --- /dev/null +++ b/sys/i386/isa/sound/midi.c @@ -0,0 +1,205 @@ +/* + * Copyright by UWM - comments to soft-eng@cs.uwm.edu + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ +#define _MIDI_TABLE_C_ +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#ifndef EXCLUDE_CHIP_MIDI + + +static int generic_midi_busy[MAX_MIDI_DEV]; + +long +CMIDI_init (long mem_start) +{ + + int i; + int n = num_midi_drivers; + + /* + * int n = sizeof (midi_supported) / sizeof( struct generic_midi_info ); + */ + for (i = 0; i < n; i++) + { + if (midi_supported[i].attach (mem_start)) + { + printk ("MIDI: Successfully attached %s\n", midi_supported[i].name); + } + + } + return (mem_start); +} + + +int +CMIDI_open (int dev, struct fileinfo *file) +{ + + int mode, err, retval; + + dev = dev >> 4; + + mode = file->mode & O_ACCMODE; + + + if (generic_midi_busy[dev]) + return (RET_ERROR (EBUSY)); + + + if (dev >= num_generic_midis) + { + printk (" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + if (!generic_midi_devs[dev]) + { + printk (" MIDI device %d not initialized\n", dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + retval = generic_midi_devs[dev]->open (dev, mode); + + /* If everything ok, set device as busy */ + + if (retval >= 0) + generic_midi_busy[dev] = 1; + + return (retval); + +} + +int +CMIDI_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + + int retval; + + dev = dev >> 4; + + if (dev >= num_generic_midis) + { + printk (" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + /* + * Make double sure of healthiness -- doubt Need we check this again?? + * + */ + + if (!generic_midi_devs[dev]) + { + printk (" MIDI device %d not initialized\n", dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + retval = generic_midi_devs[dev]->write (dev, buf); + + return (retval); + +} + +int +CMIDI_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int retval; + + dev = dev >> 4; + + if (dev >= num_generic_midis) + { + printk (" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + /* + * Make double sure of healthiness -- doubt Need we check this again?? + * + */ + + if (!generic_midi_devs[dev]) + { + printk (" MIDI device %d not initialized\n", dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + retval = generic_midi_devs[dev]->read (dev, buf); + + return (retval); + +} + +int +CMIDI_close (int dev, struct fileinfo *file) +{ + + int retval; + + dev = dev >> 4; + + if (dev >= num_generic_midis) + { + printk (" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + /* + * Make double sure of healthiness -- doubt Need we check this again?? + * + */ + + if (!generic_midi_devs[dev]) + { + printk (" MIDI device %d not initialized\n", dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + generic_midi_devs[dev]->close (dev); + + generic_midi_busy[dev] = 0; /* Free the device */ + + return (0); + +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/midibuf.c b/sys/i386/isa/sound/midibuf.c new file mode 100644 index 0000000..399fdfd --- /dev/null +++ b/sys/i386/isa/sound/midibuf.c @@ -0,0 +1,124 @@ +/* + * sound/midibuf.c + * + * Device file manager for /dev/midi + * + * NOTE! This part of the driver is currently just a stub. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_MPU401) + +#if 0 +#include "midiioctl.h" +#include "midivar.h" +#endif + +static int midibuf_busy = 0; + +int +MIDIbuf_open (int dev, struct fileinfo *file) +{ + int mode, err; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + if (midibuf_busy) + return RET_ERROR (EBUSY); + + if (!mpu401_dev) + { + printk ("Midi: MPU-401 compatible Midi interface not present\n"); + return RET_ERROR (ENXIO); + } + + if ((err = midi_devs[mpu401_dev]->open (mpu401_dev, mode, NULL, NULL)) < 0) + return err; + + midibuf_busy = 1; + + return RET_ERROR (ENXIO); +} + +void +MIDIbuf_release (int dev, struct fileinfo *file) +{ + int mode; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + midi_devs[mpu401_dev]->close (mpu401_dev); + midibuf_busy = 0; +} + +int +MIDIbuf_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + + dev = dev >> 4; + + return count; +} + + +int +MIDIbuf_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + dev = dev >> 4; + + return RET_ERROR (EIO); +} + +int +MIDIbuf_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + dev = dev >> 4; + + switch (cmd) + { + + default: + return midi_devs[0]->ioctl (dev, cmd, arg); + } +} + +void +MIDIbuf_bytes_received (int dev, unsigned char *buf, int count) +{ +} + +long +MIDIbuf_init (long mem_start) +{ + return mem_start; +} + +#endif diff --git a/sys/i386/isa/sound/mpu401.c b/sys/i386/isa/sound/mpu401.c new file mode 100644 index 0000000..0b1685d --- /dev/null +++ b/sys/i386/isa/sound/mpu401.c @@ -0,0 +1,283 @@ +/* + * sound/mpu401.c + * + * The low level driver for Roland MPU-401 compatible Midi cards. + * + * This version supports just the DUMB UART mode. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#if !defined(EXCLUDE_MPU401) && !defined(EXCLUDE_MIDI) + +#define DATAPORT (mpu401_base)/* MPU-401 Data I/O Port on IBM */ +#define COMDPORT (mpu401_base+1) /* MPU-401 Command Port on IBM */ +#define STATPORT (mpu401_base+1) /* MPU-401 Status Port on IBM */ + +#define mpu401_status() INB(STATPORT) +#define input_avail() (!(mpu401_status()&INPUT_AVAIL)) +#define output_ready() (!(mpu401_status()&OUTPUT_READY)) +#define mpu401_cmd(cmd) OUTB(cmd, COMDPORT) +#define mpu401_read() INB(DATAPORT) +#define mpu401_write(byte) OUTB(byte, DATAPORT) + +#define OUTPUT_READY 0x40 /* Mask for Data Read Redy Bit */ +#define INPUT_AVAIL 0x80 /* Mask for Data Send Ready Bit */ +#define MPU_ACK 0xFE /* MPU-401 Acknowledge Response */ +#define MPU_RESET 0xFF /* MPU-401 Total Reset Command */ +#define UART_MODE_ON 0x3F /* MPU-401 "Dumb UART Mode" */ + +static int mpu401_opened = 0; +static int mpu401_base = 0x330; +static int mpu401_irq; +static int mpu401_detected = 0; +static int my_dev; + +static int reset_mpu401 (void); +static void (*midi_input_intr) (int dev, unsigned char data); + +void +mpuintr (int unit) +{ + while (input_avail ()) + { + unsigned char c = mpu401_read (); + + if (mpu401_opened & OPEN_READ) + midi_input_intr (my_dev, c); + } +} + +static int +mpu401_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) +) +{ + if (mpu401_opened) + { + printk ("MPU-401: Midi busy\n"); + return RET_ERROR (EBUSY); + } + + mpuintr (0); + + midi_input_intr = input; + mpu401_opened = mode; + + return 0; +} + +static void +mpu401_close (int dev) +{ + mpu401_opened = 0; +} + +static int +mpu401_out (int dev, unsigned char midi_byte) +{ + int timeout; + unsigned long flags; + + /* + * Test for input since pending input seems to block the output. + */ + + DISABLE_INTR (flags); + + if (input_avail ()) + mpuintr (0); + + RESTORE_INTR (flags); + + /* + * Sometimes it takes about 13000 loops before the output becomes ready + * (After reset). Normally it takes just about 10 loops. + */ + + for (timeout = 30000; timeout > 0 && !output_ready (); timeout--); /* Wait */ + + if (!output_ready ()) + { + printk ("MPU-401: Timeout\n"); + return 0; + } + + mpu401_write (midi_byte); + return 1; +} + +static int +mpu401_command (int dev, unsigned char midi_byte) +{ + return 1; +} + +static int +mpu401_start_read (int dev) +{ + return 0; +} + +static int +mpu401_end_read (int dev) +{ + return 0; +} + +static int +mpu401_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +mpu401_kick (int dev) +{ +} + +static int +mpu401_buffer_status (int dev) +{ + return 0; /* No data in buffers */ +} + +static struct midi_operations mpu401_operations = +{ + {"MPU-401", 0, 0, SNDCARD_MPU401}, + mpu401_open, + mpu401_close, + mpu401_ioctl, + mpu401_out, + mpu401_start_read, + mpu401_end_read, + mpu401_kick, + mpu401_command, + mpu401_buffer_status +}; + + +long +attach_mpu401 (long mem_start, struct address_info *hw_config) +{ + int ok, timeout; + unsigned long flags; + + mpu401_base = hw_config->io_base; + mpu401_irq = hw_config->irq; + + if (!mpu401_detected) + return RET_ERROR (EIO); + + DISABLE_INTR (flags); + for (timeout = 30000; timeout < 0 && !output_ready (); timeout--); /* Wait */ + mpu401_cmd (UART_MODE_ON); + + ok = 0; + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (input_avail ()) + if (mpu401_read () == MPU_ACK) + ok = 1; + + RESTORE_INTR (flags); + +#ifdef __FreeBSD__ + printk ("snd5: <Roland MPU-401>"); +#else + printk (" <Roland MPU-401>"); +#endif + + my_dev = num_midis; + mpu401_dev = num_midis; + midi_devs[num_midis++] = &mpu401_operations; + return mem_start; +} + +static int +reset_mpu401 (void) +{ + unsigned long flags; + int ok, timeout, n; + + /* + * Send the RESET command. Try again if no success at the first time. + */ + + ok = 0; + + DISABLE_INTR (flags); + + for (n = 0; n < 2 && !ok; n++) + { + for (timeout = 30000; timeout < 0 && !output_ready (); timeout--); /* Wait */ + mpu401_cmd (MPU_RESET); /* Send MPU-401 RESET Command */ + + /* + * Wait at least 25 msec. This method is not accurate so let's make the + * loop bit longer. Cannot sleep since this is called during boot. + */ + + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (input_avail ()) + if (mpu401_read () == MPU_ACK) + ok = 1; + + } + + mpu401_opened = 0; + if (ok) + mpuintr (0); /* Flush input before enabling interrupts */ + + RESTORE_INTR (flags); + + return ok; +} + + +int +probe_mpu401 (struct address_info *hw_config) +{ + int ok = 0; + + mpu401_base = hw_config->io_base; + mpu401_irq = hw_config->irq; + + if (snd_set_irq_handler (mpu401_irq, mpuintr) < 0) + return 0; + + ok = reset_mpu401 (); + + mpu401_detected = ok; + return ok; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/opl3.c b/sys/i386/isa/sound/opl3.c new file mode 100644 index 0000000..4278d19 --- /dev/null +++ b/sys/i386/isa/sound/opl3.c @@ -0,0 +1,961 @@ +/* + * sound/opl3.c + * + * A low level driver for Yamaha YM3812 and OPL-3 -chips + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +/* Major improvements to the FM handling 30AUG92 by Rob Hooft, */ +/* hooft@chem.ruu.nl */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_YM3812) + +#include "opl3.h" + +#define MAX_VOICE 18 +#define OFFS_4OP 11 /* Definitions for the operators OP3 and OP4 + * begin here */ + +static int opl3_enabled = 0; +static int left_address = 0x388, right_address = 0x388, both_address = 0; + +static int nr_voices = 9; +static int logical_voices[MAX_VOICE] = +{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; + +struct voice_info + { + unsigned char keyon_byte; + long bender; + long bender_range; + unsigned long orig_freq; + unsigned long current_freq; + int mode; + }; + +static struct voice_info voices[MAX_VOICE]; + +static struct sbi_instrument *instrmap; +static struct sbi_instrument *active_instrument[MAX_VOICE] = +{NULL}; + +static struct synth_info fm_info = +{"AdLib", 0, SYNTH_TYPE_FM, FM_TYPE_ADLIB, 0, 9, 0, SBFM_MAXINSTR, 0}; + +static int already_initialized = 0; + +static int opl3_ok = 0; +static int opl3_busy = 0; +static int fm_model = 0; /* 0=no fm, 1=mono, 2=SB Pro 1, 3=SB Pro 2 */ + +static int store_instr (int instr_no, struct sbi_instrument *instr); +static void freq_to_fnum (int freq, int *block, int *fnum); +static void opl3_command (int io_addr, unsigned int addr, unsigned int val); +static int opl3_kill_note (int dev, int voice, int velocity); +static unsigned char connection_mask = 0x00; + +void +enable_opl3_mode (int left, int right, int both) +{ + if (opl3_enabled) + return; + + opl3_enabled = 1; + left_address = left; + right_address = right; + both_address = both; + fm_info.capabilities = SYNTH_CAP_OPL3; + fm_info.synth_subtype = FM_TYPE_OPL3; +} + +static void +enter_4op_mode (void) +{ + int i; + static int voices_4op[MAX_VOICE] = + {0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17}; + + connection_mask = 0x3f; + opl3_command (right_address, CONNECTION_SELECT_REGISTER, 0x3f); /* Select all 4-OP + * voices */ + for (i = 0; i < 3; i++) + physical_voices[i].voice_mode = 4; + for (i = 3; i < 6; i++) + physical_voices[i].voice_mode = 0; + + for (i = 9; i < 12; i++) + physical_voices[i].voice_mode = 4; + for (i = 12; i < 15; i++) + physical_voices[i].voice_mode = 0; + + for (i = 0; i < 12; i++) + logical_voices[i] = voices_4op[i]; + nr_voices = 12; +} + +static int +opl3_ioctl (int dev, + unsigned int cmd, unsigned int arg) +{ + switch (cmd) + { + + case SNDCTL_FM_LOAD_INSTR: + { + struct sbi_instrument ins; + + IOCTL_FROM_USER ((char *) &ins, (char *) arg, 0, sizeof (ins)); + + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) + { + printk ("FM Error: Invalid instrument number %d\n", ins.channel); + return RET_ERROR (EINVAL); + } + + pmgr_inform (dev, PM_E_PATCH_LOADED, ins.channel, 0, 0, 0); + return store_instr (ins.channel, &ins); + } + break; + + case SNDCTL_SYNTH_INFO: + fm_info.nr_voices = (nr_voices == 12) ? 6 : nr_voices; + + IOCTL_TO_USER ((char *) arg, 0, &fm_info, sizeof (fm_info)); + return 0; + break; + + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + break; + + case SNDCTL_FM_4OP_ENABLE: + if (opl3_enabled) + enter_4op_mode (); + return 0; + break; + + default: + return RET_ERROR (EINVAL); + } + +} + +int +opl3_detect (int ioaddr) +{ + /* + * This function returns 1 if the FM chicp is present at the given I/O port + * The detection algorithm plays with the timer built in the FM chip and + * looks for a change in the status register. + * + * Note! The timers of the FM chip are not connected to AdLib (and compatible) + * boards. + * + * Note2! The chip is initialized if detected. + */ + + unsigned char stat1, stat2; + int i; + + if (already_initialized) + { + return 0; /* Do avoid duplicate initializations */ + } + + if (opl3_enabled) + ioaddr = left_address; + + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); /* Reset timers 1 and 2 */ + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, IRQ_RESET); /* Reset the IRQ of FM + * chicp */ + + stat1 = INB (ioaddr); /* Read status register */ + + if ((stat1 & 0xE0) != 0x00) + { + return 0; /* Should be 0x00 */ + } + + opl3_command (ioaddr, TIMER1_REGISTER, 0xff); /* Set timer 1 to 0xff */ + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, + TIMER2_MASK | TIMER1_START); /* Unmask and start timer 1 */ + + /* + * Now we have to delay at least 80 msec + */ + + for (i = 0; i < 50; i++) + tenmicrosec (); /* To be sure */ + + stat2 = INB (ioaddr); /* Read status after timers have expired */ + + /* Stop the timers */ + + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); /* Reset timers 1 and 2 */ + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, IRQ_RESET); /* Reset the IRQ of FM + * chicp */ + + if ((stat2 & 0xE0) != 0xc0) + { + return 0; /* There is no YM3812 */ + } + + /* There is a FM chicp in this address. Now set some default values. */ + + for (i = 0; i < 9; i++) + opl3_command (ioaddr, KEYON_BLOCK + i, 0); /* Note off */ + + opl3_command (ioaddr, TEST_REGISTER, ENABLE_WAVE_SELECT); + opl3_command (ioaddr, PERCUSSION_REGISTER, 0x00); /* Melodic mode. */ + + return 1; +} + +static int +opl3_kill_note (int dev, int voice, int velocity) +{ + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return 0; + + map = &physical_voices[logical_voices[voice]]; + + DEB (printk ("Kill note %d\n", voice)); + + if (map->voice_mode == 0) + return 0; + + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, voices[voice].keyon_byte & ~0x20); + + voices[voice].keyon_byte = 0; + voices[voice].bender = 0; + voices[voice].bender_range = 200; /* 200 cents = 2 semitones */ + voices[voice].orig_freq = 0; + voices[voice].current_freq = 0; + voices[voice].mode = 0; + + return 0; +} + +#define HIHAT 0 +#define CYMBAL 1 +#define TOMTOM 2 +#define SNARE 3 +#define BDRUM 4 +#define UNDEFINED TOMTOM +#define DEFAULT TOMTOM + +static int +store_instr (int instr_no, struct sbi_instrument *instr) +{ + + if (instr->key != FM_PATCH && (instr->key != OPL3_PATCH || !opl3_enabled)) + printk ("FM warning: Invalid patch format field (key) 0x%x\n", instr->key); + memcpy ((char *) &(instrmap[instr_no]), (char *) instr, sizeof (*instr)); + + return 0; +} + +static int +opl3_set_instr (int dev, int voice, int instr_no) +{ + if (voice < 0 || voice >= nr_voices) + return 0; + + if (instr_no < 0 || instr_no >= SBFM_MAXINSTR) + return 0; + + active_instrument[voice] = &instrmap[instr_no]; + return 0; +} + +/* + * The next table looks magical, but it certainly is not. Its values have + * been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception + * for i=0. This log-table converts a linear volume-scaling (0..127) to a + * logarithmic scaling as present in the FM-synthesizer chips. so : Volume + * 64 = 0 db = relative volume 0 and: Volume 32 = -6 db = relative + * volume -8 it was implemented as a table because it is only 128 bytes and + * it saves a lot of log() calculations. (RH) + */ +char fm_volume_table[128] = +{-64, -48, -40, -35, -32, -29, -27, -26, /* 0 - 7 */ + -24, -23, -21, -20, -19, -18, -18, -17, /* 8 - 15 */ + -16, -15, -15, -14, -13, -13, -12, -12, /* 16 - 23 */ + -11, -11, -10, -10, -10, -9, -9, -8, /* 24 - 31 */ + -8, -8, -7, -7, -7, -6, -6, -6,/* 32 - 39 */ + -5, -5, -5, -5, -4, -4, -4, -4,/* 40 - 47 */ + -3, -3, -3, -3, -2, -2, -2, -2,/* 48 - 55 */ + -2, -1, -1, -1, -1, 0, 0, 0, /* 56 - 63 */ + 0, 0, 0, 1, 1, 1, 1, 1, /* 64 - 71 */ + 1, 2, 2, 2, 2, 2, 2, 2, /* 72 - 79 */ + 3, 3, 3, 3, 3, 3, 3, 4, /* 80 - 87 */ + 4, 4, 4, 4, 4, 4, 4, 5, /* 88 - 95 */ + 5, 5, 5, 5, 5, 5, 5, 5, /* 96 - 103 */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 104 - 111 */ + 6, 7, 7, 7, 7, 7, 7, 7, /* 112 - 119 */ + 7, 7, 7, 8, 8, 8, 8, 8}; /* 120 - 127 */ + +static void +calc_vol (unsigned char *regbyte, int volume) +{ + int level = (~*regbyte & 0x3f); + + if (level) + level += fm_volume_table[volume]; + + if (level > 0x3f) + level = 0x3f; + if (level < 0) + level = 0; + + *regbyte = (*regbyte & 0xc0) | (~level & 0x3f); +} + +static void +set_voice_volume (int voice, int volume) +{ + unsigned char vol1, vol2, vol3, vol4; + struct sbi_instrument *instr; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return; + + map = &physical_voices[logical_voices[voice]]; + + instr = active_instrument[voice]; + + if (!instr) + instr = &instrmap[0]; + + if (instr->channel < 0) + return; + + if (voices[voice].mode == 0) + return; + + if (voices[voice].mode == 2) + { /* 2 OP voice */ + + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + + if ((instr->operators[10] & 0x01)) + { /* Additive synthesis */ + calc_vol (&vol1, volume); + calc_vol (&vol2, volume); + } + else + { /* FM synthesis */ + calc_vol (&vol2, volume); + } + + opl3_command (map->ioaddr, KSL_LEVEL + map->op[0], vol1); /* Modulator volume */ + opl3_command (map->ioaddr, KSL_LEVEL + map->op[1], vol2); /* Carrier volume */ + } + else + { /* 4 OP voice */ + int connection; + + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + vol3 = instr->operators[OFFS_4OP + 2]; + vol4 = instr->operators[OFFS_4OP + 3]; + + /* + * The connection method for 4 OP voices is defined by the rightmost + * bits at the offsets 10 and 10+OFFS_4OP + */ + + connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + + switch (connection) + { + case 0: + calc_vol (&vol4, volume); /* Just the OP 4 is carrier */ + break; + + case 1: + calc_vol (&vol2, volume); + calc_vol (&vol4, volume); + break; + + case 2: + calc_vol (&vol1, volume); + calc_vol (&vol4, volume); + break; + + case 3: + calc_vol (&vol1, volume); + calc_vol (&vol3, volume); + calc_vol (&vol4, volume); + break; + + default:/* Why ?? */ ; + } + + opl3_command (map->ioaddr, KSL_LEVEL + map->op[0], vol1); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[1], vol2); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[2], vol3); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[3], vol4); + } +} + +static int +opl3_start_note (int dev, int voice, int note, int volume) +{ + unsigned char data, fpc; + int block, fnum, freq, voice_mode; + struct sbi_instrument *instr; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return 0; + + map = &physical_voices[logical_voices[voice]]; + + if (map->voice_mode == 0) + return 0; + + if (note == 255) /* Just change the volume */ + { + set_voice_volume (voice, volume); + return 0; + } + + /* Kill previous note before playing */ + opl3_command (map->ioaddr, KSL_LEVEL + map->op[1], 0xff); /* Carrier volume to min */ + opl3_command (map->ioaddr, KSL_LEVEL + map->op[0], 0xff); /* Modulator volume to */ + + if (map->voice_mode == 4) + { + opl3_command (map->ioaddr, KSL_LEVEL + map->op[2], 0xff); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[3], 0xff); + } + + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, 0x00); /* Note off */ + + instr = active_instrument[voice]; + + if (!instr) + instr = &instrmap[0]; + + if (instr->channel < 0) + { + printk ( + "OPL3: Initializing voice %d with undefined instrument\n", + voice); + return 0; + } + + if (map->voice_mode == 2 && instr->key == OPL3_PATCH) + return 0; /* Cannot play */ + + voice_mode = map->voice_mode; + + if (voice_mode == 4) + { + int voice_shift; + + voice_shift = (map->ioaddr == left_address) ? 0 : 3; + voice_shift += map->voice_num; + + if (instr->key != OPL3_PATCH) /* Just 2 OP patch */ + { + voice_mode = 2; + connection_mask &= ~(1 << voice_shift); + } + else + { + connection_mask |= (1 << voice_shift); + } + + opl3_command (right_address, CONNECTION_SELECT_REGISTER, connection_mask); + } + + /* Set Sound Characteristics */ + opl3_command (map->ioaddr, AM_VIB + map->op[0], instr->operators[0]); + opl3_command (map->ioaddr, AM_VIB + map->op[1], instr->operators[1]); + + /* Set Attack/Decay */ + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[0], instr->operators[4]); + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[1], instr->operators[5]); + + /* Set Sustain/Release */ + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[0], instr->operators[6]); + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[1], instr->operators[7]); + + /* Set Wave Select */ + opl3_command (map->ioaddr, WAVE_SELECT + map->op[0], instr->operators[8]); + opl3_command (map->ioaddr, WAVE_SELECT + map->op[1], instr->operators[9]); + + /* Set Feedback/Connection */ + fpc = instr->operators[10]; + if (!(fpc & 0x30)) + fpc |= 0x30; /* Ensure that at least one chn is enabled */ + opl3_command (map->ioaddr, FEEDBACK_CONNECTION + map->voice_num, + fpc); + + /* + * If the voice is a 4 OP one, initialize the operators 3 and 4 also + */ + + if (voice_mode == 4) + { + + /* Set Sound Characteristics */ + opl3_command (map->ioaddr, AM_VIB + map->op[2], instr->operators[OFFS_4OP + 0]); + opl3_command (map->ioaddr, AM_VIB + map->op[3], instr->operators[OFFS_4OP + 1]); + + /* Set Attack/Decay */ + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[2], instr->operators[OFFS_4OP + 4]); + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[3], instr->operators[OFFS_4OP + 5]); + + /* Set Sustain/Release */ + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[2], instr->operators[OFFS_4OP + 6]); + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[3], instr->operators[OFFS_4OP + 7]); + + /* Set Wave Select */ + opl3_command (map->ioaddr, WAVE_SELECT + map->op[2], instr->operators[OFFS_4OP + 8]); + opl3_command (map->ioaddr, WAVE_SELECT + map->op[3], instr->operators[OFFS_4OP + 9]); + + /* Set Feedback/Connection */ + fpc = instr->operators[OFFS_4OP + 10]; + if (!(fpc & 0x30)) + fpc |= 0x30; /* Ensure that at least one chn is enabled */ + opl3_command (map->ioaddr, FEEDBACK_CONNECTION + map->voice_num + 3, fpc); + } + + voices[voice].mode = voice_mode; + + set_voice_volume (voice, volume); + + freq = voices[voice].orig_freq = note_to_freq (note) / 1000; + + /* + * Since the pitch bender may have been set before playing the note, we + * have to calculate the bending now. + */ + + freq = compute_finetune (voices[voice].orig_freq, voices[voice].bender, voices[voice].bender_range); + voices[voice].current_freq = freq; + + freq_to_fnum (freq, &block, &fnum); + + /* Play note */ + + data = fnum & 0xff; /* Least significant bits of fnumber */ + opl3_command (map->ioaddr, FNUM_LOW + map->voice_num, data); + + data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3); + voices[voice].keyon_byte = data; + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, data); + if (voice_mode == 4) + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num + 3, data); + + return 0; +} + +static void +freq_to_fnum (int freq, int *block, int *fnum) +{ + int f, octave; + + /* Converts the note frequency to block and fnum values for the FM chip */ + /* First try to compute the block -value (octave) where the note belongs */ + + f = freq; + + octave = 5; + + if (f == 0) + octave = 0; + else if (f < 261) + { + while (f < 261) + { + octave--; + f <<= 1; + } + } + else if (f > 493) + { + while (f > 493) + { + octave++; + f >>= 1; + } + } + + if (octave > 7) + octave = 7; + + *fnum = freq * (1 << (20 - octave)) / 49716; + *block = octave; +} + +static void +opl3_command (int io_addr, unsigned int addr, unsigned int val) +{ + int i; + + /* + * The original 2-OP synth requires a quite long delay after writing to a + * register. The OPL-3 survives with just two INBs + */ + + OUTB ((unsigned char) (addr & 0xff), io_addr); /* Select register */ + + if (!opl3_enabled) + tenmicrosec (); + else + for (i = 0; i < 2; i++) + INB (io_addr); + + OUTB ((unsigned char) (val & 0xff), io_addr + 1); /* Write to register */ + + if (!opl3_enabled) + { + tenmicrosec (); + tenmicrosec (); + tenmicrosec (); + } + else + for (i = 0; i < 2; i++) + INB (io_addr); +} + +static void +opl3_reset (int dev) +{ + int i; + + for (i = 0; i < nr_voices; i++) + { + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[0], 0xff); /* OP1 volume to min */ + + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[1], 0xff); /* OP2 volume to min */ + + if (physical_voices[logical_voices[i]].voice_mode == 4) /* 4 OP voice */ + { + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[2], 0xff); /* OP3 volume to min */ + + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[3], 0xff); /* OP4 volume to min */ + } + + opl3_kill_note (dev, i, 64); + } + + if (opl3_enabled) + { + nr_voices = 18; + + for (i = 0; i < 18; i++) + logical_voices[i] = i; + + for (i = 0; i < 18; i++) + physical_voices[i].voice_mode = 2; + + } + +} + +static int +opl3_open (int dev, int mode) +{ + if (!opl3_ok) + return RET_ERROR (ENXIO); + if (opl3_busy) + return RET_ERROR (EBUSY); + opl3_busy = 1; + + connection_mask = 0x00; /* Just 2 OP voices */ + if (opl3_enabled) + opl3_command (right_address, CONNECTION_SELECT_REGISTER, connection_mask); + return 0; +} + +static void +opl3_close (int dev) +{ + opl3_busy = 0; + nr_voices = opl3_enabled ? 18 : 9; + fm_info.nr_drums = 0; + fm_info.perc_mode = 0; + + opl3_reset (dev); +} + +static void +opl3_hw_control (int dev, unsigned char *event) +{ +} + +static int +opl3_load_patch (int dev, int format, snd_rw_buf * addr, + int offs, int count, int pmgr_flag) +{ + struct sbi_instrument ins; + + if (count < sizeof (ins)) + { + printk ("FM Error: Patch record too short\n"); + return RET_ERROR (EINVAL); + } + + COPY_FROM_USER (&((char *) &ins)[offs], (char *) addr, offs, sizeof (ins) - offs); + + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) + { + printk ("FM Error: Invalid instrument number %d\n", ins.channel); + return RET_ERROR (EINVAL); + } + ins.key = format; + + return store_instr (ins.channel, &ins); +} + +static void +opl3_panning (int dev, int voice, int pressure) +{ +} + +static void +opl3_volume_method (int dev, int mode) +{ +} + +#define SET_VIBRATO(cell) { \ + tmp = instr->operators[(cell-1)+(((cell-1)/2)*OFFS_4OP)]; \ + if (pressure > 110) \ + tmp |= 0x40; /* Vibrato on */ \ + opl3_command (map->ioaddr, AM_VIB + map->op[cell-1], tmp);} + +static void +opl3_aftertouch (int dev, int voice, int pressure) +{ + int tmp; + struct sbi_instrument *instr; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return; + + map = &physical_voices[logical_voices[voice]]; + + DEB (printk ("Aftertouch %d\n", voice)); + + if (map->voice_mode == 0) + return; + + /* + * Adjust the amount of vibrato depending the pressure + */ + + instr = active_instrument[voice]; + + if (!instr) + instr = &instrmap[0]; + + if (voices[voice].mode == 4) + { + int connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + + switch (connection) + { + case 0: + SET_VIBRATO (4); + break; + + case 1: + SET_VIBRATO (2); + SET_VIBRATO (4); + break; + + case 2: + SET_VIBRATO (1); + SET_VIBRATO (4); + break; + + case 3: + SET_VIBRATO (1); + SET_VIBRATO (3); + SET_VIBRATO (4); + break; + + } + /* Not implemented yet */ + } + else + { + SET_VIBRATO (1); + + if ((instr->operators[10] & 0x01)) /* Additive synthesis */ + SET_VIBRATO (2); + } +} + +#undef SET_VIBRATO + +static void +opl3_controller (int dev, int voice, int ctrl_num, int value) +{ + unsigned char data; + int block, fnum, freq; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return; + + map = &physical_voices[logical_voices[voice]]; + + if (map->voice_mode == 0) + return; + + switch (ctrl_num) + { + case CTRL_PITCH_BENDER: + voices[voice].bender = value; + if (!value) + return; + if (!(voices[voice].keyon_byte & 0x20)) + return; /* Not keyed on */ + + freq = compute_finetune (voices[voice].orig_freq, voices[voice].bender, voices[voice].bender_range); + voices[voice].current_freq = freq; + + freq_to_fnum (freq, &block, &fnum); + + data = fnum & 0xff; /* Least significant bits of fnumber */ + opl3_command (map->ioaddr, FNUM_LOW + map->voice_num, data); + + data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3); /* KEYON|OCTAVE|MS bits + * of f-num */ + voices[voice].keyon_byte = data; + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, data); + break; + + case CTRL_PITCH_BENDER_RANGE: + voices[voice].bender_range = value; + break; + } +} + +static int +opl3_patchmgr (int dev, struct patmgr_info *rec) +{ + return RET_ERROR (EINVAL); +} + +static struct synth_operations opl3_operations = +{ + &fm_info, + SYNTH_TYPE_FM, + FM_TYPE_ADLIB, + opl3_open, + opl3_close, + opl3_ioctl, + opl3_kill_note, + opl3_start_note, + opl3_set_instr, + opl3_reset, + opl3_hw_control, + opl3_load_patch, + opl3_aftertouch, + opl3_controller, + opl3_panning, + opl3_volume_method, + opl3_patchmgr +}; + +long +opl3_init (long mem_start) +{ + int i; + + PERMANENT_MALLOC (struct sbi_instrument *, instrmap, + SBFM_MAXINSTR * sizeof (*instrmap), mem_start); + + synth_devs[num_synths++] = &opl3_operations; + fm_model = 0; + opl3_ok = 1; + if (opl3_enabled) + { +#ifdef __FreeBSD__ + printk ("snd1: <Yamaha OPL-3 FM>"); +#else + printk (" <Yamaha OPL-3 FM>"); +#endif + fm_model = 2; + nr_voices = 18; + fm_info.nr_drums = 0; + fm_info.capabilities |= SYNTH_CAP_OPL3; +#ifndef SCO + strcpy (fm_info.name, "Yamaha OPL-3"); +#endif + + for (i = 0; i < 18; i++) + if (physical_voices[i].ioaddr == USE_LEFT) + physical_voices[i].ioaddr = left_address; + else + physical_voices[i].ioaddr = right_address; + + + opl3_command (right_address, OPL3_MODE_REGISTER, OPL3_ENABLE); /* Enable OPL-3 mode */ + opl3_command (right_address, CONNECTION_SELECT_REGISTER, 0x00); /* Select all 2-OP + * voices */ + } + else + { +#ifdef __FreeBSD__ + printk ("snd1: <Yamaha 2-OP FM>"); +#else + printk (" <Yamaha 2-OP FM>"); +#endif + fm_model = 1; + nr_voices = 9; + fm_info.nr_drums = 0; + + for (i = 0; i < 18; i++) + physical_voices[i].ioaddr = left_address; + }; + + already_initialized = 1; + for (i = 0; i < SBFM_MAXINSTR; i++) + instrmap[i].channel = -1; + + return mem_start; +} + +#endif diff --git a/sys/i386/isa/sound/opl3.h b/sys/i386/isa/sound/opl3.h new file mode 100644 index 0000000..eeb8fef --- /dev/null +++ b/sys/i386/isa/sound/opl3.h @@ -0,0 +1,261 @@ +/* + * opl3.h - Definitions of the OPL-3 registers + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +/* + * The OPL-3 mode is switched on by writing 0x01, to the offset 5 + * of the right side. + * + * Another special register at the right side is at offset 4. It contains + * a bit mask defining which voices are used as 4 OP voices. + * + * The percussive mode is implemented in the left side only. + * + * With the above exeptions the both sides can be operated independently. + * + * A 4 OP voice can be created by setting the corresponding + * bit at offset 4 of the right side. + * + * For example setting the rightmost bit (0x01) changes the + * first voice on the right side to the 4 OP mode. The fourth + * voice is made inaccessible. + * + * If a voice is set to the 2 OP mode, it works like 2 OP modes + * of the original YM3812 (AdLib). In addition the voice can + * be connected the left, right or both stereo channels. It can + * even be left unconnected. This works with 4 OP voices also. + * + * The stereo connection bits are located in the FEEDBACK_CONNECTION + * register of the voice (0xC0-0xC8). In 4 OP voices these bits are + * in the second half of the voice. + */ + +/* + * Register numbers for the global registers + */ + +#define TEST_REGISTER 0x01 +#define ENABLE_WAVE_SELECT 0x20 + +#define TIMER1_REGISTER 0x02 +#define TIMER2_REGISTER 0x03 +#define TIMER_CONTROL_REGISTER 0x04 /* Left side */ +#define IRQ_RESET 0x80 +#define TIMER1_MASK 0x40 +#define TIMER2_MASK 0x20 +#define TIMER1_START 0x01 +#define TIMER2_START 0x02 + +#define CONNECTION_SELECT_REGISTER 0x04 /* Right side */ +#define RIGHT_4OP_0 0x01 +#define RIGHT_4OP_1 0x02 +#define RIGHT_4OP_2 0x04 +#define LEFT_4OP_0 0x08 +#define LEFT_4OP_1 0x10 +#define LEFT_4OP_2 0x20 + +#define OPL3_MODE_REGISTER 0x05 /* Right side */ +#define OPL3_ENABLE 0x01 + +#define KBD_SPLIT_REGISTER 0x08 /* Left side */ +#define COMPOSITE_SINE_WAVE_MODE 0x80 /* Don't use with OPL-3? */ +#define KEYBOARD_SPLIT 0x40 + +#define PERCUSSION_REGISTER 0xbd /* Left side only */ +#define TREMOLO_DEPTH 0x80 +#define VIBRATO_DEPTH 0x40 +#define PERCUSSION_ENABLE 0x20 +#define BASSDRUM_ON 0x10 +#define SNAREDRUM_ON 0x08 +#define TOMTOM_ON 0x04 +#define CYMBAL_ON 0x02 +#define HIHAT_ON 0x01 + +/* + * Offsets to the register banks for operators. To get the + * register number just add the operator offset to the bank offset + * + * AM/VIB/EG/KSR/Multiple (0x20 to 0x35) + */ + #define AM_VIB 0x20 + #define TREMOLO_ON 0x80 + #define VIBRATO_ON 0x40 + #define SUSTAIN_ON 0x20 + #define KSR 0x10 /* Key scaling rate */ + #define MULTIPLE_MASK 0x0f /* Frequency multiplier */ + + /* + * KSL/Total level (0x40 to 0x55) + */ +#define KSL_LEVEL 0x40 +#define KSL_MASK 0xc0 /* Envelope scaling bits */ +#define TOTAL_LEVEL_MASK 0x3f /* Strength (volume) of OP */ + +/* + * Attack / Decay rate (0x60 to 0x75) + */ +#define ATTACK_DECAY 0x60 +#define ATTACK_MASK 0xf0 +#define DECAY_MASK 0x0f + +/* + * Sustain level / Release rate (0x80 to 0x95) + */ +#define SUSTAIN_RELEASE 0x80 +#define SUSTAIN_MASK 0xf0 +#define RELEASE_MASK 0x0f + +/* + * Wave select (0xE0 to 0xF5) + */ +#define WAVE_SELECT 0xe0 + +/* + * Offsets to the register banks for voices. Just add to the + * voice number to get the register number. + * + * F-Number low bits (0xA0 to 0xA8). + */ +#define FNUM_LOW 0xa0 + +/* + * F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) + */ +#define KEYON_BLOCK 0xb0 +#define KEYON_BIT 0x20 +#define BLOCKNUM_MASK 0x1c +#define FNUM_HIGH_MASK 0x03 + +/* + * Feedback / Connection (0xc0 to 0xc8) + * + * These registers have two new bits when the OPL-3 mode + * is selected. These bits controls connecting the voice + * to the stereo channels. For 4 OP voices this bit is + * defined in the second half of the voice (add 3 to the + * register offset). + * + * For 4 OP voices the connection bit is used in the + * both halfs (gives 4 ways to connect the operators). + */ +#define FEEDBACK_CONNECTION 0xc0 +#define FEEDBACK_MASK 0x0e /* Valid just for 1st OP of a voice */ +#define CONNECTION_BIT 0x01 +/* + * In the 4 OP mode there is four possible configurations how the + * operators can be connected together (in 2 OP modes there is just + * AM or FM). The 4 OP connection mode is defined by the rightmost + * bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halfs. + * + * First half Second half Mode + * + * +---+ + * v | + * 0 0 >+-1-+--2--3--4--> + * + * + * + * +---+ + * | | + * 0 1 >+-1-+--2-+ + * |-> + * >--3----4-+ + * + * +---+ + * | | + * 1 0 >+-1-+-----+ + * |-> + * >--2--3--4-+ + * + * +---+ + * | | + * 1 1 >+-1-+--+ + * | + * >--2--3-+-> + * | + * >--4----+ + */ +#define STEREO_BITS 0x30 /* OPL-3 only */ +#define VOICE_TO_LEFT 0x10 +#define VOICE_TO_RIGHT 0x20 + +/* + * Definition table for the physical voices + */ + +struct physical_voice_info { + unsigned char voice_num; + unsigned char voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */ + unsigned short ioaddr; /* I/O port (left or right side) */ + unsigned char op[4]; /* Operator offsets */ + }; + +/* + * There is 18 possible 2 OP voices + * (9 in the left and 9 in the right). + * The first OP is the modulator and 2nd is the carrier. + * + * The first three voices in the both sides may be connected + * with another voice to a 4 OP voice. For example voice 0 + * can be connected with voice 3. The operators of voice 3 are + * used as operators 3 and 4 of the new 4 OP voice. + * In this case the 2 OP voice number 0 is the 'first half' and + * voice 3 is the second. + */ + +#define USE_LEFT 0 +#define USE_RIGHT 1 + +static struct physical_voice_info physical_voices[18] = +{ +/* No Mode Side OP1 OP2 OP3 OP4 */ +/* --------------------------------------------------- */ + { 0, 2, USE_LEFT, {0x00, 0x03, 0x08, 0x0b}}, + { 1, 2, USE_LEFT, {0x01, 0x04, 0x09, 0x0c}}, + { 2, 2, USE_LEFT, {0x02, 0x05, 0x0a, 0x0d}}, + + { 3, 2, USE_LEFT, {0x08, 0x0b, 0x00, 0x00}}, + { 4, 2, USE_LEFT, {0x09, 0x0c, 0x00, 0x00}}, + { 5, 2, USE_LEFT, {0x0a, 0x0d, 0x00, 0x00}}, + + { 6, 2, USE_LEFT, {0x10, 0x13, 0x00, 0x00}}, /* Used by percussive voices */ + { 7, 2, USE_LEFT, {0x11, 0x14, 0x00, 0x00}}, /* if the percussive mode */ + { 8, 2, USE_LEFT, {0x12, 0x15, 0x00, 0x00}}, /* is selected */ + + { 0, 2, USE_RIGHT, {0x00, 0x03, 0x08, 0x0b}}, + { 1, 2, USE_RIGHT, {0x01, 0x04, 0x09, 0x0c}}, + { 2, 2, USE_RIGHT, {0x02, 0x05, 0x0a, 0x0d}}, + + { 3, 2, USE_RIGHT, {0x08, 0x0b, 0x00, 0x00}}, + { 4, 2, USE_RIGHT, {0x09, 0x0c, 0x00, 0x00}}, + { 5, 2, USE_RIGHT, {0x0a, 0x0d, 0x00, 0x00}}, + + { 6, 2, USE_RIGHT, {0x10, 0x13, 0x00, 0x00}}, + { 7, 2, USE_RIGHT, {0x11, 0x14, 0x00, 0x00}}, + { 8, 2, USE_RIGHT, {0x12, 0x15, 0x00, 0x00}} +}; diff --git a/sys/i386/isa/sound/os.h b/sys/i386/isa/sound/os.h new file mode 100644 index 0000000..ec3c47f --- /dev/null +++ b/sys/i386/isa/sound/os.h @@ -0,0 +1,321 @@ +#ifndef _OS_H_ +#define _OS_H_ +/* + * OS specific settings for FreeBSD + * + * Copyright by UWM - comments to soft-eng@cs.uwm.edu + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This chould be used as an example when porting the driver to a new + * operating systems. + * + * What you should do is to rewrite the soundcard.c and os.h (this file). + * You should create a new subdirectory and put these two files there. + * In addition you have to do a makefile.<OS>. + * + * If you have to make changes to other than these two files, please contact me + * before making the changes. It's possible that I have already made the + * change. + * + * $Id$ + */ + +/* + * Insert here the includes required by your kernel. + */ + +#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 "errno.h" +#include "malloc.h" +#include "buf.h" +#include "i386/isa/isa_device.h" + +/* + * Rest of the file is compiled only if the driver is really required. + */ +#ifdef CONFIGURE_SOUNDCARD + +/* + * select() is currently implemented in Linux specific way. Don't enable. + * I don't remember what the SHORT_BANNERS means so forget it. + */ + +#undef ALLOW_SELECT +#define SHORT_BANNERS + +/* The soundcard.h could be in a nonstandard place so inclyde it here. */ +#include <machine/soundcard.h> + +/* + * Here is the first portability problem. Every OS has it's own way to + * pass a pointer to the buffer in read() and write() calls. In Linux it's + * just a char*. In BSD it's struct uio. This parameter is passed to + * all functions called from read() or write(). Since nothing can be + * assumed about this structure, the driver uses set of macros for + * accessing the user buffer. + * + * The driver reads/writes bytes in the user buffer sequentially which + * means that calls like uiomove() can be used. + * + * snd_rw_buf is the type which is passed to the device file specific + * read() and write() calls. + * + * The following macros are used to move date to and from the + * user buffer. These macros should be used only when the + * target or source parameter has snd_rw_buf type. + * The offs parameter is a offset relative to the beginning of + * the user buffer. In Linux the offset is required but for example + * BSD passes the offset info in the uio structure. It could be usefull + * if these macros verify that the offs parameter and the value in + * the snd_rw_buf structure are equal. + */ +typedef struct uio snd_rw_buf; + +/* + * Move bytes from the buffer which the application given in a + * write() call. + * offs is position relative to the beginning of the buffer in + * user space. The count is number of bytes to be moved. + */ +#define COPY_FROM_USER(target, source, offs, count) \ + do { if (uiomove(target, count, (struct uio *)source)) { \ + printf ("sb: Bad copyin()!\n"); \ + } } while(0) +/* Like COPY_FOM_USER but for writes. */ +#define COPY_TO_USER(target, offs, source, count) \ + do { if (uiomove(source, count, (struct uio *)target)) { \ + printf ("sb: Bad copyout()!\n"); \ + } } while(0) +/* + * The following macros are like COPY_*_USER but work just with one byte (8bit), + * short (16 bit) or long (32 bit) at a time. + * The same restrictions apply than for COPY_*_USER + */ +#define GET_BYTE_FROM_USER(target, addr, offs) {uiomove((char*)&(target), 1, (struct uio *)addr);} +#define GET_SHORT_FROM_USER(target, addr, offs) {uiomove((char*)&(target), 2, (struct uio *)addr);} +#define GET_WORD_FROM_USER(target, addr, offs) {uiomove((char*)&(target), 4, (struct uio *)addr);} +#define PUT_WORD_TO_USER(addr, offs, data) {uiomove((char*)&(data), 4, (struct uio *)addr);} + +/* + * The way how the ioctl arguments are passed is another nonportable thing. + * In Linux the argument is just a pointer directly to the user segment. On + * 386bsd the data is already moved to the kernel space. The following + * macros should handle the difference. + */ + +/* + * IOCTL_FROM_USER is used to copy a record pointed by the argument to + * a buffer in the kernel space. On 386bsd it can be done just by calling + * memcpy. With Linux a memcpy_from_fs should be called instead. + * Parameters of the following macros are like in the COPY_*_USER macros. + */ + +/* + * When the ioctl argument points to a record or array (longer than 32 bits), + * the macros IOCTL_*_USER are used. It's assumed that the source and target + * parameters are direct memory addresses. + */ +#define IOCTL_FROM_USER(target, source, offs, count) {memcpy(target, &((source)[offs]), count);} +#define IOCTL_TO_USER(target, offs, source, count) {memcpy(&((target)[offs]), source, count);} +/* The following macros are used if the ioctl argument points to 32 bit int */ +#define IOCTL_IN(arg) (*(int*)arg) +#define IOCTL_OUT(arg, ret) *(int*)arg = ret + +/* + * When the driver displays something to the console, printk() will be called. + * The name can be changed here. + */ +#define printk printf + +/* + * The following macros define an interface to the process management. + */ + +struct snd_wait { + int mode; int aborting; + }; + +/* + * DEFINE_WAIT_QUEUE is used where a wait queue is required. It must define + * a structure which can be passed as a parameter to a sleep(). The second + * parameter is name of a flag variable (must be defined as int). + */ +#define DEFINE_WAIT_QUEUE(qname, flag) static int *qname = NULL; \ + static volatile struct snd_wait flag = {0} +/* Like the above but defines an array of wait queues and flags */ +#define DEFINE_WAIT_QUEUES(qname, flag) static int *qname = {NULL}; \ + static volatile struct snd_wait flag = {{0}} + +#define RESET_WAIT_QUEUE(q, f) {f.aborting = 0;f.mode = WK_NONE;} +#define SET_ABORT_FLAG(q, f) f.aborting = 1 +#define TIMED_OUT(q, f) (f.mode & WK_TIMEOUT) +#define SOMEONE_WAITING(q, f) (f.mode & WK_SLEEP) +/* + * This driver handles interrupts little bit nonstandard way. The following + * macro is used to test if the current process has received a signal which + * is aborts the process. This macro is called from close() to see if the + * buffers should be discarded. If this kind info is not available, a constant + * 1 or 0 could be returned (1 should be better than 0). + * I'm not sure if the following is correct for FreeBSD. + */ +#define PROCESS_ABORTING(q, f) (f.aborting | curproc->p_siglist) + +/* + * The following macro calls sleep. It should be implemented such that + * the process is resumed if it receives a signal. The following is propably + * not the way how it should be done on 386bsd. + * The on_what parameter is a wait_queue defined with DEFINE_WAIT_QUEUE(), + * and the second is a workarea parameter. The third is a timeout + * in ticks. Zero means no timeout. + */ +#define DO_SLEEP(q, f, time_limit) \ + { \ + int flag, chn; \ + f.mode = WK_SLEEP; \ + q = &chn; \ + flag=tsleep((caddr_t)&(chn), (PRIBIO-5)|PCATCH, "sndint", time_limit); \ + if(flag == ERESTART) f.aborting = 1;\ + else f.aborting = 0;\ + f.mode &= ~WK_SLEEP; \ + } +/* An the following wakes up a process */ +#define WAKE_UP(q, f) {f.mode = WK_WAKEUP;wakeup((caddr_t)q);} + +/* + * Timing macros. This driver assumes that there is a timer running in the + * kernel. The timer should return a value which is increased once at every + * timer tick. The macro HZ should return the number of such ticks/sec. + */ + +#ifndef HZ +extern int hz; +#define HZ hz +#endif + +/* + * GET_TIME() returns current value of the counter incremented at timer + * ticks. This can overflow, so the timeout might be real big... + * + */ +extern unsigned long get_time(void); +#define GET_TIME() get_time() +/*#define GET_TIME() (lbolt) */ /* Returns current time (1/HZ secs since boot) */ + +/* + * The following three macros are called before and after atomic + * code sequences. The flags parameter has always type of unsigned long. + * The macro DISABLE_INTR() should ensure that all interrupts which + * may invoke any part of the driver (timer, soundcard interrupts) are + * disabled. + * RESTORE_INTR() should return the interrupt status back to the + * state when DISABLE_INTR() was called. The flags parameter is + * a variable which can carry 32 bits of state information between + * DISABLE_INTR() and RESTORE_INTR() calls. + */ +#define DISABLE_INTR(flags) flags = splhigh() +#define RESTORE_INTR(flags) splx(flags) + +/* + * INB() and OUTB() should be obvious. NOTE! The order of + * paratemeters of OUTB() is different than on some other + * operating systems. + */ + +#define INB inb +/* + * The outb(0, 0x80) is just for slowdown. It's bit unsafe since + * this address could be used for something usefull. + */ +#define OUTB(addr, data) {outb(data, addr);outb(0, 0x80);} + +/* memcpy() was not defined og 386bsd. Lets define it here */ +#define memcpy(d, s, c) bcopy(s, d, c) + +/* + * When a error (such as EINVAL) is returned by a function, + * the following macro is used. The driver assumes that a + * error is signalled by returning a negative value. + */ + +#define RET_ERROR(err) -(err) + +/* + KERNEL_MALLOC() allocates requested number of memory and + KERNEL_FREE is used to free it. + These macros are never called from interrupt, in addition the + nbytes will never be more than 4096 bytes. Generally the driver + will allocate memory in blocks of 4k. If the kernel has just a + page level memory allocation, 4K can be safely used as the size + (the nbytes parameter can be ignored). +*/ +#define KERNEL_MALLOC(nbytes) malloc(nbytes, M_TEMP, M_WAITOK) +#define KERNEL_FREE(addr) free(addr, M_TEMP) + +/* + * The macro PERMANENT_MALLOC(typecast, mem_ptr, size, linux_ptr) + * returns size bytes of + * (kernel virtual) memory which will never get freed by the driver. + * This macro is called only during boot. The linux_ptr is a linux specific + * parameter which should be ignored in other operating systems. + * The mem_ptr is a pointer variable where the macro assigns pointer to the + * memory area. The type is the type of the mem_ptr. + */ +#define PERMANENT_MALLOC(typecast, mem_ptr, size, linux_ptr) \ + (mem_ptr) = (typecast)malloc((size), M_TEMP, M_WAITOK) + +/* + * The macro DEFINE_TIMER defines variables for the ACTIVATE_TIMER if + * required. The name is the variable/name to be used and the proc is + * the procedure to be called when the timer expires. + */ + +#define DEFINE_TIMER(name, proc) + +/* + * The ACTIVATE_TIMER requests system to call 'proc' after 'time' ticks. + */ + +#define ACTIVATE_TIMER(name, proc, time) \ + timeout((timeout_func_t)proc, 0, time); +/* + * The rest of this file is not complete yet. The functions using these + * macros will not work + */ +#define ALLOC_DMA_CHN(chn) ({ 0; }) +#define RELEASE_DMA_CHN(chn) ({ 0; }) +#define DMA_MODE_READ 0 +#define DMA_MODE_WRITE 1 +#define RELEASE_IRQ(irq_no) + +#endif +#endif diff --git a/sys/i386/isa/sound/pas.h b/sys/i386/isa/sound/pas.h new file mode 100644 index 0000000..29f9ff6 --- /dev/null +++ b/sys/i386/isa/sound/pas.h @@ -0,0 +1,253 @@ +/* + * $Id$ + */ +/* */ +/* Port addresses and bit fields for the Media Vision Pro AudioSpectrum second generation sound cards. */ +/* */ +/* Feel free to use this header file in any application you create that has support for the Media Vision */ +/* Pro AudioSpectrum second generation sound cards. Other uses prohibited without prior permission. */ +/* */ +/* - cmetz@thor.tjhsst.edu */ +/* */ +/* Notes: */ +/* */ +/* * All of these ports go into the MVD101 multimedia controller chip, which then signals the other chips to do */ +/* the actual work. Many ports like the FM ones functionally attach directly to the destination chip though */ +/* they don't actually have a direct connection. */ +/* */ +/* * The PAS2 series cards have an MVD101 multimedia controller chip, the original PAS cards don't. The original */ +/* PAS cards are pretty defunct now, so no attempt is made here to support them. */ +/* */ +/* * The PAS2 series cards are all really different at the hardware level, though the MVD101 hides some of the */ +/* incompatibilities, there still are differences that need to be accounted for. */ +/* */ +/* Card CD-ROM interface PCM chip Mixer chip FM chip */ +/* PAS Plus Sony proprietary (Crystal?) 8-bit DAC National OPL3 */ +/* PAS 16 Zilog SCSI MVA416 16-bit Codec MVA508 OPL3 */ +/* CDPC Sony proprietary Sony 16-bit Codec National OPL3 */ +/* Fusion CD 16 Sony proprietary MVA416 16-bit Codec MVA508 OPL3 */ +/* Fusion CD Sony proprietary (Crystal?) 8-bit DAC National OPL3 */ +/* */ +#define PAS_DEFAULT_BASE 0x388 + +/* Symbolic Name Value R W Subsystem Description */ +#define SPEAKER_CONTROL 0x61 /* W PC speaker Control register */ +#define SPEAKER_CONTROL_GHOST 0x738B /* R W PC speaker Control ghost register */ +#define SPEAKER_TIMER_CONTROL 0x43 /* W PC speaker Timer control register */ +#define SPEAKER_TIMER_CONTROL_GHOST 0x778B /* R W PC speaker Timer control register ghost */ +#define SPEAKER_TIMER_DATA 0x42 /* W PC speaker Timer data register */ +#define SPEAKER_TIMER_DATA_GHOST 0x138A /* R W PC speaker Timer data register ghost */ + +#define WARM_BOOT 0x41 /* W Control Used to detect system warm boot */ +#define WARM_BOOT_GHOST 0x7789 /* ? W Control Use to get the card to fake warm boot */ +#define MASTER_DECODE 0x9A01 /* W Control Address >> 2 of card base address */ +#define PRESCALE_DIVIDER 0xBF8A /* R W PCM Ration between Codec clock and master clock */ +#define WAIT_STATE 0xBF88 /* R W Control Four-bit bus wait-state count (~140ns ea.) */ +#define BOARD_REV_ID 0x2789 /* R Control Extended Board Revision ID */ + +#define SYSTEM_CONFIGURATION_1 0x8388 /* R W Control */ + #define S_C_1_PCS_ENABLE 0x01 /* R W PC speaker 1=enable, 0=disable PC speaker emulation */ + #define S_C_1_PCM_CLOCK_SELECT 0x02 /* R W PCM 1=14.31818Mhz/12, 0=28.224Mhz master clock */ + #define S_C_1_FM_EMULATE_CLOCK 0x04 /* R W FM 1=use 28.224Mhz/2, 0=use 14.31818Mhz clock */ + #define S_C_1_PCS_STEREO 0x10 /* R W PC speaker 1=enable PC speaker stereo effect, 0=disable */ + #define S_C_1_PCS_REALSOUND 0x20 /* R W PC speaker 1=enable RealSound enhancement, 0=disable */ + #define S_C_1_FORCE_EXT_RESET 0x40 /* R W Control Force external reset */ + #define S_C_1_FORCE_INT_RESET 0x80 /* R W Control Force internal reset */ +#define SYSTEM_CONFIGURATION_2 0x8389 /* R W Control */ + #define S_C_2_PCM_OVERSAMPLING 0x03 /* R W PCM 00=0x, 01=2x, 10=4x, 11=reserved */ + #define S_C_2_PCM_16_BIT 0x04 /* R W PCM 1=16-bit, 0=8-bit samples */ +#define SYSTEM_CONFIGURATION_3 0x838A /* R W Control */ + #define S_C_3_PCM_CLOCK_SELECT 0x02 /* R W PCM 1=use 1.008Mhz clock for PCM, 0=don't */ +#define SYSTEM_CONFIGURATION_4 0x838B /* R W Control CD-ROM interface controls */ + +#define IO_CONFIGURATION_1 0xF388 /* R W Control */ + #define I_C_1_BOOT_RESET_ENABLE 0x80 /* R W Control 1=reset board on warm boot, 0=don't */ +#define IO_CONFIGURATION_2 0xF389 /* R W Control */ + #define I_C_2_PCM_DMA_DISABLED 0x00 /* R W PCM PCM DMA disabled */ +#define IO_CONFIGURATION_3 0xF38A /* R W Control */ + #define I_C_3_PCM_IRQ_DISABLED 0x00 /* R W PCM PCM IRQ disabled */ + +#define COMPATIBILITY_ENABLE 0xF788 /* R W Control */ + #define C_E_MPU401_ENABLE 0x01 /* R W MIDI 1=enable, 0=disable MPU401 MIDI emulation */ + #define C_E_SB_ENABLE 0x02 /* R W PCM 1=enable, 0=disable Sound Blaster emulation */ + #define C_E_SB_ACTIVE 0x04 /* R PCM "Sound Blaster Interrupt active" */ + #define C_E_MPU401_ACTIVE 0x08 /* R MIDI "MPU UART mode active" */ + #define C_E_PCM_COMPRESSION 0x10 /* R W PCM 1=enable, 0=disabled compression */ +#define EMULATION_ADDRESS 0xF789 /* R W Control */ + #define E_A_SB_BASE 0x0f /* R W PCM bits A4-A7 for SB base port */ + #define E_A_MPU401_BASE 0xf0 /* R W MIDI bits A4-A7 for MPU401 base port */ +#define EMULATION_CONFIGURATION 0xFB8A /* R W ***** Only valid on newer PAS2 cards (?) ***** */ + #define E_C_MPU401_IRQ 0x07 /* R W MIDI MPU401 emulation IRQ */ + #define E_C_SB_IRQ 0x38 /* R W PCM SB emulation IRQ */ + #define E_C_SB_DMA 0xC0 /* R W PCM SB emulation DMA */ + +#define OPERATION_MODE_1 0xEF8B /* R Control */ + #define O_M_1_CDROM_TYPE 0x03 /* R CD-ROM 3=SCSI, 2=Sony, 0=no CD-ROM interface */ + #define O_M_1_FM_TYPE 0x04 /* R FM 1=sterero, 0=mono FM chip */ + #define O_M_1_PCM_TYPE 0x08 /* R PCM 1=16-bit Codec, 0=8-bit DAC */ +#define OPERATION_MODE_2 0xFF8B /* R Control */ + #define O_M_2_PCS_ENABLED 0x02 /* R PC speaker PC speaker emulation 1=enabled, 0=disabled */ + #define O_M_2_BUS_TIMING 0x10 /* R Control 1=AT bus timing, 0=XT bus timing */ + #define O_M_2_BOARD_REVISION 0xe0 /* R Control Board revision */ + +#define INTERRUPT_MASK 0x0B8B /* R W Control */ + #define I_M_FM_LEFT_IRQ_ENABLE 0x01 /* R W FM Enable FM left interrupt */ + #define I_M_FM_RIGHT_IRQ_ENABLE 0x02 /* R W FM Enable FM right interrupt */ + #define I_M_PCM_RATE_IRQ_ENABLE 0x04 /* R W PCM Enable Sample Rate interrupt */ + #define I_M_PCM_BUFFER_IRQ_ENABLE 0x08 /* R W PCM Enable Sample Buffer interrupt */ + #define I_M_MIDI_IRQ_ENABLE 0x10 /* R W MIDI Enable MIDI interrupt */ + #define I_M_BOARD_REV 0xE0 /* R Control Board revision */ + +#define INTERRUPT_STATUS 0x0B89 /* R W Control */ + #define I_S_FM_LEFT_IRQ 0x01 /* R W FM Left FM Interrupt Pending */ + #define I_S_FM_RIGHT_IRQ 0x02 /* R W FM Right FM Interrupt Pending */ + #define I_S_PCM_SAMPLE_RATE_IRQ 0x04 /* R W PCM Sample Rate Interrupt Pending */ + #define I_S_PCM_SAMPLE_BUFFER_IRQ 0x08 /* R W PCM Sample Buffer Interrupt Pending */ + #define I_S_MIDI_IRQ 0x10 /* R W MIDI MIDI Interrupt Pending */ + #define I_S_PCM_CHANNEL 0x20 /* R W PCM 1=right, 0=left */ + #define I_S_RESET_ACTIVE 0x40 /* R W Control Reset is active (Timed pulse not finished) */ + #define I_S_PCM_CLIPPING 0x80 /* R W PCM Clipping has occurred */ + +#define FILTER_FREQUENCY 0x0B8A /* R W Control */ + #define F_F_FILTER_DISABLED 0x00 /* R W Mixer No filter */ +#if 0 + struct { /* R W Mixer Filter translation */ + unsigned int freq:24; + unsigned int value:8; + } F_F_FILTER_translate[] = + { { 73500, 0x01 }, /* 73500Hz - divide by 16 */ + { 65333, 0x02 }, /* 65333Hz - divide by 18 */ + { 49000, 0x09 }, /* 49000Hz - divide by 24 */ + { 36750, 0x11 }, /* 36750Hz - divide by 32 */ + { 24500, 0x19 }, /* 24500Hz - divide by 48 */ + { 18375, 0x07 }, /* 18375Hz - divide by 64 */ + { 12783, 0x0f }, /* 12783Hz - divide by 92 */ + { 12250, 0x04 }, /* 12250Hz - divide by 96 */ + { 9188, 0x17 }, /* 9188Hz - divide by 128 */ + { 6125, 0x1f }, /* 6125Hz - divide by 192 */ + }; +#endif + #define F_F_MIXER_UNMUTE 0x20 /* R W Mixer 1=disable, 0=enable board mute */ + #define F_F_PCM_RATE_COUNTER 0x40 /* R W PCM 1=enable, 0=disable sample rate counter */ + #define F_F_PCM_BUFFER_COUNTER 0x80 /* R W PCM 1=enable, 0=disable sample buffer counter */ + +#define PAS_NONE 0 +#define PAS_PLUS 1 +#define PAS_CDPC 2 +#define PAS_16 3 +#define PAS_16D 4 + +#ifdef DEFINE_TRANSLATIONS + char I_C_2_PCM_DMA_translate[] = /* R W PCM PCM DMA channel value translations */ + { 4, 1, 2, 3, 0, 5, 6, 7 }; + char I_C_3_PCM_IRQ_translate[] = /* R W PCM PCM IRQ level value translation */ + { 0, 0, 1, 2, 3, 4, 5, 6, 0, 1, 7, 8, 9, 0, 10, 11 }; + char E_C_MPU401_IRQ_translate[] = /* R W MIDI MPU401 emulation IRQ value translation */ + { 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x01, 0x05, 0x06, 0x07 }; + char E_C_SB_IRQ_translate[] = /* R W PCM SB emulation IRQ translate */ + { 0x00, 0x00, 0x08, 0x10, 0x00, 0x18, 0x00, 0x20, 0x00, 0x08, 0x28, 0x30, 0x38, 0, 0 }; + char E_C_SB_DMA_translate[] = /* R W PCM SB emulation DMA translate */ + { 0x00, 0x40, 0x80, 0xC0, 0, 0, 0, 0 }; + char O_M_1_to_card[] = /* R W Control Translate (OM1 & 0x0f) to card type */ + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 4, 0, 2, 3 }; +#else + extern char I_C_2_PCM_DMA_translate[]; /* R W PCM PCM DMA channel value translations */ + extern char I_C_3_PCM_IRQ_translate[]; /* R W PCM PCM IRQ level value translation */ + extern char E_C_MPU401_IRQ_translate[]; /* R W MIDI MPU401 emulation IRQ value translation */ + extern char E_C_SB_IRQ_translate[]; /* R W PCM SB emulation IRQ translate */ + extern char E_C_SB_DMA_translate[]; /* R W PCM SB emulation DMA translate */ + extern char O_M_1_to_card[]; /* R W Control Translate (OM1 & 0x0f) to card type */ +#endif + +#define PARALLEL_MIXER 0x078B /* W Mixer Documented for MVD101 as FM Mono Right decode?? */ + #define P_M_MV508_ADDRESS 0x80 /* W Mixer MVD508 Address/mixer select */ + #define P_M_MV508_DATA 0x00 + #define P_M_MV508_LEFT 0x20 /* W Mixer MVD508 Left channel select */ + #define P_M_MV508_RIGHT 0x40 /* W Mixer MVD508 Right channel select */ + #define P_M_MV508_BOTH 0x00 /* W Mixer MVD508 Both channel select */ + #define P_M_MV508_MIXER 0x10 /* W Mixer MVD508 Select a mixer (rather than a volume) */ + #define P_M_MV508_VOLUME 0x00 + + #define P_M_MV508_INPUTMIX 0x20 /* W Mixer MVD508 Select mixer A */ + #define P_M_MV508_OUTPUTMIX 0x00 /* W Mixer MVD508 Select mixer B */ + + #define P_M_MV508_MASTER_A 0x01 /* W Mixer MVD508 Master volume control A (output) */ + #define P_M_MV508_MASTER_B 0x02 /* W Mixer MVD508 Master volume control B (DSP input) */ + #define P_M_MV508_BASS 0x03 /* W Mixer MVD508 Bass control */ + #define P_M_MV508_TREBLE 0x04 /* W Mixer MVD508 Treble control */ + #define P_M_MV508_MODE 0x05 /* W Mixer MVD508 Master mode control */ + + #define P_M_MV508_LOUDNESS 0x04 /* W Mixer MVD508 Mode control - Loudness filter */ + #define P_M_MV508_ENHANCE_BITS 0x03 + #define P_M_MV508_ENHANCE_NONE 0x00 /* W Mixer MVD508 Mode control - No stereo enhancement */ + #define P_M_MV508_ENHANCE_40 0x01 /* W Mixer MVD508 Mode control - 40% stereo enhancement */ + #define P_M_MV508_ENHANCE_60 0x02 /* W Mixer MVD508 Mode control - 60% stereo enhancement */ + #define P_M_MV508_ENHANCE_80 0x03 /* W Mixer MVD508 Mode control - 80% stereo enhancement */ + + #define P_M_MV508_FM 0x00 /* W Mixer MVD508 Channel 0 - FM */ + #define P_M_MV508_IMIXER 0x01 /* W Mixer MVD508 Channel 1 - Input mixer (rec monitor) */ + #define P_M_MV508_LINE 0x02 /* W Mixer MVD508 Channel 2 - Line in */ + #define P_M_MV508_CDROM 0x03 /* W Mixer MVD508 Channel 3 - CD-ROM */ + #define P_M_MV508_MIC 0x04 /* W Mixer MVD508 Channel 4 - Microphone */ + #define P_M_MV508_PCM 0x05 /* W Mixer MVD508 Channel 5 - PCM */ + #define P_M_MV508_SPEAKER 0x06 /* W Mixer MVD508 Channel 6 - PC Speaker */ + #define P_M_MV508_SB 0x07 /* W Mixer MVD508 Channel 7 - SB DSP */ + +#define SERIAL_MIXER 0xB88 /* R W Control Serial mixer control (used other ways) */ + #define S_M_PCM_RESET 0x01 /* R W PCM Codec/DSP reset */ + #define S_M_FM_RESET 0x02 /* R W FM FM chip reset */ + #define S_M_SB_RESET 0x04 /* R W PCM SB emulation chip reset */ + #define S_M_MIXER_RESET 0x10 /* R W Mixer Mixer chip reset */ + #define S_M_INTEGRATOR_ENABLE 0x40 /* R W Speaker Enable PC speaker integrator (FORCE RealSound) */ + #define S_M_OPL3_DUAL_MONO 0x80 /* R W FM Set the OPL-3 to dual mono mode */ + +#define PCM_CONTROL 0xF8A /* R W PCM PCM Control Register */ + #define P_C_MIXER_CROSS_FIELD 0x0f + #define P_C_MIXER_CROSS_R_TO_R 0x01 /* R W Mixer Connect Right to Right */ + #define P_C_MIXER_CROSS_L_TO_R 0x02 /* R W Mixer Connect Left to Right */ + #define P_C_MIXER_CROSS_R_TO_L 0x04 /* R W Mixer Connect Right to Left */ + #define P_C_MIXER_CROSS_L_TO_L 0x08 /* R W Mixer Connect Left to Left */ + #define P_C_PCM_DAC_MODE 0x10 /* R W PCM Playback (DAC) mode */ + #define P_C_PCM_ADC_MODE 0x00 /* R W PCM Record (ADC) mode */ + #define P_C_PCM_MONO 0x20 /* R W PCM Mono mode */ + #define P_C_PCM_STEREO 0x00 /* R W PCM Stereo mode */ + #define P_C_PCM_ENABLE 0x40 /* R W PCM Enable PCM engine */ + #define P_C_PCM_DMA_ENABLE 0x80 /* R W PCM Enable DRQ */ + +#define SAMPLE_COUNTER_CONTROL 0x138B /* R W PCM Sample counter control register */ + #define S_C_C_SQUARE_WAVE 0x04 /* R W PCM Square wave generator (use for sample rate) */ + #define S_C_C_RATE 0x06 /* R W PCM Rate generator (use for sample buffer count) */ + #define S_C_C_LSB_THEN_MSB 0x30 /* R W PCM Change all 16 bits, LSB first, then MSB */ + + /* MVD101 and SDK documentations have S_C_C_SAMPLE_RATE and S_C_C_SAMPLE_BUFFER transposed. Only one works :-) */ + #define S_C_C_SAMPLE_RATE 0x00 /* R W PCM Select sample rate timer */ + #define S_C_C_SAMPLE_BUFFER 0x40 /* R W PCM Select sample buffer counter */ + + #define S_C_C_PC_SPEAKER 0x80 /* R W PCM Select PC speaker counter */ + +#define SAMPLE_RATE_TIMER 0x1388 /* W PCM Sample rate timer register (PCM wait interval) */ +#define SAMPLE_BUFFER_COUNTER 0x1389 /* R W PCM Sample buffer counter (DMA buffer size) */ + +#define MIDI_CONTROL 0x178b /* R W MIDI Midi control register */ + #define M_C_ENA_TSTAMP_IRQ 0x01 /* R W MIDI Enable Time Stamp Interrupts */ + #define M_C_ENA_TME_COMP_IRQ 0x02 /* R W MIDI Enable time compare interrupts */ + #define M_C_ENA_INPUT_IRQ 0x04 /* R W MIDI Enable input FIFO interrupts */ + #define M_C_ENA_OUTPUT_IRQ 0x08 /* R W MIDI Enable output FIFO interrupts */ + #define M_C_ENA_OUTPUT_HALF_IRQ 0x10 /* R W MIDI Enable output FIFO half full interrupts */ + #define M_C_RESET_INPUT_FIFO 0x20 /* R W MIDI Reset input FIFO pointer */ + #define M_C_RESET_OUTPUT_FIFO 0x40 /* R W MIDI Reset output FIFO pointer */ + #define M_C_ENA_THRU_MODE 0x80 /* R W MIDI Echo input to output (THRU) */ + +#define MIDI_STATUS 0x1B88 /* R W MIDI Midi (interrupt) status register */ + #define M_S_TIMESTAMP 0x01 /* R W MIDI Midi time stamp interrupt occurred */ + #define M_S_COMPARE 0x02 /* R W MIDI Midi compare time interrupt occurred */ + #define M_S_INPUT_AVAIL 0x04 /* R W MIDI Midi input data available interrupt occurred */ + #define M_S_OUTPUT_EMPTY 0x08 /* R W MIDI Midi output FIFO empty interrupt occurred */ + #define M_S_OUTPUT_HALF_EMPTY 0x10 /* R W MIDI Midi output FIFO half empty interrupt occurred */ + #define M_S_INPUT_OVERRUN 0x20 /* R W MIDI Midi input overrun error occurred */ + #define M_S_OUTPUT_OVERRUN 0x40 /* R W MIDI Midi output overrun error occurred */ + #define M_S_FRAMING_ERROR 0x80 /* R W MIDI Midi input framing error occurred */ + +#define MIDI_FIFO_STATUS 0x1B89 /* R W MIDI Midi fifo status */ +#define MIDI_DATA 0x178A /* R W MIDI Midi data register */ +#define MIDI_INPUT_AVAILABLE 0x0f /* RW MIDI */ diff --git a/sys/i386/isa/sound/pas2_card.c b/sys/i386/isa/sound/pas2_card.c new file mode 100644 index 0000000..fc023a0 --- /dev/null +++ b/sys/i386/isa/sound/pas2_card.c @@ -0,0 +1,384 @@ +#define _PAS2_CARD_C_ +#define SND_SA_INTERRUPT +/* + * sound/pas2_card.c + * + * Detection routine for the Pro Audio Spectrum cards. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS) + +#define DEFINE_TRANSLATIONS +#include "pas.h" + +/* + * The Address Translation code is used to convert I/O register addresses to + * be relative to the given base -register + */ + +int translat_code; +static int pas_intr_mask = 0; +static int pas_irq = 0; + +static char pas_model; +static unsigned char board_rev_id; +#define PAS_REVD_BOARD_ID 127 +static char *pas_model_names[] = +{"", "Pro AudioSpectrum+", "CDPC", "Pro AudioSpectrum 16", "Pro AudioSpectrum 16D"}; + +/* pas_read() and pas_write() are equivalents of INB() and OUTB() */ +/* These routines perform the I/O address translation required */ +/* to support other than the default base address */ + +unsigned char +pas_read (int ioaddr) +{ + return INB (ioaddr ^ translat_code); +} + +void +pas_write (unsigned char data, int ioaddr) +{ + OUTB (data, ioaddr ^ translat_code); +} + +/* + * The Revision D cards have a problem with their MVA508 interface. The + * kludge-o-rama fix is to make a 16-bit quantity with identical LSB and + * MSBs out of the output byte and to do a 16-bit out to the mixer port - + * 1. + */ + +void +mix_write (unsigned char data, int ioaddr) +{ + if (board_rev_id >= PAS_REVD_BOARD_ID) { + outw ((ioaddr ^ translat_code) - 1, data | (data << 8)); + outb (0, 0x80); + } else + OUTB (data, ioaddr ^ translat_code); +} + +void +pas2_msg (char *foo) +{ + printk (" PAS2: %s.\n", foo); +} + +/******************* Begin of the Interrupt Handler ********************/ + +void +pasintr (int unused) +{ + int status; + + status = pas_read (INTERRUPT_STATUS); + pas_write (status, INTERRUPT_STATUS); /* Clear interrupt */ + + if (status & I_S_PCM_SAMPLE_BUFFER_IRQ) + { +#ifndef EXCLUDE_AUDIO + pas_pcm_interrupt (status, 1); +#endif + status &= ~I_S_PCM_SAMPLE_BUFFER_IRQ; + } + if (status & I_S_MIDI_IRQ) + { +#ifndef EXCLUDE_MIDI +#ifdef EXCLUDE_PRO_MIDI + pas_midi_interrupt (); +#endif +#endif + status &= ~I_S_MIDI_IRQ; + } + +} + +int +pas_set_intr (int mask) +{ + int err; + + if (!mask) + return 0; + + if (!pas_intr_mask) + { + if ((err = snd_set_irq_handler (pas_irq, pasintr)) < 0) + return err; + } + pas_intr_mask |= mask; + + pas_write (pas_intr_mask, INTERRUPT_MASK); + return 0; +} + +int +pas_remove_intr (int mask) +{ + if (!mask) + return 0; + + pas_intr_mask &= ~mask; + pas_write (pas_intr_mask, INTERRUPT_MASK); + + if (!pas_intr_mask) + { + snd_release_irq (pas_irq); + } + return 0; +} + +/******************* End of the Interrupt handler **********************/ + +/******************* Begin of the Initialization Code ******************/ + +int +config_pas_hw (struct address_info *hw_config) +{ + char ok = 1; + + pas_irq = hw_config->irq; + + pas_write (0x00, INTERRUPT_MASK); + + pas_write (0x36, SAMPLE_COUNTER_CONTROL); /* Local timer control + * register */ + + pas_write (0x36, SAMPLE_RATE_TIMER); /* Sample rate timer (16 bit) */ + pas_write (0, SAMPLE_RATE_TIMER); + + pas_write (0x74, SAMPLE_COUNTER_CONTROL); /* Local timer control + * register */ + + pas_write (0x74, SAMPLE_BUFFER_COUNTER); /* Sample count register (16 + * bit) */ + pas_write (0, SAMPLE_BUFFER_COUNTER); + + pas_write (F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER | F_F_MIXER_UNMUTE | 1, FILTER_FREQUENCY); + pas_write (P_C_PCM_DMA_ENABLE | P_C_PCM_MONO | P_C_PCM_DAC_MODE | P_C_MIXER_CROSS_L_TO_L | P_C_MIXER_CROSS_R_TO_R, PCM_CONTROL); + pas_write (S_M_PCM_RESET | S_M_FM_RESET | S_M_SB_RESET | S_M_MIXER_RESET /* | S_M_OPL3_DUAL_MONO */ , SERIAL_MIXER); + + pas_write (I_C_1_BOOT_RESET_ENABLE, IO_CONFIGURATION_1); + + if (pas_irq < 0 || pas_irq > 15) + { + printk ("PAS2: Invalid IRQ %d", pas_irq); + ok = 0; + } + else + { + pas_write (I_C_3_PCM_IRQ_translate[pas_irq], IO_CONFIGURATION_3); + if (!I_C_3_PCM_IRQ_translate[pas_irq]) + { + printk ("PAS2: Invalid IRQ %d", pas_irq); + ok = 0; + } + } + + if (hw_config->dma < 0 || hw_config->dma > 7) + { + printk ("PAS2: Invalid DMA selection %d", hw_config->dma); + ok = 0; + } + else + { + pas_write (I_C_2_PCM_DMA_translate[hw_config->dma], IO_CONFIGURATION_2); + if (!I_C_2_PCM_DMA_translate[hw_config->dma]) + { + printk ("PAS2: Invalid DMA selection %d", hw_config->dma); + ok = 0; + } + } + + /* + * This fixes the timing problems of the PAS due to the Symphony chipset + * as per Media Vision. Only define this if your PAS doesn't work correctly. + */ +#ifdef SYMPHONY_PAS + OUTB (0x05, 0xa8); + OUTB (0x60, 0xa9); +#endif + +#ifdef BROKEN_BUS_CLOCK + pas_write (S_C_1_PCS_ENABLE | S_C_1_PCS_STEREO | S_C_1_PCS_REALSOUND | S_C_1_FM_EMULATE_CLOCK, SYSTEM_CONFIGURATION_1); +#else + /* pas_write(S_C_1_PCS_ENABLE, SYSTEM_CONFIGURATION_1); */ + pas_write (S_C_1_PCS_ENABLE | S_C_1_PCS_STEREO | S_C_1_PCS_REALSOUND, SYSTEM_CONFIGURATION_1); +#endif + pas_write (0x18, SYSTEM_CONFIGURATION_3); /* ??? */ + + pas_write (F_F_MIXER_UNMUTE | 0x01, FILTER_FREQUENCY); /* Sets mute off and + * selects filter rate + * of 17.897 kHz */ + + if (pas_model == PAS_16 || pas_model == PAS_16D) + pas_write (8, PRESCALE_DIVIDER); + else + pas_write (0, PRESCALE_DIVIDER); + + mix_write (P_M_MV508_ADDRESS | 5, PARALLEL_MIXER); + mix_write (5, PARALLEL_MIXER); + +#if !defined(EXCLUDE_SB_EMULATION) || !defined(EXCLUDE_SB) + + { + struct address_info *sb_config; + + if ((sb_config = sound_getconf (SNDCARD_SB))) + { + unsigned char irq_dma; + + /* Turn on Sound Blaster compatibility */ + /* bit 1 = SB emulation */ + /* bit 0 = MPU401 emulation (CDPC only :-( ) */ + pas_write (0x02, COMPATIBILITY_ENABLE); + + /* "Emulation address" */ + pas_write ((sb_config->io_base >> 4) & 0x0f, EMULATION_ADDRESS); + + if (!E_C_SB_DMA_translate[sb_config->dma]) + printk ("\n\nPAS16 Warning: Invalid SB DMA %d\n\n", + sb_config->dma); + + if (!E_C_SB_IRQ_translate[sb_config->irq]) + printk ("\n\nPAS16 Warning: Invalid SB IRQ %d\n\n", + sb_config->irq); + + irq_dma = E_C_SB_DMA_translate[sb_config->dma] | + E_C_SB_IRQ_translate[sb_config->irq]; + + pas_write (irq_dma, EMULATION_CONFIGURATION); + } + } +#endif + + if (!ok) + pas2_msg ("Driver not enabled"); + + return ok; +} + +int +detect_pas_hw (struct address_info *hw_config) +{ + unsigned char board_id, foo; + + /* + * WARNING: Setting an option like W:1 or so that disables warm boot reset + * of the card will screw up this detect code something fierce. Adding code + * to handle this means possibly interfering with other cards on the bus if + * you have something on base port 0x388. SO be forewarned. + */ + + OUTB (0xBC, MASTER_DECODE); /* Talk to first board */ + OUTB (hw_config->io_base >> 2, MASTER_DECODE); /* Set base address */ + translat_code = PAS_DEFAULT_BASE ^ hw_config->io_base; + pas_write (1, WAIT_STATE); /* One wait-state */ + + board_id = pas_read (INTERRUPT_MASK); + + if (board_id == 0xff) + return 0; + + /* + * We probably have a PAS-series board, now check for a PAS2-series board + * by trying to change the board revision bits. PAS2-series hardware won't + * let you do this - the bits are read-only. + */ + + foo = board_id ^ 0xe0; + + pas_write (foo, INTERRUPT_MASK); + foo = INB (INTERRUPT_MASK); + pas_write (board_id, INTERRUPT_MASK); + + if (board_id != foo) /* Not a PAS2 */ + return 0; + + pas_model = O_M_1_to_card[pas_read (OPERATION_MODE_1) & 0x0f]; + + return pas_model; +} + +long +attach_pas_card (long mem_start, struct address_info *hw_config) +{ + pas_irq = hw_config->irq; + + if (detect_pas_hw (hw_config)) + { + + board_rev_id = pas_read (BOARD_REV_ID); + if ((pas_model = O_M_1_to_card[pas_read (OPERATION_MODE_1) & 0x0f])) + { +#ifdef __FreeBSD__ + printk ("snd3: <%s rev %d>", pas_model_names[(int) pas_model], board_rev_id); +#else + printk (" <%s rev %d>", pas_model_names[(int) pas_model], board_rev_id); +#endif + } + + if (config_pas_hw (hw_config)) + { + +#ifndef EXCLUDE_AUDIO + mem_start = pas_pcm_init (mem_start, hw_config); +#endif + +#if !defined(EXCLUDE_SB_EMULATION) && !defined(EXCLUDE_SB) + + sb_dsp_disable_midi (); /* The SB emulation don't support + * midi */ +#endif + +#ifndef EXCLUDE_YM3812 + enable_opl3_mode (0x388, 0x38a, 0); +#endif + +#ifndef EXCLUDE_MIDI +#ifdef EXCLUDE_PRO_MIDI + mem_start = pas_midi_init (mem_start); +#endif +#endif + + pas_init_mixer (); + } + } + + return mem_start; +} + +int +probe_pas (struct address_info *hw_config) +{ + return detect_pas_hw (hw_config); +} + +#endif diff --git a/sys/i386/isa/sound/pas2_midi.c b/sys/i386/isa/sound/pas2_midi.c new file mode 100644 index 0000000..e502db9 --- /dev/null +++ b/sys/i386/isa/sound/pas2_midi.c @@ -0,0 +1,296 @@ +/* + * sound/pas2_midi.c + * + * The low level driver for the PAS Midi Interface. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "pas.h" + +#if !defined(EXCLUDE_PAS) && !defined(EXCLUDE_MIDI) && defined(EXCLUDE_PRO_MIDI) + +static int midi_busy = 0, input_opened = 0; +static int my_dev; +static volatile int ofifo_bytes = 0; + +static unsigned char tmp_queue[256]; +static volatile int qlen; +static volatile unsigned char qhead, qtail; + +static void (*midi_input_intr) (int dev, unsigned char data); + +static int +pas_midi_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) +) +{ + int err; + unsigned long flags; + unsigned char ctrl; + + + if (midi_busy) + { + printk ("PAS2: Midi busy\n"); + return RET_ERROR (EBUSY); + } + + /* Reset input and output FIFO pointers */ + pas_write (M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO, + MIDI_CONTROL); + + DISABLE_INTR (flags); + + if ((err = pas_set_intr (I_M_MIDI_IRQ_ENABLE)) < 0) + return err; + + /* Enable input available and output FIFO empty interrupts */ + + ctrl = 0; + input_opened = 0; + midi_input_intr = input; + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + { + ctrl |= M_C_ENA_INPUT_IRQ;/* Enable input */ + input_opened = 1; + } + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + { + ctrl |= M_C_ENA_OUTPUT_IRQ | /* Enable output */ + M_C_ENA_OUTPUT_HALF_IRQ; + } + + pas_write (ctrl, + MIDI_CONTROL); + + /* Acknowledge any pending interrupts */ + + pas_write (0xff, MIDI_STATUS); + ofifo_bytes = 0; + + RESTORE_INTR (flags); + + midi_busy = 1; + qlen = qhead = qtail = 0; + return 0; +} + +static void +pas_midi_close (int dev) +{ + + /* Reset FIFO pointers, disable intrs */ + pas_write (M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO, MIDI_CONTROL); + + pas_remove_intr (I_M_MIDI_IRQ_ENABLE); + midi_busy = 0; +} + +static int +dump_to_midi (unsigned char midi_byte) +{ + int fifo_space, x; + + fifo_space = ((x = pas_read (MIDI_FIFO_STATUS)) >> 4) & 0x0f; + + if (fifo_space == 15 || (fifo_space < 2 && ofifo_bytes > 13)) /* Fifo full */ + { + return 0; /* Upper layer will call again */ + } + + ofifo_bytes++; + + pas_write (midi_byte, MIDI_DATA); + + return 1; +} + +static int +pas_midi_out (int dev, unsigned char midi_byte) +{ + + unsigned long flags; + + /* + * Drain the local queue first + */ + + DISABLE_INTR (flags); + + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + + /* + * Output the byte if the local queue is empty. + */ + + if (!qlen) + if (dump_to_midi (midi_byte)) + return 1; /* OK */ + + /* + * Put to the local queue + */ + + if (qlen >= 256) + return 0; /* Local queue full */ + + DISABLE_INTR (flags); + + tmp_queue[qtail] = midi_byte; + qlen++; + qtail++; + + RESTORE_INTR (flags); + + return 1; +} + +static int +pas_midi_start_read (int dev) +{ + return 0; +} + +static int +pas_midi_end_read (int dev) +{ + return 0; +} + +static int +pas_midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +pas_midi_kick (int dev) +{ + ofifo_bytes = 0; +} + +static int +pas_buffer_status (int dev) +{ + return !qlen; +} + +static struct midi_operations pas_midi_operations = +{ + {"Pro Audio Spectrum", 0, 0, SNDCARD_PAS}, + pas_midi_open, + pas_midi_close, + pas_midi_ioctl, + pas_midi_out, + pas_midi_start_read, + pas_midi_end_read, + pas_midi_kick, + NULL, /* command */ + pas_buffer_status +}; + +long +pas_midi_init (long mem_start) +{ + my_dev = num_midis; + midi_devs[num_midis++] = &pas_midi_operations; + return mem_start; +} + +void +pas_midi_interrupt (void) +{ + unsigned char stat; + int i, incount; + unsigned long flags; + + stat = pas_read (MIDI_STATUS); + + if (stat & M_S_INPUT_AVAIL) /* Input byte available */ + { + incount = pas_read (MIDI_FIFO_STATUS) & 0x0f; /* Input FIFO count */ + if (!incount) + incount = 16; + + for (i = 0; i < incount; i++) + if (input_opened) + { + midi_input_intr (my_dev, pas_read (MIDI_DATA)); + } + else + pas_read (MIDI_DATA); /* Flush */ + } + + if (stat & (M_S_OUTPUT_EMPTY | M_S_OUTPUT_HALF_EMPTY)) + { + if (!(stat & M_S_OUTPUT_EMPTY)) + { + ofifo_bytes = 8; + } + else + { + ofifo_bytes = 0; + } + + DISABLE_INTR (flags); + + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + } + + if (stat & M_S_FRAMING_ERROR) + printk ("MIDI framing error\n"); + + if (stat & M_S_OUTPUT_OVERRUN) + { + printk ("MIDI output overrun %x,%x,%d \n", pas_read (MIDI_FIFO_STATUS), stat, ofifo_bytes); + ofifo_bytes = 100; + } + + pas_write (stat, MIDI_STATUS);/* Acknowledge interrupts */ +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/pas2_mixer.c b/sys/i386/isa/sound/pas2_mixer.c new file mode 100644 index 0000000..8d83df4 --- /dev/null +++ b/sys/i386/isa/sound/pas2_mixer.c @@ -0,0 +1,493 @@ +#define _PAS2_MIXER_C_ + +/* + * sound/pas2_mixer.c + * + * Mixer routines for the Pro Audio Spectrum cards. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS) + +#include "pas.h" + +#define TRACE(what) /* (what) */ + +extern int translat_code; + +static int rec_devices = (SOUND_MASK_MIC); /* Default recording source */ +static int mode_control = 0; + +#define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_ALTPCM) + +#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_ALTPCM | SOUND_MASK_IMIX | \ + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV | \ + SOUND_MASK_MUTE | SOUND_MASK_ENHANCE | SOUND_MASK_LOUD) + +static unsigned short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x3232, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x5050, /* FM */ + 0x4b4b, /* PCM */ + 0x3232, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x4b4b, /* Mic */ + 0x4b4b, /* CD */ + 0x6464, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x6464}; /* Recording level */ + +static int +mixer_output (int right_vol, int left_vol, int div, int bits, + int mixer /* Input or output mixer */ ) +{ + int left = left_vol * div / 100; + int right = right_vol * div / 100; + + if (bits & P_M_MV508_MIXER) + { /* Select input or output mixer */ + left |= mixer; + right |= mixer; + } + + if (bits == P_M_MV508_BASS || bits == P_M_MV508_TREBLE) + { /* Bass and trebble are mono devices */ + mix_write (P_M_MV508_ADDRESS | bits, PARALLEL_MIXER); + mix_write (left, PARALLEL_MIXER); + right_vol = left_vol; + } + else + { + mix_write (P_M_MV508_ADDRESS | P_M_MV508_LEFT | bits, PARALLEL_MIXER); + mix_write (left, PARALLEL_MIXER); + mix_write (P_M_MV508_ADDRESS | P_M_MV508_RIGHT | bits, PARALLEL_MIXER); + mix_write (right, PARALLEL_MIXER); + } + + return (left_vol | (right_vol << 8)); +} + +void +set_mode (int new_mode) +{ + mix_write (P_M_MV508_ADDRESS | P_M_MV508_MODE, PARALLEL_MIXER); + mix_write (new_mode, PARALLEL_MIXER); + + mode_control = new_mode; +} + +static int +pas_mixer_set (int whichDev, unsigned int level) +{ + int left, right, devmask, changed, i, mixer = 0; + + TRACE (printk ("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev, level)); + + left = level & 0x7f; + right = (level & 0x7f00) >> 8; + + if (whichDev < SOUND_MIXER_NRDEVICES) + if ((1 << whichDev) & rec_devices) + mixer = P_M_MV508_INPUTMIX; + else + mixer = P_M_MV508_OUTPUTMIX; + + switch (whichDev) + { + case SOUND_MIXER_VOLUME: /* Master volume (0-63) */ + levels[whichDev] = mixer_output (right, left, 63, P_M_MV508_MASTER_A, 0); + break; + + /* + * Note! Bass and Treble are mono devices. Will use just the left + * channel. + */ + case SOUND_MIXER_BASS: /* Bass (0-12) */ + levels[whichDev] = mixer_output (right, left, 12, P_M_MV508_BASS, 0); + break; + case SOUND_MIXER_TREBLE: /* Treble (0-12) */ + levels[whichDev] = mixer_output (right, left, 12, P_M_MV508_TREBLE, 0); + break; + + case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_FM, mixer); + break; + case SOUND_MIXER_PCM: /* PAS PCM (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_PCM, mixer); + break; + case SOUND_MIXER_ALTPCM: /* SB PCM (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_SB, mixer); + break; + case SOUND_MIXER_SPEAKER: /* PC speaker (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_SPEAKER, mixer); + break; + case SOUND_MIXER_LINE: /* External line (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_LINE, mixer); + break; + case SOUND_MIXER_CD: /* CD (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_CDROM, mixer); + break; + case SOUND_MIXER_MIC: /* External microphone (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_MIC, mixer); + break; + case SOUND_MIXER_IMIX: /* Recording monitor (0-31) (Only available + * on the Output Mixer) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_IMIXER, + P_M_MV508_OUTPUTMIX); + break; + case SOUND_MIXER_RECLEV: /* Recording level (0-15) */ + levels[whichDev] = mixer_output (right, left, 15, P_M_MV508_MASTER_B, 0); + break; + + case SOUND_MIXER_MUTE: + return 0; + break; + + case SOUND_MIXER_ENHANCE: + i = 0; + level &= 0x7f; + if (level) + i = (level / 20) - 1; + + mode_control &= ~P_M_MV508_ENHANCE_BITS; + mode_control |= P_M_MV508_ENHANCE_BITS; + set_mode (mode_control); + + if (i) + i = (i + 1) * 20; + return i; + break; + + case SOUND_MIXER_LOUD: + mode_control &= ~P_M_MV508_LOUDNESS; + if (level) + mode_control |= P_M_MV508_LOUDNESS; + set_mode (mode_control); + return !!level; /* 0 or 1 */ + break; + + case SOUND_MIXER_RECSRC: + devmask = level & POSSIBLE_RECORDING_DEVICES; + + changed = devmask ^ rec_devices; + rec_devices = devmask; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (changed & (1 << i)) + { + pas_mixer_set (i, levels[i]); + } + return rec_devices; + break; + + default: + return RET_ERROR (EINVAL); + } + + return (levels[whichDev]); +} + +/*****/ + +static int +mixer_set_levels (struct sb_mixer_levels *user_l) +{ +#define cmix(v) ((((v.r*100+7)/15)<<8)| ((v.l*100+7)/15)) + + struct sb_mixer_levels l; + + IOCTL_FROM_USER ((char *) &l, (char *) user_l, 0, sizeof (l)); + + if (l.master.l & ~0xF || l.master.r & ~0xF + || l.line.l & ~0xF || l.line.r & ~0xF + || l.voc.l & ~0xF || l.voc.r & ~0xF + || l.fm.l & ~0xF || l.fm.r & ~0xF + || l.cd.l & ~0xF || l.cd.r & ~0xF + || l.mic & ~0x7) + return (RET_ERROR (EINVAL)); + + pas_mixer_set (SOUND_MIXER_VOLUME, cmix (l.master)); + pas_mixer_set (SOUND_MIXER_LINE, cmix (l.line)); + pas_mixer_set (SOUND_MIXER_PCM, cmix (l.voc)); + pas_mixer_set (SOUND_MIXER_ALTPCM, cmix (l.voc)); + pas_mixer_set (SOUND_MIXER_SYNTH, cmix (l.fm)); + pas_mixer_set (SOUND_MIXER_CD, cmix (l.cd)); + pas_mixer_set (SOUND_MIXER_MIC, ((l.mic * 100 + 3) / 7) | (((l.mic * 100 + 3) / 7) << 8)); + return (0); +} + +/* + * This sets aspects of the Mixer that are not volume levels. (Recording + * source, filter level, I/O filtering, and stereo.) + */ +static int +mixer_set_params (struct sb_mixer_params *user_p) +{ + struct sb_mixer_params p; + S_BYTE val; + int src; + unsigned long flags; + + IOCTL_FROM_USER ((char *) &p, (char *) user_p, 0, sizeof (p)); + + if (p.record_source != SRC_MIC + && p.record_source != SRC_CD + && p.record_source != SRC_LINE) + return (RET_ERROR (EINVAL)); + + /* + * I'm not sure if this is The Right Thing. Should stereo be entirely + * under control of DSP? I like being able to toggle it while a sound is + * playing, so I do this... because I can. + */ + + DISABLE_INTR (flags); + + val = (pas_read (PCM_CONTROL) & ~P_C_MIXER_CROSS_FIELD) | P_C_MIXER_CROSS_R_TO_R | P_C_MIXER_CROSS_L_TO_L; + if (!p.dsp_stereo) + val |= (P_C_MIXER_CROSS_R_TO_L | P_C_MIXER_CROSS_L_TO_R); /* Mono */ + pas_write (val, PCM_CONTROL); + + RESTORE_INTR (flags); + + switch (p.record_source) + { + case SRC_CD: + src = SOUND_MASK_CD; + break; + + case SRC_LINE: + src = SOUND_MASK_LINE; + break; + + default: + src = SOUND_MASK_MIC; + break; + } + + pas_mixer_set (SOUND_MIXER_RECSRC, src); + + /* + * setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC) | + * (p.filter_output ? FILT_ON : FILT_OFF))); + */ + return (0); +} + +static int +getmixer (int dev, int chn) +{ + if (chn == P_M_MV508_RIGHT) + { + return (levels[dev] >> 8) & 0x7f; + } + else + { + return levels[dev] & 0x7f; + } +} + +/* Read the current mixer level settings into the user's struct. */ +static int +mixer_get_levels (struct sb_mixer_levels *user_l) +{ + + struct sb_mixer_levels l; + + l.master.r = ((((levels[SOUND_MIXER_VOLUME] >> 8) & 0x7f) * 15) + 50) / 100; /* Master */ + l.master.l = (((levels[SOUND_MIXER_VOLUME] & 0x7f) * 15) + 50) / 100; /* Master */ + + l.line.r = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_RIGHT) * 15) + 50) / 100; /* Line */ + l.line.l = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.voc.r = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_RIGHT) * 15) + 50) / 100; /* DAC */ + l.voc.l = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.fm.r = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_RIGHT) * 15) + 50) / 100; /* FM */ + l.fm.l = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.cd.r = ((getmixer (SOUND_MIXER_CD, P_M_MV508_RIGHT) * 15) + 50) / 100; /* CD */ + l.cd.l = ((getmixer (SOUND_MIXER_CD, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.mic = ((getmixer (SOUND_MIXER_MIC, P_M_MV508_LEFT) * 7) + 50) / 100; /* Microphone */ + + IOCTL_TO_USER ((char *) user_l, 0, (char *) &l, sizeof (l)); + return (0); +} + +/* Read the current mixer parameters into the user's struct. */ +static int +mixer_get_params (struct sb_mixer_params *user_params) +{ + S_BYTE val; + struct sb_mixer_params params; + + switch (rec_devices) + { + case SOUND_MASK_CD: + params.record_source = SRC_CD; + break; + + case SOUND_MASK_LINE: + params.record_source = SRC_LINE; + break; + + case SOUND_MASK_MIC: + params.record_source = SRC_MIC; + break; + + default: + params.record_source = SRC_MIC; + pas_mixer_set (SOUND_MIXER_RECSRC, SOUND_MASK_MIC); /* Adjust */ + } + + params.hifreq_filter = OFF; + params.filter_input = OFF; + params.filter_output = OFF; + + val = INB (PCM_CONTROL); + params.dsp_stereo = ((val & P_C_MIXER_CROSS_FIELD) == (P_C_MIXER_CROSS_L_TO_L | P_C_MIXER_CROSS_R_TO_R)); + + IOCTL_TO_USER ((char *) user_params, 0, (char *) ¶ms, sizeof (params)); + return (0); +} + +/*****/ + +static void +pas_mixer_reset (void) +{ + int foo; + + TRACE (printk ("pas2_mixer.c: void pas_mixer_reset(void)\n")); + + for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++) + pas_mixer_set (foo, levels[foo]); + + set_mode (P_M_MV508_LOUDNESS | P_M_MV508_ENHANCE_40); +} + +int +pas_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) +{ + TRACE (printk ("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); + + if (((cmd >> 8) & 0xff) == 'M') + { + if (cmd & IOC_IN) + return IOCTL_OUT (arg, pas_mixer_set (cmd & 0xff, IOCTL_IN (arg))); + else + { /* Read parameters */ + + switch (cmd & 0xff) + { + + case SOUND_MIXER_RECSRC: + return IOCTL_OUT (arg, rec_devices); + break; + + case SOUND_MIXER_STEREODEVS: + return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES & ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE)); + break; + + case SOUND_MIXER_DEVMASK: + return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES); + break; + + case SOUND_MIXER_RECMASK: + return IOCTL_OUT (arg, POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES); + break; + + case SOUND_MIXER_CAPS: + return IOCTL_OUT (arg, 0); /* No special capabilities */ + break; + + case SOUND_MIXER_MUTE: + return IOCTL_OUT (arg, 0); /* No mute yet */ + break; + + case SOUND_MIXER_ENHANCE: + if (!(mode_control & P_M_MV508_ENHANCE_BITS)) + return IOCTL_OUT (arg, 0); + return IOCTL_OUT (arg, ((mode_control & P_M_MV508_ENHANCE_BITS) + 1) * 20); + break; + + case SOUND_MIXER_LOUD: + if (mode_control & P_M_MV508_LOUDNESS) + return IOCTL_OUT (arg, 1); + return IOCTL_OUT (arg, 0); + break; + + default: + return IOCTL_OUT (arg, levels[cmd & 0xff]); + } + } + } + else + { + switch (cmd) + { + case MIXER_IOCTL_SET_LEVELS: + mixer_set_levels ((struct sb_mixer_levels *) arg); + return mixer_get_levels ((struct sb_mixer_levels *) arg); + case MIXER_IOCTL_SET_PARAMS: + mixer_set_params ((struct sb_mixer_params *) arg); + return mixer_get_params ((struct sb_mixer_params *) arg); + case MIXER_IOCTL_READ_LEVELS: + return mixer_get_levels ((struct sb_mixer_levels *) arg); + case MIXER_IOCTL_READ_PARAMS: + return mixer_get_params ((struct sb_mixer_params *) arg); + case MIXER_IOCTL_RESET: + pas_mixer_reset (); + return (0); + default: + return RET_ERROR (EINVAL); + } + } + return RET_ERROR (EINVAL); +} + +static struct mixer_operations pas_mixer_operations = +{ + pas_mixer_ioctl +}; + +int +pas_init_mixer (void) +{ + pas_mixer_reset (); + + mixer_devs[num_mixers++] = &pas_mixer_operations; + return 1; +} + +#endif diff --git a/sys/i386/isa/sound/pas2_pcm.c b/sys/i386/isa/sound/pas2_pcm.c new file mode 100644 index 0000000..1eef59b --- /dev/null +++ b/sys/i386/isa/sound/pas2_pcm.c @@ -0,0 +1,430 @@ +#define _PAS2_PCM_C_ +/* + * sound/pas2_pcm.c + * + * The low level driver for the Pro Audio Spectrum ADC/DAC. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "pas.h" + +#if !defined(EXCLUDE_PAS) && !defined(EXCLUDE_AUDIO) + +#define TRACE(WHAT) /* (WHAT) */ + +#define PAS_PCM_INTRBITS (0x08) +/* Sample buffer timer interrupt enable */ + +#define PCM_NON 0 +#define PCM_DAC 1 +#define PCM_ADC 2 + +static unsigned long pcm_speed = 0; /* sampling rate */ +static unsigned char pcm_channels = 1; /* channels/sample (1 or 2) */ +static unsigned char pcm_bits = 8; /* bits/sample (8 or 16) */ +static unsigned char pcm_filter = 0; /* filter FLAG */ +static unsigned char pcm_mode = PCM_NON; +static unsigned long pcm_count = 0; +static unsigned short pcm_bitsok = 8; /* mask of OK bits */ +static int my_devnum = 0; + +int +pcm_set_speed (int arg) +{ + int foo, tmp; + unsigned long flags; + + if (arg > 44100) + arg = 44100; + if (arg < 5000) + arg = 5000; + + foo = 1193180 / arg; + arg = 1193180 / foo; + + if (pcm_channels & 2) + foo = foo >> 1; + + pcm_speed = arg; + + tmp = pas_read (FILTER_FREQUENCY); + + DISABLE_INTR (flags); + + pas_write (tmp & ~(F_F_PCM_RATE_COUNTER | F_F_PCM_BUFFER_COUNTER), FILTER_FREQUENCY); + pas_write (S_C_C_SAMPLE_RATE | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write (foo & 0xff, SAMPLE_RATE_TIMER); + pas_write ((foo >> 8) & 0xff, SAMPLE_RATE_TIMER); + pas_write (tmp, FILTER_FREQUENCY); + + RESTORE_INTR (flags); + + return pcm_speed; +} + +int +pcm_set_channels (int arg) +{ + + if ((arg != 1) && (arg != 2)) + return pcm_channels; + + if (arg != pcm_channels) + { + pas_write (pas_read (PCM_CONTROL) ^ P_C_PCM_MONO, PCM_CONTROL); + + pcm_channels = arg; + pcm_set_speed (pcm_speed);/* The speed must be reinitialized */ + } + + return pcm_channels; +} + +int +pcm_set_bits (int arg) +{ + if ((arg & pcm_bitsok) != arg) + return pcm_bits; + + if (arg != pcm_bits) + { + pas_write (pas_read (SYSTEM_CONFIGURATION_2) ^ S_C_2_PCM_16_BIT, SYSTEM_CONFIGURATION_2); + + pcm_bits = arg; + } + + return pcm_bits; +} + +static int +pas_pcm_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + TRACE (printk ("pas2_pcm.c: static int pas_pcm_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); + + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return pcm_set_speed (arg); + return IOCTL_OUT (arg, pcm_set_speed (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return pcm_speed; + return IOCTL_OUT (arg, pcm_speed); + break; + + case SNDCTL_DSP_STEREO: + if (local) + return pcm_set_channels (arg + 1) - 1; + return IOCTL_OUT (arg, pcm_set_channels (IOCTL_IN (arg) + 1) - 1); + break; + + case SOUND_PCM_WRITE_CHANNELS: + if (local) + return pcm_set_channels (arg); + return IOCTL_OUT (arg, pcm_set_channels (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return pcm_channels; + return IOCTL_OUT (arg, pcm_channels); + break; + + case SNDCTL_DSP_SAMPLESIZE: + if (local) + return pcm_set_bits (arg); + return IOCTL_OUT (arg, pcm_set_bits (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_BITS: + if (local) + return pcm_bits; + return IOCTL_OUT (arg, pcm_bits); + + case SOUND_PCM_WRITE_FILTER: /* NOT YET IMPLEMENTED */ + if (IOCTL_IN (arg) > 1) + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + break; + + pcm_filter = IOCTL_IN (arg); + case SOUND_PCM_READ_FILTER: + return IOCTL_OUT (arg, pcm_filter); + break; + + default: + return RET_ERROR (EINVAL); + } + + return RET_ERROR (EINVAL); +} + +static void +pas_pcm_reset (int dev) +{ + TRACE (printk ("pas2_pcm.c: static void pas_pcm_reset(void)\n")); + + pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE, PCM_CONTROL); +} + +static int +pas_pcm_open (int dev, int mode) +{ + int err; + + TRACE (printk ("pas2_pcm.c: static int pas_pcm_open(int mode = %X)\n", mode)); + + if ((err = pas_set_intr (PAS_PCM_INTRBITS)) < 0) + return err; + + if (!DMAbuf_open_dma (dev)) + { + pas_remove_intr (PAS_PCM_INTRBITS); + return RET_ERROR (EBUSY); + } + + pcm_count = 0; + + return 0; +} + +static void +pas_pcm_close (int dev) +{ + unsigned long flags; + + TRACE (printk ("pas2_pcm.c: static void pas_pcm_close(void)\n")); + + DISABLE_INTR (flags); + + pas_pcm_reset (dev); + DMAbuf_close_dma (dev); + pas_remove_intr (PAS_PCM_INTRBITS); + pcm_mode = PCM_NON; + + RESTORE_INTR (flags); +} + +static void +pas_pcm_output_block (int dev, unsigned long buf, int count, + int intrflag, int restart_dma) +{ + unsigned long flags, cnt; + + TRACE (printk ("pas2_pcm.c: static void pas_pcm_output_block(char *buf = %P, int count = %X)\n", buf, count)); + + cnt = count; + if (sound_dsp_dmachan[dev] > 3) + cnt >>= 1; + + if (sound_dma_automode[dev] && + intrflag && + cnt == pcm_count) + return; /* Auto mode on. No need to react */ + + DISABLE_INTR (flags); + + pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE, + PCM_CONTROL); + + if (restart_dma) + DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + + if (count != pcm_count) + { + pas_write (pas_read (FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + pas_write (S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write (count & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write ((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + + pcm_count = count; + } + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY); + pas_write (pas_read (PCM_CONTROL) | P_C_PCM_ENABLE | P_C_PCM_DAC_MODE, PCM_CONTROL); + + pcm_mode = PCM_DAC; + + RESTORE_INTR (flags); +} + +static void +pas_pcm_start_input (int dev, unsigned long buf, int count, + int intrflag, int restart_dma) +{ + unsigned long flags; + int cnt; + + TRACE (printk ("pas2_pcm.c: static void pas_pcm_start_input(char *buf = %P, int count = %X)\n", buf, count)); + + cnt = count; + if (sound_dsp_dmachan[dev] > 3) + cnt >>= 1; + + if (sound_dma_automode[my_devnum] && + intrflag && + cnt == pcm_count) + return; /* Auto mode on. No need to react */ + + DISABLE_INTR (flags); + + if (restart_dma) + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + + if (count != pcm_count) + { + pas_write (pas_read (FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + pas_write (S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write (count & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write ((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + + pcm_count = count; + } + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY); + pas_write ((pas_read (PCM_CONTROL) | P_C_PCM_ENABLE) & ~P_C_PCM_DAC_MODE, PCM_CONTROL); + + pcm_mode = PCM_ADC; + + RESTORE_INTR (flags); +} + +static int +pas_pcm_prepare_for_input (int dev, int bsize, int bcount) +{ + return 0; +} +static int +pas_pcm_prepare_for_output (int dev, int bsize, int bcount) +{ + return 0; +} + +static struct audio_operations pas_pcm_operations = +{ + "Pro Audio Spectrum", + NOTHING_SPECIAL, + pas_pcm_open, + pas_pcm_close, + pas_pcm_output_block, + pas_pcm_start_input, + pas_pcm_ioctl, + pas_pcm_prepare_for_input, + pas_pcm_prepare_for_output, + pas_pcm_reset, + pas_pcm_reset, /* halt_xfer */ + NULL, /* has_output_drained */ + NULL /* copy_from_user */ +}; + +long +pas_pcm_init (long mem_start, struct address_info *hw_config) +{ + TRACE (printk ("pas2_pcm.c: long pas_pcm_init(long mem_start = %X)\n", mem_start)); + + pcm_bitsok = 8; + if (pas_read (OPERATION_MODE_1) & O_M_1_PCM_TYPE) + pcm_bitsok |= 16; + + pcm_set_speed (DSP_DEFAULT_SPEED); + + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[my_devnum = num_dspdevs++] = &pas_pcm_operations; + sound_dsp_dmachan[my_devnum] = hw_config->dma; +#ifndef NO_AUTODMA + if (hw_config->dma > 3) + { + sound_buffcounts[my_devnum] = 1; + sound_buffsizes[my_devnum] = 2 * 65536; + sound_dma_automode[my_devnum] = 1; + } + else + { + sound_buffcounts[my_devnum] = 1; + sound_buffsizes[my_devnum] = DSP_BUFFSIZE; + sound_dma_automode[my_devnum] = 1; + } +#else + sound_buffcounts[my_devnum] = DSP_BUFFCOUNT; + sound_buffsizes[my_devnum] = DSP_BUFFSIZE; + sound_dma_automode[my_devnum] = 0; +#endif + } + else + printk ("PAS2: Too many PCM devices available\n"); + + return mem_start; +} + +void +pas_pcm_interrupt (unsigned char status, int cause) +{ + if (cause == 1) /* PCM buffer done */ + { + /* + * Halt the PCM first. Otherwise we don't have time to start a new + * block before the PCM chip proceeds to the next sample + */ + + if (!sound_dma_automode[my_devnum]) + { + pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE, + PCM_CONTROL); + } + + switch (pcm_mode) + { + + case PCM_DAC: + DMAbuf_outputintr (my_devnum, 1); + break; + + case PCM_ADC: + DMAbuf_inputintr (my_devnum); + break; + + default: + printk ("PAS: Unexpected PCM interrupt\n"); + } + } +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/patmgr.c b/sys/i386/isa/sound/patmgr.c new file mode 100644 index 0000000..f575437 --- /dev/null +++ b/sys/i386/isa/sound/patmgr.c @@ -0,0 +1,263 @@ +/* + * sound/patmgr.c + * + * The patch maneger interface for the /dev/sequencer + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#define PATMGR_C +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SEQUENCER) + +DEFINE_WAIT_QUEUES (server_procs[MAX_SYNTH_DEV], + server_wait_flag[MAX_SYNTH_DEV]); + +static struct patmgr_info *mbox[MAX_SYNTH_DEV] = +{NULL}; +static volatile int msg_direction[MAX_SYNTH_DEV] = +{0}; + +static int pmgr_opened[MAX_SYNTH_DEV] = +{0}; + +#define A_TO_S 1 +#define S_TO_A 2 + +DEFINE_WAIT_QUEUE (appl_proc, appl_wait_flag); + +int +pmgr_open (int dev) +{ + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (pmgr_opened[dev]) + return RET_ERROR (EBUSY); + pmgr_opened[dev] = 1; + + RESET_WAIT_QUEUE (server_procs[dev], server_wait_flag[dev]); + + return 0; +} + +void +pmgr_release (int dev) +{ + + if (mbox[dev]) /* Killed in action. Inform the client */ + { + + mbox[dev]->key = PM_ERROR; + mbox[dev]->parm1 = RET_ERROR (EIO); + + if (SOMEONE_WAITING (appl_proc, appl_wait_flag)) + WAKE_UP (appl_proc, appl_wait_flag); + } + + pmgr_opened[dev] = 0; +} + +int +pmgr_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + unsigned long flags; + int ok = 0; + + if (count != sizeof (struct patmgr_info)) + { + printk ("PATMGR%d: Invalid read count\n", dev); + return RET_ERROR (EIO); + } + + while (!ok && !PROCESS_ABORTING (server_procs[dev], server_wait_flag[dev])) + { + DISABLE_INTR (flags); + + while (!(mbox[dev] && msg_direction[dev] == A_TO_S) && + !PROCESS_ABORTING (server_procs[dev], server_wait_flag[dev])) + { + DO_SLEEP (server_procs[dev], server_wait_flag[dev], 0); + } + + if (mbox[dev] && msg_direction[dev] == A_TO_S) + { + COPY_TO_USER (buf, 0, (char *) mbox[dev], count); + msg_direction[dev] = 0; + ok = 1; + } + + RESTORE_INTR (flags); + + } + + if (!ok) + return RET_ERROR (EINTR); + return count; +} + +int +pmgr_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + unsigned long flags; + + if (count < 4) + { + printk ("PATMGR%d: Write count < 4\n", dev); + return RET_ERROR (EIO); + } + + COPY_FROM_USER (mbox[dev], buf, 0, 4); + + if (*(unsigned char *) mbox[dev] == SEQ_FULLSIZE) + { + int tmp_dev; + + tmp_dev = ((unsigned short *) mbox[dev])[2]; + if (tmp_dev != dev) + return RET_ERROR (ENXIO); + + return synth_devs[dev]->load_patch (dev, *(unsigned short *) mbox[dev], + buf, 4, count, 1); + } + + if (count != sizeof (struct patmgr_info)) + { + printk ("PATMGR%d: Invalid write count\n", dev); + return RET_ERROR (EIO); + } + + /* + * If everything went OK, there should be a preallocated buffer in the + * mailbox and a client waiting. + */ + + DISABLE_INTR (flags); + + if (mbox[dev] && !msg_direction[dev]) + { + COPY_FROM_USER (&((char *) mbox[dev])[4], buf, 4, count - 4); + msg_direction[dev] = S_TO_A; + + if (SOMEONE_WAITING (appl_proc, appl_wait_flag)) + { + WAKE_UP (appl_proc, appl_wait_flag); + } + } + + RESTORE_INTR (flags); + + return count; +} + +int +pmgr_access (int dev, struct patmgr_info *rec) +{ + unsigned long flags; + int err = 0; + + DISABLE_INTR (flags); + + if (mbox[dev]) + printk (" PATMGR: Server %d mbox full. Why?\n", dev); + else + { + rec->key = PM_K_COMMAND; + mbox[dev] = rec; + msg_direction[dev] = A_TO_S; + + if (SOMEONE_WAITING (server_procs[dev], server_wait_flag[dev])) + { + WAKE_UP (server_procs[dev], server_wait_flag[dev]); + } + + DO_SLEEP (appl_proc, appl_wait_flag, 0); + + if (msg_direction[dev] != S_TO_A) + { + rec->key = PM_ERROR; + rec->parm1 = RET_ERROR (EIO); + } + else if (rec->key == PM_ERROR) + { + err = rec->parm1; + if (err > 0) + err = -err; + } + + mbox[dev] = NULL; + msg_direction[dev] = 0; + } + + RESTORE_INTR (flags); + + return err; +} + +int +pmgr_inform (int dev, int event, unsigned long p1, unsigned long p2, + unsigned long p3, unsigned long p4) +{ + unsigned long flags; + int err = 0; + + if (!pmgr_opened[dev]) + return 0; + + DISABLE_INTR (flags); + + if (mbox[dev]) + printk (" PATMGR: Server %d mbox full. Why?\n", dev); + else + { + mbox[dev] = + (struct patmgr_info *) KERNEL_MALLOC (sizeof (struct patmgr_info)); + + mbox[dev]->key = PM_K_EVENT; + mbox[dev]->command = event; + mbox[dev]->parm1 = p1; + mbox[dev]->parm2 = p2; + mbox[dev]->parm3 = p3; + msg_direction[dev] = A_TO_S; + + if (SOMEONE_WAITING (server_procs[dev], server_wait_flag[dev])) + { + WAKE_UP (server_procs[dev], server_wait_flag[dev]); + } + + DO_SLEEP (appl_proc, appl_wait_flag, 0); + if (mbox[dev]) + KERNEL_FREE (mbox[dev]); + mbox[dev] = NULL; + msg_direction[dev] = 0; + } + + RESTORE_INTR (flags); + + return err; +} + +#endif diff --git a/sys/i386/isa/sound/pro_midi.c b/sys/i386/isa/sound/pro_midi.c new file mode 100644 index 0000000..93c1937 --- /dev/null +++ b/sys/i386/isa/sound/pro_midi.c @@ -0,0 +1,188 @@ +/* + * Copyright by UWM - comments to soft-eng@cs.uwm.edu + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ +#define ALL_EXTERNAL_TO_ME +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "pas.h" +#define ESUCCESS 0 + +#if !defined(EXCLUDE_PRO_MIDI) && !defined(EXCLUDE_CHIP_MIDI) + + +/** Structure for handling operations **/ + + +static struct generic_midi_operations pro_midi_operations = +{ + + {"Pro_Audio_Spectrum 16 MV101", 0}, + pro_midi_open, + pro_midi_close, + pro_midi_write, + pro_midi_read +}; + +/* + * Note! Note! Note! Follow the same model for any other attach function you + * may write + */ + +long +pro_midi_attach (long mem_start) +{ + pro_midi_dev = num_generic_midis; + generic_midi_devs[num_generic_midis++] = &pro_midi_operations; + return mem_start; +} + +int +pro_midi_open (int dev, int mode) +{ + + int intr_mask, s; + + + s = splhigh (); + + + /* Reset the input and output FIFO pointers */ + + + outb (MIDI_CONTROL, M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO); + + /* Get the interrupt status */ + + intr_mask = inb (INTERRUPT_MASK); + + + /* Enable MIDI IRQ */ + + intr_mask |= I_M_MIDI_IRQ_ENABLE; + outb (INTERRUPT_MASK, intr_mask); + + + /* Enable READ/WRITE on MIDI port. This part is quite unsure though */ + + outb (MIDI_CONTROL, M_C_ENA_OUTPUT_IRQ | M_C_ENA_INPUT_IRQ); + + /* Acknowledge pending interrupts */ + + outb (MIDI_STATUS, 0xff); + + + splx (s); + + return (ESUCCESS); + + +} + + +void +pro_midi_close (int dev) +{ + + int intr_mask; + + /* Clean up */ + + outb (MIDI_CONTROL, M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO); + intr_mask = inb (INTERRUPT_MASK); + intr_mask &= ~I_M_MIDI_IRQ_ENABLE; + outb (INTERRUPT_MASK, intr_mask); + + return; +} + +int +pro_midi_write (int dev, struct uio *uio) +{ + + int s; + unsigned char data; + + /* printf("midi: Going to do write routine..\n"); */ + while (uio->uio_resid) + { + + if (uiomove (&data, 1, uio)) + return (ENOTTY); + + s = splhigh (); + + DELAY (30); + outb (MIDI_DATA, data); + DELAY (70); /* Ze best pause.. find a better one if you + * can :) */ + splx (s); + } + + return (ESUCCESS); + +} + + +int +pro_midi_read (int dev, struct uio *uio) +{ + + int s; + unsigned char data; + + s = splhigh (); + + /* For each uio_iov[] entry .... */ + + while (uio->uio_resid) + { + + if (((inb (MIDI_STATUS) & M_S_INPUT_AVAIL) == 0) && + ((inb (MIDI_FIFO_STATUS) & MIDI_INPUT_AVAILABLE) == 0)) + + data = 0xfe; + else + data = inb (MIDI_DATA); + + if (uiomove (&data, 1, uio)) + { + + printf ("midi: Bad copyout()!\n"); + return (ENOTTY); + + } + + } + splx (s); + return (ESUCCESS); + +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/sb.h b/sys/i386/isa/sound/sb.h new file mode 100644 index 0000000..c701bc1 --- /dev/null +++ b/sys/i386/isa/sound/sb.h @@ -0,0 +1,31 @@ +/* + * $Id$ + */ +#define DSP_RESET (sbc_base + 0x6) +#define DSP_READ (sbc_base + 0xA) +#define DSP_WRITE (sbc_base + 0xC) +#define DSP_COMMAND (sbc_base + 0xC) +#define DSP_STATUS (sbc_base + 0xC) +#define DSP_DATA_AVAIL (sbc_base + 0xE) +#define DSP_DATA_AVL16 (sbc_base + 0xF) +#define MIXER_ADDR (sbc_base + 0x4) +#define MIXER_DATA (sbc_base + 0x5) +#define OPL3_LEFT (sbc_base + 0x0) +#define OPL3_RIGHT (sbc_base + 0x2) +#define OPL3_BOTH (sbc_base + 0x8) +/* DSP Commands */ + +#define DSP_CMD_SPKON 0xD1 +#define DSP_CMD_SPKOFF 0xD3 +#define DSP_CMD_DMAON 0xD0 +#define DSP_CMD_DMAOFF 0xD4 + +#define IMODE_NONE 0 +#define IMODE_OUTPUT 1 +#define IMODE_INPUT 2 +#define IMODE_INIT 3 +#define IMODE_MIDI 4 + +#define NORMAL_MIDI 0 +#define UART_MIDI 1 + diff --git a/sys/i386/isa/sound/sb16_dsp.c b/sys/i386/isa/sound/sb16_dsp.c new file mode 100644 index 0000000..20c597d --- /dev/null +++ b/sys/i386/isa/sound/sb16_dsp.c @@ -0,0 +1,628 @@ +/* + * sound/sb16_dsp.c + * + * The low level driver for the SoundBlaster DSP chip. + * + * (C) 1993 J. Schubert (jsb@sth.ruhr-uni-bochum.de) + * + * based on SB-driver by (C) Hannu Savolainen + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#define DEB(x) +#define DEB1(x) +/* + #define DEB_DMARES + */ +#include "sound_config.h" +#include "sb.h" +#include "sb_mixer.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB16) && !defined(EXCLUDE_SB) && !defined(EXCLUDE_AUDIO) && !defined(EXCLUDE_SBPRO) + +extern int sbc_base, sbc_minor, sbc_major; + +static int sb16_dsp_ok = 0;/* Set to 1 after successful initialization */ +static int dsp_16bit = 0; +static int dsp_stereo = 0; +static int dsp_current_speed = 8000; /*DSP_DEFAULT_SPEED; */ +static int dsp_busy = 0; +static int dma16, dma8; +static unsigned long dsp_count = 0; + +static int irq_mode = IMODE_NONE; /* IMODE_INPUT, IMODE_OUTPUT or + + IMODE_NONE */ +static int my_dev = 0; + +static volatile int intr_active = 0; + +static int sb16_dsp_open (int dev, int mode); +static void sb16_dsp_close (int dev); +static void sb16_dsp_output_block (int dev, unsigned long buf, int count, int intrflag, int dma_restart); +static void sb16_dsp_start_input (int dev, unsigned long buf, int count, int intrflag, int dma_restart); +static int sb16_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg, int local); +static int sb16_dsp_prepare_for_input (int dev, int bsize, int bcount); +static int sb16_dsp_prepare_for_output (int dev, int bsize, int bcount); +static void sb16_dsp_reset (int dev); +static void sb16_dsp_halt (int dev); +static int dsp_set_speed (int); +static int dsp_set_stereo (int); +static void dsp_cleanup (void); +int sb_reset_dsp (void); + +static struct audio_operations sb16_dsp_operations = +{ + "SoundBlaster 16", + NOTHING_SPECIAL, + sb16_dsp_open, + sb16_dsp_close, + sb16_dsp_output_block, + sb16_dsp_start_input, + sb16_dsp_ioctl, + sb16_dsp_prepare_for_input, + sb16_dsp_prepare_for_output, + sb16_dsp_reset, + sb16_dsp_halt, + NULL, + NULL +}; + +static int +sb_dsp_command01 (unsigned char val) +{ + int i = 1 << 16; + + while (--i & (!INB (DSP_STATUS) & 0x80)); + if (!i) + printk ("SB16 sb_dsp_command01 Timeout\n"); + return sb_dsp_command (val); +} + +static int +wait_data_avail (unsigned long t) +{ + int loopc = 5000000; + + t += GET_TIME (); + do + { + if (INB (DSP_DATA_AVAIL) & 0x80) + return 1; + } + while (--loopc && GET_TIME () < t); + printk ("!data_avail l=%d\n", loopc); + return 0; +} + +static int +read_dsp (int t) +{ + if (!wait_data_avail ((unsigned long) t)) + return -1; + else + return INB (DSP_READ); +} + +static int +dsp_ini2 (void) +{ +#if 0 + /* sb_setmixer(0x83, sb_getmixer(0x83) | 0x03); */ + sb_dsp_command (0xe2); + sb_dsp_command (0x76); /* E0 ??? */ + sb_dsp_command (0xe2); + sb_dsp_command (0x30); /* A0 ??? */ + sb_dsp_command (0xe4); + sb_dsp_command (0xaa); + sb_dsp_command (0xe8); + if (read_dsp (100) != 0xaa) + printk ("Error dsp_ini2\n"); +#endif + return 0; +} + +/* + static char *dsp_getmessage(unsigned char command,int maxn) + { + static char buff[100]; + int n=0; + + sb_dsp_command(command); + while(n<maxn && wait_data_avail(2L)) { + buff[++n]=INB(DSP_READ); + if(!buff[n]) + break; + } + buff[0]=n; + return buff; + } + + static void dsp_showmessage(unsigned char command,int len) + { + int n; + unsigned char *c; + c=dsp_getmessage(command,len); + printk("DSP C=%x l=%d,lr=%d b=",command,len,c[0]); + for(n=1;n<=c[0];n++) + if(c[n]>=' ' & c[n]<='z') + printk("%c",c[n]); + else + printk("|%x|",c[n]); + printk("\n"); + } + */ +static int +dsp_set_speed (int mode) +{ + DEB (printk ("dsp_set_speed(%d)\n", mode)); + if (mode) + { + if (mode < 5000) + mode = 5000; + if (mode > 44100) + mode = 44100; + dsp_current_speed = mode; + } + return mode; +} + +static int +dsp_set_stereo (int mode) +{ + DEB (printk ("dsp_set_stereo(%d)\n", mode)); + + dsp_stereo = mode; + + return mode; +} + +static int +dsp_set_bits (int arg) +{ + DEB (printk ("dsp_set_bits(%d)\n", arg)); + + if (arg) + switch (arg) + { + case 8: + dsp_16bit = 0; + break; + case 16: + dsp_16bit = 1; + break; + default: + return RET_ERROR (EINVAL); + } + return dsp_16bit ? 16 : 8; +} + +static int +sb16_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return dsp_set_speed (arg); + return IOCTL_OUT (arg, dsp_set_speed (IOCTL_IN (arg))); + + case SOUND_PCM_READ_RATE: + if (local) + return dsp_current_speed; + return IOCTL_OUT (arg, dsp_current_speed); + + case SNDCTL_DSP_STEREO: + if (local) + return dsp_set_stereo (arg); + return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg))); + + case SOUND_PCM_WRITE_CHANNELS: + if (local) + return dsp_set_stereo (arg - 1) + 1; + return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg) - 1) + 1); + + case SOUND_PCM_READ_CHANNELS: + if (local) + return dsp_stereo + 1; + return IOCTL_OUT (arg, dsp_stereo + 1); + + case SNDCTL_DSP_SAMPLESIZE: + if (local) + return dsp_set_bits (arg); + return IOCTL_OUT (arg, dsp_set_bits (IOCTL_IN (arg))); + + case SOUND_PCM_READ_BITS: + if (local) + return dsp_16bit ? 16 : 8; + return IOCTL_OUT (arg, dsp_16bit ? 16 : 8); + + case SOUND_PCM_WRITE_FILTER: /* NOT YET IMPLEMENTED */ + if (IOCTL_IN (arg) > 1) + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + default: + return RET_ERROR (EINVAL); + } + + return RET_ERROR (EINVAL); +} + +static int +sb16_dsp_open (int dev, int mode) +{ + int retval; + + DEB (printk ("sb16_dsp_open()\n")); + if (!sb16_dsp_ok) + { + printk ("SB16 Error: SoundBlaster board not installed\n"); + return RET_ERROR (ENXIO); + } + + if (intr_active) + return RET_ERROR (EBUSY); + + retval = sb_get_irq (); + if (retval < 0) + return retval; + + if (ALLOC_DMA_CHN (dma8)) + { + printk ("SB16: Unable to grab DMA%d\n", dma8); + sb_free_irq (); + return RET_ERROR (EBUSY); + } + + if (dma16 != dma8) + if (ALLOC_DMA_CHN (dma16)) + { + printk ("SB16: Unable to grab DMA%d\n", dma16); + sb_free_irq (); + RELEASE_DMA_CHN (dma8); + return RET_ERROR (EBUSY); + } + + dsp_ini2 (); + + irq_mode = IMODE_NONE; + dsp_busy = 1; + + return 0; +} + +static void +sb16_dsp_close (int dev) +{ + unsigned long flags; + + DEB (printk ("sb16_dsp_close()\n")); + sb_dsp_command01 (0xd9); + sb_dsp_command01 (0xd5); + + DISABLE_INTR (flags); + RELEASE_DMA_CHN (dma8); + + if (dma16 != dma8) + RELEASE_DMA_CHN (dma16); + sb_free_irq (); + dsp_cleanup (); + dsp_busy = 0; + RESTORE_INTR (flags); +} + +static void +sb16_dsp_output_block (int dev, unsigned long buf, int count, int intrflag, int dma_restart) +{ + unsigned long flags, cnt; + + cnt = count; + if (dsp_16bit) + cnt >>= 1; + cnt--; + +#ifdef DEB_DMARES + printk ("output_block: %x %d %d\n", buf, count, intrflag); + if (intrflag) + { + int pos, chan = sound_dsp_dmachan[dev]; + + DISABLE_INTR (flags); + clear_dma_ff (chan); + disable_dma (chan); + pos = get_dma_residue (chan); + enable_dma (chan); + RESTORE_INTR (flags); + printk ("dmapos=%d %x\n", pos, pos); + } +#endif + if (sound_dma_automode[dev] && + intrflag && + cnt == dsp_count) + { + irq_mode = IMODE_OUTPUT; + intr_active = 1; + return; /* Auto mode on. No need to react */ + } + DISABLE_INTR (flags); + + if (dma_restart) + { + sb16_dsp_halt (dev); + DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); + } + sb_dsp_command (0x41); + sb_dsp_command ((unsigned char) ((dsp_current_speed >> 8) & 0xff)); + sb_dsp_command ((unsigned char) (dsp_current_speed & 0xff)); + sb_dsp_command ((unsigned char) (dsp_16bit ? 0xb6 : 0xc6)); + sb_dsp_command ((unsigned char) ((dsp_stereo ? 0x20 : 0) + + (dsp_16bit ? 0x10 : 0))); + sb_dsp_command01 ((unsigned char) (cnt & 0xff)); + sb_dsp_command ((unsigned char) (cnt >> 8)); + /* sb_dsp_command (0); + sb_dsp_command (0); */ + + RESTORE_INTR (flags); + dsp_count = cnt; + irq_mode = IMODE_OUTPUT; + intr_active = 1; +} + +static void +sb16_dsp_start_input (int dev, unsigned long buf, int count, int intrflag, int dma_restart) +{ + unsigned long flags, cnt; + + cnt = count; + if (dsp_16bit) + cnt >>= 1; + cnt--; + +#ifdef DEB_DMARES + printk ("start_input: %x %d %d\n", buf, count, intrflag); + if (intrflag) + { + int pos, chan = sound_dsp_dmachan[dev]; + + DISABLE_INTR (flags); + clear_dma_ff (chan); + disable_dma (chan); + pos = get_dma_residue (chan); + enable_dma (chan); + RESTORE_INTR (flags); + printk ("dmapos=%d %x\n", pos, pos); + } +#endif + if (sound_dma_automode[dev] && + intrflag && + cnt == dsp_count) + { + irq_mode = IMODE_INPUT; + intr_active = 1; + return; /* Auto mode on. No need to react */ + } + DISABLE_INTR (flags); + + if (dma_restart) + { + sb16_dsp_halt (dev); + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + } + + sb_dsp_command (0x42); + sb_dsp_command ((unsigned char) ((dsp_current_speed >> 8) & 0xff)); + sb_dsp_command ((unsigned char) (dsp_current_speed & 0xff)); + sb_dsp_command ((unsigned char) (dsp_16bit ? 0xbe : 0xce)); + sb_dsp_command ((unsigned char) ((dsp_stereo ? 0x20 : 0) + + (dsp_16bit ? 0x10 : 0))); + sb_dsp_command01 ((unsigned char) (cnt & 0xff)); + sb_dsp_command ((unsigned char) (cnt >> 8)); + + /* sb_dsp_command (0); + sb_dsp_command (0); */ + RESTORE_INTR (flags); + dsp_count = cnt; + irq_mode = IMODE_INPUT; + intr_active = 1; +} + +static int +sb16_dsp_prepare_for_input (int dev, int bsize, int bcount) +{ + sound_dsp_dmachan[my_dev] = dsp_16bit ? dma16 : dma8; + dsp_count = 0; + dsp_cleanup (); + return 0; +} + +static int +sb16_dsp_prepare_for_output (int dev, int bsize, int bcount) +{ + sound_dsp_dmachan[my_dev] = dsp_16bit ? dma16 : dma8; + dsp_count = 0; + dsp_cleanup (); + return 0; +} + +static void +dsp_cleanup (void) +{ + irq_mode = IMODE_NONE; + intr_active = 0; +} + +static void +sb16_dsp_reset (int dev) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + sb_reset_dsp (); + dsp_cleanup (); + + RESTORE_INTR (flags); +} + +static void +sb16_dsp_halt (int dev) +{ + if (dsp_16bit) + { + sb_dsp_command01 (0xd9); + sb_dsp_command01 (0xd5); + } + else + { + sb_dsp_command01 (0xda); + sb_dsp_command01 (0xd0); + } +} + +static void +set_irq_hw (int level) +{ + int ival; + + switch (level) + { + case 5: + ival = 2; + break; + case 7: + ival = 4; + break; + case 10: + ival = 8; + break; + default: + printk ("SB16_IRQ_LEVEL %d does not exist\n", level); + return; + } + sb_setmixer (IRQ_NR, ival); +} + +long +sb16_dsp_init (long mem_start, struct address_info *hw_config) +{ + if (sbc_major < 4) + return mem_start; + +#ifndef SCO + sprintf (sb16_dsp_operations.name, "SoundBlaster 16 %d.%d", sbc_major, sbc_minor); +#endif + +#ifdef __FreeBSD__ + printk ("snd6: <%s>", sb16_dsp_operations.name); +#else + printk (" <%s>", sb16_dsp_operations.name); +#endif + + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[my_dev = num_dspdevs++] = &sb16_dsp_operations; + sound_dsp_dmachan[my_dev] = hw_config->dma; +#ifndef NO_AUTODMA + sound_buffcounts[my_dev] = 1; + sound_dma_automode[my_dev] = 1; +#else + sound_buffcounts[my_dev] = DSP_BUFFCOUNT; + sound_dma_automode[my_dev] = 0; +#endif + sound_buffsizes[my_dev] = DSP_BUFFSIZE; + } + else + printk ("SB: Too many DSP devices available\n"); + sb16_dsp_ok = 1; + return mem_start; +} + +int +sb16_dsp_detect (struct address_info *hw_config) +{ + struct address_info *sb_config; + + if (sb16_dsp_ok) + return 1; /* Already initialized */ + + if (!(sb_config = sound_getconf (SNDCARD_SB))) + { + printk ("SB16 Error: Plain SB not configured\n"); + return 0; + } + + /* sb_setmixer(OPSW,0xf); + if(sb_getmixer(OPSW)!=0xf) + return 0; */ + + if (!sb_reset_dsp ()) + return 0; + + if (hw_config->dma < 4) + if (hw_config->dma != sb_config->dma) + { + printk ("SB16 Error: Invalid DMA channel %d/%d\n", + sb_config->dma, hw_config->dma); + return 0; + } + + dma16 = hw_config->dma; + dma8 = sb_config->dma; + set_irq_hw (sb_config->irq); + sb_setmixer (DMA_NR, (1 << hw_config->dma) | (1 << sb_config->dma)); + + DEB (printk ("SoundBlaster 16: IRQ %d DMA %d OK\n", sb_config->irq, hw_config->dma)); + + /* + dsp_showmessage(0xe3,99); + */ + sb16_dsp_ok = 1; + return 1; +} + +void +sb16_dsp_interrupt (int unused) +{ + int data; + + data = INB (DSP_DATA_AVL16); /* Interrupt acknowledge */ + + if (intr_active) + switch (irq_mode) + { + case IMODE_OUTPUT: + intr_active = 0; + DMAbuf_outputintr (my_dev, 1); + break; + + case IMODE_INPUT: + intr_active = 0; + DMAbuf_inputintr (my_dev); + break; + + default: + printk ("SoundBlaster: Unexpected interrupt\n"); + } +} + +#endif diff --git a/sys/i386/isa/sound/sb16_midi.c b/sys/i386/isa/sound/sb16_midi.c new file mode 100644 index 0000000..d15fe17 --- /dev/null +++ b/sys/i386/isa/sound/sb16_midi.c @@ -0,0 +1,288 @@ +/* + * sound/sb16_midi.c + * + * The low level driver for the MPU-401 UART emulation of the SB16. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#if !defined(EXCLUDE_SB) && !defined(EXCLUDE_SB16) && !defined(EXCLUDE_MIDI) + +#define DATAPORT (sb16midi_base) /* MPU-401 Data I/O Port on IBM */ +#define COMDPORT (sb16midi_base+1) /* MPU-401 Command Port on IBM */ +#define STATPORT (sb16midi_base+1) /* MPU-401 Status Port on IBM */ + +#define sb16midi_status() INB(STATPORT) +#define input_avail() (!(sb16midi_status()&INPUT_AVAIL)) +#define output_ready() (!(sb16midi_status()&OUTPUT_READY)) +#define sb16midi_cmd(cmd) OUTB(cmd, COMDPORT) +#define sb16midi_read() INB(DATAPORT) +#define sb16midi_write(byte) OUTB(byte, DATAPORT) + +#define OUTPUT_READY 0x40 /* Mask for Data Read Redy Bit */ +#define INPUT_AVAIL 0x80 /* Mask for Data Send Ready Bit */ +#define MPU_ACK 0xFE /* MPU-401 Acknowledge Response */ +#define MPU_RESET 0xFF /* MPU-401 Total Reset Command */ +#define UART_MODE_ON 0x3F /* MPU-401 "Dumb UART Mode" */ + +static int sb16midi_opened = 0; +static int sb16midi_base = 0x330; +static int sb16midi_detected = 0; +static int my_dev; + +static int reset_sb16midi (void); +static void (*midi_input_intr) (int dev, unsigned char data); + +extern int sbc_major; + +static void +sb16midi_input_loop (void) +{ + + while (input_avail ()) + { + unsigned char c = sb16midi_read (); + + if (sb16midi_opened & OPEN_READ) + midi_input_intr (my_dev, c); + } +} + +void +sb16midiintr (int unit) +{ + if (input_avail ()) + sb16midi_input_loop (); +} + +static int +sb16midi_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) +) +{ + if (sb16midi_opened) + { + return RET_ERROR (EBUSY); + } + + sb16midi_input_loop (); + + midi_input_intr = input; + sb16midi_opened = mode; + + return 0; +} + +static void +sb16midi_close (int dev) +{ + sb16midi_opened = 0; +} + +static int +sb16midi_out (int dev, unsigned char midi_byte) +{ + int timeout; + unsigned long flags; + + /* + * Test for input since pending input seems to block the output. + */ + + DISABLE_INTR (flags); + + if (input_avail ()) + sb16midi_input_loop (); + + RESTORE_INTR (flags); + + /* + * Sometimes it takes about 13000 loops before the output becomes ready + * (After reset). Normally it takes just about 10 loops. + */ + + for (timeout = 30000; timeout > 0 && !output_ready (); timeout--); /* Wait */ + + if (!output_ready ()) + { + printk ("MPU-401: Timeout\n"); + return 0; + } + + sb16midi_write (midi_byte); + return 1; +} + +static int +sb16midi_command (int dev, unsigned char midi_byte) +{ + return 1; +} + +static int +sb16midi_start_read (int dev) +{ + return 0; +} + +static int +sb16midi_end_read (int dev) +{ + return 0; +} + +static int +sb16midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +sb16midi_kick (int dev) +{ +} + +static int +sb16midi_buffer_status (int dev) +{ + return 0; /* No data in buffers */ +} + +static struct midi_operations sb16midi_operations = +{ + {"SoundBlaster MPU-401", 0, 0, SNDCARD_SB16MIDI}, + sb16midi_open, + sb16midi_close, + sb16midi_ioctl, + sb16midi_out, + sb16midi_start_read, + sb16midi_end_read, + sb16midi_kick, + sb16midi_command, + sb16midi_buffer_status +}; + + +long +attach_sb16midi (long mem_start, struct address_info *hw_config) +{ + int ok, timeout; + unsigned long flags; + + sb16midi_base = hw_config->io_base; + + if (!sb16midi_detected) + return RET_ERROR (EIO); + + DISABLE_INTR (flags); + for (timeout = 30000; timeout < 0 && !output_ready (); timeout--); /* Wait */ + sb16midi_cmd (UART_MODE_ON); + + ok = 0; + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (input_avail ()) + if (sb16midi_read () == MPU_ACK) + ok = 1; + + RESTORE_INTR (flags); + +#ifdef __FreeBSD__ + printk ("snd7: <SoundBlaster MPU-401>"); +#else + printk (" <SoundBlaster MPU-401>"); +#endif + + my_dev = num_midis; + midi_devs[num_midis++] = &sb16midi_operations; + return mem_start; +} + +static int +reset_sb16midi (void) +{ + unsigned long flags; + int ok, timeout, n; + + /* + * Send the RESET command. Try again if no success at the first time. + */ + + ok = 0; + + DISABLE_INTR (flags); + + for (n = 0; n < 2 && !ok; n++) + { + for (timeout = 30000; timeout < 0 && !output_ready (); timeout--); /* Wait */ + sb16midi_cmd (MPU_RESET); /* Send MPU-401 RESET Command */ + + /* + * Wait at least 25 msec. This method is not accurate so let's make the + * loop bit longer. Cannot sleep since this is called during boot. + */ + + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (input_avail ()) + if (sb16midi_read () == MPU_ACK) + ok = 1; + + } + + sb16midi_opened = 0; + if (ok) + sb16midi_input_loop (); /* Flush input before enabling interrupts */ + + RESTORE_INTR (flags); + + return ok; +} + + +int +probe_sb16midi (struct address_info *hw_config) +{ + int ok = 0; + + sb16midi_base = hw_config->io_base; + if (sbc_major < 4) + return 0; /* SB16 not detected */ + + if (sb_get_irq () < 0) + return 0; + + ok = reset_sb16midi (); + + sb16midi_detected = ok; + return ok; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/sb_card.c b/sys/i386/isa/sound/sb_card.c new file mode 100644 index 0000000..e56ebd3 --- /dev/null +++ b/sys/i386/isa/sound/sb_card.c @@ -0,0 +1,53 @@ +/* + * sound/sb_card.c + * + * Detection routine for the SoundBlaster cards. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB) + +long +attach_sb_card (long mem_start, struct address_info *hw_config) +{ +#if !defined(EXCLUDE_AUDIO) || !defined(EXCLUDE_MIDI) + if (!sb_dsp_detect (hw_config)) + return mem_start; + mem_start = sb_dsp_init (mem_start, hw_config); +#endif + + return mem_start; +} + +int +probe_sb (struct address_info *hw_config) +{ + return sb_dsp_detect (hw_config); +} + +#endif diff --git a/sys/i386/isa/sound/sb_dsp.c b/sys/i386/isa/sound/sb_dsp.c new file mode 100644 index 0000000..9e296c6 --- /dev/null +++ b/sys/i386/isa/sound/sb_dsp.c @@ -0,0 +1,786 @@ +/* + * sound/sb_dsp.c + * + * The low level driver for the SoundBlaster DSP chip. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Modified: + * Hunyue Yau Jan 6 1994 + * Added code to support Sound Galaxy NX Pro + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB) + +#include "sb.h" +#include "sb_mixer.h" +#undef SB_TEST_IRQ + +int sbc_base = 0; +static int sbc_irq = 0; +static int open_mode=0; + +/* + * The DSP channel can be used either for input or output. Variable + * 'sb_irq_mode' will be set when the program calls read or write first time + * after open. Current version doesn't support mode changes without closing + * and reopening the device. Support for this feature may be implemented in a + * future version of this driver. + */ + +int sb_dsp_ok = 0; /* Set to 1 after successful initialization */ +static int midi_disabled = 0; +int sb_dsp_highspeed = 0; +int sbc_major = 1; +int sbc_minor = 0; /* DSP version */ +static int dsp_stereo = 0; +static int dsp_current_speed = DSP_DEFAULT_SPEED; +static int sb16 = 0; +static int irq_verified = 0; + +int sb_midi_mode = NORMAL_MIDI; +int sb_midi_busy = 0; /* 1 if the process has output to MIDI */ +int sb_dsp_busy = 0; + +volatile int sb_irq_mode = IMODE_NONE; /* IMODE_INPUT, IMODE_OUTPUT + + * or IMODE_NONE */ +static volatile int irq_ok = 0; + +int sb_duplex_midi = 0; +static int my_dev = 0; + +volatile int sb_intr_active = 0; + +static int dsp_speed (int); +static int dsp_set_stereo (int mode); +int sb_dsp_command (unsigned char val); + +#if !defined(EXCLUDE_MIDI) || !defined(EXCLUDE_AUDIO) + +/* Common code for the midi and pcm functions */ + +int +sb_dsp_command (unsigned char val) +{ + int i; + unsigned long limit; + + limit = GET_TIME () + HZ / 10;/* The timeout is 0.1 secods */ + + /* + * Note! the i<500000 is an emergency exit. The sb_dsp_command() is sometimes + * called while interrupts are disabled. This means that the timer is + * disabled also. However the timeout situation is a abnormal condition. + * Normally the DSP should be ready to accept commands after just couple of + * loops. + */ + + for (i = 0; i < 500000 && GET_TIME () < limit; i++) + { + if ((INB (DSP_STATUS) & 0x80) == 0) + { + OUTB (val, DSP_COMMAND); + return 1; + } + } + + printk ("SoundBlaster: DSP Command(%x) Timeout.\n", val); + printk ("IRQ conflict???\n"); + return 0; +} + +void +sbintr (int unit) +{ + int status; + +#ifndef EXCLUDE_SBPRO + if (sb16) + { + unsigned char src = sb_getmixer (IRQ_STAT); /* Interrupt source register */ + +#ifndef EXCLUDE_SB16 + if (src & 3) + sb16_dsp_interrupt (unit); + +#ifndef EXCLUDE_MIDI + if (src & 4) + sb16midiintr (unit); /* MPU401 interrupt */ +#endif + +#endif + + if (!(src & 1)) + return; /* Not a DSP interupt */ + } +#endif + + status = INB (DSP_DATA_AVAIL);/* Clear interrupt */ + + if (sb_intr_active) + switch (sb_irq_mode) + { + case IMODE_OUTPUT: + sb_intr_active = 0; + DMAbuf_outputintr (my_dev, 1); + break; + + case IMODE_INPUT: + sb_intr_active = 0; + DMAbuf_inputintr (my_dev); + /* A complete buffer has been input. Let's start new one */ + break; + + case IMODE_INIT: + sb_intr_active = 0; + irq_ok = 1; + break; + + case IMODE_MIDI: + sb_midi_interrupt (unit); + break; + + default: + printk ("SoundBlaster: Unexpected interrupt\n"); + } +} + +static int sb_irq_usecount = 0; + +int +sb_get_irq (void) +{ + int ok; + + if (!sb_irq_usecount) + if ((ok = snd_set_irq_handler (sbc_irq, sbintr)) < 0) + return ok; + + sb_irq_usecount++; + + return 0; +} + +void +sb_free_irq (void) +{ + if (!sb_irq_usecount) + return; + + sb_irq_usecount--; + + if (!sb_irq_usecount) + snd_release_irq (sbc_irq); +} + +int +sb_reset_dsp (void) +{ + int loopc; + + OUTB (1, DSP_RESET); + tenmicrosec (); + OUTB (0, DSP_RESET); + tenmicrosec (); + tenmicrosec (); + tenmicrosec (); + + for (loopc = 0; loopc < 1000 && !(INB (DSP_DATA_AVAIL) & 0x80); loopc++); /* Wait for data + * available status */ + + if (INB (DSP_READ) != 0xAA) + return 0; /* Sorry */ + + return 1; +} + +#endif + +#ifndef EXCLUDE_AUDIO + +static void +dsp_speaker (char state) +{ + if (state) + sb_dsp_command (DSP_CMD_SPKON); + else + sb_dsp_command (DSP_CMD_SPKOFF); +} + +static int +dsp_speed (int speed) +{ + unsigned char tconst; + unsigned long flags; + int max_speed = 44100; + + if (speed < 4000) + speed = 4000; + + /* + * Older SB models don't support higher speeds than 22050. + */ + + if (sbc_major < 2 || + (sbc_major == 2 && sbc_minor == 0)) + max_speed = 22050; + + /* + * SB models earlier than SB Pro have low limit for the input speed. + */ + if (open_mode != OPEN_WRITE) /* Recording is possible */ + if (sbc_major < 3) /* Limited input speed with these cards */ + if (sbc_major == 2 && sbc_minor > 0) + max_speed = 15000; + else + max_speed = 13000; + + if (speed > max_speed) + speed = max_speed; /* Invalid speed */ + + if (dsp_stereo && speed > 22050) + speed = 22050; + /* Max. stereo speed is 22050 */ + + if ((speed > 22050) && sb_midi_busy) + { + printk ("SB Warning: High speed DSP not possible simultaneously with MIDI output\n"); + speed = 22050; + } + + if (dsp_stereo) + speed *= 2; + + /* Now the speed should be valid */ + + if (speed > 22050) + { /* High speed mode */ + int tmp; + + tconst = (unsigned char) ((65536 - + ((256000000 + speed / 2) / speed)) >> 8); + sb_dsp_highspeed = 1; + + DISABLE_INTR (flags); + if (sb_dsp_command (0x40)) + sb_dsp_command (tconst); + RESTORE_INTR (flags); + + tmp = 65536 - (tconst << 8); + speed = (256000000 + tmp / 2) / tmp; + } + else + { + int tmp; + + sb_dsp_highspeed = 0; + tconst = (256 - ((1000000 + speed / 2) / speed)) & 0xff; + + DISABLE_INTR (flags); + if (sb_dsp_command (0x40))/* Set time constant */ + sb_dsp_command (tconst); + RESTORE_INTR (flags); + + tmp = 256 - tconst; + speed = (1000000 + tmp / 2) / tmp; + } + + if (dsp_stereo) + speed /= 2; + + dsp_current_speed = speed; + return speed; +} + +static int +dsp_set_stereo (int mode) +{ + dsp_stereo = 0; + +#ifdef EXCLUDE_SBPRO + return 0; +#else + if (sbc_major < 3 || sb16) + return 0; /* Sorry no stereo */ + + if (mode && sb_midi_busy) + { + printk ("SB Warning: Stereo DSP not possible simultaneously with MIDI output\n"); + return 0; + } + + dsp_stereo = !!mode; + return dsp_stereo; +#endif +} + +static void +sb_dsp_output_block (int dev, unsigned long buf, int count, + int intrflag, int restart_dma) +{ + unsigned long flags; + + if (!sb_irq_mode) + dsp_speaker (ON); + + sb_irq_mode = IMODE_OUTPUT; + DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + count--; + + if (sb_dsp_highspeed) + { + DISABLE_INTR (flags); + if (sb_dsp_command (0x48))/* High speed size */ + { + sb_dsp_command ((unsigned char) (count & 0xff)); + sb_dsp_command ((unsigned char) ((count >> 8) & 0xff)); + sb_dsp_command (0x91);/* High speed 8 bit DAC */ + } + else + printk ("SB Error: Unable to start (high speed) DAC\n"); + RESTORE_INTR (flags); + } + else + { + DISABLE_INTR (flags); + if (sb_dsp_command (0x14))/* 8-bit DAC (DMA) */ + { + sb_dsp_command ((unsigned char) (count & 0xff)); + sb_dsp_command ((unsigned char) ((count >> 8) & 0xff)); + } + else + printk ("SB Error: Unable to start DAC\n"); + RESTORE_INTR (flags); + } + sb_intr_active = 1; +} + +static void +sb_dsp_start_input (int dev, unsigned long buf, int count, int intrflag, + int restart_dma) +{ + /* Start a DMA input to the buffer pointed by dmaqtail */ + + unsigned long flags; + + if (!sb_irq_mode) + dsp_speaker (OFF); + + sb_irq_mode = IMODE_INPUT; + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + count--; + + if (sb_dsp_highspeed) + { + DISABLE_INTR (flags); + if (sb_dsp_command (0x48))/* High speed size */ + { + sb_dsp_command ((unsigned char) (count & 0xff)); + sb_dsp_command ((unsigned char) ((count >> 8) & 0xff)); + sb_dsp_command (0x99);/* High speed 8 bit ADC */ + } + else + printk ("SB Error: Unable to start (high speed) ADC\n"); + RESTORE_INTR (flags); + } + else + { + DISABLE_INTR (flags); + if (sb_dsp_command (0x24))/* 8-bit ADC (DMA) */ + { + sb_dsp_command ((unsigned char) (count & 0xff)); + sb_dsp_command ((unsigned char) ((count >> 8) & 0xff)); + } + else + printk ("SB Error: Unable to start ADC\n"); + RESTORE_INTR (flags); + } + + sb_intr_active = 1; +} + +static void +dsp_cleanup (void) +{ + sb_intr_active = 0; +} + +static int +sb_dsp_prepare_for_input (int dev, int bsize, int bcount) +{ + dsp_cleanup (); + dsp_speaker (OFF); + + if (sbc_major == 3) /* SB Pro */ + { + if (dsp_stereo) + sb_dsp_command (0xa8); + else + sb_dsp_command (0xa0); + + dsp_speed (dsp_current_speed); /* Speed must be recalculated if #channels + * changes */ + } + return 0; +} + +static int +sb_dsp_prepare_for_output (int dev, int bsize, int bcount) +{ + dsp_cleanup (); + dsp_speaker (ON); + +#ifndef EXCLUDE_SBPRO + if (sbc_major == 3) /* SB Pro */ + { + sb_mixer_set_stereo (dsp_stereo); + dsp_speed (dsp_current_speed); /* Speed must be recalculated if #channels + * changes */ + } +#endif + return 0; +} + +static void +sb_dsp_halt_xfer (int dev) +{ +} + +static int +verify_irq (void) +{ +#if 0 + DEFINE_WAIT_QUEUE (testq, testf); + + irq_ok = 0; + + if (sb_get_irq () == -1) + { + printk ("*** SB Error: Irq %d already in use\n", sbc_irq); + return 0; + } + + + sb_irq_mode = IMODE_INIT; + + sb_dsp_command (0xf2); /* This should cause immediate interrupt */ + + DO_SLEEP (testq, testf, HZ / 5); + + sb_free_irq (); + + if (!irq_ok) + { + printk ("SB Warning: IRQ%d test not passed!", sbc_irq); + irq_ok = 1; + } +#else + irq_ok = 1; +#endif + return irq_ok; +} + +static int +sb_dsp_open (int dev, int mode) +{ + int retval; + + if (!sb_dsp_ok) + { + printk ("SB Error: SoundBlaster board not installed\n"); + return RET_ERROR (ENXIO); + } + + if (sb_intr_active || (sb_midi_busy && sb_midi_mode == UART_MIDI)) + { + printk ("SB: PCM not possible during MIDI input\n"); + return RET_ERROR (EBUSY); + } + + if (!irq_verified) + { + verify_irq (); + irq_verified = 1; + } + else if (!irq_ok) + printk ("SB Warning: Incorrect IRQ setting %d\n", + sbc_irq); + + retval = sb_get_irq (); + if (retval) + return retval; + + if (!DMAbuf_open_dma (dev)) + { + sb_free_irq (); + printk ("SB: DMA Busy\n"); + return RET_ERROR (EBUSY); + } + + sb_irq_mode = IMODE_NONE; + + sb_dsp_busy = 1; + open_mode = mode; + + return 0; +} + +static void +sb_dsp_close (int dev) +{ + DMAbuf_close_dma (dev); + sb_free_irq (); + dsp_cleanup (); + dsp_speaker (OFF); + sb_dsp_busy = 0; + sb_dsp_highspeed = 0; + open_mode = 0; +} + +static int +sb_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return dsp_speed (arg); + return IOCTL_OUT (arg, dsp_speed (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return dsp_current_speed; + return IOCTL_OUT (arg, dsp_current_speed); + break; + + case SOUND_PCM_WRITE_CHANNELS: + if (local) + return dsp_set_stereo (arg - 1) + 1; + return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg) - 1) + 1); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return dsp_stereo + 1; + return IOCTL_OUT (arg, dsp_stereo + 1); + break; + + case SNDCTL_DSP_STEREO: + if (local) + return dsp_set_stereo (arg); + return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg))); + break; + + case SOUND_PCM_WRITE_BITS: + case SOUND_PCM_READ_BITS: + if (local) + return 8; + return IOCTL_OUT (arg, 8);/* Only 8 bits/sample supported */ + break; + + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return RET_ERROR (EINVAL); + break; + + default: + return RET_ERROR (EINVAL); + } + + return RET_ERROR (EINVAL); +} + +static void +sb_dsp_reset (int dev) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + sb_reset_dsp (); + dsp_speed (dsp_current_speed); + dsp_cleanup (); + + RESTORE_INTR (flags); +} + +#endif + +int +sb_dsp_detect (struct address_info *hw_config) +{ + sbc_base = hw_config->io_base; + sbc_irq = hw_config->irq; + + if (sb_dsp_ok) + return 0; /* Already initialized */ + + if (!sb_reset_dsp ()) + return 0; + + return 1; /* Detected */ +} + +static char card_name[32] = "SoundBlaster"; + +#ifndef EXCLUDE_AUDIO +static struct audio_operations sb_dsp_operations = +{ + "SoundBlaster", + NOTHING_SPECIAL, + sb_dsp_open, + sb_dsp_close, + sb_dsp_output_block, + sb_dsp_start_input, + sb_dsp_ioctl, + sb_dsp_prepare_for_input, + sb_dsp_prepare_for_output, + sb_dsp_reset, + sb_dsp_halt_xfer, + NULL, /* has_output_drained */ + NULL /* copy_from_user */ +}; + +#endif + +long +sb_dsp_init (long mem_start, struct address_info *hw_config) +{ + int i; + int prostat = 0; + + sbc_major = sbc_minor = 0; + sb_dsp_command (0xe1); /* Get version */ + + for (i = 1000; i; i--) + { + if (INB (DSP_DATA_AVAIL) & 0x80) + { /* wait for Data Ready */ + if (sbc_major == 0) + sbc_major = INB (DSP_READ); + else + { + sbc_minor = INB (DSP_READ); + break; + } + } + } + + if (sbc_major == 2 || sbc_major == 3) /* SB 2.0 or SB Pro */ + sb_duplex_midi = 1; + + if (sbc_major == 4) + sb16 = 1; + +#ifndef EXCLUDE_SBPRO + if (sbc_major >= 3 || + (sbc_major == 2 && sbc_minor == 1)) /* Sound Galaxy ??? */ + prostat = sb_mixer_init (sbc_major); +#endif + +#ifndef EXCLUDE_YM3812 + if (sbc_major > 3 || + (sbc_major == 3 && INB (0x388) == 0x00)) /* Non OPL-3 should return 0x06 */ + enable_opl3_mode (OPL3_LEFT, OPL3_RIGHT, OPL3_BOTH); +#endif + + if (sbc_major >= 3) + { +#ifndef SCO + if (prostat) + { +#ifndef EXCLUDE_AUDIO + sprintf (sb_dsp_operations.name, "Sound Galaxy NX Pro %d.%d", sbc_major, sbc_minor); +#endif + sprintf (card_name, "Sound Galaxy NX Pro %d.%d", sbc_major, sbc_minor); + } + else + { +#ifndef EXCLUDE_AUDIO + sprintf (sb_dsp_operations.name, "SoundBlaster Pro %d.%d", sbc_major, sbc_minor); +#endif + sprintf (card_name, "SoundBlaster Pro %d.%d", sbc_major, sbc_minor); + } +#endif + } + else + { +#ifndef SCO +#ifndef EXCLUDE_AUDIO + sprintf (sb_dsp_operations.name, "SoundBlaster %d.%d", sbc_major, sbc_minor); +#endif + sprintf (card_name, "SoundBlaster %d.%d", sbc_major, sbc_minor); +#endif + } + +#ifdef __FreeBSD__ + printk ("snd2: <%s>", card_name); +#else + printk (" <%s>", card_name); +#endif + +#ifndef EXCLUDE_AUDIO +#if !defined(EXCLUDE_SB16) && !defined(EXCLUDE_SBPRO) + if (!sb16) /* There is a better driver for SB16 */ +#endif + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[my_dev = num_dspdevs++] = &sb_dsp_operations; + sound_buffcounts[my_dev] = DSP_BUFFCOUNT; + sound_buffsizes[my_dev] = DSP_BUFFSIZE; + sound_dsp_dmachan[my_dev] = hw_config->dma; + sound_dma_automode[my_dev] = 0; + } + else + printk ("SB: Too many DSP devices available\n"); +#endif + +#ifndef EXCLUDE_MIDI + if (!midi_disabled && !sb16) /* Midi don't work in the SB emulation mode + * of PAS, SB16 has better midi interface */ + sb_midi_init (sbc_major); +#endif + + sb_dsp_ok = 1; + return mem_start; +} + +void +sb_dsp_disable_midi (void) +{ + midi_disabled = 1; +} + +#endif diff --git a/sys/i386/isa/sound/sb_midi.c b/sys/i386/isa/sound/sb_midi.c new file mode 100644 index 0000000..dc6bba07 --- /dev/null +++ b/sys/i386/isa/sound/sb_midi.c @@ -0,0 +1,225 @@ +/* + * sound/sb_dsp.c + * + * The low level driver for the SoundBlaster DS chips. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB) && !defined(EXCLUDE_MIDI) + +#include "sb.h" +#undef SB_TEST_IRQ + +/* + * The DSP channel can be used either for input or output. Variable + * 'sb_irq_mode' will be set when the program calls read or write first time + * after open. Current version doesn't support mode changes without closing + * and reopening the device. Support for this feature may be implemented in a + * future version of this driver. + */ + +extern int sb_dsp_ok; /* Set to 1 after successful initialization */ + +extern int sb_midi_mode; +extern int sb_midi_busy; /* 1 if the process has output to MIDI */ +extern int sb_dsp_busy; +extern int sb_dsp_highspeed; + +extern volatile int sb_irq_mode;/* IMODE_INPUT, IMODE_OUTPUT + + * or IMODE_NONE */ +extern int sb_duplex_midi; +extern int sb_intr_active; +extern int sbc_base; + +static int input_opened = 0; +static void (*midi_input_intr) (int dev, unsigned char data); +static int my_dev = 0; + +static int +sb_midi_open (int dev, int mode, + void (*input) (int dev, unsigned char data), + void (*output) (int dev) +) +{ + int ret; + + if (!sb_dsp_ok) + { + printk ("SB Error: MIDI hardware not installed\n"); + return RET_ERROR (ENXIO); + } + + if (mode != OPEN_WRITE && !sb_duplex_midi) + { + if (num_midis == 1) + printk ("SoundBlaster: MIDI input not supported with plain SB\n"); + return RET_ERROR (EPERM); + } + + sb_midi_mode = NORMAL_MIDI; + if (mode != OPEN_WRITE) + { + if (sb_dsp_busy || sb_intr_active) + return RET_ERROR (EBUSY); + sb_midi_mode = UART_MIDI; + } + + if (sb_dsp_highspeed) + { + printk ("SB Error: Midi output not possible during stereo or high speed audio\n"); + return RET_ERROR (EBUSY); + } + + if (sb_midi_mode == UART_MIDI) + { + sb_irq_mode = IMODE_MIDI; + + sb_reset_dsp (); + + if (!sb_dsp_command (0xf2)) /* This is undodumented, isn't it */ + return RET_ERROR (EIO); /* be nice to DSP */ + + if (!sb_dsp_command (0x35)) + return RET_ERROR (EIO); /* Enter the UART mode */ + sb_intr_active = 1; + + if ((ret = sb_get_irq ()) < 0) + { + sb_reset_dsp (); + return 0; /* IRQ not free */ + } + input_opened = 1; + my_dev = dev; + midi_input_intr = input; + } + + sb_midi_busy = 1; + + return 0; +} + +static void +sb_midi_close (int dev) +{ + if (sb_midi_mode == UART_MIDI) + { + sb_reset_dsp (); /* The only way to kill the UART mode */ + sb_free_irq (); + } + sb_intr_active = 0; + sb_midi_busy = 0; + input_opened = 0; +} + +static int +sb_midi_out (int dev, unsigned char midi_byte) +{ + unsigned long flags; + + sb_midi_busy = 1; /* Kill all notes after close */ + + if (sb_midi_mode == NORMAL_MIDI) + { + DISABLE_INTR (flags); + if (sb_dsp_command (0x38)) + sb_dsp_command (midi_byte); + else + printk ("SB Error: Unable to send a MIDI byte\n"); + RESTORE_INTR (flags); + } + else + sb_dsp_command (midi_byte); /* UART write */ + + return 1; +} + +static int +sb_midi_start_read (int dev) +{ + if (sb_midi_mode != UART_MIDI) + { + printk ("SoundBlaster: MIDI input not implemented.\n"); + return RET_ERROR (EPERM); + } + return 0; +} + +static int +sb_midi_end_read (int dev) +{ + if (sb_midi_mode == UART_MIDI) + { + sb_reset_dsp (); + sb_intr_active = 0; + } + return 0; +} + +static int +sb_midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EPERM); +} + +void +sb_midi_interrupt (int dummy) +{ + unsigned long flags; + unsigned char data; + + DISABLE_INTR (flags); + + data = INB (DSP_READ); + if (input_opened) + midi_input_intr (my_dev, data); + + RESTORE_INTR (flags); +} + +static struct midi_operations sb_midi_operations = +{ + {"SoundBlaster", 0, 0, SNDCARD_SB}, + sb_midi_open, + sb_midi_close, + sb_midi_ioctl, + sb_midi_out, + sb_midi_start_read, + sb_midi_end_read, + NULL, /* Kick */ + NULL, /* command */ + NULL /* buffer_status */ +}; + +void +sb_midi_init (int model) +{ + midi_devs[num_midis++] = &sb_midi_operations; +} + +#endif diff --git a/sys/i386/isa/sound/sb_mixer.c b/sys/i386/isa/sound/sb_mixer.c new file mode 100644 index 0000000..508dc67 --- /dev/null +++ b/sys/i386/isa/sound/sb_mixer.c @@ -0,0 +1,423 @@ + +/* + * sound/sb_mixer.c + * + * The low level mixer driver for the SoundBlaster Pro and SB16 cards. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Modified: + * Hunyue Yau Jan 6 1994 + * Added code to support the Sound Galaxy NX Pro mixer. + * + * $Id$ + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB) && !defined(EXCLUDE_SBPRO) +#define __SB_MIXER_C__ + +#include "sb.h" +#include "sb_mixer.h" +#undef SB_TEST_IRQ + +extern int sbc_base; + +static int mixer_initialized = 0; + +static int supported_rec_devices; +static int supported_devices; +static int recmask = 0; +static int mixer_model; +static int mixer_caps; +static mixer_tab *iomap; + +void +sb_setmixer (unsigned int port, unsigned int value) +{ + unsigned long flags; + + DISABLE_INTR (flags); + OUTB ((unsigned char) (port & 0xff), MIXER_ADDR); /* Select register */ + tenmicrosec (); + OUTB ((unsigned char) (value & 0xff), MIXER_DATA); + tenmicrosec (); + RESTORE_INTR (flags); +} + +int +sb_getmixer (unsigned int port) +{ + int val; + unsigned long flags; + + DISABLE_INTR (flags); + OUTB ((unsigned char) (port & 0xff), MIXER_ADDR); /* Select register */ + tenmicrosec (); + val = INB (MIXER_DATA); + tenmicrosec (); + RESTORE_INTR (flags); + + return val; +} + +void +sb_mixer_set_stereo (int mode) +{ + if (!mixer_initialized) + return; + + sb_setmixer (OUT_FILTER, ((sb_getmixer (OUT_FILTER) & ~STEREO_DAC) + | (mode ? STEREO_DAC : MONO_DAC))); +} + +/* + * Returns: + * 0 No mixer detected. + * 1 Only a plain Sound Blaster Pro style mixer detected. + * 2 The Sound Galaxy NX Pro mixer detected. + */ +static int +detect_mixer (void) +{ +#ifdef __SGNXPRO__ + int oldbass, oldtreble; + +#endif + int retcode = 1; + + /* + * Detect the mixer by changing parameters of two volume channels. If the + * values read back match with the values written, the mixer is there (is + * it?) + */ + sb_setmixer (FM_VOL, 0xff); + sb_setmixer (VOC_VOL, 0x33); + + if (sb_getmixer (FM_VOL) != 0xff) + return 0; /* No match */ + if (sb_getmixer (VOC_VOL) != 0x33) + return 0; + +#ifdef __SGNXPRO__ + /* Attempt to detect the SG NX Pro by check for valid bass/treble + * registers. + */ + oldbass = sb_getmixer (BASS_LVL); + oldtreble = sb_getmixer (TREBLE_LVL); + + sb_setmixer (BASS_LVL, 0xaa); + sb_setmixer (TREBLE_LVL, 0x55); + + if ((sb_getmixer (BASS_LVL) != 0xaa) || + (sb_getmixer (TREBLE_LVL) != 0x55)) + { + retcode = 1; /* 1 == Only SB Pro detected */ + } + else + retcode = 2; /* 2 == SG NX Pro detected */ + /* Restore register in either case since SG NX Pro has EEPROM with + * 'preferred' values stored. + */ + sb_setmixer (BASS_LVL, oldbass); + sb_setmixer (TREBLE_LVL, oldtreble); +#endif + return retcode; +} + +static void +change_bits (unsigned char *regval, int dev, int chn, int newval) +{ + unsigned char mask; + int shift; + + mask = (1 << (*iomap)[dev][chn].nbits) - 1; + newval = ((newval * mask) + 50) / 100; /* Scale it */ + + shift = (*iomap)[dev][chn].bitoffs - (*iomap)[dev][LEFT_CHN].nbits + 1; + + *regval &= ~(mask << shift); /* Filter out the previous value */ + *regval |= (newval & mask) << shift; /* Set the new value */ +} + +static int +sb_mixer_get (int dev) +{ + if (!((1 << dev) & supported_devices)) + return RET_ERROR (EINVAL); + + return levels[dev]; +} + +static int +sb_mixer_set (int dev, int value) +{ + int left = value & 0x000000ff; + int right = (value & 0x0000ff00) >> 8; + + int regoffs; + unsigned char val; + + if (left > 100) + left = 100; + if (right > 100) + right = 100; + + if (dev > 31) + return RET_ERROR (EINVAL); + + if (!(supported_devices & (1 << dev))) /* Not supported */ + return RET_ERROR (EINVAL); + + regoffs = (*iomap)[dev][LEFT_CHN].regno; + + if (regoffs == 0) + return RET_ERROR (EINVAL); + + val = sb_getmixer (regoffs); + change_bits (&val, dev, LEFT_CHN, left); + + levels[dev] = left | (left << 8); + + if ((*iomap)[dev][RIGHT_CHN].regno != regoffs) /* Change register */ + { + sb_setmixer (regoffs, val); /* Save the old one */ + regoffs = (*iomap)[dev][RIGHT_CHN].regno; + + if (regoffs == 0) + return left | (left << 8); /* Just left channel present */ + + val = sb_getmixer (regoffs); /* Read the new one */ + } + + change_bits (&val, dev, RIGHT_CHN, right); + sb_setmixer (regoffs, val); + + levels[dev] = left | (right << 8); + return left | (right << 8); +} + +static void +set_recsrc (int src) +{ + sb_setmixer (RECORD_SRC, (sb_getmixer (RECORD_SRC) & ~7) | (src & 0x7)); +} + +static int +set_recmask (int mask) +{ + int devmask, i; + unsigned char regimageL, regimageR; + + devmask = mask & supported_rec_devices; + + switch (mixer_model) + { + case 3: + + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && + devmask != SOUND_MASK_CD) + { /* More than one devices selected. Drop the + * previous selection */ + devmask &= ~recmask; + } + + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && + devmask != SOUND_MASK_CD) + { /* More than one devices selected. Default to + * mic */ + devmask = SOUND_MASK_MIC; + } + + + if (devmask ^ recmask) /* Input source changed */ + { + switch (devmask) + { + + case SOUND_MASK_MIC: + set_recsrc (SRC_MIC); + break; + + case SOUND_MASK_LINE: + set_recsrc (SRC_LINE); + break; + + case SOUND_MASK_CD: + set_recsrc (SRC_CD); + break; + + default: + set_recsrc (SRC_MIC); + } + } + + break; + + case 4: + if (!devmask) + devmask = SOUND_MASK_MIC; + + regimageL = regimageR = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((1 << i) & devmask) + { + regimageL |= sb16_recmasks_L[i]; + regimageR |= sb16_recmasks_R[i]; + } + sb_setmixer (SB16_IMASK_L, regimageL); + sb_setmixer (SB16_IMASK_R, regimageR); + break; + } + + recmask = devmask; + return recmask; +} + +static int +sb_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) +{ + if (((cmd >> 8) & 0xff) == 'M') + { + if (cmd & IOC_IN) + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + return IOCTL_OUT (arg, set_recmask (IOCTL_IN (arg))); + break; + + default: + return IOCTL_OUT (arg, sb_mixer_set (cmd & 0xff, IOCTL_IN (arg))); + } + else + switch (cmd & 0xff) /* Return parameters */ + { + + case SOUND_MIXER_RECSRC: + return IOCTL_OUT (arg, recmask); + break; + + case SOUND_MIXER_DEVMASK: + return IOCTL_OUT (arg, supported_devices); + break; + + case SOUND_MIXER_STEREODEVS: + return IOCTL_OUT (arg, supported_devices & + ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER)); + break; + + case SOUND_MIXER_RECMASK: + return IOCTL_OUT (arg, supported_rec_devices); + break; + + case SOUND_MIXER_CAPS: + return IOCTL_OUT (arg, mixer_caps); + break; + + default: + return IOCTL_OUT (arg, sb_mixer_get (cmd & 0xff)); + } + } + else + return RET_ERROR (EINVAL); +} + +static struct mixer_operations sb_mixer_operations = +{ + sb_mixer_ioctl +}; + +static void +sb_mixer_reset (void) +{ + int i; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + sb_mixer_set (i, levels[i]); + set_recmask (SOUND_MASK_MIC); +} + +/* + * Returns a code depending on whether a SG NX Pro was detected. + * 0 == Plain SB 16 or SB Pro + * 1 == SG NX Pro detected. + * + * Used to update message. + */ +int +sb_mixer_init (int major_model) +{ + int mixerstat; + + sb_setmixer (0x00, 0); /* Reset mixer */ + + mixerstat = detect_mixer (); + + if (!mixerstat) + return 0; /* No mixer. Why? */ + + mixer_initialized = 1; + mixer_model = major_model; + + switch (major_model) + { + case 3: + mixer_caps = SOUND_CAP_EXCL_INPUT; +#ifdef __SGNXPRO__ + if (mixerstat == 2) + { /* A SGNXPRO was detected */ + supported_devices = SGNXPRO_MIXER_DEVICES; + supported_rec_devices = SGNXPRO_RECORDING_DEVICES; + iomap = &sgnxpro_mix; + } + else +#endif + { /* Otherwise plain SB Pro */ + supported_devices = SBPRO_MIXER_DEVICES; + supported_rec_devices = SBPRO_RECORDING_DEVICES; + iomap = &sbpro_mix; + } + + break; + + case 4: + mixer_caps = 0; + supported_devices = SB16_MIXER_DEVICES; + supported_rec_devices = SB16_RECORDING_DEVICES; + iomap = &sb16_mix; + break; + + default: + printk ("SB Warning: Unsupported mixer type\n"); + return 0; + } + + mixer_devs[num_mixers++] = &sb_mixer_operations; + sb_mixer_reset (); + return (mixerstat == 2); +} + +#endif diff --git a/sys/i386/isa/sound/sb_mixer.h b/sys/i386/isa/sound/sb_mixer.h new file mode 100644 index 0000000..89b66d0 --- /dev/null +++ b/sys/i386/isa/sound/sb_mixer.h @@ -0,0 +1,213 @@ +/* + * sound/sb_mixer.h + * + * Definitions for the SB Pro and SB16 mixers + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Modified: + * Hunyue Yau Jan 6 1994 + * Added defines for the Sound Galaxy NX Pro mixer. + * + * $Id$ + */ + +#define SBPRO_RECORDING_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +/* Same as SB Pro, unless I find otherwise */ +#define SGNXPRO_RECORDING_DEVICES SBPRO_RECORDING_DEVICES + +#define SBPRO_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +/* SG NX Pro has treble and bass settings on the mixer. The 'speaker' + * channel is the COVOX/DisneySoundSource emulation volume control + * on the mixer. It does NOT control speaker volume. Should have own + * mask eventually? + */ +#define SGNXPRO_MIXER_DEVICES (SBPRO_MIXER_DEVICES|SOUND_MASK_BASS| \ + SOUND_MASK_TREBLE|SOUND_MASK_SPEAKER ) + +#define SB16_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD) + +#define SB16_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_RECLEV | \ + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE) + +/* + * Mixer registers + * + * NOTE! RECORD_SRC == IN_FILTER + */ + +/* + * Mixer registers of SB Pro + */ +#define VOC_VOL 0x04 +#define MIC_VOL 0x0A +#define MIC_MIX 0x0A +#define RECORD_SRC 0x0C +#define IN_FILTER 0x0C +#define OUT_FILTER 0x0E +#define MASTER_VOL 0x22 +#define FM_VOL 0x26 +#define CD_VOL 0x28 +#define LINE_VOL 0x2E +#define IRQ_NR 0x80 +#define DMA_NR 0x81 +#define IRQ_STAT 0x82 +#define OPSW 0x3c + +/* + * Additional registers on the SG NX Pro + */ +#define COVOX_VOL 0x42 +#define TREBLE_LVL 0x44 +#define BASS_LVL 0x46 + +#define FREQ_HI (1 << 3)/* Use High-frequency ANFI filters */ +#define FREQ_LOW 0 /* Use Low-frequency ANFI filters */ +#define FILT_ON 0 /* Yes, 0 to turn it on, 1 for off */ +#define FILT_OFF (1 << 5) + +#define MONO_DAC 0x00 +#define STEREO_DAC 0x02 + +/* + * Mixer registers of SB16 + */ +#define SB16_IMASK_L 0x3d +#define SB16_IMASK_R 0x3e + +#define LEFT_CHN 0 +#define RIGHT_CHN 1 + +struct mixer_def { + unsigned int regno: 8; + unsigned int bitoffs:4; + unsigned int nbits:4; +}; + + +typedef struct mixer_def mixer_tab[32][2]; +typedef struct mixer_def mixer_ent; + +#define MIX_ENT(name, reg_l, bit_l, len_l, reg_r, bit_r, len_r) \ + {{reg_l, bit_l, len_l}, {reg_r, bit_r, len_r}} + +#ifdef __SB_MIXER_C__ +mixer_tab sbpro_mix = { +MIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), +MIX_ENT(SOUND_MIXER_BASS, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), +MIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), +MIX_ENT(SOUND_MIXER_SPEAKER, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), +MIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), +MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0) +}; + +#ifdef __SGNXPRO__ +mixer_tab sgnxpro_mix = { +MIX_ENT(SOUND_MIXER_VOLUME, 0x22, 7, 4, 0x22, 3, 4), +MIX_ENT(SOUND_MIXER_BASS, 0x46, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_TREBLE, 0x44, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_SYNTH, 0x26, 7, 4, 0x26, 3, 4), +MIX_ENT(SOUND_MIXER_PCM, 0x04, 7, 4, 0x04, 3, 4), +MIX_ENT(SOUND_MIXER_SPEAKER, 0x42, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 0x2e, 7, 4, 0x2e, 3, 4), +MIX_ENT(SOUND_MIXER_MIC, 0x0a, 2, 3, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_CD, 0x28, 7, 4, 0x28, 3, 4), +MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0x00, 0, 0, 0x00, 0, 0) +}; +#endif + +mixer_tab sb16_mix = { +MIX_ENT(SOUND_MIXER_VOLUME, 0x30, 7, 5, 0x31, 7, 5), +MIX_ENT(SOUND_MIXER_BASS, 0x46, 7, 4, 0x47, 7, 4), +MIX_ENT(SOUND_MIXER_TREBLE, 0x44, 7, 4, 0x45, 7, 4), +MIX_ENT(SOUND_MIXER_SYNTH, 0x34, 7, 5, 0x35, 7, 5), +MIX_ENT(SOUND_MIXER_PCM, 0x32, 7, 5, 0x33, 7, 5), +MIX_ENT(SOUND_MIXER_SPEAKER, 0x3b, 7, 2, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_LINE, 0x38, 7, 5, 0x39, 7, 5), +MIX_ENT(SOUND_MIXER_MIC, 0x3a, 7, 5, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_CD, 0x36, 7, 5, 0x37, 7, 5), +MIX_ENT(SOUND_MIXER_IMIX, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_ALTPCM, 0x00, 0, 0, 0x00, 0, 0), +MIX_ENT(SOUND_MIXER_RECLEV, 0x3f, 7, 2, 0x40, 7, 2) +}; + +static unsigned short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x5a5a, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x4b4b, /* FM */ + 0x4b4b, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x0000, /* Mic */ + 0x4b4b, /* CD */ + 0x4b4b, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b}; /* Recording level */ + +static unsigned char sb16_recmasks_L[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x40, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x10, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x04, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00 /* SOUND_MIXER_RECLEV */ +}; + +static unsigned char sb16_recmasks_R[SOUND_MIXER_NRDEVICES] = +{ + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x20, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x08, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x02, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_ALTPCM */ + 0x00 /* SOUND_MIXER_RECLEV */ +}; +#endif diff --git a/sys/i386/isa/sound/sequencer.c b/sys/i386/isa/sound/sequencer.c new file mode 100644 index 0000000..5748012 --- /dev/null +++ b/sys/i386/isa/sound/sequencer.c @@ -0,0 +1,1168 @@ +/* + * sound/sequencer.c + * + * The sequencer personality manager. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#define SEQUENCER_C +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#ifndef EXCLUDE_SEQUENCER + +static int sequencer_ok = 0; + +DEFINE_WAIT_QUEUE (seq_sleeper, seq_sleep_flag); +/* DEFINE_WAIT_QUEUE (midi_sleeper, midi_sleep_flag); */ +#define midi_sleeper seq_sleeper +#define midi_sleep_flag seq_sleep_flag + +static int midi_opened[MAX_MIDI_DEV] = +{0}; /* 1 if the process has opened MIDI */ +static int midi_written[MAX_MIDI_DEV] = +{0}; + +unsigned long seq_time = 0; /* Reference point for the timer */ + +#include "tuning.h" + +#define EV_SZ 8 +#define IEV_SZ 4 +static unsigned char *queue = NULL; /* SEQ_MAX_QUEUE * EV_SZ bytes */ +static unsigned char *iqueue = NULL; /* SEQ_MAX_QUEUE * IEV_SZ bytes */ + +static volatile int qhead = 0, qtail = 0, qlen = 0; +static volatile int iqhead = 0, iqtail = 0, iqlen = 0; +static volatile int seq_playing = 0; +static int sequencer_busy = 0; +static int output_treshold; +static unsigned synth_open_mask; + +static int seq_queue (unsigned char *note); +static void seq_startplay (void); +static int seq_sync (void); +static void seq_reset (void); +static int pmgr_present[MAX_SYNTH_DEV] = +{0}; + +#if MAX_SYNTH_DEV > 15 +#error Too many synthesizer devices +#endif + +int +sequencer_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int c = count, p = 0; + + dev = dev >> 4; + + if (dev) /* Patch manager device */ + return pmgr_read (dev - 1, file, buf, count); + + while (c > 3) + { + if (!iqlen) + { + if (c != count) /* Some data has been received */ + return count - c; /* Return what we have */ + + DO_SLEEP (midi_sleeper, midi_sleep_flag, 0); + + if (!iqlen) + return count - c; + } + + COPY_TO_USER (buf, p, &iqueue[iqhead * IEV_SZ], IEV_SZ); + p += 4; + c -= 4; + + iqhead = (iqhead + 1) % SEQ_MAX_QUEUE; + iqlen--; + } + + return count - c; +} + +static void +sequencer_midi_output (int dev) +{ + /* Currently NOP */ +} + +static void +copy_to_input (unsigned char *event) +{ + unsigned long flags; + + if (iqlen >= (SEQ_MAX_QUEUE - 1)) + return; /* Overflow */ + + memcpy (&iqueue[iqtail * IEV_SZ], event, IEV_SZ); + iqlen++; + iqtail = (iqtail + 1) % SEQ_MAX_QUEUE; + + DISABLE_INTR (flags); + if (SOMEONE_WAITING (midi_sleeper, midi_sleep_flag)) + { + WAKE_UP (midi_sleeper, midi_sleep_flag); + } + RESTORE_INTR (flags); +} + +static void +sequencer_midi_input (int dev, unsigned char data) +{ + int tstamp; + unsigned char event[4]; + + if (data == 0xfe) /* Active sensing */ + return; /* Ignore */ + + tstamp = GET_TIME () - seq_time; /* Time since open() */ + tstamp = (tstamp << 8) | SEQ_WAIT; + + copy_to_input ((unsigned char *) &tstamp); + + event[0] = SEQ_MIDIPUTC; + event[1] = data; + event[2] = dev; + event[3] = 0; + + copy_to_input (event); +} + +int +sequencer_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + unsigned char event[EV_SZ], ev_code; + int p = 0, c, ev_size; + int err; + int mode = file->mode & O_ACCMODE; + + dev = dev >> 4; + + DEB (printk ("sequencer_write(dev=%d, count=%d)\n", dev, count)); + + if (mode == OPEN_READ) + return RET_ERROR (EIO); + + if (dev) /* Patch manager device */ + return pmgr_write (dev - 1, file, buf, count); + + c = count; + + while (c >= 4) + { + COPY_FROM_USER (event, buf, p, 4); + ev_code = event[0]; + + if (ev_code == SEQ_FULLSIZE) + { + int err; + + dev = *(unsigned short *) &event[2]; + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev))) + return RET_ERROR (ENXIO); + + err = synth_devs[dev]->load_patch (dev, *(short *) &event[0], buf, p + 4, c, 0); + if (err < 0) + return err; + + return err; + } + + if (ev_code == SEQ_EXTENDED || ev_code == SEQ_PRIVATE) + { + + ev_size = 8; + + if (c < ev_size) + { + if (!seq_playing) + seq_startplay (); + return count - c; + } + + COPY_FROM_USER (&event[4], buf, p + 4, 4); + + } + else + ev_size = 4; + + if (event[0] == SEQ_MIDIPUTC) + { + + if (!midi_opened[event[2]]) + { + int mode; + int dev = event[2]; + + if (dev >= num_midis) + { + printk ("Sequencer Error: Nonexistent MIDI device %d\n", dev); + return RET_ERROR (ENXIO); + } + + mode = file->mode & O_ACCMODE; + + if ((err = midi_devs[dev]->open (dev, mode, + sequencer_midi_input, sequencer_midi_output)) < 0) + { + seq_reset (); + printk ("Sequencer Error: Unable to open Midi #%d\n", dev); + return err; + } + + midi_opened[dev] = 1; + } + + } + + if (!seq_queue (event)) + { + + if (!seq_playing) + seq_startplay (); + return count - c; + } + + p += ev_size; + c -= ev_size; + } + + if (!seq_playing) + seq_startplay (); + + return count; +} + +static int +seq_queue (unsigned char *note) +{ + + /* Test if there is space in the queue */ + + if (qlen >= SEQ_MAX_QUEUE) + if (!seq_playing) + seq_startplay (); /* Give chance to drain the queue */ + + if (qlen >= SEQ_MAX_QUEUE && !SOMEONE_WAITING (seq_sleeper, seq_sleep_flag)) + { + /* Sleep until there is enough space on the queue */ + DO_SLEEP (seq_sleeper, seq_sleep_flag, 0); + } + + if (qlen >= SEQ_MAX_QUEUE) + return 0; /* To be sure */ + + memcpy (&queue[qtail * EV_SZ], note, EV_SZ); + + qtail = (qtail + 1) % SEQ_MAX_QUEUE; + qlen++; + + return 1; +} + +static int +extended_event (unsigned char *q) +{ + int dev = q[2]; + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev))) + return RET_ERROR (ENXIO); + + switch (q[1]) + { + case SEQ_NOTEOFF: + synth_devs[dev]->kill_note (dev, q[3], q[5]); + break; + + case SEQ_NOTEON: + if (q[4] > 127 && q[4] != 255) + return 0; + + synth_devs[dev]->start_note (dev, q[3], q[4], q[5]); + break; + + case SEQ_PGMCHANGE: + synth_devs[dev]->set_instr (dev, q[3], q[4]); + break; + + case SEQ_AFTERTOUCH: + synth_devs[dev]->aftertouch (dev, q[3], q[4]); + break; + + case SEQ_BALANCE: + synth_devs[dev]->panning (dev, q[3], (char) q[4]); + break; + + case SEQ_CONTROLLER: + synth_devs[dev]->controller (dev, q[3], q[4], *(short *) &q[5]); + break; + + case SEQ_VOLMODE: + synth_devs[dev]->volume_method (dev, q[3]); + break; + + default: + return RET_ERROR (EINVAL); + } + + return 0; +} + +static void +seq_startplay (void) +{ + int this_one; + unsigned long *delay; + unsigned char *q; + + while (qlen > 0) + { + qhead = ((this_one = qhead) + 1) % SEQ_MAX_QUEUE; + qlen--; + + q = &queue[this_one * EV_SZ]; + + switch (q[0]) + { + case SEQ_NOTEOFF: + if (synth_open_mask & (1 << 0)) + if (synth_devs[0]) + synth_devs[0]->kill_note (0, q[1], q[3]); + break; + + case SEQ_NOTEON: + if (q[4] < 128 || q[4] == 255) + if (synth_open_mask & (1 << 0)) + if (synth_devs[0]) + synth_devs[0]->start_note (0, q[1], q[2], q[3]); + break; + + case SEQ_WAIT: + delay = (unsigned long *) q; /* Bytes 1 to 3 are containing the + * delay in GET_TIME() */ + *delay = (*delay >> 8) & 0xffffff; + + if (*delay > 0) + { + long time; + + seq_playing = 1; + time = *delay; + + request_sound_timer (time); + + if ((SEQ_MAX_QUEUE - qlen) >= output_treshold) + { + unsigned long flags; + + DISABLE_INTR (flags); + if (SOMEONE_WAITING (seq_sleeper, seq_sleep_flag)) + { + WAKE_UP (seq_sleeper, seq_sleep_flag); + } + RESTORE_INTR (flags); + } + return; /* Stop here. Timer routine will continue + * playing after the delay */ + } + break; + + case SEQ_PGMCHANGE: + if (synth_open_mask & (1 << 0)) + if (synth_devs[0]) + synth_devs[0]->set_instr (0, q[1], q[2]); + break; + + case SEQ_SYNCTIMER: /* Reset timer */ + seq_time = GET_TIME (); + break; + + case SEQ_MIDIPUTC: /* Put a midi character */ + if (midi_opened[q[2]]) + { + int dev; + + dev = q[2]; + + if (!midi_devs[dev]->putc (dev, q[1])) + { + /* + * Output FIFO is full. Wait one timer cycle and try again. + */ + + qlen++; + qhead = this_one; /* Restore queue */ + seq_playing = 1; + request_sound_timer (-1); + return; + } + else + midi_written[dev] = 1; + } + break; + + case SEQ_ECHO: + copy_to_input (q); /* Echo back to the process */ + break; + + case SEQ_PRIVATE: + if (q[1] < num_synths) + synth_devs[q[1]]->hw_control (q[1], q); + break; + + case SEQ_EXTENDED: + extended_event (q); + break; + + default:; + } + + } + + seq_playing = 0; + + if ((SEQ_MAX_QUEUE - qlen) >= output_treshold) + { + unsigned long flags; + + DISABLE_INTR (flags); + if (SOMEONE_WAITING (seq_sleeper, seq_sleep_flag)) + { + WAKE_UP (seq_sleeper, seq_sleep_flag); + } + RESTORE_INTR (flags); + } + +} + +int +sequencer_open (int dev, struct fileinfo *file) +{ + int retval, mode, i; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + DEB (printk ("sequencer_open(dev=%d)\n", dev)); + + if (!sequencer_ok) + { + printk ("Soundcard: Sequencer not initialized\n"); + return RET_ERROR (ENXIO); + } + + if (dev) /* Patch manager device */ + { + int err; + + dev--; + if (pmgr_present[dev]) + return RET_ERROR (EBUSY); + if ((err = pmgr_open (dev)) < 0) + return err; /* Failed */ + + pmgr_present[dev] = 1; + return err; + } + + if (sequencer_busy) + { + printk ("Sequencer busy\n"); + return RET_ERROR (EBUSY); + } + + if (!(num_synths + num_midis)) + return RET_ERROR (ENXIO); + + synth_open_mask = 0; + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + for (i = 0; i < num_synths; i++) /* Open synth devices */ + if (synth_devs[i]->open (i, mode) < 0) + printk ("Sequencer: Warning! Cannot open synth device #%d\n", i); + else + synth_open_mask |= (1 << i); + + seq_time = GET_TIME (); + + for (i = 0; i < num_midis; i++) + { + midi_opened[i] = 0; + midi_written[i] = 0; + } + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + { /* Initialize midi input devices */ + if (!num_midis) + { + printk ("Sequencer: No Midi devices. Input not possible\n"); + return RET_ERROR (ENXIO); + } + + for (i = 0; i < num_midis; i++) + { + if ((retval = midi_devs[i]->open (i, mode, + sequencer_midi_input, sequencer_midi_output)) >= 0) + midi_opened[i] = 1; + } + } + + sequencer_busy = 1; + RESET_WAIT_QUEUE (seq_sleeper, seq_sleep_flag); + RESET_WAIT_QUEUE (midi_sleeper, midi_sleep_flag); + output_treshold = SEQ_MAX_QUEUE / 2; + + for (i = 0; i < num_synths; i++) + if (pmgr_present[i]) + pmgr_inform (i, PM_E_OPENED, 0, 0, 0, 0); + + return 0; +} + +void +seq_drain_midi_queues (void) +{ + int i, n; + + /* + * Give the Midi drivers time to drain their output queues + */ + + n = 1; + + while (!PROCESS_ABORTING (midi_sleeper, midi_sleep_flag) && n) + { + n = 0; + + for (i = 0; i < num_midis; i++) + if (midi_opened[i] && midi_written[i]) + if (midi_devs[i]->buffer_status != NULL) + if (midi_devs[i]->buffer_status (i)) + n++; + + /* + * Let's have a delay + */ + if (n) + { + DO_SLEEP (seq_sleeper, seq_sleep_flag, HZ / 10); + } + } +} + +void +sequencer_release (int dev, struct fileinfo *file) +{ + int i; + int mode = file->mode & O_ACCMODE; + + dev = dev >> 4; + + DEB (printk ("sequencer_release(dev=%d)\n", dev)); + + if (dev) /* Patch manager device */ + { + dev--; + pmgr_release (dev); + pmgr_present[dev] = 0; + return; + } + + /* + * Wait until the queue is empty + */ + + while (!PROCESS_ABORTING (seq_sleeper, seq_sleep_flag) && qlen) + { + seq_sync (); + } + + if (mode != OPEN_READ) + seq_drain_midi_queues (); /* Ensure the output queues are empty */ + seq_reset (); + if (mode != OPEN_READ) + seq_drain_midi_queues (); /* Flush the all notes off messages */ + + for (i = 0; i < num_midis; i++) + if (midi_opened[i]) + midi_devs[i]->close (i); + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + for (i = 0; i < num_synths; i++) + if (synth_open_mask & (1 << i)) /* Actually opened */ + if (synth_devs[i]) + synth_devs[i]->close (i); + + for (i = 0; i < num_synths; i++) + if (pmgr_present[i]) + pmgr_inform (i, PM_E_CLOSED, 0, 0, 0, 0); + + sequencer_busy = 0; +} + +static int +seq_sync (void) +{ + if (qlen && !seq_playing && !PROCESS_ABORTING (seq_sleeper, seq_sleep_flag)) + seq_startplay (); + + if (qlen && !SOMEONE_WAITING (seq_sleeper, seq_sleep_flag)) /* Queue not empty */ + { + DO_SLEEP (seq_sleeper, seq_sleep_flag, 0); + } + + return qlen; +} + +static void +midi_outc (int dev, unsigned char data) +{ + /* + * NOTE! Calls sleep(). Don't call this from interrupt. + */ + + int n; + + /* This routine sends one byte to the Midi channel. */ + /* If the output Fifo is full, it waits until there */ + /* is space in the queue */ + + n = 300; /* Timeout in jiffies */ + + while (n && !midi_devs[dev]->putc (dev, data)) + { + DO_SLEEP (seq_sleeper, seq_sleep_flag, 4); + n--; + } +} + +static void +seq_reset (void) +{ + /* + * NOTE! Calls sleep(). Don't call this from interrupt. + */ + + int i, chn; + + sound_stop_timer (); + + qlen = qhead = qtail = 0; + iqlen = iqhead = iqtail = 0; + + for (i = 0; i < num_synths; i++) + if (synth_open_mask & (1 << i)) + if (synth_devs[i]) + synth_devs[i]->reset (i); + + for (i = 0; i < num_midis; i++) + if (midi_written[i]) /* Midi used. Some notes may still be playing */ + { + for (chn = 0; chn < 16; chn++) + { + midi_outc (i, + (unsigned char) (0xb0 + (chn & 0xff))); /* Channel msg */ + midi_outc (i, 0x7b);/* All notes off */ + midi_outc (i, 0); /* Dummy parameter */ + } + + midi_devs[i]->close (i); + + midi_written[i] = 0; + midi_opened[i] = 0; + } + + seq_playing = 0; + + if (SOMEONE_WAITING (seq_sleeper, seq_sleep_flag)) + printk ("Sequencer Warning: Unexpected sleeping process\n"); + +} + +int +sequencer_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + int midi_dev, orig_dev; + int mode = file->mode & O_ACCMODE; + + orig_dev = dev = dev >> 4; + + switch (cmd) + { + + case SNDCTL_SEQ_SYNC: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (mode == OPEN_READ) + return 0; + while (qlen && !PROCESS_ABORTING (seq_sleeper, seq_sleep_flag)) + seq_sync (); + return 0; + break; + + case SNDCTL_SEQ_RESET: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + seq_reset (); + return 0; + break; + + case SNDCTL_SEQ_TESTMIDI: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + midi_dev = IOCTL_IN (arg); + if (midi_dev >= num_midis) + return RET_ERROR (ENXIO); + + if (!midi_opened[midi_dev]) + { + int err, mode; + + mode = file->mode & O_ACCMODE; + if ((err = midi_devs[midi_dev]->open (midi_dev, mode, + sequencer_midi_input, + sequencer_midi_output)) < 0) + return err; + } + + midi_opened[midi_dev] = 1; + + return 0; + break; + + case SNDCTL_SEQ_GETINCOUNT: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (mode == OPEN_WRITE) + return 0; + return IOCTL_OUT (arg, iqlen); + break; + + case SNDCTL_SEQ_GETOUTCOUNT: + + if (mode == OPEN_READ) + return 0; + return IOCTL_OUT (arg, SEQ_MAX_QUEUE - qlen); + break; + + case SNDCTL_SEQ_CTRLRATE: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + /* If *arg == 0, just return the current rate */ + return IOCTL_OUT (arg, HZ); + break; + + case SNDCTL_SEQ_RESETSAMPLES: + dev = IOCTL_IN (arg); + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev)) && !orig_dev) + return RET_ERROR (EBUSY); + + if (!orig_dev && pmgr_present[dev]) + pmgr_inform (dev, PM_E_PATCH_RESET, 0, 0, 0, 0); + + return synth_devs[dev]->ioctl (dev, cmd, arg); + break; + + case SNDCTL_SEQ_NRSYNTHS: + return IOCTL_OUT (arg, num_synths); + break; + + case SNDCTL_SEQ_NRMIDIS: + return IOCTL_OUT (arg, num_midis); + break; + + case SNDCTL_SYNTH_MEMAVL: + { + int dev = IOCTL_IN (arg); + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev)) && !orig_dev) + return RET_ERROR (EBUSY); + + return IOCTL_OUT (arg, synth_devs[dev]->ioctl (dev, cmd, arg)); + } + break; + + case SNDCTL_FM_4OP_ENABLE: + { + int dev = IOCTL_IN (arg); + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev))) + return RET_ERROR (ENXIO); + + synth_devs[dev]->ioctl (dev, cmd, arg); + return 0; + } + break; + + case SNDCTL_SYNTH_INFO: + { + struct synth_info inf; + int dev; + + IOCTL_FROM_USER ((char *) &inf, (char *) arg, 0, sizeof (inf)); + dev = inf.device; + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev)) && !orig_dev) + return RET_ERROR (EBUSY); + + return synth_devs[dev]->ioctl (dev, cmd, arg); + } + break; + + case SNDCTL_MIDI_INFO: + { + struct midi_info inf; + int dev; + + IOCTL_FROM_USER ((char *) &inf, (char *) arg, 0, sizeof (inf)); + dev = inf.device; + + if (dev < 0 || dev >= num_midis) + return RET_ERROR (ENXIO); + + IOCTL_TO_USER ((char *) arg, 0, (char *) &(midi_devs[dev]->info), sizeof (inf)); + return 0; + } + break; + + case SNDCTL_PMGR_IFACE: + { + struct patmgr_info *inf; + int dev, err; + + inf = (struct patmgr_info *) KERNEL_MALLOC (sizeof (*inf)); + + IOCTL_FROM_USER ((char *) inf, (char *) arg, 0, sizeof (*inf)); + dev = inf->device; + + if (dev < 0 || dev >= num_synths) + { + KERNEL_FREE (inf); + return RET_ERROR (ENXIO); + } + + if (!synth_devs[dev]->pmgr_interface) + { + KERNEL_FREE (inf); + return RET_ERROR (ENXIO); + } + + if ((err = synth_devs[dev]->pmgr_interface (dev, inf)) == -1) + { + KERNEL_FREE (inf); + return err; + } + + IOCTL_TO_USER ((char *) arg, 0, (char *) inf, sizeof (*inf)); + KERNEL_FREE (inf); + return 0; + } + break; + + case SNDCTL_PMGR_ACCESS: + { + struct patmgr_info *inf; + int dev, err; + + inf = (struct patmgr_info *) KERNEL_MALLOC (sizeof (*inf)); + + IOCTL_FROM_USER ((char *) inf, (char *) arg, 0, sizeof (*inf)); + dev = inf->device; + + if (dev < 0 || dev >= num_synths) + { + KERNEL_FREE (inf); + return RET_ERROR (ENXIO); + } + + if (!pmgr_present[dev]) + { + KERNEL_FREE (inf); + return RET_ERROR (ESRCH); + } + + if ((err = pmgr_access (dev, inf)) < 0) + { + KERNEL_FREE (inf); + return err; + } + + IOCTL_TO_USER ((char *) arg, 0, (char *) inf, sizeof (*inf)); + KERNEL_FREE (inf); + return 0; + } + break; + + case SNDCTL_SEQ_TRESHOLD: + { + int tmp = IOCTL_IN (arg); + + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (tmp < 1) + tmp = 1; + if (tmp >= SEQ_MAX_QUEUE) + tmp = SEQ_MAX_QUEUE - 1; + output_treshold = tmp; + return 0; + } + break; + + default: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (mode == OPEN_READ) + return RET_ERROR (EIO); + + if (!synth_devs[0]) + return RET_ERROR (ENXIO); + if (!(synth_open_mask & (1 << 0))) + return RET_ERROR (ENXIO); + return synth_devs[0]->ioctl (0, cmd, arg); + break; + } + + return RET_ERROR (EINVAL); +} + +#ifdef ALLOW_SELECT +int +sequencer_select (int dev, struct fileinfo *file, int sel_type, select_table * wait) +{ + dev = dev >> 4; + + switch (sel_type) + { + case SEL_IN: + if (!iqlen) + { + select_wait (&midi_sleeper, wait); + return 0; + } + return 1; + + break; + + case SEL_OUT: + if (qlen >= SEQ_MAX_QUEUE) + { + select_wait (&seq_sleeper, wait); + return 0; + } + return 1; + break; + + case SEL_EX: + return 0; + } + + return 0; +} + +#endif + +void +sequencer_timer (void) +{ + seq_startplay (); +} + +int +note_to_freq (int note_num) +{ + + /* + * This routine converts a midi note to a frequency (multiplied by 1000) + */ + + int note, octave, note_freq; + int notes[] = + { + 261632, 277189, 293671, 311132, 329632, 349232, + 369998, 391998, 415306, 440000, 466162, 493880 + }; /* Note freq*1000 for octave 5 */ + +#define BASE_OCTAVE 5 + + octave = note_num / 12; + note = note_num % 12; + + note_freq = notes[note]; + + if (octave < BASE_OCTAVE) + note_freq >>= (BASE_OCTAVE - octave); + else if (octave > BASE_OCTAVE) + note_freq <<= (octave - BASE_OCTAVE); + + /* note_freq >>= 1; */ + + return note_freq; +} + +unsigned long +compute_finetune (unsigned long base_freq, int bend, int range) +{ + unsigned long amount; + int negative, semitones, cents, multiplier = 1; + + if (!bend) + return base_freq; + if (!range) + return base_freq; + + if (!base_freq) + return base_freq; + + if (range >= 8192) + range = 8191; + + bend = bend * range / 8192; + if (!bend) + return base_freq; + + negative = bend < 0 ? 1 : 0; + + if (bend < 0) + bend *= -1; + if (bend > range) + bend = range; + + /* + if (bend > 2399) + bend = 2399; + */ + while (bend > 2399) + { + multiplier *= 4; + bend -= 2400; + } + + semitones = bend / 100; + cents = bend % 100; + + amount = semitone_tuning[semitones] * multiplier * cent_tuning[cents] / 10000; + + if (negative) + return (base_freq * 10000) / amount; /* Bend down */ + else + return (base_freq * amount) / 10000; /* Bend up */ +} + + +long +sequencer_init (long mem_start) +{ + + sequencer_ok = 1; + PERMANENT_MALLOC (unsigned char *, queue, SEQ_MAX_QUEUE * EV_SZ, mem_start); + PERMANENT_MALLOC (unsigned char *, iqueue, SEQ_MAX_QUEUE * IEV_SZ, mem_start); + + return mem_start; +} + +#else +/* Stub version */ +int +sequencer_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +sequencer_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +sequencer_open (int dev, struct fileinfo *file) +{ + return RET_ERROR (ENXIO); +} + +void +sequencer_release (int dev, struct fileinfo *file) +{ +} +int +sequencer_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + return RET_ERROR (EIO); +} + +int +sequencer_lseek (int dev, struct fileinfo *file, off_t offset, int orig) +{ + return RET_ERROR (EIO); +} + +long +sequencer_init (long mem_start) +{ + return mem_start; +} + +int +sequencer_select (int dev, struct fileinfo *file, int sel_type, select_table * wait) +{ + return RET_ERROR (EIO); +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/sound_calls.h b/sys/i386/isa/sound/sound_calls.h new file mode 100644 index 0000000..789d1d0 --- /dev/null +++ b/sys/i386/isa/sound/sound_calls.h @@ -0,0 +1,211 @@ +/* + * $Id$ + */ +/* + * DMA buffer calls + */ + +int DMAbuf_open(int dev, int mode); +int DMAbuf_release(int dev, int mode); +int DMAbuf_read (int dev, snd_rw_buf *user_buf, int count); +int DMAbuf_getwrbuffer(int dev, char **buf, int *size); +int DMAbuf_getrdbuffer(int dev, char **buf, int *len); +int DMAbuf_rmchars(int dev, int buff_no, int c); +int DMAbuf_start_output(int dev, int buff_no, int l); +int DMAbuf_ioctl(int dev, unsigned int cmd, unsigned int arg, int local); +long DMAbuf_init(long mem_start); +int DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode); +int DMAbuf_open_dma (int chan); +void DMAbuf_close_dma (int chan); +void DMAbuf_reset_dma (int chan); +void DMAbuf_inputintr(int dev); +void DMAbuf_outputintr(int dev, int underflow_flag); + +/* + * System calls for /dev/dsp and /dev/audio + */ + +int audio_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int audio_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int audio_open (int dev, struct fileinfo *file); +void audio_release (int dev, struct fileinfo *file); +int audio_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int audio_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +long audio_init (long mem_start); + +/* + * System calls for the /dev/sequencer + */ + +int sequencer_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int sequencer_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int sequencer_open (int dev, struct fileinfo *file); +void sequencer_release (int dev, struct fileinfo *file); +int sequencer_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int sequencer_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +long sequencer_init (long mem_start); +void sequencer_timer(void); +int note_to_freq(int note_num); +unsigned long compute_finetune(unsigned long base_freq, int bend, int range); + +#ifdef ALLOW_SELECT +int sequencer_select(int dev, struct fileinfo *file, int sel_type, select_table * wait); +#endif + +/* + * System calls for the /dev/midi + */ + +int MIDIbuf_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int MIDIbuf_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int MIDIbuf_open (int dev, struct fileinfo *file); +void MIDIbuf_release (int dev, struct fileinfo *file); +int MIDIbuf_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int MIDIbuf_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +void MIDIbuf_bytes_received(int dev, unsigned char *buf, int count); +long MIDIbuf_init(long mem_start); + +/* + * System calls for the generic midi interface. + * + */ + +long CMIDI_init (long mem_start); +int CMIDI_open (int dev, struct fileinfo *file); +int CMIDI_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int CMIDI_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int CMIDI_close (int dev, struct fileinfo *file); + +/* + * + * Misc calls from various sources + */ + +/* From pro_midi.c */ + +long pro_midi_attach(long mem_start); +int pro_midi_open(int dev, int mode); +void pro_midi_close(int dev); +int pro_midi_write(int dev, snd_rw_buf *uio); +int pro_midi_read(int dev, snd_rw_buf *uio); + +/* From soundcard.c */ +long soundcard_init(long mem_start); +void tenmicrosec(void); +void request_sound_timer (int count); +void sound_stop_timer(void); +int snd_ioctl_return(int *addr, int value); +int snd_set_irq_handler (int interrupt_level, void(*hndlr)(int)); +void snd_release_irq(int vect); +void sound_dma_malloc(int dev); +void sound_dma_free(int dev); + +/* From sound_switch.c */ +int sound_read_sw (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int sound_write_sw (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int sound_open_sw (int dev, struct fileinfo *file); +void sound_release_sw (int dev, struct fileinfo *file); +int sound_ioctl_sw (int dev, struct fileinfo *file, + unsigned int cmd, unsigned long arg); + +/* From sb_dsp.c */ +int sb_dsp_detect (struct address_info *hw_config); +long sb_dsp_init (long mem_start, struct address_info *hw_config); +void sb_dsp_disable_midi(void); +int sb_get_irq(void); +void sb_free_irq(void); +int sb_dsp_command (unsigned char val); +int sb_reset_dsp (void); + +/* From sb16_dsp.c */ +void sb16_dsp_interrupt (int unused); +long sb16_dsp_init(long mem_start, struct address_info *hw_config); +int sb16_dsp_detect(struct address_info *hw_config); + +/* From sb16_midi.c */ +void sb16midiintr (int unit); +long attach_sb16midi(long mem_start, struct address_info * hw_config); +int probe_sb16midi(struct address_info *hw_config); + +/* From sb_midi.c */ +void sb_midi_init(int model); +void sb_midi_interrupt(int dummy); + +/* From sb_mixer.c */ +void sb_setmixer (unsigned int port, unsigned int value); +int sb_getmixer (unsigned int port); +void sb_mixer_set_stereo(int mode); +int sb_mixer_init(int major_model); + +/* From opl3.c */ +int opl3_detect (int ioaddr); +long opl3_init(long mem_start); + +/* From sb_card.c */ +long attach_sb_card(long mem_start, struct address_info *hw_config); +int probe_sb(struct address_info *hw_config); + +/* From adlib_card.c */ +long attach_adlib_card(long mem_start, struct address_info *hw_config); +int probe_adlib(struct address_info *hw_config); + +/* From pas_card.c */ +long attach_pas_card(long mem_start, struct address_info *hw_config); +int probe_pas(struct address_info *hw_config); +int pas_set_intr(int mask); +int pas_remove_intr(int mask); +unsigned char pas_read(int ioaddr); +void pas_write(unsigned char data, int ioaddr); + +/* From pas_audio.c */ +void pas_pcm_interrupt(unsigned char status, int cause); +long pas_pcm_init(long mem_start, struct address_info *hw_config); + +/* From pas_mixer.c */ +int pas_init_mixer(void); + +/* From pas_midi.c */ +long pas_midi_init(long mem_start); +void pas_midi_interrupt(void); + +/* From gus_card.c */ +long attach_gus_card(long mem_start, struct address_info * hw_config); +int probe_gus(struct address_info *hw_config); +int gus_set_midi_irq(int num); +void gusintr(int); + +/* From gus_wave.c */ +int gus_wave_detect(int baseaddr); +long gus_wave_init(long mem_start, int irq, int dma); +void gus_voice_irq(void); +unsigned char gus_read8 (int reg); +void gus_write8(int reg, unsigned int data); +void guswave_dma_irq(void); +void gus_delay(void); +int gus_default_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg); + +/* From gus_midi.c */ +long gus_midi_init(long mem_start); +void gus_midi_interrupt(int dummy); + +/* From mpu401.c */ +long attach_mpu401(long mem_start, struct address_info * hw_config); +int probe_mpu401(struct address_info *hw_config); + +/* From opl3.c */ +void enable_opl3_mode(int left, int right, int both); + +/* From patmgr.c */ +int pmgr_open(int dev); +void pmgr_release(int dev); +int pmgr_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count); +int pmgr_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count); +int pmgr_access(int dev, struct patmgr_info *rec); +int pmgr_inform(int dev, int event, unsigned long parm1, unsigned long parm2, + unsigned long parm3, unsigned long parm4); + +/* From ics2101.c */ +long ics2101_mixer_init(long mem_start); diff --git a/sys/i386/isa/sound/sound_config.h b/sys/i386/isa/sound/sound_config.h new file mode 100644 index 0000000..e692eae --- /dev/null +++ b/sys/i386/isa/sound/sound_config.h @@ -0,0 +1,242 @@ +/* sound_config.h + * + * A driver for Soundcards, misc configuration parameters. + * + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "local.h" + + +#undef CONFIGURE_SOUNDCARD +#undef DYNAMIC_BUFFER + +#ifdef KERNEL_SOUNDCARD +#define CONFIGURE_SOUNDCARD +#define DYNAMIC_BUFFER +#undef LOADABLE_SOUNDCARD +#endif + +#ifdef EXCLUDE_SEQUENCER +#ifndef EXCLUDE_MIDI +#define EXCLUDE_MIDI +#endif +#ifndef EXCLUDE_YM3812 +#define EXCLUDE_YM3812 +#endif +#ifndef EXCLUDE_OPL3 +#define EXCLUDE_OPL3 +#endif +#endif + +#ifndef SND_DEFAULT_ENABLE +#define SND_DEFAULT_ENABLE 1 +#endif + +/** UWM - new MIDI stuff **/ + +#ifdef EXCLUDE_CHIP_MIDI +#ifndef EXCLUDE_PRO_MIDI +#define EXCLUDE_PRO_MIDI +#endif +#endif + +/** UWM - stuff **/ + +#if defined(EXCLUDE_SEQUENCER) && defined(EXCLUDE_AUDIO) +#undef CONFIGURE_SOUNDCARD +#endif + +#ifdef CONFIGURE_SOUNDCARD + +/* ****** IO-address, DMA and IRQ settings **** + +If your card has nonstandard I/O address or IRQ number, change defines + for the following settings in your kernel Makefile */ + +#ifndef SBC_BASE +#define SBC_BASE 0x220 /* 0x220 is the factory default. */ +#endif + +#ifndef SBC_IRQ +#define SBC_IRQ 7 /* IQR7 is the factory default. */ +#endif + +#ifndef SBC_DMA +#define SBC_DMA 1 +#endif + +#ifndef SB16_DMA +#define SB16_DMA 6 +#endif + +#ifndef SB16MIDI_BASE +#define SB16MIDI_BASE 0x300 +#endif + +#ifndef PAS_BASE +#define PAS_BASE 0x388 +#endif + +#ifndef PAS_IRQ +#define PAS_IRQ 5 +#endif + +#ifndef PAS_DMA +#define PAS_DMA 3 +#endif + +#ifndef GUS_BASE +#define GUS_BASE 0x220 +#endif + +#ifndef GUS_IRQ +#define GUS_IRQ 15 +#endif + +#ifndef GUS_MIDI_IRQ +#define GUS_MIDI_IRQ GUS_IRQ +#endif + +#ifndef GUS_DMA +#define GUS_DMA 6 +#endif + +#ifndef MPU_BASE +#define MPU_BASE 0x330 +#endif + +#ifndef MPU_IRQ +#define MPU_IRQ 6 +#endif + +#ifndef MAX_REALTIME_FACTOR +#define MAX_REALTIME_FACTOR 4 +#endif + +/************* PCM DMA buffer sizes *******************/ + +/* If you are using high playback or recording speeds, the default buffersize + is too small. DSP_BUFFSIZE must be 64k or less. + + A rule of thumb is 64k for PAS16, 32k for PAS+, 16k for SB Pro and + 4k for SB. + + If you change the DSP_BUFFSIZE, don't modify this file. + Use the make config command instead. */ + +#ifndef DSP_BUFFSIZE +#define DSP_BUFFSIZE (4096) +#endif + +#ifndef DSP_BUFFCOUNT +#define DSP_BUFFCOUNT 2 /* 2 is recommended. */ +#endif + +#define DMA_AUTOINIT 0x10 + +#define FM_MONO 0x388 /* This is the I/O address used by AdLib */ + +/* SEQ_MAX_QUEUE is the maximum number of sequencer events buffered by the + driver. (There is no need to alter this) */ +#define SEQ_MAX_QUEUE 1024 + +#define SBFM_MAXINSTR (256) /* Size of the FM Instrument + bank */ +/* 128 instruments for general MIDI setup and 16 unassigned */ + +#define SND_NDEVS 50 /* Number of supported devices */ +#define SND_DEV_CTL 0 /* Control port /dev/mixer */ +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* MIDI input /dev/midin (not implemented + yet) */ +#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ +#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ +#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ +#define SND_DEV_STATUS 6 /* /dev/sndstatus */ + +/* UWM ... note add new MIDI devices here.. + * Also do not forget to add table midi_supported[] + * Minor numbers for on-chip midi devices start from 15.. and + * should be contiguous.. viz. 15,16,17.... + * ERROR!!!!!!!!! NO NO. Minor numbers above 15 are reserved!!!!!! Hannu + * Also note the max # of midi devices as MAX_MIDI_DEV + */ + +#define CMIDI_DEV_PRO 15 /* Chip midi device == /dev/pro_midi */ + +/* + * Add other midis here... + . + . + . + . + */ + +#define DSP_DEFAULT_SPEED 8000 + +#define ON 1 +#define OFF 0 + +#define MAX_DSP_DEV 4 +#define MAX_MIXER_DEV 2 +#define MAX_SYNTH_DEV 3 +#define MAX_MIDI_DEV 4 + +struct fileinfo { + int mode; /* Open mode */ + }; + +struct address_info { + int io_base; + int irq; + int dma; +}; + +/* + * Process wakeup reasons + */ +#define WK_NONE 0x00 +#define WK_WAKEUP 0x01 +#define WK_TIMEOUT 0x02 +#define WK_SIGNAL 0x04 +#define WK_SLEEP 0x08 + +#define OPEN_READ 1 +#define OPEN_WRITE 2 +#define OPEN_READWRITE 3 + +#include "os.h" +#include "sound_calls.h" +#include "dev_table.h" + +#ifndef DEB +#define DEB(x) +#endif + +#endif diff --git a/sys/i386/isa/sound/sound_switch.c b/sys/i386/isa/sound/sound_switch.c new file mode 100644 index 0000000..a271a6a --- /dev/null +++ b/sys/i386/isa/sound/sound_switch.c @@ -0,0 +1,446 @@ +/* + * sound/sound_switch.c + * + * The system call switch + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +struct sbc_device + { + int usecount; + }; + +static struct sbc_device sbc_devices[SND_NDEVS] = +{ + {0}}; + +static int in_use = 0; /* Total # of open device files (excluding + + * minor 0) */ + +/* + * /dev/sndstatus -device + */ +static char *status_buf = NULL; +static int status_len, status_ptr; +static int status_busy = 0; + +static int +put_status (char *s) +{ + int l; + + for (l = 0; l < 256, s[l]; l++); /* l=strlen(s); */ + + if (status_len + l >= 4000) + return 0; + + memcpy (&status_buf[status_len], s, l); + status_len += l; + + return 1; +} + +static int +put_status_int (unsigned int val, int radix) +{ + int l, v; + + static char hx[] = "0123456789abcdef"; + char buf[11]; + + if (!val) + return put_status ("0"); + + l = 0; + buf[10] = 0; + + while (val) + { + v = val % radix; + val = val / radix; + + buf[9 - l] = hx[v]; + l++; + } + + if (status_len + l >= 4000) + return 0; + + memcpy (&status_buf[status_len], &buf[10 - l], l); + status_len += l; + + return 1; +} + +static void +init_status (void) +{ + /* + * Write the status information to the status_buf and update status_len. + * There is a limit of 4000 bytes for the data. + */ + + int i; + + status_ptr = 0; + + put_status ("Sound Driver:" SOUND_VERSION_STRING + " (" SOUND_CONFIG_DATE " " SOUND_CONFIG_BY "@" + SOUND_CONFIG_HOST "." SOUND_CONFIG_DOMAIN ")" + "\n"); + + if (!put_status ("Config options: ")) + return; + if (!put_status_int (SELECTED_SOUND_OPTIONS, 16)) + return; + + if (!put_status ("\n\nHW config: \n")) + return; + + for (i = 0; i < (num_sound_drivers - 1); i++) + { + if (!supported_drivers[i].enabled) + if (!put_status ("(")) + return; + + if (!put_status ("Type ")) + return; + if (!put_status_int (supported_drivers[i].card_type, 10)) + return; + if (!put_status (": ")) + return; + if (!put_status (supported_drivers[i].name)) + return; + if (!put_status (" at 0x")) + return; + if (!put_status_int (supported_drivers[i].config.io_base, 16)) + return; + if (!put_status (" irq ")) + return; + if (!put_status_int (supported_drivers[i].config.irq, 10)) + return; + if (!put_status (" drq ")) + return; + if (!put_status_int (supported_drivers[i].config.dma, 10)) + return; + + if (!supported_drivers[i].enabled) + if (!put_status (")")) + return; + + if (!put_status ("\n")) + return; + } + + if (!put_status ("\nPCM devices:\n")) + return; + + for (i = 0; i < num_dspdevs; i++) + { + if (!put_status_int (i, 10)) + return; + if (!put_status (": ")) + return; + if (!put_status (dsp_devs[i]->name)) + return; + if (!put_status ("\n")) + return; + } + + if (!put_status ("\nSynth devices:\n")) + return; + + for (i = 0; i < num_synths; i++) + { + if (!put_status_int (i, 10)) + return; + if (!put_status (": ")) + return; + if (!put_status (synth_devs[i]->info->name)) + return; + if (!put_status ("\n")) + return; + } + + if (!put_status ("\nMidi devices:\n")) + return; + + for (i = 0; i < num_midis; i++) + { + if (!put_status_int (i, 10)) + return; + if (!put_status (": ")) + return; + if (!put_status (midi_devs[i]->info.name)) + return; + if (!put_status ("\n")) + return; + } + + if (num_mixers) + { + if (!put_status ("\nMixer(s) installed\n")) + return; + } + else + { + if (!put_status ("\nNo mixers installed\n")) + return; + } +} + +static int +read_status (snd_rw_buf * buf, int count) +{ + /* + * Return at most 'count' bytes from the status_buf. + */ + int l, c; + + l = count; + c = status_len - status_ptr; + + if (l > c) + l = c; + if (l <= 0) + return 0; + + COPY_TO_USER (buf, 0, &status_buf[status_ptr], l); + status_ptr += l; + + return l; +} + +int +sound_read_sw (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + DEB (printk ("sound_read_sw(dev=%d, count=%d)\n", dev, count)); + + switch (dev & 0x0f) + { + case SND_DEV_STATUS: + return read_status (buf, count); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + return audio_read (dev, file, buf, count); + break; + + case SND_DEV_SEQ: + return sequencer_read (dev, file, buf, count); + break; + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + return MIDIbuf_read (dev, file, buf, count); +#endif + + default: + printk ("Sound: Undefined minor device %d\n", dev); + } + + return RET_ERROR (EPERM); +} + +int +sound_write_sw (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + + DEB (printk ("sound_write_sw(dev=%d, count=%d)\n", dev, count)); + + switch (dev & 0x0f) + { + + case SND_DEV_SEQ: + return sequencer_write (dev, file, buf, count); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + return audio_write (dev, file, buf, count); + break; + + default: + return RET_ERROR (EPERM); + } + + return count; +} + +int +sound_open_sw (int dev, struct fileinfo *file) +{ + int retval; + + DEB (printk ("sound_open_sw(dev=%d) : usecount=%d\n", dev, sbc_devices[dev].usecount)); + + if ((dev >= SND_NDEVS) || (dev < 0)) + { + printk ("Invalid minor device %d\n", dev); + return RET_ERROR (ENXIO); + } + + switch (dev & 0x0f) + { + case SND_DEV_STATUS: + if (status_busy) + return RET_ERROR (EBUSY); + status_busy = 1; + if ((status_buf = (char *) KERNEL_MALLOC (4000)) == NULL) + return RET_ERROR (EIO); + status_len = status_ptr = 0; + init_status (); + break; + + case SND_DEV_CTL: + return 0; + break; + + case SND_DEV_SEQ: + if ((retval = sequencer_open (dev, file)) < 0) + return retval; + break; + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + if ((retval = MIDIbuf_open (dev, file)) < 0) + return retval; + break; +#endif + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + if ((retval = audio_open (dev, file)) < 0) + return retval; + break; + + default: + printk ("Invalid minor device %d\n", dev); + return RET_ERROR (ENXIO); + } + + sbc_devices[dev].usecount++; + in_use++; + + return 0; +} + +void +sound_release_sw (int dev, struct fileinfo *file) +{ + + DEB (printk ("sound_release_sw(dev=%d)\n", dev)); + + switch (dev & 0x0f) + { + case SND_DEV_STATUS: + if (status_buf) + KERNEL_FREE (status_buf); + status_buf = NULL; + status_busy = 0; + break; + + case SND_DEV_CTL: + break; + + case SND_DEV_SEQ: + sequencer_release (dev, file); + break; + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + MIDIbuf_release (dev, file); + break; +#endif + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + audio_release (dev, file); + break; + + default: + printk ("Sound error: Releasing unknown device 0x%02x\n", dev); + } + + sbc_devices[dev].usecount--; + in_use--; +} + +int +sound_ioctl_sw (int dev, struct fileinfo *file, + unsigned int cmd, unsigned long arg) +{ + DEB (printk ("sound_ioctl_sw(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg)); + + switch (dev & 0x0f) + { + + case SND_DEV_CTL: + + if (!num_mixers) + return RET_ERROR (ENXIO); + + if ((dev >> 4) >= num_mixers) + return RET_ERROR (ENXIO); + + return mixer_devs[dev >> 4]->ioctl (dev >> 4, cmd, arg); + break; + + case SND_DEV_SEQ: + return sequencer_ioctl (dev, file, cmd, arg); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + case SND_DEV_AUDIO: + return audio_ioctl (dev, file, cmd, arg); + break; + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + return MIDIbuf_ioctl (dev, file, cmd, arg); + break; +#endif + + default: + return RET_ERROR (EPERM); + break; + } + + return RET_ERROR (EPERM); +} + +#endif diff --git a/sys/i386/isa/sound/soundcard.c b/sys/i386/isa/sound/soundcard.c new file mode 100644 index 0000000..bc44b00 --- /dev/null +++ b/sys/i386/isa/sound/soundcard.c @@ -0,0 +1,395 @@ +/* + * sound/386bsd/soundcard.c + * + * Soundcard driver for FreeBSD. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "dev_table.h" + +u_int snd1_imask; +u_int snd2_imask; +u_int snd3_imask; +u_int snd4_imask; +u_int snd5_imask; +u_int snd6_imask; +u_int snd7_imask; +u_int snd8_imask; +u_int snd9_imask; + +#define FIX_RETURN(ret) {if ((ret)<0) return -(ret); else return 0;} + +static int timer_running = 0; + +static int soundcards_installed = 0; /* Number of installed + * soundcards */ +static int soundcard_configured = 0; +extern char *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern int snd_raw_count[MAX_DSP_DEV]; + +static struct fileinfo files[SND_NDEVS]; + +int sndprobe (struct isa_device *dev); +int sndattach (struct isa_device *dev); +int sndopen (dev_t dev, int flags); +int sndclose (dev_t dev, int flags); +int sndioctl (dev_t dev, int cmd, caddr_t arg, int mode); +int sndread (int dev, struct uio *uio); +int sndwrite (int dev, struct uio *uio); +int sndselect (int dev, int rw); +static void sound_mem_init(void); + +unsigned +long +get_time(void) +{ +extern struct timeval time; +struct timeval timecopy; +int x; + + x = splclock(); + timecopy = time; + splx(x); + return timecopy.tv_usec/(1000000/HZ) + + (unsigned long)timecopy.tv_sec*HZ; +} + + +int +sndread (int dev, struct uio *buf) +{ + int count = buf->uio_resid; + + dev = minor (dev); + + FIX_RETURN (sound_read_sw (dev, &files[dev], buf, count)); +} + +int +sndwrite (int dev, struct uio *buf) +{ + int count = buf->uio_resid; + + dev = minor (dev); + + FIX_RETURN (sound_write_sw (dev, &files[dev], buf, count)); +} + +int +sndopen (dev_t dev, int flags) +{ + int retval; + + dev = minor (dev); + + if (!soundcard_configured && dev) + { + printk ("SoundCard Error: The soundcard system has not been configured\n"); + FIX_RETURN (-ENODEV); + } + + files[dev].mode = 0; + + if (flags & FREAD && flags & FWRITE) + files[dev].mode = OPEN_READWRITE; + else if (flags & FREAD) + files[dev].mode = OPEN_READ; + else if (flags & FWRITE) + files[dev].mode = OPEN_WRITE; + + FIX_RETURN(sound_open_sw (dev, &files[dev])); +} + +int +sndclose (dev_t dev, int flags) +{ + + dev = minor (dev); + + sound_release_sw(dev, &files[dev]); + FIX_RETURN (0); +} + +int +sndioctl (dev_t dev, int cmd, caddr_t arg, int mode) +{ + dev = minor (dev); + + FIX_RETURN (sound_ioctl_sw (dev, &files[dev], cmd, (unsigned int) arg)); +} + +int +sndselect (int dev, int rw) +{ + dev = minor (dev); + + DEB (printk ("sound_ioctl(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg)); + + FIX_RETURN (0); +} + +static unsigned short +ipri_to_irq (unsigned short ipri) +{ + /* + * Converts the ipri (bitmask) to the corresponding irq number + */ + int irq; + + for (irq = 0; irq < 16; irq++) + if (ipri == (1 << irq)) + return irq; + + return -1; /* Invalid argument */ +} + +int +sndprobe (struct isa_device *dev) +{ + struct address_info hw_config; + + hw_config.io_base = dev->id_iobase; + hw_config.irq = ipri_to_irq (dev->id_irq); + hw_config.dma = dev->id_drq; + + return sndtable_probe (dev->id_unit, &hw_config); +} + +int +sndattach (struct isa_device *dev) +{ + int i; + static int dsp_initialized = 0; + static int midi_initialized = 0; + static int seq_initialized = 0; + static int generic_midi_initialized = 0; + unsigned long mem_start = 0xefffffffUL; + struct address_info hw_config; + + hw_config.io_base = dev->id_iobase; + hw_config.irq = ipri_to_irq (dev->id_irq); + hw_config.dma = dev->id_drq; + + if (dev->id_unit) /* Card init */ + if (!sndtable_init_card (dev->id_unit, &hw_config)) + { + printf (" <Driver not configured>"); + return FALSE; + } + + /* + * Init the high level sound driver + */ + + if (!(soundcards_installed = sndtable_get_cardcount ())) + { + printf (" <No such hardware>"); + return FALSE; /* No cards detected */ + } + + printf("\n"); + +#ifndef EXCLUDE_AUDIO + soundcard_configured = 1; + if (num_dspdevs) + sound_mem_init (); +#endif + + if (num_dspdevs && !dsp_initialized) /* Audio devices present */ + { + dsp_initialized = 1; + mem_start = DMAbuf_init (mem_start); + mem_start = audio_init (mem_start); + } + +/** UWM stuff **/ + +#ifndef EXCLUDE_CHIP_MIDI + + if (!generic_midi_initialized) + { + generic_midi_initialized = 1; + mem_start = CMIDI_init (mem_start); + } + +#endif + +#ifndef EXCLUDE_MPU401 + if (num_midis && !midi_initialized) + { + midi_initialized = 1; + mem_start = MIDIbuf_init (mem_start); + } +#endif + + if ((num_midis + num_synths) && !seq_initialized) + { + seq_initialized = 1; + mem_start = sequencer_init (mem_start); + } + + return TRUE; +} + +void +tenmicrosec (void) +{ + int i; + + for (i = 0; i < 16; i++) + inb (0x80); +} + +#ifdef EXCLUDE_GUS +void +gusintr (int unit) +{ + return; +} +#endif + +void +request_sound_timer (int count) +{ + static int current = 0; + int tmp = count; + + if (count < 0) + timeout ((timeout_func_t)sequencer_timer, 0, -count); + else + { + + if (count < current) + current = 0; /* Timer restarted */ + + count = count - current; + + current = tmp; + + if (!count) + count = 1; + + timeout ((timeout_func_t)sequencer_timer, 0, count); + } + timer_running = 1; +} + +void +sound_stop_timer (void) +{ + if (timer_running) + untimeout ((timeout_func_t)sequencer_timer, 0); + timer_running = 0; +} + +#ifndef EXCLUDE_AUDIO +static void +sound_mem_init (void) +{ + int i, dev; + unsigned long dma_pagesize; + static unsigned long dsp_init_mask = 0; + + for (dev = 0; dev < num_dspdevs; dev++) /* Enumerate devices */ + if (!(dsp_init_mask & (1 << dev))) /* Not already done */ + if (sound_buffcounts[dev] > 0 && sound_dsp_dmachan[dev] > 0) + { + dsp_init_mask |= (1 << dev); + + if (sound_dma_automode[dev]) + sound_buffcounts[dev] = 1; + + if (sound_dsp_dmachan[dev] > 3 && sound_buffsizes[dev] > 65536) + dma_pagesize = 131072; /* 128k */ + else + dma_pagesize = 65536; + + /* More sanity checks */ + + if (sound_buffsizes[dev] > dma_pagesize) + sound_buffsizes[dev] = dma_pagesize; + sound_buffsizes[dev] &= ~0xfff; /* Truncate to n*4k */ + if (sound_buffsizes[dev] < 4096) + sound_buffsizes[dev] = 4096; + + /* Now allocate the buffers */ + + for (snd_raw_count[dev] = 0; snd_raw_count[dev] < sound_buffcounts[dev]; snd_raw_count[dev]++) + { + char *tmpbuf = contigmalloc (sound_buffsizes[dev], M_DEVBUF, M_NOWAIT, + 0xFFFFFFul, 0ul, dma_pagesize - 1); + + if (tmpbuf == NULL) + { + printk ("snd: Unable to allocate %d bytes of buffer\n", + sound_buffsizes[dev]); + return; + } + + snd_raw_buf[dev][snd_raw_count[dev]] = tmpbuf; + /* + * Use virtual address as the physical address, since + * isa_dmastart performs the phys address computation. + */ + snd_raw_buf_phys[dev][snd_raw_count[dev]] = + (unsigned long) snd_raw_buf[dev][snd_raw_count[dev]]; + } + } /* for dev */ + +} + +#endif + +struct isa_driver snddriver = +{sndprobe, sndattach, "snd"}; + +int +snd_ioctl_return (int *addr, int value) +{ + if (value < 0) + return value; /* Error */ + suword (addr, value); + return 0; +} + +int +snd_set_irq_handler (int interrupt_level, void(*hndlr)(int)) +{ + return 1; +} + +void +snd_release_irq(int vect) +{ +} + +#endif diff --git a/sys/i386/isa/sound/tuning.h b/sys/i386/isa/sound/tuning.h new file mode 100644 index 0000000..23086af --- /dev/null +++ b/sys/i386/isa/sound/tuning.h @@ -0,0 +1,32 @@ +/* + * $Id$ + */ +#ifdef SEQUENCER_C + +unsigned short semitone_tuning[24] = +{ +/* 0 */ 10000, 10595, 11225, 11892, 12599, 13348, 14142, 14983, +/* 8 */ 15874, 16818, 17818, 18877, 20000, 21189, 22449, 23784, +/* 16 */ 25198, 26697, 28284, 29966, 31748, 33636, 35636, 37755 +}; + +unsigned short cent_tuning[100] = +{ +/* 0 */ 10000, 10006, 10012, 10017, 10023, 10029, 10035, 10041, +/* 8 */ 10046, 10052, 10058, 10064, 10070, 10075, 10081, 10087, +/* 16 */ 10093, 10099, 10105, 10110, 10116, 10122, 10128, 10134, +/* 24 */ 10140, 10145, 10151, 10157, 10163, 10169, 10175, 10181, +/* 32 */ 10187, 10192, 10198, 10204, 10210, 10216, 10222, 10228, +/* 40 */ 10234, 10240, 10246, 10251, 10257, 10263, 10269, 10275, +/* 48 */ 10281, 10287, 10293, 10299, 10305, 10311, 10317, 10323, +/* 56 */ 10329, 10335, 10341, 10347, 10353, 10359, 10365, 10371, +/* 64 */ 10377, 10383, 10389, 10395, 10401, 10407, 10413, 10419, +/* 72 */ 10425, 10431, 10437, 10443, 10449, 10455, 10461, 10467, +/* 80 */ 10473, 10479, 10485, 10491, 10497, 10503, 10509, 10515, +/* 88 */ 10521, 10528, 10534, 10540, 10546, 10552, 10558, 10564, +/* 96 */ 10570, 10576, 10582, 10589 +}; +#else +extern unsigned short semitone_tuning[24]; +extern unsigned short cent_tuning[100]; +#endif diff --git a/sys/i386/isa/sound/ulaw.h b/sys/i386/isa/sound/ulaw.h new file mode 100644 index 0000000..9984e36 --- /dev/null +++ b/sys/i386/isa/sound/ulaw.h @@ -0,0 +1,72 @@ +/* + * $Id$ + */ +static unsigned char ulaw_dsp[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, + 5, 9, 13, 17, 21, 25, 29, 33, + 37, 41, 45, 49, 53, 57, 61, 65, + 68, 70, 72, 74, 76, 78, 80, 82, + 84, 86, 88, 90, 92, 94, 96, 98, + 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, + 115, 116, 116, 117, 117, 118, 118, 119, + 119, 120, 120, 121, 121, 122, 122, 123, + 123, 123, 124, 124, 124, 124, 125, 125, + 125, 125, 126, 126, 126, 126, 127, 127, + 127, 127, 127, 127, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 252, 248, 244, 240, 236, 232, 228, 224, + 220, 216, 212, 208, 204, 200, 196, 192, + 189, 187, 185, 183, 181, 179, 177, 175, + 173, 171, 169, 167, 165, 163, 161, 159, + 157, 156, 155, 154, 153, 152, 151, 150, + 149, 148, 147, 146, 145, 144, 143, 142, + 142, 141, 141, 140, 140, 139, 139, 138, + 138, 137, 137, 136, 136, 135, 135, 134, + 134, 134, 133, 133, 133, 133, 132, 132, + 132, 132, 131, 131, 131, 131, 130, 130, + 130, 130, 130, 130, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, +}; + +static unsigned char dsp_ulaw[] = { + 31, 31, 31, 32, 32, 32, 32, 33, + 33, 33, 33, 34, 34, 34, 34, 35, + 35, 35, 35, 36, 36, 36, 36, 37, + 37, 37, 37, 38, 38, 38, 38, 39, + 39, 39, 39, 40, 40, 40, 40, 41, + 41, 41, 41, 42, 42, 42, 42, 43, + 43, 43, 43, 44, 44, 44, 44, 45, + 45, 45, 45, 46, 46, 46, 46, 47, + 47, 47, 47, 48, 48, 49, 49, 50, + 50, 51, 51, 52, 52, 53, 53, 54, + 54, 55, 55, 56, 56, 57, 57, 58, + 58, 59, 59, 60, 60, 61, 61, 62, + 62, 63, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 81, 83, 85, 87, 89, + 91, 93, 95, 99, 103, 107, 111, 119, + 255, 247, 239, 235, 231, 227, 223, 221, + 219, 217, 215, 213, 211, 209, 207, 206, + 205, 204, 203, 202, 201, 200, 199, 198, + 197, 196, 195, 194, 193, 192, 191, 191, + 190, 190, 189, 189, 188, 188, 187, 187, + 186, 186, 185, 185, 184, 184, 183, 183, + 182, 182, 181, 181, 180, 180, 179, 179, + 178, 178, 177, 177, 176, 176, 175, 175, + 175, 175, 174, 174, 174, 174, 173, 173, + 173, 173, 172, 172, 172, 172, 171, 171, + 171, 171, 170, 170, 170, 170, 169, 169, + 169, 169, 168, 168, 168, 168, 167, 167, + 167, 167, 166, 166, 166, 166, 165, 165, + 165, 165, 164, 164, 164, 164, 163, 163, + 163, 163, 162, 162, 162, 162, 161, 161, + 161, 161, 160, 160, 160, 160, 159, 159, +}; diff --git a/sys/i386/isa/spkr.c b/sys/i386/isa/spkr.c new file mode 100644 index 0000000..4f9f2fa --- /dev/null +++ b/sys/i386/isa/spkr.c @@ -0,0 +1,541 @@ +/* + * spkr.c -- device driver for console speaker + * + * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993 + * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su> + * + * $Id: spkr.c,v 1.8 1994/04/21 14:21:50 sos Exp $ + */ + +#include "speaker.h" + +#if NSPEAKER > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/buf.h> +#include <sys/uio.h> +#include <i386/isa/isa.h> +#include <i386/isa/timerreg.h> +#include <machine/speaker.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_SPKR 0x03 /* turn these PPI bits on to pass sound */ +#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 */ + +#define SPKRPRI PSOCK +static char endtone, endrest; + +static void tone(thz, ticks) +/* emit tone of frequency thz for given number of ticks */ +unsigned int thz, ticks; +{ + unsigned int divisor = TIMER_CLK / thz; + int sps; + +#ifdef DEBUG + (void) printf("tone: thz=%d ticks=%d\n", thz, ticks); +#endif /* DEBUG */ + + /* set timer to generate clicks at given frequency in Hertz */ + sps = spltty(); + + if (acquire_timer2(PIT_MODE)) { + /* enter list of waiting procs ??? */ + return; + } + outb(TIMER_CNTR2, (divisor & 0xff)); /* send lo byte */ + outb(TIMER_CNTR2, (divisor >> 8)); /* send hi byte */ + splx(sps); + + /* turn the speaker on */ + outb(IO_PPI, inb(IO_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. + */ + (void) tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks); + outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR); + release_timer2(); +} + +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 + (void) printf("rest: %d\n", ticks); +#endif /* DEBUG */ + (void) tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks); +} + +/**************** PLAY STRING INTERPRETER BEGINS HERE ********************** + * + * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement; + * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave- + * tracking facility are 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--) + { + /* See the BUGS section in the man page for discussion */ + 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 + (void) 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, oldfill, 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 + (void) 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++; + } + + /* ...and/or a slur mark */ + oldfill = fill; + if (cp[1] == '_') + { + fill = LEGATO; + ++cp; + slen--; + } + + /* time to emit the actual tone */ + playtone(pitch, timeval, sustain); + + fill = oldfill; + 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++; + } + oldfill = fill; + if (cp[1] == '_') + { + fill = LEGATO; + ++cp; + slen--; + } + playtone(pitch - 1, value, sustain); + fill = oldfill; + 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 = FALSE; /* exclusion flag */ +static struct buf *spkr_inbuf; /* incoming buf */ + +int spkropen(dev) +dev_t dev; +{ +#ifdef DEBUG + (void) printf("spkropen: entering with dev = %x\n", dev); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else if (spkr_active) + return(EBUSY); + else + { +#ifdef DEBUG + (void) printf("spkropen: about to perform play initialization\n"); +#endif /* DEBUG */ + playinit(); + spkr_inbuf = geteblk(DEV_BSIZE); + spkr_active = TRUE; + return(0); + } +} + +int spkrwrite(dev, uio) +dev_t dev; +struct uio *uio; +{ +#ifdef DEBUG + printf("spkrwrite: entering with dev = %x, count = %d\n", + dev, uio->uio_resid); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else if (uio->uio_resid > DEV_BSIZE) /* prevent system crashes */ + return(E2BIG); + else + { + unsigned n; + char *cp; + int error; + + n = uio->uio_resid; + cp = spkr_inbuf->b_un.b_addr; + if (!(error = uiomove(cp, n, uio))) + playstring(cp, n); + return(error); + } +} + +int spkrclose(dev) +dev_t dev; +{ +#ifdef DEBUG + (void) printf("spkrclose: entering with dev = %x\n", dev); +#endif /* DEBUG */ + + if (minor(dev) != 0) + return(ENXIO); + else + { + wakeup((caddr_t)&endtone); + wakeup((caddr_t)&endrest); + brelse(spkr_inbuf); + spkr_active = FALSE; + return(0); + } +} + +int spkrioctl(dev, cmd, cmdarg) +dev_t dev; +int cmd; +caddr_t cmdarg; +{ +#ifdef DEBUG + (void) printf("spkrioctl: entering with dev = %x, cmd = %x\n"); +#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); + return 0; + } + 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); + } + return(0); + } + return(EINVAL); +} + +#endif /* NSPEAKER > 0 */ +/* spkr.c ends here */ diff --git a/sys/i386/isa/syscons.c b/sys/i386/isa/syscons.c new file mode 100644 index 0000000..eae66d6 --- /dev/null +++ b/sys/i386/isa/syscons.c @@ -0,0 +1,2707 @@ +/*- + * Copyright (c) 1992-1994 Søren Schmidt + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz and 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. + * + * $Id: syscons.c,v 1.51 1994/08/17 19:32:23 sos Exp $ + */ + +#include "sc.h" + +#if NSC > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/ioctl.h> +#include <sys/proc.h> +#include <sys/user.h> +#include <sys/tty.h> +#include <sys/uio.h> +#include <sys/callout.h> +#include <sys/kernel.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <machine/console.h> +#include <machine/psl.h> +#include <machine/frame.h> +#include <machine/pc/display.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/timerreg.h> +#include <i386/isa/kbdtables.h> +#include <i386/i386/cons.h> + +#if !defined(NCONS) +#define NCONS 12 +#endif + +#if !defined(NO_HARDFONTS) +#include <i386/isa/iso8859.font> +#endif + +/* status flags */ +#define LOCK_KEY_MASK 0x0000F +#define LED_MASK 0x00007 +#define UNKNOWN_MODE 0x00010 +#define KBD_RAW_MODE 0x00020 +#define SWITCH_WAIT_REL 0x00040 +#define SWITCH_WAIT_ACQ 0x00080 + +/* video hardware memory addresses */ +#define VIDEOMEM 0x000A0000 + +/* misc defines */ +#define MAX_ESC_PAR 5 +#define TEXT80x25 1 +#define TEXT80x50 2 +#define LOAD 1 +#define SAVE 0 +#define COL 80 +#define ROW 25 +#define BELL_DURATION 5 +#define BELL_PITCH 800 +#define TIMER_FREQ 1193182 /* should be in isa.h */ +#define CONSOLE_BUFSIZE 1024 +#define PCBURST 128 +#define FONT_8_LOADED 0x001 +#define FONT_14_LOADED 0x002 +#define FONT_16_LOADED 0x004 + +/* defines related to hardware addresses */ +#define MONO_BASE 0x3B4 /* crt controller base mono */ +#define COLOR_BASE 0x3D4 /* crt controller base color */ +#define ATC IO_VGA+0x00 /* attribute controller */ +#define TSIDX IO_VGA+0x04 /* timing sequencer idx */ +#define TSREG IO_VGA+0x05 /* timing sequencer data */ +#define PIXMASK IO_VGA+0x06 /* pixel write mask */ +#define PALRADR IO_VGA+0x07 /* palette read address */ +#define PALWADR IO_VGA+0x08 /* palette write address */ +#define PALDATA IO_VGA+0x09 /* palette data register */ +#define GDCIDX IO_VGA+0x0E /* graph data controller idx */ +#define GDCREG IO_VGA+0x0F /* graph data controller data */ + +/* special characters */ +#define cntlc 0x03 +#define cntld 0x04 +#define bs 0x08 +#define lf 0x0a +#define cr 0x0d +#define del 0x7f + +typedef struct term_stat { + int esc; /* processing escape sequence */ + int num_param; /* # of parameters to ESC */ + int last_param; /* last parameter # */ + int param[MAX_ESC_PAR]; /* contains ESC parameters */ + int cur_attr; /* current attributes */ + int std_attr; /* normal attributes */ + int rev_attr; /* reverse attributes */ +} term_stat; + +typedef struct scr_stat { + u_short *crt_base; /* address of screen memory */ + u_short *scr_buf; /* buffer when off screen */ + u_short *crtat; /* cursor address */ + int xpos; /* current X position */ + int ypos; /* current Y position */ + int xsize; /* X size */ + int ysize; /* Y size */ + term_stat term; /* terminal emulation stuff */ + char cursor_start; /* cursor start line # */ + char cursor_end; /* cursor end line # */ + u_char border; /* border color */ + u_short bell_duration; + u_short bell_pitch; + u_short status; /* status (bitfield) */ + u_short mode; /* mode */ + pid_t pid; /* pid of controlling proc */ + struct proc *proc; /* proc* of controlling proc */ + struct vt_mode smode; /* switch mode */ +} scr_stat; + +typedef struct default_attr { + int std_attr; /* normal attributes */ + int rev_attr; /* reverse attributes */ +} default_attr; + +static default_attr user_default = { + (FG_LIGHTGREY | BG_BLACK) << 8, + (FG_BLACK | BG_LIGHTGREY) << 8 +}; + +static default_attr kernel_default = { + (FG_WHITE | BG_BLACK) << 8, + (FG_BLACK | BG_LIGHTGREY) << 8 +}; + +static scr_stat console[NCONS]; +static scr_stat *cur_console = &console[0]; +static scr_stat *new_scp, *old_scp; +static term_stat kernel_console; +static default_attr *current_default; +static int console_buffer_count; +static char console_buffer[CONSOLE_BUFSIZE]; +static int switch_in_progress = 0; +static u_short *crtat = 0; +static u_int crtc_addr = MONO_BASE; +static char crtc_vga = 0; +static u_char shfts = 0, ctls = 0, alts = 0, agrs = 0, metas = 0; +static u_char nlkcnt = 0, clkcnt = 0, slkcnt = 0, alkcnt = 0; +static char *font_8 = NULL, *font_14 = NULL, *font_16 = NULL; +static int fonts_loaded = 0; +static char palette[3*256]; +static const u_int n_fkey_tab = sizeof(fkey_tab) / sizeof(*fkey_tab); +static int cur_cursor_pos = -1; +static char in_putc = 0; +static char polling = 0; +#if ASYNCH +static u_char kbd_reply = 0; +#endif +static int delayed_next_scr; +static char saved_console = -1; /* saved console number */ +static long scrn_blank_time = 0; /* screen saver timeout value */ +static int scrn_blanked = 0; /* screen saver active flag */ +static int scrn_saver = 0; /* screen saver routine */ +static long scrn_time_stamp; +static u_char scr_map[256]; + +/* function prototypes */ +int pcprobe(struct isa_device *dev); +int pcattach(struct isa_device *dev); +int pcopen(dev_t dev, int flag, int mode, struct proc *p); +int pcclose(dev_t dev, int flag, int mode, struct proc *p); +int pcread(dev_t dev, struct uio *uio, int flag); +int pcwrite(dev_t dev, struct uio *uio, int flag); +int pcparam(struct tty *tp, struct termios *t); +int pcioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p); +void pcxint(dev_t dev); +void pcstart(struct tty *tp); +void pccnprobe(struct consdev *cp); +void pccninit(struct consdev *cp); +void pccnputc(dev_t dev, char c); +int pccngetc(dev_t dev); +void scintr(int unit); +int pcmmap(dev_t dev, int offset, int nprot); +u_int sgetc(int noblock); +int getchar(void); +static void scinit(void); +static void scput(u_char c); +static u_int scgetc(int noblock); +static struct tty *get_tty_ptr(dev_t dev); +static scr_stat *get_scr_stat(dev_t dev); +static int get_scr_num(); +static void cursor_shape(int start, int end); +static void get_cursor_shape(int *start, int *end); +static void cursor_pos(int force); +static void clear_screen(scr_stat *scp); +static int switch_scr(u_int next_scr); +static void exchange_scr(void); +static void move_crsr(scr_stat *scp, int x, int y); +static void move_up(u_short *s, u_short *d, u_int len); +static void move_down(u_short *s, u_short *d, u_int len); +static void scan_esc(scr_stat *scp, u_char c); +static void ansi_put(scr_stat *scp, u_char c); +static u_char *get_fstr(u_int c, u_int *len); +static void update_leds(int which); +static void kbd_wait(void); +static void kbd_cmd(u_char command); +static void set_mode(scr_stat *scp); +static void set_border(int color); +static void copy_font(int direction, int segment, int size, char* font); +static void save_palette(void); +static void load_palette(void); +static void change_winsize(struct tty *tp, int x, int y); + +/* available screen savers */ +static void none_saver(int test); +static void blank_saver(int test); +static void fade_saver(int test); +static void star_saver(int test); +static void snake_saver(int test); + +static const struct { + char *name; + void (*routine)(); +} screen_savers[] = { + { "none", none_saver }, /* 0 */ + { "blank", blank_saver }, /* 1 */ + { "fade", fade_saver }, /* 2 */ + { "star", star_saver }, /* 3 */ + { "snake", snake_saver }, /* 4 */ +}; +#define SCRN_SAVER(arg) (*screen_savers[scrn_saver].routine)(arg) +#define NUM_SCRN_SAVERS (sizeof(screen_savers) / sizeof(screen_savers[0])) + +/* OS specific stuff */ +#if 0 +#define VIRTUAL_TTY(x) (pccons[x] = ttymalloc(pccons[x])) +#define CONSOLE_TTY (pccons[NCONS] = ttymalloc(pccons[NCONS])) +struct tty *pccons[NCONS+1]; +#else +#define VIRTUAL_TTY(x) &pccons[x] +#define CONSOLE_TTY &pccons[NCONS] +struct tty pccons[NCONS+1]; +#endif +#define timeout_t timeout_func_t +#define MONO_BUF (KERNBASE+0xB0000) +#define CGA_BUF (KERNBASE+0xB8000) +#include "ddb.h" +#if NDDB > 0 +#define DDB 1 +#endif +u_short *Crtat = (u_short *)MONO_BUF; +void consinit(void) {scinit();} + +struct isa_driver scdriver = { + pcprobe, pcattach, "sc", +}; + + +int +pcprobe(struct isa_device *dev) +{ + int i, retries = 5; + unsigned char val; + + /* Enable interrupts and keyboard controller */ + kbd_wait(); + outb(KB_STAT, KB_WRITE); + kbd_wait(); + outb(KB_DATA, KB_MODE); + + /* flush any noise in the buffer */ + while (inb(KB_STAT) & KB_BUF_FULL) { + DELAY(10); + (void) inb(KB_DATA); + } + + /* Reset keyboard hardware */ + while (retries--) { + kbd_wait(); + outb(KB_DATA, KB_RESET); + for (i=0; i<100000; i++) { + DELAY(10); + val = inb(KB_DATA); + if (val == KB_ACK || val == KB_ECHO) + goto gotres; + if (val == KB_RESEND) + break; + } + } +gotres: + if (!retries) + printf("scprobe: keyboard won't accept RESET command\n"); + else { +gotack: + DELAY(10); + while ((inb(KB_STAT) & KB_BUF_FULL) == 0) DELAY(10); + DELAY(10); + val = inb(KB_DATA); + if (val == KB_ACK) + goto gotack; + if (val != KB_RESET_DONE) + printf("scprobe: keyboard RESET failed %02x\n", val); + } + return (IO_KBDSIZE); +} + + +int +pcattach(struct isa_device *dev) +{ + scr_stat *scp; + int start = -1, end = -1, i; + + printf("sc%d: ", dev->id_unit); + if (crtc_vga) + if (crtc_addr == MONO_BASE) + printf("VGA mono"); + else + printf("VGA color"); + else + if (crtc_addr == MONO_BASE) + printf("MDA/hercules"); + else + printf("CGA/EGA"); + + if (NCONS > 1) + printf(" <%d virtual consoles>\n", NCONS); + else + printf("\n"); +#if defined(FAT_CURSOR) + start = 0; + end = 18; + if (crtc_vga) { +#else + if (crtc_vga) { + get_cursor_shape(&start, &end); +#endif +#if !defined(NO_HARDFONTS) + font_8 = font_8x8; + font_14 = font_8x14; + font_16 = font_8x16; + fonts_loaded = FONT_8_LOADED|FONT_14_LOADED|FONT_16_LOADED; + copy_font(LOAD, 1, 8, font_8); + copy_font(LOAD, 2, 14, font_14); + copy_font(LOAD, 0, 16, font_16); +#else + font_8 = (char *)malloc(8*256, M_DEVBUF, M_NOWAIT); + font_14 = (char *)malloc(14*256, M_DEVBUF, M_NOWAIT); + font_16 = (char *)malloc(16*256, M_DEVBUF, M_NOWAIT); + copy_font(SAVE, 0, 16, font_16); + fonts_loaded = FONT_16_LOADED; +#endif + save_palette(); + } + current_default = &user_default; + for (i = 0; i < NCONS; i++) { + scp = &console[i]; + scp->scr_buf = (u_short *)malloc(COL * ROW * 2, + M_DEVBUF, M_NOWAIT); + scp->mode = TEXT80x25; + scp->term.esc = 0; + scp->term.std_attr = current_default->std_attr; + scp->term.rev_attr = current_default->rev_attr; + scp->term.cur_attr = scp->term.std_attr; + scp->border = BG_BLACK; + scp->cursor_start = start; + scp->cursor_end = end; + scp->xsize = COL; + scp->ysize = ROW; + scp->bell_pitch = BELL_PITCH; + scp->bell_duration = BELL_DURATION; + scp->status = NLKED; + scp->pid = 0; + scp->proc = NULL; + scp->smode.mode = VT_AUTO; + if (i > 0) { + scp->crt_base = scp->crtat = scp->scr_buf; + fillw(scp->term.cur_attr|scr_map[0x20], + scp->scr_buf, COL*ROW); + } + } + /* get cursor going */ +#if defined(FAT_CURSOR) + cursor_shape(console[0].cursor_start, + console[0].cursor_end); +#endif + cursor_pos(1); + update_leds(console[0].status); + return 0; +} + + +static struct tty +*get_tty_ptr(dev_t dev) +{ + int unit = minor(dev); + + if (unit > NCONS) + return(NULL); + if (unit == NCONS) + return(CONSOLE_TTY); + return(VIRTUAL_TTY(unit)); +} + + +static scr_stat +*get_scr_stat(dev_t dev) +{ + int unit = minor(dev); + + if (unit > NCONS) + return(NULL); + if (unit == NCONS) + return(&console[0]); + return(&console[unit]); +} + + +static int +get_scr_num() +{ + int i = 0; + + while ((i < NCONS) && (cur_console != &console[i])) i++; + return i < NCONS ? i : 0; +} + + +int +pcopen(dev_t dev, int flag, int mode, struct proc *p) +{ + struct tty *tp = get_tty_ptr(dev); + + if (!tp) + return(ENXIO); + + tp->t_oproc = pcstart; + tp->t_param = pcparam; + tp->t_dev = dev; + if (!(tp->t_state & TS_ISOPEN)) { + ttychars(tp); + tp->t_iflag = TTYDEF_IFLAG; + tp->t_oflag = TTYDEF_OFLAG; + tp->t_cflag = TTYDEF_CFLAG; + tp->t_lflag = TTYDEF_LFLAG; + tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; + pcparam(tp, &tp->t_termios); + ttsetwater(tp); + } else if (tp->t_state&TS_XCLUDE && p->p_ucred->cr_uid != 0) + return(EBUSY); + tp->t_state |= TS_CARR_ON; + tp->t_cflag |= CLOCAL; + return((*linesw[tp->t_line].l_open)(dev, tp)); +} + + +int +pcclose(dev_t dev, int flag, int mode, struct proc *p) +{ + struct tty *tp = get_tty_ptr(dev); + struct scr_stat *scp; + + if (!tp) + return(ENXIO); + if (minor(dev) < NCONS) { + scp = get_scr_stat(tp->t_dev); + if (scp->status & SWITCH_WAIT_ACQ) + wakeup((caddr_t)&scp->smode); + scp->pid = 0; + scp->proc = NULL; + scp->smode.mode = VT_AUTO; + } + (*linesw[tp->t_line].l_close)(tp, flag); + ttyclose(tp); + return(0); +} + + +int +pcread(dev_t dev, struct uio *uio, int flag) +{ + struct tty *tp = get_tty_ptr(dev); + + if (!tp) + return(ENXIO); + return((*linesw[tp->t_line].l_read)(tp, uio, flag)); +} + + +int +pcwrite(dev_t dev, struct uio *uio, int flag) +{ + struct tty *tp = get_tty_ptr(dev); + + if (!tp) + return(ENXIO); + return((*linesw[tp->t_line].l_write)(tp, uio, flag)); +} + + +void +scintr(int unit) +{ + static struct tty *cur_tty; + int c, len; + u_char *cp; + + /* make screensaver happy */ + scrn_time_stamp = time.tv_sec; + if (scrn_blanked) + SCRN_SAVER(0); + + c = scgetc(1); + + cur_tty = VIRTUAL_TTY(get_scr_num()); + if (!(cur_tty->t_state & TS_ISOPEN)) + cur_tty = CONSOLE_TTY; + + if (!(cur_tty->t_state & TS_ISOPEN) || polling) + return; + + switch (c & 0xff00) { + case 0x0000: /* normal key */ + (*linesw[cur_tty->t_line].l_rint)(c & 0xFF, cur_tty); + break; + case NOKEY: /* nothing there */ + break; + case FKEY: /* function key, return string */ + if (cp = get_fstr((u_int)c, (u_int *)&len)) { + while (len-- > 0) + (*linesw[cur_tty->t_line].l_rint) + (*cp++ & 0xFF, cur_tty); + } + break; + case MKEY: /* meta is active, prepend ESC */ + (*linesw[cur_tty->t_line].l_rint)(0x1b, cur_tty); + (*linesw[cur_tty->t_line].l_rint)(c & 0xFF, cur_tty); + break; + } +} + + +int +pcparam(struct tty *tp, struct termios *t) +{ + int cflag = t->c_cflag; + + /* and copy to tty */ + tp->t_ispeed = t->c_ispeed; + tp->t_ospeed = t->c_ospeed; + tp->t_cflag = cflag; + return 0; +} + + +int +pcioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p) +{ + int i, error; + struct tty *tp; + struct trapframe *fp; + scr_stat *scp; + + tp = get_tty_ptr(dev); + if (!tp) + return ENXIO; + scp = get_scr_stat(tp->t_dev); + + switch (cmd) { /* process console hardware related ioctl's */ + + case CONS_BLANKTIME: /* set screen saver timeout (0 = no saver) */ + scrn_blank_time = *(int*)data; + return 0; + case CONS_SSAVER: /* set screen saver */ + { + register ssaver_t *sav = (ssaver_t *)data; + if (sav->num < 0 || sav->num >= NUM_SCRN_SAVERS) + return EIO; + SCRN_SAVER(0); + scrn_saver = sav->num; + scrn_blank_time = sav->time; + return 0; + } + case CONS_GSAVER: /* get screen saver info */ + { + register ssaver_t *sav = (ssaver_t *)data; + if (sav->num < 0) + sav->num = scrn_saver; + else if (sav->num >= NUM_SCRN_SAVERS) + return EIO; + sav->time = scrn_blank_time; + strcpy(sav->name, screen_savers[sav->num].name); + return 0; + } + case CONS_80x25TEXT: /* set 80x25 text mode */ + if (!crtc_vga) + return ENXIO; + scp->mode = TEXT80x25; + scp->ysize = 25; + free(scp->scr_buf, M_DEVBUF); + scp->scr_buf = (u_short *)malloc(scp->xsize*scp->ysize*2, + M_DEVBUF, M_NOWAIT); + if (scp != cur_console) + scp->crt_base = scp->scr_buf; + set_mode(scp); + clear_screen(scp); + change_winsize(tp, scp->xsize, scp->ysize); + return 0; + + case CONS_80x50TEXT: /* set 80x50 text mode */ + if (!crtc_vga) + return ENXIO; + /* is there a 8x8 font loaded ? */ + if (fonts_loaded & FONT_8_LOADED) { + scp->mode = TEXT80x50; + scp->ysize = 50; + free(scp->scr_buf, M_DEVBUF); + scp->scr_buf = + (u_short *)malloc(scp->xsize * scp->ysize * 2, + M_DEVBUF, M_NOWAIT); + if (scp != cur_console) + scp->crt_base = scp->scr_buf; + set_mode(scp); + clear_screen(scp); + change_winsize(tp, scp->xsize, scp->ysize); + return 0; + } + else + return EINVAL; + + case CONS_GETVERS: /* get version number */ + *(int*)data = 0x103; /* version 1.3 */ + return 0; + + case CONS_GETINFO: /* get current (virtual) console info */ + { + vid_info_t *ptr = (vid_info_t*)data; + if (ptr->size == sizeof(struct vid_info)) { + ptr->m_num = get_scr_num(); + ptr->mv_col = scp->xpos; + ptr->mv_row = scp->ypos; + ptr->mv_csz = scp->xsize; + ptr->mv_rsz = scp->ysize; + ptr->mv_norm.fore = (scp->term.std_attr & 0x0f00)>>8; + ptr->mv_norm.back = (scp->term.std_attr & 0xf000)>>12; + ptr->mv_rev.fore = (scp->term.rev_attr & 0x0f00)>>8; + ptr->mv_rev.back = (scp->term.rev_attr & 0xf000)>>12; + ptr->mv_grfc.fore = 0; /* not supported */ + ptr->mv_grfc.back = 0; /* not supported */ + ptr->mv_ovscan = scp->border; + ptr->mk_keylock = scp->status & LOCK_KEY_MASK; + return 0; + } + return EINVAL; + } + + case VT_SETMODE: /* set screen switcher mode */ + bcopy(data, &scp->smode, sizeof(struct vt_mode)); + if (scp->smode.mode == VT_PROCESS) { + scp->proc = p; + scp->pid = scp->proc->p_pid; + } + return 0; + + case VT_GETMODE: /* get screen switcher mode */ + bcopy(&scp->smode, data, sizeof(struct vt_mode)); + return 0; + + case VT_RELDISP: /* screen switcher ioctl */ + switch(*data) { + case VT_FALSE: /* user refuses to release screen, abort */ + if (scp == old_scp && (scp->status & SWITCH_WAIT_REL)) { + old_scp->status &= ~SWITCH_WAIT_REL; + switch_in_progress = 0; + return 0; + } + return EINVAL; + + case VT_TRUE: /* user has released screen, go on */ + if (scp == old_scp && (scp->status & SWITCH_WAIT_REL)) { + scp->status &= ~SWITCH_WAIT_REL; + exchange_scr(); + if (new_scp->smode.mode == VT_PROCESS) { + new_scp->status |= SWITCH_WAIT_ACQ; + psignal(new_scp->proc, + new_scp->smode.acqsig); + } + else + switch_in_progress = 0; + return 0; + } + return EINVAL; + + case VT_ACKACQ: /* acquire acknowledged, switch completed */ + if (scp == new_scp && (scp->status & SWITCH_WAIT_ACQ)) { + scp->status &= ~SWITCH_WAIT_ACQ; + switch_in_progress = 0; + return 0; + } + return EINVAL; + + default: + return EINVAL; + } + /* NOT REACHED */ + + case VT_OPENQRY: /* return free virtual console */ + for (i = 0; i < NCONS; i++) { + tp = VIRTUAL_TTY(i); + if (!(tp->t_state & TS_ISOPEN)) { + *data = i + 1; + return 0; + } + } + return EINVAL; + + case VT_ACTIVATE: /* switch to screen *data */ + return switch_scr((*data) - 1); + + case VT_WAITACTIVE: /* wait for switch to occur */ + if (*data > NCONS) + return EINVAL; + if (minor(dev) == (*data) - 1) + return 0; + if (*data == 0) { + if (scp == cur_console) + return 0; + while ((error=tsleep((caddr_t)&scp->smode, + PZERO|PCATCH, "waitvt", 0)) == ERESTART) ; + } + else + while ((error=tsleep( + (caddr_t)&console[*(data-1)].smode, + PZERO|PCATCH, "waitvt", 0)) == ERESTART) ; + return error; + + case VT_GETACTIVE: + *data = get_scr_num()+1; + return 0; + + case KDENABIO: /* allow io operations */ + fp = (struct trapframe *)p->p_md.md_regs; + fp->tf_eflags |= PSL_IOPL; + return 0; + + case KDDISABIO: /* disallow io operations (default) */ + fp = (struct trapframe *)p->p_md.md_regs; + fp->tf_eflags &= ~PSL_IOPL; + return 0; + + case KDSETMODE: /* set current mode of this (virtual) console */ + switch (*data) { + case KD_TEXT: /* switch to TEXT (known) mode */ + /* restore fonts & palette ! */ + if (crtc_vga) { + copy_font(LOAD, 0, 16, font_16); + copy_font(LOAD, 1, 8, font_8); + copy_font(LOAD, 2, 14, font_14); + load_palette(); + } + /* FALL THROUGH */ + + case KD_TEXT1: /* switch to TEXT (known) mode */ + /* no restore fonts & palette */ + scp->status &= ~UNKNOWN_MODE; + set_mode(scp); + clear_screen(scp); + return 0; + + case KD_GRAPHICS:/* switch to GRAPHICS (unknown) mode */ + scp->status |= UNKNOWN_MODE; + return 0; + default: + return EINVAL; + } + /* NOT REACHED */ + + case KDGETMODE: /* get current mode of this (virtual) console */ + *data = (scp->status & UNKNOWN_MODE) ? KD_GRAPHICS : KD_TEXT; + return 0; + + case KDSBORDER: /* set border color of this (virtual) console */ + if (!crtc_vga) + return ENXIO; + scp->border = *data; + if (scp == cur_console) + set_border(scp->border); + return 0; + + case KDSKBSTATE: /* set keyboard state (locks) */ + if (*data >= 0 && *data <= LOCK_KEY_MASK) { + scp->status &= ~LOCK_KEY_MASK; + scp->status |= *data; + if (scp == cur_console) + update_leds(scp->status); + return 0; + } + return EINVAL; + + case KDGKBSTATE: /* get keyboard state (locks) */ + *data = scp->status & LOCK_KEY_MASK; + return 0; + + case KDSETRAD: /* set keyboard repeat & delay rates */ + if (*data & 0x80) + return EINVAL; + i = spltty(); + kbd_cmd(KB_SETRAD); + kbd_cmd(*data); + splx(i); + return 0; + + case KDSKBMODE: /* set keyboard mode */ + switch (*data) { + case K_RAW: /* switch to RAW scancode mode */ + scp->status |= KBD_RAW_MODE; + return 0; + + case K_XLATE: /* switch to XLT ascii mode */ + if (scp == cur_console && scp->status == KBD_RAW_MODE) + shfts = ctls = alts = agrs = metas = 0; + scp->status &= ~KBD_RAW_MODE; + return 0; + default: + return EINVAL; + } + /* NOT REACHED */ + + case KDGKBMODE: /* get keyboard mode */ + *data = (scp->status & KBD_RAW_MODE) ? K_RAW : K_XLATE; + return 0; + + case KDMKTONE: /* sound the bell */ + if (scp == cur_console) + sysbeep(scp->bell_pitch, scp->bell_duration); + return 0; + + case KIOCSOUND: /* make tone (*data) hz */ + if (scp == cur_console) { + if (*(int*)data) { + int pitch = TIMER_FREQ/(*(int*)data); + /* set command for counter 2, 2 byte write */ + if (acquire_timer2(TIMER_16BIT|TIMER_SQWAVE)) { + return EBUSY; + } + /* set pitch */ + outb(TIMER_CNTR2, pitch); + outb(TIMER_CNTR2, (pitch>>8)); + /* enable counter 2 output to speaker */ + outb(IO_PPI, inb(IO_PPI) | 3); + } + else { + /* disable counter 2 output to speaker */ + outb(IO_PPI, inb(IO_PPI) & 0xFC); + release_timer2(); + } + } + return 0; + + case KDGKBTYPE: /* get keyboard type */ + *data = 0; /* type not known (yet) */ + return 0; + + case KDSETLED: /* set keyboard LED status */ + if (*data >= 0 && *data <= LED_MASK) { + scp->status &= ~LED_MASK; + scp->status |= *data; + if (scp == cur_console) + update_leds(scp->status); + return 0; + } + return EINVAL; + + case KDGETLED: /* get keyboard LED status */ + *data = scp->status & LED_MASK; + return 0; + + case GETFKEY: /* get functionkey string */ + if (*(u_short*)data < n_fkey_tab) { + fkeyarg_t *ptr = (fkeyarg_t*)data; + bcopy(&fkey_tab[ptr->keynum].str, + ptr->keydef, + fkey_tab[ptr->keynum].len); + ptr->flen = fkey_tab[ptr->keynum].len; + return 0; + } + else + return EINVAL; + + case SETFKEY: /* set functionkey string */ + if (*(u_short*)data < n_fkey_tab) { + fkeyarg_t *ptr = (fkeyarg_t*)data; + bcopy(ptr->keydef, + &fkey_tab[ptr->keynum].str, + min(ptr->flen, MAXFK)); + fkey_tab[ptr->keynum].len = min(ptr->flen, MAXFK); + return 0; + } + else + return EINVAL; + + case GIO_SCRNMAP: /* get output translation table */ + bcopy(&scr_map, data, sizeof(scr_map)); + return 0; + + case PIO_SCRNMAP: /* set output translation table */ + bcopy(data, &scr_map, sizeof(scr_map)); + return 0; + + case GIO_KEYMAP: /* get keyboard translation table */ + bcopy(&key_map, data, sizeof(key_map)); + return 0; + + case PIO_KEYMAP: /* set keyboard translation table */ + bcopy(data, &key_map, sizeof(key_map)); + return 0; + + case PIO_FONT8x8: /* set 8x8 dot font */ + if (!crtc_vga) + return ENXIO; + bcopy(data, font_8, 8*256); + fonts_loaded |= FONT_8_LOADED; + copy_font(LOAD, 1, 8, font_8); + return 0; + + case GIO_FONT8x8: /* get 8x8 dot font */ + if (!crtc_vga) + return ENXIO; + if (fonts_loaded & FONT_8_LOADED) { + bcopy(font_8, data, 8*256); + return 0; + } + else + return ENXIO; + + case PIO_FONT8x14: /* set 8x14 dot font */ + if (!crtc_vga) + return ENXIO; + bcopy(data, font_14, 14*256); + fonts_loaded |= FONT_14_LOADED; + copy_font(LOAD, 2, 14, font_14); + return 0; + + case GIO_FONT8x14: /* get 8x14 dot font */ + if (!crtc_vga) + return ENXIO; + if (fonts_loaded & FONT_14_LOADED) { + bcopy(font_14, data, 14*256); + return 0; + } + else + return ENXIO; + + case PIO_FONT8x16: /* set 8x16 dot font */ + if (!crtc_vga) + return ENXIO; + bcopy(data, font_16, 16*256); + fonts_loaded |= FONT_16_LOADED; + copy_font(LOAD, 0, 16, font_16); + return 0; + + case GIO_FONT8x16: /* get 8x16 dot font */ + if (!crtc_vga) + return ENXIO; + if (fonts_loaded & FONT_16_LOADED) { + bcopy(font_16, data, 16*256); + return 0; + } + else + return ENXIO; + + case CONSOLE_X_MODE_ON: /* just to be compatible */ + if (saved_console < 0) { + saved_console = get_scr_num(); + switch_scr(minor(dev)); + fp = (struct trapframe *)p->p_md.md_regs; + fp->tf_eflags |= PSL_IOPL; + scp->status |= UNKNOWN_MODE; + scp->status |= KBD_RAW_MODE; + return 0; + } + return EAGAIN; + + case CONSOLE_X_MODE_OFF:/* just to be compatible */ + fp = (struct trapframe *)p->p_md.md_regs; + fp->tf_eflags &= ~PSL_IOPL; + if (crtc_vga) { + copy_font(LOAD, 0, 16, font_16); + copy_font(LOAD, 1, 8, font_8); + copy_font(LOAD, 2, 14, font_14); + load_palette(); + } + scp->status &= ~UNKNOWN_MODE; + set_mode(scp); + clear_screen(scp); + scp->status &= ~KBD_RAW_MODE; + switch_scr(saved_console); + saved_console = -1; + return 0; + + case CONSOLE_X_BELL: /* more compatibility */ + /* + * if set, data is a pointer to a length 2 array of + * integers. data[0] is the pitch in Hz and data[1] + * is the duration in msec. + */ + if (data) + sysbeep(TIMER_FREQ/((int*)data)[0], + ((int*)data)[1]*hz/1000); + else + sysbeep(scp->bell_pitch, scp->bell_duration); + return 0; + + default: + break; + } + + error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p); + if (error >= 0) + return(error); + error = ttioctl(tp, cmd, data, flag); + if (error >= 0) + return(error); + return(ENOTTY); +} + + +void +pcxint(dev_t dev) +{ + struct tty *tp = get_tty_ptr(dev); + + if (!tp) + return; + tp->t_state &= ~TS_BUSY; + if (tp->t_line) + (*linesw[tp->t_line].l_start)(tp); + else + pcstart(tp); +} + + +void +pcstart(struct tty *tp) +{ + struct clist *rbp; + int i, s, len; + u_char buf[PCBURST]; + scr_stat *scp = get_scr_stat(tp->t_dev); + + if (scp->status & SLKED) + return; + s = spltty(); + if (!(tp->t_state & (TS_TIMEOUT|TS_BUSY|TS_TTSTOP))) { + tp->t_state |= TS_BUSY; + splx(s); + rbp = &tp->t_outq; + while (rbp->c_cc) { + len = q_to_b(rbp, buf, PCBURST); + for (i=0; i<len; i++) + if (buf[i]) ansi_put(scp, buf[i]); + } + s = spltty(); + tp->t_state &= ~TS_BUSY; +#if 0 + if (rbp->c_cc) { + tp->t_state |= TS_TIMEOUT; + timeout((timeout_t)ttrstrt, (caddr_t)tp, 1); + } +#endif + if (rbp->c_cc <= tp->t_lowat) { + if (tp->t_state & TS_ASLEEP) { + tp->t_state &= ~TS_ASLEEP; + wakeup((caddr_t)rbp); + } + selwakeup(&tp->t_wsel); + } + } + splx(s); +} + + +void +pccnprobe(struct consdev *cp) +{ + int maj; + + /* locate the major number */ + for (maj = 0; maj < nchrdev; maj++) + if ((void*)cdevsw[maj].d_open == (void*)pcopen) + break; + + /* initialize required fields */ + cp->cn_dev = makedev(maj, NCONS); + cp->cn_pri = CN_INTERNAL; +} + + +void +pccninit(struct consdev *cp) +{ + scinit(); +} + + +void +pccnputc(dev_t dev, char c) +{ + if (c == '\n') + scput('\r'); + scput(c); + if (cur_console == &console[0]) { + int pos = cur_console->crtat - cur_console->crt_base; + if (pos != cur_cursor_pos) { + cur_cursor_pos = pos; + outb(crtc_addr,14); + outb(crtc_addr+1,pos >> 8); + outb(crtc_addr,15); + outb(crtc_addr+1,pos&0xff); + } + } +} + + +int +pccngetc(dev_t dev) +{ + int s = spltty(); /* block scintr while we poll */ + int c = scgetc(0); + splx(s); + if (c == '\r') c = '\n'; + return(c); +} + + +static void +none_saver(int test) +{ +} + + +static void +fade_saver(int test) +{ + static int count = 0; + int i; + + if (test) { + scrn_blanked = 1; + if (count < 64) { + outb(PIXMASK, 0xFF); /* no pixelmask */ + outb(PALWADR, 0x00); + outb(PALDATA, 0); + outb(PALDATA, 0); + outb(PALDATA, 0); + for (i = 3; i < 768; i++) { + if (palette[i] - count > 15) + outb(PALDATA, palette[i]-count); + else + outb(PALDATA, 15); + } + inb(crtc_addr+6); /* reset flip/flop */ + outb(ATC, 0x20); /* enable palette */ + count++; + } + } + else { + count = scrn_blanked = 0; + load_palette(); + } +} + + +static void +blank_saver(int test) +{ + u_char val; + if (test) { + scrn_blanked = 1; + outb(TSIDX, 0x01); val = inb(TSREG); + outb(TSIDX, 0x01); outb(TSREG, val | 0x20); + } + else { + scrn_blanked = 0; + outb(TSIDX, 0x01); val = inb(TSREG); + outb(TSIDX, 0x01); outb(TSREG, val & 0xDF); + } +} + +#define NUM_STARS 50 + +/* + * Alternate saver that got its inspiration from a well known utility + * package for an inferior^H^H^H^H^H^Hfamous OS. + */ +static void +star_saver(int test) +{ + scr_stat *scp = cur_console; + int cell, i; + char pattern[] = {"...........++++*** "}; + char colors[] = {FG_DARKGREY, FG_LIGHTGREY, + FG_WHITE, FG_LIGHTCYAN}; + static u_short stars[NUM_STARS][2]; + + if (test) { + if (!scrn_blanked) { + bcopy(Crtat, scp->scr_buf, + scp->xsize * scp->ysize * 2); + fillw((FG_LIGHTGREY|BG_BLACK)<<8 | scr_map[0x20], Crtat, + scp->xsize * scp->ysize); + set_border(0); + i = scp->ysize * scp->xsize + 5; + outb(crtc_addr, 14); + outb(crtc_addr+1, i >> 8); + outb(crtc_addr, 15); + outb(crtc_addr+1, i & 0xff); + scrn_blanked = 1; + for(i=0; i<NUM_STARS; i++) { + stars[i][0] = + random() % (scp->xsize*scp->ysize); + stars[i][1] = 0; + } + } + cell = random() % NUM_STARS; + *((u_short*)(Crtat + stars[cell][0])) = + scr_map[pattern[stars[cell][1]]] | + colors[random()%sizeof(colors)] << 8; + if ((stars[cell][1]+=(random()%4)) >= sizeof(pattern)-1) { + stars[cell][0] = random() % (scp->xsize*scp->ysize); + stars[cell][1] = 0; + } + } + else { + if (scrn_blanked) { + bcopy(scp->scr_buf, Crtat, scp->xsize*scp->ysize*2); + cur_cursor_pos = -1; + set_border(scp->border); + scrn_blanked = 0; + } + } +} + + +static void +snake_saver(int test) +{ + const char saves[] = {"FreeBSD-2.0"}; + static u_char *savs[sizeof(saves)-1]; + static int dirx, diry; + int f; + scr_stat *scp = cur_console; + + if (test) { + if (!scrn_blanked) { + bcopy(Crtat, scp->scr_buf, + scp->xsize * scp->ysize * 2); + fillw((FG_LIGHTGREY|BG_BLACK)<<8 | scr_map[0x20], + Crtat, scp->xsize * scp->ysize); + set_border(0); + dirx = (scp->xpos ? 1 : -1); + diry = (scp->ypos ? + scp->xsize : -scp->xsize); + for (f=0; f< sizeof(saves)-1; f++) + savs[f] = (u_char *)Crtat + 2 * + (scp->xpos+scp->ypos*scp->xsize); + *(savs[0]) = scr_map[*saves]; + f = scp->ysize * scp->xsize + 5; + outb(crtc_addr, 14); + outb(crtc_addr+1, f >> 8); + outb(crtc_addr, 15); + outb(crtc_addr+1, f & 0xff); + scrn_blanked = 1; + } + if (scrn_blanked++ < 4) + return; + scrn_blanked = 1; + *(savs[sizeof(saves)-2]) = scr_map[0x20]; + for (f=sizeof(saves)-2; f > 0; f--) + savs[f] = savs[f-1]; + f = (savs[0] - (u_char *)Crtat) / 2; + if ((f % scp->xsize) == 0 || + (f % scp->xsize) == scp->xsize - 1 || + (random() % 50) == 0) + dirx = -dirx; + if ((f / scp->xsize) == 0 || + (f / scp->xsize) == scp->ysize - 1 || + (random() % 20) == 0) + diry = -diry; + savs[0] += 2*dirx + 2*diry; + for (f=sizeof(saves)-2; f>=0; f--) + *(savs[f]) = scr_map[saves[f]]; + } + else { + if (scrn_blanked) { + bcopy(scp->scr_buf, Crtat, + scp->xsize * scp->ysize * 2); + cur_cursor_pos = -1; + set_border(scp->border); + scrn_blanked = 0; + } + } +} + + +static void +cursor_shape(int start, int end) +{ + outb(crtc_addr, 10); + outb(crtc_addr+1, start & 0xFF); + outb(crtc_addr, 11); + outb(crtc_addr+1, end & 0xFF); +} + + +#if !defined(FAT_CURSOR) +static void +get_cursor_shape(int *start, int *end) +{ + outb(crtc_addr, 10); + *start = inb(crtc_addr+1) & 0x1F; + outb(crtc_addr, 11); + *end = inb(crtc_addr+1) & 0x1F; +} +#endif + + +static void +cursor_pos(int force) +{ + int pos; + + if (cur_console->status & UNKNOWN_MODE) + return; + if (scrn_blank_time && (time.tv_sec > scrn_time_stamp+scrn_blank_time)) + SCRN_SAVER(1); + pos = cur_console->crtat - cur_console->crt_base; + if (force || (!scrn_blanked && pos != cur_cursor_pos)) { + cur_cursor_pos = pos; + outb(crtc_addr, 14); + outb(crtc_addr+1, pos>>8); + outb(crtc_addr, 15); + outb(crtc_addr+1, pos&0xff); + } + timeout((timeout_t)cursor_pos, 0, hz/20); +} + + +static void +clear_screen(scr_stat *scp) +{ + move_crsr(scp, 0, 0); + fillw(scp->term.cur_attr | scr_map[0x20], scp->crt_base, + scp->xsize * scp->ysize); +} + + +static int +switch_scr(u_int next_scr) +{ + if (switch_in_progress && + (cur_console->proc != pfind(cur_console->pid))) + switch_in_progress = 0; + + if (next_scr >= NCONS || switch_in_progress) { + sysbeep(BELL_PITCH, BELL_DURATION); + return EINVAL; + } + + /* is the wanted virtual console open ? */ + if (next_scr) { + struct tty *tp = VIRTUAL_TTY(next_scr); + if (!(tp->t_state & TS_ISOPEN)) { + sysbeep(BELL_PITCH, BELL_DURATION); + return EINVAL; + } + } + if (in_putc) { /* delay switch if in putc */ + delayed_next_scr = next_scr+1; + return 0; + } + switch_in_progress = 1; + old_scp = cur_console; + new_scp = &console[next_scr]; + wakeup((caddr_t)&new_scp->smode); + if (new_scp == old_scp) { + switch_in_progress = 0; + return 0; + } + + /* has controlling process died? */ + if (old_scp->proc && (old_scp->proc != pfind(old_scp->pid))) + old_scp->smode.mode = VT_AUTO; + if (new_scp->proc && (new_scp->proc != pfind(new_scp->pid))) + new_scp->smode.mode = VT_AUTO; + + /* check the modes and switch approbiatly */ + if (old_scp->smode.mode == VT_PROCESS) { + old_scp->status |= SWITCH_WAIT_REL; + psignal(old_scp->proc, old_scp->smode.relsig); + } + else { + exchange_scr(); + if (new_scp->smode.mode == VT_PROCESS) { + new_scp->status |= SWITCH_WAIT_ACQ; + psignal(new_scp->proc, new_scp->smode.acqsig); + } + else + switch_in_progress = 0; + } + return 0; +} + + +static void +exchange_scr(void) +{ + struct tty *tp; + + bcopy(Crtat, old_scp->scr_buf, old_scp->xsize * old_scp->ysize * 2); + old_scp->crt_base = old_scp->scr_buf; + move_crsr(old_scp, old_scp->xpos, old_scp->ypos); + cur_console = new_scp; + set_mode(new_scp); + new_scp->crt_base = Crtat; + move_crsr(new_scp, new_scp->xpos, new_scp->ypos); + bcopy(new_scp->scr_buf, Crtat, new_scp->xsize * new_scp->ysize * 2); + update_leds(new_scp->status); + if ((old_scp->status & UNKNOWN_MODE) && crtc_vga) { + copy_font(LOAD, 0, 16, font_16); + copy_font(LOAD, 1, 8, font_8); + copy_font(LOAD, 2, 14, font_14); + load_palette(); + } + if (old_scp->status & KBD_RAW_MODE || new_scp->status & KBD_RAW_MODE) + shfts = ctls = alts = agrs = metas = 0; + delayed_next_scr = 0; +} + + +static void +move_crsr(scr_stat *scp, int x, int y) +{ + if (x < 0 || y < 0 || x >= scp->xsize || y >= scp->ysize) + return; + scp->xpos = x; + scp->ypos = y; + scp->crtat = scp->crt_base + scp->ypos * scp->xsize + scp->xpos; +} + + +static void +move_up(u_short *s, u_short *d, u_int len) +{ + s += len; + d += len; + while (len-- > 0) + *--d = *--s; +} + + +static void +move_down(u_short *s, u_short *d, u_int len) +{ + while (len-- > 0) + *d++ = *s++; +} + + +static void +scan_esc(scr_stat *scp, u_char c) +{ + static u_char ansi_col[16] = + {0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15}; + int i, n; + u_short *src, *dst, count; + + if (scp->term.esc == 1) { + switch (c) { + + case '[': /* Start ESC [ sequence */ + scp->term.esc = 2; + scp->term.last_param = -1; + for (i = scp->term.num_param; i < MAX_ESC_PAR; i++) + scp->term.param[i] = 1; + scp->term.num_param = 0; + return; + + case 'M': /* Move cursor up 1 line, scroll if at top */ + if (scp->ypos > 0) + move_crsr(scp, scp->xpos, scp->ypos - 1); + else { + move_up(scp->crt_base, + scp->crt_base + scp->xsize, + (scp->ysize - 1) * scp->xsize); + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crt_base, scp->xsize); + } + break; +#if notyet + case 'Q': + scp->term.esc = 4; + break; +#endif + case 'c': /* Clear screen & home */ + clear_screen(scp); + break; + } + } + else if (scp->term.esc == 2) { + if (c >= '0' && c <= '9') { + if (scp->term.num_param < MAX_ESC_PAR) { + if (scp->term.last_param != scp->term.num_param) { + scp->term.last_param = scp->term.num_param; + scp->term.param[scp->term.num_param] = 0; + } + else + scp->term.param[scp->term.num_param] *= 10; + scp->term.param[scp->term.num_param] += c - '0'; + return; + } + } + scp->term.num_param = scp->term.last_param + 1; + switch (c) { + + case ';': + if (scp->term.num_param < MAX_ESC_PAR) + return; + break; + + case '=': + scp->term.esc = 3; + scp->term.last_param = -1; + for (i = scp->term.num_param; i < MAX_ESC_PAR; i++) + scp->term.param[i] = 1; + scp->term.num_param = 0; + return; + + case 'A': /* up n rows */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos, scp->ypos - n); + break; + + case 'B': /* down n rows */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos, scp->ypos + n); + break; + + case 'C': /* right n columns */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos + n, scp->ypos); + break; + + case 'D': /* left n columns */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos - n, scp->ypos); + break; + + case 'E': /* cursor to start of line n lines down */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, 0, scp->ypos + n); + break; + + case 'F': /* cursor to start of line n lines up */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, 0, scp->ypos - n); + break; + + case 'f': /* System V consoles .. */ + case 'H': /* Cursor move */ + if (scp->term.num_param == 0) + move_crsr(scp, 0, 0); + else if (scp->term.num_param == 2) + move_crsr(scp, scp->term.param[1] - 1, + scp->term.param[0] - 1); + break; + + case 'J': /* Clear all or part of display */ + if (scp->term.num_param == 0) + n = 0; + else + n = scp->term.param[0]; + switch (n) { + case 0: /* clear form cursor to end of display */ + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crtat, scp->crt_base + + scp->xsize * scp->ysize - + scp->crtat); + break; + case 1: /* clear from beginning of display to cursor */ + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crt_base, + scp->crtat - scp->crt_base); + break; + case 2: /* clear entire display */ + clear_screen(scp); + break; + } + break; + + case 'K': /* Clear all or part of line */ + if (scp->term.num_param == 0) + n = 0; + else + n = scp->term.param[0]; + switch (n) { + case 0: /* clear form cursor to end of line */ + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crtat, scp->xsize - scp->xpos); + break; + case 1: /* clear from beginning of line to cursor */ + fillw(scp->term.cur_attr|scr_map[0x20], + scp->crtat - (scp->xsize - scp->xpos), + (scp->xsize - scp->xpos) + 1); + break; + case 2: /* clear entire line */ + fillw(scp->term.cur_attr|scr_map[0x20], + scp->crtat - (scp->xsize - scp->xpos), + scp->xsize); + break; + } + break; + + case 'L': /* Insert n lines */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->ysize - scp->ypos) + n = scp->ysize - scp->ypos; + src = scp->crt_base + scp->ypos * scp->xsize; + dst = src + n * scp->xsize; + count = scp->ysize - (scp->ypos + n); + move_up(src, dst, count * scp->xsize); + fillw(scp->term.cur_attr | scr_map[0x20], src, + n * scp->xsize); + break; + + case 'M': /* Delete n lines */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->ysize - scp->ypos) + n = scp->ysize - scp->ypos; + dst = scp->crt_base + scp->ypos * scp->xsize; + src = dst + n * scp->xsize; + count = scp->ysize - (scp->ypos + n); + move_down(src, dst, count * scp->xsize); + src = dst + count * scp->xsize; + fillw(scp->term.cur_attr | scr_map[0x20], src, + n * scp->xsize); + break; + + case 'P': /* Delete n chars */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->xsize - scp->xpos) + n = scp->xsize - scp->xpos; + dst = scp->crtat; + src = dst + n; + count = scp->xsize - (scp->xpos + n); + move_down(src, dst, count); + src = dst + count; + fillw(scp->term.cur_attr | scr_map[0x20], src, n); + break; + + case '@': /* Insert n chars */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->xsize - scp->xpos) + n = scp->xsize - scp->xpos; + src = scp->crtat; + dst = src + n; + count = scp->xsize - (scp->xpos + n); + move_up(src, dst, count); + fillw(scp->term.cur_attr | scr_map[0x20], src, n); + break; + + case 'S': /* scroll up n lines */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->ypos) + n = scp->ypos; + bcopy(scp->crt_base + (scp->xsize * n), + scp->crt_base, + scp->xsize * (scp->ysize - n) * + sizeof(u_short)); + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crt_base + scp->xsize * + (scp->ysize - 1), + scp->xsize); + break; + + case 'T': /* scroll down n lines */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->ysize - scp->ypos) + n = scp->ysize - scp->ypos; + bcopy(scp->crt_base, + scp->crt_base + (scp->xsize * n), + scp->xsize * (scp->ysize - n) * + sizeof(u_short)); + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crt_base, scp->xsize); + break; + + case 'X': /* delete n characters in line */ + n = scp->term.param[0]; if (n < 1) n = 1; + if (n > scp->xsize - scp->xpos) + n = scp->xsize - scp->xpos; + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crt_base + scp->xpos + + ((scp->xsize*scp->ypos) * sizeof(u_short)), n); + break; + + case 'Z': /* move n tabs backwards */ + n = scp->term.param[0]; if (n < 1) n = 1; + if ((i = scp->xpos & 0xf8) == scp->xpos) + i -= 8*n; + else + i -= 8*(n-1); + if (i < 0) + i = 0; + move_crsr(scp, i, scp->ypos); + break; + + case '`': /* move cursor to column n */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, n, scp->ypos); + break; + + case 'a': /* move cursor n columns to the right */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos + n, scp->ypos); + break; + + case 'd': /* move cursor to row n */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos, n); + break; + + case 'e': /* move cursor n rows down */ + n = scp->term.param[0]; if (n < 1) n = 1; + move_crsr(scp, scp->xpos, scp->ypos + n); + break; + + case 'm': /* change attribute */ + if (scp->term.num_param == 0) { + scp->term.cur_attr = scp->term.std_attr; + break; + } + for (i = 0; i < scp->term.num_param; i++) { + switch (n = scp->term.param[i]) { + case 0: /* back to normal */ + scp->term.cur_attr = scp->term.std_attr; + break; + case 1: /* highlight (bold) */ + scp->term.cur_attr &= 0xFF00; + scp->term.cur_attr |= 0x0800; + break; + case 4: /* highlight (underline) */ + scp->term.cur_attr &= 0xFF00; + scp->term.cur_attr |= 0x0800; + break; + case 5: /* blink */ + scp->term.cur_attr &= 0xFF00; + scp->term.cur_attr |= 0x8000; + break; + case 7: /* reverse video */ + scp->term.cur_attr = scp->term.rev_attr; + break; + case 30: case 31: /* set fg color */ + case 32: case 33: case 34: + case 35: case 36: case 37: + scp->term.cur_attr = + (scp->term.cur_attr & 0xF8FF) + | (ansi_col[(n-30) & 7] << 8); + break; + case 40: case 41: /* set bg color */ + case 42: case 43: case 44: + case 45: case 46: case 47: + scp->term.cur_attr = + (scp->term.cur_attr & 0x8FFF) + | (ansi_col[(n-40) & 7] << 12); + break; + } + } + break; + + case 'x': + if (scp->term.num_param == 0) + n = 0; + else + n = scp->term.param[0]; + switch (n) { + case 0: /* reset attributes */ + scp->term.cur_attr = scp->term.std_attr = + current_default->std_attr; + scp->term.rev_attr = current_default->rev_attr; + break; + case 1: /* set ansi background */ + scp->term.cur_attr = scp->term.std_attr = + (scp->term.std_attr & 0x0F00) | + (ansi_col[(scp->term.param[1])&0x0F]<<12); + break; + case 2: /* set ansi foreground */ + scp->term.cur_attr = scp->term.std_attr = + (scp->term.std_attr & 0xF000) | + (ansi_col[(scp->term.param[1])&0x0F]<<8); + break; + case 3: /* set ansi attribute directly */ + scp->term.cur_attr = scp->term.std_attr = + (scp->term.param[1]&0xFF)<<8; + break; + case 5: /* set ansi reverse video background */ + scp->term.rev_attr = + (scp->term.rev_attr & 0x0F00) | + (ansi_col[(scp->term.param[1])&0x0F]<<12); + break; + case 6: /* set ansi reverse video foreground */ + scp->term.rev_attr = + (scp->term.rev_attr & 0xF000) | + (ansi_col[(scp->term.param[1])&0x0F]<<8); + break; + case 7: /* set ansi reverse video directly */ + scp->term.rev_attr = (scp->term.param[1]&0xFF)<<8; + break; + } + break; + + case 'z': /* switch to (virtual) console n */ + if (scp->term.num_param == 1) + switch_scr(scp->term.param[0]); + break; + } + } + else if (scp->term.esc == 3) { + if (c >= '0' && c <= '9') { + if (scp->term.num_param < MAX_ESC_PAR) { + if (scp->term.last_param != scp->term.num_param) { + scp->term.last_param = scp->term.num_param; + scp->term.param[scp->term.num_param] = 0; + } + else + scp->term.param[scp->term.num_param] *= 10; + scp->term.param[scp->term.num_param] += c - '0'; + return; + } + } + scp->term.num_param = scp->term.last_param + 1; + switch (c) { + + case ';': + if (scp->term.num_param < MAX_ESC_PAR) + return; + break; + + case 'A': /* set display border color */ + if (scp->term.num_param == 1) + scp->border=scp->term.param[0] & 0xff; + if (scp == cur_console) + set_border(scp->border); + break; + + case 'B': /* set bell pitch and duration */ + if (scp->term.num_param == 2) { + scp->bell_pitch = scp->term.param[0]; + scp->bell_duration = scp->term.param[1]*10; + } + break; + + case 'C': /* set cursor shape (start & end line) */ + if (scp->term.num_param == 2) { + scp->cursor_start = scp->term.param[0] & 0x1F; + scp->cursor_end = scp->term.param[1] & 0x1F; + if (scp == cur_console) + cursor_shape(scp->cursor_start, + scp->cursor_end); + } + break; + + case 'F': /* set ansi foreground */ + if (scp->term.num_param == 1) + scp->term.cur_attr = scp->term.std_attr = + (scp->term.std_attr & 0xF000) + | ((scp->term.param[0] & 0x0F) << 8); + break; + + case 'G': /* set ansi background */ + if (scp->term.num_param == 1) + scp->term.cur_attr = scp->term.std_attr = + (scp->term.std_attr & 0x0F00) + | ((scp->term.param[0] & 0x0F) << 12); + break; + + case 'H': /* set ansi reverse video foreground */ + if (scp->term.num_param == 1) + scp->term.rev_attr = + (scp->term.rev_attr & 0xF000) + | ((scp->term.param[0] & 0x0F) << 8); + break; + + case 'I': /* set ansi reverse video background */ + if (scp->term.num_param == 1) + scp->term.rev_attr = + (scp->term.rev_attr & 0x0F00) + | ((scp->term.param[0] & 0x0F) << 12); + break; + } + } + scp->term.esc = 0; +} + + +static void +ansi_put(scr_stat *scp, u_char c) +{ + if (scp->status & UNKNOWN_MODE) + return; + + /* make screensaver happy */ + if (scp == cur_console) { + scrn_time_stamp = time.tv_sec; + if (scrn_blanked) + SCRN_SAVER(0); + } + in_putc++; + if (scp->term.esc) + scan_esc(scp, c); + else switch(c) { + case 0x1B: /* start escape sequence */ + scp->term.esc = 1; + scp->term.num_param = 0; + break; + case 0x07: + if (scp == cur_console) + sysbeep(scp->bell_pitch, scp->bell_duration); + break; + case '\t': /* non-destructive tab */ + scp->crtat += (8 - scp->xpos % 8); + scp->xpos += (8 - scp->xpos % 8); + break; + case '\b': /* non-destructive backspace */ + if (scp->crtat > scp->crt_base) { + scp->crtat--; + if (scp->xpos > 0) + scp->xpos--; + else { + scp->xpos += scp->xsize - 1; + scp->ypos--; + } + } + break; + case '\r': /* return to pos 0 */ + move_crsr(scp, 0, scp->ypos); + break; + case '\n': /* newline, same pos */ + scp->crtat += scp->xsize; + scp->ypos++; + break; + case '\f': /* form feed, clears screen */ + clear_screen(scp); + break; + default: + /* Print only printables */ + *scp->crtat = (scp->term.cur_attr | scr_map[c]); + scp->crtat++; + if (++scp->xpos >= scp->xsize) { + scp->xpos = 0; + scp->ypos++; + } + break; + } + if (scp->crtat >= scp->crt_base + scp->ysize * scp->xsize) { + bcopy(scp->crt_base + scp->xsize, scp->crt_base, + scp->xsize * (scp->ysize - 1) * sizeof(u_short)); + fillw(scp->term.cur_attr | scr_map[0x20], + scp->crt_base + scp->xsize * (scp->ysize - 1), + scp->xsize); + scp->crtat -= scp->xsize; + scp->ypos--; + } + in_putc--; + if (delayed_next_scr) + switch_scr(delayed_next_scr - 1); +} + + +static void +scinit(void) +{ + u_short volatile *cp = Crtat + (CGA_BUF-MONO_BUF)/sizeof(u_short), was; + unsigned cursorat; + int i; + + /* + * catch that once in a blue moon occurence when scinit is called + * TWICE, adding the CGA_BUF offset again -> poooff + */ + if (crtat != 0) + return; + /* + * Crtat initialized to point to MONO buffer, if not present change + * to CGA_BUF offset. ONLY ADD the difference since locore.s adds + * in the remapped offset at the "right" time + */ + was = *cp; + *cp = (u_short) 0xA55A; + if (*cp != 0xA55A) { + crtc_addr = MONO_BASE; + } else { + *cp = was; + crtc_addr = COLOR_BASE; + Crtat = Crtat + (CGA_BUF-MONO_BUF)/sizeof(u_short); + } + + /* Extract cursor location */ + outb(crtc_addr,14); + cursorat = inb(crtc_addr+1)<<8 ; + outb(crtc_addr,15); + cursorat |= inb(crtc_addr+1); + crtat = Crtat + cursorat; + + /* is this a VGA or higher ? */ + outb(crtc_addr, 7); + if (inb(crtc_addr) == 7) + crtc_vga = 1; + + current_default = &user_default; + console[0].crtat = crtat; + console[0].crt_base = Crtat; + console[0].term.esc = 0; + console[0].term.std_attr = current_default->std_attr; + console[0].term.rev_attr = current_default->rev_attr; + console[0].term.cur_attr = current_default->std_attr; + console[0].xpos = cursorat % COL; + console[0].ypos = cursorat / COL; + console[0].border = BG_BLACK;; + console[0].xsize = COL; + console[0].ysize = ROW; + console[0].status = NLKED; + console[0].pid = 0; + console[0].proc = NULL; + console[0].smode.mode = VT_AUTO; + console[0].bell_pitch = BELL_PITCH; + console[0].bell_duration = BELL_DURATION; + kernel_console.esc = 0; + kernel_console.std_attr = kernel_default.std_attr; + kernel_console.rev_attr = kernel_default.rev_attr; + kernel_console.cur_attr = kernel_default.std_attr; + /* initialize mapscrn array to a one to one map */ + for (i=0; i<sizeof(scr_map); i++) + scr_map[i] = i; + clear_screen(&console[0]); +} + + +static void +scput(u_char c) +{ + scr_stat *scp = &console[0]; + term_stat save; + + if (crtat == 0) + scinit(); + if( in_putc == 0) { + ++in_putc; + save = scp->term; + scp->term = kernel_console; + current_default = &kernel_default; + ansi_put(scp, c); + kernel_console = scp->term; + current_default = &user_default; + scp->term = save; + --in_putc; + } else { + if( console_buffer_count < CONSOLE_BUFSIZE) + console_buffer[console_buffer_count++] = c; + } +} + + +static u_char +*get_fstr(u_int c, u_int *len) +{ + u_int i; + + if (!(c & FKEY)) + return(NULL); + i = (c & 0xFF) - F_FN; + if (i > n_fkey_tab) + return(NULL); + *len = fkey_tab[i].len; + return(fkey_tab[i].str); +} + + +static void +update_leds(int which) +{ + int s; + static u_char xlate_leds[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + + /* replace CAPS led with ALTGR led for ALTGR keyboards */ + if (key_map.n_keys > ALTGR_OFFSET) { + if (which & ALKED) + which |= CLKED; + else + which &= ~CLKED; + } + s = spltty(); + kbd_cmd(KB_SETLEDS); + kbd_cmd(xlate_leds[which & LED_MASK]); + splx(s); +} + + +/* + * scgetc(noblock) - get character from keyboard. + * If noblock = 0 wait until a key is pressed. + * Else return NOKEY. + */ +u_int +scgetc(int noblock) +{ + u_char scancode, keycode; + u_int state, action; + struct key_t *key; + static u_char esc_flag = 0, compose = 0; + static u_int chr = 0; + +next_code: + kbd_wait(); + /* First see if there is something in the keyboard port */ + if (inb(KB_STAT) & KB_BUF_FULL) + scancode = inb(KB_DATA); + else if (noblock) + return(NOKEY); + else + goto next_code; + + if (cur_console->status & KBD_RAW_MODE) + return scancode; +#if ASYNCH + if (scancode == KB_ACK || scancode == KB_RESEND) { + kbd_reply = scancode; + if (noblock) + return(NOKEY); + goto next_code; + } +#endif + keycode = scancode & 0x7F; + switch (esc_flag) { + case 0x00: /* normal scancode */ + switch(scancode) { + case 0xB8: /* left alt (compose key) */ + if (compose) { + compose = 0; + if (chr > 255) { + sysbeep(BELL_PITCH, BELL_DURATION); + chr = 0; + } + } + break; + case 0x38: + if (!compose) { + compose = 1; + chr = 0; + } + break; + case 0xE0: + case 0xE1: + esc_flag = scancode; + goto next_code; + } + break; + case 0xE0: /* 0xE0 prefix */ + esc_flag = 0; + switch (keycode) { + case 0x1C: /* right enter key */ + keycode = 0x59; + break; + case 0x1D: /* right ctrl key */ + keycode = 0x5A; + break; + case 0x35: /* keypad divide key */ + keycode = 0x5B; + break; + case 0x37: /* print scrn key */ + keycode = 0x5C; + break; + case 0x38: /* right alt key (alt gr) */ + keycode = 0x5D; + break; + case 0x47: /* grey home key */ + keycode = 0x5E; + break; + case 0x48: /* grey up arrow key */ + keycode = 0x5F; + break; + case 0x49: /* grey page up key */ + keycode = 0x60; + break; + case 0x4B: /* grey left arrow key */ + keycode = 0x61; + break; + case 0x4D: /* grey right arrow key */ + keycode = 0x62; + break; + case 0x4F: /* grey end key */ + keycode = 0x63; + break; + case 0x50: /* grey down arrow key */ + keycode = 0x64; + break; + case 0x51: /* grey page down key */ + keycode = 0x65; + break; + case 0x52: /* grey insert key */ + keycode = 0x66; + break; + case 0x53: /* grey delete key */ + keycode = 0x67; + break; + default: /* ignore everything else */ + goto next_code; + } + break; + case 0xE1: /* 0xE1 prefix */ + esc_flag = 0; + if (keycode == 0x1D) + esc_flag = 0x1D; + goto next_code; + /* NOT REACHED */ + case 0x1D: /* pause / break */ + esc_flag = 0; + if (keycode != 0x45) + goto next_code; + keycode = 0x68; + break; + } + + if (compose) { + switch (scancode) { + /* key pressed process it */ + case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ + chr = (scancode - 0x40) + chr*10; + goto next_code; + case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ + chr = (scancode - 0x47) + chr*10; + goto next_code; + case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ + chr = (scancode - 0x4E) + chr*10; + goto next_code; + case 0x52: /* keypad 0 */ + chr *= 10; + goto next_code; + + /* key release, no interest here */ + case 0xC7: case 0xC8: case 0xC9: /* keypad 7,8,9 */ + case 0xCB: case 0xCC: case 0xCD: /* keypad 4,5,6 */ + case 0xCF: case 0xD0: case 0xD1: /* keypad 1,2,3 */ + case 0xD2: /* keypad 0 */ + goto next_code; + + case 0x38: /* left alt key */ + break; + default: + if (chr) { + compose = chr = 0; + sysbeep(BELL_PITCH, BELL_DURATION); + goto next_code; + } + break; + } + } + + state = (shfts ? 1 : 0 ) | (2 * (ctls ? 1 : 0)) | (4 * (alts ? 1 : 0)); + if ((!agrs && (cur_console->status & ALKED)) + || (agrs && !(cur_console->status & ALKED))) + keycode += ALTGR_OFFSET; + key = &key_map.key[keycode]; + if ( ((key->flgs & FLAG_LOCK_C) && (cur_console->status & CLKED)) + || ((key->flgs & FLAG_LOCK_N) && (cur_console->status & NLKED)) ) + state ^= 1; + + /* Check for make/break */ + action = key->map[state]; + if (scancode & 0x80) { /* key released */ + if (key->spcl & 0x80) { + switch (action) { + case LSH: + shfts &= ~1; + break; + case RSH: + shfts &= ~2; + break; + case LCTR: + ctls &= ~1; + break; + case RCTR: + ctls &= ~2; + break; + case LALT: + alts &= ~1; + break; + case RALT: + alts &= ~2; + break; + case NLK: + nlkcnt = 0; + break; + case CLK: + clkcnt = 0; + break; + case SLK: + slkcnt = 0; + break; + case ASH: + agrs = 0; + break; + case ALK: + alkcnt = 0; + break; + case META: + metas = 0; + break; + } + } + if (chr && !compose) { + action = chr; + chr = 0; + return(action); + } + } else { + /* key pressed */ + if (key->spcl & (0x80>>state)) { + switch (action) { + /* LOCKING KEYS */ + case NLK: + if (!nlkcnt) { + nlkcnt++; + if (cur_console->status & NLKED) + cur_console->status &= ~NLKED; + else + cur_console->status |= NLKED; + update_leds(cur_console->status); + } + break; + case CLK: + if (!clkcnt) { + clkcnt++; + if (cur_console->status & CLKED) + cur_console->status &= ~CLKED; + else + cur_console->status |= CLKED; + update_leds(cur_console->status); + } + break; + case SLK: + if (!slkcnt) { + slkcnt++; + if (cur_console->status & SLKED) { + cur_console->status &= ~SLKED; + pcstart(VIRTUAL_TTY(get_scr_num())); + } + else + cur_console->status |= SLKED; + update_leds(cur_console->status); + } + break; + case ALK: + if (!alkcnt) { + alkcnt++; + if (cur_console->status & ALKED) + cur_console->status &= ~ALKED; + else + cur_console->status |= ALKED; + update_leds(cur_console->status); + } + break; + + /* NON-LOCKING KEYS */ + case NOP: + break; + case RBT: + shutdown_nice(); + break; + case DBG: +#if DDB > 0 /* try to switch to console 0 */ + if (cur_console->smode.mode == VT_AUTO && + console[0].smode.mode == VT_AUTO) + switch_scr(0); + Debugger("manual escape to debugger"); + return(NOKEY); +#else + printf("No debugger in kernel\n"); +#endif + break; + case LSH: + shfts |= 1; + break; + case RSH: + shfts |= 2; + break; + case LCTR: + ctls |= 1; + break; + case RCTR: + ctls |= 2; + break; + case LALT: + alts |= 1; + break; + case RALT: + alts |= 2; + break; + case ASH: + agrs = 1; + break; + case META: + metas = 1; + break; + case NEXT: + switch_scr((get_scr_num()+1)%NCONS); + break; + default: + if (action >= F_SCR && action <= L_SCR) { + switch_scr(action - F_SCR); + break; + } + if (action >= F_FN && action <= L_FN) + action |= FKEY; + return(action); + } + } + else { + if (metas) + action |= MKEY; + return(action); + } + } + goto next_code; +} + + +int +getchar(void) +{ + u_char thechar; + int s; + + polling = 1; + s = splhigh(); + scput('>'); + thechar = (u_char) scgetc(0); + polling = 0; + splx(s); + switch (thechar) { + default: + if (thechar >= scr_map[0x20]) + scput(thechar); + return(thechar); + case cr: + case lf: + scput(cr); scput(lf); + return(lf); + case bs: + case del: + scput(bs); scput(scr_map[0x20]); scput(bs); + return(thechar); + case cntld: + scput('^'); scput('D'); scput('\r'); scput('\n'); + return(0); + } +} + + +u_int +sgetc(int noblock) +{ + return (scgetc(noblock) & 0xff); +} + + +int +pcmmap(dev_t dev, int offset, int nprot) +{ + if (offset > 0x20000) + return EINVAL; + return i386_btop((VIDEOMEM + offset)); +} + + +static void +kbd_wait(void) +{ + int i = 1000; + + while (i--) { + if ((inb(KB_STAT) & KB_READY) == 0) + break; + DELAY (10); + } +} + + +static void +kbd_cmd(u_char command) +{ + int retry = 5; + do { + int i = 100000; + + kbd_wait(); +#if ASYNCH + kbd_reply = 0; + outb(KB_DATA, command); + while (i--) { + if (kbd_reply == KB_ACK) + return; + if (kbd_reply == KB_RESEND) + break; + } +#else + outb(KB_DATA, command); + while (i--) { + if (inb(KB_STAT) & KB_BUF_FULL) { + int val; + DELAY(10); + val = inb(KB_DATA); + if (val == KB_ACK) + return; + if (val == KB_RESEND) + break; + } + } +#endif + } while (retry--); +} + + +static void +set_mode(scr_stat *scp) +{ + u_char byte; + int s; + + if (scp != cur_console) + return; + + /* (re)activate cursor */ + untimeout((timeout_t)cursor_pos, 0); + cursor_pos(1); + + /* change cursor type if set */ + if (scp->cursor_start != -1 && scp->cursor_end != -1) + cursor_shape(scp->cursor_start, scp->cursor_end); + + /* mode change only on VGA's */ + if (!crtc_vga) + return; + + /* setup video hardware for the given mode */ + s = splhigh(); + switch(scp->mode) { + case TEXT80x25: + outb(crtc_addr, 9); byte = inb(crtc_addr+1); + outb(crtc_addr, 9); outb(crtc_addr+1, byte | 0x0F); + outb(TSIDX, 0x03); outb(TSREG, 0x00); /* select font 0 */ + break; + case TEXT80x50: + outb(crtc_addr, 9); byte = inb(crtc_addr+1); + outb(crtc_addr, 9); outb(crtc_addr+1, (byte & 0xF0) | 0x07); + outb(TSIDX, 0x03); outb(TSREG, 0x05); /* select font 1 */ + break; + default: + break; + } + splx(s); + + /* set border color for this (virtual) console */ + set_border(scp->border); + return; +} + + +static void +set_border(int color) +{ + inb(crtc_addr+6); /* reset flip-flop */ + outb(ATC, 0x11); outb(ATC, color); + inb(crtc_addr+6); /* reset flip-flop */ + outb(ATC, 0x20); /* enable Palette */ +} + + +static void +copy_font(int direction, int segment, int size, char* font) +{ + int ch, line, s; + u_char val; + + outb(TSIDX, 0x01); val = inb(TSREG); /* blank screen */ + outb(TSIDX, 0x01); outb(TSREG, val | 0x20); + + /* setup vga for loading fonts (graphics plane mode) */ + s = splhigh(); + inb(crtc_addr+6); /* reset flip/flop */ + outb(ATC, 0x30); outb(ATC, 0x01); + outb(TSIDX, 0x02); outb(TSREG, 0x04); + outb(TSIDX, 0x04); outb(TSREG, 0x06); + outb(GDCIDX, 0x04); outb(GDCREG, 0x02); + outb(GDCIDX, 0x05); outb(GDCREG, 0x00); + outb(GDCIDX, 0x06); outb(GDCREG, 0x05); /* addr = a0000, 64kb */ + splx(s); + for (ch=0; ch < 256; ch++) + for (line=0; line < size; line++) + if (direction) + *((char *)atdevbase+(segment*0x4000)+(ch*32)+line) = + font[(ch*size)+line]; + else + font[(ch*size)+line] = + *((char *)atdevbase+(segment*0x4000)+(ch*32)+line); + + /* setup vga for text mode again */ + s = splhigh(); + inb(crtc_addr+6); /* reset flip/flop */ + outb(ATC, 0x30); outb(ATC, 0x0C); + outb(TSIDX, 0x02); outb(TSREG, 0x03); + outb(TSIDX, 0x04); outb(TSREG, 0x02); + outb(GDCIDX, 0x04); outb(GDCREG, 0x00); + outb(GDCIDX, 0x05); outb(GDCREG, 0x10); + if (crtc_addr == MONO_BASE) { + outb(GDCIDX, 0x06); outb(GDCREG, 0x0A); /* addr = b0000, 32kb */ + } + else { + outb(GDCIDX, 0x06); outb(GDCREG, 0x0E); /* addr = b8000, 32kb */ + } + splx(s); + outb(TSIDX, 0x01); val = inb(TSREG); /* unblank screen */ + outb(TSIDX, 0x01); outb(TSREG, val & 0xDF); +} + + +static void +load_palette(void) +{ + int i; + + outb(PIXMASK, 0xFF); /* no pixelmask */ + outb(PALWADR, 0x00); + for (i=0x00; i<0x300; i++) + outb(PALDATA, palette[i]); + inb(crtc_addr+6); /* reset flip/flop */ + outb(ATC, 0x20); /* enable palette */ +} + + +static void +save_palette(void) +{ + int i; + + outb(PALRADR, 0x00); + for (i=0x00; i<0x300; i++) + palette[i] = inb(PALDATA); + inb(crtc_addr+6); /* reset flip/flop */ +} + + +static void +change_winsize(struct tty *tp, int x, int y) +{ + if (tp->t_winsize.ws_col != x || tp->t_winsize.ws_row != y) { + tp->t_winsize.ws_col = x; + tp->t_winsize.ws_row = y; + pgsignal(tp->t_pgrp, SIGWINCH, 1); + } +} + +#endif /* NSC */ diff --git a/sys/i386/isa/timerreg.h b/sys/i386/isa/timerreg.h new file mode 100644 index 0000000..5742f66 --- /dev/null +++ b/sys/i386/isa/timerreg.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 1993 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. + * + * from: Header: timerreg.h,v 1.2 93/02/28 15:08:58 mccanne Exp + * $Id$ + */ + +/* + * + * Register definitions for the Intel 8253 Programmable Interval Timer. + * + * This chip has three independent 16-bit down counters that can be + * read on the fly. There are three mode registers and three countdown + * registers. The countdown registers are addressed directly, via the + * first three I/O ports. The three mode registers are accessed via + * the fourth I/O port, with two bits in the mode byte indicating the + * register. (Why are hardware interfaces always so braindead?). + * + * To write a value into the countdown register, the mode register + * is first programmed with a command indicating the which byte of + * the two byte register is to be modified. The three possibilities + * are load msb (TMR_MR_MSB), load lsb (TMR_MR_LSB), or load lsb then + * msb (TMR_MR_BOTH). + * + * To read the current value ("on the fly") from the countdown register, + * you write a "latch" command into the mode register, then read the stable + * value from the corresponding I/O port. For example, you write + * TMR_MR_LATCH into the corresponding mode register. Presumably, + * after doing this, a write operation to the I/O port would result + * in undefined behavior (but hopefully not fry the chip). + * Reading in this manner has no side effects. + * + * The outputs of the three timers are connected as follows: + * + * timer 0 -> irq 0 + * timer 1 -> dma chan 0 (for dram refresh) + * timer 2 -> speaker (via keyboard controller) + * + * Timer 0 is used to call hardclock. + * Timer 2 is used to generate console beeps. + */ + +/* + * Macros for specifying values to be written into a mode register. + */ +#define TIMER_CNTR0 (IO_TIMER1 + 0) /* timer 0 counter port */ +#define TIMER_CNTR1 (IO_TIMER1 + 1) /* timer 1 counter port */ +#define TIMER_CNTR2 (IO_TIMER1 + 2) /* timer 2 counter port */ +#define TIMER_MODE (IO_TIMER1 + 3) /* timer mode port */ +#define TIMER_SEL0 0x00 /* select counter 0 */ +#define TIMER_SEL1 0x40 /* select counter 1 */ +#define TIMER_SEL2 0x80 /* select counter 2 */ +#define TIMER_INTTC 0x00 /* mode 0, intr on terminal cnt */ +#define TIMER_ONESHOT 0x02 /* mode 1, one shot */ +#define TIMER_RATEGEN 0x04 /* mode 2, rate generator */ +#define TIMER_SQWAVE 0x06 /* mode 3, square wave */ +#define TIMER_SWSTROBE 0x08 /* mode 4, s/w triggered strobe */ +#define TIMER_HWSTROBE 0x0a /* mode 5, h/w triggered strobe */ +#define TIMER_LATCH 0x00 /* latch counter for reading */ +#define TIMER_LSB 0x10 /* r/w counter LSB */ +#define TIMER_MSB 0x20 /* r/w counter MSB */ +#define TIMER_16BIT 0x30 /* r/w counter 16 bits, LSB first */ +#define TIMER_BCD 0x01 /* count in BCD */ + diff --git a/sys/i386/isa/ultra14f.c b/sys/i386/isa/ultra14f.c new file mode 100644 index 0000000..d723ea0 --- /dev/null +++ b/sys/i386/isa/ultra14f.c @@ -0,0 +1,1353 @@ +/* + * Ported for use with the UltraStor 14f by Gary Close (gclose@wvnvms.wvnet.edu) + * Slight fixes to timeouts to run with the 34F + * Thanks to Julian Elischer for advice and help with this port. + * + * Written by Julian Elischer (julian@tfs.com) + * for TRW Financial Systems for use under the MACH(2.5) operating system. + * + * TRW Financial Systems, in accordance with their agreement with Carnegie + * Mellon University, makes this software available to CMU to distribute + * or use in any manner that they see fit as long as this message is kept with + * the software. For this reason TFS also grants any other persons or + * organisations permission to use or modify this software. + * + * TFS supplies this software to be publicly redistributed + * on the understanding that TFS is not responsible for the correct + * functioning of this software in any circumstances. + * + * commenced: Sun Sep 27 18:14:01 PDT 1992 + * slight mod to make work with 34F as well: Wed Jun 2 18:05:48 WST 1993 + * + * today: Fri Jun 2 17:21:03 EST 1994 + * added 24F support ++sg + * + * $Id: ultra14f.c,v 1.21 1994/08/18 23:36:40 phk Exp $ + */ + +#include <sys/types.h> + +#ifdef KERNEL /* don't laugh.. this compiles to a program too.. look */ +#include <uha.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/ioctl.h> +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/user.h> + +#include <i386/include/pio.h> +#include <i386/isa/isa_device.h> +#endif /*KERNEL */ +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> + +/* */ + +#ifdef KERNEL +#include "ddb.h" +#else /*KERNEL */ +#define NUHA 1 +#endif /*KERNEL */ + +typedef struct { + unsigned char addr[4]; +} physaddr; +typedef struct { + unsigned char len[4]; +} physlen; + +#define KVTOPHYS(x) vtophys(x) + +#define UHA_MSCP_MAX 32 /* store up to 32MSCPs at any one time + * MAX = ? + */ +#define MSCP_HASH_SIZE 32 /* when we have a physical addr. for + * a mscp and need to find the mscp in + * space, look it up in the hash table + */ +#define MSCP_HASH_SHIFT 9 /* only hash on multiples of 512 */ +#define MSCP_HASH(x) ((((long int)(x))>>MSCP_HASH_SHIFT) % MSCP_HASH_SIZE) + +extern int hz; +#define UHA_NSEG 33 /* number of dma segments supported */ + +/************************** board definitions *******************************/ +struct uha_reg +{ + int id; /* product id reg */ + int type; /* product type reg */ + int ectl; /* EISA expansion control bits */ + int config; /* configuration bits */ + int lmask; /* local doorbell mask reg */ + int lint; /* local doorbell int/stat reg */ + int smask; /* system doorbell mask reg */ + int sint; /* system doorbell int/stat reg */ + int ogmcmd; /* outgoing mail command */ + int ogmptr; /* outgoing mail ptr */ + int icmcmd; /* incoming mail command */ + int icmptr; /* incoming mail ptr */ +} *uhareg[NUHA]; + +struct uha_bits +{ + /* uha_lint (read) */ + unsigned char ldip; + + /* uha_lint (write) */ + unsigned char adrst; + unsigned char sbrst; + unsigned char asrst; + unsigned char abort; + unsigned char ogmint; + + /* uha_sint (read) */ + unsigned char sintp; + unsigned char abort_succ; + unsigned char abort_fail; + + /* uha_sint (write) */ + unsigned char abort_ack; + unsigned char icm_ack; +} *uhabits[NUHA]; + + +/* + * UHA_LINT bits (read) + */ + +#define UHA_LDIP 0x80 /* local doorbell int pending */ +#define U24_LDIP 0x02 + +/* + * UHA_LINT bits (write) + */ + +#define UHA_ADRST 0x40 /* adapter soft reset */ +#define UHA_SBRST 0x20 /* scsi bus reset */ +#define UHA_ASRST 0x60 /* adapter and scsi reset */ +#define UHA_ABORT 0x10 /* abort MSCP */ +#define UHA_OGMINT 0x01 /* tell adapter to get mail */ +#define U24_SBRST 0x40 /* scsi bus reset */ +#define U24_ADRST 0x80 /* adapter soft reset */ +#define U24_ASRST 0xc0 /* adapter and scsi reset */ +#define U24_ABORT 0x10 /* same? */ +#define U24_OGMINT 0x02 /* enable OGM interrupt */ + +/* + * UHA_SMASK bits (read) + */ + +#define UHA_SINTEN 0x80 /* system doorbell interupt Enabled */ +#define UHA_ABORT_COMPLETE_EN 0x10 /* abort MSCP command complete int Enabled */ +#define UHA_ICM_ENABLED 0x01 /* ICM interrupt enabled */ + +/* + * UHA_SMASK bits (write) + */ + +#define UHA_ENSINT 0x80 /* enable system doorbell interrupt */ +#define UHA_EN_ABORT_COMPLETE 0x10 /* enable abort MSCP complete int */ +#define UHA_ENICM 0x01 /* enable ICM interrupt */ + +/* + * UHA_SINT bits (read) + */ + +#define UHA_SINTP 0x80 /* system doorbell int pending */ +#define UHA_ABORT_SUCC 0x10 /* abort MSCP successful */ +#define UHA_ABORT_FAIL 0x18 /* abort MSCP failed */ +#define U24_SINTP 0x02 /* system doorbell int pending */ +#define U24_ABORT_SUCC 0x10 /* same? */ +#define U24_ABORT_FAIL 0x18 /* same? */ + +/* + * UHA_SINT bits (write) + */ + +#define UHA_ABORT_ACK 0x18 /* acknowledge status and clear */ +#define UHA_ICM_ACK 0x01 /* acknowledge ICM and clear */ +#define U24_ABORT_ACK 0x18 /* same */ +#define U24_ICM_ACK 0x02 /* 24F acknowledge ICM and clear */ + +/* + * UHA_CONF1 bits (read only) + */ + +#define UHA_DMA_CH5 0x00 /* DMA channel 5 */ +#define UHA_DMA_CH6 0x40 /* 6 */ +#define UHA_DMA_CH7 0x80 /* 7 */ +#define UHA_IRQ15 0x00 /* IRQ 15 */ +#define UHA_IRQ14 0x10 /* 14 */ +#define UHA_IRQ11 0x20 /* 11 */ +#define UHA_IRQ10 0x30 /* 10 */ + +#define EISA_CONFIG 0x0c80 /* Configuration base port */ +#define EISA_DISABLE 0x01 /* EISA disable bit */ + +/* + * ha_status error codes + */ + +#define UHA_NO_ERR 0x00 /* No error supposedly */ +#define UHA_SBUS_ABORT_ERR 0x84 /* scsi bus abort error */ +#define UHA_SBUS_TIMEOUT 0x91 /* scsi bus selection timeout */ +#define UHA_SBUS_OVER_UNDER 0x92 /* scsi bus over/underrun */ +#define UHA_BAD_SCSI_CMD 0x96 /* illegal scsi command */ +#define UHA_AUTO_SENSE_ERR 0x9b /* auto request sense err */ +#define UHA_SBUS_RES_ERR 0xa3 /* scsi bus reset error */ +#define UHA_BAD_SG_LIST 0xff /* invalid scatter gath list */ + +struct uha_dma_seg { + physaddr addr; + physlen len; +}; + +struct mscp { + unsigned char opcode:3; +#define U14_HAC 0x01 /* host adapter command */ +#define U14_TSP 0x02 /* target scsi pass through command */ +#define U14_SDR 0x04 /* scsi device reset */ + unsigned char xdir:2; /* xfer direction */ +#define U14_SDET 0x00 /* determined by scsi command */ +#define U14_SDIN 0x01 /* scsi data in */ +#define U14_SDOUT 0x02 /* scsi data out */ +#define U14_NODATA 0x03 /* no data xfer */ + unsigned char dcn:1; /* disable disconnect for this command */ + unsigned char ca:1; /* cache control */ + unsigned char sgth:1; /* scatter gather flag */ + unsigned char target:3; + unsigned char chan:2; /* scsi channel (always 0 for 14f) */ + unsigned char lun:3; + physaddr data; + physlen datalen; + physaddr link; + unsigned char link_id; + unsigned char sg_num; /*number of scat gath segs */ + /*in s-g list if sg flag is */ + /*set. starts at 1, 8bytes per */ + unsigned char senselen; + unsigned char cdblen; + unsigned char cdb[12]; + unsigned char ha_status; + unsigned char targ_status; + physaddr sense; /* if 0 no auto sense */ + /*-----------------end of hardware supported fields----------------*/ + struct mscp *next; /* in free list */ + struct scsi_xfer *xs; /* the scsi_xfer for this cmd */ + int flags; +#define MSCP_FREE 0 +#define MSCP_ACTIVE 1 +#define MSCP_ABORTED 2 + struct uha_dma_seg uha_dma[UHA_NSEG]; + struct scsi_sense_data mscp_sense; + struct mscp *nexthash; + long int hashkey; +}; + +struct uha_data { + int flags; +#define UHA_INIT 0x01 +#define UHA_24F 0x02 + int baseport; + struct mscp *mscphash[MSCP_HASH_SIZE]; + struct mscp *free_mscp; + int our_id; /* our scsi id */ + int vect; + int dma; + int nummscps; + struct scsi_link sc_link; +} *uhadata[NUHA]; + +int uhaprobe(); +int uha_attach(); +int uhaintr(); +int32 uha_scsi_cmd(); +void uha_timeout(caddr_t, int); +void uha_free_mscp(); +int uha_abort(); +void uhaminphys(); +void uha_done(); +u_int32 uha_adapter_info(); +struct mscp *uha_mscp_phys_kv(); + +struct mscp *cheat; +unsigned long int scratch; +static uha_unit = 0; +#define UHA_SHOWMSCPS 0x01 +#define UHA_SHOWINTS 0x02 +#define UHA_SHOWCMDS 0x04 +#define UHA_SHOWMISC 0x08 +#define FAIL 1 +#define SUCCESS 0 +#define PAGESIZ 4096 + +#ifdef KERNEL +struct isa_driver uhadriver = +{ + uhaprobe, + uha_attach, + "uha" +}; + +struct scsi_adapter uha_switch = +{ + uha_scsi_cmd, + uhaminphys, + 0, + 0, + uha_adapter_info, + "uha", + { 0, 0 } +}; + +/* the below structure is so we have a default dev struct for out link struct */ +struct scsi_device uha_dev = +{ + NULL, /* Use default error handler */ + NULL, /* have a queue, served by this */ + NULL, /* have no async handler */ + NULL, /* Use default 'done' routine */ + "uha", + 0, + { 0, 0 } +}; + +#endif /*KERNEL */ + +#ifndef KERNEL +main() +{ + printf("uha_data is %d bytes\n", sizeof(struct uha_data)); + printf("mscp is %d bytes\n", sizeof(struct mscp)); +} + +#else /*KERNEL*/ +/* + * Function to send a command out through a mailbox + */ +void +uha_send_mbox(int unit, struct mscp *mscp) +{ + struct uha_data *uha = uhadata[unit]; + int port = uha->baseport; + int spincount = 100000; /* 1s should be enough */ + struct uha_reg *ur = uhareg[unit]; + struct uha_bits *ub = uhabits[unit]; + int s = splbio(); + + while (--spincount) { + if ((inb(ur->lint) & ub->ldip) == 0) + break; + DELAY(100); + } + if (spincount == 0) { + printf("uha%d: uha_send_mbox, board not responding\n", unit); + Debugger("ultra14f"); + } + outl(ur->ogmptr, KVTOPHYS(mscp)); + if (uha->flags & UHA_24F) outb(ur->ogmcmd, 1); + outb(ur->lint, ub->ogmint); + splx(s); +} + +/* + * Function to send abort to 14f + */ +int +uha_abort(int unit, struct mscp *mscp) +{ + struct uha_data *uha = uhadata[unit]; + int port = uha->baseport; + int spincount = 100; /* 1 mSec */ + int abortcount = 200000; /*2 secs */ + struct uha_reg *ur = uhareg[unit]; + struct uha_bits *ub = uhabits[unit]; + int s = splbio(); + + while (--spincount) { + if ((inb(ur->lint) & ub->ldip) == 0) + break; + DELAY(10); + } + if (spincount == 0) { + printf("uha%d: uha_abort, board not responding\n", unit); + Debugger("ultra14f"); + } + outl(ur->ogmptr,KVTOPHYS(mscp)); + if (uha->flags & UHA_24F) outb(ur->ogmcmd, 1); + outb(ur->lint, ub->abort); + + while (--abortcount) { + if (inb(ur->sint) & ub->abort_fail) + break; + DELAY(10); + } + if (abortcount == 0) { + printf("uha%d: uha_abort, board not responding\n", unit); + Debugger("ultra14f"); + } + if ((inb(ur->sint) & 0x10) != 0) { + outb(ur->sint, ub->abort_ack); + splx(s); + return (1); + } else { + outb(ur->sint, ub->abort_ack); + splx(s); + return (0); + } +} + +/* + * Function to poll for command completion when in poll mode. + * + * wait = timeout in msec + */ +int +uha_poll(int unit, int wait) +{ + struct uha_data *uha = uhadata[unit]; + int port = uha->baseport; + struct uha_reg *ur = uhareg[unit]; + struct uha_bits *ub = uhabits[unit]; + int stport = ur->sint; + + retry: + while (--wait) { + if (inb(stport) & ub->sintp) + break; + DELAY(1000); /* 1 mSec per loop */ + } + if (wait == 0) { + printf("uha%d: uha_poll, board not responding\n", unit); + return (EIO); + } + uhaintr(unit); + return (0); +} + +/* + * Check if the device can be found at the port given and if so, set it up + * ready for further work as an argument, takes the isa_device structure + * from autoconf.c + */ +int +uhaprobe(dev) + struct isa_device *dev; +{ + int unit = uha_unit; + struct uha_data *uha; + struct uha_reg *ur; + struct uha_bits *ub; + + dev->id_unit = unit; + + /* + * find unit and check we have that many defined + */ + if (unit >= NUHA) { + printf("uha: unit number (%d) too high\n", unit); + return (0); + } + dev->id_unit = unit; + + /* + * Allocate a storage area for us + */ + if (uhadata[unit]) { + printf("uha%d: memory already allocated\n", unit); + return 0; + } + uha = malloc(sizeof(struct uha_data), M_TEMP, M_NOWAIT); + if (!uha) { + printf("uha%d: cannot malloc!\n", unit); + return 0; + } + bzero(uha, sizeof(struct uha_data)); + + ur = malloc(sizeof(struct uha_reg), M_TEMP, M_NOWAIT); + if (!ur) { + printf("uha%d: cannot malloc!\n", unit); + return 0; + } + bzero(ur, sizeof(struct uha_reg)); + + ub = malloc(sizeof(struct uha_bits), M_TEMP, M_NOWAIT); + if (!ub) { + printf("uha%d: cannot malloc!\n", unit); + return 0; + } + bzero(ub, sizeof(struct uha_bits)); + + uhareg[unit] = ur; + uhabits[unit] = ub; + uhadata[unit] = uha; + uha->baseport = dev->id_iobase; + /* + * Try initialise a unit at this location + * sets up dma and bus speed, loads uha->vect + */ + if (uha_init(unit) != 0 && uha24_init(unit) != 0) { + uhadata[unit] = NULL; + free(uha, M_TEMP); + free(ur, M_TEMP); + free(ub, M_TEMP); + return (0); + } + /* if it's there put in its interrupt and DRQ vectors */ + dev->id_irq = (1 << uha->vect); + dev->id_drq = uha->dma; + dev->id_iobase = uha->baseport; + + uha_unit++; + return (16); +} + +/* + * Attach all the sub-devices we can find + */ +int +uha_attach(dev) + struct isa_device *dev; +{ + int unit = dev->id_unit; + struct uha_data *uha = uhadata[unit]; + + /* + * fill in the prototype scsi_link. + */ + uha->sc_link.adapter_unit = unit; + uha->sc_link.adapter_targ = uha->our_id; + uha->sc_link.adapter = &uha_switch; + uha->sc_link.device = &uha_dev; + uha->sc_link.flags = SDEV_BOUNCE; + + /* + * ask the adapter what subunits are present + */ + scsi_attachdevs(&(uha->sc_link)); + + return 1; +} + +/* + * Return some information to the caller about + * the adapter and it's capabilities + */ +u_int32 +uha_adapter_info(unit) + int unit; +{ + return (2); /* 2 outstanding requests at a time per device */ +} + +/* + * Catch an interrupt from the adaptor + */ +int +uhaintr(unit) + int unit; +{ + struct uha_data *uha = uhadata[unit]; + struct mscp *mscp; + u_char uhastat; + unsigned long int mboxval; + struct uha_reg *ur = uhareg[unit]; + struct uha_bits *ub = uhabits[unit]; + + int port = uha->baseport; + +#ifdef UHADEBUG + printf("uhaintr "); +#endif /*UHADEBUG */ + + while ((uhastat = inb(ur->sint)) & ub->sintp) { + /* + * First get all the information and then + * acknowledge the interrupt + */ + mboxval = inl(ur->icmptr); + outb(ur->sint, ub->icm_ack); + if (uha->flags & UHA_24F) outb(ur->icmcmd, 0); + +#ifdef UHADEBUG + printf("status = 0x%x ", uhastat); +#endif /*UHADEBUG*/ + /* + * Process the completed operation + */ + + mscp = uha_mscp_phys_kv(uha, mboxval); + if (!mscp) { + printf("uha: BAD MSCP RETURNED\n"); + return (0); /* whatever it was, it'll timeout */ + } + untimeout(uha_timeout, (caddr_t)mscp); + + uha_done(unit, mscp); + } + return (1); +} + +/* + * We have a mscp which has been processed by the adaptor, now we look to see + * how the operation went. + */ +void +uha_done(unit, mscp) + int unit; + struct mscp *mscp; +{ + struct uha_data *uha = uhadata[unit]; + struct scsi_sense_data *s1, *s2; + struct scsi_xfer *xs = mscp->xs; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("uha_done\n")); + /* + * Otherwise, put the results of the operation + * into the xfer and call whoever started it + */ + if ((mscp->ha_status == UHA_NO_ERR) || (xs->flags & SCSI_ERR_OK)) { /* All went correctly OR errors expected */ + xs->resid = 0; + xs->error = 0; + } else { + + s1 = &(mscp->mscp_sense); + s2 = &(xs->sense); + + if (mscp->ha_status != UHA_NO_ERR) { + switch (mscp->ha_status) { + case UHA_SBUS_TIMEOUT: /* No response */ + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("timeout reported back\n")); + xs->error = XS_TIMEOUT; + break; + case UHA_SBUS_OVER_UNDER: + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("scsi bus xfer over/underrun\n")); + xs->error = XS_DRIVER_STUFFUP; + break; + case UHA_BAD_SG_LIST: + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("bad sg list reported back\n")); + xs->error = XS_DRIVER_STUFFUP; + break; + default: /* Other scsi protocol messes */ + xs->error = XS_DRIVER_STUFFUP; + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("unexpected ha_status: %x\n", + mscp->ha_status)); + } + } else { + + if (mscp->targ_status != 0) +/* + * I have no information for any possible value of target status field + * other than 0 means no error!! So I guess any error is unexpected in that + * event!! + */ + + { + SC_DEBUG(xs->sc_link, SDEV_DB3, + ("unexpected targ_status: %x\n", + mscp->targ_status)); + xs->error = XS_DRIVER_STUFFUP; + } + } + } + done: + xs->flags |= ITSDONE; + uha_free_mscp(unit, mscp, xs->flags); + scsi_done(xs); +} + +/* + * A mscp (and hence a mbx-out) is put onto the free list. + */ +void +uha_free_mscp(unit, mscp, flags) + int unit; + struct mscp *mscp; + int flags; +{ + struct uha_data *uha = uhadata[unit]; + unsigned int opri = 0; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + + mscp->next = uha->free_mscp; + uha->free_mscp = mscp; + mscp->flags = MSCP_FREE; + /* + * If there were none, wake abybody waiting for + * one to come free, starting with queued entries + */ + if (!mscp->next) { + wakeup((caddr_t)&uha->free_mscp); + } + if (!(flags & SCSI_NOMASK)) + splx(opri); +} + +/* + * Get a free mscp + * + * If there are none, see if we can allocate a new one. If so, put it in the + * hash table too otherwise either return an error or sleep. + */ +struct mscp * +uha_get_mscp(unit, flags) + int unit, flags; +{ + struct uha_data *uha = uhadata[unit]; + unsigned opri = 0; + struct mscp *mscpp; + int hashnum; + + if (!(flags & SCSI_NOMASK)) + opri = splbio(); + /* + * If we can and have to, sleep waiting for one to come free + * but only if we can't allocate a new one + */ + while (!(mscpp = uha->free_mscp)) { + if (uha->nummscps < UHA_MSCP_MAX) { + if (mscpp = (struct mscp *)malloc(sizeof(struct mscp), + M_TEMP, + M_NOWAIT)) { + bzero(mscpp, sizeof(struct mscp)); + uha->nummscps++; + mscpp->flags = MSCP_ACTIVE; + /* + * put in the phystokv hash table + * Never gets taken out. + */ + mscpp->hashkey = KVTOPHYS(mscpp); + hashnum = MSCP_HASH(mscpp->hashkey); + mscpp->nexthash = uha->mscphash[hashnum]; + uha->mscphash[hashnum] = mscpp; + } else { + printf("uha%d: Can't malloc MSCP\n", unit); + } + goto gottit; + } else { + if (!(flags & SCSI_NOSLEEP)) { + tsleep((caddr_t)&uha->free_mscp, PRIBIO, + "uhamscp", 0); + } + } + } + if (mscpp) { + /* Get MSCP from from free list */ + uha->free_mscp = mscpp->next; + mscpp->flags = MSCP_ACTIVE; + } + gottit: + if (!(flags & SCSI_NOMASK)) + splx(opri); + + return (mscpp); +} + +/* + * given a physical address, find the mscp that it corresponds to. + */ +struct mscp * +uha_mscp_phys_kv(uha, mscp_phys) + struct uha_data *uha; + long int mscp_phys; +{ + int hashnum = MSCP_HASH(mscp_phys); + struct mscp *mscpp = uha->mscphash[hashnum]; + + while (mscpp) { + if (mscpp->hashkey == mscp_phys) + break; + mscpp = mscpp->nexthash; + } + return mscpp; +} + +/* + * Start the board, ready for normal operation + */ +int +uha_init(unit) + int unit; +{ + struct uha_data *uha = uhadata[unit]; + unsigned char ad[4]; + volatile unsigned char model; + volatile unsigned char submodel; + unsigned char config_reg1; + unsigned char config_reg2; + unsigned char dma_ch; + unsigned char irq_ch; + unsigned char uha_id; + int port = uha->baseport; + int i; + int resetcount = 4000; /* 4 secs? */ + struct uha_reg *ur = uhareg[unit]; + struct uha_bits *ub = uhabits[unit]; + + /* + * Prepare to use a 14/34F. + */ + ur->id = port + 0x04; + ur->type = port + 0x00; /* 24F only */ + ur->ectl = port + 0x00; /* 24F only */ + ur->config = port + 0x06; /* 0-1 for 14F */ + ur->lmask = port + 0x00; + ur->lint = port + 0x01; + ur->smask = port + 0x02; + ur->sint = port + 0x03; + ur->ogmcmd = port + 0x00; /* 24F only */ + ur->ogmptr = port + 0x08; + ur->icmcmd = port + 0x00; /* 24F only */ + ur->icmptr = port + 0x0c; + + ub->ldip = UHA_LDIP; + ub->adrst = UHA_ADRST; + ub->sbrst = UHA_SBRST; + ub->asrst = UHA_ASRST; + ub->abort = UHA_ABORT; + ub->ogmint = UHA_OGMINT; + ub->sintp = UHA_SINTP; + ub->abort_succ = UHA_ABORT_SUCC; + ub->abort_fail = UHA_ABORT_FAIL; + ub->abort_ack = UHA_ABORT_ACK; + ub->icm_ack = UHA_ICM_ACK; + + model = inb(ur->id); + submodel = inb(ur->id + 1); + if ((model != 0x56) & (submodel != 0x40)) return(ENXIO); + printf("uha%d: reading board settings, ", unit); + + config_reg1 = inb(ur->config); + config_reg2 = inb(ur->config + 1); + dma_ch = (config_reg1 & 0xc0); + irq_ch = (config_reg1 & 0x30); + uha_id = (config_reg2 & 0x07); + + switch (dma_ch) { + case UHA_DMA_CH5: + uha->dma = 5; + printf("dma=5 "); + break; + case UHA_DMA_CH6: + uha->dma = 6; + printf("dma=6 "); + break; + case UHA_DMA_CH7: + uha->dma = 7; + printf("dma=7 "); + break; + default: + printf("illegal dma jumper setting\n"); + return (EIO); + } + switch (irq_ch) { + case UHA_IRQ10: + uha->vect = 10; + printf("int=10 "); + break; + case UHA_IRQ11: + uha->vect = 11; + printf("int=11 "); + break; + case UHA_IRQ14: + uha->vect = 14; + printf("int=14 "); + break; + case UHA_IRQ15: + uha->vect = 15; + printf("int=15 "); + break; + default: + printf("illegal int jumper setting\n"); + return (EIO); + } + + /* who are we on the scsi bus */ + printf("id=%x\n", uha_id); + uha->our_id = uha_id; + + /* + * Note that we are going and return (to probe) + */ + outb(ur->lint, ub->asrst); + while (--resetcount) { + if (inb(ur->lint)) + break; + DELAY(1000); /* 1 mSec per loop */ + } + if (resetcount == 0) { + printf("uha%d: board timed out during reset\n", unit); + return (ENXIO); + } + outb(ur->smask, 0x81); /* make sure interrupts are enabled */ + uha->flags |= UHA_INIT; + return (0); +} + + +/* + * Initialize an Ultrastor 24F + */ +int +uha24_init(unit) +int unit; +{ + unsigned char p0, p1, p2, p3, p5, p6, p7; + unsigned char id[7], rev, emu, haid; + int slot, port, irq, i; + int resetcount = 4000; + struct uha_data *uha = uhadata[unit]; + struct uha_reg *ur = uhareg[unit]; + struct uha_bits *ub = uhabits[unit]; + + /* Search for the 24F's product ID */ + for (slot = 1; slot < 15; slot++) { + /* + * Prepare to use a 24F. + */ + port = EISA_CONFIG | (slot << 12); + ur->id = port + 0x00; + ur->type = port + 0x02; + ur->ectl = port + 0x04; + ur->config = port + 0x05; /* 0-2 for 24F */ + ur->lmask = port + 0x0c; + ur->lint = port + 0x0d; + ur->smask = port + 0x0e; + ur->sint = port + 0x0f; + ur->ogmcmd = port + 0x16; + ur->ogmptr = port + 0x17; + ur->icmcmd = port + 0x1b; + ur->icmptr = port + 0x1c; + + ub->ldip = U24_LDIP; + ub->adrst = U24_ADRST; + ub->sbrst = U24_SBRST; + ub->asrst = U24_ASRST; + ub->abort = U24_ABORT; + ub->ogmint = U24_OGMINT; + ub->sintp = U24_SINTP; + ub->abort_succ = U24_ABORT_SUCC; + ub->abort_fail = U24_ABORT_FAIL; + ub->abort_ack = U24_ABORT_ACK; + ub->icm_ack = U24_ICM_ACK; + + /* Make sure an EISA card is installed in this slot. */ + outb(ur->id, 0xff); + p0 = inb(ur->id); + if (p0 == 0xff || (p0 & 0x80) != 0) continue; + + /* It's EISA, so make sure the card is enabled. */ + if ((inb(ur->ectl) & EISA_DISABLE) == 0) continue; + + /* Found an enabled card. Grab the product ID. */ + p1 = inb(ur->id+1); + p2 = inb(ur->type); + p3 = inb(ur->type+1); + id[0] = 0x40 + ((p0 >> 2) & 0x1f); + id[1] = 0x40 + (((p0 & 0x03) << 3) | ((p1 >> 5) & 0x07)); + id[2] = 0x40 + (p1 & 0x1f); + id[3] = "0123456789abcdef"[(p2 >> 4) & 0x0f]; + id[4] = "0123456789abcdef"[p2 & 0x0f]; + id[5] = "0123456789abcdef"[(p3 >> 4) & 0x0f]; + id[6] = '\0'; + rev = p3 & 0xf; + + /* We only want the 24F product ID. */ + if (!strcmp(id, "USC024")) break; + } + if (slot == 15) return(ENODEV); + + /* We have the card! Grab remaining config. */ + p5 = inb(ur->config); + p6 = inb(ur->config+1); + p7 = inb(ur->config+2); + + /* If the 24F is currently emulating an ISA device, leave. */ + emu = ((p6 & 0x04) >> 1) | ((p5 & 0x08) >> 3); + if (emu != 3) return(ENODEV); + + switch (p5 & 0xf0) { + case 0x10: irq = 15; break; + case 0x20: irq = 14; break; + case 0x40: irq = 11; break; + case 0x80: irq = 10; break; + default: + printf("uha%d: bad 24F irq\n", unit); + return(ENXIO); + } + + haid = (p7 & 0x07); + printf("uha%d: UltraStor 24F int=%d id=%d\n", unit, irq, haid); + + /* Issue SCSI and adapter reset */ + outb(ur->lint, ub->asrst); + while (--resetcount) { + if (inb(ur->lint)) + break; + DELAY(1000); /* 1 mSec per loop */ + } + if (resetcount == 0) { + printf("uha%d: board timed out during reset\n", unit); + return (ENXIO); + } + outb(ur->smask, 0xc2); /* make sure interrupts are enabled */ + uha->flags |= (UHA_INIT | UHA_24F); + uha->baseport = port; + uha->our_id = haid; + uha->vect = irq; + uha->dma = -1; + return(0); +} + +#ifndef min +#define min(x,y) (x < y ? x : y) +#endif /* min */ + +void +uhaminphys(bp) + struct buf *bp; +{ + if (bp->b_bcount > ((UHA_NSEG - 1) * PAGESIZ)) { + bp->b_bcount = ((UHA_NSEG - 1) * PAGESIZ); + } +} + +/* + * start a scsi operation given the command and the data address. Also + * needs the unit, target and lu. + */ +int32 +uha_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_sense_data *s1, *s2; + struct mscp *mscp; + struct uha_dma_seg *sg; + int seg; /* scatter gather seg being worked on */ + int i = 0; + int rc = 0; + int thiskv; + unsigned long int thisphys, nextphys; + int unit = xs->sc_link->adapter_unit; + int bytes_this_seg, bytes_this_page, datalen, flags; + struct iovec *iovp; + struct uha_data *uha = uhadata[unit]; + int s; + unsigned int stat; + int port = uha->baseport; + unsigned long int templen; + + SC_DEBUG(xs->sc_link, SDEV_DB2, ("uha_scsi_cmd\n")); + /* + * get a mscp (mbox-out) to use. If the transfer + * is from a buf (possibly from interrupt time) + * then we can't allow it to sleep + */ + flags = xs->flags; + if (xs->bp) + flags |= (SCSI_NOSLEEP); /* just to be sure */ + if (flags & ITSDONE) { + printf("uha%d: Already done?", unit); + xs->flags &= ~ITSDONE; + } + if (!(flags & INUSE)) { + printf("uha%d: Not in use?", unit); + xs->flags |= INUSE; + } + if (!(mscp = uha_get_mscp(unit, flags))) { + xs->error = XS_DRIVER_STUFFUP; + return (TRY_AGAIN_LATER); + } + cheat = mscp; + SC_DEBUG(xs->sc_link, SDEV_DB3, ("start mscp(%x)\n", mscp)); + mscp->xs = xs; + + /* + * Put all the arguments for the xfer in the mscp + */ + if (flags & SCSI_RESET) { + mscp->opcode = 0x04; + mscp->ca = 0x01; + } else { + mscp->opcode = 0x02; + mscp->ca = 0x01; + } + if (flags & SCSI_DATA_IN) { + mscp->xdir = 0x01; + } + if (flags & SCSI_DATA_OUT) { + mscp->xdir = 0x02; + } +#ifdef GOTTABEJOKING + if (xs->sc_link->lun != 0) { + xs->error = XS_DRIVER_STUFFUP; + uha_free_mscp(unit, mscp, flags); + return (HAD_ERROR); + } +#endif + mscp->dcn = 0x00; + mscp->chan = 0x00; + mscp->target = xs->sc_link->target; + mscp->lun = xs->sc_link->lun; + mscp->link.addr[0] = 0x00; + mscp->link.addr[1] = 0x00; + mscp->link.addr[2] = 0x00; + mscp->link.addr[3] = 0x00; + mscp->link_id = 0x00; + mscp->cdblen = xs->cmdlen; + scratch = KVTOPHYS(&(mscp->mscp_sense)); + mscp->sense.addr[0] = (scratch & 0xff); + mscp->sense.addr[1] = ((scratch >> 8) & 0xff); + mscp->sense.addr[2] = ((scratch >> 16) & 0xff); + mscp->sense.addr[3] = ((scratch >> 24) & 0xff); + mscp->senselen = sizeof(mscp->mscp_sense); + mscp->ha_status = 0x00; + mscp->targ_status = 0x00; + + if (xs->datalen) { /* should use S/G only if not zero length */ + scratch = KVTOPHYS(mscp->uha_dma); + mscp->data.addr[0] = (scratch & 0xff); + mscp->data.addr[1] = ((scratch >> 8) & 0xff); + mscp->data.addr[2] = ((scratch >> 16) & 0xff); + mscp->data.addr[3] = ((scratch >> 24) & 0xff); + sg = mscp->uha_dma; + seg = 0; + mscp->sgth = 0x01; + +#ifdef TFS + if (flags & SCSI_DATA_UIO) { + iovp = ((struct uio *) xs->data)->uio_iov; + datalen = ((struct uio *) xs->data)->uio_iovcnt; + xs->datalen = 0; + while ((datalen) && (seg < UHA_NSEG)) { + scratch = (unsigned long) iovp->iov_base; + sg->addr.addr[0] = (scratch & 0xff); + sg->addr.addr[1] = ((scratch >> 8) & 0xff); + sg->addr.addr[2] = ((scratch >> 16) & 0xff); + sg->addr.addr[3] = ((scratch >> 24) & 0xff); + xs->datalen += *(unsigned long *) sg->len.len = iovp->iov_len; + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("(0x%x@0x%x)", + iovp->iov_len, + iovp->iov_base)); + sg++; + iovp++; + seg++; + datalen--; + } + } else +#endif /*TFS */ + { + /* + * Set up the scatter gather block + */ + + SC_DEBUG(xs->sc_link, SDEV_DB4, + ("%d @0x%x:- ", xs->datalen, xs->data)); + datalen = xs->datalen; + thiskv = (int) xs->data; + thisphys = KVTOPHYS(thiskv); + templen = 0; + + while ((datalen) && (seg < UHA_NSEG)) { + bytes_this_seg = 0; + + /* put in the base address */ + sg->addr.addr[0] = (thisphys & 0xff); + sg->addr.addr[1] = ((thisphys >> 8) & 0xff); + sg->addr.addr[2] = ((thisphys >> 16) & 0xff); + sg->addr.addr[3] = ((thisphys >> 24) & 0xff); + + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("0x%x", thisphys)); + + /* do it at least once */ + nextphys = thisphys; + while ((datalen) && (thisphys == nextphys)) + /* + * This page is contiguous (physically) with + * the the last, just extend the length + */ + { + /* how far to the end of the page */ + nextphys = (thisphys & (~(PAGESIZ - 1))) + + PAGESIZ; + bytes_this_page = nextphys - thisphys; + /**** or the data ****/ + bytes_this_page = min(bytes_this_page + ,datalen); + bytes_this_seg += bytes_this_page; + datalen -= bytes_this_page; + + /* get more ready for the next page */ + thiskv = (thiskv & (~(PAGESIZ - 1))) + + PAGESIZ; + if (datalen) + thisphys = KVTOPHYS(thiskv); + } + /* + * next page isn't contiguous, finish the seg + */ + SC_DEBUGN(xs->sc_link, SDEV_DB4, + ("(0x%x)", bytes_this_seg)); + sg->len.len[0] = (bytes_this_seg & 0xff); + sg->len.len[1] = ((bytes_this_seg >> 8) & 0xff); + sg->len.len[2] = ((bytes_this_seg >> 16) & 0xff); + sg->len.len[3] = ((bytes_this_seg >> 24) & 0xff); + templen += bytes_this_seg; + sg++; + seg++; + } + } + + /* end of iov/kv decision */ + mscp->datalen.len[0] = (templen & 0xff); + mscp->datalen.len[1] = ((templen >> 8) & 0xff); + mscp->datalen.len[2] = ((templen >> 16) & 0xff); + mscp->datalen.len[3] = ((templen >> 24) & 0xff); + mscp->sg_num = seg; + + SC_DEBUGN(xs->sc_link, SDEV_DB4, ("\n")); + if (datalen) { /* there's still data, must have run out of segs! */ + printf("uha%d: uha_scsi_cmd, more than %d DMA segs\n", + unit, UHA_NSEG); + xs->error = XS_DRIVER_STUFFUP; + uha_free_mscp(unit, mscp, flags); + return (HAD_ERROR); + } + } else { /* No data xfer, use non S/G values */ + mscp->data.addr[0] = 0x00; + mscp->data.addr[1] = 0x00; + mscp->data.addr[2] = 0x00; + mscp->data.addr[3] = 0x00; + mscp->datalen.len[0] = 0x00; + mscp->datalen.len[1] = 0x00; + mscp->datalen.len[2] = 0x00; + mscp->datalen.len[3] = 0x00; + mscp->xdir = 0x03; + mscp->sgth = 0x00; + mscp->sg_num = 0x00; + } + + /* + * Put the scsi command in the mscp and start it + */ + bcopy(xs->cmd, mscp->cdb, xs->cmdlen); + + /* + * Usually return SUCCESSFULLY QUEUED + */ + if (!(flags & SCSI_NOMASK)) { + s = splbio(); + uha_send_mbox(unit, mscp); + timeout(uha_timeout, (caddr_t)mscp, (xs->timeout * hz) / 1000); + splx(s); + SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd_sent\n")); + return (SUCCESSFULLY_QUEUED); + } + + /* + * If we can't use interrupts, poll on completion + */ + uha_send_mbox(unit, mscp); + SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd_wait\n")); + do { + if (uha_poll(unit, xs->timeout)) { + if (!(xs->flags & SCSI_SILENT)) + printf("uha%d: cmd fail\n", unit); + if (!(uha_abort(unit, mscp))) { + printf("uha%d: abort failed in wait\n", unit); + uha_free_mscp(unit, mscp, flags); + } + xs->error = XS_DRIVER_STUFFUP; + return (HAD_ERROR); + } + } + while (!(xs->flags & ITSDONE)); /* something (?) else finished */ + if (xs->error) { + return (HAD_ERROR); + } + return (COMPLETE); +} + +void +uha_timeout(caddr_t arg1, int arg2) +{ + struct mscp *mscp = (struct mscp *)arg1; + int unit; + struct uha_data *uha; + int s = splbio(); + /*int port = uha->baseport; */ + + unit = mscp->xs->sc_link->adapter_unit; + uha = uhadata[unit]; + printf("uha%d:%d:%d (%s%d) timed out ", unit + ,mscp->xs->sc_link->target + ,mscp->xs->sc_link->lun + ,mscp->xs->sc_link->device->name + ,mscp->xs->sc_link->dev_unit); + +#ifdef UHADEBUG + uha_print_active_mscp(unit); +#endif /*UHADEBUG */ + + if ((uha_abort(unit, mscp) != 1) || (mscp->flags = MSCP_ABORTED)) { + printf("AGAIN"); + mscp->xs->retries = 0; /* I MEAN IT ! */ + uha_done(unit, mscp, FAIL); + } else { /* abort the operation that has timed out */ + printf("\n"); + timeout(uha_timeout, (caddr_t)mscp, 2 * hz); + mscp->flags = MSCP_ABORTED; + } + splx(s); +} + +#ifdef UHADEBUG +void +uha_print_mscp(mscp) + struct mscp *mscp; +{ + printf("mscp:%x op:%x cmdlen:%d senlen:%d\n" + ,mscp + ,mscp->opcode + ,mscp->cdblen + ,mscp->senselen); + printf(" sg:%d sgnum:%x datlen:%d hstat:%x tstat:%x flags:%x\n" + ,mscp->sgth + ,mscp->sg_num + ,mscp->datalen + ,mscp->ha_status + ,mscp->targ_status + ,mscp->flags); + show_scsi_cmd(mscp->xs); +} + +void +uha_print_active_mscp(int unit) +{ + struct uha_data *uha = uhadata[unit]; + struct mscp *mscp; + int i = 0; + + while (i < MSCP_HASH_SIZE) { + mscp = uha->mscphash[i]; + while (mscp) { + if (mscp->flags != MSCP_FREE) { + uha_print_mscp(mscp); + } + mscp = mscp->nexthash; + } + i++; + } +} +#endif /*UHADEBUG */ +#endif /*KERNEL */ diff --git a/sys/i386/isa/vector.s b/sys/i386/isa/vector.s new file mode 100644 index 0000000..252bd95 --- /dev/null +++ b/sys/i386/isa/vector.s @@ -0,0 +1,290 @@ +/* + * from: vector.s, 386BSD 0.1 unknown origin + * $Id: vector.s,v 1.7 1994/04/02 07:00:50 davidg Exp $ + */ + +#include "i386/isa/icu.h" +#include "i386/isa/isa.h" +#include "vector.h" + +#define ICU_EOI 0x20 /* XXX - define elsewhere */ + +#define IRQ_BIT(irq_num) (1 << ((irq_num) % 8)) +#define IRQ_BYTE(irq_num) ((irq_num) / 8) + +#ifdef AUTO_EOI_1 +#define ENABLE_ICU1 /* use auto-EOI to reduce i/o */ +#else +#define ENABLE_ICU1 \ + movb $ICU_EOI,%al ; /* as soon as possible send EOI ... */ \ + FASTER_NOP ; /* ... ASAP ... */ \ + outb %al,$IO_ICU1 /* ... to clear in service bit */ +#endif + +#ifdef AUTO_EOI_2 +/* + * The data sheet says no auto-EOI on slave, but it sometimes works. + */ +#define ENABLE_ICU1_AND_2 ENABLE_ICU1 +#else +#define ENABLE_ICU1_AND_2 \ + movb $ICU_EOI,%al ; /* as above */ \ + FASTER_NOP ; \ + outb %al,$IO_ICU2 ; /* but do second icu first */ \ + FASTER_NOP ; \ + outb %al,$IO_ICU1 /* then first icu */ +#endif + +#ifdef FAST_INTR_HANDLER_USES_ES +#define ACTUALLY_PUSHED 1 +#define MAYBE_MOVW_AX_ES movl %ax,%es +#define MAYBE_POPL_ES popl %es +#define MAYBE_PUSHL_ES pushl %es +#else +/* + * We can usually skip loading %es for fastintr handlers. %es should + * only be used for string instructions, and fastintr handlers shouldn't + * do anything slow enough to justify using a string instruction. + */ +#define ACTUALLY_PUSHED 0 +#define MAYBE_MOVW_AX_ES +#define MAYBE_POPL_ES +#define MAYBE_PUSHL_ES +#endif + +/* + * Macros for interrupt interrupt entry, call to handler, and exit. + * + * XXX - the interrupt frame is set up to look like a trap frame. This is + * usually a waste of time. The only interrupt handlers that want a frame + * are the clock handler (it wants a clock frame), the npx handler (it's + * easier to do right all in assembler). The interrupt return routine + * needs a trap frame for rare AST's (it could easily convert the frame). + * The direct costs of setting up a trap frame are two pushl's (error + * code and trap number), an addl to get rid of these, and pushing and + * popping the call-saved regs %esi, %edi and %ebp twice, The indirect + * costs are making the driver interface nonuniform so unpending of + * interrupts is more complicated and slower (call_driver(unit) would + * be easier than ensuring an interrupt frame for all handlers. Finally, + * there are some struct copies in the npx handler and maybe in the clock + * handler that could be avoided by working more with pointers to frames + * instead of frames. + * + * XXX - should we do a cld on every system entry to avoid the requirement + * for scattered cld's? + * + * Coding notes for *.s: + * + * If possible, avoid operations that involve an operand size override. + * Word-sized operations might be smaller, but the operand size override + * makes them slower on on 486's and no faster on 386's unless perhaps + * the instruction pipeline is depleted. E.g., + * + * Use movl to seg regs instead of the equivalent but more descriptive + * movw - gas generates an irelevant (slower) operand size override. + * + * Use movl to ordinary regs in preference to movw and especially + * in preference to movz[bw]l. Use unsigned (long) variables with the + * top bits clear instead of unsigned short variables to provide more + * opportunities for movl. + * + * If possible, use byte-sized operations. They are smaller and no slower. + * + * Use (%reg) instead of 0(%reg) - gas generates larger code for the latter. + * + * If the interrupt frame is made more flexible, INTR can push %eax first + * and decide the ipending case with less overhead, e.g., by avoiding + * loading segregs. + */ + +#define FAST_INTR(irq_num, enable_icus) \ + .text ; \ + SUPERALIGN_TEXT ; \ +IDTVEC(fastintr/**/irq_num) ; \ + pushl %eax ; /* save only call-used registers */ \ + pushl %ecx ; \ + pushl %edx ; \ + pushl %ds ; \ + MAYBE_PUSHL_ES ; \ + movl $KDSEL,%eax ; \ + movl %ax,%ds ; \ + MAYBE_MOVW_AX_ES ; \ + FAKE_MCOUNT((4+ACTUALLY_PUSHED)*4(%esp)) ; \ + pushl _intr_unit + (irq_num) * 4 ; \ + call *_intr_handler + (irq_num) * 4 ; /* do the work ASAP */ \ + enable_icus ; /* (re)enable ASAP (helps edge trigger?) */ \ + addl $4,%esp ; \ + incl _cnt+V_INTR ; /* book-keeping can wait */ \ + movl _intr_countp + (irq_num) * 4,%eax ; \ + incl (%eax) ; \ + movl _cpl,%eax ; /* are we unmasking pending HWIs or SWIs? */ \ + notl %eax ; \ + andl _ipending,%eax ; \ + jne 1f ; /* yes, handle them */ \ + MEXITCOUNT ; \ + MAYBE_POPL_ES ; \ + popl %ds ; \ + popl %edx ; \ + popl %ecx ; \ + popl %eax ; \ + iret ; \ +; \ + ALIGN_TEXT ; \ +1: ; \ + movl _cpl,%eax ; \ + movl $HWI_MASK|SWI_MASK,_cpl ; /* limit nesting ... */ \ + sti ; /* ... to do this as early as possible */ \ + MAYBE_POPL_ES ; /* discard most of thin frame ... */ \ + popl %ecx ; /* ... original %ds ... */ \ + popl %edx ; \ + xchgl %eax,(1+ACTUALLY_PUSHED)*4(%esp) ; /* orig %eax; save cpl */ \ + pushal ; /* build fat frame (grrr) ... */ \ + pushl %ecx ; /* ... actually %ds ... */ \ + pushl %es ; \ + movl $KDSEL,%eax ; \ + movl %ax,%es ; \ + movl (2+8+0)*4(%esp),%ecx ; /* ... %ecx from thin frame ... */ \ + movl %ecx,(2+6)*4(%esp) ; /* ... to fat frame ... */ \ + movl (2+8+1)*4(%esp),%eax ; /* ... cpl from thin frame */ \ + pushl %eax ; \ + subl $4,%esp ; /* junk for unit number */ \ + MEXITCOUNT ; \ + jmp _doreti + +#define INTR(irq_num, icu, enable_icus, reg) \ + .text ; \ + SUPERALIGN_TEXT ; \ +IDTVEC(intr/**/irq_num) ; \ + pushl $0 ; /* dumby error code */ \ + pushl $0 ; /* dumby trap type */ \ + pushal ; \ + pushl %ds ; /* save our data and extra segments ... */ \ + pushl %es ; \ + movl $KDSEL,%eax ; /* ... and reload with kernel's own ... */ \ + movl %ax,%ds ; /* ... early for obsolete reasons */ \ + movl %ax,%es ; \ + movb _imen + IRQ_BYTE(irq_num),%al ; \ + orb $IRQ_BIT(irq_num),%al ; \ + movb %al,_imen + IRQ_BYTE(irq_num) ; \ + FASTER_NOP ; \ + outb %al,$icu+1 ; \ + enable_icus ; \ + incl _cnt+V_INTR ; /* tally interrupts */ \ + movl _cpl,%eax ; \ + testb $IRQ_BIT(irq_num),%reg ; \ + jne 2f ; \ +1: ; \ +Xresume/**/irq_num: ; \ + FAKE_MCOUNT(12*4(%esp)) ; /* XXX late to avoid double count */ \ + movl _intr_countp + (irq_num) * 4,%eax ; \ + incl (%eax) ; \ + movl _cpl,%eax ; \ + pushl %eax ; \ + pushl _intr_unit + (irq_num) * 4 ; \ + orl _intr_mask + (irq_num) * 4,%eax ; \ + movl %eax,_cpl ; \ + sti ; \ + call *_intr_handler + (irq_num) * 4 ; \ + movb _imen + IRQ_BYTE(irq_num),%al ; \ + andb $~IRQ_BIT(irq_num),%al ; \ + movb %al,_imen + IRQ_BYTE(irq_num) ; \ + FASTER_NOP ; \ + outb %al,$icu+1 ; \ + MEXITCOUNT ; \ + /* We could usually avoid the following jmp by inlining some of */ \ + /* _doreti, but it's probably better to use less cache. */ \ + jmp _doreti ; \ +; \ + ALIGN_TEXT ; \ +2: ; \ + /* XXX skip mcounting here to avoid double count */ \ + orb $IRQ_BIT(irq_num),_ipending + IRQ_BYTE(irq_num) ; \ + popl %es ; \ + popl %ds ; \ + popal ; \ + addl $4+4,%esp ; \ + iret + +MCOUNT_LABEL(bintr) + FAST_INTR(0, ENABLE_ICU1) + FAST_INTR(1, ENABLE_ICU1) + FAST_INTR(2, ENABLE_ICU1) + FAST_INTR(3, ENABLE_ICU1) + FAST_INTR(4, ENABLE_ICU1) + FAST_INTR(5, ENABLE_ICU1) + FAST_INTR(6, ENABLE_ICU1) + FAST_INTR(7, ENABLE_ICU1) + FAST_INTR(8, ENABLE_ICU1_AND_2) + FAST_INTR(9, ENABLE_ICU1_AND_2) + FAST_INTR(10, ENABLE_ICU1_AND_2) + FAST_INTR(11, ENABLE_ICU1_AND_2) + FAST_INTR(12, ENABLE_ICU1_AND_2) + FAST_INTR(13, ENABLE_ICU1_AND_2) + FAST_INTR(14, ENABLE_ICU1_AND_2) + FAST_INTR(15, ENABLE_ICU1_AND_2) + INTR(0, IO_ICU1, ENABLE_ICU1, al) + INTR(1, IO_ICU1, ENABLE_ICU1, al) + INTR(2, IO_ICU1, ENABLE_ICU1, al) + INTR(3, IO_ICU1, ENABLE_ICU1, al) + INTR(4, IO_ICU1, ENABLE_ICU1, al) + INTR(5, IO_ICU1, ENABLE_ICU1, al) + INTR(6, IO_ICU1, ENABLE_ICU1, al) + INTR(7, IO_ICU1, ENABLE_ICU1, al) + INTR(8, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(9, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(10, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(11, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(12, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(13, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(14, IO_ICU2, ENABLE_ICU1_AND_2, ah) + INTR(15, IO_ICU2, ENABLE_ICU1_AND_2, ah) +MCOUNT_LABEL(eintr) + + .data +ihandlers: /* addresses of interrupt handlers */ + /* actually resumption addresses for HWI's */ + .long Xresume0, Xresume1, Xresume2, Xresume3 + .long Xresume4, Xresume5, Xresume6, Xresume7 + .long Xresume8, Xresume9, Xresume10, Xresume11 + .long Xresume12, Xresume13, Xresume14, Xresume15 + .long swi_tty, swi_net, 0, 0, 0, 0, 0, 0 + .long 0, 0, 0, 0, 0, 0, swi_clock, swi_ast +imasks: /* masks for interrupt handlers */ + .space NHWI*4 /* padding; HWI masks are elsewhere */ + .long SWI_TTY_MASK, SWI_NET_MASK, 0, 0, 0, 0, 0, 0 + .long 0, 0, 0, 0, 0, 0, SWI_CLOCK_MASK, SWI_AST_MASK + +/* + * Interrupt counters and names. The format of these and the label names + * must agree with what vmstat expects. The tables are indexed by device + * ids so that we don't have to move the names around as devices are + * attached. + */ +#include "vector.h" + .globl _intrcnt, _eintrcnt +_intrcnt: + .space (NR_DEVICES + ICU_LEN) * 4 +_eintrcnt: + + .globl _intrnames, _eintrnames +_intrnames: + .ascii DEVICE_NAMES + .asciz "stray irq0" + .asciz "stray irq1" + .asciz "stray irq2" + .asciz "stray irq3" + .asciz "stray irq4" + .asciz "stray irq5" + .asciz "stray irq6" + .asciz "stray irq7" + .asciz "stray irq8" + .asciz "stray irq9" + .asciz "stray irq10" + .asciz "stray irq11" + .asciz "stray irq12" + .asciz "stray irq13" + .asciz "stray irq14" + .asciz "stray irq15" +_eintrnames: + + .text diff --git a/sys/i386/isa/wd.c b/sys/i386/isa/wd.c new file mode 100644 index 0000000..cafe0d5 --- /dev/null +++ b/sys/i386/isa/wd.c @@ -0,0 +1,1833 @@ +#define WD_COUNT_RETRIES +static int wdtest = 0; + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * from: @(#)wd.c 7.2 (Berkeley) 5/9/91 + * $Id: wd.c,v 1.42 1994/08/13 03:50:18 wollman Exp $ + */ + +/* TODO: + * o Bump error count after timeout. + * o Satisfy ATA timing in all cases. + * o Finish merging berry/sos timeout code (bump error count...). + * o Merge/fix TIH/NetBSD bad144 code. + * o Merge/fix Dyson/NetBSD clustering code. + * o Don't use polling except for initialization. Need to + * reorganize the state machine. Then "extra" interrupts + * shouldn't happen (except maybe one for initialization). + * o Fix disklabel, boot and driver inconsistencies with + * bad144 in standard versions. + * o Support extended DOS partitions. + * o Support swapping to DOS partitions. + * o Look at latest linux clustering methods. Our disksort() + * gets in the way of clustering. + * o Handle bad sectors, clustering, disklabelling, DOS + * partitions and swapping driver-independently. Use + * i386/dkbad.c for bad sectors. Swapping will need new + * driver entries for polled reinit and polled write). + */ + +#include "wd.h" +#if NWDC > 0 + +#include <sys/param.h> +#include <sys/dkbad.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/disklabel.h> +#include <sys/buf.h> +#include <sys/uio.h> +#include <sys/malloc.h> +#include <machine/cpu.h> +#include <i386/isa/isa.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/wdreg.h> +#include <sys/syslog.h> +#include <vm/vm.h> + +#define TIMEOUT 10000 +#define RETRIES 5 /* number of retries before giving up */ +#define RECOVERYTIME 500000 /* usec for controller to recover after err */ +#define MAXTRANSFER 255 /* max size of transfer in sectors */ + /* correct max is 256 but some controllers */ + /* can't handle that in all cases */ +#define BAD144_NO_CYL 0xffff /* XXX should be in dkbad.h; bad144.c uses -1 */ + +#ifdef notyet +#define wdnoreloc(dev) (minor(dev) & 0x80) /* ignore partition table */ +#endif +#define wddospart(dev) (minor(dev) & 0x40) /* use dos partitions */ +#define wdunit(dev) ((minor(dev) & 0x38) >> 3) +#define wdpart(dev) (minor(dev) & 0x7) +#define makewddev(maj, unit, part) (makedev(maj,((unit<<3)+part))) +#define WDRAW 3 /* 'd' partition isn't a partition! */ + +/* Cylinder number for doing IO to. Shares an entry in the buf struct. */ +#define b_cylin b_resid + +/* + * This biotab field doubles as a field for the physical unit number on + * the controller. + */ +#define id_physid id_scsiid + +/* + * Drive states. Used to initialize drive. + */ + +#define CLOSED 0 /* disk is closed. */ +#define WANTOPEN 1 /* open requested, not started */ +#define RECAL 2 /* doing restore */ +#define OPEN 3 /* done with open */ + +/* + * The structure of a disk drive. + */ +struct disk { + long dk_bc; /* byte count left */ + short dk_skip; /* blocks already transferred */ + char dk_ctrlr; /* physical controller number */ + char dk_unit; /* physical unit number */ + char dk_lunit; /* logical unit number */ + char dk_state; /* control state */ + u_char dk_status; /* copy of status reg. */ + u_char dk_error; /* copy of error reg. */ + u_char dk_timeout; /* countdown to next timeout */ + short dk_port; /* i/o port base */ + + u_long dk_copenpart; /* character units open on this drive */ + u_long dk_bopenpart; /* block units open on this drive */ + u_long dk_openpart; /* all units open on this drive */ + short dk_wlabel; /* label writable? */ + short dk_flags; /* drive characteistics found */ +#define DKFL_DOSPART 0x00001 /* has DOS partition table */ +#define DKFL_SINGLE 0x00004 /* sector at a time mode */ +#define DKFL_ERROR 0x00008 /* processing a disk error */ +#define DKFL_BSDLABEL 0x00010 /* has a BSD disk label */ +#define DKFL_BADSECT 0x00020 /* has a bad144 badsector table */ +#define DKFL_WRITEPROT 0x00040 /* manual unit write protect */ +#define DKFL_LABELLING 0x00080 /* readdisklabel() in progress */ + struct wdparams dk_params; /* ESDI/IDE drive/controller parameters */ + struct disklabel dk_dd; /* device configuration data */ + struct disklabel dk_dd2; /* DOS view converted to label */ + struct dos_partition + dk_dospartitions[NDOSPART]; /* DOS view of disk */ + struct dkbad dk_bad; /* bad sector table */ + long dk_badsect[127]; /* 126 plus trailing -1 marker */ +}; + +static struct disk *wddrives[NWD]; /* table of units */ +static struct buf wdtab[NWDC]; +static struct buf wdutab[NWD]; /* head of queue per drive */ +#ifdef notyet +static struct buf rwdbuf[NWD]; /* buffers for raw IO */ +#endif +static long wdxfer[NWD]; /* count of transfers */ + + +static void bad144intern(struct disk *); +static int wdprobe(struct isa_device *dvp); +static int wdattach(struct isa_device *dvp); +static void wdustart(struct disk *du); +static void wdstart(int ctrlr); +static int wdcontrol(struct buf *bp); +static int wdcommand(struct disk *du, u_int cylinder, u_int head, + u_int sector, u_int count, u_int command); +static int wdsetctlr(struct disk *du); +static int wdwsetctlr(struct disk *du); +static int wdgetctlr(struct disk *du); +static void wderror(struct buf *bp, struct disk *du, char *mesg); +static void wdflushirq(struct disk *du, int old_ipl); +static int wdreset(struct disk *du); +static void wdsleep(int ctrlr, char *wmesg); +static timeout_t wdtimeout; +static int wdunwedge(struct disk *du); +static int wdwait(struct disk *du, u_char bits_wanted, int timeout); + +struct isa_driver wdcdriver = { + wdprobe, wdattach, "wdc", +}; + +extern char *readdisklabel(); + +/* + * Probe for controller. + */ +static int +wdprobe(struct isa_device *dvp) +{ + int unit = dvp->id_unit; + struct disk *du; + + if (unit >= NWDC) + return (0); + du = malloc(sizeof *du, M_TEMP, M_NOWAIT); + if (du == NULL) + return (0); + bzero(du, sizeof *du); + du->dk_ctrlr = dvp->id_unit; + du->dk_port = dvp->id_iobase; + + /* check if we have registers that work */ + outb(du->dk_port + wd_cyl_lo, 0xa5); /* wd_cyl_lo is read/write */ + if (inb(du->dk_port + wd_cyl_lo) != 0xa5) + goto nodevice; + + if (wdreset(du) != 0 && (DELAY(RECOVERYTIME), wdreset(du)) != 0) + goto nodevice; + + /* execute a controller only command */ + if (wdcommand(du, 0, 0, 0, 0, WDCC_DIAGNOSE) != 0 + || wdwait(du, 0, TIMEOUT) < 0) + goto nodevice; + + /* + * drive(s) did not time out during diagnostic : + * Get error status and check that both drives are OK. + * Table 9-2 of ATA specs suggests that we must check for + * a value of 0x01 + * + * Strangely, some controllers will return a status of + * 0x81 (drive 0 OK, drive 1 failure), and then when + * the DRV bit is set, return status of 0x01 (OK) for + * drive 2. (This seems to contradict the ATA spec.) + */ + du->dk_error = inb(du->dk_port + wd_error); + /* printf("Error : %x\n", du->dk_error); */ + if(du->dk_error != 0x01) { + if(du->dk_error & 0x80) { /* drive 1 failure */ + + /* first set the DRV bit */ + u_int sdh; + sdh = inb(du->dk_port+ wd_sdh); + sdh = sdh | 0x10; + outb(du->dk_port+ wd_sdh, sdh); + + /* Wait, to make sure drv 1 has completed diags */ + if ( wdwait(du, 0, TIMEOUT) < 0) + goto nodevice; + + /* Get status for drive 1 */ + du->dk_error = inb(du->dk_port + wd_error); + /* printf("Error (drv 1) : %x\n", du->dk_error); */ + + if(du->dk_error != 0x01) + goto nodevice; + } else /* drive 0 fail */ + goto nodevice; + } + + + free(du, M_TEMP); + return (IO_WDCSIZE); + +nodevice: + free(du, M_TEMP); + return (0); +} + +/* + * Attach each drive if possible. + */ +static int +wdattach(struct isa_device *dvp) +{ + int unit, lunit; + struct isa_device *wdup; + struct disk *du; + + if (dvp->id_unit >= NWDC) + return (0); + + for (wdup = isa_biotab_wdc; wdup->id_driver != 0; wdup++) { + if (wdup->id_iobase != dvp->id_iobase) + continue; + lunit = wdup->id_unit; + if (lunit >= NWD) + continue; + unit = wdup->id_physid; + + du = malloc(sizeof *du, M_TEMP, M_NOWAIT); + if (du == NULL) + continue; + if (wddrives[lunit] != NULL) + panic("drive attached twice"); + wddrives[lunit] = du; + bzero(du, sizeof *du); + du->dk_ctrlr = dvp->id_unit; + du->dk_unit = unit; + du->dk_lunit = lunit; + du->dk_port = dvp->id_iobase; + + /* + * Print out description of drive. + * wdp_model can be [0..40] bytes, thus \0 can be missing so + * so copy it and add a null before printing. + */ + if (wdgetctlr(du) == 0) { + char buf[sizeof(du->dk_params.wdp_model) + 1]; + bcopy(du->dk_params.wdp_model, buf, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + printf("wdc%d: unit %d (wd%d): <%s>\n", + dvp->id_unit, unit, lunit, buf); + if (du->dk_params.wdp_heads == 0) + printf("wd%d: size unknown\n", lunit); + else + printf("wd%d: %luMB (%lu total sec), ", + lunit, + du->dk_dd.d_secperunit + * du->dk_dd.d_secsize / (1024 * 1024), + du->dk_dd.d_secperunit); + printf("%lu cyl, %lu head, %lu sec, bytes/sec %lu\n", + du->dk_dd.d_ncylinders, + du->dk_dd.d_ntracks, + du->dk_dd.d_nsectors, + du->dk_dd.d_secsize); + /* + * Start timeout routine for this drive. + * XXX timeout should be per controller. + */ + wdtimeout((caddr_t)du); + } else { + free(du, M_TEMP); + wddrives[lunit] = NULL; + } + } + + /* + * Discard any interrupts generated by wdgetctlr(). wdflushirq() + * doesn't work now because the ambient ipl is too high. + */ + wdtab[dvp->id_unit].b_active = 2; + + return (1); +} + +/* Read/write routine for a buffer. Finds the proper unit, range checks + * arguments, and schedules the transfer. Does not wait for the transfer + * to complete. Multi-page transfers are supported. All I/O requests must + * be a multiple of a sector in length. + */ +void +wdstrategy(register struct buf *bp) +{ + register struct buf *dp; + struct disk *du; + int lunit = wdunit(bp->b_dev); + int s; + + /* valid unit, controller, and request? */ + if (lunit >= NWD || bp->b_blkno < 0 || (du = wddrives[lunit]) == NULL) { + + bp->b_error = EINVAL; + bp->b_flags |= B_ERROR; + goto done; + } + + /* "soft" write protect check */ + if ((du->dk_flags & DKFL_WRITEPROT) && (bp->b_flags & B_READ) == 0) { + bp->b_error = EROFS; + bp->b_flags |= B_ERROR; + goto done; + } + + /* + * Do bounds checking, adjust transfer, and set b_cylin. + */ + if (bounds_check_with_label(bp, wddospart(bp->b_dev) + ? &du->dk_dd2 : &du->dk_dd, + du->dk_wlabel) <= 0) + goto done; + + /* + * Check for *any* block on this transfer being on the bad block list + * if it is, then flag the block as a transfer that requires + * bad block handling. Also, used as a hint for low level disksort + * clustering code to keep from coalescing a bad transfer into + * a normal transfer. Single block transfers for a large number of + * blocks associated with a cluster I/O are undersirable. + */ + if( du->dk_flags & DKFL_BADSECT) { + int i; + int nsecs = howmany(bp->b_bcount, DEV_BSIZE); + int blkend = bp->b_pblkno + nsecs; + for(i=0;du->dk_badsect[i] != -1 && du->dk_badsect[i] < blkend;i++) { + if( du->dk_badsect[i] >= bp->b_pblkno) { + bp->b_flags |= B_BAD; + break; + } + } + } + + /* queue transfer on drive, activate drive and controller if idle */ + dp = &wdutab[lunit]; + s = splbio(); + + /* cldisksort(dp, bp, 254*DEV_BSIZE); */ + disksort(dp, bp); + + if (dp->b_active == 0) + wdustart(du); /* start drive */ + + /* Pick up changes made by readdisklabel(). */ + if (du->dk_flags & DKFL_LABELLING && du->dk_state > RECAL) { + wdsleep(du->dk_ctrlr, "wdlab"); + du->dk_state = WANTOPEN; + } + + if (wdtab[du->dk_ctrlr].b_active == 0) + wdstart(du->dk_ctrlr); /* start controller */ + splx(s); + return; + +done: + s = splbio(); + /* toss transfer, we're done early */ + biodone(bp); + splx(s); +} + +/* + * Routine to queue a command to the controller. The unit's + * request is linked into the active list for the controller. + * If the controller is idle, the transfer is started. + */ +static void +wdustart(register struct disk *du) +{ + register struct buf *bp, *dp = &wdutab[du->dk_lunit]; + int ctrlr = du->dk_ctrlr; + + /* unit already active? */ + if (dp->b_active) + return; + + /* anything to start? */ + bp = dp->b_actf; + if (bp == NULL) + return; + + dp->b_actf = bp->b_actf; + bp->b_actf = NULL; + /* link onto controller queue */ + if (wdtab[ctrlr].b_actf == NULL) { + wdtab[ctrlr].b_actf = bp; + } else { + *wdtab[ctrlr].b_actb = bp; + } + wdtab[ctrlr].b_actb = &bp->b_actf; + + /* mark the drive unit as busy */ + dp->b_active = 1; +} + +/* + * Controller startup routine. This does the calculation, and starts + * a single-sector read or write operation. Called to start a transfer, + * or from the interrupt routine to continue a multi-sector transfer. + * RESTRICTIONS: + * 1. The transfer length must be an exact multiple of the sector size. + */ + +static void +wdstart(int ctrlr) +{ + register struct disk *du; + register struct buf *bp; + struct disklabel *lp; + struct buf *dp; + register struct bt_bad *bt_ptr; + long blknum, cylin, head, sector; + long secpertrk, secpercyl; + int lunit; + +loop: + /* is there a drive for the controller to do a transfer with? */ + bp = wdtab[ctrlr].b_actf; + if (bp == NULL) + return; + + /* obtain controller and drive information */ + lunit = wdunit(bp->b_dev); + du = wddrives[lunit]; + + /* if not really a transfer, do control operations specially */ + if (du->dk_state < OPEN) { + if (du->dk_state != WANTOPEN) + printf("wd%d: wdstart: weird dk_state %d\n", + du->dk_lunit, du->dk_state); + if (wdcontrol(bp) != 0) + printf("wd%d: wdstart: wdcontrol returned nonzero, state = %d\n", + du->dk_lunit, du->dk_state); + return; + } + + /* calculate transfer details */ + blknum = bp->b_pblkno + du->dk_skip; +#ifdef WDDEBUG + if (du->dk_skip == 0) + printf("wd%d: wdstart: %s %d@%d; map ", lunit, + (bp->b_flags & B_READ) ? "read" : "write", + bp->b_bcount, blknum); + else + printf(" %d)%x", du->dk_skip, inb(du->dk_port + wd_altsts)); +#endif + + lp = &du->dk_dd; + secpertrk = lp->d_nsectors; + secpercyl = lp->d_secpercyl; + + if (du->dk_skip == 0) { + du->dk_bc = bp->b_bcount; + if (bp->b_flags & B_BAD + /* + * XXX handle large transfers inefficiently instead + * of crashing on them. + */ + || howmany(du->dk_bc, DEV_BSIZE) > MAXTRANSFER) + du->dk_flags |= DKFL_SINGLE; + } + + if ((du->dk_flags & (DKFL_SINGLE|DKFL_BADSECT)) /* 19 Aug 92*/ + == (DKFL_SINGLE|DKFL_BADSECT)) { + int i; + + for(i=0; + du->dk_badsect[i] != -1 && du->dk_badsect[i] <= blknum; + i++) { + + if( du->dk_badsect[i] == blknum) { + /* + * XXX the offset of the bad sector table ought + * to be stored in the in-core copy of the table. + */ +#define BAD144_PART 2 /* XXX scattered magic numbers */ +#define BSD_PART 0 /* XXX should be 2 but bad144.c uses 0 */ + if (lp->d_partitions[BSD_PART].p_offset != 0) + blknum = lp->d_partitions[BAD144_PART].p_offset + + lp->d_partitions[BAD144_PART].p_size; + else + blknum = lp->d_secperunit; + blknum -= lp->d_nsectors + i + 1; + + break; + } + } + } + + + cylin = blknum / secpercyl; + head = (blknum % secpercyl) / secpertrk; + sector = blknum % secpertrk; + + wdtab[ctrlr].b_active = 1; /* mark controller active */ + + /* if starting a multisector transfer, or doing single transfers */ + if (du->dk_skip == 0 || (du->dk_flags & DKFL_SINGLE)) { + u_int command; + u_int count; + + if (wdtab[ctrlr].b_errcnt && (bp->b_flags & B_READ) == 0) + du->dk_bc += DEV_BSIZE; + +#ifdef B_FORMAT + if (bp->b_flags & B_FORMAT) { + command = WDCC_FORMAT; + count = lp->d_nsectors; + sector = lp->d_gap3 - 1; /* + 1 later */ + } else +#endif + { + if (du->dk_flags & DKFL_SINGLE) + count = 1; + else + count = howmany(du->dk_bc, DEV_BSIZE); + command = (bp->b_flags & B_READ) + ? WDCC_READ : WDCC_WRITE; + } + + /* + * XXX this loop may never terminate. The code to handle + * counting down of retries and eventually failing the i/o + * is in wdintr() and we can't get there from here. + */ + if (wdtest != 0) { + if (--wdtest == 0) { + wdtest = 100; + printf("dummy wdunwedge\n"); + wdunwedge(du); + } + } + while (wdcommand(du, cylin, head, sector, count, command) + != 0) { + wderror(bp, du, + "wdstart: timeout waiting to give command"); + wdunwedge(du); + } +#ifdef WDDEBUG + printf("cylin %ld head %ld sector %ld addr %x sts %x\n", + cylin, head, sector, + (int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE, + inb(du->dk_port + wd_altsts)); +#endif + } + + /* + * Schedule wdtimeout() to wake up after a few seconds. Retrying + * unmarked bad blocks can take 3 seconds! Then it is not good that + * we retry 5 times. + * + * XXX wdtimeout() doesn't increment the error count so we may loop + * forever. More seriously, the loop isn't forever but causes a + * crash. + * + * TODO fix b_resid bug elsewhere (fd.c....). Fix short but positive + * counts being discarded after there is an error (in physio I + * think). Discarding them would be OK if the (special) file offset + * was not advanced. + */ + du->dk_timeout = 1 + 3; + + /* If this is a read operation, just go away until it's done. */ + if (bp->b_flags & B_READ) + return; + + /* Ready to send data? */ + if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) < 0) { + wderror(bp, du, "wdstart: timeout waiting for DRQ"); + /* + * XXX what do we do now? If we've just issued the command, + * then we can treat this failure the same as a command + * failure. But if we are continuing a multi-sector write, + * the command was issued ages ago, so we can't simply + * restart it. + * + * XXX we waste a lot of time unnecessarily translating block + * numbers to cylin/head/sector for continued i/o's. + */ + } + + /* then send it! */ + outsw(du->dk_port + wd_data, + (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), + DEV_BSIZE / sizeof(short)); + du->dk_bc -= DEV_BSIZE; +} + +/* Interrupt routine for the controller. Acknowledge the interrupt, check for + * errors on the current operation, mark it done if necessary, and start + * the next request. Also check for a partially done transfer, and + * continue with the next chunk if so. + */ +void +wdintr(int unit) +{ + register struct disk *du; + register struct buf *bp, *dp; + + if (wdtab[unit].b_active == 2) + return; /* intr in wdflushirq() */ + if (!wdtab[unit].b_active) { +#ifndef LAPTOP + printf("wdc%d: extra interrupt\n", unit); +#endif + return; + } + + bp = wdtab[unit].b_actf; + du = wddrives[wdunit(bp->b_dev)]; + dp = &wdutab[du->dk_lunit]; + + du->dk_timeout = 0; + + if (wdwait(du, 0, TIMEOUT) < 0) { + wderror(bp, du, "wdintr: timeout waiting for status"); + du->dk_status |= WDCS_ERR; /* XXX */ + } + + /* is it not a transfer, but a control operation? */ + if (du->dk_state < OPEN) { + wdtab[unit].b_active = 0; + switch (wdcontrol(bp)) { + case 0: + return; + case 1: + wdstart(unit); + return; + case 2: + goto done; + } + } + + /* have we an error? */ + if (du->dk_status & (WDCS_ERR | WDCS_ECCCOR)) { +oops: +#ifdef WDDEBUG + wderror(bp, du, "wdintr"); +#endif + if ((du->dk_flags & DKFL_SINGLE) == 0) { + du->dk_flags |= DKFL_ERROR; + goto outt; + } +#ifdef B_FORMAT + if (bp->b_flags & B_FORMAT) { + bp->b_error = EIO; + bp->b_flags |= B_ERROR; + goto done; + } +#endif + + /* error or error correction? */ + if (du->dk_status & WDCS_ERR) { + if (++wdtab[unit].b_errcnt < RETRIES) { + wdtab[unit].b_active = 0; + } else { + wderror(bp, du, "hard error"); + bp->b_error = EIO; + bp->b_flags |= B_ERROR; /* flag the error */ + } + } else + wderror(bp, du, "soft ecc"); + } + + /* + * If this was a successful read operation, fetch the data. + */ + if (((bp->b_flags & (B_READ | B_ERROR)) == B_READ) + && wdtab[unit].b_active) { + int chk, dummy; + + chk = min(DEV_BSIZE / sizeof(short), du->dk_bc / sizeof(short)); + + /* ready to receive data? */ + if ((du->dk_status & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ)) + != (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ)) + wderror(bp, du, "wdintr: read intr arrived early"); + if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) { + wderror(bp, du, "wdintr: read error detected late"); + goto oops; + } + + /* suck in data */ + insw(du->dk_port + wd_data, + (void *)((int)bp->b_un.b_addr + du->dk_skip * DEV_BSIZE), + chk); + du->dk_bc -= chk * sizeof(short); + + /* XXX for obsolete fractional sector reads. */ + while (chk++ < DEV_BSIZE / sizeof(short)) + insw(du->dk_port + wd_data, &dummy, 1); + } + + wdxfer[du->dk_lunit]++; +outt: + if (wdtab[unit].b_active) { + if ((bp->b_flags & B_ERROR) == 0) { + du->dk_skip++; /* add to successful sectors */ + if (wdtab[unit].b_errcnt) + wderror(bp, du, "soft error"); + wdtab[unit].b_errcnt = 0; + + /* see if more to transfer */ + if (du->dk_bc > 0 && (du->dk_flags & DKFL_ERROR) == 0) { + wdtab[unit].b_active = 0; + wdstart(unit); + return; /* next chunk is started */ + } else if ((du->dk_flags & (DKFL_SINGLE | DKFL_ERROR)) + == DKFL_ERROR) { + du->dk_skip = 0; + du->dk_flags &= ~DKFL_ERROR; + du->dk_flags |= DKFL_SINGLE; + wdtab[unit].b_active = 0; + wdstart(unit); + return; /* redo xfer sector by sector */ + } + } + +done: ; + /* done with this transfer, with or without error */ + du->dk_flags &= ~DKFL_SINGLE; + wdtab[unit].b_actf = bp->b_actf; + wdtab[unit].b_errcnt = 0; + bp->b_resid = bp->b_bcount - du->dk_skip * DEV_BSIZE; + dp->b_active = 0; + dp->b_errcnt = 0; + du->dk_skip = 0; + biodone(bp); + } + + /* controller idle */ + wdtab[unit].b_active = 0; + + /* anything more on drive queue? */ + wdustart(du); + /* anything more for controller to do? */ + if (wdtab[unit].b_actf) + wdstart(unit); +} + +/* + * Initialize a drive. + */ +int +wdopen(dev_t dev, int flags, int fmt, struct proc *p) +{ + register unsigned int lunit; + register struct disk *du; + int part = wdpart(dev), mask = 1 << part; + struct partition *pp; + char *msg; + struct disklabel save_label; + + lunit = wdunit(dev); + if (lunit >= NWD) + return (ENXIO); + du = wddrives[lunit]; + if (du == NULL) + return (ENXIO); + + /* Finish flushing IRQs left over from wdattach(). */ + if (wdtab[du->dk_ctrlr].b_active == 2) + wdtab[du->dk_ctrlr].b_active = 0; + + /* + * That's all for valid DOS partitions. We don't need a BSD label. + * The openmask is only used for checking BSD partitions so we don't + * need to maintain it. + */ + if (wddospart(dev)) { + /* XXX we do need a disklabel for now. */ + if ((du->dk_flags & DKFL_BSDLABEL) == 0) + return (ENXIO); + + return (part > NDOSPART ? ENXIO : 0); + } + + while (du->dk_flags & DKFL_LABELLING) + tsleep((caddr_t)&du->dk_flags, PZERO - 1, "wdopen", 1); + if ((du->dk_flags & DKFL_BSDLABEL) == 0) { + /* + * wdtab[ctrlr].b_active != 0 implies + * wdutab[lunit].b_actf == NULL (?) + * so the following guards most things (until the next i/o). + * It doesn't guard against a new i/o starting and being + * affected by the label being changed. Sigh. + */ + wdsleep(du->dk_ctrlr, "wdopn1"); + + du->dk_flags |= DKFL_LABELLING | DKFL_WRITEPROT; + du->dk_state = WANTOPEN; + wdutab[lunit].b_actf = NULL; + + /* + * Read label using WDRAW partition. + * + * If the drive has an MBR, then the current geometry (from + * wdgetctlr()) is used to read it; then the BIOS/DOS + * geometry is inferred and used to read the label off the + * 'c' partition. Otherwise the label is read using the + * current geometry. The label gives the final geometry. + * If bad sector handling is enabled, then this geometry + * is used to read the bad sector table. The geometry + * changes occur inside readdisklabel() and are propagated + * to the driver by resetting the state machine. + */ + save_label = du->dk_dd; + du->dk_dd.d_partitions[WDRAW].p_offset = 0; + du->dk_dd.d_partitions[WDRAW].p_size = 0x7fffffff;/* XXX */ +#define WDSTRATEGY ((int (*)(struct buf *)) wdstrategy) /* XXX */ + msg = readdisklabel(makewddev(major(dev), lunit, WDRAW), + WDSTRATEGY, &du->dk_dd, + du->dk_dospartitions, &du->dk_bad); +/* + msg = readdisklabel(makewddev(major(dev), lunit, WDRAW), + WDSTRATEGY, &du->dk_dd); +*/ + du->dk_flags &= ~DKFL_LABELLING; + if (msg != NULL) { + du->dk_dd = save_label; + log(LOG_WARNING, "wd%d: cannot find label (%s)\n", + lunit, msg); + if (part != WDRAW) + return (EINVAL); /* XXX needs translation */ + } else { + int dospart; + unsigned long newsize, offset, size; + + du->dk_flags |= DKFL_BSDLABEL; + du->dk_flags &= ~DKFL_WRITEPROT; + if (du->dk_dd.d_flags & D_BADSECT) { + du->dk_flags |= DKFL_BADSECT; + bad144intern(du); + } + + /* + * Force WDRAW partition to be the whole disk. + */ + offset = du->dk_dd.d_partitions[WDRAW].p_offset; + if (offset != 0) { + printf( + "wd%d: changing offset of 'd' partition from %lu to 0\n", + du->dk_lunit, offset); + du->dk_dd.d_partitions[WDRAW].p_offset = 0; + } + size = du->dk_dd.d_partitions[WDRAW].p_size; + newsize = du->dk_dd.d_secperunit; /* XXX */ + if (size != newsize) { + printf( + "wd%d: changing size of 'd' partition from %lu to %lu\n", + du->dk_lunit, size, newsize); + du->dk_dd.d_partitions[WDRAW].p_size = newsize; + } + + /* + * Convert DOS partition data to a label. + */ + du->dk_dd2 = du->dk_dd; + bzero(du->dk_dd2.d_partitions, + sizeof du->dk_dd2.d_partitions); + du->dk_dd2.d_partitions[0].p_size + = du->dk_dd.d_secperunit; /* XXX */ + for (dospart = 1; dospart <= NDOSPART; dospart++) { + du->dk_dd2.d_partitions[dospart].p_offset = + du->dk_dospartitions[dospart - 1].dp_start; + du->dk_dd2.d_partitions[dospart].p_size = + du->dk_dospartitions[dospart - 1].dp_size; + } + } + + /* Pick up changes made by readdisklabel(). */ + wdsleep(du->dk_ctrlr, "wdopn2"); + du->dk_state = WANTOPEN; + } + + /* + * Warn if a partion is opened that overlaps another partition which + * is open unless one is the "raw" partition (whole disk). + */ + if ((du->dk_openpart & mask) == 0 && part != WDRAW) { + int start, end; + + pp = &du->dk_dd.d_partitions[part]; + start = pp->p_offset; + end = pp->p_offset + pp->p_size; + for (pp = du->dk_dd.d_partitions; + pp < &du->dk_dd.d_partitions[du->dk_dd.d_npartitions]; + pp++) { + if (pp->p_offset + pp->p_size <= start || + pp->p_offset >= end) + continue; + if (pp - du->dk_dd.d_partitions == WDRAW) + continue; + if (du->dk_openpart + & (1 << (pp - du->dk_dd.d_partitions))) + log(LOG_WARNING, + "wd%d%c: overlaps open partition (%c)\n", + lunit, part + 'a', + pp - du->dk_dd.d_partitions + 'a'); + } + } + if (part >= du->dk_dd.d_npartitions && part != WDRAW) + return (ENXIO); + + switch (fmt) { + case S_IFCHR: + du->dk_copenpart |= mask; + break; + case S_IFBLK: + du->dk_bopenpart |= mask; + break; + } + du->dk_openpart = du->dk_copenpart | du->dk_bopenpart; + + return (0); +} + +/* + * Implement operations other than read/write. + * Called from wdstart or wdintr during opens and formats. + * Uses finite-state-machine to track progress of operation in progress. + * Returns 0 if operation still in progress, 1 if completed, 2 if error. + */ +static int +wdcontrol(register struct buf *bp) +{ + register struct disk *du; + int ctrlr; + + du = wddrives[wdunit(bp->b_dev)]; + ctrlr = du->dk_ctrlr; + + switch (du->dk_state) { + case WANTOPEN: +tryagainrecal: + wdtab[ctrlr].b_active = 1; + if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0) { + wderror(bp, du, "wdcontrol: wdcommand failed"); + goto maybe_retry; + } + du->dk_state = RECAL; + return (0); + case RECAL: + if (du->dk_status & WDCS_ERR || wdsetctlr(du) != 0) { + wderror(bp, du, "wdcontrol: recal failed"); +maybe_retry: + if (du->dk_status & WDCS_ERR) + wdunwedge(du); + du->dk_state = WANTOPEN; + if (++wdtab[ctrlr].b_errcnt < RETRIES) + goto tryagainrecal; + bp->b_error = ENXIO; /* XXX needs translation */ + bp->b_flags |= B_ERROR; + return (2); + } + wdtab[ctrlr].b_errcnt = 0; + du->dk_state = OPEN; + /* + * The rest of the initialization can be done by normal + * means. + */ + return (1); + } + panic("wdcontrol"); + return (2); +} + +/* + * Wait uninterruptibly until controller is not busy, then send it a command. + * The wait usually terminates immediately because we waited for the previous + * command to terminate. + */ +static int +wdcommand(struct disk *du, u_int cylinder, u_int head, u_int sector, + u_int count, u_int command) +{ + u_int wdc; + + if (wdwait(du, 0, TIMEOUT) < 0) + return (1); + wdc = du->dk_port; + outb(wdc + wd_precomp, du->dk_dd.d_precompcyl / 4); + outb(wdc + wd_cyl_lo, cylinder); + outb(wdc + wd_cyl_hi, cylinder >> 8); + outb(wdc + wd_sdh, WDSD_IBM | (du->dk_unit << 4) | head); + outb(wdc + wd_sector, sector + 1); + outb(wdc + wd_seccnt, count); + if (wdwait(du, command == WDCC_DIAGNOSE || command == WDCC_IDC + ? 0 : WDCS_READY, TIMEOUT) < 0) + return (1); + outb(wdc + wd_command, command); + return (0); +} + +/* + * issue IDC to drive to tell it just what geometry it is to be. + */ +static int +wdsetctlr(struct disk *du) +{ + int error = 0; +#ifdef WDDEBUG + printf("wd(%d,%d): wdsetctlr: C %lu H %lu S %lu\n", + du->dk_ctrlr, du->dk_unit, + du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks, + du->dk_dd.d_nsectors); +#endif + if (du->dk_dd.d_ntracks == 0 || du->dk_dd.d_ntracks > 16) { + struct wdparams *wp; + + printf("wd%d: can't handle %lu heads from partition table ", + du->dk_lunit, du->dk_dd.d_ntracks); + /* obtain parameters */ + wp = &du->dk_params; + if (wp->wdp_heads > 0 && wp->wdp_heads <= 16) { + printf("(controller value %lu restored)\n", + wp->wdp_heads); + du->dk_dd.d_ntracks = wp->wdp_heads; + } + else { + printf("(truncating to 16)\n"); + du->dk_dd.d_ntracks = 16; + } + } + + if (du->dk_dd.d_nsectors == 0 || du->dk_dd.d_nsectors > 255) { + printf("wd%d: cannot handle %lu sectors (max 255)\n", + du->dk_lunit, du->dk_dd.d_nsectors); + error = 1; + } + if (error) { + wdtab[du->dk_ctrlr].b_errcnt += RETRIES; + return (1); + } + if (wdcommand(du, du->dk_dd.d_ncylinders, du->dk_dd.d_ntracks - 1, 0, + du->dk_dd.d_nsectors, WDCC_IDC) != 0 + || wdwait(du, WDCS_READY, TIMEOUT) < 0) { + wderror((struct buf *)NULL, du, "wdsetctlr failed"); + return (1); + } + return (0); +} + +/* + * Wait until driver is inactive, then set up controller. + */ +static int +wdwsetctlr(struct disk *du) +{ + int stat; + int x; + + wdsleep(du->dk_ctrlr, "wdwset"); + x = splbio(); + stat = wdsetctlr(du); + wdflushirq(du, x); + splx(x); + return (stat); +} + +/* + * issue READP to drive to ask it what it is. + */ +static int +wdgetctlr(struct disk *du) +{ + int i; + char tb[DEV_BSIZE]; + struct wdparams *wp; + + if (wdcommand(du, 0, 0, 0, 0, WDCC_READP) != 0 + || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) != 0) { + /* XXX need to check error status after final transfer. */ + /* + * Old drives don't support WDCC_READP. Try a seek to 0. + * Some IDE controllers return trash if there is no drive + * attached, so first test that the drive can be selected. + * This also avoids long waits for nonexistent drives. + */ + if (wdwait(du, 0, TIMEOUT) < 0) + return (1); + outb(du->dk_port + wd_sdh, WDSD_IBM | (du->dk_unit << 4)); + DELAY(5000); /* usually unnecessary; drive select is fast */ + if ((inb(du->dk_port + wd_status) & (WDCS_BUSY | WDCS_READY)) + != WDCS_READY + || wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0 + || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0) + return (1); + + /* + * Fake minimal drive geometry for reading the MBR. + * readdisklabel() may enlarge it to read the label and the + * bad sector table. + */ + du->dk_dd.d_secsize = DEV_BSIZE; + du->dk_dd.d_nsectors = 17; + du->dk_dd.d_ntracks = 1; + du->dk_dd.d_ncylinders = 1; + du->dk_dd.d_secpercyl = 17; + du->dk_dd.d_secperunit = 17; + + /* + * Fake maximal drive size for writing the label. + */ + du->dk_dd.d_partitions[WDRAW].p_size = 64 * 16 * 1024; + + /* + * Fake some more of the label for printing by disklabel(1) + * in case there is no real label. + */ + du->dk_dd.d_type = DTYPE_ST506; + du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; + strncpy(du->dk_dd.d_typename, "Fake geometry", + sizeof du->dk_dd.d_typename); + + /* Fake the model name for printing by wdattach(). */ + strncpy(du->dk_params.wdp_model, "unknown", + sizeof du->dk_params.wdp_model); + + return (0); + } + + /* obtain parameters */ + wp = &du->dk_params; + insw(du->dk_port + wd_data, tb, sizeof(tb) / sizeof(short)); + bcopy(tb, wp, sizeof(struct wdparams)); + + /* shuffle string byte order */ + for (i = 0; i < sizeof(wp->wdp_model); i += 2) { + u_short *p; + + p = (u_short *) (wp->wdp_model + i); + *p = ntohs(*p); + } + /* + * Clean up the wdp_model by converting nulls to spaces, and + * then removing the trailing spaces. + */ + for (i=0; i < sizeof(wp->wdp_model); i++) { + if (wp->wdp_model[i] == '\0') { + wp->wdp_model[i] = ' '; + } + } + for (i=sizeof(wp->wdp_model)-1; i>=0 && wp->wdp_model[i]==' '; i--) { + wp->wdp_model[i] = '\0'; + } + +#ifdef WDDEBUG + printf( +"\nwd(%d,%d): wdgetctlr: gc %x cyl %d trk %d sec %d type %d sz %d model %s\n", + du->dk_ctrlr, du->dk_unit, wp->wdp_config, + wp->wdp_fixedcyl + wp->wdp_removcyl, wp->wdp_heads, + wp->wdp_sectors, wp->wdp_cntype, wp->wdp_cnsbsz, + wp->wdp_model); +#endif + + /* update disklabel given drive information */ + du->dk_dd.d_secsize = DEV_BSIZE; + du->dk_dd.d_ncylinders = wp->wdp_fixedcyl + wp->wdp_removcyl /*+- 1*/ ; + du->dk_dd.d_ntracks = wp->wdp_heads; + du->dk_dd.d_nsectors = wp->wdp_sectors; + du->dk_dd.d_secpercyl = du->dk_dd.d_ntracks * du->dk_dd.d_nsectors; + du->dk_dd.d_partitions[WDRAW].p_size = du->dk_dd.d_secperunit + = du->dk_dd.d_secpercyl * du->dk_dd.d_ncylinders; + /* dubious ... */ + bcopy("ESDI/IDE", du->dk_dd.d_typename, 9); + bcopy(wp->wdp_model + 20, du->dk_dd.d_packname, 14 - 1); + /* better ... */ + du->dk_dd.d_type = DTYPE_ESDI; + du->dk_dd.d_subtype |= DSTYPE_GEOMETRY; + + return (0); +} + + +/* ARGSUSED */ +int +wdclose(dev_t dev, int flags, int fmt) +{ + register struct disk *du; + int part = wdpart(dev), mask = 1 << part; + + if (wddospart(dev)) + return (0); + + du = wddrives[wdunit(dev)]; + + switch (fmt) { + case S_IFCHR: + du->dk_copenpart &= ~mask; + break; + case S_IFBLK: + du->dk_bopenpart &= ~mask; + break; + } + du->dk_openpart = du->dk_copenpart | du->dk_bopenpart; + + return (0); +} + +int +wdioctl(dev_t dev, int cmd, caddr_t addr, int flag) +{ + int lunit = wdunit(dev); + register struct disk *du; + int error = 0; +#ifdef notyet + struct uio auio; + struct iovec aiov; +#endif + + du = wddrives[lunit]; + + switch (cmd) { + + case DIOCSBAD: + if ((flag & FWRITE) == 0) + error = EBADF; + else + du->dk_bad = *(struct dkbad *)addr; + break; + + case DIOCGDINFO: + *(struct disklabel *)addr = du->dk_dd; + break; + + case DIOCGPART: + if (wddospart(dev)) + return (EINVAL); + ((struct partinfo *)addr)->disklab = &du->dk_dd; + ((struct partinfo *)addr)->part = + &du->dk_dd.d_partitions[wdpart(dev)]; + break; + + case DIOCSDINFO: + if ((flag & FWRITE) == 0) + error = EBADF; + else + error = setdisklabel(&du->dk_dd, + (struct disklabel *)addr, +#if 0 + /* + * XXX setdisklabel() uses the + * openmask to allow it to reject + * changing open partitions. Why + * are we pretending nothing is + * open? + */ + du->dk_flags & DKFL_BSDLABEL + ? du->dk_openpart : +#endif + 0, + du->dk_dospartitions); + if (error == 0) { + du->dk_flags |= DKFL_BSDLABEL; + wdwsetctlr(du); /* XXX - check */ + } + break; + + case DIOCWLABEL: + du->dk_flags &= ~DKFL_WRITEPROT; + if ((flag & FWRITE) == 0) + error = EBADF; + else + du->dk_wlabel = *(int *)addr; + break; + + case DIOCWDINFO: + du->dk_flags &= ~DKFL_WRITEPROT; + if ((flag & FWRITE) == 0) + error = EBADF; + else if ((error = setdisklabel(&du->dk_dd, + (struct disklabel *)addr, +#if 0 + du->dk_flags & DKFL_BSDLABEL + ? du->dk_openpart : +#endif + 0, + du->dk_dospartitions)) == 0) { + int wlab; + + du->dk_flags |= DKFL_BSDLABEL; + wdwsetctlr(du); /* XXX - check */ + + /* simulate opening partition 0 so write succeeds */ + du->dk_openpart |= (1 << 0); /* XXX */ + wlab = du->dk_wlabel; + du->dk_wlabel = 1; + error = writedisklabel(dev, WDSTRATEGY, + &du->dk_dd, du->dk_dospartitions); + du->dk_openpart = du->dk_copenpart | du->dk_bopenpart; + du->dk_wlabel = wlab; + } + break; + +#ifdef notyet + case DIOCGDINFOP: + *(struct disklabel **)addr = &(du->dk_dd); + break; + + case DIOCWFORMAT: + if ((flag & FWRITE) == 0) + error = EBADF; + else { + register struct format_op *fop; + + fop = (struct format_op *)addr; + aiov.iov_base = fop->df_buf; + aiov.iov_len = fop->df_count; + auio.uio_iov = &aiov; + auio.uio_iovcnt = 1; + auio.uio_resid = fop->df_count; + auio.uio_segflg = 0; + auio.uio_offset = + fop->df_startblk * du->dk_dd.d_secsize; +#error /* XXX the 386BSD interface is different */ + error = physio(wdformat, &rwdbuf[lunit], 0, dev, + B_WRITE, minphys, &auio); + fop->df_count -= auio.uio_resid; + fop->df_reg[0] = du->dk_status; + fop->df_reg[1] = du->dk_error; + } + break; +#endif + + default: + error = ENOTTY; + break; + } + return (error); +} + +#ifdef B_FORMAT +int +wdformat(struct buf *bp) +{ + + bp->b_flags |= B_FORMAT; + return (wdstrategy(bp)); +} +#endif + +int +wdsize(dev_t dev) +{ + int lunit = wdunit(dev), part = wdpart(dev), val; + struct disk *du; + int size; + + if (lunit >= NWD || wddospart(dev) || (du = wddrives[lunit]) == NULL) { + return (-1); + } + val = 0; + if (du->dk_state == CLOSED) { + val = wdopen(makewddev(major(dev), lunit, WDRAW), + FREAD, S_IFBLK, 0); + } + if (val != 0 || du->dk_flags & DKFL_WRITEPROT) { + return (-1); + } + size = ((int)du->dk_dd.d_partitions[part].p_size); + return size; +} + +extern char *ptvmmap; /* poor name! */ + +/* + * Dump core after a system crash. + */ +int +wddump(dev_t dev) +{ + register struct disk *du; + register struct bt_bad *bt_ptr; + struct disklabel *lp; + long num; /* number of sectors to write */ + int lunit, part; + long blkoff, blknum; + long blkchk, blkcnt, blknext; + long cylin, head, sector; + long secpertrk, secpercyl, nblocks; + char *addr; + extern int Maxmem; + static int wddoingadump = 0; + extern caddr_t CADDR1; + + /* Toss any characters present prior to dump. */ + while (sgetc(1)) + ; + + /* Check for acceptable device. */ + /* XXX should reset to maybe allow du->dk_state < OPEN. */ + lunit = wdunit(dev); /* eventually support floppies? */ + part = wdpart(dev); + if (lunit >= NWD || wddospart(dev) || (du = wddrives[lunit]) == NULL + || du->dk_state < OPEN || du->dk_flags & DKFL_WRITEPROT) + return (ENXIO); + + /* Size of memory to dump, in disk sectors. */ + num = (u_long)Maxmem * NBPG / du->dk_dd.d_secsize; + + secpertrk = du->dk_dd.d_nsectors; + secpercyl = du->dk_dd.d_secpercyl; + nblocks = du->dk_dd.d_partitions[part].p_size; + blkoff = du->dk_dd.d_partitions[part].p_offset; + +#if 0 + pg("part %x, nblocks %d, dumplo %d num %d\n", + part, nblocks, dumplo, num); +#endif + + /* Check transfer bounds against partition size. */ + if (dumplo < 0 || dumplo + num > nblocks) + return (EINVAL); + + /* Check if we are being called recursively. */ + if (wddoingadump) + return (EFAULT); + +#if 0 + /* Mark controller active for if we panic during the dump. */ + wdtab[du->dk_ctrlr].b_active = 1; +#endif + wddoingadump = 1; + + /* Recalibrate the drive. */ + DELAY(5); /* ATA spec XXX NOT */ + if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) != 0 + || wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0 + || wdsetctlr(du) != 0) { + wderror((struct buf *)NULL, du, "wddump: recalibrate failed"); + return (EIO); + } + + du->dk_flags |= DKFL_SINGLE; + addr = (char *) 0; + blknum = dumplo + blkoff; + while (num > 0) { + blkcnt = num; + if (blkcnt > MAXTRANSFER) + blkcnt = MAXTRANSFER; + /* Keep transfer within current cylinder. */ + if ((blknum + blkcnt - 1) / secpercyl != blknum / secpercyl) + blkcnt = secpercyl - (blknum % secpercyl); + blknext = blknum + blkcnt; + + /* + * See if one of the sectors is in the bad sector list + * (if we have one). If the first sector is bad, then + * reduce the transfer to this one bad sector; if another + * sector is bad, then reduce reduce the transfer to + * avoid any bad sectors. + */ + if ((du->dk_flags & (DKFL_SINGLE | DKFL_BADSECT)) + == (DKFL_SINGLE | DKFL_BADSECT)) + for (blkchk = blknum; blkchk < blknum + blkcnt; blkchk++) { + cylin = blkchk / secpercyl; + head = (blkchk % secpercyl) / secpertrk; + sector = blkchk % secpertrk; + for (bt_ptr = du->dk_bad.bt_bad; + bt_ptr->bt_cyl != BAD144_NO_CYL; bt_ptr++) { + if (bt_ptr->bt_cyl > cylin) + /* + * Sorted list, and we passed our cylinder. + * quit. + */ + break; + if (bt_ptr->bt_cyl == cylin && + bt_ptr->bt_trksec == (head << 8) + sector) { + /* Found bad block. */ + blkcnt = blkchk - blknum; + if (blkcnt > 0) { + blknext = blknum + blkcnt; + goto out; + } + blkcnt = 1; + blknext = blknum + blkcnt; + /* + * Found bad block. Calculate new block number. + * This starts at the end of the disk (skip the + * last track which is used for the bad block list), + * and works backwards to the front of the disk. + */ + /* XXX as usual. */ +#ifdef WDDEBUG + printf("--- badblock code -> Old = %ld; ", + blknum); +#endif + lp = &du->dk_dd; + if (lp->d_partitions[BSD_PART].p_offset != 0) + blknum = lp->d_partitions[BAD144_PART] + .p_offset + + lp->d_partitions[BAD144_PART] + .p_size; + else + blknum = lp->d_secperunit; + blknum -= lp->d_nsectors + + (bt_ptr - du->dk_bad.bt_bad) + 1; +#ifdef WDDEBUG + printf("new = %ld\n", blknum); +#endif + break; + } + } + } +out: + + /* Compute disk address. */ + cylin = blknum / secpercyl; + head = (blknum % secpercyl) / secpertrk; + sector = blknum % secpertrk; + +#if 0 + /* Let's just talk about this first... */ + pg("cylin l%d head %ld sector %ld addr 0x%x count %ld", + cylin, head, sector, addr, blkcnt); +#endif + + /* Do the write. */ + if (wdcommand(du, cylin, head, sector, blkcnt, WDCC_WRITE) + != 0) { + wderror((struct buf *)NULL, du, + "wddump: timeout waiting to to give command"); + return (EIO); + } + while (blkcnt != 0) { + pmap_enter(kernel_pmap, (vm_offset_t)CADDR1, trunc_page(addr), + VM_PROT_READ, TRUE); + + /* Ready to send data? */ + DELAY(5); /* ATA spec */ + if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ, TIMEOUT) + < 0) { + wderror((struct buf *)NULL, du, + "wddump: timeout waiting for DRQ"); + return (EIO); + } + outsw(du->dk_port + wd_data, + CADDR1 + ((int)addr & (NBPG - 1)), + DEV_BSIZE / sizeof(short)); + addr += DEV_BSIZE; + if ((unsigned)addr % (1024 * 1024) == 0) + printf("%ld ", num / (1024 * 1024 / DEV_BSIZE)); + num--; + blkcnt--; + } + + /* Wait for completion. */ + DELAY(5); /* ATA spec XXX NOT */ + if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) < 0) { + wderror((struct buf *)NULL, du, + "wddump: timeout waiting for status"); + return (EIO); + } + + /* Check final status. */ + if (du->dk_status + & (WDCS_READY | WDCS_SEEKCMPLT | WDCS_DRQ | WDCS_ERR) + != (WDCS_READY | WDCS_SEEKCMPLT)) { + wderror((struct buf *)NULL, du, + "wddump: extra DRQ, or error"); + return (EIO); + } + + /* Update block count. */ + blknum = blknext; + + /* Operator aborting dump? */ + if (sgetc(1) & 0xff) /* EWS: A hack to work with syscons */ + return (EINTR); + } + return (0); +} + +static void +wderror(struct buf *bp, struct disk *du, char *mesg) +{ + if (bp == NULL) + printf("wd%d: %s:\n", du->dk_lunit, mesg); + else + diskerr(bp, "wd", mesg, LOG_PRINTF, du->dk_skip, &du->dk_dd); + printf("wd%d: status %b error %b\n", du->dk_lunit, + du->dk_status, WDCS_BITS, du->dk_error, WDERR_BITS); +} + +/* + * Discard any interrupts that were latched by the interrupt system while + * we were doing polled i/o. + */ +static void +wdflushirq(struct disk *du, int old_ipl) +{ + wdtab[du->dk_ctrlr].b_active = 2; + splx(old_ipl); + (void)splbio(); + wdtab[du->dk_ctrlr].b_active = 0; +} + +/* + * Reset the controller. + */ +static int +wdreset(struct disk *du) +{ + int wdc; + + wdc = du->dk_port; + (void)wdwait(du, 0, TIMEOUT); + outb(wdc + wd_ctlr, WDCTL_IDS | WDCTL_RST); + DELAY(10 * 1000); + outb(wdc + wd_ctlr, WDCTL_IDS); + if (wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) != 0 + || (du->dk_error = inb(wdc + wd_error)) != 0x01) + return (1); + outb(wdc + wd_ctlr, WDCTL_4BIT); + return (0); +} + +/* + * Sleep until driver is inactive. + * This is used only for avoiding rare race conditions, so it is unimportant + * that the sleep may be far too short or too long. + */ +static void +wdsleep(int ctrlr, char *wmesg) +{ + while (wdtab[ctrlr].b_active) + tsleep((caddr_t)&wdtab[ctrlr].b_active, PZERO - 1, wmesg, 1); +} + +static void +wdtimeout(void *cdu) +{ + struct disk *du; + int x; + + du = (struct disk *)cdu; + x = splbio(); + if (du->dk_timeout != 0 && --du->dk_timeout == 0) { + wderror((struct buf *)NULL, du, "interrupt timeout"); + wdunwedge(du); + wdflushirq(du, x); + du->dk_skip = 0; + du->dk_flags |= DKFL_SINGLE; + wdstart(du->dk_ctrlr); + } + timeout(wdtimeout, cdu, hz); + splx(x); +} + +/* + * Reset the controller after it has become wedged. This is different from + * wdreset() so that wdreset() can be used in the probe and so that this + * can restore the geometry . + */ +static int +wdunwedge(struct disk *du) +{ + struct disk *du1; + int lunit; + + /* Schedule other drives for recalibration. */ + for (lunit = 0; lunit < NWD; lunit++) + if ((du1 = wddrives[lunit]) != NULL && du1 != du + && du1->dk_ctrlr == du->dk_ctrlr + && du1->dk_state > WANTOPEN) + du1->dk_state = WANTOPEN; + + DELAY(RECOVERYTIME); + if (wdreset(du) == 0) { + /* + * XXX - recalibrate current drive now because some callers + * aren't prepared to have its state change. + */ + if (wdcommand(du, 0, 0, 0, 0, WDCC_RESTORE | WD_STEP) == 0 + && wdwait(du, WDCS_READY | WDCS_SEEKCMPLT, TIMEOUT) == 0 + && wdsetctlr(du) == 0) + return (0); + } + wderror((struct buf *)NULL, du, "wdunwedge failed"); + return (1); +} + +/* + * Wait uninterruptibly until controller is not busy and either certain + * status bits are set or an error has occurred. + * The wait is usually short unless it is for the controller to process + * an entire critical command. + * Return 1 for (possibly stale) controller errors, -1 for timeout errors, + * or 0 for no errors. + * Return controller status in du->dk_status and, if there was a controller + * error, return the error code in du->dk_error. + */ +#ifdef WD_COUNT_RETRIES +static int min_retries[NWDC]; +#endif + +static int +wdwait(struct disk *du, u_char bits_wanted, int timeout) +{ + int wdc; + u_char status; + +#define POLLING 1000 + + wdc = du->dk_port; + timeout += POLLING; + do { +#ifdef WD_COUNT_RETRIES + if (min_retries[du->dk_ctrlr] > timeout + || min_retries[du->dk_ctrlr] == 0) + min_retries[du->dk_ctrlr] = timeout; +#endif + DELAY(5); /* ATA spec XXX NOT */ + du->dk_status = status = inb(wdc + wd_status); + if (!(status & WDCS_BUSY)) { + if (status & WDCS_ERR) { + du->dk_error = inb(wdc + wd_error); + /* + * We once returned here. This is wrong + * because the error bit is apparently only + * valid after the controller has interrupted + * (e.g., the error bit is stale when we wait + * for DRQ for writes). So we can't depend + * on the error bit at all when polling for + * command completion. + */ + } + if ((status & bits_wanted) == bits_wanted) + return (status & WDCS_ERR); + } + if (timeout < TIMEOUT) + /* + * Switch to a polling rate of about 1 KHz so that + * the timeout is almost machine-independent. The + * controller is taking a long time to respond, so + * an extra msec won't matter. + */ + DELAY(1000); + } while (--timeout != 0); + return (-1); +} + +/* + * Internalize the bad sector table. + */ +void bad144intern(struct disk *du) { + int i; + if (du->dk_flags & DKFL_BADSECT) { + for (i = 0; i < 127; i++) { + du->dk_badsect[i] = -1; + } + for (i = 0; i < 126; i++) { + if (du->dk_bad.bt_bad[i].bt_cyl == 0xffff) { + break; + } else { + du->dk_badsect[i] = + du->dk_bad.bt_bad[i].bt_cyl * du->dk_dd.d_secpercyl + + (du->dk_bad.bt_bad[i].bt_trksec >> 8) * du->dk_dd.d_nsectors ++ + (du->dk_bad.bt_bad[i].bt_trksec & 0x00ff); + } + } + } +} + +#endif /* NWDC > 0 */ diff --git a/sys/i386/isa/wdreg.h b/sys/i386/isa/wdreg.h new file mode 100644 index 0000000..2694bed --- /dev/null +++ b/sys/i386/isa/wdreg.h @@ -0,0 +1,144 @@ +/*- + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * 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. + * + * from: @(#)wdreg.h 7.1 (Berkeley) 5/9/91 + * $Id: wxreg.h,v 1.1 1993/10/26 22:26:39 nate Exp $ + */ + +/* + * Disk Controller register definitions. + */ +#define wd_data 0x0 /* data register (R/W - 16 bits) */ +#define wd_error 0x1 /* error register (R) */ +#define wd_precomp wd_error /* write precompensation (W) */ +#define wd_seccnt 0x2 /* sector count (R/W) */ +#define wd_sector 0x3 /* first sector number (R/W) */ +#define wd_cyl_lo 0x4 /* cylinder address, low byte (R/W) */ +#define wd_cyl_hi 0x5 /* cylinder address, high byte (R/W)*/ +#define wd_sdh 0x6 /* sector size/drive/head (R/W)*/ +#define wd_command 0x7 /* command register (W) */ +#define wd_status wd_command /* immediate status (R) */ + +#define wd_altsts 0x206 /*alternate fixed disk status(via 1015) (R)*/ +#define wd_ctlr 0x206 /*fixed disk controller control(via 1015) (W)*/ +#define WDCTL_4BIT 0x8 /* use four head bits (wd1003) */ +#define WDCTL_RST 0x4 /* reset the controller */ +#define WDCTL_IDS 0x2 /* disable controller interrupts */ +#define wd_digin 0x207 /* disk controller input(via 1015) (R)*/ + +/* + * Status Bits. + */ +#define WDCS_BUSY 0x80 /* Controller busy bit. */ +#define WDCS_READY 0x40 /* Selected drive is ready */ +#define WDCS_WRTFLT 0x20 /* Write fault */ +#define WDCS_SEEKCMPLT 0x10 /* Seek complete */ +#define WDCS_DRQ 0x08 /* Data request bit. */ +#define WDCS_ECCCOR 0x04 /* ECC correction made in data */ +#define WDCS_INDEX 0x02 /* Index pulse from selected drive */ +#define WDCS_ERR 0x01 /* Error detect bit. */ + +#define WDCS_BITS "\020\010busy\006rdy\006wrtflt\005seekdone\004drq\003ecc_cor\002index\001err" + +#define WDERR_BITS "\020\010badblk\007uncorr\006id_crc\005no_id\003abort\002tr000\001no_dam" + +/* + * Commands for Disk Controller. + */ +#define WDCC_RESTORE 0x10 /* disk restore code -- resets cntlr */ + +#define WDCC_READ 0x20 /* disk read code */ +#define WDCC_WRITE 0x30 /* disk write code */ +#define WDCC__LONG 0x02 /* modifier -- access ecc bytes */ +#define WDCC__NORETRY 0x01 /* modifier -- no retrys */ + +#define WDCC_FORMAT 0x50 /* disk format code */ +#define WDCC_DIAGNOSE 0x90 /* controller diagnostic */ +#define WDCC_IDC 0x91 /* initialize drive command */ + +#define WDCC_EXTDCMD 0xE0 /* send extended command */ +#define WDCC_READP 0xEC /* read parameters from controller */ +#define WDCC_CACHEC 0xEF /* cache control */ + +#define WD_STEP 0 /* winchester- default 35us step */ + +#define WDSD_IBM 0xa0 /* forced to 512 byte sector, ecc */ + + +#ifdef KERNEL +/* + * read parameters command returns this: + */ +struct wdparams { + /* drive info */ + short wdp_config; /* general configuration */ + short wdp_fixedcyl; /* number of non-removable cylinders */ + short wdp_removcyl; /* number of removable cylinders */ + short wdp_heads; /* number of heads */ + short wdp_unfbytespertrk; /* number of unformatted bytes/track */ + short wdp_unfbytes; /* number of unformatted bytes/sector */ + short wdp_sectors; /* number of sectors */ + short wdp_minisg; /* minimum bytes in inter-sector gap*/ + short wdp_minplo; /* minimum bytes in postamble */ + short wdp_vendstat; /* number of words of vendor status */ + /* controller info */ + char wdp_cnsn[20]; /* controller serial number */ + short wdp_cntype; /* controller type */ +#define WDTYPE_SINGLEPORTSECTOR 1 /* single port, single sector buffer */ +#define WDTYPE_DUALPORTMULTI 2 /* dual port, multiple sector buffer */ +#define WDTYPE_DUALPORTMULTICACHE 3 /* above plus track cache */ + short wdp_cnsbsz; /* sector buffer size, in sectors */ + short wdp_necc; /* ecc bytes appended */ + char wdp_rev[8]; /* firmware revision */ + char wdp_model[40]; /* model name */ + short wdp_nsecperint; /* sectors per interrupt */ + short wdp_usedmovsd; /* can use double word read/write? */ +}; + +/* + * wd driver entry points + */ +void wdstrategy(struct buf *bp); +void wdintr(int unit); +int wdopen(dev_t dev, int flags, int fmt, struct proc *p); +int wdclose(dev_t dev, int flags, int fmt); +int wdioctl(dev_t dev, int cmd, caddr_t addr, int flag); +#ifdef B_FORMAT +int wdformat(struct buf *bp); +#endif +int wdsize(dev_t dev); +int wddump(dev_t dev); + +#endif KERNEL diff --git a/sys/i386/isa/wt.c b/sys/i386/isa/wt.c new file mode 100644 index 0000000..c080a2a --- /dev/null +++ b/sys/i386/isa/wt.c @@ -0,0 +1,899 @@ +/* + * Streamer tape driver for 386bsd and FreeBSD. + * Supports Archive and Wangtek compatible QIC-02/QIC-36 boards. + * + * Copyright (C) 1993 by: + * Sergey Ryzhkov <sir@kiae.su> + * Serge Vakulenko <vak@zebub.msk.su> + * + * This software is distributed with NO WARRANTIES, not even the implied + * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Authors grant any other persons or organisations permission to use + * or modify this software as long as this message is kept with the software, + * all derivative works or modified versions. + * + * This driver is derived from the old 386bsd Wangtek streamer tape driver, + * made by Robert Baron at CMU, based on Intel sources. + * Authors thank Robert Baron, CMU and Intel and retain here + * the original CMU copyright notice. + * + * Version 1.3, Thu Nov 11 12:09:13 MSK 1993 + * $Id: wt.c,v 1.9 1994/08/20 03:48:43 davidg Exp $ + * + */ + +/* + * Copyright (c) 1989 Carnegie-Mellon University. + * All rights reserved. + * + * Authors: Robert Baron + * + * Permission to use, copy, modify and distribute this software and + * its documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND + * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ + +#include "wt.h" +#if NWT > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/buf.h> +#include <sys/fcntl.h> +#include <sys/malloc.h> +#include <sys/ioctl.h> +#include <sys/mtio.h> +#include <vm/vm_param.h> +#include <i386/include/pio.h> +#include <i386/isa/isa_device.h> +#include <i386/isa/wtreg.h> + +/* + * Uncomment this to enable internal device tracing. + */ +#define DEBUG(s) /* printf s */ + +#define WTPRI (PZERO+10) /* sleep priority */ + +/* + * Wangtek controller ports + */ +#define WT_CTLPORT(base) ((base)+0) /* control, write only */ +#define WT_STATPORT(base) ((base)+0) /* status, read only */ +#define WT_CMDPORT(base) ((base)+1) /* command, write only */ +#define WT_DATAPORT(base) ((base)+1) /* data, read only */ +#define WT_NPORT 2 /* 2 i/o ports */ + +/* status port bits */ +#define WT_BUSY 0x01 /* not ready bit define */ +#define WT_NOEXCEP 0x02 /* no exception bit define */ +#define WT_RESETMASK 0x07 /* to check after reset */ +#define WT_RESETVAL 0x05 /* state after reset */ + +/* control port bits */ +#define WT_ONLINE 0x01 /* device selected */ +#define WT_RESET 0x02 /* reset command */ +#define WT_REQUEST 0x04 /* request command */ +#define WT_IEN 0x08 /* enable dma */ + +/* + * Archive controller ports + */ +#define AV_DATAPORT(base) ((base)+0) /* data, read only */ +#define AV_CMDPORT(base) ((base)+0) /* command, write only */ +#define AV_STATPORT(base) ((base)+1) /* status, read only */ +#define AV_CTLPORT(base) ((base)+1) /* control, write only */ +#define AV_SDMAPORT(base) ((base)+2) /* start dma */ +#define AV_RDMAPORT(base) ((base)+3) /* reset dma */ +#define AV_NPORT 4 /* 4 i/o ports */ + +/* status port bits */ +#define AV_BUSY 0x40 /* not ready bit define */ +#define AV_NOEXCEP 0x20 /* no exception bit define */ +#define AV_RESETMASK 0xf8 /* to check after reset */ +#define AV_RESETVAL 0x50 /* state after reset */ + +/* control port bits */ +#define AV_RESET 0x80 /* reset command */ +#define AV_REQUEST 0x40 /* request command */ +#define AV_IEN 0x20 /* enable interrupts */ + +enum wttype { + UNKNOWN = 0, /* unknown type, driver disabled */ + ARCHIVE, /* Archive Viper SC499, SC402 etc */ + WANGTEK, /* Wangtek */ +}; + +typedef struct { + unsigned short err; /* code for error encountered */ + unsigned short ercnt; /* number of error blocks */ + unsigned short urcnt; /* number of underruns */ +} wtstatus_t; + +typedef struct { + enum wttype type; /* type of controller */ + unsigned unit; /* unit number */ + unsigned port; /* base i/o port */ + unsigned chan; /* dma channel number, 1..3 */ + unsigned flags; /* state of tape drive */ + unsigned dens; /* tape density */ + int bsize; /* tape block size */ + void *buf; /* internal i/o buffer */ + + void *dmavaddr; /* virtual address of dma i/o buffer */ + unsigned dmatotal; /* size of i/o buffer */ + unsigned dmaflags; /* i/o direction, B_READ or B_WRITE */ + unsigned dmacount; /* resulting length of dma i/o */ + + wtstatus_t error; /* status of controller */ + + unsigned short DATAPORT, CMDPORT, STATPORT, CTLPORT, SDMAPORT, RDMAPORT; + unsigned char BUSY, NOEXCEP, RESETMASK, RESETVAL; + unsigned char ONLINE, RESET, REQUEST, IEN; +} wtinfo_t; + +wtinfo_t wttab[NWT]; /* tape info by unit number */ + +static int wtwait (wtinfo_t *t, int catch, char *msg); +static int wtcmd (wtinfo_t *t, int cmd); +static int wtstart (wtinfo_t *t, unsigned mode, void *vaddr, unsigned len); +static void wtdma (wtinfo_t *t); +static timeout_t wtimer; +static void wtclock (wtinfo_t *t); +static int wtreset (wtinfo_t *t); +static int wtsense (wtinfo_t *t, int verb, int ignor); +static int wtstatus (wtinfo_t *t); +static void wtrewind (wtinfo_t *t); +static int wtreadfm (wtinfo_t *t); +static int wtwritefm (wtinfo_t *t); +static int wtpoll (wtinfo_t *t, int mask, int bits); + +/* + * Probe for the presence of the device. + */ +int wtprobe (struct isa_device *id) +{ + wtinfo_t *t = wttab + id->id_unit; + + t->unit = id->id_unit; + t->chan = id->id_drq; + t->port = id->id_iobase; + if (t->chan<1 || t->chan>3) { + printf ("wt%d: Bad drq=%d, should be 1..3\n", t->unit, t->chan); + return (0); + } + + /* Try Wangtek. */ + t->type = WANGTEK; + t->CTLPORT = WT_CTLPORT (t->port); t->STATPORT = WT_STATPORT (t->port); + t->CMDPORT = WT_CMDPORT (t->port); t->DATAPORT = WT_DATAPORT (t->port); + t->SDMAPORT = 0; t->RDMAPORT = 0; + t->BUSY = WT_BUSY; t->NOEXCEP = WT_NOEXCEP; + t->RESETMASK = WT_RESETMASK; t->RESETVAL = WT_RESETVAL; + t->ONLINE = WT_ONLINE; t->RESET = WT_RESET; + t->REQUEST = WT_REQUEST; t->IEN = WT_IEN; + if (wtreset (t)) + return (WT_NPORT); + + /* Try Archive. */ + t->type = ARCHIVE; + t->CTLPORT = AV_CTLPORT (t->port); t->STATPORT = AV_STATPORT (t->port); + t->CMDPORT = AV_CMDPORT (t->port); t->DATAPORT = AV_DATAPORT (t->port); + t->SDMAPORT = AV_SDMAPORT (t->port); t->RDMAPORT = AV_RDMAPORT (t->port); + t->BUSY = AV_BUSY; t->NOEXCEP = AV_NOEXCEP; + t->RESETMASK = AV_RESETMASK; t->RESETVAL = AV_RESETVAL; + t->ONLINE = 0; t->RESET = AV_RESET; + t->REQUEST = AV_REQUEST; t->IEN = AV_IEN; + if (wtreset (t)) + return (AV_NPORT); + + /* Tape controller not found. */ + t->type = UNKNOWN; + return (0); +} + +/* + * Device is found, configure it. + */ +int wtattach (struct isa_device *id) +{ + wtinfo_t *t = wttab + id->id_unit; + + if (t->type == ARCHIVE) { + printf ("wt%d: type <Archive>\n", t->unit); + outb (t->RDMAPORT, 0); /* reset dma */ + } else + printf ("wt%d: type <Wangtek>\n", t->unit); + t->flags = TPSTART; /* tape is rewound */ + t->dens = -1; /* unknown density */ + return (1); +} + +struct isa_driver wtdriver = { wtprobe, wtattach, "wt", }; + +int wtdump (int dev) +{ + /* Not implemented */ + return (EINVAL); +} + +int wtsize (int dev) +{ + /* Not implemented */ + return (-1); +} + +/* + * Open routine, called on every device open. + */ +int wtopen (int dev, int flag) +{ + int u = minor (dev) & T_UNIT; + wtinfo_t *t = wttab + u; + int error; + + if (u >= NWT || t->type == UNKNOWN) + return (ENXIO); + + /* Check that device is not in use */ + if (t->flags & TPINUSE) + return (EBUSY); + + /* If the tape is in rewound state, check the status and set density. */ + if (t->flags & TPSTART) { + /* If rewind is going on, wait */ + if (error = wtwait (t, PCATCH, "wtrew")) + return (error); + + /* Check the controller status */ + if (! wtsense (t, 0, (flag & FWRITE) ? 0 : TP_WRP)) { + /* Bad status, reset the controller */ + if (! wtreset (t)) + return (EIO); + if (! wtsense (t, 1, (flag & FWRITE) ? 0 : TP_WRP)) + return (EIO); + } + + /* Set up tape density. */ + if (t->dens != (minor (dev) & WT_DENSEL)) { + int d = 0; + + switch (minor (dev) & WT_DENSEL) { + case WT_DENSDFLT: default: break; /* default density */ + case WT_QIC11: d = QIC_FMT11; break; /* minor 010 */ + case WT_QIC24: d = QIC_FMT24; break; /* minor 020 */ + case WT_QIC120: d = QIC_FMT120; break; /* minor 030 */ + case WT_QIC150: d = QIC_FMT150; break; /* minor 040 */ + case WT_QIC300: d = QIC_FMT300; break; /* minor 050 */ + case WT_QIC600: d = QIC_FMT600; break; /* minor 060 */ + } + if (d) { + /* Change tape density. */ + if (! wtcmd (t, d)) + return (EIO); + if (! wtsense (t, 1, TP_WRP | TP_ILL)) + return (EIO); + + /* Check the status of the controller. */ + if (t->error.err & TP_ILL) { + printf ("wt%d: invalid tape density\n", t->unit); + return (ENODEV); + } + } + t->dens = minor (dev) & WT_DENSEL; + } + t->flags &= ~TPSTART; + } else if (t->dens != (minor (dev) & WT_DENSEL)) + return (ENXIO); + + t->bsize = (minor (dev) & WT_BSIZE) ? 1024 : 512; + t->buf = malloc (t->bsize, M_TEMP, M_WAITOK); + if (! t->buf) + return (EAGAIN); + + t->flags = TPINUSE; + if (flag & FREAD) + t->flags |= TPREAD; + if (flag & FWRITE) + t->flags |= TPWRITE; + return (0); +} + +/* + * Close routine, called on last device close. + */ +int wtclose (int dev) +{ + int u = minor (dev) & T_UNIT; + wtinfo_t *t = wttab + u; + + if (u >= NWT || t->type == UNKNOWN) + return (ENXIO); + + /* If rewind is pending, do nothing */ + if (t->flags & TPREW) + goto done; + + /* If seek forward is pending and no rewind on close, do nothing */ + if (t->flags & TPRMARK) { + if (minor (dev) & T_NOREWIND) + goto done; + + /* If read file mark is going on, wait */ + wtwait (t, 0, "wtrfm"); + } + + if (t->flags & TPWANY) + /* Tape was written. Write file mark. */ + wtwritefm (t); + + if (! (minor (dev) & T_NOREWIND)) { + /* Rewind tape to beginning of tape. */ + /* Don't wait until rewind, though. */ + wtrewind (t); + goto done; + } + if ((t->flags & TPRANY) && ! (t->flags & (TPVOL | TPWANY))) + /* Space forward to after next file mark if no writing done. */ + /* Don't wait for completion. */ + wtreadfm (t); +done: + t->flags &= TPREW | TPRMARK | TPSTART | TPTIMER; + free (t->buf, M_TEMP); + return (0); +} + +/* + * Ioctl routine. Compatible with BSD ioctls. + * Direct QIC-02 commands ERASE and RETENSION added. + * There are three possible ioctls: + * ioctl (int fd, MTIOCGET, struct mtget *buf) -- get status + * ioctl (int fd, MTIOCTOP, struct mtop *buf) -- do BSD-like op + * ioctl (int fd, WTQICMD, int qicop) -- do QIC op + */ +int wtioctl (int dev, int cmd, void *arg, int mode) +{ + int u = minor (dev) & T_UNIT; + wtinfo_t *t = wttab + u; + int error, count, op; + + if (u >= NWT || t->type == UNKNOWN) + return (ENXIO); + + switch (cmd) { + default: + return (EINVAL); + case WTQICMD: /* direct QIC command */ + op = (int) *(void**)arg; + switch (op) { + default: + return (EINVAL); + case QIC_ERASE: /* erase the whole tape */ + if (! (t->flags & TPWRITE) || (t->flags & TPWP)) + return (EACCES); + if (error = wtwait (t, PCATCH, "wterase")) + return (error); + break; + case QIC_RETENS: /* retension the tape */ + if (error = wtwait (t, PCATCH, "wtretens")) + return (error); + break; + } + /* Both ERASE and RETENS operations work like REWIND. */ + /* Simulate the rewind operation here. */ + t->flags &= ~(TPRO | TPWO | TPVOL); + if (! wtcmd (t, op)) + return (EIO); + t->flags |= TPSTART | TPREW; + if (op == QIC_ERASE) + t->flags |= TPWANY; + wtclock (t); + return (0); + case MTIOCIEOT: /* ignore EOT errors */ + case MTIOCEEOT: /* enable EOT errors */ + return (0); + case MTIOCGET: + ((struct mtget*)arg)->mt_type = + t->type == ARCHIVE ? MT_ISVIPER1 : 0x11; + ((struct mtget*)arg)->mt_dsreg = t->flags; /* status */ + ((struct mtget*)arg)->mt_erreg = t->error.err; /* errors */ + ((struct mtget*)arg)->mt_resid = 0; + ((struct mtget*)arg)->mt_fileno = 0; /* file */ + ((struct mtget*)arg)->mt_blkno = 0; /* block */ + return (0); + case MTIOCTOP: + break; + } + switch ((short) ((struct mtop*)arg)->mt_op) { + default: + case MTFSR: /* forward space record */ + case MTBSR: /* backward space record */ + case MTBSF: /* backward space file */ + break; + case MTNOP: /* no operation, sets status only */ + case MTCACHE: /* enable controller cache */ + case MTNOCACHE: /* disable controller cache */ + return (0); + case MTREW: /* rewind */ + case MTOFFL: /* rewind and put the drive offline */ + if (t->flags & TPREW) /* rewind is running */ + return (0); + if (error = wtwait (t, PCATCH, "wtorew")) + return (error); + wtrewind (t); + return (0); + case MTFSF: /* forward space file */ + for (count=((struct mtop*)arg)->mt_count; count>0; --count) { + if (error = wtwait (t, PCATCH, "wtorfm")) + return (error); + if (error = wtreadfm (t)) + return (error); + } + return (0); + case MTWEOF: /* write an end-of-file record */ + if (! (t->flags & TPWRITE) || (t->flags & TPWP)) + return (EACCES); + if (error = wtwait (t, PCATCH, "wtowfm")) + return (error); + if (error = wtwritefm (t)) + return (error); + return (0); + } + return (EINVAL); +} + +/* + * Strategy routine. + */ +void wtstrategy (struct buf *bp) +{ + int u = minor (bp->b_dev) & T_UNIT; + wtinfo_t *t = wttab + u; + int s; + + bp->b_resid = bp->b_bcount; + if (u >= NWT || t->type == UNKNOWN) + goto errxit; + + /* at file marks and end of tape, we just return '0 bytes available' */ + if (t->flags & TPVOL) + goto xit; + + if (bp->b_flags & B_READ) { + /* Check read access and no previous write to this tape. */ + if (! (t->flags & TPREAD) || (t->flags & TPWANY)) + goto errxit; + + /* For now, we assume that all data will be copied out */ + /* If read command outstanding, just skip down */ + if (! (t->flags & TPRO)) { + if (! wtsense (t, 1, TP_WRP)) /* clear status */ + goto errxit; + if (! wtcmd (t, QIC_RDDATA)) { /* sed read mode */ + wtsense (t, 1, TP_WRP); + goto errxit; + } + t->flags |= TPRO | TPRANY; + } + } else { + /* Check write access and write protection. */ + /* No previous read from this tape allowed. */ + if (! (t->flags & TPWRITE) || (t->flags & (TPWP | TPRANY))) + goto errxit; + + /* If write command outstanding, just skip down */ + if (! (t->flags & TPWO)) { + if (! wtsense (t, 1, 0)) /* clear status */ + goto errxit; + if (! wtcmd (t, QIC_WRTDATA)) { /* set write mode */ + wtsense (t, 1, 0); + goto errxit; + } + t->flags |= TPWO | TPWANY; + } + } + + if (! bp->b_bcount) + goto xit; + + t->flags &= ~TPEXCEP; + s = splbio (); + if (wtstart (t, bp->b_flags, bp->b_un.b_addr, bp->b_bcount)) { + wtwait (t, 0, (bp->b_flags & B_READ) ? "wtread" : "wtwrite"); + bp->b_resid -= t->dmacount; + } + splx (s); + + if (t->flags & TPEXCEP) { +errxit: bp->b_flags |= B_ERROR; + bp->b_error = EIO; + } +xit: biodone (bp); + return; +} + +/* + * Interrupt routine. + */ +void wtintr (int u) +{ + wtinfo_t *t = wttab + u; + unsigned char s; + + if (u >= NWT || t->type == UNKNOWN) { + DEBUG (("wtintr() -- device not configured\n")); + return; + } + + s = inb (t->STATPORT); /* get status */ + DEBUG (("wtintr() status=0x%x -- ", s)); + if ((s & (t->BUSY | t->NOEXCEP)) == (t->BUSY | t->NOEXCEP)) { + DEBUG (("busy\n")); + return; /* device is busy */ + } + + /* + * Check if rewind finished. + */ + if (t->flags & TPREW) { + DEBUG (((s & (t->BUSY | t->NOEXCEP)) == (t->BUSY | t->NOEXCEP) ? + "rewind busy?\n" : "rewind finished\n")); + t->flags &= ~TPREW; /* Rewind finished. */ + wtsense (t, 1, TP_WRP); + wakeup ((caddr_t)t); + return; + } + + /* + * Check if writing/reading of file mark finished. + */ + if (t->flags & (TPRMARK | TPWMARK)) { + DEBUG (((s & (t->BUSY | t->NOEXCEP)) == (t->BUSY | t->NOEXCEP) ? + "marker r/w busy?\n" : "marker r/w finished\n")); + if (! (s & t->NOEXCEP)) /* operation failed */ + wtsense (t, 1, (t->flags & TPRMARK) ? TP_WRP : 0); + t->flags &= ~(TPRMARK | TPWMARK); /* operation finished */ + wakeup ((caddr_t)t); + return; + } + + /* + * Do we started any i/o? If no, just return. + */ + if (! (t->flags & TPACTIVE)) { + DEBUG (("unexpected interrupt\n")); + return; + } + t->flags &= ~TPACTIVE; + t->dmacount += t->bsize; /* increment counter */ + + /* + * Clean up dma. + */ + if ((t->dmaflags & B_READ) && (t->dmatotal - t->dmacount) < t->bsize) { + /* If reading short block, copy the internal buffer + * to the user memory. */ + isa_dmadone (t->dmaflags, t->buf, t->bsize, t->chan); + bcopy (t->buf, t->dmavaddr, t->dmatotal - t->dmacount); + } else + isa_dmadone (t->dmaflags, t->dmavaddr, t->bsize, t->chan); + + /* + * On exception, check for end of file and end of volume. + */ + if (! (s & t->NOEXCEP)) { + DEBUG (("i/o exception\n")); + wtsense (t, 1, (t->dmaflags & B_READ) ? TP_WRP : 0); + if (t->error.err & (TP_EOM | TP_FIL)) + t->flags |= TPVOL; /* end of file */ + else + t->flags |= TPEXCEP; /* i/o error */ + wakeup ((caddr_t)t); + return; + } + + if (t->dmacount < t->dmatotal) { /* continue i/o */ + t->dmavaddr += t->bsize; + wtdma (t); + DEBUG (("continue i/o, %d\n", t->dmacount)); + return; + } + if (t->dmacount > t->dmatotal) /* short last block */ + t->dmacount = t->dmatotal; + wakeup ((caddr_t)t); /* wake up user level */ + DEBUG (("i/o finished, %d\n", t->dmacount)); +} + +/* start the rewind operation */ +static void wtrewind (wtinfo_t *t) +{ + int rwmode = (t->flags & (TPRO | TPWO)); + + t->flags &= ~(TPRO | TPWO | TPVOL); + /* + * Wangtek strictly follows QIC-02 standard: + * clearing ONLINE in read/write modes causes rewind. + * REWIND command is not allowed in read/write mode + * and gives `illegal command' error. + */ + if (t->type==WANGTEK && rwmode) { + outb (t->CTLPORT, 0); + } else if (! wtcmd (t, QIC_REWIND)) + return; + t->flags |= TPSTART | TPREW; + wtclock (t); +} + +/* start the `read marker' operation */ +static int wtreadfm (wtinfo_t *t) +{ + t->flags &= ~(TPRO | TPWO | TPVOL); + if (! wtcmd (t, QIC_READFM)) { + wtsense (t, 1, TP_WRP); + return (EIO); + } + t->flags |= TPRMARK | TPRANY; + wtclock (t); + /* Don't wait for completion here. */ + return (0); +} + +/* write marker to the tape */ +static int wtwritefm (wtinfo_t *t) +{ + tsleep ((caddr_t)wtwritefm, WTPRI, "wtwfm", hz); /* timeout: 1 second */ + t->flags &= ~(TPRO | TPWO); + if (! wtcmd (t, QIC_WRITEFM)) { + wtsense (t, 1, 0); + return (EIO); + } + t->flags |= TPWMARK | TPWANY; + wtclock (t); + return (wtwait (t, 0, "wtwfm")); +} + +/* while controller status & mask == bits continue waiting */ +static int wtpoll (wtinfo_t *t, int mask, int bits) +{ + int s, i; + + /* Poll status port, waiting for specified bits. */ + for (i=0; i<1000; ++i) { /* up to 1 msec */ + s = inb (t->STATPORT); + if ((s & mask) != bits) + return (s); + DELAY (1); + } + for (i=0; i<100; ++i) { /* up to 10 msec */ + s = inb (t->STATPORT); + if ((s & mask) != bits) + return (s); + DELAY (100); + } + for (;;) { /* forever */ + s = inb (t->STATPORT); + if ((s & mask) != bits) + return (s); + tsleep ((caddr_t)wtpoll, WTPRI, "wtpoll", 1); /* timeout: 1 tick */ + } +} + +/* execute QIC command */ +static int wtcmd (wtinfo_t *t, int cmd) +{ + int s; + + DEBUG (("wtcmd() cmd=0x%x\n", cmd)); + s = wtpoll (t, t->BUSY | t->NOEXCEP, t->BUSY | t->NOEXCEP); /* ready? */ + if (! (s & t->NOEXCEP)) /* error */ + return (0); + + outb (t->CMDPORT, cmd); /* output the command */ + + outb (t->CTLPORT, t->REQUEST | t->ONLINE); /* set request */ + wtpoll (t, t->BUSY, t->BUSY); /* wait for ready */ + outb (t->CTLPORT, t->IEN | t->ONLINE); /* reset request */ + wtpoll (t, t->BUSY, 0); /* wait for not ready */ + return (1); +} + +/* wait for the end of i/o, seeking marker or rewind operation */ +static int wtwait (wtinfo_t *t, int catch, char *msg) +{ + int error; + + DEBUG (("wtwait() `%s'\n", msg)); + while (t->flags & (TPACTIVE | TPREW | TPRMARK | TPWMARK)) + if (error = tsleep ((caddr_t)t, WTPRI | catch, msg, 0)) + return (error); + return (0); +} + +/* initialize dma for the i/o operation */ +static void wtdma (wtinfo_t *t) +{ + t->flags |= TPACTIVE; + wtclock (t); + + if (t->type == ARCHIVE) + outb (t->SDMAPORT, 0); /* set dma */ + + if ((t->dmaflags & B_READ) && (t->dmatotal - t->dmacount) < t->bsize) + /* Reading short block. Do it through the internal buffer. */ + isa_dmastart (t->dmaflags, t->buf, t->bsize, t->chan); + else + isa_dmastart (t->dmaflags, t->dmavaddr, t->bsize, t->chan); +} + +/* start i/o operation */ +static int wtstart (wtinfo_t *t, unsigned flags, void *vaddr, unsigned len) +{ + int s; + + DEBUG (("wtstart()\n")); + s = wtpoll (t, t->BUSY | t->NOEXCEP, t->BUSY | t->NOEXCEP); /* ready? */ + if (! (s & t->NOEXCEP)) { + t->flags |= TPEXCEP; /* error */ + return (0); + } + t->flags &= ~TPEXCEP; /* clear exception flag */ + t->dmavaddr = vaddr; + t->dmatotal = len; + t->dmacount = 0; + t->dmaflags = flags; + wtdma (t); + return (1); +} + +/* start timer */ +static void wtclock (wtinfo_t *t) +{ + if (! (t->flags & TPTIMER)) { + t->flags |= TPTIMER; + /* Some controllers seem to lose dma interrupts too often. + * To make the tape stream we need 1 tick timeout. */ + timeout (wtimer, (caddr_t)t, (t->flags & TPACTIVE) ? 1 : hz); + } +} + +/* + * Simulate an interrupt periodically while i/o is going. + * This is necessary in case interrupts get eaten due to + * multiple devices on a single IRQ line. + */ +static void wtimer (void *xt) +{ + wtinfo_t *t = (wtinfo_t *)xt; + int s; + + t->flags &= ~TPTIMER; + if (! (t->flags & (TPACTIVE | TPREW | TPRMARK | TPWMARK))) + return; + + /* If i/o going, simulate interrupt. */ + s = splbio (); + if ((inb (t->STATPORT) & (t->BUSY | t->NOEXCEP)) != (t->BUSY | t->NOEXCEP)) { + DEBUG (("wtimer() -- ")); + wtintr (t->unit); + } + splx (s); + + /* Restart timer if i/o pending. */ + if (t->flags & (TPACTIVE | TPREW | TPRMARK | TPWMARK)) + wtclock (t); +} + +/* reset the controller */ +static int wtreset (wtinfo_t *t) +{ + /* Perform QIC-02 and QIC-36 compatible reset sequence. */ + /* Thanks to Mikael Hybsch <micke@dynas.se>. */ + int s, i; + + outb (t->CTLPORT, t->RESET | t->ONLINE); /* send reset */ + DELAY (30); + outb (t->CTLPORT, t->ONLINE); /* turn off reset */ + DELAY (30); + + /* Read the controller status. */ + s = inb (t->STATPORT); + if (s == 0xff) /* no port at this address? */ + return (0); + + /* Wait 3 sec for reset to complete. Needed for QIC-36 boards? */ + for (i=0; i<3000; ++i) { + if (! (s & t->BUSY) || ! (s & t->NOEXCEP)) + break; + DELAY (1000); + s = inb (t->STATPORT); + } + return ((s & t->RESETMASK) == t->RESETVAL); +} + +/* get controller status information */ +/* return 0 if user i/o request should receive an i/o error code */ +static int wtsense (wtinfo_t *t, int verb, int ignor) +{ + char *msg = 0; + int err; + + DEBUG (("wtsense() ignor=0x%x\n", ignor)); + t->flags &= ~(TPRO | TPWO); + if (! wtstatus (t)) + return (0); + if (! (t->error.err & TP_ST0)) + t->error.err &= ~TP_ST0MASK; + if (! (t->error.err & TP_ST1)) + t->error.err &= ~TP_ST1MASK; + t->error.err &= ~ignor; /* ignore certain errors */ + err = t->error.err & (TP_FIL | TP_BNL | TP_UDA | TP_EOM | TP_WRP | + TP_USL | TP_CNI | TP_MBD | TP_NDT | TP_ILL); + if (! err) + return (1); + if (! verb) + return (0); + + /* lifted from tdriver.c from Wangtek */ + if (err & TP_USL) msg = "Drive not online"; + else if (err & TP_CNI) msg = "No cartridge"; + else if ((err & TP_WRP) && !(t->flags & TPWP)) { + msg = "Tape is write protected"; + t->flags |= TPWP; + } + else if (err & TP_FIL) msg = 0 /*"Filemark detected"*/; + else if (err & TP_EOM) msg = 0 /*"End of tape"*/; + else if (err & TP_BNL) msg = "Block not located"; + else if (err & TP_UDA) msg = "Unrecoverable data error"; + else if (err & TP_NDT) msg = "No data detected"; + else if (err & TP_ILL) msg = "Illegal command"; + if (msg) + printf ("wt%d: %s\n", t->unit, msg); + return (0); +} + +/* get controller status information */ +static int wtstatus (wtinfo_t *t) +{ + char *p; + + wtpoll (t, t->BUSY | t->NOEXCEP, t->BUSY | t->NOEXCEP); /* ready? */ + outb (t->CMDPORT, QIC_RDSTAT); /* send `read status' command */ + + outb (t->CTLPORT, t->REQUEST | t->ONLINE); /* set request */ + wtpoll (t, t->BUSY, t->BUSY); /* wait for ready */ + outb (t->CTLPORT, t->ONLINE); /* reset request */ + wtpoll (t, t->BUSY, 0); /* wait for not ready */ + + p = (char*) &t->error; + while (p < (char*)&t->error + 6) { + int s = wtpoll (t, t->BUSY | t->NOEXCEP, t->BUSY | t->NOEXCEP); + if (! (s & t->NOEXCEP)) /* error */ + return (0); + + *p++ = inb (t->DATAPORT); /* read status byte */ + + outb (t->CTLPORT, t->REQUEST | t->ONLINE); /* set request */ + wtpoll (t, t->BUSY, 0); /* wait for not ready */ + outb (t->CTLPORT, t->ONLINE); /* unset request */ + } + return (1); +} +#endif /* NWT */ diff --git a/sys/i386/isa/wtreg.h b/sys/i386/isa/wtreg.h new file mode 100644 index 0000000..e45e514 --- /dev/null +++ b/sys/i386/isa/wtreg.h @@ -0,0 +1,125 @@ +/* + * Streamer tape driver for 386bsd and FreeBSD. + * Supports Archive and Wangtek compatible QIC-02/QIC-36 boards. + * + * Copyright (C) 1993 by: + * Sergey Ryzhkov <sir@kiae.su> + * Serge Vakulenko <vak@zebub.msk.su> + * + * This software is distributed with NO WARRANTIES, not even the implied + * warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Authors grant any other persons or organisations permission to use + * or modify this software as long as this message is kept with the software, + * all derivative works or modified versions. + * + * This driver is derived from the old 386bsd Wangtek streamer tape driver, + * made by Robert Baron at CMU, based on Intel sources. + * Authors thank Robert Baron, CMU and Intel and retain here + * the original CMU copyright notice. + * + * Version 1.3, Thu Nov 11 12:09:13 MSK 1993 + * $Id$ + * + */ + +/* + * Copyright (c) 1989 Carnegie-Mellon University. + * All rights reserved. + * + * Authors: Robert Baron + * + * Permission to use, copy, modify and distribute this software and + * its documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND + * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ + +/* ioctl for direct QIC commands */ +#define WTQICMD _IO('W', 0) + +/* QIC-02 commands allowed for WTQICMD */ +#define QIC_ERASE 0x22 /* erase the tape */ +#define QIC_RETENS 0x24 /* retension the tape */ + +/* internal QIC-02 commands */ +#define QIC_RDDATA 0x80 /* read data */ +#define QIC_READFM 0xa0 /* read file mark */ +#define QIC_WRTDATA 0x40 /* write data */ +#define QIC_WRITEFM 0x60 /* write file mark */ +#define QIC_RDSTAT 0xc0 /* read status command */ +#define QIC_REWIND 0x21 /* rewind command (position+bot) */ +#define QIC_FMT11 0x26 /* set format QIC-11 */ +#define QIC_FMT24 0x27 /* set format QIC-24 */ +#define QIC_FMT120 0x28 /* set format QIC-120 */ +#define QIC_FMT150 0x29 /* set format QIC-150 */ +#define QIC_FMT300 0x2a /* set format QIC-300/QIC-2100 */ +#define QIC_FMT600 0x2b /* set format QIC-600/QIC-2200 */ + +/* tape driver flags */ +#define TPINUSE 0x0001 /* tape is already open */ +#define TPREAD 0x0002 /* tape is only open for reading */ +#define TPWRITE 0x0004 /* tape is only open for writing */ +#define TPSTART 0x0008 /* tape must be rewound and reset */ +#define TPRMARK 0x0010 /* read file mark command outstanding */ +#define TPWMARK 0x0020 /* write file mark command outstanding */ +#define TPREW 0x0040 /* rewind command outstanding */ +#define TPEXCEP 0x0080 /* i/o exception flag */ +#define TPVOL 0x0100 /* read file mark or hit end of tape */ +#define TPWO 0x0200 /* write command outstanding */ +#define TPRO 0x0400 /* read command outstanding */ +#define TPWANY 0x0800 /* write command requested */ +#define TPRANY 0x1000 /* read command requested */ +#define TPWP 0x2000 /* write protect error seen */ +#define TPTIMER 0x4000 /* timer() is active */ +#define TPACTIVE 0x8000 /* dma i/o active */ + +/* controller error register bits */ +#define TP_FIL 0x0001 /* File mark detected */ +#define TP_BNL 0x0002 /* Block not located */ +#define TP_UDA 0x0004 /* Unrecoverable data error */ +#define TP_EOM 0x0008 /* End of media */ +#define TP_WRP 0x0010 /* Write protected cartridge */ +#define TP_USL 0x0020 /* Unselected drive */ +#define TP_CNI 0x0040 /* Cartridge not in place */ +#define TP_ST0 0x0080 /* Status byte 0 bits */ +#define TP_ST0MASK 0x00ff /* Status byte 0 mask */ +#define TP_POR 0x0100 /* Power on/reset occurred */ +#define TP_ERM 0x0200 /* Reserved for end of recorded media */ +#define TP_BPE 0x0400 /* Reserved for bus parity error */ +#define TP_BOM 0x0800 /* Beginning of media */ +#define TP_MBD 0x1000 /* Marginal block detected */ +#define TP_NDT 0x2000 /* No data detected */ +#define TP_ILL 0x4000 /* Illegal command - should not happen! */ +#define TP_ST1 0x8000 /* Status byte 1 bits */ +#define TP_ST1MASK 0xff00 /* Status byte 1 mask */ + +/* formats for printing flags and error values */ +#define WTDS_BITS "\20\1inuse\2read\3write\4start\5rmark\6wmark\7rew\10excep\11vol\12wo\13ro\14wany\15rany\16wp\17timer\20active" +#define WTER_BITS "\20\1eof\2bnl\3uda\4eom\5wrp\6usl\7cni\11por\12erm\13bpe\14bom\15mbd\16ndt\17ill" + +/* device minor number */ +#define WT_BSIZE 0100 /* long block flag */ +#define WT_DENSEL 0070 /* density select mask */ +#define WT_DENSDFLT 0000 /* default density */ +#define WT_QIC11 0010 /* 11 megabytes? */ +#define WT_QIC24 0020 /* 60 megabytes */ +#define WT_QIC120 0030 /* 120 megabytes */ +#define WT_QIC150 0040 /* 150 megabytes */ +#define WT_QIC300 0050 /* 300 megabytes? */ +#define WT_QIC600 0060 /* 600 megabytes? */ |