|
|
1.1 ! root 1: #include "u.h" ! 2: #include "../port/lib.h" ! 3: #include "mem.h" ! 4: #include "dat.h" ! 5: #include "fns.h" ! 6: #include "../port/error.h" ! 7: #include "devtab.h" ! 8: #include "io.h" ! 9: /* ! 10: * CD-ROM driver for Panasonic and Mitsumi drives on SB16 and SBPRO cards ! 11: */ ! 12: ! 13: ! 14: typedef struct Drive Drive; ! 15: ! 16: enum ! 17: { ! 18: FRAMESIZE = 2048, /* max in a transfer */ ! 19: Panasonic = 1, ! 20: Mitsumi = 2, ! 21: ! 22: Qdir = 0, ! 23: Qcd, ! 24: Qcdctl, ! 25: ! 26: CMDLEN = 7, ! 27: ! 28: DTEN = 0x02, /* Status register */ ! 29: STEN = 0x04, ! 30: ! 31: DRIVE0 = 0, ! 32: ! 33: CMD = 0, /* Ports */ ! 34: DATA = 0, ! 35: PHASE = 1, ! 36: STATUS = 1, ! 37: RESET = 2, ! 38: SELECT = 3, ! 39: ! 40: ABORT = 0x08, ! 41: BLOCKPARAM = 0x00, ! 42: DOORCLOSE = 0x07, ! 43: DOOROPEN = 0x06, ! 44: LOCK = 0x0c, ! 45: MODESELECT = 0x09, ! 46: NOP = 0x05, ! 47: PAUSE = 0x0d, ! 48: PLAYBLOCKS = 0x0e, ! 49: PLAYTRKS = 0x0f, ! 50: READ = 0x10, ! 51: READDINFO = 0x8b, ! 52: READERROR = 0x82, ! 53: READID = 0x83, ! 54: RESUME = 0x80, ! 55: ! 56: ST_DOOROPEN = 0x80, ! 57: ST_DSKIN = 0x40, ! 58: ST_SPIN = 0x20, ! 59: ST_ERROR = 0x10, ! 60: ST_BUSY = 0x04, ! 61: ST_AUDIOBSY = 0x02, ! 62: ! 63: MEDIA_CHANGED = 0x11, ! 64: NOT_READY = 0x03, ! 65: HARD_RESET = 0x12, ! 66: DISC_OUT = 0x15, ! 67: ! 68: MST_CMD_CHECK = 0x01, /* command error */ ! 69: MST_BUSY = 0x02, /* now playing */ ! 70: MST_READ_ERR = 0x04, /* read error */ ! 71: MST_DSK_TYPE = 0x08, /* cdda */ ! 72: MST_SERVO_CHECK = 0x10, ! 73: MST_DSK_CHG = 0x20, /* disk removed or changed */ ! 74: MST_READY = 0x40, /* disk in the drive */ ! 75: MST_DOOR_OPEN = 0x80, /* door is open */ ! 76: ! 77: MFL_DATA = 0x02, /* data available */ ! 78: MFL_STATUS = 0x04, /* status available */ ! 79: ! 80: MCMD_RESET = 0x00, ! 81: MCMD_GET_DISK_INFO = 0x10, /* read info from disk */ ! 82: MCMD_GET_Q_CHANNEL = 0x20, /* read info from q channel */ ! 83: MCMD_GET_STATUS = 0x40, ! 84: MCMD_SET_MODE = 0x50, ! 85: MCMD_SOFT_RESET = 0x60, ! 86: MCMD_STOP = 0x70, /* stop play */ ! 87: MCMD_CONFIG_DRIVE = 0x90, ! 88: MCMD_SET_VOLUME = 0xAE, /* set audio level */ ! 89: MCMD_PLAY_READ = 0xC0, /* play or read data */ ! 90: MCMD_GET_VERSION = 0xDC, ! 91: MCMD_EJECT = 0xF6, /* eject (FX drive) */ ! 92: ! 93: MSTAT_DOOR = 1, ! 94: MSTAT_CHNG, ! 95: MSTAT_CDDA, ! 96: MSTAT_READY, ! 97: MSTAT_ERROR, ! 98: }; ! 99: ! 100: static char *etable[] = ! 101: { ! 102: "no error", ! 103: "soft read error after retry", ! 104: "soft read error after error correction", ! 105: "not ready", ! 106: "unable to read TOC", ! 107: "hard read error of data track", ! 108: "seek failed", ! 109: "tracking servo failure", ! 110: "drive RAM failure", ! 111: "drive self-test failed", ! 112: "focusing servo failure", ! 113: "spindle servo failure", ! 114: "data path failure", ! 115: "illegal logical block address", ! 116: "illegal field in CDB", ! 117: "end of user encountered on this track", ! 118: "illegal data mode for this track", ! 119: "media changed", ! 120: "power-on or drive reset occurred", ! 121: "drive ROM failure", ! 122: "illegal drive command received from host", ! 123: "disc removed during operation", ! 124: "drive Hardware error", ! 125: "illegal request from host" ! 126: }; ! 127: ! 128: static char *metable[] = ! 129: { ! 130: [MSTAT_DOOR] = "cdrom door is open", ! 131: [MSTAT_CHNG] = "cdrom media changed", ! 132: [MSTAT_CDDA] = "cd is not a cdrom", ! 133: [MSTAT_READY] = "cdrom is not ready", ! 134: [MSTAT_ERROR] = "cannot get cd status", ! 135: }; ! 136: ! 137: struct Drive ! 138: { ! 139: int type; ! 140: Ref opens; ! 141: QLock; ! 142: Rendez rendez; ! 143: ulong blocks; /* blocks on disk */ ! 144: int port; ! 145: uchar buf[FRAMESIZE]; ! 146: ! 147: void (*sbcdio)(int, ulong); ! 148: void (*initdrive)(int); ! 149: }; ! 150: ! 151: Dirtab ! 152: cdtab[] = ! 153: { ! 154: "cd", {Qcd}, 0, 0666, ! 155: "cdctl", {Qcdctl}, 0, 0666, ! 156: }; ! 157: #define Ncdtab (sizeof cdtab/sizeof(Dirtab)) ! 158: #define CDFILE 0 /* Array index of Qcd direntry */ ! 159: ! 160: static Drive sbcd; ! 161: ! 162: static void pansbcdio(int, ulong); ! 163: static void mitsbcdio(int, ulong); ! 164: static void paninitdrive(int); ! 165: static void mitinitdrive(int); ! 166: ! 167: static void ! 168: drain(int port) ! 169: { ! 170: outb(port+STATUS, 1); ! 171: while((inb(port+STATUS) & (DTEN|STEN)) == STEN) ! 172: inb(port+DATA); ! 173: outb(port+STATUS,0); ! 174: } ! 175: ! 176: static int ! 177: status(int port) ! 178: { ! 179: int sr; ! 180: ! 181: sr = inb(port+DATA); ! 182: ! 183: for(;;) { ! 184: if(inb(port+STATUS) == 0xff) ! 185: break; ! 186: if((sr&DTEN|STEN) == STEN) ! 187: drain(port); ! 188: ! 189: print("devsbcd: busy for sr\n"); ! 190: sr = inb(port+DATA); ! 191: } ! 192: ! 193: return sr; ! 194: } ! 195: ! 196: static void ! 197: issue(int port, uchar *cmd) ! 198: { ! 199: ulong s; ! 200: uchar sr; ! 201: int i, len; ! 202: ! 203: i = inb(port+STATUS); ! 204: if(i != 0xff) { ! 205: if ((i & DTEN|STEN) == STEN) ! 206: drain(port); ! 207: i = status(port); ! 208: USED(i); ! 209: sr = inb(port+STATUS); ! 210: if(sr != 0xff) { ! 211: print("devsbcd: device wedged, sr=%2.2ux\n", inb(port+STATUS)); ! 212: outb(port+RESET, 0); ! 213: tsleep(&sbcd.rendez, return0, 0, 500); ! 214: } ! 215: } ! 216: ! 217: outb(port+SELECT, DRIVE0); ! 218: ! 219: len = CMDLEN; ! 220: if(cmd[0] == ABORT) ! 221: len=1; ! 222: ! 223: s = splhi(); ! 224: for (i = 0; i < len; i++) ! 225: outb(port+CMD,*cmd++); ! 226: splx(s); ! 227: } ! 228: ! 229: void ! 230: sbcdreset(void) ! 231: { ! 232: } ! 233: ! 234: void ! 235: sbcdinit(void) ! 236: { ! 237: ISAConf sbconf; ! 238: ! 239: sbconf.port = 0; ! 240: if(isaconfig("cdrom", 0, &sbconf) == 0) ! 241: return; ! 242: if(strcmp(sbconf.type, "panasonic") == 0 || strcmp(sbconf.type, "matsushita") == 0) { ! 243: sbcd.sbcdio = pansbcdio; ! 244: sbcd.initdrive = paninitdrive; ! 245: if(sbconf.port == 0) ! 246: sbconf.port = 0x230; ! 247: } ! 248: else ! 249: if(strcmp(sbconf.type, "mitsumi") == 0) { ! 250: sbcd.sbcdio = mitsbcdio; ! 251: sbcd.initdrive = mitinitdrive; ! 252: if(sbconf.port == 0) ! 253: sbconf.port = 0x340; ! 254: } ! 255: if(sbcd.sbcdio == 0) { ! 256: print("devsbcd: %s is not a valid cd-rom type\n", sbconf.type); ! 257: return; ! 258: } ! 259: switch(sbconf.port) { ! 260: case 0x230: ! 261: case 0x250: ! 262: case 0x270: ! 263: case 0x290: ! 264: case 0x340: ! 265: break; ! 266: default: ! 267: print("devsbcd: bad port 0x%x\n", sbconf.port); ! 268: return; ! 269: } ! 270: sbcd.port = sbconf.port; ! 271: } ! 272: ! 273: Chan* ! 274: sbcdattach(char *param) ! 275: { ! 276: if(sbcd.port == 0) ! 277: error("no cd-rom configured"); ! 278: ! 279: return devattach('m', param); ! 280: } ! 281: ! 282: Chan* ! 283: sbcdclone(Chan *c, Chan *nc) ! 284: { ! 285: return devclone(c, nc); ! 286: } ! 287: ! 288: int ! 289: sbcdwalk(Chan *c, char *name) ! 290: { ! 291: return devwalk(c, name, cdtab, Ncdtab, devgen); ! 292: } ! 293: ! 294: void ! 295: sbcdstat(Chan *c, char *dp) ! 296: { ! 297: devstat(c, dp, cdtab, Ncdtab, devgen); ! 298: } ! 299: ! 300: static int ! 301: poll(int timeo, int state, int port, int delay) ! 302: { ! 303: int i, sr; ! 304: ! 305: sr = 0; ! 306: for(i = 0; i < timeo; i++) { ! 307: sr = inb(port+STATUS) & (STEN|DTEN); ! 308: if(sr != (STEN|DTEN)) ! 309: break; ! 310: if(delay != 0) ! 311: tsleep(&sbcd.rendez, return0, 0, TK2MS(1)); ! 312: } ! 313: return sr != state; ! 314: } ! 315: ! 316: static int ! 317: reqsense(int port) ! 318: { ! 319: int i; ! 320: uchar cmd[CMDLEN], data[12]; ! 321: ! 322: memset(cmd, 0, sizeof(cmd)); ! 323: cmd[0] = READERROR; ! 324: issue(port, cmd); ! 325: i = poll(2000, DTEN, port, 0); ! 326: if(i < 0) { ! 327: print("devsbcd: get error failed\n"); ! 328: error(Eio); ! 329: } ! 330: insb(port, data, 8); ! 331: status(port); ! 332: ! 333: return data[2]; ! 334: } ! 335: ! 336: static void ! 337: getcap(Drive *d) ! 338: { ! 339: int port, i, sr, retry; ! 340: uchar cmd[CMDLEN], data[12]; ! 341: ! 342: port = d->port; ! 343: ! 344: for(retry = 0; retry < 10; retry++) { ! 345: memset(cmd, 0, sizeof(cmd)); ! 346: cmd[0] = READDINFO; ! 347: issue(port, cmd); ! 348: i = poll(10, DTEN, port, 1); ! 349: if(i < 0) { ! 350: print("devsbcd: cmd error, sr=%2.2ux\n", status(port)); ! 351: error(Eio); ! 352: } ! 353: insb(port, data, 6); ! 354: sr = status(port); ! 355: ! 356: sbcd.blocks = (data[3]*4500) + (data[4]*75) + data[5]; ! 357: sbcd.blocks -= 150; ! 358: cdtab[CDFILE].length = sbcd.blocks*FRAMESIZE; ! 359: ! 360: if((sr & ST_ERROR) == 0) ! 361: break; ! 362: } ! 363: if(retry >= 10) ! 364: error(Eio); ! 365: } ! 366: ! 367: Chan* ! 368: sbcdopen(Chan *c, int omode) ! 369: { ! 370: switch(c->qid.path) { ! 371: case Qcd: ! 372: if(incref(&sbcd.opens) == 1) { ! 373: if(waserror()) { ! 374: decref(&sbcd.opens); ! 375: nexterror(); ! 376: } ! 377: sbcd.initdrive(sbcd.port); ! 378: poperror(); ! 379: } ! 380: break; ! 381: } ! 382: return devopen(c, omode, cdtab, Ncdtab, devgen); ! 383: } ! 384: ! 385: void ! 386: sbcdcreate(Chan *c, char *name, int omode, ulong perm) ! 387: { ! 388: USED(c, name, omode, perm); ! 389: error(Eperm); ! 390: } ! 391: ! 392: void ! 393: sbcdclose(Chan *c) ! 394: { ! 395: switch(c->qid.path) { ! 396: default: ! 397: break; ! 398: case Qcd: ! 399: if(c->flag & COPEN) ! 400: decref(&sbcd.opens); ! 401: break; ! 402: } ! 403: } ! 404: ! 405: void ! 406: sbcdremove(Chan *c) ! 407: { ! 408: USED(c); ! 409: error(Eperm); ! 410: } ! 411: ! 412: void ! 413: sbcdwstat(Chan *c, char *dp) ! 414: { ! 415: USED(c, dp); ! 416: error(Eperm); ! 417: } ! 418: ! 419: long ! 420: sbcdread(Chan *c, char *a, long n, ulong offset) ! 421: { ! 422: char *t, buf[64]; ! 423: long m, o, n0, bn; ! 424: ! 425: if(c->qid.path & CHDIR) ! 426: return devdirread(c, a, n, cdtab, Ncdtab, devgen); ! 427: ! 428: n0 = n; ! 429: switch(c->qid.path) { ! 430: case Qcdctl: ! 431: t = "panasonic"; ! 432: if(sbcd.sbcdio != pansbcdio) ! 433: t = "mitsumi"; ! 434: sprint(buf, "port=0x%ux drive=%s\n", sbcd.port, t); ! 435: return readstr(offset, a, n, buf); ! 436: case Qcd: ! 437: qlock(&sbcd); ! 438: if(waserror()) { ! 439: qunlock(&sbcd); ! 440: nexterror(); ! 441: } ! 442: while(n > 0) { ! 443: bn = offset / FRAMESIZE; ! 444: o = offset % FRAMESIZE; ! 445: m = FRAMESIZE - o; ! 446: if(m > n) ! 447: m = n; ! 448: if(bn >= sbcd.blocks) ! 449: break; ! 450: sbcd.sbcdio(sbcd.port, bn); ! 451: memmove(a, sbcd.buf+o, m); ! 452: n -= m; ! 453: offset += m; ! 454: a += m; ! 455: } ! 456: qunlock(&sbcd); ! 457: poperror(); ! 458: break; ! 459: } ! 460: return n0-n; ! 461: } ! 462: ! 463: long ! 464: sbcdwrite(Chan *c, char *a, long n, ulong offset) ! 465: { ! 466: ! 467: USED(c, a, n, offset); ! 468: error(Eperm); ! 469: return 0; ! 470: } ! 471: ! 472: static void ! 473: bin2bcd(uchar *p) ! 474: { ! 475: int u, t; ! 476: ! 477: u = *p % 10; ! 478: t = *p / 10; ! 479: *p = u | (t << 4); ! 480: } ! 481: ! 482: static void ! 483: bin2msf(uchar *msf, long addr) ! 484: { ! 485: addr += 150; ! 486: msf[0] = addr / 4500; ! 487: addr %= 4500; ! 488: msf[1] = addr / 75; ! 489: msf[2] = addr % 75; ! 490: } ! 491: ! 492: static int ! 493: bcd2bin(uchar bcd) ! 494: { ! 495: return (bcd >> 4) * 10 + (bcd & 0xF); ! 496: } ! 497: ! 498: /* ! 499: * panasonic specific ! 500: */ ! 501: static void ! 502: paninitdrive(int port) ! 503: { ! 504: int i, sr; ! 505: uchar cmd[CMDLEN]; ! 506: ! 507: qlock(&sbcd); ! 508: if(waserror()) { ! 509: qunlock(&sbcd); ! 510: nexterror(); ! 511: } ! 512: ! 513: memset(cmd, 0, sizeof(cmd)); ! 514: cmd[0] = NOP; ! 515: issue(port, cmd); ! 516: i = poll(10, DTEN, port, 1); ! 517: if(i < 0) ! 518: error("cd not responding"); ! 519: ! 520: sr = status(port); ! 521: if((sr&ST_DSKIN) == 0) ! 522: error("no cd in drive"); ! 523: ! 524: if(sr&ST_ERROR) { ! 525: i = reqsense(port); ! 526: switch(i) { ! 527: case NOT_READY: /* Just media changes */ ! 528: case MEDIA_CHANGED: ! 529: case HARD_RESET: ! 530: case DISC_OUT: ! 531: break; ! 532: default: ! 533: error(etable[i]); /* Real errors */ ! 534: } ! 535: } ! 536: ! 537: getcap(&sbcd); ! 538: ! 539: qunlock(&sbcd); ! 540: poperror(); ! 541: } ! 542: ! 543: static void ! 544: pansbcdio(int port, ulong adr) ! 545: { ! 546: int sr, i, try, errno; ! 547: uchar *p, msf[3], cmd[CMDLEN]; ! 548: ! 549: bin2msf(msf, adr); ! 550: ! 551: retry: ! 552: memset(cmd, 0, sizeof(cmd)); ! 553: cmd[0] = READ; ! 554: cmd[1] = msf[0]; ! 555: cmd[2] = msf[1]; ! 556: cmd[3] = msf[2]; ! 557: cmd[6] = 1; ! 558: issue(port, cmd); ! 559: ! 560: for(try = 0; try < 100; try++) { ! 561: sr = inb(port+STATUS) & (STEN|DTEN); ! 562: switch(sr) { ! 563: case DTEN|STEN: ! 564: tsleep(&sbcd.rendez, return0, 0, TK2MS(1)); ! 565: break; ! 566: ! 567: case 0: ! 568: case STEN: ! 569: outb(port+STATUS, 1); ! 570: p = sbcd.buf; ! 571: for(i = 0; i < sizeof(sbcd.buf); i++) { ! 572: if(inb(port+STATUS) != 0xfd) ! 573: break; ! 574: *p++ = inb(port+DATA); ! 575: } ! 576: outb(port+STATUS,0); ! 577: while((inb(port+STATUS)&(DTEN|STEN)) != DTEN) ! 578: ; ! 579: sr = status(port); ! 580: if(sr & ST_ERROR) { ! 581: errno = reqsense(port); ! 582: error(etable[errno]); ! 583: } ! 584: return; ! 585: ! 586: case DTEN: ! 587: i = status(port); ! 588: errno = reqsense(port); ! 589: print("devsbcd: %s reading block %d\n", etable[errno], adr); ! 590: ! 591: switch(errno) { ! 592: case NOT_READY: ! 593: case MEDIA_CHANGED: ! 594: case HARD_RESET: ! 595: case DISC_OUT: ! 596: error(etable[i]); ! 597: } ! 598: goto retry; ! 599: } ! 600: } ! 601: } ! 602: ! 603: /* ! 604: * mitsumi specific ! 605: */ ! 606: static int ! 607: statmap(int s) ! 608: { ! 609: if(s & MST_DOOR_OPEN) ! 610: return MSTAT_DOOR; /* door is open */ ! 611: if(s & MST_DSK_CHG) ! 612: return MSTAT_CHNG; /* disk was changed */ ! 613: if(s & MST_DSK_TYPE) ! 614: return MSTAT_CDDA; /* not cdrom type disk */ ! 615: if((s & MST_READY) == 0) ! 616: return MSTAT_READY; /* not ready */ ! 617: return 0; ! 618: } ! 619: ! 620: static int ! 621: mitdata(int port, uchar *d, long n) ! 622: { ! 623: int j, f; ! 624: ! 625: for(j=500; j; j--) { ! 626: f = inb(port+1) & (MFL_DATA|MFL_STATUS); ! 627: if(f == (MFL_DATA|MFL_STATUS)) { ! 628: if(j > 400) ! 629: sched(); ! 630: else ! 631: tsleep(&sbcd.rendez, return0, 0, TK2MS(1)); ! 632: continue; ! 633: } ! 634: if(f == MFL_STATUS) { ! 635: *d++ = inb(port+0); ! 636: if(n == 1) ! 637: return 0; ! 638: n--; ! 639: j = 500; ! 640: continue; ! 641: } ! 642: break; ! 643: } ! 644: return MSTAT_ERROR; ! 645: } ! 646: ! 647: static int ! 648: mitstatus(int port, uchar *d, long n) ! 649: { ! 650: int j, f; ! 651: ! 652: for(j=500; j; j--) { ! 653: f = inb(port+1) & (MFL_DATA|MFL_STATUS); ! 654: if(f == (MFL_DATA|MFL_STATUS)) { ! 655: if(j > 400) ! 656: sched(); ! 657: else ! 658: tsleep(&sbcd.rendez, return0, 0, TK2MS(1)); ! 659: continue; ! 660: } ! 661: if(f == MFL_DATA) { ! 662: *d++ = inb(port+0); ! 663: if(n == 1) ! 664: return 0; ! 665: n--; ! 666: j = 500; ! 667: continue; ! 668: } ! 669: break; ! 670: } ! 671: return MSTAT_ERROR; ! 672: } ! 673: ! 674: static void ! 675: mitinitdrive(int port) ! 676: { ! 677: int i, e; ! 678: uchar dat[10]; ! 679: ! 680: qlock(&sbcd); ! 681: if(waserror()) { ! 682: qunlock(&sbcd); ! 683: nexterror(); ! 684: } ! 685: ! 686: sbcd.blocks = 0; ! 687: for(i=0; i<5; i++) { ! 688: outb(port+0, MCMD_GET_DISK_INFO); ! 689: if(mitstatus(port, dat, 9)) ! 690: continue; ! 691: e = statmap(dat[0]); ! 692: if(e) ! 693: continue; ! 694: sbcd.blocks = bcd2bin(dat[3])*4500 + ! 695: bcd2bin(dat[4])*75 + ! 696: bcd2bin(dat[5]) - ! 697: 150; ! 698: cdtab[CDFILE].length = sbcd.blocks*FRAMESIZE; ! 699: ! 700: break; ! 701: } ! 702: if(sbcd.blocks == 0) ! 703: error("cant issue capacity"); ! 704: ! 705: qunlock(&sbcd); ! 706: poperror(); ! 707: } ! 708: ! 709: static void ! 710: del(void) ! 711: { ! 712: int i; ! 713: ! 714: for(i=0; i<10; i++) ! 715: ; ! 716: } ! 717: ! 718: static void ! 719: mitsbcdio(int port, ulong adr) ! 720: { ! 721: int i, e; ! 722: ulong s; ! 723: uchar status, msf[3]; ! 724: ! 725: bin2msf(msf, adr); ! 726: bin2bcd(msf+0); /* convert to BCD */ ! 727: bin2bcd(msf+1); ! 728: bin2bcd(msf+2); ! 729: ! 730: e = 0; ! 731: for(i=0; i<5; i++) { ! 732: ! 733: s = splhi(); ! 734: outb(port+0, MCMD_SET_MODE); ! 735: outb(port+0, 1); /* cooked data */ ! 736: splx(s); ! 737: ! 738: if(mitstatus(port, &status, 1)) ! 739: continue; ! 740: e = statmap(status); ! 741: if(e) ! 742: continue; ! 743: ! 744: s = splhi(); ! 745: outb(port+0, MCMD_PLAY_READ); ! 746: outb(port+0, msf[0]); ! 747: outb(port+0, msf[1]); ! 748: outb(port+0, msf[2]); ! 749: outb(port+0, 0); ! 750: outb(port+0, 0); ! 751: outb(port+0, 1); ! 752: splx(s); ! 753: ! 754: if(mitdata(port, sbcd.buf, FRAMESIZE) == 0) { ! 755: if(mitstatus(port, &status, 1)) ! 756: continue; ! 757: e = statmap(status); ! 758: if(e) ! 759: continue; ! 760: return; ! 761: } ! 762: } ! 763: if(e == 0) ! 764: e = MSTAT_ERROR; ! 765: error(metable[e]); ! 766: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.