Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Path: utzoo!utgpu!water!watnot!watmath!clyde!rutgers!husc6!seismo!mimsy!chris From: chris@mimsy.UUCP Newsgroups: net.sources Subject: A replacement for the 4.3BSD UDA50 driver (1 of 3) Message-ID: <6141@mimsy.UUCP> Date: Sun, 5-Apr-87 02:08:29 EST Article-I.D.: mimsy.6141 Posted: Sun Apr 5 02:08:29 1987 Date-Received: Sun, 5-Apr-87 22:04:11 EST Organization: University of Maryland, Dept. of Computer Sci. Lines: 1561 : Run this shell script with "sh" not "csh" PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH export PATH all=FALSE if [ x$1 = x-a ]; then all=TRUE fi echo Extracting Installation sed 's/^X//' <<'//go.sysin dd *' >Installation Installation instructions (such as they are): 1. Install the `vax' files in /sys/vax. These are: mscp.c (new) mscp.h (replacement - be sure to keep backups!) mscpvar.h (new) 2. Install the `vaxuba' files in /sys/vaxuba. These are: uba.c.diff (patch) ubavar.h.diff (patch) uda.c (replacement) udareg.h (replacement) You can use the `patch' program (in /usr/new) to apply the patches. 3. Install the `vaxif' file in /sys/vaxif: if_ec.c.diff (patch) 4. Edit /sys/conf/files.vax: add the line vax/mscp.c optional genmscp (in alphabetical order if you like to preseve such things). 5. Edit your configuration file (/sys/conf/PICKLE or whatnot): add the line pseudo-device genmscp You should also add the same line to the LINT configuration file, if you run lint on your kernel code. 6. Configure a new kernel (see `Installing and Operating 4.3BSD' in the System Manager's Manual). /etc/config PICKLE # or whatever cd ../PICKLE # or whatever make depend make This will take some time. 7. Put the new kernel in /, and try booting it. If it works, cheer. Put the new manual entry in /usr/man/man4/uda.4, and the standalone uda.c in /sys/stand/uda.c. //go.sysin dd * if [ `wc -c < Installation` != 1219 ]; then made=FALSE echo error transmitting Installation -- echo length should be 1219, not `wc -c < Installation` else made=TRUE fi if [ $made = TRUE ]; then chmod 644 Installation echo -n ' '; ls -ld Installation fi echo Making directory vax mkdir vax echo Extracting vax/mscp.c sed 's/^X//' <<'//go.sysin dd *' >vax/mscp.c X/* * MSCP generic driver routines */ #include "param.h" #include "buf.h" #include "errno.h" #include "dk.h" #include "syslog.h" #include "../vaxuba/ubavar.h" #include "mscp.h" #include "mscpvar.h" #define PCMD PSWP /* priority for command packet waits */ X/* * During transfers, Unibus mapping info is saved in the buffer's * `ubinfo' field. */ #define b_ubinfo b_resid X/* * Get a command packet. Second argument is true iff we are * to wait if necessary. Return NULL if none are available and * we cannot wait. */ struct mscp * mscp_getcp(mi, canwait) register struct mscp_info *mi; int canwait; { #define mri (&mi->mi_cmd) register struct mscp *mp; register int i; int s = spl5(); again: /* * Ensure that we have some command credits, and * that the next command packet is free. */ if (mi->mi_credits <= MSCP_MINCREDITS) { if (!canwait) { splx(s); return (NULL); } mi->mi_wantcredits = 1; sleep((caddr_t) &mi->mi_wantcredits, PCMD); goto again; } i = mri->mri_next; if (mri->mri_desc[i] & MSCP_OWN) { if (!canwait) { splx(s); return (NULL); } mi->mi_wantcmd = 1; sleep((caddr_t) &mi->mi_wantcmd, PCMD); goto again; } mi->mi_credits--; mri->mri_desc[i] &= ~MSCP_INT; mri->mri_next = (mri->mri_next + 1) % mri->mri_size; splx(s); mp = &mri->mri_ring[i]; /* * Initialise some often-zero fields. * ARE THE LAST TWO NECESSARY IN GENERAL? IT SURE WOULD BE * NICE IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS. */ mp->mscp_msglen = MSCP_MSGLEN; mp->mscp_flags = 0; mp->mscp_modifier = 0; mp->mscp_seq.seq_bytecount = 0; mp->mscp_seq.seq_buffer = 0; X/*???*/ mp->mscp_sccc.sccc_errlgfl = 0; X/*???*/ mp->mscp_sccc.sccc_copyspd = 0; return (mp); #undef mri } #ifdef AVOID_EMULEX_BUG int mscp_aeb_xor = 0x8000bb80; #endif X/* * Do a device go. The driver calls this routine when its go * routine is called; that occurs when the Unibus code has allocated * resources for the transfer. Save the resource information in * bp->b_ubinfo, and finish the MSCP packet. * * N.B.: If we were blocked in ubago(), the drive could have gone * off line and might still be that way. We should probably handle * such a case by changing this command into an on line request, * freeing the Unibus resources, and requeuing the transfer. */ mscp_go(um, mi, mp) register struct uba_ctlr *um; register struct mscp_info *mi; register struct mscp *mp; { register struct buf *bp, *dp; /* * Now is also the time to move the transfer off the * controller and drive queues. However, if there are * more transfers on the drive queue, re-append the drive * to the controller queue. The point of all this, by * the way, is to try to keep as many drives busy as * possible---to deal the controller's credits out to the * drives in a `fair share' arrangement. (To do this fully * would be more trouble than it is worth, though.) */ dp = um->um_tab.b_actf; bp = dp->b_actf; dp->b_actf = bp->av_forw; /* transfer off drive queue */ um->um_tab.b_actf = dp->b_forw; /* drive off ctlr queue */ if (dp->b_actf != NULL) { /* if more on the drive queue */ APPEND(dp, &um->um_tab, b_forw);/* append to ctlr queue */ } else dp->b_active = 0; /* * Move the buffer to the I/O wait queue. */ bp->av_back = mi->mi_wtab.av_back; bp->av_forw = &mi->mi_wtab; mi->mi_wtab.av_back->av_forw = bp; mi->mi_wtab.av_back = bp; /* * Copy the mapping info, finish the command packet, and give * it to the device. The device's dgo routine should then * initiate polling. */ bp->b_ubinfo = um->um_ubinfo; #ifdef AVOID_EMULEX_BUG /* * The Emulex SC41/MS will occasionally zero the lower half word * of the command reference number. The upper half word remains * intact. To keep running, we convert the buffer address into * a small but nonzero integer that is unique over all pending * transfers, and store that value in the upper half word. To * catch occurrances of the bug (so that we can gripe to Emulex), * we also put a nonzero value in the lower word. */ { register u_int i = mi->mi_nextbp; do { /* find a free value */ if (mi->mi_bp[i] == 0) goto found; i = (i + 1) % AEB_MAX_BP; } while (i != mi->mi_nextbp); panic("mscp_go: AEB_MAX_BP too small"); found: mi->mi_bp[i++] = bp; mi->mi_nextbp = i % AEB_MAX_BP; mp->mscp_cmdref = (i << 16) ^ mscp_aeb_xor; } #else mp->mscp_cmdref = (long) bp; #endif mp->mscp_seq.seq_buffer = (bp->b_ubinfo & 0x3ffff) | (UBAI_BDP(bp->b_ubinfo) << 24); *mp->mscp_seq.seq_addr |= MSCP_OWN | MSCP_INT; } X/* * Handle a response ring transition. */ mscp_dorsp(mi) register struct mscp_info *mi; { register struct uba_ctlr *um = mi->mi_um; register struct uba_device *ui; register struct buf *bp; register struct mscp *mp; register int nextrsp; struct mscp_driver *md = mi->mi_md; char *ctlrname, *drivename; int st, error; ctlrname = um->um_driver->ud_mname; drivename = um->um_driver->ud_dname; nextrsp = mi->mi_rsp.mri_next; loop: if (mi->mi_rsp.mri_desc[nextrsp] & MSCP_OWN) { /* * No more responses. Remember the next expected * response index. Check to see if we have some * credits back, and wake up sleepers if so. */ mi->mi_rsp.mri_next = nextrsp; if (mi->mi_wantcredits && mi->mi_credits > MSCP_MINCREDITS) { mi->mi_wantcredits = 0; wakeup((caddr_t) &mi->mi_wantcredits); } return; } /* * Found a response. Update credit information. If there is * nothing else to do, jump to `done' to get the next response. */ mp = &mi->mi_rsp.mri_ring[nextrsp]; mi->mi_credits += MSCP_CREDITS(mp->mscp_msgtc); switch (MSCP_MSGTYPE(mp->mscp_msgtc)) { case MSCPT_SEQ: break; case MSCPT_DATAGRAM: (*md->md_dgram)(um, mp); goto done; case MSCPT_CREDITS: goto done; case MSCPT_MAINTENANCE: default: printf("%s%d: unit %d: unknown message type 0x%x ignored\n", ctlrname, um->um_ctlr, mp->mscp_unit, MSCP_MSGTYPE(mp->mscp_msgtc)); goto done; } /* * Controllers are allowed to interrupt as any drive, so we * must check the command before checking for a drive. */ if (mp->mscp_opcode == (M_OP_SETCTLRC | M_OP_END)) { (*md->md_ctlrdone)(um, mp); goto done; } /* * Find the drive info. If there is none, and this is an * available attention response, try configuring a new drive. */ if (mp->mscp_unit > md->md_ndpc) { printf("%s%d: unit %d out of range\n", ctlrname, um->um_ctlr, mp->mscp_unit); goto done; } if ((ui = mi->mi_ip[mp->mscp_unit]) == NULL) { if ((*md->md_unconf)(um, mp) != MSCP_DONE) { printf("%s%d: unit %d not configured, ", ctlrname, um->um_ctlr, mp->mscp_unit); if (mp->mscp_opcode == M_OP_AVAILATTN) printf("available attn"); else printf("stray response op 0x%x status 0x%x", mp->mscp_opcode, mp->mscp_status); printf(" ignored\n"); } goto done; } /* * Handle individual responses. */ st = mp->mscp_status & M_ST_MASK; error = 0; switch (mp->mscp_opcode) { case M_OP_END: /* * The controller presents a bogus END packet when * a read/write command is given with an illegal * block number. This is contrary to the MSCP * specification (ENDs are to be given only for * invalid commands), but that is the way of it. */ if (st == M_ST_INVALCMD && mp->mscp_cmdref != 0) { printf("%s%d: bad lbn (%d)?\n", drivename, ui->ui_unit, mp->mscp_seq.seq_lbn); error = EIO; goto rwend; } goto unknown; case M_OP_ONLINE | M_OP_END: /* * Finished an ON LINE request. Call the driver to * find out whether it succeeded. If so, mark it on * line. */ if (ui->ui_flags & UNIT_ONLINE) { printf("%s%d: duplicate ONLINE ignored\n", drivename, ui->ui_unit); break; } if ((*md->md_online)(ui, mp) == MSCP_DONE) ui->ui_flags |= UNIT_ONLINE; break; case M_OP_GETUNITST | M_OP_END: /* * Got unit status. Call the driver to find out * whether it succeeded, and if so, mark it. */ if ((*md->md_gotstatus)(ui, mp) == MSCP_DONE) ui->ui_flags |= UNIT_HAVESTATUS; break; case M_OP_AVAILATTN: /* * The drive went offline and we did not notice. * Mark it off line now, to force an on line request * next, so we can make sure it is still the same * drive. * * IF THE UDA DRIVER HAS A COMMAND AWAITING UNIBUS * RESOURCES, THAT COMMAND MAY GO OUT BEFORE THE ON * LINE. IS IT WORTH FIXING?? */ ui->ui_flags &= ~(UNIT_ONLINE | UNIT_HAVESTATUS); break; case M_OP_READ | M_OP_END: case M_OP_WRITE | M_OP_END: /* * A transfer finished. Get the buffer, and release its * map registers via ubadone(). If the command finished * with an off line or available status, the drive went * off line (the idiot controller does not tell us until * it comes back *on* line, or until we try to use it). */ if (mp->mscp_cmdref == 0) { /* * No buffer means there is a bug somewhere! */ printf("%s%d: io done, but no buffer?\n", drivename, ui->ui_unit); mscp_hexdump(mp); break; } rwend: #ifdef AVOID_EMULEX_BUG { register u_short *p = (u_short *) &mp->mscp_cmdref; /* * Note any errors on the part of the controller. * The lower word should be zero after exclusive * or'ing with mscp_aeb_xor, and the upper should * then be in the range [1..AEB_MAX_BP]. */ mp->mscp_cmdref ^= mscp_aeb_xor; p[1]--; if (p[1] >= AEB_MAX_BP) panic("unrecoverable Emulex screwup"); if (p[0] == 0) mi->mi_ok++; else { /* * Calculate the expected response, * assuming p[1] is correct. The * actual response is then the expected * response xor p[0]. */ int sb = ((p[1] + 1) << 16) ^ mscp_aeb_xor; log(LOG_WARNING, "\ Emulex SC41/MS screwup: %s%d, got %d correct, then changed 0x%x to 0x%x\n", ctlrname, um->um_ctlr, mi->mi_ok, sb, sb ^ p[0]); mi->mi_ok = 0; } /* convert index back to buffer, and mark free */ bp = mi->mi_bp[p[1]]; mi->mi_bp[p[1]] = 0; } #else bp = (struct buf *) mp->mscp_cmdref; #ifdef MSCP_PARANOIA { register struct buf *q = mi->mi_wtab.av_forw; /* * Ensure that this response corresponds to * some outstanding request. If not, ignore * it entirely. This will likely cause a * Unibus reset soon, after which the controller * just might behave. */ while (q != bp && q != &mi->mi_wtab) q = q->av_forw; if (q != bp) { printf("%s%d: bad response packet ignored\n", ctlrname, um->um_ctlr); mscp_hexdump(mp); goto out; } } #endif MSCP_PARANOIA #endif AVOID_EMULEX_BUG /* * Mark any error-due-to-bad-LBN (via `goto rwend'). * WHAT STATUS WILL THESE HAVE? IT SURE WOULD BE NICE * IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS. */ if (error) { bp->b_flags |= B_ERROR; bp->b_error = error; } um->um_ubinfo = bp->b_ubinfo; ubadone(um); if (st == M_ST_OFFLINE || st == M_ST_AVAILABLE) ui->ui_flags &= ~(UNIT_ONLINE | UNIT_HAVESTATUS); /* * Unlink the transfer from the wait queue mi_wtab. * If there are no more transfers on the drive queue * for this drive, and it is a profiled disk, turn * off its busy bit. */ bp->av_back->av_forw = bp->av_forw; bp->av_forw->av_back = bp->av_back; if (ui->ui_dk >= 0 && md->md_utab[ui->ui_unit].b_forw == NULL) dk_busy &= ~(1 << ui->ui_dk); /* * If the transfer has something to do with bad * block forwarding, let the driver handle the * rest. */ if ((bp->b_flags & B_BAD) != 0 && md->md_bb != NULL) { (*md->md_bb)(ui, mp, bp); goto out; } /* * If the transfer failed, give the driver a crack * at fixing things up. */ if (st != M_ST_SUCCESS) { switch ((*md->md_ioerr)(ui, mp, bp)) { case MSCP_DONE: /* fixed */ break; case MSCP_RESTARTED: /* still working on it */ goto out; case MSCP_FAILED: /* no luck */ harderr(bp, drivename); mscp_printevent(mp); bp->b_flags |= B_ERROR; bp->b_error = EIO; break; } } /* * Set the residual count and mark the transfer as * done. If the I/O wait queue is now empty, release * the shared BDP, if any. */ bp->b_resid = bp->b_bcount - mp->mscp_seq.seq_bytecount; biodone(bp); if (um->um_bdp && mi->mi_wtab.av_forw == &mi->mi_wtab) ubarelse(um->um_ubanum, &um->um_bdp); out: break; case M_OP_REPLACE | M_OP_END: /* * A replace operation finished. Just let the driver * handle it (if it does replaces). */ if (md->md_replace == NULL) printf("%s%d: bogus REPLACE end\n", drivename, ui->ui_unit); else (*md->md_replace)(ui, mp); break; default: /* * If it is not one of the above, we cannot handle it. * (And we should not have received it, for that matter.) */ unknown: printf("%s%d: unknown opcode 0x%x status 0x%x ignored\n", drivename, ui->ui_unit, mp->mscp_opcode, mp->mscp_status); mscp_hexdump(mp); break; } /* * If the drive needs to be put back in the controller queue, * do that now. (`bp' below ought to be `dp', but they are all * struct buf *.) Note that b_active was cleared in the driver; * we presume that there is something to be done, hence reassert it. */ if (ui->ui_flags & UNIT_REQUEUE) { bp = &md->md_utab[ui->ui_unit]; if (bp->b_active) panic("mscp_dorsp requeue"); APPEND(bp, &um->um_tab, b_forw); bp->b_active = 1; ui->ui_flags &= ~UNIT_REQUEUE; } done: /* * Give back the response packet, and take a look at the next. */ mp->mscp_msglen = MSCP_MSGLEN; mi->mi_rsp.mri_desc[nextrsp] |= MSCP_OWN; nextrsp = (nextrsp + 1) % mi->mi_rsp.mri_size; goto loop; } X/* * Dump the entire contents of an MSCP packet in hex. Mainly useful * for debugging.... */ mscp_hexdump(mp) register struct mscp *mp; { register long *p = (long *) mp; register int i = mp->mscp_msglen; if (i > 256) /* sanity */ i = 256; i /= sizeof (*p); /* ASSUMES MULTIPLE OF sizeof(long) */ while (--i >= 0) printf("0x%x ", *p++); printf("\n"); } X/* * Requeue outstanding transfers, e.g., after Unibus reset. * Also requeue any drives that have on line or unit status * info pending. */ mscp_requeue(mi) struct mscp_info *mi; { register struct uba_ctlr *um = mi->mi_um; register struct uba_device *ui; register struct mscp_driver *md = mi->mi_md; register struct buf *bp, *dp; register int unit; struct buf *nextbp; /* * Clear the controller chain. Mark everything un-busy; we * will soon fix any that are in fact busy. */ um->um_tab.b_actf = NULL; um->um_tab.b_active = 0; for (unit = 0, dp = md->md_utab; unit < md->md_nunits; unit++, dp++) { dp->b_forw = NULL; dp->b_active = 0; } /* * Scan the wait queue, linking buffers onto drive queues. * Note that these must be put at the front of the drive queue, * lest we reorder I/O operations. */ for (bp = mi->mi_wtab.av_back; bp != &mi->mi_wtab; bp = nextbp) { nextbp = bp->av_back; dp = &md->md_utab[minor(bp->b_dev) >> md->md_unitshift]; bp->av_forw = dp->b_actf; if (dp->b_actf == NULL) dp->b_actl = bp; dp->b_actf = bp; } mi->mi_wtab.av_forw = mi->mi_wtab.av_back = &mi->mi_wtab; /* * Scan for drives waiting for on line or status responses, * and for drives with pending transfers. Put these on the * controller queue, and mark the controller busy. */ for (unit = 0, dp = md->md_utab; unit < md->md_nunits; unit++, dp++) { if ((ui = um->um_driver->ud_dinfo[unit]) == NULL || ui->ui_mi != um || ui->ui_alive == 0) continue; /* does this next line belong here?? */ ui->ui_flags &= ~(UNIT_HAVESTATUS | UNIT_ONLINE); if ((ui->ui_flags & UNIT_REQUEUE) == 0 && dp->b_actf == NULL) continue; ui->ui_flags &= ~UNIT_REQUEUE; APPEND(dp, &um->um_tab, b_forw); dp->b_active = 1; um->um_tab.b_active = 1; } #ifdef AVOID_EMULEX_BUG /* * ... and clear the index-to-buffer table. */ for (unit = 0; unit < AEB_MAX_BP; unit++) mi->mi_bp[unit] = 0; #endif } X/* * MSCP error reporting */ X/* * Messages for the various subcodes. */ static char unknown_msg[] = "unknown subcode"; X/* * Subcodes for Success (0) */ static char *succ_msgs[] = { "normal", /* 0 */ "spin down ignored", /* 1 = Spin-Down Ignored */ "still connected", /* 2 = Still Connected */ unknown_msg, "dup. unit #", /* 4 = Duplicate Unit Number */ unknown_msg, unknown_msg, unknown_msg, "already online", /* 8 = Already Online */ unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, "still online", /* 16 = Still Online */ }; X/* * Subcodes for Invalid Command (1) */ static char *icmd_msgs[] = { "invalid msg length", /* 0 = Invalid Message Length */ }; X/* * Subcodes for Command Aborted (2) */ X/* none known */ X/* * Subcodes for Unit Offline (3) */ static char *offl_msgs[] = { "unknown drive", /* 0 = Unknown, or online to other ctlr */ "not mounted", /* 1 = Unmounted, or RUN/STOP at STOP */ "inoperative", /* 2 = Unit Inoperative */ unknown_msg, "duplicate", /* 4 = Duplicate Unit Number */ unknown_msg, unknown_msg, unknown_msg, "in diagnosis", /* 8 = Disabled by FS or diagnostic */ }; X/* * Subcodes for Unit Available (4) */ X/* none known */ X/* * Subcodes for Media Format Error (5) */ static char *media_fmt_msgs[] = { "fct unread - edc", /* 0 = FCT unreadable */ "invalid sector header",/* 1 = Invalid Sector Header */ "not 512 sectors", /* 2 = Not 512 Byte Sectors */ "not formatted", /* 3 = Not Formatted */ "fct ecc", /* 4 = FCT ECC */ }; X/* * Subcodes for Write Protected (6) * N.B.: Code 6 subcodes are 7 bits higher than other subcodes * (i.e., bits 12-15). */ static char *wrprot_msgs[] = { unknown_msg, "software", /* 1 = Software Write Protect */ "hardware", /* 2 = Hardware Write Protect */ }; X/* * Subcodes for Compare Error (7) */ X/* none known */ X/* * Subcodes for Data Error (8) */ static char *data_msgs[] = { "forced error", /* 0 = Forced Error (software) */ unknown_msg, "header compare", /* 2 = Header Compare Error */ "sync timeout", /* 3 = Sync Timeout Error */ unknown_msg, unknown_msg, unknown_msg, "uncorrectable ecc", /* 7 = Uncorrectable ECC */ "1 symbol ecc", /* 8 = 1 bit ECC */ "2 symbol ecc", /* 9 = 2 bit ECC */ "3 symbol ecc", /* 10 = 3 bit ECC */ "4 symbol ecc", /* 11 = 4 bit ECC */ "5 symbol ecc", /* 12 = 5 bit ECC */ "6 symbol ecc", /* 13 = 6 bit ECC */ "7 symbol ecc", /* 14 = 7 bit ECC */ "8 symbol ecc", /* 15 = 8 bit ECC */ }; X/* * Subcodes for Host Buffer Access Error (9) */ static char *host_buffer_msgs[] = { unknown_msg, "odd xfer addr", /* 1 = Odd Transfer Address */ "odd xfer count", /* 2 = Odd Transfer Count */ "non-exist. memory", /* 3 = Non-Existent Memory */ "memory parity", /* 4 = Memory Parity Error */ }; X/* * Subcodes for Controller Error (10) */ static char *cntlr_msgs[] = { unknown_msg, "serdes overrun", /* 1 = Serialiser/Deserialiser Overrun */ "edc", /* 2 = Error Detection Code? */ "inconsistant internal data struct",/* 3 = Internal Error */ }; X/* * Subcodes for Drive Error (11) */ static char *drive_msgs[] = { unknown_msg, "sdi command timeout", /* 1 = SDI Command Timeout */ "ctlr detected protocol",/* 2 = Controller Detected Protocol Error */ "positioner", /* 3 = Positioner Error */ "lost rd/wr ready", /* 4 = Lost R/W Ready Error */ "drive clock dropout", /* 5 = Lost Drive Clock */ "lost recvr ready", /* 6 = Lost Receiver Ready */ "drive detected error", /* 7 = Drive Error */ "ctlr detected pulse or parity",/* 8 = Pulse or Parity Error */ }; X/* * The following table correlates message codes with the * decoding strings. */ struct code_decode { char *cdc_msg; int cdc_nsubcodes; char **cdc_submsgs; } code_decode[] = { #define SC(m) sizeof (m) / sizeof (m[0]), m "success", SC(succ_msgs), "invalid command", SC(icmd_msgs), "command aborted", 0, 0, "unit offline", SC(offl_msgs), "unit available", 0, 0, "media format error", SC(media_fmt_msgs), "write protected", SC(wrprot_msgs), "compare error", 0, 0, "data error", SC(data_msgs), "host buffer access error", SC(host_buffer_msgs), "controller error", SC(cntlr_msgs), "drive error", SC(drive_msgs), #undef SC }; X/* * Print the decoded error event from an MSCP error datagram. */ mscp_printevent(mp) struct mscp *mp; { register int event = mp->mscp_event; register struct code_decode *cdc; int c, sc; char *cm, *scm; /* * The code is the lower six bits of the event number (aka * status). If that is 6 (write protect), the subcode is in * bits 12-15; otherwise, it is in bits 5-11. * I WONDER WHAT THE OTHER BITS ARE FOR. IT SURE WOULD BE * NICE IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS. */ c = event & M_ST_MASK; sc = (c != 6 ? event >> 5 : event >> 12) & 0x7ff; if (c >= sizeof code_decode / sizeof code_decode[0]) cm = "- unknown code", scm = "??"; else { cdc = &code_decode[c]; cm = cdc->cdc_msg; if (sc >= cdc->cdc_nsubcodes) scm = unknown_msg; else scm = cdc->cdc_submsgs[sc]; } printf("%s (%s) (code %d, subcode %d)\n", cm, scm, c, sc); } X/* * Print the code and logical block number for an error packet. * THIS IS PROBABLY PECULIAR TO DISK DRIVES. IT SURE WOULD BE * NICE IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS. */ mscp_decodeerror(um, mp) register struct uba_ctlr *um; register struct mscp *mp; { /* * For bad blocks, mp->mscp_erd.erd_hdr identifies a code and * the logical block number. Code 0 is a regular block; code 6 * is a replacement block. The remaining codes are currently * undefined. The code is in the upper four bits of the header * (bits 0-27 are the lbn). */ int issoft = mp->mscp_flags & (M_LF_SUCC | M_LF_CONT); static char *codemsg[16] = { "lbn", "code 1", "code 2", "code 3", "code 4", "code 5", "rbn", "code 7", "code 8", "code 9", "code 10", "code 11", "code 12", "code 13", "code 14", "code 15" }; #define BADCODE(h) (codemsg[(unsigned)(h) >> 28]) #define BADLBN(h) ((h) & 0xfffffff) printf("%s%d: %s error datagram%s: ", um->um_driver->ud_mname, um->um_ctlr, issoft ? "soft" : "hard", mp->mscp_flags & M_LF_CONT ? " (continuing)" : ""); switch (mp->mscp_format & 0377) { case M_FM_CTLRERR: /* controller error */ break; case M_FM_BUSADDR: /* host memory access error */ printf("memory addr 0x%x: ", mp->mscp_erd.erd_busaddr); break; case M_FM_DISKTRN: printf("unit %d: level %d retry %d, %s %d: ", mp->mscp_unit, mp->mscp_erd.erd_level, mp->mscp_erd.erd_retry, BADCODE(mp->mscp_erd.erd_hdr), BADLBN(mp->mscp_erd.erd_hdr)); break; case M_FM_SDI: printf("unit %d: %s %d: ", mp->mscp_unit, BADCODE(mp->mscp_erd.erd_hdr), BADLBN(mp->mscp_erd.erd_hdr)); break; case M_FM_SMLDSK: printf("unit %d: small disk error, cyl %d: ", mp->mscp_unit, mp->mscp_erd.erd_sdecyl); break; default: printf("unit %d: unknown error, format 0x%x: ", mp->mscp_unit, mp->mscp_format); } mscp_printevent(mp); #undef BADCODE #undef BADLBN } //go.sysin dd * if [ `wc -c < vax/mscp.c` != 23071 ]; then made=FALSE echo error transmitting vax/mscp.c -- echo length should be 23071, not `wc -c < vax/mscp.c` else made=TRUE fi if [ $made = TRUE ]; then chmod 644 vax/mscp.c echo -n ' '; ls -ld vax/mscp.c fi echo Extracting vax/mscp.h sed 's/^X//' <<'//go.sysin dd *' >vax/mscp.h X/* mscp.h 6.1 83/07/29 */ X/* * Definitions for the Mass Storage Control Protocol * I WISH I KNEW WHAT MORE OF THESE WERE. IT SURE WOULD BE NICE * IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS. */ X/* * Control message opcodes */ #define M_OP_ABORT 0x01 /* Abort command */ #define M_OP_GETCMDST 0x02 /* Get command status command */ #define M_OP_GETUNITST 0x03 /* Get unit status command */ #define M_OP_SETCTLRC 0x04 /* Set controller characteristics command */ #define M_OP_SEREX 0x07 /* Serious exception end message */ #define M_OP_AVAILABLE 0x08 /* Available command */ #define M_OP_ONLINE 0x09 /* Online command */ #define M_OP_SETUNITC 0x0a /* Set unit characteristics command */ #define M_OP_DTACCPATH 0x0b /* Determine access paths command */ #define M_OP_ACCESS 0x10 /* Access command */ #define M_OP_COMPCD 0x11 /* Compare controller data command */ #define M_OP_ERASE 0x12 /* Erase command */ #define M_OP_FLUSH 0x13 /* Flush command */ #define M_OP_REPLACE 0x14 /* Replace command */ #define M_OP_COMPHD 0x20 /* Compare host data command */ #define M_OP_READ 0x21 /* Read command */ #define M_OP_WRITE 0x22 /* Write command */ #define M_OP_AVAILATTN 0x40 /* Available attention message */ #define M_OP_DUPUNIT 0x41 /* Duplicate unit number attention message */ #define M_OP_ACCPATH 0x42 /* Access path attention message */ #define M_OP_END 0x80 /* End message flag */ X/* * Generic command modifiers */ #define M_MD_EXPRS 0x8000 /* Express request */ #define M_MD_COMP 0x4000 /* Compare */ #define M_MD_CLSEX 0x2000 /* Clear serious exception */ #define M_MD_ERROR 0x1000 /* Force error */ #define M_MD_SCCHH 0x0800 /* Suppress caching (high speed) */ #define M_MD_SCCHL 0x0400 /* Suppress caching (low speed) */ #define M_MD_SECOR 0x0200 /* Suppress error correction */ #define M_MD_SEREC 0x0100 /* Suppress error recovery */ #define M_MD_SSHDW 0x0080 /* Suppress shadowing */ #define M_MD_WBKNV 0x0040 /* Write back (non-volatile) */ #define M_MD_WBKVL 0x0020 /* Write back (volatile) */ #define M_MD_WRSEQ 0x0010 /* Write shadow set one unit at a time */ X/* * AVAILABLE command modifiers */ #define M_AVM_ALLCD 0x0002 /* All class drivers */ #define M_AVM_SPINDOWN 0x0001 /* Spin down */ X/* * FLUSH command modifiers */ #define M_FLM_FLUSHENU 0x0001 /* Flush entire unit */ #define M_FLM_VOLATILE 0x0002 /* Volatile only */ X/* * GET UNIT STATUS command modifiers */ #define M_GUM_NEXTUNIT 0x0001 /* Next unit */ X/* * ONLINE command modifiers */ #define M_OLM_RIP 0x0001 /* Allow self destruction */ #define M_OLM_IGNMF 0x0002 /* Ignore media format error */ X/* * ONLINE and SET UNIT CHARACTERISTICS command modifiers */ #define M_OSM_ALTERHI 0x0020 /* Alter host identifier */ #define M_OSM_SHADOWSP 0x0010 /* Shadow unit specified */ #define M_OSM_CLEARWBL 0x0008 /* Clear write-back data lost */ #define M_OSM_SETWRPROT 0x0004 /* Set write protect */ X/* * REPLACE command modifiers */ #define M_RPM_PRIMARY 0x0001 /* Primary replacement block */ X/* * End message flags */ #define M_EF_BBLKR 0x80 /* Bad block reported */ #define M_EF_BBLKU 0x40 /* Bad block unreported */ #define M_EF_ERLOG 0x20 /* Error log generated */ #define M_EF_SEREX 0x10 /* Serious exception */ X/* * Controller flags */ #define M_CF_ATTN 0x80 /* Enable attention messages */ #define M_CF_MISC 0x40 /* Enable miscellaneous error log messages */ #define M_CF_OTHER 0x20 /* Enable other host's error log messages */ #define M_CF_THIS 0x10 /* Enable this host's error log messages */ #define M_CF_MLTHS 0x04 /* Multi-host */ #define M_CF_SHADW 0x02 /* Shadowing */ #define M_CF_576 0x01 /* 576 byte sectors */ X/* * Unit flags */ #define M_UF_REPLC 0x8000 /* Controller initiated bad block replacement */ #define M_UF_INACT 0x4000 /* Inactive shadow set unit */ #define M_UF_WRTPH 0x2000 /* Write protect (hardware) */ #define M_UF_WRTPS 0x1000 /* Write protect (software or volume) */ #define M_UF_SCCHH 0x8000 /* Suppress caching (high speed) */ #define M_UF_SCCHL 0x4000 /* Suppress caching (low speed) */ #define M_UF_RMVBL 0x0080 /* Removable media */ #define M_UF_WBKNV 0x0040 /* Write back (non-volatile) */ #define M_UF_576 0x0004 /* 576 byte sectors */ #define M_UF_CMPWR 0x0002 /* Compare writes */ #define M_UF_CMPRD 0x0001 /* Compare reads */ X/* * Error Log message format codes */ #define M_FM_CTLRERR 0x00 /* Controller error */ #define M_FM_BUSADDR 0x01 /* Host memory access error */ #define M_FM_DISKTRN 0x02 /* Disk transfer error */ #define M_FM_SDI 0x03 /* SDI error */ #define M_FM_SMLDSK 0x04 /* Small disk error */ X/* * Error Log message flags */ #define M_LF_SUCC 0x80 /* Operation successful */ #define M_LF_CONT 0x40 /* Operation continuing */ #define M_LF_SQNRS 0x01 /* Sequence number reset */ X/* * Status codes */ #define M_ST_MASK 0x1f /* Status code mask */ #define M_ST_SUCCESS 0x00 /* Success */ #define M_ST_INVALCMD 0x01 /* Invalid command */ #define M_ST_ABORTED 0x02 /* Command aborted */ #define M_ST_OFFLINE 0x03 /* Unit offline */ #define M_ST_AVAILABLE 0x04 /* Unit available */ #define M_ST_MFMTERR 0x05 /* Media format error */ #define M_ST_WRPROT 0x06 /* Write protected */ #define M_ST_COMPERR 0x07 /* Compare error */ #define M_ST_DATAERR 0x08 /* Data error */ #define M_ST_HOSTBUFERR 0x09 /* Host buffer access error */ #define M_ST_CTLRERR 0x0a /* Controller error */ #define M_ST_DRIVEERR 0x0b /* Drive error */ #define M_ST_DIAG 0x1f /* Message from an internal diagnostic */ X/* * Subcodes of M_ST_OFFLINE */ #define M_OFFLINE_UNKNOWN (0 << 5) /* unknown or on other ctlr */ #define M_OFFLINE_UNMOUNTED (1 << 5) /* unmounted or RUN/STOP at STOP */ #define M_OFFLINE_INOPERATIVE (2 << 5) /* inoperative? */ #define M_OFFLINE_DUPLICATE (4 << 5) /* duplicate unit number */ #define M_OFFLINE_INDIAGNOSTIC (8 << 5) /* disabled by FS or diagnostic */ X/* * An MSCP packet begins with a header giving the length of * the entire packet (including the header itself)(?), two bytes * of device specific data, and the a whole bunch of variants * depending on message type. * * N.B.: In most cases we distinguish between a `command' and * an `end' variant as well. The command variant is that which * is given to the controller; the `end' variant is its response. */ X/* * Generic sequential message variant (command and response). */ struct mscpv_seq { long seq_bytecount; /* byte count */ #define seq_rbn seq_bytecount /* aka RBN (replace) */ #define seq_outref seq_bytecount /* aka outref (abort/get cmd status) */ long seq_buffer; /* buffer descriptor */ long seq_xxx1[2]; /* ? */ /* unused */ long seq_lbn; /* logical block number */ long seq_xxx2; /* ? */ /* unused */ long *seq_addr; /* pointer to cmd descriptor */ long seq_software[4]; /* reserved to software; unused */ }; X/* * Set Controller Characteristics command variant */ struct mscpv_sccc { u_short sccc_version; /* MSCP version number */ u_short sccc_ctlrflags; /* controller flags */ u_short sccc_hosttimo; /* host timeout */ u_short sccc_usefrac; /* use fraction */ long sccc_time; /* time and date */ long sccc_xxx1; /* ? */ long sccc_errlgfl; /* ? */ short sccc_xxx2; /* ? */ short sccc_copyspd; /* ? */ long *sccc_addr; /* pointer to cmd descriptor */ }; X/* * Set Controller Characteristics end variant */ struct mscpv_scce { u_short scce_version; /* MSCP version number */ u_short scce_ctlrflags; /* controller flags */ u_short scce_ctlrtimo; /* controller timeout */ u_short scce_ctlrcmdl; /* ??? */ quad scce_ctlrid; /* controller ID */ long scce_xxx[3]; /* ? */ long scce_volser; /* volume serial number */ }; X/* * On Line command variant */ struct mscpv_onlc { long onlc_xxx1[4]; /* ? */ long onlc_errlgfl; /* error log flag? */ short onlc_xxx2; /* ? */ short onlc_copyspd; /* copy speed? */ long *onlc_addr; /* pointer to cmd descriptor */ }; X/* * On Line end variant */ struct mscpv_onle { long onle_xxx1[3]; /* ? */ X/*???*/ short onle_xxx2; /* ? */ u_char onle_drivetype; /* drive type index (same in guse) */ char onle_xxx3; /* ? */ long onle_mediaid; /* media type id (same in guse) */ long onle_xxx4; /* ? */ long onle_unitsize; /* unit size in sectors */ long onle_volser; /* volume serial number */ }; X/* * Get Unit Status end variant (and Avail Attn?) */ struct mscpv_guse { u_short guse_multunit; /* multi-unit code */ u_short guse_unitflags; /* unit flags */ long guse_hostid; /* host id */ long guse_unitid0; /*???*/ short guse_unitid1; /*???*/ u_char guse_drivetype; /* drive type index */ u_char guse_unitid2; /*???*/ long guse_mediaid; /* media type id (encoded) */ short guse_shadowunit; /* shadow unit */ short guse_shadowstat; /* shadow status */ u_short guse_nspt; /* sectors per track */ u_short guse_group; /* group size (?) */ u_short guse_ntpc; /* tracks per cylinder */ u_short guse_xxx; /* reserved */ u_short guse_rctsize; /* RCT size (sectors) */ u_char guse_nrpt; /* RBNs per track */ u_char guse_nrct; /* number of RCTs */ }; X/* * Error datagram variant. */ struct mscpv_erd { quad erd_ctlrid; /* controller ID */ u_char erd_ctlrsoftware; /* controller software version */ u_char erd_ctlrhardware; /* controller hardware version */ u_short erd_multiunit; /* multi-unit code (?) */ union { u_long un_busaddr; /* bus address, if mem access err */ quad un_unitid; /* unit id, otherwise */ } erd_un1; #define erd_busaddr erd_un1.un_busaddr #define erd_unitid erd_un1.un_unitid u_char erd_unitsoftware; /* unit software version */ u_char erd_unithardware; /* unit hardware version */ union { u_char un_b[2]; /* level, retry (if disk xfer err) */ u_short un_s; /* cylinder (if small disk error) */ } erd_un2; #define erd_level erd_un2.un_b[0] #define erd_retry erd_un2.un_b[1] #define erd_sdecyl erd_un2.un_s long erd_volser; /* volume serial number */ u_long erd_hdr; /* `header' (block number) */ char erd_sdistat[12]; /* SDI status information (?) */ }; X/* * I am making brash assumptions about the first four bytes of all * MSCP packets. These appear to be true for both UDA50s and TMSCP * devices (TU81, TA81, TK50). DEC claim that these four bytes are * not part of MSCP itself, yet at least the length is necessary * for, e.g., error checking. */ struct mscp { u_short mscp_msglen; /* length in bytes */ u_char mscp_msgtc; /* type (high 4 bits) and credits */ u_char mscp_vcid; /* virtual circuit ID */ long mscp_cmdref; /* command reference number */ u_short mscp_unit; /* unit number */ u_short mscp_seqnum; /* sequence number */ u_char mscp_opcode; /* opcode */ #define mscp_format mscp_opcode /* aka format (datagrams) */ u_char mscp_flags; /* flags */ u_short mscp_modifier; /* modifier (commands) */ #define mscp_status mscp_modifier /* aka status (ends) */ #define mscp_event mscp_modifier /* aka event (datagrams) */ union { struct mscpv_seq un_seq; /* generic sequential msg */ struct mscpv_sccc un_sccc; /* SCC command */ struct mscpv_scce un_scce; /* SCC end */ struct mscpv_onlc un_onlc; /* on line command */ struct mscpv_onle un_onle; /* on line end */ struct mscpv_guse un_guse; /* get unit status */ struct mscpv_erd un_erd; /* error datagram */ } mscp_un; X/*???*/ long mscp_xxx; /* pad to 64 bytes */ }; X/* * Define message length according to the DEC specifications by dropping * the four byte header. */ #define MSCP_MSGLEN (sizeof (struct mscp) - 4) X/* * Shorthand */ X/* * Generic packet */ #define mscp_seq mscp_un.un_seq X/* * Set Controller Characteristics packet */ #define mscp_sccc mscp_un.un_sccc X/* * Set Controller Characteristics end packet */ #define mscp_scce mscp_un.un_scce X/* * Online / Set Unit Characteristics command packet */ #define mscp_onlc mscp_un.un_onlc X/* * Online end packet */ #define mscp_onle mscp_un.un_onle X/* * Get Unit Status end packet */ #define mscp_guse mscp_un.un_guse X/* * MSCP Error Log packet */ #define mscp_erd mscp_un.un_erd X/* * Macros to break up mscp_msgtc, and types. */ #define MSCP_MSGTYPE(m) ((m) & 0xf0) #define MSCP_CREDITS(m) ((m) & 0x0f) #define MSCPT_SEQ 0x00 /* sequential message */ #define MSCPT_DATAGRAM 0x10 /* error datagram */ #define MSCPT_CREDITS 0x20 /* credit notification */ #define MSCPT_MAINTENANCE 0xf0 /* who knows */ X/* * Here begin more perhaps brash assumptions about MSCP devices... */ X/* * MSCP controllers have `command rings' and `response rings'. A * command ring is a pool of MSCP packets that the host uses to give * commands to the controller; a response ring is a pool of MSCP * packets that the controller uses to give back responses. Entries * in the command and response rings are `owned' by either the host * or the controller; only the owner is allowed to alter any of the * fields in the MSCP packet. Thus, free command packets are owned * by the host, and free response packets by the controller. When * the host gives a packet to the controller, it tells the controller * by touching a device register; when the controller gives a response * to the host, it generates an interrupt if enabled, and sets * a device register as well. * * The pool is `described' by a set of pointers to the packets, along * with the two flags below. */ #define MSCP_OWN 0x80000000 /* controller owns this packet */ #define MSCP_INT 0x40000000 /* controller should interrupt */ //go.sysin dd * if [ `wc -c < vax/mscp.h` != 13411 ]; then made=FALSE echo error transmitting vax/mscp.h -- echo length should be 13411, not `wc -c < vax/mscp.h` else made=TRUE fi if [ $made = TRUE ]; then chmod 644 vax/mscp.h echo -n ' '; ls -ld vax/mscp.h fi echo Extracting vax/mscpvar.h sed 's/^X//' <<'//go.sysin dd *' >vax/mscpvar.h X/* * MSCP generic driver configuration */ X/* * Enabling MSCP_PARANOIA makes the response code perform various checks * on the hardware. (Right now it verifies only the buffer pointer in * mscp_cmdref.) * * Enabling AVOID_EMULEX_BUG selects an alternative method of identifying * transfers in progress, which gets around a rather peculiar bug in the * SC41/MS. Enabling MSCP_PARANOIA instead should work, but will cause * `extra' Unibus resets. * * Either of these flags can simply be included as an `options' line in * your configuration file. */ X/* #define MSCP_PARANOIA */ X/* #define AVOID_EMULEX_BUG */ X/* * Per driver information. * * md_ndpc sets the maximum unit number allowed in response packets. * md_nunits is the number of drives attached to all controllers. * md_unitshift is the divisor for converting a minor device number * to a unit index for the device queues in md_utab. * * The routines are called from the generic response dispatcher. * The first three (dgram, ctlrdone, and unconf) get passed a pointer * to the uba_ctlr and to the packet; the rest get a pointer to the * uba_device and to the packet (`um, mp' and `ui, mp' respectively). * The routines unconf, online, gotstatus, and ioerr are functions * and should return one of the values given below. In addition, * the ioerr and bb routines get a third argument, `bp': a pointer * to the buffer describing the transfer in error. */ struct mscp_driver { int md_ndpc; /* number of drives per ctlr */ int md_nunits; /* total number drives (all ctlrs) */ int md_unitshift; /* device number to unit: >> count */ struct buf *md_utab; /* pointer to device queues */ int (*md_dgram)(); /* error datagram */ int (*md_ctlrdone)(); /* controller operation complete */ int (*md_unconf)(); /* response from unconfigured drive */ int (*md_online)(); /* drive on line */ int (*md_gotstatus)(); /* got unit status */ int (*md_replace)(); /* replace done */ int (*md_ioerr)(); /* read or write failed */ int (*md_bb)(); /* B_BAD io done */ }; X/* * Return values from functions. * MSCP_RESTARTED is peculiar to I/O errors. */ #define MSCP_DONE 0 /* all ok */ #define MSCP_FAILED 1 /* no go */ #define MSCP_RESTARTED 2 /* transfer restarted */ X/* * Ring information, per ring (one each for commands and responses). */ struct mscp_ri { int mri_size; /* ring size */ int mri_next; /* next (expected|free) */ long *mri_desc; /* base address of descriptors */ struct mscp *mri_ring; /* base address of packets */ }; X/* * Per device information. * * mi_ip is a pointer to the inverting pointers (things that get `ui's * given unit numbers) FOR THIS CONTROLLER (NOT the whole set!). * * mi_wtab holds a queue of those transfers that were started but have * not yet finished. Other Unibus drivers do not need this as they hand * out requests one at a time. MSCP devices, however, take a slew of * requests and pick their own order to execute them. This means that * we have to have a place to move transfers that were given to the * controller, so we can tell those apart from those that have not yet * been handed out; mi_wtab is that place. */ struct mscp_info { struct mscp_driver *mi_md; /* pointer to driver info */ struct uba_ctlr *mi_um; /* pointer to ctlr */ struct uba_device **mi_ip; /* pointer to inverting pointers */ struct mscp_ri mi_cmd; /* MSCP command ring info */ struct mscp_ri mi_rsp; /* MSCP response ring info */ short mi_credits; /* transfer credits */ char mi_wantcmd; /* waiting for command packet */ char mi_wantcredits; /* waiting for transfer credits */ struct buf mi_wtab; /* transfer wait queue */ #ifdef AVOID_EMULEX_BUG #define AEB_MAX_BP 32 /* max pend xfers (power of 2) XXX */ struct buf *mi_bp[AEB_MAX_BP]; /* xfer no. to buffer */ u_int mi_nextbp; /* generates unique xfer no's */ int mi_ok; /* for error rate statistics */ #endif AVOID_EMULEX_BUG }; X/* * We have run out of credits when mi_credits is <= MSCP_MINCREDITS. * It is still possible to issue one command in this case, but it must * not be a data transfer. E.g., `get command status' or `abort command' * is legal, while `read' is not. */ #define MSCP_MINCREDITS 1 X/* * Flags for mscp_getcp(). */ #define MSCP_WAIT 1 #define MSCP_DONTWAIT 0 struct mscp *mscp_getcp(); /* get a command packet */ X/* * Unit flags */ #define UNIT_ONLINE 0x01 /* drive is on line */ #define UNIT_HAVESTATUS 0x02 /* got unit status */ #define UNIT_REQUEUE 0x04 /* requeue after response */ X/* * Handle a command ring transition: wake up sleepers for command packets. * This is too simple to bother with a function call. */ #define MSCP_DOCMD(mi) { \ if ((mi)->mi_wantcmd) { \ (mi)->mi_wantcmd = 0; \ wakeup((caddr_t) &(mi)->mi_wantcmd); \ } \ } X/* * The following macro appends a buffer to a drive queue or a drive to * a controller queue, given the name of the forward link. Use as * `APPEND(dp, &um->um_tab, b_forw)' or `APPEND(bp, dp, av_forw)', * where `bp' is a transfer request, `dp' is a drive queue, and `um_tab' * is a controller queue. (That is, the forward link for controller * queues is `b_forw'; for drive queues, it is `av_forw'.) */ #define APPEND(bp, queue, link) { \ (bp)->link = NULL; \ if ((queue)->b_actf == NULL) \ (queue)->b_actf = (bp); \ else \ (queue)->b_actl->link = (bp); \ (queue)->b_actl = (bp); \ } //go.sysin dd * if [ `wc -c < vax/mscpvar.h` != 5413 ]; then made=FALSE echo error transmitting vax/mscpvar.h -- echo length should be 5413, not `wc -c < vax/mscpvar.h` else made=TRUE fi if [ $made = TRUE ]; then chmod 644 vax/mscpvar.h echo -n ' '; ls -ld vax/mscpvar.h fi made=TRUE if [ $made = TRUE ]; then chmod 755 vax echo -n ' '; ls -ld vax fi -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690) UUCP: seismo!mimsy!chris ARPA/CSNet: chris@mimsy.umd.edu