Files
TermiNexus cfb76b1b94 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,无需三重编译。
2026-10-16 20:39:56 +08:00

194 lines
8.0 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asm
import drivers.usb.pci as pci
enum IDError:
IDE_ERR_NONE, IDE_ERR_TIMEOUT, IDE_ERR_DEVICE_ERROR
IDE_ERR_DEVICE_BUSY, IDE_ERR_INVALID_PARAM, IDE_ERR_BUFFER_NULL
IDE_ERR_SECTOR_COUNT, IDE_ERR_DEVICE_NOT_READY, IDE_ERR_DEVICE_FAILURE
IDE_ERR_COMMAND_FAILED, IDE_ERR_INVALID_LBA
define IDE_baseport_PRIMARY = 0x1F0
define IDE_baseport_SECONDARY = 0x170
define IDE_STATUS_BSY = 0x80
define IDE_STATUS_DRDY = 0x40
define IDE_STATUS_DF = 0x20
define IDE_STATUS_DRQ = 0x08
define IDE_STATUS_ERR = 0x01
define SECTOR_SIZE = 512
_ide_initialized: int = 0
def waitReady(baseport: uint16) -> static IDError:
timeout: int = 100000
for _ in range(timeout, 0, -1):
status: uint8 = asm.inb(baseport + 7)
if status & IDE_STATUS_DF:
return IDError.IDE_ERR_DEVICE_FAILURE
if not status & IDE_STATUS_BSY and status & IDE_STATUS_DRDY:
return IDError.IDE_ERR_NONE
return IDError.IDE_ERR_TIMEOUT
def waitDataReady(baseport: uint16) -> static IDError:
timeout: int = 100000
for _ in range(timeout, 0, -1):
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
# 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
bus: uint8
dev_n: uint8
func: uint8
for bus in range(2):
for dev_n in range(32):
for func in range(8):
vendor: uint16 = pci.pci_read16(bus, dev_n, func, 0x00)
if vendor == 0xFFFF: continue
cls: uint32 = pci.pci_get_class(bus, dev_n, func)
if cls == 0x010180 or cls == 0x0101:
cmd: uint16 = pci.pci_read16(bus, dev_n, func, 0x04)
pci.pci_write32(bus, dev_n, func, 0x04, uint32(cmd | 0x0007))
asm.outb(0x3F6, 0x04)
for _ in range(10000): asm.nop()
asm.outb(0x3F6, 0x00)
for _ in range(10000): asm.nop()
status: uint8 = asm.inb(0x1F7)
_ide_initialized = 1
return IDError.IDE_ERR_NONE
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 是逐条采样寄存器的,缺延时
# 会出现"只写入部分寄存器"⇒ LBA 保留上一次的值 ⇒ 读到**另一个扇区**的数据(现场:
# 读根目录扇区 LBA 8192 却读回 ELF 文件头,导致目录项全是垃圾 → spawn 失败)。
asm.outb(0x1F2, 1)
asm.io_wait()
asm.outb(0x1F3, uint8(lba & 0xFF))
asm.io_wait()
asm.outb(0x1F4, uint8((lba >> 8) & 0xFF))
asm.io_wait()
asm.outb(0x1F5, uint8((lba >> 16) & 0xFF))
asm.io_wait()
asm.outb(0x1F6, uint8(0xE0 | ((lba >> 24) & 0x0F)))
asm.io_wait()
asm.outb(0x1F7, 0x20)
asm.io_wait()
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE:
# 失败后必须把设备里可能的残留数据取走,否则后续每一次读都会被"命令寄存器写被忽略"污染。
flushStaleData()
return err
st2: uint8 = asm.inb(0x1F7)
# 读完再核一次 ERR:置位说明这次数据无效,不能当成功返回(否则上层会拿到错内容)
if st2 & IDE_STATUS_ERR:
return IDError.IDE_ERR_DEVICE_ERROR
buf: uint16* = uint16*(buffer)
for j in range(256):
*buf = asm.inw(0x1F0)
# buf 是 i16*(uint16*):+1 表示前进 1 个元素 = 2 字节,256 次恰好填满 512B
buf = buf + 1
first_byte: uint8 = *(uint8*(buffer))
# viperlib.snprintf(c.Addr(dbg), 120, "[readSector] lba=%lu status=0x%02x first=%02x\n", lba, uint32(st2), uint32(first_byte))
# serial.puts(dbg)
return IDError.IDE_ERR_NONE
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:
flushStaleData()
return err
flushStaleData()
asm.outb(0x3F6, 0x00)
asm.io_wait()
# 与 readSector 同理:命令块寄存器连写之间必须插 I/O 延时,否则 QEMU 只采样到部分寄存器
# ⇒ LBA 残留旧值 ⇒ 把数据写到**错误的扇区**(写盘路径同样致命)。
asm.outb(0x1F2, 1)
asm.io_wait()
asm.outb(0x1F3, uint8(lba & 0xFF))
asm.io_wait()
asm.outb(0x1F4, uint8((lba >> 8) & 0xFF))
asm.io_wait()
asm.outb(0x1F5, uint8((lba >> 16) & 0xFF))
asm.io_wait()
asm.outb(0x1F6, uint8(0xE0 | ((lba >> 24) & 0x0F)))
asm.io_wait()
asm.outb(0x1F7, 0x30)
asm.io_wait()
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE:
flushStaleData()
return err
buf: uint16* = uint16*(buffer)
for j in range(256):
val: uint16 = *buf
asm.outw(0x1F0, val)
# buf 是 i16*(uint16*):+1 表示前进 1 个元素 = 2 字节,256 次恰好读完 512B
# (IR 取证:`buf + 2` 生成 getelementptr ..., i16,按元素缩放 → +4 字节,越界)
buf = buf + 1
asm.outb(0x1F7, 0xE7)
waitReady(IDE_baseport_PRIMARY)
return IDError.IDE_ERR_NONE
def getDiskSize() -> uint64:
err: IDError = waitReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return 0
asm.outb(0x1F6, 0xE0)
asm.outb(0x1F7, 0xEC)
err = waitDataReady(IDE_baseport_PRIMARY)
if err != IDError.IDE_ERR_NONE: return 0
status: uint8 = asm.inb(0x1F7)
if not status & IDE_STATUS_DRDY: return 0
if status & IDE_STATUS_ERR: return 0
identify_data: t.CArray[uint16, 256]
for i in range(256):
identify_data[i] = asm.inw(0x1F0)
totalSectors: uint64 = identify_data[60] | uint64(identify_data[61]) << 16
if identify_data[83] & (1 << 10):
lba48_sectors: uint64 = (
uint64(identify_data[100]) |
uint64(identify_data[101]) << 16 |
uint64(identify_data[102]) << 32 |
uint64(identify_data[103]) << 48)
if lba48_sectors > totalSectors:
totalSectors = lba48_sectors
return totalSectors