//Changes vs. your version (hard drive / ATA path only, CD/DVD code untouched): // * Hard drive reads/writes now use plain PIO READ SECTORS (0x20/0x24) and // WRITE SECTORS (0x30/0x34) instead of READ/WRITE MULTIPLE. No dependency // on SET MULTIPLE MODE any more (that setting is also lost on reset). // * Data port is accessed 16 bits at a time (the native width of the ATA // data register) instead of 32-bit REP IN/OUT. // * Proper handshake: select drive FIRST, wait BSY=0 & DRDY=1, then write // the task file, then the command. 400ns delay after every command and // after every sector so a stale DRQ is never trusted. // * Every sector waits BSY=0 then checks ERR/DF/DRQ. After the last written // sector we wait for completion and then issue FLUSH CACHE. // * The block device lock is released BEFORE throw(). A leaked lock was // what froze the whole system after the first failed write. // * No more timeout=0 (infinite) waits on the hard drive path. // * Interrupts are disabled on the drive (nIEN), TempleOS polls anyway. // * Disk size comes from IDENTIFY DEVICE. READ NATIVE MAX / SET MAX are no // longer sent to hard drives (SET MAX can permanently change the size // the drive reports). // * base1 is masked to the start of the control block (0xF0C2->0xF0C0, // 0x3F6->0x3F4) so either form works. ATAR1_CTRL (offset 2) is added // on top of it by the driver. // * Errors print status/error register and LBA so problems are visible. // * CD/DVD path: identical to upstream TempleOS, except that a CD read // that fails every retry now throws instead of returning garbage. // * FLUSH CACHE is sent once per write request, and only to hard drives. // * CD/DVD burning: ATAPIWriteBlks() no longer waits forever for DRQ. // If the drive rejects a command (ERR set, DRQ never comes) it now // prints Stat/Err plus REQUEST SENSE (Key/ASC/ASCQ) and returns FALSE. // Data is sent 16 bits at a time and the interrupt reason is checked. #define ATAX_READ_SECTS 0x20 #define ATAX_READ_SECTS_EXT 0x24 #define ATAX_WRITE_SECTS 0x30 #define ATAX_WRITE_SECTS_EXT 0x34 #define ATAX_FLUSH_CACHE 0xE7 #define ATAX_CTRL_NIEN 0x0A //bit 3 (legacy, keep 1) + nIEN (no IRQs) #define ATAX_CTRL_SRST 0x0E //same + SRST #define ATAX_STAT_DF 0x20 //Device fault #define ATAX_CHUNK 64 //Sectors per command #define ATAX_RETRIES 5 #define ATAX_TIMEOUT 10.0 //Seconds U0 ATABlkSel(CBlkDev *bd,I64 blk,I64 cnt) {//Only used for CD/DVD now (and SET MAX on ATAPI). if (bd->type!=BDT_ATAPI && bd->base1) OutU8(bd->base1+ATAR1_CTRL,ATAX_CTRL_NIEN); if (bd->flags & BDF_EXT_SIZE) { //48 Bit LBA? OutU8(bd->base0+ATAR0_NSECT,cnt.u8[1]); OutU8(bd->base0+ATAR0_SECT,blk.u8[3]); OutU8(bd->base0+ATAR0_LCYL,blk.u8[4]); OutU8(bd->base0+ATAR0_HCYL,blk.u8[5]); OutU8(bd->base0+ATAR0_NSECT,cnt); OutU8(bd->base0+ATAR0_SECT,blk); OutU8(bd->base0+ATAR0_LCYL,blk.u8[1]); OutU8(bd->base0+ATAR0_HCYL,blk.u8[2]); OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); } else { //28 Bit LBA OutU8(bd->base0+ATAR0_NSECT,cnt); OutU8(bd->base0+ATAR0_SECT,blk); OutU8(bd->base0+ATAR0_LCYL,blk.u8[1]); OutU8(bd->base0+ATAR0_HCYL,blk.u8[2]); OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4|blk.u8[3]); } } Bool ATAWaitNotBUSY(CBlkDev *bd,F64 timeout) {//timeout is an absolute tS value, 0 means wait forever. I64 i; do { for (i=0;i<3;i++) if (!(InU8(bd->base0+ATAR0_STAT)&ATAS_BSY)) return TRUE; Yield; } while (!(0<timeout<tS)); return FALSE; } Bool ATAWaitDRQ(CBlkDev *bd,F64 timeout) { I64 i; do { for (i=0;i<3;i++) if (InU8(bd->base0+ATAR0_STAT)&ATAS_DRQ) return TRUE; Yield; } while (!(0<timeout<tS)); return FALSE; } Bool ATANop(CBlkDev *bd,F64 timeout) { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_FEAT,0); OutU8(bd->base0+ATAR0_CMD,ATA_NOP); return ATAWaitNotBUSY(bd,timeout); } U0 ATACmd(CBlkDev *bd,U8 cmd) { OutU8(bd->base0+ATAR0_FEAT,0); OutU8(bd->base0+ATAR0_CMD,cmd); bd->last_time=tS; PortNop; } //---------------------------------------------------------------------- //Safe polled PIO helpers for hard drives. //---------------------------------------------------------------------- U8 ATAXAltStat(CBlkDev *bd) {//Alternate status has no side effects. Falls back to status reg. if (bd->base1) return InU8(bd->base1+ATAR1_CTRL); return InU8(bd->base0+ATAR0_STAT); } U0 ATAX400ns(CBlkDev *bd) {//Every port read takes well over 100ns, 5 of them are > 400ns. I64 i; for (i=0;i<5;i++) ATAXAltStat(bd); } U0 ATAXCtrl(CBlkDev *bd,U8 val) { if (bd->base1) OutU8(bd->base1+ATAR1_CTRL,val); } I64 ATAXWaitNotBusy(CBlkDev *bd,F64 timeout) {//timeout in seconds from now. Returns status, or -1 on timeout. F64 deadline=tS+timeout; I64 s; while (TRUE) { s=InU8(bd->base0+ATAR0_STAT); if (s!=0xFF && !(s&ATAS_BSY)) return s; if (tS>deadline) return -1; Yield; } } U0 ATAChannelReset(CBlkDev *bd) {//Software reset (SRST) of the channel, then wait until the drive is back. F64 t; "ATA: Resetting channel Base0:0x%04X Base1:0x%04X\n",bd->base0,bd->base1; if (bd->base1) { OutU8(bd->base1+ATAR1_CTRL,ATAX_CTRL_SRST); t=tS+0.005; while (tS<t) Yield; OutU8(bd->base1+ATAR1_CTRL,ATAX_CTRL_NIEN); t=tS+0.01; while (tS<t) Yield; } if (ATAXWaitNotBusy(bd,31.0)<0) PrintErr("ATA: Drive still busy after reset.\n"); } U0 ATAXReportErr(CBlkDev *bd,U8 *op,I64 blk) { I64 s=InU8(bd->base0+ATAR0_STAT),e=InU8(bd->base0+ATAR0_FEAT); PrintErr("ATA %s failed. LBA:0x%X Stat:0x%02X Err:0x%02X\n",op,blk,s,e); } Bool ATAXSelect(CBlkDev *bd,I64 lba_nibble) {//Select drive, then wait for BSY=0 and DRDY=1. F64 deadline; I64 s; if (ATAXWaitNotBusy(bd,ATAX_TIMEOUT)<0) return FALSE; OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4|lba_nibble&0xF); ATAX400ns(bd); deadline=tS+ATAX_TIMEOUT; while (TRUE) { s=InU8(bd->base0+ATAR0_STAT); if (s!=0xFF && !(s&ATAS_BSY) && s&ATAS_DRDY) return TRUE; if (tS>deadline) return FALSE; Yield; } } Bool ATAXIssue(CBlkDev *bd,I64 blk,I64 cnt,I64 cmd28,I64 cmd48) {//cnt must be 1..ATAX_CHUNK. Uses 28-bit commands whenever possible. Bool ext=FALSE; if (blk+cnt>=0x0FFFFFFF) { if (!(bd->flags&BDF_EXT_SIZE)) return FALSE; ext=TRUE; } ATAXCtrl(bd,ATAX_CTRL_NIEN); if (ext) { if (!ATAXSelect(bd,0)) return FALSE; OutU8(bd->base0+ATAR0_NSECT,cnt.u8[1]); OutU8(bd->base0+ATAR0_SECT,blk.u8[3]); OutU8(bd->base0+ATAR0_LCYL,blk.u8[4]); OutU8(bd->base0+ATAR0_HCYL,blk.u8[5]); OutU8(bd->base0+ATAR0_NSECT,cnt.u8[0]); OutU8(bd->base0+ATAR0_SECT,blk.u8[0]); OutU8(bd->base0+ATAR0_LCYL,blk.u8[1]); OutU8(bd->base0+ATAR0_HCYL,blk.u8[2]); OutU8(bd->base0+ATAR0_CMD,cmd48); } else { if (!ATAXSelect(bd,blk.u8[3])) return FALSE; OutU8(bd->base0+ATAR0_FEAT,0); OutU8(bd->base0+ATAR0_NSECT,cnt.u8[0]); OutU8(bd->base0+ATAR0_SECT,blk.u8[0]); OutU8(bd->base0+ATAR0_LCYL,blk.u8[1]); OutU8(bd->base0+ATAR0_HCYL,blk.u8[2]); OutU8(bd->base0+ATAR0_CMD,cmd28); } ATAX400ns(bd); bd->last_time=tS; return TRUE; } I64 ATAXWaitData(CBlkDev *bd,F64 timeout) {//Wait for BSY=0. Returns 1 if DRQ (data ready), 0 on ERR/DF, -1 timeout. F64 deadline=tS+timeout; I64 s; while (TRUE) { s=InU8(bd->base0+ATAR0_STAT); if (s!=0xFF && !(s&ATAS_BSY)) { if (s&(ATAS_ERR|ATAX_STAT_DF)) return 0; if (s&ATAS_DRQ) return 1; } if (tS>deadline) return -1; Yield; } } Bool ATAXReadChunk(CBlkDev *bd,U8 *buf,I64 blk,I64 cnt) { I64 i,j,r; U16 *w=buf; bd->flags&=~BDF_LAST_WAS_WRITE; if (!ATAXIssue(bd,blk,cnt,ATAX_READ_SECTS,ATAX_READ_SECTS_EXT)) { ATAXReportErr(bd,"Read (select)",blk); return FALSE; } for (i=0;i<cnt;i++) { r=ATAXWaitData(bd,ATAX_TIMEOUT); if (r<=0) { ATAXReportErr(bd,"Read",blk+i); return FALSE; } for (j=0;j<256;j++) *w++=InU16(bd->base0+ATAR0_DATA); ATAX400ns(bd); } r=ATAXWaitNotBusy(bd,ATAX_TIMEOUT); if (r<0 || r&(ATAS_ERR|ATAX_STAT_DF)) { ATAXReportErr(bd,"Read (end)",blk); return FALSE; } return TRUE; } Bool ATAXWriteChunk(CBlkDev *bd,U8 *buf,I64 blk,I64 cnt) { I64 i,j,r; U16 *w=buf; bd->flags|=BDF_LAST_WAS_WRITE; if (!ATAXIssue(bd,blk,cnt,ATAX_WRITE_SECTS,ATAX_WRITE_SECTS_EXT)) { ATAXReportErr(bd,"Write (select)",blk); return FALSE; } for (i=0;i<cnt;i++) { r=ATAXWaitData(bd,ATAX_TIMEOUT); if (r<=0) { ATAXReportErr(bd,"Write",blk+i); return FALSE; } for (j=0;j<256;j++) OutU16(bd->base0+ATAR0_DATA,*w++); ATAX400ns(bd); } //Drive commits the last sector, give it plenty of time. r=ATAXWaitNotBusy(bd,3*ATAX_TIMEOUT); if (r<0 || r&(ATAS_ERR|ATAX_STAT_DF|ATAS_DRQ)) { ATAXReportErr(bd,"Write (end)",blk); return FALSE; } return TRUE; } U0 ATAXFlush(CBlkDev *bd) {//Make sure data really is on the platter, not only in the drive cache. ATAXCtrl(bd,ATAX_CTRL_NIEN); if (!ATAXSelect(bd,0)) { ATAChannelReset(bd); return; } OutU8(bd->base0+ATAR0_FEAT,0); OutU8(bd->base0+ATAR0_CMD,ATAX_FLUSH_CACHE); ATAX400ns(bd); if (ATAXWaitNotBusy(bd,60.0)<0) ATAChannelReset(bd); //ERR here only means a very old drive without FLUSH CACHE: ignored. } //---------------------------------------------------------------------- Bool ATAGetRes(CBlkDev *bd,F64 timeout,U8 *buf,I64 cnt, I64 _avail,Bool one_read) { I64 avail,overflow; bd->flags&=~BDF_LAST_WAS_WRITE; MemSet(buf,0,cnt); while (cnt>0) { if (!ATAWaitDRQ(bd,timeout)) return FALSE; if (_avail) avail=_avail; else avail=InU8(bd->base0+ATAR0_HCYL)<<8+InU8(bd->base0+ATAR0_LCYL); if (avail) { if (avail>cnt) { overflow=avail-cnt; avail=cnt; } else overflow=0; //Back to the original upstream transfer for the CD/DVD path (the hard //drive no longer uses this function for sector data). if (avail&2) RepInU16(buf,avail>>1,bd->base0+ATAR0_DATA); else RepInU32(buf,avail>>2,bd->base0+ATAR0_DATA); cnt-=avail; buf+=avail; while (overflow>0) { InU16(bd->base0+ATAR0_DATA); overflow-=2; if (0<timeout<tS) return FALSE; } if (one_read) break; } else Yield; } return ATAWaitNotBUSY(bd,timeout); } Bool ATAPIWritePktWord(CBlkDev *bd,F64 timeout,...) { I64 i; for (i=0;i<argc;i++) { if (!ATAWaitDRQ(bd,timeout)) return FALSE; OutU16(bd->base0+ATAR0_DATA,EndianU16(argv[i])); bd->last_time=tS; } return TRUE; } Bool ATAPISetMaxSpeed(CBlkDev *bd) { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,0); OutU8(bd->base0+ATAR0_HCYL,0); ATACmd(bd,ATA_PACKET); ATAPIWritePktWord(bd,0,0xBB00,0xFFFF,0xFFFF,0,0,0); return ATAWaitNotBUSY(bd,0); } Bool ATAPISeek(CBlkDev *bd,I64 native_blk) { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,0); OutU8(bd->base0+ATAR0_HCYL,0); ATACmd(bd,ATA_PACKET); ATAPIWritePktWord(bd,0,0x2B00,native_blk>>16,native_blk,0,0,0); return ATAWaitNotBUSY(bd,0); } Bool ATAPIStartStop(CBlkDev *bd,F64 timeout,Bool start) { I64 i; if (start) i=0x100; else i=0; if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); ATACmd(bd,ATA_PACKET); //Start/Stop if (ATAPIWritePktWord(bd,timeout,0x1B00,0,i,0,0,0)) return ATAWaitNotBUSY(bd,timeout); else return FALSE; } I64 ATAGetDevId(CBlkDev *bd,F64 timeout,Bool keep_id_record) {//Upstream sequence (CD/DVD probing depends on it). Only change: //hard drives get nIEN in the control register. I64 res=BDT_NULL; U16 *id_record=NULL; if (bd->type!=BDT_ATAPI && bd->base1) OutU8(bd->base1+ATAR1_CTRL,ATAX_CTRL_NIEN); if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); ATACmd(bd,ATA_ID_DEV); if (ATAWaitNotBUSY(bd,timeout)) { if (InU8(bd->base0+ATAR0_STAT)&ATAS_ERR) res=BDT_ATAPI; else { id_record=ACAlloc(512); if (ATAGetRes(bd,timeout,id_record,512,512,FALSE)) res=BDT_ATA; else { Free(id_record); id_record=NULL; } } } if (keep_id_record) { Free(bd->dev_id_record); bd->dev_id_record=id_record; } return res; } I64 ATAReadNativeMax(CBlkDev *bd,F64 timeout) {//Returns last LBA, zero on err I64 res=0; U16 *id; Bool okay=TRUE; if (bd->type!=BDT_ATAPI) { //Hard drive: take the size from IDENTIFY DEVICE. if (ATAXWaitNotBusy(bd,ATAX_TIMEOUT)<0) ATAChannelReset(bd); if (ATAGetDevId(bd,timeout,TRUE)==BDT_ATA && (id=bd->dev_id_record)) { if (Bt(&id[83],10) && Bt(&id[86],10)) //48-bit supported & enabled res=id[100] + id[101]<<16 + id[102]<<32 + id[103]<<48; if (res) bd->flags|=BDF_EXT_SIZE; else { bd->flags&=~BDF_EXT_SIZE; res=id[60] + id[61]<<16; } if (res>0) res--; } return bd->max_blk=res; } //CD/DVD: original code. if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); ATACmd(bd,ATA_DEV_RST); if (!ATAWaitNotBUSY(bd,0)) okay=FALSE; if (okay) { if (bd->flags & BDF_EXT_SIZE && bd->base1) { OutU8(bd->base1+ATAR1_CTRL,0x8); OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); ATACmd(bd,ATA_READ_NATIVE_MAX_EXT); if (ATAWaitNotBUSY(bd,timeout)) { res.u8[0]=InU8(bd->base0+ATAR0_SECT); res.u8[1]=InU8(bd->base0+ATAR0_LCYL); res.u8[2]=InU8(bd->base0+ATAR0_HCYL); OutU8(bd->base1+ATAR1_CTRL,0x80); res.u8[3]=InU8(bd->base0+ATAR0_SECT); res.u8[4]=InU8(bd->base0+ATAR0_LCYL); res.u8[5]=InU8(bd->base0+ATAR0_HCYL); if (res>>24==res&0xFFFFFF) {//Kludge to make QEMU work bd->flags&=~BDF_EXT_SIZE; res&=0xFFFFFF; } } } else { OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); ATACmd(bd,ATA_READ_NATIVE_MAX); if (ATAWaitNotBUSY(bd,timeout)) { res.u8[0]=InU8(bd->base0+ATAR0_SECT); res.u8[1]=InU8(bd->base0+ATAR0_LCYL); res.u8[2]=InU8(bd->base0+ATAR0_HCYL); res.u8[3]=InU8(bd->base0+ATAR0_SEL) & 0xF; } } } return bd->max_blk=res; } I64 ATAPIReadCapacity(CBlkDev *bd,I64 *_blk_size=NULL) {//Supposedly this can return a res +/- 75 sects. //Error might just be for music. Bool unlock=BlkDevLock(bd); U32 buf[2]; if (ATAWaitNotBUSY(bd,0)) { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,8); OutU8(bd->base0+ATAR0_HCYL,0); ATACmd(bd,ATA_PACKET); ATAPIWritePktWord(bd,0,0x2500,0,0,0,0,0); if (!ATAGetRes(bd,0,buf,8,0,TRUE)) buf[0]=buf[1]=0; } else buf[0]=buf[1]=0; if (unlock) BlkDevUnlock(bd); if (_blk_size) *_blk_size=EndianU32(buf[1]); return EndianU32(buf[0]); } CATAPITrack *ATAPIReadTrackInfo(CBlkDev *bd,I64 blk) { CATAPITrack *res=CAlloc(sizeof(CATAPITrack)); Bool unlock=BlkDevLock(bd); if (ATAWaitNotBUSY(bd,0)) { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,sizeof(CATAPITrack)&0xFF); OutU8(bd->base0+ATAR0_HCYL,sizeof(CATAPITrack)>>8); ATACmd(bd,ATA_PACKET); ATAPIWritePktWord(bd,0,0x5200,blk.u16[1],blk.u16[0], (sizeof(CATAPITrack)&0xFF00)>>8,(sizeof(CATAPITrack)&0x00FF)<<8,0); if (!ATAGetRes(bd,0,res,sizeof(CATAPITrack),0,TRUE)) { Free(res); res=NULL; } } else { Free(res); res=NULL; } if (unlock) BlkDevUnlock(bd); return res; } Bool ATAInit(CBlkDev *bd) { Bool unlock=BlkDevLock(bd),okay=FALSE; if (bd->type==BDT_ATAPI) { //CD/DVD: original upstream sequence, ports left exactly as mounted. bd->flags&=~BDF_EXT_SIZE; if (ATAReadNativeMax(bd,tS+0.1)) { ATABlkSel(bd,bd->max_blk,0); ATACmd(bd,ATA_SET_MAX); if (ATAWaitNotBUSY(bd,0)) { okay=TRUE; if (ATAPIStartStop(bd,0,TRUE)) { if(!ATAPISetMaxSpeed(bd)) okay=FALSE; } else okay=FALSE; } } } else { //Hard drive: no SET MAX, no SET MULTIPLE. Just IDENTIFY. bd->base0&=~7; bd->base1&=~3; //0xF0C2->0xF0C0, 0x3F6->0x3F4. CTRL reg is base1+2. ATAXCtrl(bd,ATAX_CTRL_NIEN); if (ATAXWaitNotBusy(bd,ATAX_TIMEOUT)<0) ATAChannelReset(bd); if (ATAReadNativeMax(bd,tS+ATAX_TIMEOUT)) okay=TRUE; else PrintErr("ATA: IDENTIFY failed Base0:0x%04X Base1:0x%04X Unit:%d\n", bd->base0,bd->base1,bd->unit); } if (unlock) BlkDevUnlock(bd); return okay; } Bool ATAPIWaitReady(CBlkDev *bd,F64 timeout) { do { if (!ATAWaitNotBUSY(bd,timeout) || !ATANop(bd,timeout) || !ATAPIStartStop(bd,timeout,TRUE)) return FALSE; if (InU8(bd->base0+ATAR0_STAT) & ATAS_DRDY && !InU8(bd->base0+ATAR0_FEAT)); return TRUE; ATAInit(bd); Yield; } while (!(0<timeout<tS)); return FALSE; } U0 ATAReadBlks(CBlkDev *bd,U8 *buf, I64 blk, I64 cnt) { I64 n,tries; Bool unlock=BlkDevLock(bd); while (cnt>0) { n=cnt; if (n>ATAX_CHUNK) n=ATAX_CHUNK; tries=0; while (!ATAXReadChunk(bd,buf,blk,n)) { ATAChannelReset(bd); if (++tries>=ATAX_RETRIES) { if (unlock) BlkDevUnlock(bd); //Never throw with the lock held! throw('BlkDev'); } } blkdev.read_cnt+=(n*bd->blk_size)>>BLK_SIZE_BITS; buf+=n<<BLK_SIZE_BITS; blk+=n; cnt-=n; } if (unlock) BlkDevUnlock(bd); } I64 ATAProbe(I64 base0,I64 base1,I64 unit) { CBlkDev bd; MemSet(&bd,0,sizeof(CBlkDev)); bd.type=BDT_ATAPI; bd.base0=base0; bd.base1=base1; bd.unit=unit; bd.blk_size=DVD_BLK_SIZE; return ATAGetDevId(&bd,tS+0.1,FALSE); } Bool ATAPIReadBlks2(CBlkDev *bd,F64 timeout,U8 *buf, I64 native_blk, I64 cnt,Bool lock) { Bool res=FALSE,unlock; if (cnt<=0) return FALSE; if (lock) unlock=BlkDevLock(bd); if (ATAPIWaitReady(bd,timeout)) { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,bd->blk_size); OutU8(bd->base0+ATAR0_HCYL,bd->blk_size.u8[1]); ATACmd(bd,ATA_PACKET); if (ATAPIWritePktWord(bd,timeout,0xA800, native_blk.u16[1],native_blk,cnt.u16[1],cnt,0) && ATAGetRes(bd,timeout,buf,cnt*bd->blk_size,0,FALSE)) { blkdev.read_cnt+=(cnt*bd->blk_size)>>BLK_SIZE_BITS; res=TRUE; } } // ATAPIStartStop(bd,0,FALSE); if (lock && unlock) BlkDevUnlock(bd); return res; } U0 ATAPIReadBlks(CBlkDev *bd,U8 *buf, I64 blk, I64 cnt) { CDrv *dv=Let2Drv(bd->first_drv_let); I64 retry,spc=bd->blk_size>>BLK_SIZE_BITS,n,blk2, l2=bd->max_reads<<1+spc<<1; Bool ok=FALSE; U8 *dvd_buf=CAlloc(l2<<BLK_SIZE_BITS); if (cnt>0) { if (blk<=bd->max_reads) blk2=0; else blk2=FloorU64(blk-bd->max_reads,spc); if (blk2+l2>dv->size+dv->drv_offset) l2=dv->size+dv->drv_offset-blk2; n=(l2+spc-1)/spc; //The original ignored the result of the last attempt (with an infinite //timeout) and copied an uninitialized buffer on failure. That turned a //failed read into random ISO9660 metadata. Now a failed read is an error. for (retry=0;retry<4 && !ok;retry++) { ok=ATAPIReadBlks2(bd,tS+7.0+0.004*n,dvd_buf,blk2/spc,n,TRUE); //n is 0x800 if max_reads. Up to 8 additional seconds if (!ok) PrintErr("ATAPI read attempt %d failed. Sect:%d Cnt:%d " "Stat:0x%02X Err:0x%02X\n",retry+1,blk2/spc,n, InU8(bd->base0+ATAR0_STAT),InU8(bd->base0+ATAR0_FEAT)); } if (!ok) { Free(dvd_buf); throw('BlkDev'); } if (bd->flags & BDF_READ_CACHE) DskCacheAdd(dv,dvd_buf,blk2,n*spc); MemCpy(buf,dvd_buf+(blk-blk2)<<BLK_SIZE_BITS,cnt<<BLK_SIZE_BITS); } Free(dvd_buf); } Bool ATARBlks(CDrv *dv,U8 *buf, I64 blk, I64 cnt) { I64 n; CBlkDev *bd=dv->bd; while (cnt>0) { n=cnt; if (n>bd->max_reads) n=bd->max_reads; if (n<1) n=1; if (bd->type==BDT_ATAPI) ATAPIReadBlks(bd,buf,blk,n); else ATAReadBlks(bd,buf,blk,n); buf+=n<<BLK_SIZE_BITS; blk+=n; cnt-=n; } return TRUE; } U0 ATAWriteBlks(CBlkDev *bd,U8 *buf, I64 blk, I64 cnt) {//For low level disk access. //Use BlkWrite() instead. I64 n,tries; Bool unlock=BlkDevLock(bd); while (cnt>0) { n=cnt; if (n>ATAX_CHUNK) n=ATAX_CHUNK; tries=0; while (!ATAXWriteChunk(bd,buf,blk,n)) { ATAChannelReset(bd); if (++tries>=ATAX_RETRIES) { if (unlock) BlkDevUnlock(bd); //Never throw with the lock held! throw('BlkDev'); } } buf+=n<<BLK_SIZE_BITS; blk+=n; cnt-=n; } //No FLUSH CACHE here: ATAWBlks flushes once after the whole request. if (unlock) BlkDevUnlock(bd); } Bool ATAPISync(CBlkDev *bd) { Bool okay=TRUE; if (!ATAWaitNotBUSY(bd,0)) okay=FALSE; else { if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,0); OutU8(bd->base0+ATAR0_HCYL,0); ATACmd(bd,ATA_PACKET); ATAPIWritePktWord(bd,0,0x3500,0,0,0,0,0); if (!ATAWaitNotBUSY(bd,0)) okay=FALSE; } return okay; } U0 ATAPIClose(CBlkDev *bd,I64 close_field=0x200,I64 track=0) {//0x200 CD/DVD part 1 // 0x300 DVD part 2 if (bd->flags & BDF_EXT_SIZE) OutU8(bd->base0+ATAR0_SEL,0xEF|bd->unit<<4); else OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); OutU8(bd->base0+ATAR0_LCYL,0); OutU8(bd->base0+ATAR0_HCYL,0); ATACmd(bd,ATA_PACKET); ATAPIWritePktWord(bd,0,0x5B00,close_field,track,0,0,0); ATAWaitNotBUSY(bd,0); } //---------------------------------------------------------------------- //CD/DVD write path with diagnostics. The upstream version waited for DRQ //with timeout=0 (forever). When the drive REJECTS the WRITE command it //sets ERR and never sets DRQ, so that loop spun forever right after //"[ATAPIWriteBlks] Four". Now the drive's reason is read (REQUEST SENSE) //and printed, and the function returns FALSE instead of hanging. //---------------------------------------------------------------------- U0 ATAPIX400ns(CBlkDev *bd) {//Five status reads > 400ns. Never trust DRQ right after a cmd/packet. I64 i; for (i=0;i<5;i++) InU8(bd->base0+ATAR0_STAT); } I64 ATAPIReqSense(CBlkDev *bd) {//REQUEST SENSE (0x03). Prints and returns Key<<16|ASC<<8|ASCQ, -1 on fail. U8 sense[32]; I64 res=-1; MemSet(sense,0,32); if (ATAWaitNotBUSY(bd,tS+5.0)) { OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); ATAPIX400ns(bd); OutU8(bd->base0+ATAR0_LCYL,18); OutU8(bd->base0+ATAR0_HCYL,0); ATACmd(bd,ATA_PACKET); if (ATAPIWritePktWord(bd,tS+5.0,0x0300,0,0x1200,0,0,0)) { ATAPIX400ns(bd); if (ATAWaitNotBUSY(bd,tS+5.0) && !(InU8(bd->base0+ATAR0_STAT)&ATAS_ERR) && ATAGetRes(bd,tS+5.0,sense,18,0,TRUE)) res=(sense[2]&0xF)<<16|sense[12]<<8|sense[13]; } } if (res<0) PrintErr("ATAPI: REQUEST SENSE failed too. Stat:0x%02X\n", InU8(bd->base0+ATAR0_STAT)); /* else "ATAPI SENSE Key:%X ASC:%02X ASCQ:%02X\n", res.u8[2],res.u8[1],res.u8[0]; */ return res; } Bool ATAPIXDone(CBlkDev *bd,F64 timeout,U8 *what) {//Wait for the end of a non-data packet cmd. Prints sense on ERR. F64 deadline=tS+timeout; I64 s; Bool res=FALSE,done=FALSE; ATAPIX400ns(bd); while (!done) { s=InU8(bd->base0+ATAR0_STAT); if (!(s&ATAS_BSY)) { done=TRUE; if (s&ATAS_ERR) { ATAPIReqSense(bd); } else res=TRUE; } else if (tS>deadline) { done=TRUE; PrintErr("%s: still BSY after %d s. Stat:0x%02X\n",what, ToI64(timeout),s); } else Yield; } return res; } U0 ATAPIXPktStart(CBlkDev *bd,I64 byte_cnt) {//Select drive, set byte count limit and issue PACKET. ATAWaitNotBUSY(bd,tS+10.0); OutU8(bd->base0+ATAR0_SEL,0xE0|bd->unit<<4); ATAPIX400ns(bd); ATAWaitNotBUSY(bd,tS+10.0); OutU8(bd->base0+ATAR0_LCYL,byte_cnt.u8[0]); OutU8(bd->base0+ATAR0_HCYL,byte_cnt.u8[1]); ATACmd(bd,ATA_PACKET); } Bool ATAPIWriteBlks(CBlkDev *bd,U8 *buf, I64 native_blk, I64 cnt) {//Returns FALSE (and prints why) on error instead of hanging forever. I64 j,s,ireason,bytes; U16 *w; U8 *buf2=buf+bd->blk_size*cnt; F64 deadline; Bool done=FALSE,res=FALSE; if (!ATAWaitNotBUSY(bd,tS+10.0)) { PrintErr("ATAPIWriteBlks: drive BSY before write. Stat:0x%02X\n", InU8(bd->base0+ATAR0_STAT)); return FALSE; } ATAPIXPktStart(bd,bd->blk_size); if (ATAPIWritePktWord(bd,tS+10.0, 0x0400,native_blk.u16[1],native_blk,cnt.u16[1],cnt,0)) ATAPIXDone(bd,300.0," FORMAT UNIT (an error is normal on CD-R)"); else PrintErr(" FORMAT UNIT: drive never asked for packet\n"); ATAPIXPktStart(bd,0); if (ATAPIWritePktWord(bd,tS+10.0,0x2B00,native_blk>>16,native_blk,0,0,0)) ATAPIXDone(bd,30.0," SEEK (an error is normal on blank media)"); else PrintErr(" SEEK: drive never asked for packet\n"); ATAPIXPktStart(bd,bd->blk_size); if (!ATAPIWritePktWord(bd,tS+10.0, 0xAA00,native_blk.u16[1],native_blk,cnt.u16[1],cnt,0)) { PrintErr("WRITE(12): drive never asked for the packet. Stat:0x%02X\n", InU8(bd->base0+ATAR0_STAT)); return FALSE; } bd->flags|=BDF_LAST_WAS_WRITE; ATAPIX400ns(bd); //DRQ from the packet phase may still be visible here. deadline=tS+30.0; while (!done) { s=InU8(bd->base0+ATAR0_STAT); if (s&ATAS_BSY) { if (tS>deadline) { PrintErr("WRITE(12): stuck BSY. Stat:0x%02X LBA:%d Left:%d bytes\n", s,native_blk,buf2-buf); done=TRUE; } else Yield; } else if (s&ATAS_ERR) { //This is the case that hung the old code: rejected cmd => ERR, no DRQ. PrintErr("WRITE(12) rejected by drive. LBA:%d Cnt:%d " "Stat:0x%02X Err:0x%02X Left:%d bytes\n", native_blk,cnt,s,InU8(bd->base0+ATAR0_FEAT),buf2-buf); ATAPIReqSense(bd); done=TRUE; } else if (!(s&ATAS_DRQ)) { //Command complete. done=TRUE; if (buf<buf2) { PrintErr("WRITE(12) ended early, %d bytes not taken. Stat:0x%02X\n", buf2-buf,s); ATAPIReqSense(bd); } else res=TRUE; } else { //Drive wants data. Interrupt reason (sector cnt reg) must be 0 = data out. ireason=InU8(bd->base0+ATAR0_NSECT)&3; bytes=InU8(bd->base0+ATAR0_HCYL)<<8+InU8(bd->base0+ATAR0_LCYL); if (ireason || !bytes || bytes&1 || buf+bytes>buf2) { PrintErr("WRITE(12): unexpected data request. IReason:%d " "Bytes:%d Left:%d Stat:0x%02X\n",ireason,bytes,buf2-buf,s); done=TRUE; } else { w=buf; for (j=0;j<bytes>>1;j++) OutU16(bd->base0+ATAR0_DATA,*w++); buf+=bytes; blkdev.write_cnt+=bytes>>BLK_SIZE_BITS; ATAPIX400ns(bd); deadline=tS+30.0; } } } return res; } Bool ATAWBlks(CDrv *dv,U8 *buf, I64 blk, I64 cnt) { I64 n,spc; CBlkDev *bd=dv->bd; Bool unlock; spc=bd->blk_size>>BLK_SIZE_BITS; if (bd->type==BDT_ATAPI) { unlock=BlkDevLock(bd); ATAPIWaitReady(bd,0); } while (cnt>0) { n=cnt; if (n>bd->max_writes) n=bd->max_writes; if (n<1) //Guard: max_writes==0 would loop forever n=1; if (bd->type==BDT_ATAPI) ATAPIWriteBlks(bd,buf,blk/spc,(n+spc-1)/spc); else ATAWriteBlks(bd,buf,blk,n); buf+=n<<BLK_SIZE_BITS; blk+=n; cnt-=n; blkdev.write_cnt+=n; } if (bd->type==BDT_ATAPI) { ATAPISync(bd); // ATAPIStartStop(bd,0,FALSE); if (unlock) BlkDevUnlock(bd); } else { //One FLUSH CACHE per request, hard drives only (never send it to a CD). unlock=BlkDevLock(bd); ATAXFlush(bd); if (unlock) BlkDevUnlock(bd); } return TRUE; }