现场(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,无需三重编译。
194 lines
8.0 KiB
Plaintext
194 lines
8.0 KiB
Plaintext
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
|