U0 ATABlkSel(CBlkDev *bd, I64 blk, I64 cnt) { if (bd->type != BDT_ATAPI && bd->base1) OutU8(bd->base1 + ATAR1_CTRL, 0x8); 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) { 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; } 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; 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) { I64 res = BDT_NULL; U16 *id_record = NULL; if (bd->type != BDT_ATAPI && bd->base1) OutU8(bd->base1 + ATAR1_CTRL, 0x8); 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); 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 zero on err I64 res = 0; Bool okay = TRUE; if (bd->type == BDT_ATAPI) { 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; } else { while (InU8(bd->base0 + ATAR0_STAT) & ATAS_BSY) { if (bd->flags & BDF_LAST_WAS_WRITE) OutU16(bd->base0 + ATAR0_DATA, 0); else InU16(bd->base0 + ATAR0_DATA); Yield; if (0 < timeout < tS) return FALSE; } if (ATAGetDevId(bd, timeout, TRUE) == BDT_NULL) okay = FALSE; else BEqu(&bd->flags, BDf_EXT_SIZE, Bt(&bd->dev_id_record[86], 10)); } 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 { if (bd->type != BDT_ATAPI && bd->base1) OutU8(bd->base1 + ATAR1_CTRL, 0x8); 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) bd->flags &= ~BDF_EXT_SIZE; else bd->flags |= BDF_EXT_SIZE; if (ATAReadNativeMax(bd, tS + 0.1)) { ATABlkSel(bd, bd->max_blk, 0); if (bd->flags & BDF_EXT_SIZE) ATACmd(bd, ATA_SET_MAX_EXT); else ATACmd(bd, ATA_SET_MAX); if (ATAWaitNotBUSY(bd, 0)) { okay = TRUE; if (bd->type == BDT_ATAPI) { if (ATAPIStartStop(bd, 0, TRUE)) { if (!ATAPISetMaxSpeed(bd)) okay = FALSE; } else okay = FALSE; } } } 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 retries = 3; Bool unlock = BlkDevLock(bd); retry: ATABlkSel(bd, blk, cnt); if (bd->flags & BDF_EXT_SIZE) ATACmd(bd, ATA_READ_MULTI_EXT); else ATACmd(bd, ATA_READ_MULTI); if (!ATAGetRes(bd, tS + 1.0, buf, cnt * bd->blk_size, BLK_SIZE, FALSE)) { if (retries--) { ATAWaitNotBUSY(bd, 0); goto retry; } else throw('BlkDev'); } blkdev.read_cnt += (cnt * bd->blk_size) >> BLK_SIZE_BITS; 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; U8 *dvd_buf = MAlloc(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; retry = 4; while (--retry) if (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 break; if (!retry) ATAPIReadBlks2(bd, 0, dvd_buf, blk2 / spc, n, TRUE); 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 (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 i, U32s_avail, sects_avail, retries = 3; F64 timeout; Bool unlock = BlkDevLock(bd); retry: ATABlkSel(bd, blk, cnt); if (bd->flags & BDF_EXT_SIZE) ATACmd(bd, ATA_WRITE_MULTI_EXT); else ATACmd(bd, ATA_WRITE_MULTI); bd->flags |= BDF_LAST_WAS_WRITE; while (cnt > 0) { timeout = tS + 1.0; while (TRUE) { i = InU8(bd->base0 + ATAR0_STAT); if (!(i & ATAS_DRDY) || !(i & ATAS_DRQ)) { Yield; } else break; if (/* i&ATAS_ERR||*/ tS > timeout) { if (retries--) { ATAWaitNotBUSY(bd, 0); goto retry; } else throw('BlkDev'); } } sects_avail = 1; U32s_avail = sects_avail << BLK_SIZE_BITS >> 2; RepOutU32(buf, U32s_avail, bd->base0 + ATAR0_DATA); buf += U32s_avail << 2; cnt -= sects_avail; retries = 3; } ATAWaitNotBUSY(bd, 0); 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); } U0 ATAPIWriteBlks(CBlkDev *bd, U8 *buf, I64 native_blk, I64 cnt) { I64 U32s_avail; U8 *buf2; ATAWaitNotBUSY(bd, 0); ATAPISeek(bd, native_blk); OutU8(bd->base0 + ATAR0_FEAT, 0); OutU8(bd->base0 + ATAR0_LCYL, bd->blk_size); OutU8(bd->base0 + ATAR0_HCYL, bd->blk_size.u8[1]); 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_CMD, ATA_PACKET); ATAPIWritePktWord(bd, 0, 0x0400, native_blk.u16[1], native_blk, cnt.u16[1], cnt, 0); bd->flags |= BDF_LAST_WAS_WRITE; ATAWaitNotBUSY(bd, 0); ATAPISeek(bd, 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, bd->blk_size); OutU8(bd->base0 + ATAR0_HCYL, bd->blk_size.u8[1]); ATACmd(bd, ATA_PACKET); ATAPIWritePktWord(bd, 0, 0xAA00, native_blk.u16[1], native_blk, cnt.u16[1], cnt, 0); buf2 = buf + bd->blk_size * cnt; while (buf < buf2) { ATAWaitDRQ(bd, 0); U32s_avail = (InU8(bd->base0 + ATAR0_HCYL) << 8 + InU8(bd->base0 + ATAR0_LCYL)) >> 2; if (buf + U32s_avail << 2 > buf2) U32s_avail = (buf2 - buf) >> 2; if (U32s_avail) { RepOutU32(buf, U32s_avail, bd->base0 + ATAR0_DATA); buf += U32s_avail << 2; blkdev.write_cnt += U32s_avail >> (BLK_SIZE_BITS - 2); } } ATAWaitNotBUSY(bd, 0); } 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 (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); } return TRUE; }