/*---------------------------------------------------------------------------- Polling ATAPI driver. Compile with Turbo C, Watcom C, or DJGPP. Chris Giese http://SisAndHappy.com/ChrisGiese/ I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide. If this is not legally possible: I grant any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law. Sources: SFF-8020 specification plus the SCSI docs: SPC (primary commands), SBC (block commands), MMC (multimedia commands) 3 June 2017: - stylistic changes - code to interpret sense values removed from scsi_request_sense() to separate scsi_dump_sense() function - scsi_dump_sense() returns a "recovery" value -- operations won't be retried if won't help 28 June 2015: - stylistic changes - scsi_request_sense() is now being called only in main(); and only when another command fails - added code to automatically find ATAPI drives - added a primitive UI to let the user navigate the CD-ROM, eject the disk, reset the drive, etc. 15 June 2013: - fixed scsi_read_capacity() to display sector count, not LBA of last sector (off-by-one error) 21 October 2012: initial release ----------------------------------------------------------------------------*/ #include /* memset() */ #include /* printf() */ #include /* getch(), inpw() */ #include /* inportb(), outportb(), inportw(), outportw(), delay() */ #if 0 #include #else typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; #endif #define COUNT(N) (sizeof(N) / sizeof((N)[0])) #if defined(__TURBOC__) #define inportw(P) inport(P) #define outportw(P,V) outport(P,V) #elif defined(__WATCOMC__) #define inportb(P) inp(P) #define outportb(P,V) outp(P,V) #define inportw(P) inpw(P) #define outportw(P,V) outpw(P,V) #elif defined(__DJGPP__) /* nothing */ #else #error Sorry, unsupported compiler #endif typedef struct { unsigned ctl_io, cmd_io; unsigned unit : 1; } state_t; /****************************************************************************/ #define BPERL 16 /* bytes displayed per line of the dump */ void dump(const void *data0, unsigned count) { const unsigned char *data = data0; unsigned j, k; while(count != 0) { k = (count < BPERL) ? count : BPERL; for(j = 0; j < k; j++) printf("%02X ", data[j]); printf("\t"); for(j = 0; j < k; j++) putchar((data[j] < ' ') ? '_' : data[j]); printf("\n"); data = &data[k]; count -= k; } } /****************************************************************************/ static unsigned read_be16(const void *buf0) { const unsigned char *buf = buf0; unsigned rv; rv = buf[0]; rv <<= 8; rv |= buf[1]; return rv; } /****************************************************************************/ static uint32_t read_be32(const void *buf0) { const unsigned char *buf = buf0; unsigned long rv; rv = buf[0]; rv <<= 8; rv |= buf[1]; rv <<= 8; rv |= buf[2]; rv <<= 8; rv |= buf[3]; return rv; } /****************************************************************************/ static void write_be16(void *buf0, unsigned val) { unsigned char *buf = buf0; buf[1] = val; val >>= 8; buf[0] = val; } /****************************************************************************/ static void write_be32(void *buf0, uint32_t val) { unsigned char *buf = buf0; buf[3] = val; val >>= 8; buf[2] = val; val >>= 8; buf[1] = val; val >>= 8; buf[0] = val; } /****************************************************************************/ static void insw(unsigned io, void *data0, unsigned word_count) { uint16_t *data = data0; /* read and discard */ if(data0 == NULL) { for(; word_count != 0; word_count--) (void)inportw(io); } else { for(; word_count != 0; word_count--) { *data = inportw(io); data++; } } } /****************************************************************************/ static void outsw(unsigned io, const void *data0, unsigned word_count) { const uint16_t *data = data0; for(; word_count != 0; word_count--) { outportw(io, *data); data++; } } /***************************************************************************** when 'mask' is ANDed with the drive status bits, it should equal 'want' *****************************************************************************/ #define POSITIVE 0x0100 #define NEGATIVE (-1 & ~0xFF) /* 0xFF00 or 0xFFFFFF00 or ? */ static int await_drive(unsigned io, unsigned milliseconds, unsigned mask, unsigned want) { unsigned j, status = 0; /* poll until BSY=0 */ for(j = milliseconds / 10; j != 0; j--) { /* read status register */ status = inportb(io + 7); if((status & 0x80) == 0) break; delay(10); } if(j == 0) { printf("Error in await_drive(): timeout\n"); return 0; /* timeout: return 0 */ } if((status & mask) != want) { printf("Error in await_drive(): wanted status 0x%02X, " "got 0x%02X\n", want & mask, status & mask); return NEGATIVE | status; /* wrong status: return <0 */ } else return POSITIVE | status; /* success: return >0 */ } /****************************************************************************/ static const char *phase_name(unsigned phase) { static const char *pname[] = { "??", "??", "??", "DONE", "BUS RELEASE", "??", "??", "??", "DATA OUT", "CMD OUT", "DATA IN", "(CMD IN ?)" }; /**/ return (phase >= COUNT(pname)) ? "???" : pname[phase]; } /***************************************************************************** ATAPI drives are supposed to respond to the DEVICE RESET command at any time; not just when status.BSY=0. ATA drives do not implement DEVICE RESET. *****************************************************************************/ #define ATAPI_TIMEOUT 10000 static int atapi_reset(state_t *s) { unsigned io; io = s->cmd_io; outportb(io + 6, 0xA0 | (s->unit ? 0x10 : 0x00)); outportb(io + 7, 0x08); /* 0x08 = DEVICE RESET */ return (await_drive(io, ATAPI_TIMEOUT, 0x00, 0x00) <= 0) ? -1 : 0; } /****************************************************************************/ /* Rea- I/!O C/!D DRQ son b1 b0 --- ---- ---- ---- */ #define PHASE_DONE (0 + 3) /* 1 1 */ #define PHASE_DATA_IN (8 + 2) /* 1 0 */ #define PHASE_CMD_OUT (8 + 1) /* 0 1 */ #define PHASE_DATA_OUT (8 + 0) /* 0 0 */ int atapi_read(state_t *s, const void *pkt0, void *buf0, unsigned want) { unsigned io, phase, got, rv, j; const uint8_t *pkt = pkt0; uint8_t *buf = buf0; int status; io = s->cmd_io; /* select drive */ outportb(io + 6, s->unit ? 0xB0 : 0xA0); /* want BSY=0 DRQ=0 */ if((status = await_drive(io, ATAPI_TIMEOUT, 0x88, 0)) <= 0) { printf("Error in atapi_read(): drive did not become ready " "after select\n"); return -1; } outportb(io + 1, 0); /* b0=0: use PIO */ outportb(io + 4, 0); /* max. byte count... */ outportb(io + 5, 0x80); /* ...=32768 */ outportb(io + 7, 0xA0); /* "PACKET" command */ /* should take no longer than 10ms */ if((status = await_drive(io, 10, 0x80, 0)) <= 0) { printf("Error in atapi_read(): drive did not become ready " "after PACKET (status=0x%02X)\n", ~status); return -1; } /* 'phase' = DRQ and... */ phase = status & 0x08; /* ...I/!O and C/!D bits from ATAPI "Interrupt Reason" register (same as ATA "Sector Count") register */ phase |= (inportb(io + 2) & 3); if(phase != PHASE_CMD_OUT) { printf("Error in atapi_read(): wanted phase 0x09 (CMD OUT), " "got phase 0x%02X (%s)\n", phase, phase_name(phase)); return -1; } /* xxx - I am assuming the drive uses 12-byte packets... */ outsw(io + 0, pkt, 6); rv = 0; while(1) { if((status = await_drive(io, ATAPI_TIMEOUT, 0x81, 0)) <= 0) { printf("Error in atapi_read(): ?\n"); return -1; } phase = status & 0x08; phase |= (inportb(io + 2) & 3); if(phase == PHASE_DONE) break; else if(phase == PHASE_DATA_IN) { /* how many bytes does the drive have available? */ got = inportb(io + 5); got <<= 8; got |= inportb(io + 4); /* is it <, =, or > the number of bytes we want? */ if(want != 0 && got != 0) { j = (got < want) ? got : want; insw(io + 0, buf, j / 2); buf += j; want -= j; got -= j; rv += j; } /* if the drive has more data than we want, read and discard */ if(got != 0) insw(io + 0, NULL, got / 2); } else { printf("Error in atapi_read(): drive in strange phase" " 0x%02X (%s)\n", phase, phase_name(phase)); return -1; } } return rv; /* return non-negative value: number of bytes read */ } /***************************************************************************** ((sense >> 24) & 0xFF) = sense key ((sense >> 16) & 0xFF) = additional sense code (ASC) ((sense >> 8) & 0xFF) = additional sense code qualifier (ASCQ) ((sense >> 0) & 0xFF) = 1 if ASCQ valid *****************************************************************************/ #define REC_IGNORE 1 #define REC_RETRY 2 #define REC_RESET 3 #define REC_ABORT 4 static unsigned scsi_dump_sense(uint32_t sense) { static const struct { unsigned char asc, ascq; unsigned recovery; char *msg; } scsi_error[] = { /* ASC, ASCQ, and error message text copy-and-pasted from SFF-8020i */ { 0x00, 0x00, REC_IGNORE, "NO ADDITIONAL SENSE INFORMATION" }, { 0x00, 0x11, REC_IGNORE, "PLAY OPERATION IN PROGRESS" }, { 0x00, 0x12, REC_IGNORE, "PLAY OPERATION PAUSED" }, { 0x00, 0x13, REC_IGNORE, "PLAY OPERATION SUCCESSFULLY COMPLETED" }, { 0x00, 0x14, REC_IGNORE, "PLAY OPERATION STOPPED DUE TO ERROR" }, { 0x00, 0x15, REC_IGNORE, "NO CURRENT AUDIO STATUS TO RETURN" }, { 0x01, 0x00, REC_RESET, "MECHANICAL POSITIONING OR CHANGER ERROR" }, { 0x02, 0x00, REC_RESET, "NO SEEK COMPLETE" }, { 0x04, 0x00, REC_RESET, "LOGICAL DRIVE NOT READY - CAUSE NOT REPORTABLE" }, { 0x04, 0x01, REC_RETRY, "LOGICAL DRIVE NOT READY - IN PROGRESS OF BECOMING READY" }, { 0x04, 0x02, REC_ABORT, "LOGICAL DRIVE NOT READY - INITIALIZING COMMAND REQUIRED" }, { 0x04, 0x03, REC_ABORT, "LOGICAL DRIVE NOT READY - MANUAL INTERVENTION REQUIRED" }, { 0x05, 0x01, REC_RESET, "MEDIA LOAD - EJECT FAILED" }, { 0x06, 0x00, REC_ABORT, "NO REFERENCE POSITION FOUND (disk may be upside-down)" }, { 0x09, 0x00, REC_RESET, "TRACK FOLLOWING ERROR" }, { 0x09, 0x01, REC_RESET, "TRACKING SERVO FAILURE" }, { 0x09, 0x02, REC_RESET, "FOCUS SERVO FAILURE" }, { 0x09, 0x03, REC_RESET, "SPINDLE SERVO FAILURE" }, { 0x11, 0x00, REC_RESET, "UNRECOVERED READ ERROR" }, { 0x11, 0x06, REC_RESET, "CIRC UNRECOVERED ERROR" }, { 0x15, 0x00, REC_RESET, "RANDOM POSITIONING ERROR" }, { 0x15, 0x01, REC_RESET, "MECHANICAL POSITIONING OR CHANGER ERROR" }, { 0x15, 0x02, REC_RESET, "POSITIONING ERROR DETECTED BY READ OF MEDIUM" }, { 0x17, 0x00, REC_IGNORE, "RECOVERED DATA WITH NO ERROR CORRECTION APPLIED" }, { 0x17, 0x01, REC_IGNORE, "RECOVERED DATA WITH RETRIES" }, { 0x17, 0x02, REC_IGNORE, "RECOVERED DATA WITH POSITIVE HEAD OFFSET" }, { 0x17, 0x03, REC_IGNORE, "RECOVERED DATA WITH NEGATIVE HEAD OFFSET" }, { 0x17, 0x04, REC_IGNORE, "RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED" }, { 0x17, 0x05, REC_IGNORE, "RECOVERED DATA USING PREVIOUS SECTOR ID" }, { 0x18, 0x00, REC_IGNORE, "RECOVERED DATA WITH ERROR CORRECTION APPLIED" }, { 0x18, 0x01, REC_IGNORE, "RECOVERED DATA WITH ERROR CORRECTION & RETRIES APPLIED" }, { 0x18, 0x02, REC_IGNORE, "RECOVERED DATA - THE DATA WAS AUTO-REALLOCATED" }, { 0x18, 0x03, REC_IGNORE, "RECOVERED DATA WITH CIRC" }, { 0x18, 0x04, REC_IGNORE, "RECOVERED DATA WITH L-EC" }, { 0x1A, 0x00, REC_ABORT, "PARAMETER LIST LENGTH ERROR" }, { 0x20, 0x00, REC_ABORT, "INVALID COMMAND OPERATION CODE" }, { 0x21, 0x00, REC_ABORT, "LOGICAL BLOCK ADDRESS OUT OF RANGE" }, { 0x24, 0x00, REC_ABORT, "INVALID FIELD IN COMMAND PACKET" }, { 0x26, 0x00, REC_ABORT, "INVALID FIELD IN PARAMETER LIST" }, { 0x26, 0x01, REC_ABORT, "PARAMETER NOT SUPPORTED" }, { 0x26, 0x02, REC_ABORT, "PARAMETER VALUE INVALID" }, { 0x28, 0x00, REC_RETRY, "NOT READY TO READY TRANSITION, MEDIUM MAY HAVE CHANGED" }, { 0x29, 0x00, REC_RETRY, "POWER ON, RESET OR BUS DEVICE RESET OCCURRED" }, { 0x2A, 0x00, REC_IGNORE, "PARAMETERS CHANGED" }, { 0x2A, 0x01, REC_IGNORE, "MODE PARAMETERS CHANGED" }, { 0x30, 0x00, REC_ABORT, "INCOMPATIBLE MEDIUM INSTALLED" }, { 0x30, 0x01, REC_ABORT, "CANNOT READ MEDIUM - UNKNOWN FORMAT" }, { 0x30, 0x02, REC_ABORT, "CANNOT READ MEDIUM - INCOMPATIBLE FORMAT" }, { 0x39, 0x00, REC_ABORT, "SAVING PARAMETERS NOT SUPPORTED" }, { 0x3A, 0x00, REC_ABORT, "MEDIUM NOT PRESENT" }, { 0x3F, 0x00, REC_IGNORE, "ATAPI CD-ROM DRIVE OPERATING CONDITIONS HAVE CHANGED" }, { 0x3F, 0x01, REC_IGNORE, "MICROCODE HAS BEEN CHANGED" }, { 0x40, 0x00, REC_IGNORE, "DIAGNOSTIC FAILURE ON COMPONENT #ascq" }, { 0x44, 0x00, REC_RESET, "INTERNAL ATAPI CD-ROM DRIVE FAILURE" }, { 0x4E, 0x00, REC_ABORT, "OVERLAPPED COMMANDS ATTEMPTED" }, { 0x53, 0x00, REC_RESET, "MEDIA LOAD OR EJECT FAILED" }, { 0x53, 0x02, REC_ABORT, "MEDIUM REMOVAL PREVENTED" }, { 0x57, 0x00, REC_RESET, "UNABLE TO RECOVER TABLE OF CONTENTS" }, { 0x5A, 0x00, REC_IGNORE, "OPERATOR REQUEST OR STATE CHANGE INPUT (UNSPECIFIED)" }, { 0x5A, 0x01, REC_IGNORE, "OPERATOR MEDIUM REMOVAL REQUEST" }, { 0x63, 0x00, REC_ABORT, "END OF USER AREA ENCOUNTERED ON THIS TRACK" }, { 0x64, 0x00, REC_ABORT, "ILLEGAL MODE FOR THIS TRACK" }, { 0xB9, 0x00, REC_IGNORE, "PLAY OPERATION ABORTED" }, { 0xBF, 0x00, REC_IGNORE, "LOSS OF STREAMING" } /* prostate trouble? */ }; static const char *sense_key[] = { "NO SENSE", "RECOVERED ERROR", "NOT READY", "MEDIUM ERROR", "HARDWARE ERROR", "ILLEGAL REQUEST", "UNIT ATTENTION", "DATA PROTECT", "BLANK CHECK", "vendor specific", "COPY ABORTED", "ABORTED COMMAND", "obsolete", "VOLUME OVERFLOW", "MISCOMPARE", "reserved" }; /**/ uint8_t ascq_valid, ascq, asc, key; unsigned j; ascq_valid = (sense & 0x01) ? 1 : 0; sense >>= 8; ascq = (uint8_t)(sense & 0xFF); sense >>= 8; asc = (uint8_t)(sense & 0xFF); sense >>= 8; key = (uint8_t)sense; /* display sense key value and message */ printf("scsi_dump_sense(): sense key=%u", key); if(key < COUNT(sense_key)) printf(" (%s)", sense_key[key]); /* display Additional Sense Code value */ printf(",\n\tASC=%u", asc); /* find message corresponding to ASC (if any) and display it */ for(j = 0; j < COUNT(scsi_error); j++) { if(scsi_error[j].asc == asc && (asc == 0x40 || scsi_error[j].ascq == ascq)) break; } if(j < COUNT(scsi_error)) { printf(" (%s)", scsi_error[j].msg); j = scsi_error[j].recovery; } else { printf(" (?)"); j = REC_IGNORE; } /* display Additional Sense Code Qualifier value, if valid */ if(ascq_valid) printf(", ASCQ=%u", ascq); printf("\n"); /* return recovery value */ return j; } /****************************************************************************/ int scsi_request_sense(state_t *s, uint32_t *sense_ptr) { unsigned char pkt[16], buf[14]; uint32_t sense; unsigned len; int i; printf("\n*** REQUEST SENSE ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x03; /* REQUEST SENSE */ pkt[4] = sizeof(buf); if((i = atapi_read(s, pkt, buf, sizeof(buf))) < 0) { printf("Error: command failed\n"); return i; } sense = buf[2] & 0x0F; /* sense key */ len = 8 + buf[7]; if(len < 13) sense <<= 24; else { sense <<= 8; sense |= buf[12]; /* ASC */ if(len < 14) sense <<= 16; /* ASCQ=0, ASCQ valid=0 */ else { sense <<= 8; sense |= buf[13]; /* ASCQ */ sense <<= 8; sense |= 0x01; /* ASCQ valid */ } } if(sense_ptr != NULL) *sense_ptr = sense; return 0; } /***************************************************************************** peripheral device type, vendor, product, and revision are also present in the data returned by IDENTIFY PACKET DEVICE *****************************************************************************/ int scsi_inquiry(state_t *s) { char pkt[12], buf[96]; int i; printf("\n*** INQUIRY ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x12; /* INQUIRY */ pkt[4] = sizeof(buf); if((i = atapi_read(s, pkt, buf, sizeof(buf))) < 0) { printf("Error: command failed\n"); return i; } dump(buf, 32); /* 5=CD/DVD */ printf("peripheral device type=%u\n", buf[0] & 0x1F); printf("Version byte=0x%02X (ISO=%u, ECMA=%u, ANSI=%u)\n", buf[2], buf[2] >> 6, (buf[2] >> 3) & 7, buf[2] & 7); /* top 4 bits of buf[3] are redefined in SPC-3. RDF=2 for SPC-3. Presumably, RDF=0 for SPC and RDF=1 for SPC-2. */ printf("ATAPI version=%u, response data format=%u\n", buf[3] >> 4, buf[3] & 15); printf("Vendor '%8.8s', product '%16.16s', revision '%4.4s'\n", &buf[8], &buf[16], &buf[32]); return 0; } /***************************************************************************** I am puzzled. Why is the sector size 2352 bytes for data CDs? *****************************************************************************/ int scsi_read_capacity(state_t *s) { char pkt[12], buf[8]; int i; printf("\n*** READ [CD-ROM] CAPACITY ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x25; /* READ CAPACITY */ if((i = atapi_read(s, pkt, buf, sizeof(buf))) < 0) { printf("Error: command failed\n"); return i; } printf("Drive has %lu sectors of %lu bytes each", read_be32(&buf[0]), read_be32(&buf[4])); #if defined(__GNUC__) { unsigned long long j; /* 64-bit */ j = (unsigned long long)(read_be32(&buf[0])) * read_be32(&buf[4]) / 1024 / 1024; printf(" (%llu MB)", j); } #endif printf("\n"); return 0; } /****************************************************************************/ int scsi_load_eject(state_t *s, int eject) { char pkt[12]; printf("\n*** START STOP UNIT ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x1B; /* START STOP UNIT */ pkt[4] = (eject ? 0x02 : 0x03); return atapi_read(s, pkt, NULL, 0); } /***************************************************************************** I _thought_ this worked only for audio CDs... *****************************************************************************/ #define MAX_TRACKS 20 int scsi_read_toc(state_t *state) /* TOC=Table Of Contents */ { unsigned char pkt[12], buf[4 + 8 * MAX_TRACKS]; unsigned len, k, m, s, f; unsigned long lba, j; int i; printf("\n*** READ TOC[/PMA/ATIP] ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x43; /* READ TOC */ /* I need to subtract block addresses below -- that's clutzy in MSF format. (Alas, the LBA-to-MSF conversion isn't all that elegant either...) pkt[1] = 0x02; */ write_be16(&pkt[7], sizeof(buf)); if((i = atapi_read(state, pkt, buf, sizeof(buf))) < 0) { printf("Error: command failed\n"); return i; } len = 2 + read_be16(&buf[0]); printf( "track start time end\n" "----- -------- -------- --------\n"); lba = 0; for(k = 4; k < len; k += 8) { printf("%5u ", buf[k + 2]); /* each 2352-byte sector is one "frame" (1/75th second) of audio (44100 samples per second, 2 bytes (=16 bits) per sample, stereo) */ f = (unsigned)(lba % 75); s = (unsigned)((lba / 75) % 60); m = (unsigned)(lba / 75 / 60); printf("%2u:%02u:%02u ", m, s, f); j = read_be32(&buf[k + 4]) - lba; f = (unsigned)(j % 75); s = (unsigned)((j / 75) % 60); m = (unsigned)(j / 75 / 60); printf("%2u:%02u:%02u ", m, s, f); lba = read_be32(&buf[k + 4]); f = (unsigned)(lba % 75); s = (unsigned)((lba / 75) % 60); m = (unsigned)(lba / 75 / 60); printf("%2u:%02u:%02u\n", m, s, f); } return 0; } /****************************************************************************/ int scsi_mode_sense42(state_t *s) { static char *loader[] = { "caddy", "tray", "pop-up", "?", "changer", "changer w/ cartridge", "?", "?" }; static char *media[] = { /* 0 */ "unknown", "120mm CD-ROM data", "120mm CD-ROM audio", "120mm CD-ROM data+audio", "120mm Photo CD-ROM", /* 5 */ "80mm CD-ROM data", "80mm CD-ROM audio", "80mm CD-ROM data+audio", "80mm Photo CD-ROM", /* 9 */ "?", "?", "?", "?", "?", "?", "?", /* 16 */ "CD-R; unknown size", "120mm CD-R data", "120mm CD-R audio", "120mm CD-R data+audio", "120mm Photo CD-R", /* 21 */ "80mm CD-R data", "80mm CD-R audio", "80mm CD-R data+audio", "80mm Photo CD-R", /* 25 */ "?", "?", "?", "?", "?", "?", "?", /* 32 */ "CD-E; unknown size", "120mm CD-E data", "120mm CD-E audio", "120mm CD-E data+audio", "120mm Photo CD-E", /* 37 */ "80mm CD-E data", "80mm CD-E audio", "80mm CD-E data+audio", "80mm Photo CD-E", /* 41 */ "?", "?", "?", "?", "?", "?", "?", /* 48 */ "unknown" }; /**/ unsigned char pkt[12], buf[96], *ptr; unsigned j, tot_len, len = 0; int i; printf("\n*** MODE SENSE (page=42) ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x5A; /* MODE SENSE */ /* b7-6=0 for current values: */ pkt[2] = 42; /* CD-ROM capabilities and mechanical status */ write_be16(&pkt[7], sizeof(buf)); if((i = atapi_read(s, pkt, buf, sizeof(buf))) < 0) { printf("Error: command failed\n"); return i; } tot_len = read_be16(&buf[0]); dump(buf, tot_len); printf("tot_len=%u, medium type=%u ", tot_len, buf[2]); if(buf[2] == 0x70) printf("(none)"); else if(buf[2] == 0x71) printf("(door open or no caddy)"); else if(buf[2] == 0x72) printf("(media error)"); else if(buf[2] < COUNT(media)) printf("(%s)", media[buf[2]]); else printf("(?)"); printf("\n"); ptr = &buf[8]; for(; tot_len > 8; tot_len -= len) { len = ptr[1]; printf("code %u, len=%u\n", ptr[0], len); if(ptr[0] == 42) goto OK; ptr += len; } printf("Error: page 42 not found\n"); return -1; OK: /* DVD stuff is in MMC-3 but not in SFF-8020 */ #define YN(byte,bit) ((ptr[byte] & bit) ? "y " : "n ") printf( " DVD-RAM DVD-R DVD-ROM Method2 CD-RW CD-R\n" " ------- ------- ------- ------- ------- -------\n" "read: %s %s %s %s %s %s\n" "write: %s %s n n %s %s\n", YN(2,0x20), YN(2,0x10), YN(2,0x08), YN(2,0x04), YN(2,0x02), YN(2,0x01), YN(3,0x20), YN(3,0x10), YN(3,0x02), YN(3,0x01)); printf( "multi-session :%s ""Mode 2 Form 2 :%s\n" "Mode 2 Form 1 :%s ""composite A/V output:%s\n" /* I think "audio play" means these commands are supported: PLAY AUDIO, PLAY AUDIO MSF, PAUSE/RESUME, SCAN, STOP PLAY/SCAN. Bar code is in MMC-3 but not in SFF-8020 */ "audio play :%s ""read bar code :%s\n" "UPC :%s ""restart. READ CD-DA :%s\n" /* "READ CD-DA works" means sector type 1 (CD-DA) can be used with the READ CD command (j.e. it means the drive can "rip" audio) */ "READ CD-DA works :%s ""PREVENT/ALLOW works :%s\n" "drive locked :%s\n", YN(4,0x40), YN(4,0x20), YN(4,0x10), YN(4,0x02), YN(4,0x01), YN(5,0x80), YN(5,0x40), YN(5,0x02), YN(5,0x01), YN(6,0x01), YN(6,0x02)); j = buf[6] >> 5; printf("Loading mechanism type %u (%s)\n", j, loader[j]); return 0; } /***************************************************************************** this is not in SFF-8020 (j.e. old CD-ROM drives don't support it) *****************************************************************************/ int scsi_get_config(state_t *s) { char pkt[16], buf[64]; unsigned len, j, p; int i; printf("\n*** GET CONFIGURATION ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x46; /* GET CONFIGURATION */ /* return descriptor(s) for: 0=all features, 1=current features, 2=only one feature */ pkt[1] = 2; /* write_be16(&pkt[2], 0); starting feature # (0=profile list) */ write_be16(&pkt[7], sizeof(buf)); if((i = atapi_read(s, pkt, buf, sizeof(buf))) < 0) { printf("Error: command failed\n"); return i; } len = 4 + buf[3]; /* 2=removable disk, 8=CD-ROM, 9=CD-R, 10=CD-RW, 16=DVD-ROM, 17=DVD-R Sequential, 18=DVD-RAM, 19=DVD-RW Restricted Overwrite, 20=DVD-RW Sequential, 26=DVD+RW (see SCSI MMC specification for profile-feature matrix) */ printf("Supported profiles: "); for(j = 4; j < len; j += 4) { p = read_be16(&buf[j]); printf("%4u ", p); } printf("\n"); return 0; } /***************************************************************************** this works for data CDs only *****************************************************************************/ int scsi_read_sectors(state_t *s, void *buf, unsigned num_sects, unsigned long lba) { unsigned count; char pkt[12]; int i; printf("\n*** READ(10) ***\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x28; /* READ(10) */ write_be32(&pkt[2], lba); write_be16(&pkt[7], num_sects); // xxx - 16-bit compilers need a loop here in case num_sects * 2048 >= 64K count = num_sects * 2048; if((i = atapi_read(s, pkt, buf, count)) != count) { printf("Error: command failed\n"); return i; } return 0; } /****************************************************************************/ static int detect_atapi_drive(state_t *s) { unsigned status, sig; printf("detect_atapi_drive(): %s drive on interface 0x%X, unit %u: ", s->cmd_io, s->unit); /* reset both units on this interface (selects drive #0) */ if(s->unit == 0) { printf("(1)\n"); /* b3=1, b2=SRST, b1=nIEN (enables interrupts when=0), b0=0 */ outportb(s->ctl_io, 0x0C); /* "Some devices may take up to 2 ms to set BSY when coming out of Sleep mode. The host shall not begin polling the Status register until at least 2 ms after the SRST bit has been set to one." -- ATA-4, section 9.3 */ delay(5); outportb(s->ctl_io, 0x08); delay(5); } /* select drive #1 */ else outportb(s->cmd_io + 6, 0xA0 | 0x10); /* Wait up to 1 second for drive to become ready. NOTE: Do NOT check for DRDY=1 here. */ if(await_drive(s->cmd_io, 1000, 0, 0) <= 0) { NOPE: printf("nothing there\n"); return 0; } /* read drive signature from cylinder registers */ sig = inportb(s->cmd_io + 5); sig <<= 8; sig |= inportb(s->cmd_io + 4); /* (parallel interface) ATA drive */ if(sig == 0) { /* test if ATA device has DRDY=1 (prevents false detection of non-existent slave drive) */ status = inportb(s->cmd_io + 7); if((status & 0x40) == 0) goto NOPE; printf("ATA drive\n"); return 0; } /* (parallel interface) ATAPI */ else if(sig == 0xEB14) { printf("ATAPI drive\n"); return 1; } /* SATA? SATAPI? */ else { printf("unknown drive signature 0x%X\n", sig); return 0; } } /****************************************************************************/ int main(void) { static char buf[2048]; static char loaded = 1; /**/ uint32_t sense, lba; unsigned tries, j; state_t s; int key; /* Find an ATAPI drive. A 'real' ATA/ATAPI driver should get these I/O addresses from PCI, and use these addresses only if PCI is not present. primary interface: cmd_io=0x1F0, ctl_io=0x3F6 secondary interface: cmd_io=0x170, ctl_io=0x376 */ for(s.cmd_io = 0x1F0; s.cmd_io >= 0x170; s.cmd_io -= 0x80) { s.ctl_io = s.cmd_io + 0x206; /* drive on this interface: 0 or 1? */ s.unit = 0; do { if(detect_atapi_drive(&s)) goto OK; s.unit++; } while(s.unit != 0); } printf("No ATAPI drives detected\n"); return 1; OK: if(atapi_reset(&s)) { printf("Error resetting ATAPI device\n"); return 1; } for(tries = 3; tries != 0; tries--) { if(!scsi_inquiry(&s)) break; if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } /* how many sectors of data on this disc? */ for(tries = 3; tries != 0; tries--) { if(!scsi_read_capacity(&s)) break; if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } /* read Table Of Contents (mainly for audio CD) */ for(tries = 3; tries != 0; tries--) { if(!scsi_read_toc(&s)) break; if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } /* page 42 indicates if drive supports CD-R, CD-RW, DVD, analog audio play, "ripping" audio CDs (with READ CD command), etc. */ for(tries = 3; tries != 0; tries--) { if(!scsi_mode_sense42(&s)) break; if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } /* new drives only */ for(tries = 3; tries != 0; tries--) { if(!scsi_get_config(&s)) break; if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } printf("\nUse left and right arrow keys to change sector number,\n" "R to reset drive, E to load/eject, Esc to quit\n"); lba = 16; while(1) // if(loaded)... { for(tries = 3 * loaded; tries != 0; tries--) { if(!scsi_read_sectors(&s, buf, 1, lba)) { printf("Partial dump of disc sector %lu\n", lba); dump(buf, 64); break; } if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } if((key = getch()) == 0) key = 0x100 | getch(); if(key == 27) break; else if(key == 'r' || key == 'R') (void)atapi_reset(&s); else if(key == 0x14B) /* left arrow */ { if(lba != 0) lba--; } else if(key == 0x14D) /* right arrow */ lba++; else if(key == 'e' || key == 'E') { for(tries = 3; tries != 0; tries--) { if(!scsi_load_eject(&s, loaded)) break; if(!scsi_request_sense(&s, &sense)) { j = scsi_dump_sense(sense); if(j == REC_RETRY) /* continue */; else if(j == REC_RESET) (void)atapi_reset(&s); else /* REC_IGNORE, REC_ABORT */ break; } } loaded = !loaded; } } return 0; }