diff options
| author | hachem <im@hachem.wtf> | 2026-10-04 00:52:49 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-04 00:52:49 +0200 |
| commit | 627f05098785327744e666c9149375125499d2ff (patch) | |
| tree | a72f3949e0729f1e73ad39ccb1809698a5ed8298 /boot | |
| parent | 09982d80c45805715984cc1cae96c37e4fc148c3 (diff) | |
feat: revelfs type shi
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/ata.zig | 160 | ||||
| -rw-r--r-- | boot/boot.zig | 22 | ||||
| -rw-r--r-- | boot/port.zig | 15 |
3 files changed, 197 insertions, 0 deletions
diff --git a/boot/ata.zig b/boot/ata.zig new file mode 100644 index 0000000..ec63c1c --- /dev/null +++ b/boot/ata.zig @@ -0,0 +1,160 @@ +// ata pio driver, primary bus master drive, 28 bit lba +// +// this is the dumbest most simple most retartded possible block device driver +// it busy polls the status port and shovels one sector at a time through the 16 +// bit data port. no dma no interrupts no second drive. its slow but the whole point +// is that revo builds a real filesystem on top of these two calls (read/write a 512 byte sector) so +// i need to rewrite this when im not drunk (never gonna happen) +// +// see: https://wiki.osdev.org/ATA_PIO_Mode + +const port = @import("port.zig"); +const serial = @import("serial.zig"); + +pub const SECTOR = 512; + +// primary bus io ports +const DATA = 0x1F0; +const FEATURES = 0x1F1; // error on read +const SECCOUNT = 0x1F2; +const LBA_LO = 0x1F3; +const LBA_MID = 0x1F4; +const LBA_HI = 0x1F5; +const DRIVE = 0x1F6; +const CMD = 0x1F7; // status on read +const CTRL = 0x3F6; // alt status / device control + +// status bits +const ST_ERR = 0x01; +const ST_DRQ = 0x08; // data request, drive wants a word moved +const ST_DRDY = 0x40; +const ST_BSY = 0x80; + +const CMD_READ = 0x20; +const CMD_WRITE = 0x30; +const CMD_FLUSH = 0xE7; +const CMD_IDENTIFY = 0xEC; + +var g_present = false; +var g_sectors: u64 = 0; + +pub fn present() bool { + return g_present; +} + +pub fn sectorCount() u64 { + return g_sectors; +} + +// reading the alt status 4 times burns ~400ns, the spec wants that gap after a +// command before the status bits mean anything +fn delay400() void { + _ = port.inb(CTRL); + _ = port.inb(CTRL); + _ = port.inb(CTRL); + _ = port.inb(CTRL); +} + +// spin until bsy drops +// returns false if it never does (dead bus) +fn waitNotBusy() bool { + var spins: u32 = 0; + while (spins < 1_000_000) : (spins += 1) { + if (port.inb(CMD) & ST_BSY == 0) return true; + } + return false; +} + +// spin until the drive is ready to move a word (drq set) +// false on error/timeout +fn waitDrq() bool { + var spins: u32 = 0; + while (spins < 1_000_000) : (spins += 1) { + const st = port.inb(CMD); + if (st & ST_ERR != 0) return false; + if (st & ST_BSY == 0 and st & ST_DRQ != 0) return true; + } + return false; +} + +// select master and load the lba + a one sector count +fn selectSector(lba: u28) void { + port.outb(DRIVE, 0xE0 | @as(u8, @truncate((lba >> 24) & 0x0F))); + port.outb(FEATURES, 0); + port.outb(SECCOUNT, 1); + port.outb(LBA_LO, @truncate(lba & 0xFF)); + port.outb(LBA_MID, @truncate((lba >> 8) & 0xFF)); + port.outb(LBA_HI, @truncate((lba >> 16) & 0xFF)); +} + +// probe the primary master with identify, the floating bus reads status +// 0xFF, a present drive clears bsy and coughs up 256 words of identify data, +// words 60/61 hold the 28 bit sector count +pub fn init() void { + // a nonexistent bus floats high, bail before we wait a million spins on it + if (port.inb(CMD) == 0xFF) { + serial.write("ata: no drive on primary bus\r\n"); + return; + } + + port.outb(DRIVE, 0xA0); // master + delay400(); + port.outb(SECCOUNT, 0); + port.outb(LBA_LO, 0); + port.outb(LBA_MID, 0); + port.outb(LBA_HI, 0); + port.outb(CMD, CMD_IDENTIFY); + delay400(); + + if (port.inb(CMD) == 0) { + serial.write("ata: primary master absent\r\n"); + return; + } + if (!waitDrq()) { + serial.write("ata: identify failed\r\n"); + return; + } + + var id: [256]u16 = undefined; + for (&id) |*w| w.* = port.inw(DATA); + + g_sectors = (@as(u64, id[61]) << 16) | id[60]; + g_present = true; + serial.write("ata: primary master, sectors="); + serial.writeDec(g_sectors); + serial.write("\r\n"); +} + +// read one 512 byte sector at lba into buf +pub fn read(lba: u28, buf: *[SECTOR]u8) bool { + if (!g_present) return false; + if (!waitNotBusy()) return false; + selectSector(lba); + port.outb(CMD, CMD_READ); + if (!waitDrq()) return false; + var i: usize = 0; + while (i < SECTOR) : (i += 2) { + const w = port.inw(DATA); + buf[i] = @truncate(w & 0xFF); + buf[i + 1] = @truncate(w >> 8); + } + return true; +} + +// write one 512 byte sector from buf to lba, then flush the drive cache so it +// actually hits the image and survives a reboot, yk, like an actual file system +pub fn write(lba: u28, buf: *const [SECTOR]u8) bool { + if (!g_present) return false; + if (!waitNotBusy()) return false; + selectSector(lba); + port.outb(CMD, CMD_WRITE); + if (!waitDrq()) return false; + var i: usize = 0; + while (i < SECTOR) : (i += 2) { + const w = @as(u16, buf[i]) | (@as(u16, buf[i + 1]) << 8); + port.outw(DATA, w); + } + port.outb(CMD, CMD_FLUSH); + _ = waitNotBusy(); + return true; +} diff --git a/boot/boot.zig b/boot/boot.zig index 9f384fa..28fb8c1 100644 --- a/boot/boot.zig +++ b/boot/boot.zig @@ -12,6 +12,7 @@ const elf = @import("elf.zig"); const keyboard = @import("keyboard.zig"); const timer = @import("timer.zig"); const sched = @import("sched.zig"); +const ata = @import("ata.zig"); const bridge = @import("bridge"); // we need a panic handler otherwise zig wont be happy, @@ -100,6 +101,22 @@ fn kProcCount() u64 { return sched.procCount(); } +// block device, revo builds the filesystem on top of these +fn kDiskPresent() u64 { + return if (ata.present()) 1 else 0; +} +fn kDiskSectors() u64 { + return ata.sectorCount(); +} +fn kDiskRead(lba: u64, phys: u64) u64 { + const buf: *[ata.SECTOR]u8 = @ptrFromInt(pmm.physToVirt(phys)); + return if (ata.read(@truncate(lba), buf)) 1 else 0; +} +fn kDiskWrite(lba: u64, phys: u64) u64 { + const buf: *const [ata.SECTOR]u8 = @ptrFromInt(pmm.physToVirt(phys)); + return if (ata.write(@truncate(lba), buf)) 1 else 0; +} + // entry(_start) export fn _start() callconv(.c) noreturn { serial.init(); @@ -124,6 +141,7 @@ export fn _start() callconv(.c) noreturn { pmmSelfTest(); vmmSelfTest(); + ata.init(); const elf_pages = (elf.hello_elf.len + 4095) / 4096; const elf_phys = pmm.allocContig(elf_pages) orelse @panic("no room for the embedded ELF"); @@ -150,6 +168,10 @@ export fn _start() callconv(.c) noreturn { .proc_count = &kProcCount, .elf_phys = elf_phys, .elf_size = elf.hello_elf.len, + .disk_present = &kDiskPresent, + .disk_sectors = &kDiskSectors, + .disk_read = &kDiskRead, + .disk_write = &kDiskWrite, }; // hand the kernel heap + framebuffer + low level ops to revo and bring up the vm diff --git a/boot/port.zig b/boot/port.zig index be760b1..d38f829 100644 --- a/boot/port.zig +++ b/boot/port.zig @@ -17,3 +17,18 @@ pub inline fn inb(port: u16) u8 { : [port] "N{dx}" (port), ); } + +pub inline fn outw(port: u16, val: u16) void { + asm volatile ("outw %[val], %[port]" + : + : [val] "{ax}" (val), + [port] "N{dx}" (port), + ); +} + +pub inline fn inw(port: u16) u16 { + return asm volatile ("inw %[port], %[result]" + : [result] "={ax}" (-> u16), + : [port] "N{dx}" (port), + ); +} |
