hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-rw-r--r--boot/ata.zig160
-rw-r--r--boot/boot.zig22
-rw-r--r--boot/port.zig15
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),
+ );
+}