fix(ide): PIO 数据就绪只认 DRQ,并在发命令前/失败后清掉残留扇区
现场(serial.log 113-136):winread FAIL abs=8192 res=1,紧接着读根目录扇区 8192 读回 ELF 文件头 ⇒ 目录项全是垃圾 ⇒ Hello/Terminal/Gargantua 三个 spawn 连锁失败,之后又自行恢复(间歇)。两个成因:① waitDataReady 把 DRDY 当作数据就绪(DRDY 空闲时恒为 1),在 BSY 刚清、DRQ 未置的窗口里误判成功,随后从 0x1F0 读到上一次传输的残留;② 一旦某次读提前失败返回、数据没被取走(DRQ 仍置位),ATA 规定设备会忽略命令块寄存器写入 ⇒ LBA 保留旧值 ⇒ 后续每次读都拿到同一块残留缓冲,形成连锁污染。改法:waitDataReady 改为必须 !BSY && DRQ;新增 flushStaleData(),在 readSector/writeSector 发命令前以及失败返回前把残留扇区取走丢弃。未改 includes,无需三重编译。
This commit is contained in:
1 parent
4d0f29d533
commit
cfb76b1b94
1 file changed
+35
-4
@@ -37,10 +37,32 @@ def waitDataReady(baseport: uint16) -> static IDError:
|
||||
status: uint8 = asm.inb(baseport + 7)
|
||||
if status & IDE_STATUS_ERR: return IDError.IDE_ERR_DEVICE_ERROR
|
||||
if status & IDE_STATUS_DF: return IDError.IDE_ERR_DEVICE_FAILURE
|
||||
if not status & IDE_STATUS_BSY and (status & IDE_STATUS_DRQ or status & IDE_STATUS_DRDY):
|
||||
# PIO 数据就绪的唯一标志是 **DRQ**。DRDY 只表示设备在位/就绪,空闲时一直是 1;
|
||||
# 拿它当"数据到了"会在 BSY 刚清、DRQ 未置的窗口里误判成功,随后从 0x1F0 读到
|
||||
# 上一次传输的残留(现场:读目录扇区 8192 读回 ELF 文件头)。
|
||||
if not status & IDE_STATUS_BSY and status & IDE_STATUS_DRQ:
|
||||
return IDError.IDE_ERR_NONE
|
||||
return IDError.IDE_ERR_TIMEOUT
|
||||
|
||||
# 残留数据缓冲:flushStaleData 用它把设备里待取的一扇区读掉(PIO 单线程,模块级静态即可)。
|
||||
_stale_buf: [512]uint8
|
||||
|
||||
def flushStaleData():
|
||||
"""把设备里残留的一扇区数据取走丢弃。
|
||||
|
||||
为什么必须有:ATA 规定设备"有数据待取"(DRQ 置位)时会忽略命令块寄存器写入。
|
||||
若上一次读提前失败返回、数据没被取走,后续 0x1F2..0x1F7 的写全部被忽略 ⇒ LBA 保留
|
||||
旧值 ⇒ 下一次读拿到上一次的残留缓冲(现场:读根目录扇区 8192 读回 ELF 头,目录项
|
||||
全是垃圾 ⇒ Hello/Terminal/Gargantua 三个 spawn 连锁失败)。
|
||||
"""
|
||||
if not (asm.inb(IDE_baseport_PRIMARY + 7) & IDE_STATUS_DRQ):
|
||||
return
|
||||
p: uint16* = uint16*(&_stale_buf[0])
|
||||
for _k in range(256):
|
||||
*p = asm.inw(0x1F0)
|
||||
# p 是 i16*:+1 表示前进 1 个元素 = 2 字节,256 次恰好一扇区
|
||||
p = p + 1
|
||||
|
||||
def init() -> int:
|
||||
global _ide_initialized
|
||||
if _ide_initialized: return IDError.IDE_ERR_NONE
|
||||
@@ -68,7 +90,10 @@ def readSector(lba: uint32, buffer: rawptr) -> int:
|
||||
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
|
||||
err: IDError = waitReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE:
|
||||
flushStaleData()
|
||||
return err
|
||||
# 命令块寄存器能被接受的前提是设备**没有待取数据**:DRQ 若仍置位,写会被忽略。
|
||||
flushStaleData()
|
||||
asm.outb(0x3F6, 0x00)
|
||||
asm.io_wait()
|
||||
# 命令块寄存器**连写之间必须插 I/O 延时**:QEMU 的 IDE 是逐条采样寄存器的,缺延时
|
||||
@@ -88,7 +113,8 @@ def readSector(lba: uint32, buffer: rawptr) -> int:
|
||||
asm.io_wait()
|
||||
err = waitDataReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE:
|
||||
st: uint8 = asm.inb(0x1F7)
|
||||
# 失败后必须把设备里可能的残留数据取走,否则后续每一次读都会被"命令寄存器写被忽略"污染。
|
||||
flushStaleData()
|
||||
return err
|
||||
st2: uint8 = asm.inb(0x1F7)
|
||||
# 读完再核一次 ERR:置位说明这次数据无效,不能当成功返回(否则上层会拿到错内容)
|
||||
@@ -107,7 +133,10 @@ def readSector(lba: uint32, buffer: rawptr) -> int:
|
||||
def writeSector(lba: uint32, buffer: const rawptr) -> int:
|
||||
if buffer is None: return IDError.IDE_ERR_BUFFER_NULL
|
||||
err: IDError = waitReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE: return err
|
||||
if err != IDError.IDE_ERR_NONE:
|
||||
flushStaleData()
|
||||
return err
|
||||
flushStaleData()
|
||||
asm.outb(0x3F6, 0x00)
|
||||
asm.io_wait()
|
||||
# 与 readSector 同理:命令块寄存器连写之间必须插 I/O 延时,否则 QEMU 只采样到部分寄存器
|
||||
@@ -125,7 +154,9 @@ def writeSector(lba: uint32, buffer: const rawptr) -> int:
|
||||
asm.outb(0x1F7, 0x30)
|
||||
asm.io_wait()
|
||||
err = waitDataReady(IDE_baseport_PRIMARY)
|
||||
if err != IDError.IDE_ERR_NONE: return err
|
||||
if err != IDError.IDE_ERR_NONE:
|
||||
flushStaleData()
|
||||
return err
|
||||
buf: uint16* = uint16*(buffer)
|
||||
for j in range(256):
|
||||
val: uint16 = *buf
|
||||
|
||||
Reference in new issue
Block a user