chris@umcp-cs.UUCP (Chris Torek) (01/26/86)
For those of you who do not have time to read this because your RA81 is dying: `event 0353' means you have a `drive detected drive error'---something is wrong with the RA81. `event 0350' is bad news: a hard ECC error. Without a better driver, or a standalone bad block forwarding program, you will have to have DEC reformat the disk. -------- There has been a lot of concern about UDA50 error messages, and what to do about them. Not much is generally known about this; and the standard 4.2 and 4.3 BSD Unix UDA50 drivers do a terrible job of reporting and correcting errors. This is my attempt to shed a bit of light on the matter. Before I even begin, I need to make a few disclaimers. I have not read any DEC manuals on this stuff; all my information comes from suspect sources like working drivers and Emulex manuals. To quote the Emulex SC41/MS manual, `a comprehensive description of MSCP may be ordered from DEC's Software Distribution Center, Order Administration/Processing, 20 Forbes Rd., Northboro, MA 01532'. (Of course if I had read that I would have to worry about copyrights and such, and besides, *you* try to get a University to pay for useful things . . . . :-/ ) First, let me define a few terms. (I always hate not knowing what each acronym stands for.) MSCP - DEC's Mass Storage Control Protocol. This defines the set of commands and responses from UDA50s (and incidentally from TU81 controllers as well). SDI - Storage Disk Interconnect. DSA - Digital Storage Architecture (or maybe Disk Storage, or something like that). RCT - Replacement and Caching Tables. These define where bad block replacements are and so forth. There are multiple copies of these: four on RA81s, 10 on CDC 9771 drives with the Emulex SC41/MS controller. LBN - Logical Block Number. This is just like <cyl,trk,sec> on other drives, mashed together. In fact, at least on RA81s, LBNs start at the outside edge of the disk and work in just like <cyl*M+trk*N+sec> on any other drive. However, they are only piecewise continuous, because of RBNs: RBN - Replacement Block Number. Replacement blocks are scattered about the disk, usually one per track. Replacements are normally allocated on the same track as the bad block, if possible, to avoid seek or head switch delays. DBN - Diagnostic Block Number. On RA81s the diagnostic blocks are separate from the logical and replacement blocks, I think. I suspect they are on the innermost cylinders, but only because that is the best place to put test blocks since the data density is highest there. When an MSCP controller has trouble with a drive, it does several things. It tries to correct the problem itself; and it (usually) reports the problem to the host. This report is called an `error datagram'. It looks like this: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Message Length | Credits | Virt Circ ID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Command Reference Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Unit Number | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Format | Flags | Event Code | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Controller ID + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Ctlr Software | Ctlr Hardware | Multi-Unit Code | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + Unit ID + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Unit Software | Unit Hardware | Group | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Volume Serial Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Header | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + + | SDI Status Information | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The first four bytes are part of the `device specific header'; the remainder is the same for all MSCP devices. The message length is just the length in bytes of the complete datagram. The field called `credits' really consists of four bits of credits (bits 0-3) and four bits of message type (bits 4-7). For error datagrams the message type is `datagram' (1). (0 is Sequential, 2 is credits, 3-14 are undefined, and 15 is reserved for maintenance; these have a different format for the remainder of the packet.) [For those of you who have not realized it yet, MSCP is modeled on networking packets, so it has circuit IDs and sequence numbers and all that rot. Makes the code bigger, so DEC can sell more memory :-). But back to the packet format:] Next is the command reference number, which I presume is just the sequence number of the original command that caused the error. Unix ignores it. After this is two bytes of unit number; this is just the drive number off the RA81 or whatever. Then there is the sequence number, also ignored. After this is the format. This defines what kind of error occurred. Currently defined format numbers are: Controller Error 0 (who knows) Host Memory Access Error 1 (could not get to Vax memory; the attempted address is in the lower longword of the unit ID) Disk Transfer Error 2 (ECC errors and the like; normal stuff) SDI Error 3 (who knows) Small Disk Error 4 (who knows.. `group' contains the cylinder number involved) Other values are undefined. The normal kinds of errors cause Disk Transfer Error reports, so I will describe the remainder of the fields as they apply to these. After the format come flags: Operation Successful 0x80 Operation Continuing 0x40 Sequence Number Reset 0x01 (These are all I know about; there may be others.) The first two indicate that the error was corrected or is now being corrected, and that no host action is required (though a later report may say otherwise). I am not sure what `Sequence Number Reset' means. After the flags comes an `event code'. This is composed of two subfields specifying a major and minor code. The major code is contained in the lower five bits, and the minor in the rest, except for code 6 (write protected), where it is in bits 12-15 only. The major and minor codes are broken up as follows: Major Minor ---- ---- 0 Success 0 normal 1 spin down ignored 2 still connected 4 duplicate unit number 8 already on line 16 still on line 1 Invalid command 2 Command aborted 3 Unit off line 0 unknown drive 1 not mounted 2 inoperative 4 duplicate 8 in diagnosis 4 Unit available 5 Media format error 0 FCT unreadable - EDC 1 invalid sector header 2 not 512 byte sectors 3 not formatted 4 FCT ECC 6 Write protected 1 via hardware 2 via software 7 Compare error 8 Data error 0 forced error 2 header compare 3 sync timeout 7 uncorrectable ECC 8 1 symbol ECC 9 2 symbol ECC 10 3 symbol ECC 11 4 symbol ECC 12 5 symbol ECC 13 6 symbol ECC 14 7 symbol ECC 15 8 symbol ECC 9 Host buffer access error 1 odd transfer address 2 odd transfer count 3 non-existent memory 4 memory parity error 10 Controller error 1 serdes overrun 2 EDC 3 inconsistent internal data structures 11 Drive error 1 SDI command timeout 2 controller detected protocol error 3 positioner error 4 lost read/write ready 5 drive clock dropout 6 lost receiver ready 7 drive detected error 8 controller detected pulse or parity error (I do not know what an `FCT' is---format control table?---but I would guess that it describes the media to the UDA50. I suspect `serdes' is a serialiser/deserialiser box for converting bytes to bits and back. I have no idea what `EDC' means.) Anything not listed in the table is classed `unknown'. Some may be reserved; I have no way of knowing what they really are. Anyway, to move on, after the event code is a quadword giving the controller ID number, the two bytes giving the software and hardware version numbers of the controller. Then comes a `multi-unit code', whatever that may be, then another quadword giving the unit ID number, then the software and hardware version numbers of the unit. After this is the `group', which for a disk format error is a retry number and a level or count. The retry number is the first (lower) byte, the count in the second. This is followed by the volume serial number off the drive, then something called the `header'. The header identifies the bad block and gives a code indicating whether this is a normal block or a replacement. The code is in the upper four bits (28-31) and should be either 0 (logical block) or 6 (replacement block). The remaining codes are undefined. The lower 28 bits are the block number. This is followed by 12 bytes of `SDI status information'. I do not know what this is for; and the Unix drivers ignore it. Given the above information, you can decode error packets. I am not sure if the 4.2 UDA driver prints everything you need. The 4.3 driver prints the unit number, the group (count*256+retry), the header (0+LBN or 6<<28 + RBN), and the event code. At the end of this posting are two routines. The first, which is a regular utility program, takes a `hdr' and `event' as printed by 4.3 (that is, in hex and octal respectively) and prints out the LBN or RBN and the code & subcode. The latter is a replacement uderror() plus a new routine. I believe it will fit right in on a plain 4.2 or 4.3 machine, though I have none to test it on. It prints the error messages in a (hopefully) more readable format. ------- Ok. You have a bad block. Now what? Well, if your driver is fancy enough, it will forward it for you. This process is horribly complex, and I will not attempt to describe it here. What I *will* do is provide some information on the format of RCTs. An RCT consists of some number of 512 byte sectors (the number varies with each drive; RA81s have 765 sectors per RCT) with several functions. The first two sectors are `scratch' blocks used during the forwarding operation, with 0 holding status and 1 the data from the block being replaced. Blocks 2 through (RCT size)-2 are RCT itself; the last block ((RCT size)-1) contains only null RCT entries. RCT blocks are addressed by giving a logical block number that is greater than any of the normal logical blocks. For example, an RA81 has 891072 blocks, numbered 0 through 891071; and its first RCT's scratch block is addressed as LBN 891072. Since each RCT is 765 blocks, the next RCT begins at block 891837. Each RCT sector contains 128 replacement entries. These are longwords whose upper four bits contain a code, and lower 28 bits contain the logical block number of the bad block being replaced. The replacement block number is simply the index of the replacement entry. For example, the fifth entry in the second RCT sector (which is really RCT sector 4) corresponds to replacement block 132. (Got that?) The code numbers for replacement entries are as follows: Code Name Description ---- ---- ----------- 0 unused This RBN is free. 2 primary Allocated, and is the first choice for the block that was replaced. 3 secondary Allocated, but is not the first choice. 4 bad This RBN is unusable. 5 altbad Alternate RBN unusable. 11 null This entry is beyond the last RBN. Other codes are currently undefined. Primary replacements are those that are on the same track as the block being replaced. Why there is any distinction between primary and non-primary replacements I do not know; perhaps VMS uses the information when allocating contiguous files. Code 5 is not listed in the Emulex manual, nor is it used by the bad-block-forwarding driver. It also seems fairly useless. (Note that a good formatter should test all the replacement blocks and mark any that are bad. I do not know how this can be done, as the only way to write to RBNs that I know of is to use the MSCP `replace' command, which marks the replaced block as forwarded.) Well, I hope this is useful... Chris -------- : 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 udadecode.c' sed 's/^X//' <<'//go.sysin dd *' >udadecode.c X/* * Copyright (C) 1986 University of Maryland Computer Science Department * * This code may be freely distributed so long as the copyright notice * and this note remain intact. Distributed 25 January 1986 by Chris * Torek. */ #include <stdio.h> #include <sys/types.h> X/*ARGSUSED*/ main(argc, argv) int argc; char **argv; { u_long hdr; u_short event; char buf[BUFSIZ]; (void) printf("Type control-D to exit\n"); for (;;) { (void) printf("Enter `hdr' (hex) and `event' (octal): "); (void) fflush(stdout); if (fgets(buf, sizeof buf, stdin) == NULL) { (void) printf("\n"); exit(0); } if (sscanf(buf, "%lx %ho", &hdr, &event) < 2) (void) printf("input conversion error; try again\n"); else decode(hdr, event); } } X/* * Messages for the various subcodes. */ char unknown_msg[] = "unknown subcode"; char *succ_msgs[] = { "normal", "spin down ignored", "still connected", unknown_msg, "dup. unit #", unknown_msg, unknown_msg, unknown_msg, "already online", unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, "still online" }; char *offl_msgs[] = { "unknown drive", "not mounted", "inoperative", unknown_msg, "duplicate", unknown_msg, unknown_msg, unknown_msg, "in diagnosis" }; char *wrprot_msgs[] = { unknown_msg, "hardware", "software" }; char *data_msgs[] = { "forced error", unknown_msg, "header compare", "sync timeout", unknown_msg, unknown_msg, unknown_msg, "uncorrectable ecc", "1 symbol ecc", "2 symbol ecc", "3 symbol ecc", "4 symbol ecc", "5 symbol ecc", "6 symbol ecc", "7 symbol ecc", "8 symbol ecc", }; char *media_fmt_msgs[] = { "fct unread - edc", "invalid sector header", "not 512 sectors", "not formatted", "fct ecc" } ; char *host_buffer_msgs[] = { unknown_msg, "odd xfer addr", "odd xfer count", "non-exist. memory", "memory parity" }; char *cntlr_msgs[] = { unknown_msg, "serdes overrun", "edc", "inconsistant internal data struct" }; char *drive_msgs[] = { unknown_msg, "sdi command timeout", "ctlr detected protocol", "positioner", "lost rd/wr ready", "drive clock dropout", "lost recvr ready", "drive detected error", "ctlr detected pulse or parity" }; 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", 0, 0, "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 }; #define CODE(c) ((c) & 0x1f) #define SUBCODE(c) ((CODE(c) != 6 ? (c) >> 5 : (c) >> 12) & 0x7ff) decode(hdr, event) u_long hdr; u_short event; { register struct code_decode *cdc; int c, sc; char *cm, *scm; /* * For bad blocks, mp->mslg_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 mslg_hdr (bits 0-27 are the lbn). */ 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" }; (void) printf("%s %d: ", codemsg[hdr >> 28], hdr & 0xffffff); c = CODE(event); sc = SUBCODE(event); 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]; } (void) printf("%s %s (code %d, subcode %d)\n", scm, cm, c, sc); } //go.sysin dd * if [ `wc -c < udadecode.c` != 3948 ]; then made=FALSE echo 'error transmitting "udadecode.c" --' echo 'length should be 3948, not' `wc -c < udadecode.c` else made=TRUE fi if [ $made = TRUE ]; then chmod 644 udadecode.c echo -n ' '; ls -ld udadecode.c fi echo 'Extracting uderror' sed 's/^X//' <<'//go.sysin dd *' >uderror X/* * Process a UDA50 error log message * * For now, just log the error on the console. * Only minimal decoding is done, only "useful" * information is printed. Eventually should * send message to an error logger. */ #define CODE(c) ((c) & 0x1f) #define SUBCODE(c) ((CODE(c) != 6 ? (c) >> 5 : (c) >> 12) & 0x7ff) uderror(um, mp) register struct uba_ctlr *um; register struct mslg *mp; { int issoft = mp->mslg_flags & (M_LF_SUCC|M_LF_CONT); /* * For bad blocks, mp->mslg_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 mslg_hdr (bits 0-27 are the lbn). */ 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) if (!softerrormsgs && issoft) return; printf("uda%d: %s error datagram, ", um->um_ctlr, issoft ? "soft" : "hard"); switch (mp->mslg_format & 0377) { case M_FM_CNTERR: break; case M_FM_BUSADDR: printf("memory addr 0x%x", *(long *)&mp->mslg_busaddr); break; case M_FM_DISKTRN: printf("ra%d: retry %d count %d, %s %d", mp->mslg_unit, mp->mslg_group & 0xff, (mp->mslg_group >> 8) & 0xff, BADCODE(mp->mslg_hdr), BADLBN(mp->mslg_hdr)); break; case M_FM_SDI: printf("ra%d: %s %d", mp->mslg_unit, BADCODE(mp->mslg_hdr), BADLBN(mp->mslg_hdr)); break; case M_FM_SMLDSK: printf("ra%d: small disk error, cyl %d", mp->mslg_unit, mp->mslg_sdecyl); break; default: printf("ra%d: unknown error, format 0%o", mp->mslg_unit, mp->mslg_format); } printf("%s\n", mp->mslg_flags & M_LF_CONT ? "; continuing" : ""); udputstatus((struct mscp *)mp); if (udaerror) { register long *p = (long *)mp; register int i; for (i = 0; i < mp->mslg_header.uda_msglen+4; i += sizeof(*p)) printf("0x%x ", *p++); printf("\n"); } #undef BADCODE #undef BADLBN } X/* * Messages for the various subcodes. */ static char unknown_msg[] = "unknown subcode"; static char *succ_msgs[] = { "normal", "spin down ignored", "still connected", unknown_msg, "dup. unit #", unknown_msg, unknown_msg, unknown_msg, "already online", unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, unknown_msg, "still online" }; static char *offl_msgs[] = { "unknown drive", "not mounted", "inoperative", unknown_msg, "duplicate", unknown_msg, unknown_msg, unknown_msg, "in diagnosis" }; static char *wrprot_msgs[] = { unknown_msg, "hardware", "software" }; static char *data_msgs[] = { "forced error", unknown_msg, "header compare", "sync timeout", unknown_msg, unknown_msg, unknown_msg, "uncorrectable ecc", "1 symbol ecc", "2 symbol ecc", "3 symbol ecc", "4 symbol ecc", "5 symbol ecc", "6 symbol ecc", "7 symbol ecc", "8 symbol ecc", }; static char *media_fmt_msgs[] = { "fct unread - edc", "invalid sector header", "not 512 sectors", "not formatted", "fct ecc" } ; static char *host_buffer_msgs[] = { unknown_msg, "odd xfer addr", "odd xfer count", "non-exist. memory", "memory parity" }; static char *cntlr_msgs[] = { unknown_msg, "serdes overrun", "edc", "inconsistant internal data struct" }; static char *drive_msgs[] = { unknown_msg, "sdi command timeout", "ctlr detected protocol", "positioner", "lost rd/wr ready", "drive clock dropout", "lost recvr ready", "drive detected error", "ctlr detected pulse or parity" }; 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", 0, 0, "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 }; udputstatus(mp) struct mscp *mp; { register int event = ((struct mslg *)mp)->mslg_event; register struct code_decode *cdc; int c, sc; char *cm, *scm; c = CODE(event); sc = SUBCODE(event); 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", scm, cm, c, sc); } //go.sysin dd * if [ `wc -c < uderror` != 4787 ]; then made=FALSE echo 'error transmitting "uderror" --' echo 'length should be 4787, not' `wc -c < uderror` else made=TRUE fi if [ $made = TRUE ]; then chmod 644 uderror echo -n ' '; ls -ld uderror fi exit 0 -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251) UUCP: seismo!umcp-cs!chris CSNet: chris@umcp-cs ARPA: chris@mimsy.umd.edu
chris@umcp-cs.UUCP (Chris Torek) (02/02/86)
There were a few errors in my original article on UDA50/MSCP errors (aside from the numerous typographical ggglitches, that is). First, I should have mentioned in the `quick note' at the top that events 0350 and 0353 are not really serious if the `flags' modifier has bits 0x80 or 0x40 set. I did mention these in the full description: they are the `success' and `continuing' flags, respectively, meaning `I fixed it' or `I think I can fix it'. (Thanks to msdc!dan for pointing out that `event 0350' is not necessarily cause for immediate suicide.) Second, in the list of error codes, `hardware' and `software' write protect are transposed. This is also true in both C routines. Change the table of errors, code 6, to 6 Write protected 1 via software 2 via hardware and change `wrprot_msgs' to unknown_msg, "software", "hardware" in udadecode.c and in uderror. -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1415) UUCP: seismo!umcp-cs!chris CSNet: chris@umcp-cs ARPA: chris@mimsy.umd.edu