diff --git a/.gitignore b/.gitignore index 2040c29..70c7e68 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ zig-cache +disk.img diff --git a/qemu.sh b/qemu.sh index 63c80b2..6f606af 100755 --- a/qemu.sh +++ b/qemu.sh @@ -4,6 +4,7 @@ QEMU_GDB_PORT=4242 KERNEL=build/kernel start() { + touch disk.img sudo pkill -9 qemu sudo qemu-system-i386 \ -gdb tcp::${QEMU_GDB_PORT} \ @@ -11,6 +12,7 @@ start() { -enable-kvm \ -m 1341M \ -curses \ + -hda disk.img \ -kernel ${KERNEL} # -drive file=disk.img,if=virtio\ # -no-reboot \ diff --git a/src/arch/x86/lib/io.zig b/src/arch/x86/lib/io.zig index f45b3ca..ad3a36a 100644 --- a/src/arch/x86/lib/io.zig +++ b/src/arch/x86/lib/io.zig @@ -19,6 +19,17 @@ pub inline fn inl(port: u16) u32 { ); } +pub inline fn insl(port: u16, addr: var, cnt: usize) void { + asm volatile ("cld; repne; insl;" + : [addr] "={edi}" (addr), + [cnt] "={ecx}" (cnt) + : [port] "{dx}" (port), + [addr] "0" (addr), + [cnt] "1" (cnt) + : "memory", "cc" + ); +} + pub inline fn outb(port: u16, value: u8) void { asm volatile ("outb %[value], %[port]" : diff --git a/src/console.zig b/src/console.zig index 8d45db0..ff3585b 100644 --- a/src/console.zig +++ b/src/console.zig @@ -15,7 +15,6 @@ fn execute(input: []u8) void { if (eql(u8, input, "paging")) return x86.paging.format(); if (eql(u8, input, "memory")) return x86.pmem.format(); if (eql(u8, input, "tasks")) return task.format(); - if (eql(u8, input, "tasks")) return task.format_short(); if (eql(u8, input, "lspci")) return pci.lspci(); if (eql(u8, input, "sleep2")) return sleep_for_2(); if (eql(u8, input, "t-sleep2")) { diff --git a/src/pci/ide.zig b/src/pci/ide.zig new file mode 100644 index 0000000..603d1a1 --- /dev/null +++ b/src/pci/ide.zig @@ -0,0 +1,249 @@ +usingnamespace @import("pci.zig"); + +const IDE_ATA = 0x00; +const IDE_ATAPI = 0x01; + +const ATA_MASTER = 0x00; +const ATA_SLAVE = 0x01; + +// Channels: +const ATA_PRIMARY = 0x00; +const ATA_SECONDARY = 0x01; + +// Directions: +const ATA_READ = 0x00; +const ATA_WRITE = 0x01; + +// Commands +const ATA_CMD_READ_PIO = 0x20; +const ATA_CMD_READ_PIO_EXT = 0x24; +const ATA_CMD_READ_DMA = 0xC8; +const ATA_CMD_READ_DMA_EXT = 0x25; +const ATA_CMD_WRITE_PIO = 0x30; +const ATA_CMD_WRITE_PIO_EXT = 0x34; +const ATA_CMD_WRITE_DMA = 0xCA; +const ATA_CMD_WRITE_DMA_EXT = 0x35; +const ATA_CMD_CACHE_FLUSH = 0xE7; +const ATA_CMD_CACHE_FLUSH_EXT = 0xEA; +const ATA_CMD_PACKET = 0xA0; +const ATA_CMD_IDENTIFY_PACKET = 0xA1; +const ATA_CMD_IDENTIFY = 0xEC; + +// Status: +const ATA_SR_BSY = 0x80; // Busy +const ATA_SR_DRDY = 0x40; // Drive ready +const ATA_SR_DF = 0x20; // Drive write fault +const ATA_SR_DSC = 0x10; // Drive seek complete +const ATA_SR_DRQ = 0x08; // Data request ready +const ATA_SR_CORR = 0x04; // Corrected data +const ATA_SR_IDX = 0x02; // Index +const ATA_SR_ERR = 0x01; // Error + +// Registers: +const ATA_REG_DATA = 0x00; +const ATA_REG_ERROR = 0x01; +const ATA_REG_FEATURES = 0x01; +const ATA_REG_SECCOUNT0 = 0x02; +const ATA_REG_LBA0 = 0x03; +const ATA_REG_LBA1 = 0x04; +const ATA_REG_LBA2 = 0x05; +const ATA_REG_HDDEVSEL = 0x06; +const ATA_REG_COMMAND = 0x07; +const ATA_REG_STATUS = 0x07; +const ATA_REG_SECCOUNT1 = 0x08; +const ATA_REG_LBA3 = 0x09; +const ATA_REG_LBA4 = 0x0A; +const ATA_REG_LBA5 = 0x0B; +const ATA_REG_CONTROL = 0x0C; +const ATA_REG_ALTSTATUS = 0x0C; +const ATA_REG_DEVADDRESS = 0x0D; + +// Identification space +const ATA_IDENT_DEVICETYPE = 0; +const ATA_IDENT_CYLINDERS = 2; +const ATA_IDENT_HEADS = 6; +const ATA_IDENT_SECTORS = 12; +const ATA_IDENT_SERIAL = 20; +const ATA_IDENT_MODEL = 54; +const ATA_IDENT_CAPABILITIES = 98; +const ATA_IDENT_FIELDVALID = 106; +const ATA_IDENT_MAX_LBA = 120; +const ATA_IDENT_COMMANDSETS = 164; +const ATA_IDENT_MAX_LBA_EXT = 200; + +const ide_buf: [2048]u8 = [1]u8{0} ** 2048; +const atapi_packet: [12]u8 = [1]u8{0xA8} ++ [1]u8{0} ** 11; +const ide_irq_invoked = false; + +const IDEDevice = struct { + reserved: u8, // 0 (Empty) or 1 (This Drive really exists). + channel: u8, // 0 (Primary Channel) or 1 (Secondary Channel). + drive: u8, // 0 (Master Drive) or 1 (Slave Drive). + idetype: u16, // 0: ATA, 1:ATAPI. + signature: u16, // Drive Signature + capabilities: u16, // Features. + commandsets: usize, // Command Sets Supported. + size: usize, // Size in Sectors. + model: [41]u8, // Model in string. +}; + +var ide_devices: [4]IDEDevice = undefined; + +const IDEChannelRegister = struct { + base: u16, // I/O Base. + ctrl: u16, // Control Base + bmide: u16, // Bus Master IDE + nIEN: u8, // nIEN (No Interrupt); +}; + +var channels: [2]IDEChannelRegister = undefined; + +pub inline fn ide_read(channel: u8, comptime reg: u8) u8 { + if (reg > 0x07 and reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN); + defer if (reg > 0x07 and reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN); + return switch (reg) { + 0x0...0x7 => x86.inb(channels[channel].base + reg - 0x0), + 0x8...0xb => x86.inb(channels[channel].base + reg - 0x6), + 0xc...0xd => x86.inb(channels[channel].ctrl + reg - 0xa), + 0xe...0x16 => x86.inb(channels[channel].bmide + reg - 0xe), + else => @compileError("bad IDE register."), + }; +} + +pub inline fn ide_read_buffer(channel: u8, comptime reg: u8, buf: var, cnt: usize) void { + if (reg > 0x07 and reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN); + defer if (reg > 0x07 and reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN); + switch (reg) { + 0x0...0x7 => x86.insl(channels[channel].base + reg - 0x0, buf, cnt), + 0x8...0xb => x86.insl(channels[channel].base + reg - 0x6, buf, cnt), + 0xc...0xd => x86.insl(channels[channel].ctrl + reg - 0xa, buf, cnt), + 0xe...0x16 => x86.insl(channels[channel].bmide + reg - 0xe, buf, cnt), + else => @compileError("bad IDE register."), + } +} + +pub inline fn ide_write(channel: u8, comptime reg: u8, data: u8) void { + if (reg > 0x07 and reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, 0x80 | channels[channel].nIEN); + defer if (reg > 0x07 and reg < 0x0C) ide_write(channel, ATA_REG_CONTROL, channels[channel].nIEN); + switch (reg) { + 0x0...0x7 => x86.outb(channels[channel].base + reg - 0x0, data), + 0x8...0xb => x86.outb(channels[channel].base + reg - 0x6, data), + 0xc...0xd => x86.outb(channels[channel].ctrl + reg - 0xa, data), + 0xe...0x16 => x86.outb(channels[channel].bmide + reg - 0xe, data), + else => @compileError("bad IDE register."), + } +} + +pub fn init(dev: PciDevice) void { + println("-- ide init --"); + print("[ide] "); + dev.format(); + assert(dev.header_type() == 0x0); // mass storage device + + dev.config_write(@intCast(u8, 0xfe), 0x3c); + if (dev.intr_line() == 0xfe) { + println("[ide] needs IRQ assignement"); + } else { + println("The device doesn't use IRQs"); + } + + const BAR0 = @intCast(u16, dev.bar(0)); + const BAR1 = @intCast(u16, dev.bar(1)); + const BAR2 = @intCast(u16, dev.bar(2)); + const BAR3 = @intCast(u16, dev.bar(3)); + const BAR4 = @intCast(u16, dev.bar(4)); + channels[ATA_PRIMARY].base = if (BAR0 == 0) 0x1f0 else BAR0; + channels[ATA_PRIMARY].ctrl = if (BAR1 == 0) 0x3F6 else BAR1; + channels[ATA_SECONDARY].base = if (BAR2 == 0) 0x170 else BAR2; + channels[ATA_SECONDARY].ctrl = if (BAR3 == 0) 0x376 else BAR3; + channels[ATA_PRIMARY].bmide = (BAR4 & 0xFFFC); // Bus Master IDE + channels[ATA_SECONDARY].bmide = (BAR4 & 0xFFFC) + 8; // Bus Master IDE + + // turn off irqs + ide_write(ATA_PRIMARY, ATA_REG_CONTROL, 2); + ide_write(ATA_SECONDARY, ATA_REG_CONTROL, 2); + + // parse identification space + var count: usize = 0; + var err: u8 = 0; + var status: u8 = 0; + for ([_]u8{ 0, 1 }) |i| { + for ([_]u8{ 0, 1 }) |j| { + var idetype: u8 = IDE_ATA; + ide_devices[count].reserved = 0; // Assuming that no drive here. + + // (I) Select Drive: + ide_write(i, ATA_REG_HDDEVSEL, 0xA0 | (j << 4)); // Select Drive. + task.usleep(1000) catch unreachable; // Wait 1ms for drive select to work. + + // (II) Send ATA Identify Command: + ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY); + task.usleep(1000) catch unreachable; + + if (ide_read(i, ATA_REG_STATUS) == 0) continue; // If Status = 0, No Device. + while (true) { + status = ide_read(i, ATA_REG_STATUS); + if (status & ATA_SR_ERR != 0) { + err = 1; + break; + } // If Err, Device is not ATA. + if ((status & ATA_SR_BSY == 0) and (status & ATA_SR_DRQ != 0)) break; // Everything is right. + } + + // (IV) Probe for ATAPI Devices:) + if (err != 0) { + // Device is not ATA + const cl = ide_read(i, ATA_REG_LBA1); + const ch = ide_read(i, ATA_REG_LBA2); + + if (cl == 0x14 and ch == 0xEB) idetype = IDE_ATAPI; + if (cl == 0x69 and ch == 0x96) idetype = IDE_ATAPI; + if (idetype != IDE_ATAPI) continue; // Unknown Type (may not be a device). + + ide_write(i, ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET); + task.usleep(1000) catch unreachable; + } + + // (V) Read Identification Space of the Device: + ide_read_buffer(i, ATA_REG_DATA, &ide_buf, 128); + + ide_devices[count].reserved = 1; + ide_devices[count].idetype = idetype; + ide_devices[count].channel = i; + ide_devices[count].drive = j; + ide_devices[count].signature = @ptrCast(*const u8, &ide_buf[ATA_IDENT_DEVICETYPE]).*; + ide_devices[count].capabilities = @ptrCast(*const u8, &ide_buf[ATA_IDENT_CAPABILITIES]).*; + ide_devices[count].commandsets = @ptrCast(*const usize, &ide_buf[ATA_IDENT_COMMANDSETS]).*; + + // (VII) Get Size: + if (ide_devices[count].commandsets & (1 << 26) != 0) { + // Device uses 48-Bit Addressing: + ide_devices[count].size = @ptrCast(*const usize, &ide_buf[ATA_IDENT_MAX_LBA_EXT]).*; + } else { + // Device uses CHS or 28-bit Addressing: + ide_devices[count].size = @ptrCast(*const usize, &ide_buf[ATA_IDENT_MAX_LBA]).*; + } + + // (VIII) String indicates model of device (like Western Digital HDD and SONY DVD-RW...): + var k: u16 = 0; + while (k < 40) : (k = k + 2) { + ide_devices[count].model[k] = ide_buf[ATA_IDENT_MODEL + k + 1]; + ide_devices[count].model[k + 1] = ide_buf[ATA_IDENT_MODEL + k]; + } + ide_devices[count].model[40] = 0; // Terminate String. + + count = count + 1; + } + } + // 4- Print Summary: + for ([_]u8{ 0, 1, 2, 3 }) |i| { + if (ide_devices[i].reserved == 1) { + println( + "[ide] found {} Drive {}GB - {}", + if (ide_devices[i].idetype == 0) "ATA" else "ATAPI", + ide_devices[i].size / 1024 / 1024 / 2, + ide_devices[i].model, + ); + } + } +} diff --git a/src/pci/pci.zig b/src/pci/pci.zig index 0606b37..7cbf031 100644 --- a/src/pci/pci.zig +++ b/src/pci/pci.zig @@ -1,5 +1,6 @@ pub usingnamespace @import("../index.zig"); pub const virtio = @import("virtio.zig"); +pub const ide = @import("ide.zig"); const PCI_CONFIG_ADDRESS = 0xCF8; const PCI_CONFIG_DATA = 0xCFC; @@ -94,12 +95,30 @@ pub const PciDevice = struct { pub fn header_type(self: PciDevice) u8 { return self.config_read(u8, 0xe); } - + pub fn intr_line(self: PciDevice) u8 { + return self.config_read(u8, 0x3c); + } + pub fn bar(self: PciDevice, comptime n: usize) u32 { + assert(n <= 5); + return self.config_read(u32, 0x10 + 4 * n); + } // only for header_type == 0 pub fn subsystem(self: PciDevice) u16 { return self.config_read(u8, 0x2e); } + pub inline fn config_write(self: PciDevice, value: var, comptime offset: u8) void { + // ask for access before writing config + x86.outl(PCI_CONFIG_ADDRESS, self.address(offset)); + switch (@typeOf(value)) { + // read the correct size + u8 => return x86.outb(PCI_CONFIG_DATA, value), + u16 => return x86.outw(PCI_CONFIG_DATA, value), + u32 => return x86.outl(PCI_CONFIG_DATA, value), + else => @compileError("pci config space only supports writing u8, u16, u32."), + } + } + pub inline fn config_read(self: PciDevice, comptime size: type, comptime offset: u8) size { // ask for access before reading config x86.outl(PCI_CONFIG_ADDRESS, self.address(offset)); @@ -114,7 +133,7 @@ pub const PciDevice = struct { }; const Driver = struct { - name: [*]u8, + name: []const u8, class: u8, subclass: u8, vendor: ?u16 = null, @@ -122,8 +141,10 @@ const Driver = struct { init: fn (PciDevice) void, }; -const name = "virtio-blk"; -pub var Drivers: [1]Driver = [_]Driver{Driver{ .name = &name, .class = 0x1, .subclass = 0x0, .vendor = 0x1af4, .subsystem = 0x2, .init = virtio.init }}; +pub var Drivers = [_]Driver{ + Driver{ .name = "virtio-blk"[0..], .class = 0x1, .subclass = 0x0, .vendor = 0x1af4, .subsystem = 0x2, .init = virtio.init }, + Driver{ .name = "ide-ata", .class = 0x1, .subclass = 0x1, .init = ide.init }, +}; // TODO: factor 2 functions when anonymous fn is released pub fn scan() void {