From f0caf010bf5e08d114cda1891f4adee295eb5b4b Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 10 May 2023 19:14:23 +0200 Subject: [PATCH 01/28] kernel/x86_64: Add a way to register IRQ handlers from other kernel subsystems --- kernel/src/arch/CPU.h | 2 ++ kernel/src/arch/x86_64/CPU.asm | 14 +++++++++ kernel/src/arch/x86_64/CPU.cpp | 52 +++++++++++++++++++++++++++------- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/kernel/src/arch/CPU.h b/kernel/src/arch/CPU.h index 4e994339..3d9a2849 100644 --- a/kernel/src/arch/CPU.h +++ b/kernel/src/arch/CPU.h @@ -29,5 +29,7 @@ namespace CPU u16 get_processor_id(); + bool register_interrupt(u8 interrupt, void (*handler)(Registers*)); + void pause(); } diff --git a/kernel/src/arch/x86_64/CPU.asm b/kernel/src/arch/x86_64/CPU.asm index f7989b4e..b8b433d2 100644 --- a/kernel/src/arch/x86_64/CPU.asm +++ b/kernel/src/arch/x86_64/CPU.asm @@ -188,4 +188,18 @@ ISR_ERROR 21 ; control-protection exception (#CP) ; ISR 22-31 reserved IRQ 32, 0 ; timer interrupt IRQ 33, 1 ; keyboard interrupt +IRQ 34, 2 +IRQ 35, 3 +IRQ 36, 4 +IRQ 37, 5 +IRQ 38, 6 +IRQ 39, 7 +IRQ 40, 8 +IRQ 41, 9 +IRQ 42, 10 +IRQ 43, 11 +IRQ 44, 12 +IRQ 45, 13 +IRQ 46, 14 +IRQ 47, 15 ISR 66 ; system call diff --git a/kernel/src/arch/x86_64/CPU.cpp b/kernel/src/arch/x86_64/CPU.cpp index 5b8e957d..ab3e213c 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -28,6 +28,10 @@ extern void setup_idt(); static Thread* g_io_thread; +typedef void (*interrupt_handler_t)(Registers*); + +static interrupt_handler_t irq_handlers[16]; + void FPData::save() { asm volatile("fxsave (%0)" : : "r"(m_data)); @@ -150,21 +154,34 @@ void io_thread() } } +static void timer_interrupt(Registers* regs) +{ + Timer::tick(); + if (should_invoke_scheduler()) Scheduler::invoke(regs); +} + +static void keyboard_interrupt(Registers*) +{ + u8 scancode = IO::inb(0x60); + scancode_queue.try_push(scancode); + g_io_thread->wake_up(); +} + // Called from _asm_interrupt_entry extern "C" void arch_interrupt_entry(Registers* regs) { if (regs->isr < 32) handle_x86_exception(regs); - else if (regs->isr == 32) // Timer interrupt + else if (regs->isr >= 32 && regs->isr < 48) // IRQ from the PIC { - Timer::tick(); - if (should_invoke_scheduler()) Scheduler::invoke(regs); - pic_eoi(regs); - } - else if (regs->isr == 33) // Keyboard interrupt - { - u8 scancode = IO::inb(0x60); - scancode_queue.try_push(scancode); - g_io_thread->wake_up(); + u64 irq = regs->error; + interrupt_handler_t handler = irq_handlers[irq]; + if (!handler) + { + kwarnln("Unhandled IRQ catched! Halting."); + CPU::efficient_halt(); + } + + handler(regs); pic_eoi(regs); } else if (regs->isr == 66) // System call @@ -174,7 +191,7 @@ extern "C" void arch_interrupt_entry(Registers* regs) } else { - kwarnln("IRQ catched! Halting."); + kwarnln("Unhandled interrupt catched! Halting."); CPU::efficient_halt(); } } @@ -233,6 +250,10 @@ namespace CPU kwarnln("not setting the NX bit as it is unsupported"); setup_gdt(); setup_idt(); + + memset(irq_handlers, 0, sizeof(irq_handlers)); + register_interrupt(0, timer_interrupt); + register_interrupt(1, keyboard_interrupt); } void platform_finish_init() @@ -353,6 +374,15 @@ namespace CPU __get_cpuid(1, &unused, &ebx, &unused, &unused); return (u16)(ebx >> 24); } + + bool register_interrupt(u8 interrupt, interrupt_handler_t handler) + { + if (irq_handlers[interrupt]) return false; + + irq_handlers[interrupt] = handler; + + return true; + } } // called by kernel_yield -- 2.34.1 From beeafb73e6e5125355c97fcce9730a72714ab0f5 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 10 May 2023 19:15:02 +0200 Subject: [PATCH 02/28] kernel/PCI: Add more PCI field types --- kernel/src/arch/PCI.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/src/arch/PCI.h b/kernel/src/arch/PCI.h index 9157c4aa..5e457048 100644 --- a/kernel/src/arch/PCI.h +++ b/kernel/src/arch/PCI.h @@ -17,7 +17,14 @@ namespace PCI HeaderType = 0x0e, - SecondaryBus = 0x19 + BAR0 = 0x10, + BAR1 = 0x14, + BAR2 = 0x18, + BAR3 = 0x1c, + + SecondaryBus = 0x19, + + InterruptLine = 0x3c, }; struct Device -- 2.34.1 From 7efc6dc985f197de61992f4d1c411807ea2e9e8e Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 10 May 2023 19:15:47 +0200 Subject: [PATCH 03/28] kernel/x86_64: Add basic ATA controller and channel identification --- kernel/CMakeLists.txt | 1 + kernel/src/arch/x86_64/disk/ATA.cpp | 73 +++++++++++++++++++++++++++++ kernel/src/arch/x86_64/disk/ATA.h | 72 ++++++++++++++++++++++++++++ kernel/src/main.cpp | 14 +++--- 4 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 kernel/src/arch/x86_64/disk/ATA.cpp create mode 100644 kernel/src/arch/x86_64/disk/ATA.h diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 2e2e40d2..8c266e6d 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -65,6 +65,7 @@ if("${LUNA_ARCH}" MATCHES "x86_64") src/arch/x86_64/Thread.cpp src/arch/x86_64/PCI.cpp src/arch/x86_64/Keyboard.cpp + src/arch/x86_64/disk/ATA.cpp src/arch/x86_64/init/GDT.cpp src/arch/x86_64/init/IDT.cpp src/arch/x86_64/init/PIC.cpp diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp new file mode 100644 index 00000000..0c371d4d --- /dev/null +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -0,0 +1,73 @@ +#include "arch/x86_64/disk/ATA.h" +#include "Log.h" +#include "arch/x86_64/IO.h" +#include + +SharedPtr g_controller; + +namespace ATA +{ + Result Controller::scan() + { + // FIXME: Propagate errors. + PCI::scan( + [](const PCI::Device& device) { + if (!g_controller) + { + auto controller = adopt_shared_if_nonnull(new (std::nothrow) Controller(device)).release_value(); + kinfoln("ata: Found ATA controller on PCI bus (%x:%x:%x)", device.address.bus, + device.address.function, device.address.slot); + + if (controller->initialize()) g_controller = controller; + } + }, + { .klass = 1, .subclass = 1 }); + + return {}; + } + + bool Controller::initialize() + { + if (!m_primary_channel.initialize()) return false; + + return m_secondary_channel.initialize(); + } + + Controller::Controller(const PCI::Device& device) + : m_device(device), m_primary_channel(this, 0, {}), m_secondary_channel(this, 1, {}) + { + } + + Channel::Channel(Controller* controller, u8 channel_index, Badge) + : m_controller(controller), m_channel_index(channel_index) + { + } + + bool Channel::initialize() + { + int offset = m_channel_index ? 2 : 0; + m_is_pci_native_mode = m_controller->device().type.prog_if & (1 << offset); + kinfoln("ata: Channel %d is %sin PCI native mode", m_channel_index, m_is_pci_native_mode ? "" : "not "); + + if (m_is_pci_native_mode) + { + m_base_address = PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR2 : PCI::BAR0); + m_control_port_base_address = + PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR3 : PCI::BAR1); + } + else + { + m_base_address = m_channel_index ? 0x170 : 0x1f0; + m_control_port_base_address = m_channel_index ? 0x376 : 0x3f6; + } + + u8 interrupt_line; + if (m_is_pci_native_mode) interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine); + else + interrupt_line = m_channel_index ? 15 : 14; + + kinfoln("ata: Channel %d uses IRQ %hhu", m_channel_index, interrupt_line); + + return true; + } +} diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h new file mode 100644 index 00000000..ad46feef --- /dev/null +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -0,0 +1,72 @@ +#pragma once +#include "arch/PCI.h" +#include "fs/devices/DeviceRegistry.h" +#include +#include + +namespace ATA +{ + class Controller; + class Channel + { + public: + Channel(Controller* controller, u8 channel_index, Badge); + + bool initialize(); + + private: + Controller* m_controller; + u8 m_channel_index; + bool m_is_pci_native_mode; + + u32 m_base_address; + u32 m_control_port_base_address; + }; + + class Controller + { + public: + static Result scan(); + + const PCI::Device& device() const + { + return m_device; + } + + bool initialize(); + + private: + Controller(const PCI::Device& device); + PCI::Device m_device; + Channel m_primary_channel; + Channel m_secondary_channel; + + Atomic m_hwlock = 0; + }; + +} + +class ATADevice : public Device +{ + public: + // Initializer for DeviceRegistry. + static Result create(SharedPtr controller, int channel, int drive); + + Result read(u8*, usize, usize) const override; + + Result write(const u8*, usize, usize) override; + + bool blocking() const override; + + Result ioctl(int request, void* arg) override; + + usize size() const override; + + virtual ~ATADevice() = default; + + private: + ATADevice(SharedPtr controller); + SharedPtr m_controller; + int m_channel; + int m_drive; +}; diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 31dc8137..fc56ed17 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -1,6 +1,5 @@ #include "Log.h" #include "arch/CPU.h" -#include "arch/PCI.h" #include "arch/Timer.h" #include "boot/Init.h" #include "config.h" @@ -11,6 +10,10 @@ #include "thread/Scheduler.h" #include +#ifdef ARCH_X86_64 +#include "arch/x86_64/disk/ATA.h" +#endif + extern void set_host_name(StringView); void reap_thread() @@ -53,12 +56,9 @@ Result init() auto reap = Scheduler::new_kernel_thread(reap_thread, "[reap]").release_value(); Scheduler::set_reap_thread(reap); - PCI::scan( - [](const PCI::Device& device) { - kinfoln("Found PCI mass storage device %.4x:%.4x, at address %u:%u:%u", device.id.vendor, device.id.device, - device.address.bus, device.address.slot, device.address.function); - }, - { .klass = 1 }); +#ifdef ARCH_X86_64 + ATA::Controller::scan(); +#endif // Disable console logging before transferring control to userspace. setup_log(log_debug_enabled(), log_serial_enabled(), false); -- 2.34.1 From d9a1e8a980a6a6d68db8eb66bcb0459bbea6ffd8 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 10 May 2023 21:58:30 +0200 Subject: [PATCH 04/28] kernel: Add a KMutex class and use that for ATA::Controller locking --- kernel/src/arch/x86_64/disk/ATA.h | 3 +- kernel/src/lib/KMutex.h | 78 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 kernel/src/lib/KMutex.h diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index ad46feef..03d7cbd7 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -1,6 +1,7 @@ #pragma once #include "arch/PCI.h" #include "fs/devices/DeviceRegistry.h" +#include "lib/KMutex.h" #include #include @@ -41,7 +42,7 @@ namespace ATA Channel m_primary_channel; Channel m_secondary_channel; - Atomic m_hwlock = 0; + KMutex<100> m_lock; }; } diff --git a/kernel/src/lib/KMutex.h b/kernel/src/lib/KMutex.h new file mode 100644 index 00000000..d55a947e --- /dev/null +++ b/kernel/src/lib/KMutex.h @@ -0,0 +1,78 @@ +#pragma once +#include "Log.h" +#include "arch/CPU.h" +#include "thread/Scheduler.h" +#include "thread/Thread.h" +#include + +template class KMutex +{ + public: + void lock() + { + int expected = 0; + while (!m_lock.compare_exchange_strong(expected, 1)) + { + expected = 0; + auto* current = Scheduler::current(); + + // We cannot be interrupted between these functions, otherwise we might never exit the loop + CPU::disable_interrupts(); + bool ok = m_blocked_threads.try_push(current); + if (!ok) kernel_sleep(10); + else + kernel_wait_for_event(); + CPU::enable_interrupts(); + } + }; + + void unlock() + { + int expected = 1; + if (!m_lock.compare_exchange_strong(expected, 0)) + { + kwarnln("KMutex::unlock() called on an unlocked lock with value %d", expected); + } + + Thread* blocked; + if (m_blocked_threads.try_pop(blocked)) blocked->wake_up(); + } + + bool try_lock() + { + int expected = 0; + return m_lock.compare_exchange_strong(expected, 1); + } + + private: + CircularQueue m_blocked_threads; + Atomic m_lock; +}; + +template class ScopedKMutexLock +{ + public: + ScopedKMutexLock(KMutex& lock) + { + m_lock = lock; + m_lock.lock(); + } + + ~ScopedKMutexLock() + { + if (!m_taken_over) m_lock.unlock(); + } + + ScopedKMutexLock(const ScopedKMutexLock&) = delete; + ScopedKMutexLock(ScopedKMutexLock&&) = delete; + + KMutex& take_over() + { + m_taken_over = true; + return m_lock; + } + + private: + KMutex& m_lock; + bool m_taken_over { false }; +}; -- 2.34.1 From e8507d23ee41e968b26e33436e81d9307fe45fb4 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 11 May 2023 19:27:05 +0200 Subject: [PATCH 05/28] kernel: Warn if no ATA controller is found --- kernel/src/arch/x86_64/disk/ATA.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 0c371d4d..0336ed0d 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -23,6 +23,8 @@ namespace ATA }, { .klass = 1, .subclass = 1 }); + if (!g_controller) kwarnln("ata: No ATA controller found."); + return {}; } -- 2.34.1 From 3a84127fd681cd19a48413d3bf878aabd778092c Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 14:11:09 +0200 Subject: [PATCH 06/28] kernel/ATA: Start reading/writing registers and detecting drives --- kernel/src/arch/x86_64/disk/ATA.cpp | 78 +++++++++++++++++++++++++---- kernel/src/arch/x86_64/disk/ATA.h | 53 ++++++++++++++++++-- kernel/src/lib/KMutex.h | 3 +- 3 files changed, 118 insertions(+), 16 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 0336ed0d..680bf4a8 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -45,30 +45,88 @@ namespace ATA { } + u8 Channel::read_register(Register reg) + { + return IO::inb(m_io_base + (u16)reg); + } + + void Channel::write_register(Register reg, u8 value) + { + IO::outb(m_io_base + (u16)reg, value); + } + + u8 Channel::read_control(ControlRegister reg) + { + return IO::inb(m_control_base + (u16)reg); + } + + void Channel::write_control(ControlRegister reg, u8 value) + { + IO::outb(m_control_base + (u16)reg, value); + } + + void Channel::delay_400ns() + { + read_control(ControlRegister::AltStatus); + read_control(ControlRegister::AltStatus); + read_control(ControlRegister::AltStatus); + read_control(ControlRegister::AltStatus); + } + + void Channel::select(u8 drive) + { + if (drive == m_current_drive) return; + + u8 value = (drive << 4) | 0xa0; + write_register(Register::DriveSelect, value); + + delay_400ns(); + + m_current_drive = drive; + } + bool Channel::initialize() { int offset = m_channel_index ? 2 : 0; m_is_pci_native_mode = m_controller->device().type.prog_if & (1 << offset); - kinfoln("ata: Channel %d is %sin PCI native mode", m_channel_index, m_is_pci_native_mode ? "" : "not "); + + u32 control_port_base_address; + u32 io_base_address; if (m_is_pci_native_mode) { - m_base_address = PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR2 : PCI::BAR0); - m_control_port_base_address = + // FIXME: Properly decode BARs. + io_base_address = PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR2 : PCI::BAR0); + control_port_base_address = PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR3 : PCI::BAR1); } else { - m_base_address = m_channel_index ? 0x170 : 0x1f0; - m_control_port_base_address = m_channel_index ? 0x376 : 0x3f6; + io_base_address = m_channel_index ? 0x170 : 0x1f0; + control_port_base_address = m_channel_index ? 0x376 : 0x3f6; } - u8 interrupt_line; - if (m_is_pci_native_mode) interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine); - else - interrupt_line = m_channel_index ? 15 : 14; + m_io_base = (u16)io_base_address; + m_control_base = (u16)control_port_base_address + 2; - kinfoln("ata: Channel %d uses IRQ %hhu", m_channel_index, interrupt_line); + if (m_is_pci_native_mode) m_interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine); + else + m_interrupt_line = m_channel_index ? 15 : 14; + + for (u8 drive = 0; drive < 2; drive++) + { + ScopedKMutexLock<100> lock(m_lock); + + select(drive); + + if (read_register(Register::Status) == 0) + { + // No drive on this slot. + continue; + } + + kinfoln("ata: Channel %d has a drive on slot %d!", m_channel_index, drive); + } return true; } diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 03d7cbd7..39ab2d21 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -7,12 +7,53 @@ namespace ATA { + enum class Register : u16 + { + Data = 0, + Error = 1, + Features = 1, + SectorCount = 2, + SectorNumber = 3, + LBALow = 3, + CylinderLow = 4, + LBAMiddle = 4, + CylinderHigh = 5, + LBAHigh = 5, + DriveSelect = 6, + Status = 7, + Command = 8, + }; + + enum class ControlRegister : u16 + { + AltStatus = 0, + DeviceControl = 0, + DriveAddress = 1, + }; + class Controller; + class Channel; + + class Drive + { + private: + Channel* m_channel; + }; + class Channel { public: Channel(Controller* controller, u8 channel_index, Badge); + u8 read_register(Register reg); + void write_register(Register reg, u8 value); + u8 read_control(ControlRegister reg); + void write_control(ControlRegister reg, u8 value); + + void delay_400ns(); + + void select(u8 drive); + bool initialize(); private: @@ -20,8 +61,14 @@ namespace ATA u8 m_channel_index; bool m_is_pci_native_mode; - u32 m_base_address; - u32 m_control_port_base_address; + u8 m_interrupt_line; + + KMutex<100> m_lock {}; + + u16 m_io_base; + u16 m_control_base; + + u8 m_current_drive = (u8)-1; }; class Controller @@ -41,8 +88,6 @@ namespace ATA PCI::Device m_device; Channel m_primary_channel; Channel m_secondary_channel; - - KMutex<100> m_lock; }; } diff --git a/kernel/src/lib/KMutex.h b/kernel/src/lib/KMutex.h index d55a947e..8929de06 100644 --- a/kernel/src/lib/KMutex.h +++ b/kernel/src/lib/KMutex.h @@ -52,9 +52,8 @@ template class KMutex template class ScopedKMutexLock { public: - ScopedKMutexLock(KMutex& lock) + ScopedKMutexLock(KMutex& lock) : m_lock(lock) { - m_lock = lock; m_lock.lock(); } -- 2.34.1 From 739950e8f05c42b4f80f69bdb01fabe49fe244fc Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 14:14:12 +0200 Subject: [PATCH 07/28] kernel/CPU: Allow passing arbitrary data to interrupt handlers --- kernel/src/arch/CPU.h | 2 +- kernel/src/arch/x86_64/CPU.cpp | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/kernel/src/arch/CPU.h b/kernel/src/arch/CPU.h index 3d9a2849..f446a0a0 100644 --- a/kernel/src/arch/CPU.h +++ b/kernel/src/arch/CPU.h @@ -29,7 +29,7 @@ namespace CPU u16 get_processor_id(); - bool register_interrupt(u8 interrupt, void (*handler)(Registers*)); + bool register_interrupt(u8 interrupt, void (*handler)(Registers*, void*), void* context); void pause(); } diff --git a/kernel/src/arch/x86_64/CPU.cpp b/kernel/src/arch/x86_64/CPU.cpp index ab3e213c..4c719eb3 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -28,9 +28,15 @@ extern void setup_idt(); static Thread* g_io_thread; -typedef void (*interrupt_handler_t)(Registers*); +typedef void (*interrupt_handler_t)(Registers*, void*); -static interrupt_handler_t irq_handlers[16]; +struct InterruptHandler +{ + interrupt_handler_t function; + void* context; +}; + +static InterruptHandler irq_handlers[16]; void FPData::save() { @@ -154,13 +160,13 @@ void io_thread() } } -static void timer_interrupt(Registers* regs) +static void timer_interrupt(Registers* regs, void*) { Timer::tick(); if (should_invoke_scheduler()) Scheduler::invoke(regs); } -static void keyboard_interrupt(Registers*) +static void keyboard_interrupt(Registers*, void*) { u8 scancode = IO::inb(0x60); scancode_queue.try_push(scancode); @@ -174,14 +180,14 @@ extern "C" void arch_interrupt_entry(Registers* regs) else if (regs->isr >= 32 && regs->isr < 48) // IRQ from the PIC { u64 irq = regs->error; - interrupt_handler_t handler = irq_handlers[irq]; - if (!handler) + auto handler = irq_handlers[irq]; + if (!handler.function) { kwarnln("Unhandled IRQ catched! Halting."); CPU::efficient_halt(); } - handler(regs); + handler.function(regs, handler.context); pic_eoi(regs); } else if (regs->isr == 66) // System call @@ -252,8 +258,8 @@ namespace CPU setup_idt(); memset(irq_handlers, 0, sizeof(irq_handlers)); - register_interrupt(0, timer_interrupt); - register_interrupt(1, keyboard_interrupt); + register_interrupt(0, timer_interrupt, nullptr); + register_interrupt(1, keyboard_interrupt, nullptr); } void platform_finish_init() @@ -375,11 +381,11 @@ namespace CPU return (u16)(ebx >> 24); } - bool register_interrupt(u8 interrupt, interrupt_handler_t handler) + bool register_interrupt(u8 interrupt, interrupt_handler_t handler, void* context) { - if (irq_handlers[interrupt]) return false; + if (irq_handlers[interrupt].function) return false; - irq_handlers[interrupt] = handler; + irq_handlers[interrupt] = { handler, context }; return true; } -- 2.34.1 From ee691bbb0f2abebe7094b5496631b51fa4e6eb2f Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 14:24:45 +0200 Subject: [PATCH 08/28] kernel/ATA: Handle drive IRQs in compatibility mode --- kernel/src/arch/x86_64/disk/ATA.cpp | 29 +++++++++++++++++++++++++++++ kernel/src/arch/x86_64/disk/ATA.h | 5 +++++ 2 files changed, 34 insertions(+) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 680bf4a8..b4b1aca8 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -5,6 +5,11 @@ SharedPtr g_controller; +static void irq_handler(Registers* regs, void* ctx) +{ + ((ATA::Channel*)ctx)->irq_handler(regs); +} + namespace ATA { Result Controller::scan() @@ -85,6 +90,22 @@ namespace ATA m_current_drive = drive; } + void Channel::irq_handler(Registers*) + { + // FIXME: Read the Busmaster register to make sure the IRQ is for this channel. + + // FIXME: Also read it in case there was a DMA read error. + + m_thread->wake_up(); + } + + void Channel::wait_for_irq() + { + m_thread = Scheduler::current(); + + kernel_wait_for_event(); + } + bool Channel::initialize() { int offset = m_channel_index ? 2 : 0; @@ -113,6 +134,14 @@ namespace ATA else m_interrupt_line = m_channel_index ? 15 : 14; + bool ok = CPU::register_interrupt(m_interrupt_line, ::irq_handler, this); + if (!ok) + { + kerrorln("ata: Failed to register IRQ handler for ATA channel %d (IRQ %d)", m_channel_index, + m_interrupt_line); + return false; + } + for (u8 drive = 0; drive < 2; drive++) { ScopedKMutexLock<100> lock(m_lock); diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 39ab2d21..18766f91 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -52,6 +52,9 @@ namespace ATA void delay_400ns(); + void wait_for_irq(); + void irq_handler(Registers*); + void select(u8 drive); bool initialize(); @@ -65,6 +68,8 @@ namespace ATA KMutex<100> m_lock {}; + Thread* m_thread; + u16 m_io_base; u16 m_control_base; -- 2.34.1 From e118c9ea0d0639d3cf7340596e237e35c2595712 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 16:13:26 +0200 Subject: [PATCH 09/28] kernel/ATA: Implement enough to send an IDENTIFY command and read the model number :) --- kernel/src/arch/x86_64/disk/ATA.cpp | 149 +++++++++++++++++++++++++++- kernel/src/arch/x86_64/disk/ATA.h | 52 +++++++++- libluna/include/luna/StaticString.h | 33 ++++++ 3 files changed, 229 insertions(+), 5 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index b4b1aca8..5f84bd68 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -1,5 +1,7 @@ #include "arch/x86_64/disk/ATA.h" #include "Log.h" +#include "arch/Serial.h" +#include "arch/Timer.h" #include "arch/x86_64/IO.h" #include @@ -55,6 +57,11 @@ namespace ATA return IO::inb(m_io_base + (u16)reg); } + u16 Channel::read_data() + { + return IO::inw(m_io_base + (u16)Register::Data); + } + void Channel::write_register(Register reg, u8 value) { IO::outb(m_io_base + (u16)reg, value); @@ -72,10 +79,8 @@ namespace ATA void Channel::delay_400ns() { - read_control(ControlRegister::AltStatus); - read_control(ControlRegister::AltStatus); - read_control(ControlRegister::AltStatus); - read_control(ControlRegister::AltStatus); + // FIXME: We should use kernel_sleep(), but it doesn't support nanosecond granularity. + for (int i = 0; i < 14; i++) { read_control(ControlRegister::AltStatus); } } void Channel::select(u8 drive) @@ -96,6 +101,8 @@ namespace ATA // FIXME: Also read it in case there was a DMA read error. + if (m_current_drive < 2 && m_drives[m_current_drive]) m_drives[m_current_drive]->irq_handler(); + m_thread->wake_up(); } @@ -106,6 +113,30 @@ namespace ATA kernel_wait_for_event(); } + bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout) + { + u64 begin = Timer::ticks_ms(); + while (true) + { + u8 reg_value = read_register(reg); + if (reg_value & value) return true; + if ((Timer::ticks_ms() - begin) >= timeout) return false; + kernel_sleep(1); + } + } + + bool Channel::wait_for_reg_clear(Register reg, u8 value, u64 timeout) + { + u64 begin = Timer::ticks_ms(); + while (true) + { + u8 reg_value = read_register(reg); + if ((reg_value & value) == 0) return true; + if ((Timer::ticks_ms() - begin) >= timeout) return false; + kernel_sleep(1); + } + } + bool Channel::initialize() { int offset = m_channel_index ? 2 : 0; @@ -155,8 +186,118 @@ namespace ATA } kinfoln("ata: Channel %d has a drive on slot %d!", m_channel_index, drive); + + auto rc = adopt_shared_if_nonnull(new (std::nothrow) Drive(this, drive, {})); + if (rc.has_error()) + { + kinfoln("ata: Failed to create drive object: %s", rc.error_string()); + return false; + } + + m_drives[drive] = rc.release_value(); + + if (!m_drives[drive]->initialize()) + { + m_drives[drive] = {}; + return false; + } } return true; } + + Drive::Drive(Channel* channel, u8 drive_index, Badge) : m_channel(channel), m_drive_index(drive_index) + { + } + + bool Drive::identify_ata() + { + m_channel->write_register(Register::Command, m_is_atapi ? CMD_Identify_Packet : CMD_Identify); + + m_channel->delay_400ns(); + + if (!m_channel->wait_for_reg_clear(Register::Status, SR_Busy, 1000)) + { + kwarnln("ata: Drive %d timed out clearing SR_Busy (waited for 1000 ms)", m_drive_index); + return false; + } + + if (m_channel->read_register(Register::Status) & SR_Error) + { + u8 lbam = m_channel->read_register(Register::LBAMiddle); + u8 lbah = m_channel->read_register(Register::LBAHigh); + + if ((lbam == 0x14 && lbah == 0xeb) || (lbam == 0x69 && lbah == 0x96)) + { + if (!m_is_atapi) + { + kinfoln("ata: Drive %d is ATAPI, sending IDENTIFY_PACKET command", m_drive_index); + m_is_atapi = true; + return identify_ata(); + } + } + + kwarnln("ata: IDENTIFY command for drive %d returned error", m_drive_index); + + return false; + } + + if (!m_channel->wait_for_reg_set(Register::Status, SR_DataRequestReady | SR_Error, 1000)) + { + kwarnln("ata: Drive %d timed out setting SR_DataRequestReady (waited for 1000 ms)", m_drive_index); + return false; + } + + u8 status = m_channel->read_register(Register::Status); + if (status & SR_Error) + { + kwarnln("ata: IDENTIFY command for drive %d returned error", m_drive_index); + return false; + } + + for (usize i = 0; i < 256; i++) + { + u16 data = m_channel->read_data(); + m_identify_words[i] = data; + } + + return true; + } + + bool Drive::initialize() + { + m_channel->select(m_drive_index); + + m_channel->write_register(Register::SectorCount, 0); + m_channel->write_register(Register::LBALow, 0); + m_channel->write_register(Register::LBAMiddle, 0); + m_channel->write_register(Register::LBAHigh, 0); + + if (!identify_ata()) return false; + + m_serial = StringView::from_fixed_size_cstring((const char*)&m_identify_words[10], SERIAL_LEN); + m_revision = StringView::from_fixed_size_cstring((const char*)&m_identify_words[23], REVISION_LEN); + m_model = StringView::from_fixed_size_cstring((const char*)&m_identify_words[27], MODEL_LEN); + + m_serial.trim(" "); + m_revision.trim(" "); + m_model.trim(" "); + + kinfoln("ata: Drive IDENTIFY returned serial='%s', revision='%s' and model='%s'", m_serial.chars(), + m_revision.chars(), m_model.chars()); + + return true; + } + + void Drive::irq_handler() + { + // Clear the IRQ flag. + u8 status = m_channel->read_register(Register::Status); + + if (status & SR_Error) + { + u8 error = m_channel->read_register(Register::Error); + (void)error; + } + } } diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 18766f91..c7f9a720 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -4,6 +4,7 @@ #include "lib/KMutex.h" #include #include +#include namespace ATA { @@ -21,7 +22,7 @@ namespace ATA LBAHigh = 5, DriveSelect = 6, Status = 7, - Command = 8, + Command = 7, }; enum class ControlRegister : u16 @@ -31,13 +32,56 @@ namespace ATA DriveAddress = 1, }; + enum StatusRegister + { + SR_Busy = 0x80, + SR_DriveReady = 0x40, + SR_WriteFault = 0x20, + SR_SeekComplete = 0x10, + SR_DataRequestReady = 0x08, + SR_CorrectedData = 0x04, + SR_Index = 0x02, + SR_Error = 0x01 + }; + + enum CommandRegister + { + CMD_Identify = 0xec, + CMD_Identify_Packet = 0xa1 + }; + class Controller; class Channel; class Drive { + public: + Drive(Channel* channel, u8 drive_index, Badge); + + bool initialize(); + + void irq_handler(); + private: + bool identify_ata(); + Channel* m_channel; + + u8 m_drive_index; + union { + u16 m_identify_words[256]; + u8 m_identify_data[512]; + }; + + bool m_is_atapi { false }; + + constexpr static usize SERIAL_LEN = 20; + constexpr static usize REVISION_LEN = 8; + constexpr static usize MODEL_LEN = 40; + + StaticString m_serial; + StaticString m_revision; + StaticString m_model; }; class Channel @@ -46,10 +90,14 @@ namespace ATA Channel(Controller* controller, u8 channel_index, Badge); u8 read_register(Register reg); + u16 read_data(); void write_register(Register reg, u8 value); u8 read_control(ControlRegister reg); void write_control(ControlRegister reg, u8 value); + bool wait_for_reg_set(Register reg, u8 value, u64 timeout); + bool wait_for_reg_clear(Register reg, u8 value, u64 timeout); + void delay_400ns(); void wait_for_irq(); @@ -74,6 +122,8 @@ namespace ATA u16 m_control_base; u8 m_current_drive = (u8)-1; + + SharedPtr m_drives[2]; }; class Controller diff --git a/libluna/include/luna/StaticString.h b/libluna/include/luna/StaticString.h index b038ed8c..cc1835fd 100644 --- a/libluna/include/luna/StaticString.h +++ b/libluna/include/luna/StaticString.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include template class StaticString @@ -24,12 +25,27 @@ template class StaticString m_length = length; } + void adopt(StringView string) + { + usize length = strlcpy(m_buffer, string.chars(), + string.length() > sizeof(m_buffer) ? sizeof(m_buffer) : string.length() + 1); + if (length > Size) { m_length = Size; } + else + m_length = length; + } + StaticString& operator=(const char* string) { adopt(string); return *this; } + StaticString& operator=(StringView string) + { + adopt(string); + return *this; + } + template StaticString& operator=(const StaticString& string) { if constexpr (OtherSize == Size) @@ -51,6 +67,23 @@ template class StaticString return m_length; } + void trim(StringView delim) + { + isize i = (isize)m_length; + + while (i--) + { + char c = m_buffer[i]; + if (!strchr(delim.chars(), c)) break; + } + + i++; + + m_buffer[i] = '\0'; + + m_length = (usize)i; + } + private: char m_buffer[Size + 1]; usize m_length { 0 }; -- 2.34.1 From 6307b0168978f7df99a7ea747c00f8e9c7624a39 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 16:34:18 +0200 Subject: [PATCH 10/28] kernel/ATA: Read ATA strings properly instead of backwards Now we can see the model string. What does it say... "QEMU DVD-ROM". Let's go! --- kernel/src/arch/x86_64/disk/ATA.cpp | 20 +++++++++++++++++--- libluna/include/luna/StaticString.h | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 5f84bd68..4229322c 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -12,6 +12,20 @@ static void irq_handler(Registers* regs, void* ctx) ((ATA::Channel*)ctx)->irq_handler(regs); } +static usize copy_ata_string(char* out, u16* in, usize size) +{ + for (usize i = 0; i < size; i += 2) + { + u16 val = in[i / 2]; + out[i] = (u8)(val >> 8); + out[i + 1] = (u8)(val & 0xff); + } + + out[size + 1] = '\0'; + + return size; +} + namespace ATA { Result Controller::scan() @@ -275,9 +289,9 @@ namespace ATA if (!identify_ata()) return false; - m_serial = StringView::from_fixed_size_cstring((const char*)&m_identify_words[10], SERIAL_LEN); - m_revision = StringView::from_fixed_size_cstring((const char*)&m_identify_words[23], REVISION_LEN); - m_model = StringView::from_fixed_size_cstring((const char*)&m_identify_words[27], MODEL_LEN); + m_serial.set_length(copy_ata_string(m_serial.data(), &m_identify_words[10], SERIAL_LEN)); + m_revision.set_length(copy_ata_string(m_revision.data(), &m_identify_words[23], REVISION_LEN)); + m_model.set_length(copy_ata_string(m_model.data(), &m_identify_words[27], MODEL_LEN)); m_serial.trim(" "); m_revision.trim(" "); diff --git a/libluna/include/luna/StaticString.h b/libluna/include/luna/StaticString.h index cc1835fd..33e79b11 100644 --- a/libluna/include/luna/StaticString.h +++ b/libluna/include/luna/StaticString.h @@ -62,6 +62,16 @@ template class StaticString return m_buffer; } + char* data() + { + return m_buffer; + } + + void set_length(usize len) + { + m_length = len; + } + usize length() const { return m_length; -- 2.34.1 From 5d1675463207731adda0514039fcf08b8cb806e7 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 17:54:02 +0200 Subject: [PATCH 11/28] kernel: Handle device BARs properly --- kernel/src/arch/PCI.cpp | 13 +++++++++ kernel/src/arch/PCI.h | 41 +++++++++++++++++++++++++++++ kernel/src/arch/x86_64/disk/ATA.cpp | 28 ++++++++++++++------ 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/kernel/src/arch/PCI.cpp b/kernel/src/arch/PCI.cpp index 25c0cc3b..3a7bb6a9 100644 --- a/kernel/src/arch/PCI.cpp +++ b/kernel/src/arch/PCI.cpp @@ -9,6 +9,10 @@ struct ScanInfo namespace PCI { + BAR::BAR(u32 raw) : m_raw(raw) + { + } + Device::ID read_id(const Device::Address& address) { const u16 vendor = read16(address, Field::VendorID); @@ -108,4 +112,13 @@ namespace PCI } } } + + BAR Device::getBAR(u8 index) const + { + check(index < 6); + + u32 raw = read32(address, 0x10 + (index * 4)); + + return { raw }; + } } diff --git a/kernel/src/arch/PCI.h b/kernel/src/arch/PCI.h index 5e457048..6e138f3a 100644 --- a/kernel/src/arch/PCI.h +++ b/kernel/src/arch/PCI.h @@ -27,6 +27,45 @@ namespace PCI InterruptLine = 0x3c, }; + struct BAR + { + public: + BAR(u32 raw); + + bool is_iospace() + { + return m_raw & 0x01; + } + + bool is_memory_space() + { + return !is_iospace(); + } + + u16 port() + { + return (u16)(m_raw & 0xfffffffc); + } + + u8 type() + { + return (m_raw >> 1) & 0x03; + } + + bool is_prefetchable() + { + return m_raw & (1 << 3); + } + + u32 address_32bit() + { + return m_raw & 0xFFFFFFF0; + } + + private: + u32 m_raw; + }; + struct Device { struct ID @@ -49,6 +88,8 @@ namespace PCI u32 function; }; + BAR getBAR(u8 index) const; + ID id; Type type; Address address; diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 4229322c..1fb852cf 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -156,15 +156,27 @@ namespace ATA int offset = m_channel_index ? 2 : 0; m_is_pci_native_mode = m_controller->device().type.prog_if & (1 << offset); - u32 control_port_base_address; - u32 io_base_address; + u16 control_port_base_address; + u16 io_base_address; if (m_is_pci_native_mode) { - // FIXME: Properly decode BARs. - io_base_address = PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR2 : PCI::BAR0); - control_port_base_address = - PCI::read32(m_controller->device().address, m_channel_index ? PCI::BAR3 : PCI::BAR1); + auto io_base = m_controller->device().getBAR(m_channel_index ? 2 : 0); + if (!io_base.is_iospace()) + { + kwarnln("ata: Channel %d's IO base BAR is not in IO space", m_channel_index); + return false; + } + io_base_address = io_base.port(); + + auto io_control = m_controller->device().getBAR(m_channel_index ? 3 : 1); + if (!io_control.is_iospace()) + { + kwarnln("ata: Channel %d's control base BAR is not in IO space", m_channel_index); + return false; + } + + control_port_base_address = io_control.port(); } else { @@ -172,8 +184,8 @@ namespace ATA control_port_base_address = m_channel_index ? 0x376 : 0x3f6; } - m_io_base = (u16)io_base_address; - m_control_base = (u16)control_port_base_address + 2; + m_io_base = io_base_address; + m_control_base = control_port_base_address + 2; if (m_is_pci_native_mode) m_interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine); else -- 2.34.1 From 268252c89efaf1bc34d52cf1217a0a63ccea46ec Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 18:12:34 +0200 Subject: [PATCH 12/28] kernel/ATA: Read the Busmaster base port and verify it --- kernel/src/arch/x86_64/disk/ATA.cpp | 8 ++++++++ kernel/src/arch/x86_64/disk/ATA.h | 1 + 2 files changed, 9 insertions(+) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 1fb852cf..dec6f2a5 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -187,6 +187,14 @@ namespace ATA m_io_base = io_base_address; m_control_base = control_port_base_address + 2; + auto io_busmaster = m_controller->device().getBAR(4); + if (!io_busmaster.is_iospace()) + { + kwarnln("ata: Channel %d's busmaster base BAR is not in IO space", m_channel_index); + return false; + } + m_busmaster_base = io_busmaster.port() + (u16)(m_channel_index * 8u); + if (m_is_pci_native_mode) m_interrupt_line = PCI::read8(m_controller->device().address, PCI::InterruptLine); else m_interrupt_line = m_channel_index ? 15 : 14; diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index c7f9a720..45770de4 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -120,6 +120,7 @@ namespace ATA u16 m_io_base; u16 m_control_base; + u16 m_busmaster_base; u8 m_current_drive = (u8)-1; -- 2.34.1 From cfcde5af55229b36115c8a99f6f88801bb07b054 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 18:56:27 +0200 Subject: [PATCH 13/28] kernel/ATA: Read the PCI Busmaster registers and start preparing for DMA --- kernel/src/arch/x86_64/disk/ATA.cpp | 55 +++++++++++++++++++++++++++++ kernel/src/arch/x86_64/disk/ATA.h | 38 ++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index dec6f2a5..6835ed98 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -3,6 +3,7 @@ #include "arch/Serial.h" #include "arch/Timer.h" #include "arch/x86_64/IO.h" +#include "memory/MemoryManager.h" #include SharedPtr g_controller; @@ -91,6 +92,26 @@ namespace ATA IO::outb(m_control_base + (u16)reg, value); } + u8 Channel::read_bm(BusmasterRegister reg) + { + return IO::inb(m_busmaster_base + (u16)reg); + } + + void Channel::write_bm(BusmasterRegister reg, u8 value) + { + IO::outb(m_busmaster_base + (u16)reg, value); + } + + u32 Channel::read_prdt_address() + { + return IO::inl(m_busmaster_base + (u16)BusmasterRegister::PRDTAddress); + } + + void Channel::write_prdt_address(u32 value) + { + IO::outl(m_busmaster_base + (u16)BusmasterRegister::PRDTAddress, value); + } + void Channel::delay_400ns() { // FIXME: We should use kernel_sleep(), but it doesn't support nanosecond granularity. @@ -127,6 +148,15 @@ namespace ATA kernel_wait_for_event(); } + bool Channel::wait_for_irq_or_timeout(u64 timeout) + { + m_thread = Scheduler::current(); + + kernel_sleep(timeout); + + return !m_thread->sleep_ticks_left; + } + bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout) { u64 begin = Timer::ticks_ms(); @@ -320,6 +350,31 @@ namespace ATA kinfoln("ata: Drive IDENTIFY returned serial='%s', revision='%s' and model='%s'", m_serial.chars(), m_revision.chars(), m_model.chars()); + auto status = m_channel->read_bm(BusmasterRegister::Status); + if (status & BMS_SimplexOnly) + { + kwarnln("ata: Drive %d will not use DMA because of simplex shenanigans", m_drive_index); + m_uses_dma = false; + } + + auto frame = MemoryManager::alloc_frame(); + if (frame.has_error() || frame.value() > 0xffffffff) + { + kwarnln("ata: Failed to allocate memory below the 32-bit limit for the PRDT"); + return false; + } + m_dma_prdt_phys = frame.release_value(); + m_dma_prdt = (prdt_entry*)MMU::translate_physical_address(m_dma_prdt_phys); + + frame = MemoryManager::alloc_frame(); + if (frame.has_error() || frame.value() > 0xffffffff) + { + kwarnln("ata: Failed to allocate memory below the 32-bit limit for DMA memory"); + return false; + } + m_dma_mem_phys = frame.release_value(); + m_dma_mem = (void*)MMU::translate_physical_address(m_dma_mem_phys); + return true; } diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 45770de4..dc980529 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -32,6 +32,13 @@ namespace ATA DriveAddress = 1, }; + enum class BusmasterRegister : u16 + { + Command = 0, + Status = 2, + PRDTAddress = 4, + }; + enum StatusRegister { SR_Busy = 0x80, @@ -50,9 +57,28 @@ namespace ATA CMD_Identify_Packet = 0xa1 }; + enum BusMasterStatus + { + BMS_SimplexOnly = 0x80, + BMS_SlaveInit = 0x40, + BMS_MasterInit = 0x20, + BMS_IRQPending = 0x4, + BMS_DMAFailure = 0x2, + BMS_DMAMode = 0x1 + }; + class Controller; class Channel; + struct prdt_entry + { + u32 address; + u16 count; + u16 flags; + }; + + static constexpr u16 END_OF_PRDT = (1 << 15); + class Drive { public: @@ -74,6 +100,12 @@ namespace ATA }; bool m_is_atapi { false }; + bool m_uses_dma { true }; + + volatile prdt_entry* m_dma_prdt; + u64 m_dma_prdt_phys; + volatile void* m_dma_mem; + u64 m_dma_mem_phys; constexpr static usize SERIAL_LEN = 20; constexpr static usize REVISION_LEN = 8; @@ -95,12 +127,18 @@ namespace ATA u8 read_control(ControlRegister reg); void write_control(ControlRegister reg, u8 value); + u8 read_bm(BusmasterRegister reg); + void write_bm(BusmasterRegister reg, u8 value); + u32 read_prdt_address(); + void write_prdt_address(u32 value); + bool wait_for_reg_set(Register reg, u8 value, u64 timeout); bool wait_for_reg_clear(Register reg, u8 value, u64 timeout); void delay_400ns(); void wait_for_irq(); + bool wait_for_irq_or_timeout(u64 timeout); void irq_handler(Registers*); void select(u8 drive); -- 2.34.1 From 46c45068e0e8fde2fc556c24a8137e2c132eab02 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 22:18:44 +0200 Subject: [PATCH 14/28] kernel/ATA: Send a READ CAPACITY packet to an ATA drive on initialization --- kernel/src/arch/x86_64/disk/ATA.cpp | 98 ++++++++++++++++++++++++++++- kernel/src/arch/x86_64/disk/ATA.h | 31 ++++++++- 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 6835ed98..ed06c625 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -77,6 +77,11 @@ namespace ATA return IO::inw(m_io_base + (u16)Register::Data); } + void Channel::write_data(u16 value) + { + IO::outw(m_io_base + (u16)Register::Data, value); + } + void Channel::write_register(Register reg, u8 value) { IO::outb(m_io_base + (u16)reg, value); @@ -154,7 +159,7 @@ namespace ATA kernel_sleep(timeout); - return !m_thread->sleep_ticks_left; + return m_thread->sleep_ticks_left; } bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout) @@ -181,6 +186,31 @@ namespace ATA } } + Result Channel::wait_until_ready() + { + if (!wait_for_reg_clear(Register::Status, SR_Busy, 1000)) + { + kwarnln("ata: Drive %d:%d timed out (BSY)", m_channel_index, m_current_drive); + return err(EIO); + } + + if (!wait_for_reg_set(Register::Status, SR_DataRequestReady | SR_Error, 1000)) + { + kwarnln("ata: Drive %d:%d timed out (DRQ)", m_channel_index, m_current_drive); + return err(EIO); + } + + u8 status = read_register(Register::Status); + if (status & SR_Error) + { + kwarnln("ata: An error occurred in drive %d:%d while waiting for data to become available", m_channel_index, + m_current_drive); + return err(EIO); + } + + return {}; + } + bool Channel::initialize() { int offset = m_channel_index ? 2 : 0; @@ -375,9 +405,75 @@ namespace ATA m_dma_mem_phys = frame.release_value(); m_dma_mem = (void*)MMU::translate_physical_address(m_dma_mem_phys); + if (m_is_atapi) + { + atapi_packet packet; + memset(&packet, 0, sizeof(packet)); + packet.command_bytes[0] = ATAPI_ReadCapacity; + + atapi_read_capacity_reply reply; + + if (send_packet_atapi_pio(&packet, &reply, sizeof(reply)).has_error()) + { + kwarnln("ata: Failed to send Read Capacity command to ATAPI drive"); + return false; + } + + // FIXME: This assumes the host machine is little-endian. + u32 last_lba = __builtin_bswap32(reply.last_lba); + u32 sector_size = __builtin_bswap32(reply.sector_size); + + kinfoln("ata: ATAPI drive %d capacity information: Last LBA=%u, Sector Size=%u, Total Capacity=%u", + m_drive_index, last_lba, sector_size, (last_lba + 1) * sector_size); + } + return true; } + Result Drive::send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size) + { + u8* ptr = (u8*)out; + + m_channel->select(m_drive_index); + + // We use PIO here. + m_channel->write_register(Register::Features, 0x00); + + m_channel->write_register(Register::LBAMiddle, (u8)(response_size & 0xff)); + m_channel->write_register(Register::LBAHigh, (u8)(response_size >> 8)); + + m_channel->write_register(Register::Command, CMD_Packet); + + m_channel->delay_400ns(); + + usize i = 0; + + TRY(m_channel->wait_until_ready()); + + for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]); + + while (i < response_size) + { + TRY(m_channel->wait_until_ready()); + + usize byte_count = + m_channel->read_register(Register::LBAHigh) << 8 | m_channel->read_register(Register::LBAMiddle); + usize word_count = byte_count / 2; + + while (word_count--) + { + u16 value = m_channel->read_data(); + ptr[0] = (u8)(value & 0xff); + ptr[1] = (u8)(value >> 8); + ptr += 2; + } + + i += byte_count; + } + + return {}; + } + void Drive::irq_handler() { // Clear the IRQ flag. diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index dc980529..82c6075b 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -39,7 +39,7 @@ namespace ATA PRDTAddress = 4, }; - enum StatusRegister + enum StatusRegister : u8 { SR_Busy = 0x80, SR_DriveReady = 0x40, @@ -51,13 +51,14 @@ namespace ATA SR_Error = 0x01 }; - enum CommandRegister + enum CommandRegister : u8 { CMD_Identify = 0xec, + CMD_Packet = 0xa0, CMD_Identify_Packet = 0xa1 }; - enum BusMasterStatus + enum BusMasterStatus : u8 { BMS_SimplexOnly = 0x80, BMS_SlaveInit = 0x40, @@ -67,6 +68,11 @@ namespace ATA BMS_DMAMode = 0x1 }; + enum ATAPICommand : u8 + { + ATAPI_ReadCapacity = 0x25, + }; + class Controller; class Channel; @@ -77,6 +83,20 @@ namespace ATA u16 flags; }; + struct atapi_packet + { + union { + u16 command_words[6]; + u8 command_bytes[12]; + }; + }; + + struct atapi_read_capacity_reply + { + u32 last_lba; + u32 sector_size; + }; + static constexpr u16 END_OF_PRDT = (1 << 15); class Drive @@ -91,6 +111,8 @@ namespace ATA private: bool identify_ata(); + Result send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size); + Channel* m_channel; u8 m_drive_index; @@ -123,6 +145,7 @@ namespace ATA u8 read_register(Register reg); u16 read_data(); + void write_data(u16 value); void write_register(Register reg, u8 value); u8 read_control(ControlRegister reg); void write_control(ControlRegister reg, u8 value); @@ -135,6 +158,8 @@ namespace ATA bool wait_for_reg_set(Register reg, u8 value, u64 timeout); bool wait_for_reg_clear(Register reg, u8 value, u64 timeout); + Result wait_until_ready(); + void delay_400ns(); void wait_for_irq(); -- 2.34.1 From 82db0e39ea2bf55f35c181a2831ac1028806b3b2 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 22:56:16 +0200 Subject: [PATCH 15/28] kernel/ATA: Calculate block sizes for ATA devices as well --- kernel/src/arch/x86_64/disk/ATA.cpp | 30 ++++++++++++++++++++++++++-- kernel/src/arch/x86_64/disk/ATA.h | 31 ++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index ed06c625..56178418 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -4,6 +4,7 @@ #include "arch/Timer.h" #include "arch/x86_64/IO.h" #include "memory/MemoryManager.h" +#include #include SharedPtr g_controller; @@ -419,13 +420,38 @@ namespace ATA return false; } + m_is_lba48 = true; + // FIXME: This assumes the host machine is little-endian. u32 last_lba = __builtin_bswap32(reply.last_lba); u32 sector_size = __builtin_bswap32(reply.sector_size); - kinfoln("ata: ATAPI drive %d capacity information: Last LBA=%u, Sector Size=%u, Total Capacity=%u", - m_drive_index, last_lba, sector_size, (last_lba + 1) * sector_size); + m_block_count = last_lba + 1; + m_block_size = sector_size; } + else + { + if (m_identify_data.big_lba) m_is_lba48 = true; + + if (m_is_lba48) m_block_count = m_identify_data.sectors_48; + else + m_block_count = m_identify_data.sectors_28; + + // FIXME: Should we check for CHS? + + // FIXME: Maybe a different block size is in use? Detect that. + m_block_size = 512; + } + + u64 total_capacity; + if (!safe_mul(m_block_count, m_block_size).try_set_value(total_capacity)) + { + kwarnln("ata: Drive %d's total capacity is too large", m_drive_index); + return false; + } + + kinfoln("ata: Drive %d capacity information: Block Count=%lu, Block Size=%lu, Total Capacity=%lu", + m_drive_index, m_block_count, m_block_size, total_capacity); return true; } diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 82c6075b..03c04138 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -68,6 +68,31 @@ namespace ATA BMS_DMAMode = 0x1 }; + struct ATAIdentify + { + u16 flags; + u16 unused1[9]; + char serial[20]; + u16 unused2[3]; + char firmware[8]; + char model[40]; + u16 sectors_per_int; + u16 unused3; + u16 capabilities[2]; + u16 unused4[2]; + u16 valid_ext_data; + u16 unused5[5]; + u16 size_of_rw_mult; + u32 sectors_28; + u16 unused6[21]; + u16 unused7 : 10; + u16 big_lba : 1; + u16 unused8 : 5; + u16 unused9[17]; + u64 sectors_48; + u16 unused10[152]; + }; + enum ATAPICommand : u8 { ATAPI_ReadCapacity = 0x25, @@ -118,12 +143,16 @@ namespace ATA u8 m_drive_index; union { u16 m_identify_words[256]; - u8 m_identify_data[512]; + ATAIdentify m_identify_data; }; bool m_is_atapi { false }; bool m_uses_dma { true }; + bool m_is_lba48; + u64 m_block_count; + u64 m_block_size; + volatile prdt_entry* m_dma_prdt; u64 m_dma_prdt_phys; volatile void* m_dma_mem; -- 2.34.1 From a99c5e325d9b1a493beffdb9c69a454c508aa5ae Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 14 May 2023 15:24:46 +0200 Subject: [PATCH 16/28] kernel: Actually register interrupt handlers properly --- kernel/src/arch/CPU.h | 4 ++++ kernel/src/arch/x86_64/CPU.cpp | 37 +++++++++++++++++++++++++++++ kernel/src/arch/x86_64/init/IDT.cpp | 28 ++++++++++++++++++++++ kernel/src/arch/x86_64/init/PIC.cpp | 11 ++++++++- 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/kernel/src/arch/CPU.h b/kernel/src/arch/CPU.h index f446a0a0..e705bf0e 100644 --- a/kernel/src/arch/CPU.h +++ b/kernel/src/arch/CPU.h @@ -22,6 +22,9 @@ namespace CPU void disable_interrupts(); void wait_for_interrupt(); + bool save_interrupts(); + void restore_interrupts(bool saved); + void get_stack_trace(void (*callback)(u64, void*), void* arg); void print_stack_trace(); void get_stack_trace_at(Registers* regs, void (*callback)(u64, void*), void* arg); @@ -30,6 +33,7 @@ namespace CPU u16 get_processor_id(); bool register_interrupt(u8 interrupt, void (*handler)(Registers*, void*), void* context); + void sync_interrupts(); void pause(); } diff --git a/kernel/src/arch/x86_64/CPU.cpp b/kernel/src/arch/x86_64/CPU.cpp index 4c719eb3..d7733861 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -22,6 +22,7 @@ extern "C" void enable_nx(); extern void setup_gdt(); extern void remap_pic(); +extern void change_pic_masks(u8 pic1_mask, u8 pic2_mask); extern void pic_eoi(unsigned char irq); extern void pic_eoi(Registers* regs); extern void setup_idt(); @@ -268,6 +269,8 @@ namespace CPU .expect_value("Could not create the IO background thread!"); remap_pic(); + + sync_interrupts(); } void enable_interrupts() @@ -280,6 +283,20 @@ namespace CPU asm volatile("cli"); } + bool save_interrupts() + { + u64 flags; + asm volatile("pushfq; pop %0" : "=r"(flags)); + return flags & 0x200; + } + + void restore_interrupts(bool saved) + { + if (saved) enable_interrupts(); + else + disable_interrupts(); + } + void wait_for_interrupt() { asm volatile("hlt"); @@ -387,8 +404,28 @@ namespace CPU irq_handlers[interrupt] = { handler, context }; + sync_interrupts(); + return true; } + + void sync_interrupts() + { + u8 pic1_mask, pic2_mask; + pic1_mask = pic2_mask = 0b11111111; + for (int i = 0; i < 8; i++) + { + if (irq_handlers[i].function) pic1_mask &= (u8)(~(1 << i)); + if (irq_handlers[i + 8].function) pic2_mask &= (u8)(~(1 << i)); + } + + if (pic2_mask != 0b11111111) pic1_mask &= 0b11111011; + + auto val = CPU::save_interrupts(); + CPU::disable_interrupts(); + change_pic_masks(pic1_mask, pic2_mask); + CPU::restore_interrupts(val); + } } // called by kernel_yield diff --git a/kernel/src/arch/x86_64/init/IDT.cpp b/kernel/src/arch/x86_64/init/IDT.cpp index 22ea980a..a226ff43 100644 --- a/kernel/src/arch/x86_64/init/IDT.cpp +++ b/kernel/src/arch/x86_64/init/IDT.cpp @@ -84,6 +84,20 @@ INT(20); INT(21); INT(32); INT(33); +INT(34); +INT(35); +INT(36); +INT(37); +INT(38); +INT(39); +INT(40); +INT(41); +INT(42); +INT(43); +INT(44); +INT(45); +INT(46); +INT(47); INT(66); void setup_idt() @@ -112,6 +126,20 @@ void setup_idt() TRAP(21); IRQ(32); IRQ(33); + IRQ(34); + IRQ(35); + IRQ(36); + IRQ(37); + IRQ(38); + IRQ(39); + IRQ(40); + IRQ(41); + IRQ(42); + IRQ(43); + IRQ(44); + IRQ(45); + IRQ(46); + IRQ(47); SYS(66); static IDTR idtr; diff --git a/kernel/src/arch/x86_64/init/PIC.cpp b/kernel/src/arch/x86_64/init/PIC.cpp index e24a23a1..35ca6481 100644 --- a/kernel/src/arch/x86_64/init/PIC.cpp +++ b/kernel/src/arch/x86_64/init/PIC.cpp @@ -36,9 +36,18 @@ void remap_pic() IO::outb(PIC2_DATA, ICW4_8086); io_delay(); - IO::outb(PIC1_DATA, 0b11111100); + IO::outb(PIC1_DATA, 0b11111111); io_delay(); IO::outb(PIC2_DATA, 0b11111111); + io_delay(); +} + +void change_pic_masks(u8 pic1_mask, u8 pic2_mask) +{ + IO::outb(PIC1_DATA, pic1_mask); + io_delay(); + IO::outb(PIC2_DATA, pic2_mask); + io_delay(); } void pic_eoi(unsigned char irq) -- 2.34.1 From 3762d3f9591104a43fdcff23a2403e1b74fa7872 Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 14 May 2023 21:44:23 +0200 Subject: [PATCH 17/28] kernel/PCI: Add bit enum for the Command field --- kernel/src/arch/PCI.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/kernel/src/arch/PCI.h b/kernel/src/arch/PCI.h index 6e138f3a..ecd43f81 100644 --- a/kernel/src/arch/PCI.h +++ b/kernel/src/arch/PCI.h @@ -27,6 +27,20 @@ namespace PCI InterruptLine = 0x3c, }; + enum CommandField : u16 + { + CMD_IO_SPACE = 1 << 0, + CMD_MEMORY_SPACE = 1 << 1, + CMD_BUS_MASTER = 1 << 2, + CMD_SPECIAL_CYCLES = 1 << 3, + CMD_MEMORY_WRITE_AND_INVALIDATE = 1 << 4, + CMD_VGA_PALETTE_SNOOP = 1 << 5, + CMD_PARITY_ERROR_RESPONSE = 1 << 6, + CMD_SERR = 1 << 8, + CMD_FAST_BACK_TO_BACK = 1 << 9, + CMD_INTERRUPT_DISABLE = 1 << 10, + }; + struct BAR { public: -- 2.34.1 From cc8450751c3d5d377f75469f4fc344d545f30646 Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 14 May 2023 21:45:18 +0200 Subject: [PATCH 18/28] kernel/x86_64: Implement writing to PCI fields --- kernel/src/arch/x86_64/PCI.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/kernel/src/arch/x86_64/PCI.cpp b/kernel/src/arch/x86_64/PCI.cpp index 0ce6d920..16fd0815 100644 --- a/kernel/src/arch/x86_64/PCI.cpp +++ b/kernel/src/arch/x86_64/PCI.cpp @@ -33,19 +33,32 @@ namespace PCI void write8(const Device::Address& address, u32 field, u8 value) { - ignore(address, field, value); - todo(); + u8 offset = (u8)(field & ~0x3); + union { + u8 split[4]; + u32 full; + }; + full = read32(address, offset); + split[(field & 0x3)] = value; + write32(address, offset, full); } - void write16(const Device::Address& address, u32 field, u8 value) + void write16(const Device::Address& address, u32 field, u16 value) { - ignore(address, field, value); - todo(); + u8 offset = (u8)(field & ~0x3); + union { + u8 split[4]; + u32 full; + }; + full = read32(address, offset); + split[(field & 0x3)] = (u8)(value >> 8); + split[(field & 0x3) + 1] = (u8)(value & 0xff); + write32(address, offset, full); } - void write32(const Device::Address& address, u32 field, u8 value) + void write32(const Device::Address& address, u32 field, u32 value) { - ignore(address, field, value); - todo(); + IO::outl(PCI_ADDRESS_PORT, make_pci_address(address, field)); + IO::outl(PCI_VALUE_PORT, value); } } -- 2.34.1 From 2fa11a5ae360fbcaed4284e51836a0e40e1334cb Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 14 May 2023 22:39:29 +0200 Subject: [PATCH 19/28] kernel/ATA: Read the CDROM's first sector using ATAPI PIO!! Sadly, for some reason, DMA is not working right now. This is a problem, as PIO is inconvenient. But hey, it works for now! --- kernel/src/arch/x86_64/disk/ATA.cpp | 226 +++++++++++++++++++++++++--- kernel/src/arch/x86_64/disk/ATA.h | 25 ++- 2 files changed, 227 insertions(+), 24 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 56178418..0e891734 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -4,6 +4,7 @@ #include "arch/Timer.h" #include "arch/x86_64/IO.h" #include "memory/MemoryManager.h" +#include #include #include @@ -53,6 +54,15 @@ namespace ATA bool Controller::initialize() { + u16 command_old = PCI::read16(m_device.address, PCI::Command); + u16 command_new = command_old; + + command_new &= ~PCI::CMD_INTERRUPT_DISABLE; + command_new |= PCI::CMD_IO_SPACE; + command_new |= PCI::CMD_BUS_MASTER; + + if (command_new != command_old) PCI::write16(m_device.address, PCI::Command, command_new); + if (!m_primary_channel.initialize()) return false; return m_secondary_channel.initialize(); @@ -121,7 +131,7 @@ namespace ATA void Channel::delay_400ns() { // FIXME: We should use kernel_sleep(), but it doesn't support nanosecond granularity. - for (int i = 0; i < 14; i++) { read_control(ControlRegister::AltStatus); } + for (int i = 0; i < 14; i++) { [[maybe_unused]] volatile u8 val = read_control(ControlRegister::AltStatus); } } void Channel::select(u8 drive) @@ -140,27 +150,37 @@ namespace ATA { // FIXME: Read the Busmaster register to make sure the IRQ is for this channel. - // FIXME: Also read it in case there was a DMA read error. - if (m_current_drive < 2 && m_drives[m_current_drive]) m_drives[m_current_drive]->irq_handler(); - m_thread->wake_up(); + m_irq_called = true; + + if (m_thread) m_thread->wake_up(); + } + + void Channel::prepare_for_irq() + { + m_thread = Scheduler::current(); + m_irq_called = false; } void Channel::wait_for_irq() { - m_thread = Scheduler::current(); + if (!m_irq_called) kernel_wait_for_event(); - kernel_wait_for_event(); + m_irq_called = false; } bool Channel::wait_for_irq_or_timeout(u64 timeout) { - m_thread = Scheduler::current(); + if (!m_irq_called) + { + kernel_sleep(timeout); + m_irq_called = false; + return m_thread->sleep_ticks_left; + } - kernel_sleep(timeout); - - return m_thread->sleep_ticks_left; + m_irq_called = false; + return true; } bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout) @@ -168,7 +188,7 @@ namespace ATA u64 begin = Timer::ticks_ms(); while (true) { - u8 reg_value = read_register(reg); + u8 reg_value = reg == Register::Status ? read_control(ControlRegister::AltStatus) : read_register(reg); if (reg_value & value) return true; if ((Timer::ticks_ms() - begin) >= timeout) return false; kernel_sleep(1); @@ -180,7 +200,7 @@ namespace ATA u64 begin = Timer::ticks_ms(); while (true) { - u8 reg_value = read_register(reg); + u8 reg_value = reg == Register::Status ? read_control(ControlRegister::AltStatus) : read_register(reg); if ((reg_value & value) == 0) return true; if ((Timer::ticks_ms() - begin) >= timeout) return false; kernel_sleep(1); @@ -201,7 +221,7 @@ namespace ATA return err(EIO); } - u8 status = read_register(Register::Status); + u8 status = read_control(ControlRegister::AltStatus); if (status & SR_Error) { kwarnln("ata: An error occurred in drive %d:%d while waiting for data to become available", m_channel_index, @@ -237,7 +257,7 @@ namespace ATA return false; } - control_port_base_address = io_control.port(); + control_port_base_address = io_control.port() + 2; } else { @@ -246,7 +266,7 @@ namespace ATA } m_io_base = io_base_address; - m_control_base = control_port_base_address + 2; + m_control_base = control_port_base_address; auto io_busmaster = m_controller->device().getBAR(4); if (!io_busmaster.is_iospace()) @@ -260,13 +280,7 @@ namespace ATA else m_interrupt_line = m_channel_index ? 15 : 14; - bool ok = CPU::register_interrupt(m_interrupt_line, ::irq_handler, this); - if (!ok) - { - kerrorln("ata: Failed to register IRQ handler for ATA channel %d (IRQ %d)", m_channel_index, - m_interrupt_line); - return false; - } + write_control(ControlRegister::DeviceControl, 0); for (u8 drive = 0; drive < 2; drive++) { @@ -298,6 +312,26 @@ namespace ATA } } + bool ok = CPU::register_interrupt(m_interrupt_line, ::irq_handler, this); + if (!ok) + { + kerrorln("ata: Failed to register IRQ handler for ATA channel %d (IRQ %d)", m_channel_index, + m_interrupt_line); + return false; + } + + for (u8 drive = 0; drive < 2; drive++) + { + if (m_drives[drive]) + { + if (!m_drives[drive]->post_initialize()) + { + m_drives[drive] = {}; + return false; + } + } + } + return true; } @@ -397,6 +431,8 @@ namespace ATA m_dma_prdt_phys = frame.release_value(); m_dma_prdt = (prdt_entry*)MMU::translate_physical_address(m_dma_prdt_phys); + memset(m_dma_prdt, 0, ARCH_PAGE_SIZE); + frame = MemoryManager::alloc_frame(); if (frame.has_error() || frame.value() > 0xffffffff) { @@ -406,6 +442,20 @@ namespace ATA m_dma_mem_phys = frame.release_value(); m_dma_mem = (void*)MMU::translate_physical_address(m_dma_mem_phys); + memset(const_cast(m_dma_mem), 0, ARCH_PAGE_SIZE); + + if (m_uses_dma) + { + auto cmd = m_channel->read_bm(BusmasterRegister::Command); + cmd &= ~BMC_StartStop; + m_channel->write_bm(BusmasterRegister::Command, cmd); + } + + return true; + } + + bool Drive::post_initialize() + { if (m_is_atapi) { atapi_packet packet; @@ -453,6 +503,14 @@ namespace ATA kinfoln("ata: Drive %d capacity information: Block Count=%lu, Block Size=%lu, Total Capacity=%lu", m_drive_index, m_block_count, m_block_size, total_capacity); + char buf[2048]; + if (atapi_read_pio(0, buf, sizeof(buf)).has_error()) return false; + + char readable[2049]; + for (int i = 0; i < 2048; i++) { readable[i] = _iscntrl(buf[i]) ? '.' : buf[i]; } + readable[2048] = 0; + kinfoln("ata: Read first sector from CD-ROM: %s", readable); + return true; } @@ -500,6 +558,123 @@ namespace ATA return {}; } +#if 0 + + Result Drive::send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size) + { + check(m_uses_dma); + + m_channel->select(m_drive_index); + + kdbgln("have selected"); + + // We use DMA here. + m_channel->write_register(Register::Features, 0x01); + + m_channel->write_register(Register::LBAMiddle, 0); + m_channel->write_register(Register::LBAHigh, 0); + + kdbgln("will do_dma_command"); + + TRY(do_dma_command(CMD_Packet, response_size, false)); + + TRY(m_channel->wait_until_ready()); + + kdbgln("send atapi packet data"); + + for (int j = 0; j < 6; j++) m_channel->write_data(packet->command_words[j]); + + kdbgln("do dma transfer"); + + TRY(do_dma_transfer()); + + memcpy(out, const_cast(m_dma_mem), response_size); + + return {}; + } + + Result Drive::do_dma_command(u8 command, u16 count, bool write) + { + m_dma_prdt->address = (u32)m_dma_mem_phys; + m_dma_prdt->count = count; + m_dma_prdt->flags = END_OF_PRDT; + + kdbgln("ata: do_dma_command: phys=%x, command=%x, count=%u, write=%d", m_dma_prdt->address, command, count, + write); + + m_channel->write_prdt_address((u32)m_dma_prdt_phys); + + auto status = m_channel->read_bm(BusmasterRegister::Status); + status &= ~(BMS_DMAFailure | BMS_IRQPending); + m_channel->write_bm(BusmasterRegister::Status, status); + + auto cmd = m_channel->read_bm(BusmasterRegister::Command); + if (!write) cmd |= BMC_ReadWrite; + else + cmd &= ~BMC_ReadWrite; + m_channel->write_bm(BusmasterRegister::Command, cmd); + + m_channel->prepare_for_irq(); + + m_channel->write_register(Register::Command, command); + + cmd = m_channel->read_bm(BusmasterRegister::Command); + cmd |= BMC_StartStop; + m_channel->write_bm(BusmasterRegister::Command, cmd); + + m_channel->delay_400ns(); + + return {}; + } + + Result Drive::do_dma_transfer() + { + if (!m_channel->wait_for_irq_or_timeout(2000)) + { + kwarnln("ata: Drive %d timed out (DMA)", m_drive_index); + return err(EIO); + } + + u8 status = m_channel->read_control(ControlRegister::AltStatus); + kdbgln("ata: status after irq: %#x", status); + + m_channel->delay_400ns(); + + auto cmd = m_channel->read_bm(BusmasterRegister::Command); + cmd &= ~BMC_StartStop; + m_channel->write_bm(BusmasterRegister::Command, cmd); + + status = m_channel->read_bm(BusmasterRegister::Status); + m_channel->write_bm(BusmasterRegister::Status, status & ~(BMS_DMAFailure | BMS_IRQPending)); + + if (status & BMS_DMAFailure) + { + kwarnln("ata: DMA failure while trying to read drive %d", m_drive_index); + return err(EIO); + } + + return {}; + } + +#endif + + Result Drive::atapi_read_pio(u64 lba, void* out, usize size) + { + check(lba < m_block_count); + check(size <= ARCH_PAGE_SIZE); + + atapi_packet read_packet; + memset(&read_packet, 0, sizeof(read_packet)); + read_packet.command_bytes[0] = ATAPI_Read; + read_packet.command_bytes[2] = (lba >> 0x18) & 0xff; + read_packet.command_bytes[3] = (lba >> 0x10) & 0xff; + read_packet.command_bytes[4] = (lba >> 0x08) & 0xff; + read_packet.command_bytes[5] = (lba >> 0x00) & 0xff; + read_packet.command_bytes[9] = (u8)(size / m_block_size); + + return send_packet_atapi_pio(&read_packet, out, (u16)size); + } + void Drive::irq_handler() { // Clear the IRQ flag. @@ -510,5 +685,12 @@ namespace ATA u8 error = m_channel->read_register(Register::Error); (void)error; } + + if (m_uses_dma) + { + status = m_channel->read_bm(BusmasterRegister::Status); + if (status & BMS_DMAFailure) { kwarnln("ata: DMA failure in irq"); } + m_channel->write_bm(BusmasterRegister::Status, 4); + } } } diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 03c04138..a6ad2147 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -68,6 +68,12 @@ namespace ATA BMS_DMAMode = 0x1 }; + enum BusMasterCommand : u8 + { + BMC_ReadWrite = 0x8, + BMC_StartStop = 0x1, + }; + struct ATAIdentify { u16 flags; @@ -96,6 +102,7 @@ namespace ATA enum ATAPICommand : u8 { ATAPI_ReadCapacity = 0x25, + ATAPI_Read = 0xa8, }; class Controller; @@ -131,12 +138,22 @@ namespace ATA bool initialize(); + bool post_initialize(); + void irq_handler(); private: bool identify_ata(); Result send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size); +#if 0 + Result send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size); + + Result do_dma_command(u8 command, u16 count, bool write); + Result do_dma_transfer(); +#endif + + Result atapi_read_pio(u64 lba, void* out, usize size); Channel* m_channel; @@ -153,7 +170,7 @@ namespace ATA u64 m_block_count; u64 m_block_size; - volatile prdt_entry* m_dma_prdt; + prdt_entry* m_dma_prdt; u64 m_dma_prdt_phys; volatile void* m_dma_mem; u64 m_dma_mem_phys; @@ -191,6 +208,8 @@ namespace ATA void delay_400ns(); + void prepare_for_irq(); + void wait_for_irq(); bool wait_for_irq_or_timeout(u64 timeout); void irq_handler(Registers*); @@ -208,12 +227,14 @@ namespace ATA KMutex<100> m_lock {}; - Thread* m_thread; + Thread* m_thread { nullptr }; u16 m_io_base; u16 m_control_base; u16 m_busmaster_base; + bool m_irq_called { false }; + u8 m_current_drive = (u8)-1; SharedPtr m_drives[2]; -- 2.34.1 From a0fa1f2cfd08eb57fcc4c1b2a9af5a592626fc1a Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 17 May 2023 18:15:01 +0200 Subject: [PATCH 20/28] kernel+init: Create a device node in /dev to access the CDROM from userspace! Still using PIO, though. --- kernel/src/arch/x86_64/disk/ATA.cpp | 84 ++++++++++++++++++++++++++ kernel/src/arch/x86_64/disk/ATA.h | 42 ++++++++++--- kernel/src/fs/devices/DeviceRegistry.h | 1 + 3 files changed, 117 insertions(+), 10 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 0e891734..e28ec535 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -4,6 +4,7 @@ #include "arch/Timer.h" #include "arch/x86_64/IO.h" #include "memory/MemoryManager.h" +#include #include #include #include @@ -329,6 +330,12 @@ namespace ATA m_drives[drive] = {}; return false; } + + if (ATADevice::create(m_drives[drive]).has_error()) + { + kwarnln("ata: Failed to register ATA drive %d:%d in DeviceRegistry", m_channel_index, drive); + continue; + } } } @@ -675,6 +682,25 @@ namespace ATA return send_packet_atapi_pio(&read_packet, out, (u16)size); } + Result Drive::read_lba(u64 lba, void* out, usize nblocks) + { + const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size; + if (m_is_atapi) + { + kdbgln("ata: Reading %zu blocks from ATAPI drive using PIO, at LBA %ld", nblocks, lba); + while (nblocks > blocks_per_page) + { + TRY(atapi_read_pio(lba, out, ARCH_PAGE_SIZE)); + lba += blocks_per_page; + nblocks -= blocks_per_page; + out = offset_ptr(out, ARCH_PAGE_SIZE); + } + return atapi_read_pio(lba, out, nblocks * m_block_size); + } + else + todo(); + } + void Drive::irq_handler() { // Clear the IRQ flag. @@ -694,3 +720,61 @@ namespace ATA } } } + +static u32 next_minor = 0; + +Result ATADevice::create(SharedPtr drive) +{ + auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) ATADevice())); + device->m_drive = drive; + return DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device); +} + +Result ATADevice::read(u8* buf, usize offset, usize size) const +{ + if (size == 0) return 0; + + if (offset > m_drive->capacity()) return 0; + if (offset + size > m_drive->capacity()) size = m_drive->capacity() - offset; + + usize length = size; + + auto block_size = m_drive->block_size(); + + auto* temp = TRY(make_array(block_size)); + auto guard = make_scope_guard([temp] { delete[] temp; }); + + if (offset % block_size) + { + usize extra_size = block_size - (offset % block_size); + TRY(m_drive->read_lba(offset / block_size, temp, 1)); + memcpy(buf, temp + (offset % block_size), extra_size); + offset += extra_size; + size -= extra_size; + buf += extra_size; + } + + while (size >= ARCH_PAGE_SIZE) + { + TRY(m_drive->read_lba(offset / block_size, buf, ARCH_PAGE_SIZE / block_size)); + offset += ARCH_PAGE_SIZE; + size -= ARCH_PAGE_SIZE; + buf += ARCH_PAGE_SIZE; + } + + while (size >= block_size) + { + TRY(m_drive->read_lba(offset / block_size, buf, 1)); + offset += block_size; + size -= block_size; + buf += block_size; + } + + if (size) + { + TRY(m_drive->read_lba(offset / block_size, temp, 1)); + memcpy(buf, temp, size); + } + + return length; +} diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index a6ad2147..fbcd3ff3 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -142,6 +142,23 @@ namespace ATA void irq_handler(); + usize block_size() const + { + return m_block_size; + } + + usize block_count() const + { + return m_block_count; + } + + usize capacity() const + { + return m_block_count * m_block_size; + } + + Result read_lba(u64 lba, void* out, usize nblocks); + private: bool identify_ata(); @@ -265,23 +282,28 @@ class ATADevice : public Device { public: // Initializer for DeviceRegistry. - static Result create(SharedPtr controller, int channel, int drive); + static Result create(SharedPtr drive); Result read(u8*, usize, usize) const override; - Result write(const u8*, usize, usize) override; + Result write(const u8*, usize, usize) override + { + return err(ENOTSUP); + } - bool blocking() const override; + bool blocking() const override + { + return false; + } - Result ioctl(int request, void* arg) override; - - usize size() const override; + usize size() const override + { + return m_drive->capacity(); + } virtual ~ATADevice() = default; private: - ATADevice(SharedPtr controller); - SharedPtr m_controller; - int m_channel; - int m_drive; + ATADevice() = default; + SharedPtr m_drive; }; diff --git a/kernel/src/fs/devices/DeviceRegistry.h b/kernel/src/fs/devices/DeviceRegistry.h index 552a78c9..b8c01dfe 100644 --- a/kernel/src/fs/devices/DeviceRegistry.h +++ b/kernel/src/fs/devices/DeviceRegistry.h @@ -13,6 +13,7 @@ namespace DeviceRegistry Console = 1, Memory = 2, Framebuffer = 3, + Disk = 4, }; Result> fetch_special_device(u32 major, u32 minor); -- 2.34.1 From bb0db450b31543e191a7d7a977f13a2b1c29c513 Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 17 May 2023 20:45:25 +0200 Subject: [PATCH 21/28] kernel/ATA: Pass extra information to DeviceRegistry This is needed since merging e7d482e from main. --- kernel/src/arch/x86_64/disk/ATA.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index e28ec535..7e2050c0 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -727,7 +727,7 @@ Result ATADevice::create(SharedPtr drive) { auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) ATADevice())); device->m_drive = drive; - return DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device); + return DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, "cdrom", 0400); } Result ATADevice::read(u8* buf, usize offset, usize size) const -- 2.34.1 From a3beaa4d53707de2482c114dfc0a62d1908cff9c Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 26 May 2023 20:29:31 +0200 Subject: [PATCH 22/28] ATA: Mark the CDROM as a block device --- kernel/src/arch/x86_64/disk/ATA.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index fbcd3ff3..c86cf4d9 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -296,6 +296,11 @@ class ATADevice : public Device return false; } + bool is_block_device() const override + { + return true; + } + usize size() const override { return m_drive->capacity(); -- 2.34.1 From 93922932faa66ddb057f17f1b5930809f3b6b249 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 26 May 2023 23:28:51 +0200 Subject: [PATCH 23/28] kernel: Start reading the MBR partition table from the ATAPI drive --- kernel/CMakeLists.txt | 1 + kernel/src/arch/x86_64/disk/ATA.cpp | 5 +++++ kernel/src/fs/MBR.cpp | 29 ++++++++++++++++++++++++ kernel/src/fs/MBR.h | 35 +++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 kernel/src/fs/MBR.cpp create mode 100644 kernel/src/fs/MBR.h diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 8c266e6d..4352da53 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -43,6 +43,7 @@ set(SOURCES src/fs/VFS.cpp src/fs/Pipe.cpp src/fs/Mount.cpp + src/fs/MBR.cpp src/fs/tmpfs/FileSystem.cpp src/fs/devices/DeviceRegistry.cpp src/fs/devices/NullDevice.cpp diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 7e2050c0..412b1c9d 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -3,6 +3,7 @@ #include "arch/Serial.h" #include "arch/Timer.h" #include "arch/x86_64/IO.h" +#include "fs/MBR.h" #include "memory/MemoryManager.h" #include #include @@ -336,6 +337,10 @@ namespace ATA kwarnln("ata: Failed to register ATA drive %d:%d in DeviceRegistry", m_channel_index, drive); continue; } + + // FIXME: Do not hardcode the path like this. + auto inode = VFS::resolve_path("/dev/cdrom", Credentials {}).value(); + MBR::identify(inode); } } diff --git a/kernel/src/fs/MBR.cpp b/kernel/src/fs/MBR.cpp new file mode 100644 index 00000000..21bce837 --- /dev/null +++ b/kernel/src/fs/MBR.cpp @@ -0,0 +1,29 @@ +#include "fs/MBR.h" +#include "Log.h" + +namespace MBR +{ + Result identify(SharedPtr inode) + { + // Cannot read a partition table from a pipe/socket/character device! Who is even coming up with this silliness? + if (!VFS::is_seekable(inode)) return false; + + DiskHeader hdr; + usize nread = TRY(inode->read((u8*)&hdr, 0, sizeof(hdr))); + check(nread == 512); + + if (hdr.signature[0] != MBR_SIGNATURE_1 || hdr.signature[1] != MBR_SIGNATURE_2) return false; + + for (int i = 0; i < 4; i++) + { + const auto& part = hdr.partitions[i]; + if (part.partition_type == 0) continue; // Not active. + + bool bootable = part.attributes & MBR_BOOTABLE; + kinfoln("mbr: Partition #%d is active: bootable=%d, type=%x, start=%d, sectors=%d", i, bootable, + part.partition_type, part.start_lba, part.num_sectors); + } + + return true; + } +} diff --git a/kernel/src/fs/MBR.h b/kernel/src/fs/MBR.h new file mode 100644 index 00000000..d447cdef --- /dev/null +++ b/kernel/src/fs/MBR.h @@ -0,0 +1,35 @@ +#pragma once + +#include "fs/VFS.h" +#include + +#define MBR_BOOTABLE 0x80 + +#define MBR_SIGNATURE_1 0x55 +#define MBR_SIGNATURE_2 0xAA + +namespace MBR +{ + struct [[gnu::packed]] PartitionHeader + { + u8 attributes; + u8 chs_start[3]; + u8 partition_type; + u8 chs_end[3]; + u32 start_lba; + u32 num_sectors; + }; + + struct [[gnu::packed]] DiskHeader + { + u8 mbr_code[440]; + u8 disk_id[4]; + u8 reserved[2]; + PartitionHeader partitions[4]; + u8 signature[2]; + }; + + static_assert(sizeof(DiskHeader) == 512ul); + + Result identify(SharedPtr inode); +}; -- 2.34.1 From 7593947c338dd988b6dbe0ffe2121e65418f9a1f Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 27 May 2023 19:16:01 +0200 Subject: [PATCH 24/28] kernel/ATA: Route interrupts to the correct drive --- kernel/src/arch/x86_64/CPU.cpp | 2 +- kernel/src/arch/x86_64/CPU.h | 6 +++++- kernel/src/arch/x86_64/disk/ATA.cpp | 18 +++++++++--------- kernel/src/arch/x86_64/disk/ATA.h | 7 +++++++ kernel/src/arch/x86_64/init/PIC.cpp | 2 +- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/kernel/src/arch/x86_64/CPU.cpp b/kernel/src/arch/x86_64/CPU.cpp index d7733861..4c4e519c 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -180,7 +180,7 @@ extern "C" void arch_interrupt_entry(Registers* regs) if (regs->isr < 32) handle_x86_exception(regs); else if (regs->isr >= 32 && regs->isr < 48) // IRQ from the PIC { - u64 irq = regs->error; + u64 irq = regs->irq; auto handler = irq_handlers[irq]; if (!handler.function) { diff --git a/kernel/src/arch/x86_64/CPU.h b/kernel/src/arch/x86_64/CPU.h index a2efee77..def5d41e 100644 --- a/kernel/src/arch/x86_64/CPU.h +++ b/kernel/src/arch/x86_64/CPU.h @@ -5,7 +5,11 @@ struct Registers // Saved CPU registers for x86-64 { u64 r15, r14, r13, r12, r11, r10, r9, r8; u64 rbp, rdi, rsi, rdx, rcx, rbx, rax; - u64 isr, error; + u64 isr; + union { + u64 error; + u64 irq; + }; u64 rip, cs, rflags, rsp, ss; }; diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 412b1c9d..3a0d4837 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -14,7 +14,7 @@ SharedPtr g_controller; static void irq_handler(Registers* regs, void* ctx) { - ((ATA::Channel*)ctx)->irq_handler(regs); + ((ATA::Controller*)ctx)->irq_handler(regs); } static usize copy_ata_string(char* out, u16* in, usize size) @@ -70,6 +70,12 @@ namespace ATA return m_secondary_channel.initialize(); } + void Controller::irq_handler(Registers* regs) + { + if (regs->irq == m_primary_channel.irq_line()) m_primary_channel.irq_handler(regs); + if (regs->irq == m_secondary_channel.irq_line()) m_secondary_channel.irq_handler(regs); + } + Controller::Controller(const PCI::Device& device) : m_device(device), m_primary_channel(this, 0, {}), m_secondary_channel(this, 1, {}) { @@ -150,7 +156,7 @@ namespace ATA void Channel::irq_handler(Registers*) { - // FIXME: Read the Busmaster register to make sure the IRQ is for this channel. + if (!(read_bm(BusmasterRegister::Status) & BMS_IRQPending)) return; if (m_current_drive < 2 && m_drives[m_current_drive]) m_drives[m_current_drive]->irq_handler(); @@ -314,13 +320,7 @@ namespace ATA } } - bool ok = CPU::register_interrupt(m_interrupt_line, ::irq_handler, this); - if (!ok) - { - kerrorln("ata: Failed to register IRQ handler for ATA channel %d (IRQ %d)", m_channel_index, - m_interrupt_line); - return false; - } + CPU::register_interrupt(m_interrupt_line, ::irq_handler, m_controller); for (u8 drive = 0; drive < 2; drive++) { diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index c86cf4d9..9ceabcf5 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -231,6 +231,11 @@ namespace ATA bool wait_for_irq_or_timeout(u64 timeout); void irq_handler(Registers*); + u8 irq_line() + { + return m_interrupt_line; + } + void select(u8 drive); bool initialize(); @@ -269,6 +274,8 @@ namespace ATA bool initialize(); + void irq_handler(Registers*); + private: Controller(const PCI::Device& device); PCI::Device m_device; diff --git a/kernel/src/arch/x86_64/init/PIC.cpp b/kernel/src/arch/x86_64/init/PIC.cpp index 35ca6481..03a82fce 100644 --- a/kernel/src/arch/x86_64/init/PIC.cpp +++ b/kernel/src/arch/x86_64/init/PIC.cpp @@ -58,5 +58,5 @@ void pic_eoi(unsigned char irq) void pic_eoi(Registers* regs) { - pic_eoi((unsigned char)(regs->error)); // On IRQs, the error code is the IRQ number + pic_eoi((unsigned char)(regs->irq)); } -- 2.34.1 From 72e798cedbad3fd30b308c60c2ee3dfe41450aed Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 3 Jun 2023 21:06:42 +0200 Subject: [PATCH 25/28] kernel: Do not automatically read the MBR partition table from /dev/cdrom Since ff952cfe16 made /dev mounted from userspace, /dev/cdrom does not exist when ATA drives are scanned. --- kernel/src/arch/x86_64/disk/ATA.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 3a0d4837..1bcf86bf 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -339,8 +339,8 @@ namespace ATA } // FIXME: Do not hardcode the path like this. - auto inode = VFS::resolve_path("/dev/cdrom", Credentials {}).value(); - MBR::identify(inode); + /* auto inode = VFS::resolve_path("/dev/cdrom", Credentials {}).value(); + MBR::identify(inode); */ } } -- 2.34.1 From 72b8ebe02c6a95ac8030f0f49be7eab89f2523d9 Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 4 Jun 2023 11:25:16 +0200 Subject: [PATCH 26/28] kernel: Make the MBR code read from a device instead of an inode --- kernel/src/arch/x86_64/disk/ATA.cpp | 14 ++++++++------ kernel/src/fs/MBR.cpp | 8 ++++---- kernel/src/fs/MBR.h | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 1bcf86bf..4a6a6690 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -332,15 +332,16 @@ namespace ATA return false; } - if (ATADevice::create(m_drives[drive]).has_error()) + auto rc = ATADevice::create(m_drives[drive]); + + if (rc.has_error()) { kwarnln("ata: Failed to register ATA drive %d:%d in DeviceRegistry", m_channel_index, drive); continue; } - // FIXME: Do not hardcode the path like this. - /* auto inode = VFS::resolve_path("/dev/cdrom", Credentials {}).value(); - MBR::identify(inode); */ + auto device = rc.release_value(); + MBR::identify(device); } } @@ -728,11 +729,12 @@ namespace ATA static u32 next_minor = 0; -Result ATADevice::create(SharedPtr drive) +Result> ATADevice::create(SharedPtr drive) { auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) ATADevice())); device->m_drive = drive; - return DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, "cdrom", 0400); + TRY(DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, "cdrom", 0400)); + return (SharedPtr)device; } Result ATADevice::read(u8* buf, usize offset, usize size) const diff --git a/kernel/src/fs/MBR.cpp b/kernel/src/fs/MBR.cpp index 21bce837..c3d5da52 100644 --- a/kernel/src/fs/MBR.cpp +++ b/kernel/src/fs/MBR.cpp @@ -3,13 +3,13 @@ namespace MBR { - Result identify(SharedPtr inode) + Result identify(SharedPtr device) { - // Cannot read a partition table from a pipe/socket/character device! Who is even coming up with this silliness? - if (!VFS::is_seekable(inode)) return false; + // Cannot read a partition table from a character device! Who is even coming up with this silliness? + if (!device->is_block_device()) return false; DiskHeader hdr; - usize nread = TRY(inode->read((u8*)&hdr, 0, sizeof(hdr))); + usize nread = TRY(device->read((u8*)&hdr, 0, sizeof(hdr))); check(nread == 512); if (hdr.signature[0] != MBR_SIGNATURE_1 || hdr.signature[1] != MBR_SIGNATURE_2) return false; diff --git a/kernel/src/fs/MBR.h b/kernel/src/fs/MBR.h index d447cdef..b361ea78 100644 --- a/kernel/src/fs/MBR.h +++ b/kernel/src/fs/MBR.h @@ -1,6 +1,6 @@ #pragma once -#include "fs/VFS.h" +#include "fs/devices/DeviceRegistry.h" #include #define MBR_BOOTABLE 0x80 @@ -31,5 +31,5 @@ namespace MBR static_assert(sizeof(DiskHeader) == 512ul); - Result identify(SharedPtr inode); + Result identify(SharedPtr device); }; -- 2.34.1 From 738b218a49e3e63e9a6a23d638284bc595fec5b8 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 16 Jun 2023 21:30:50 +0200 Subject: [PATCH 27/28] kernel/ATA+MBR: Dynamically generate device names + create devices for MBR partitions --- kernel/src/arch/x86_64/disk/ATA.cpp | 12 +++++- kernel/src/arch/x86_64/disk/ATA.h | 15 ++++++- kernel/src/fs/MBR.cpp | 51 ++++++++++++++++++++++- kernel/src/fs/MBR.h | 48 +++++++++++++++++++++ kernel/src/fs/devices/ConsoleDevice.h | 5 +++ kernel/src/fs/devices/Device.h | 12 ++++++ kernel/src/fs/devices/DeviceRegistry.h | 1 + kernel/src/fs/devices/FramebufferDevice.h | 5 +++ kernel/src/fs/devices/FullDevice.h | 5 +++ kernel/src/fs/devices/NullDevice.h | 5 +++ kernel/src/fs/devices/ZeroDevice.h | 5 +++ 11 files changed, 161 insertions(+), 3 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 4a6a6690..6c476f70 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -729,11 +729,21 @@ namespace ATA static u32 next_minor = 0; +Result ATA::Drive::create_drive_name(SharedPtr drive) +{ + static u32 cd_index = 0; + static u32 sd_index = 0; + + return String::format("%s%d"_sv, drive->m_is_atapi ? "cd" : "sd", drive->m_is_atapi ? cd_index++ : sd_index++); +} + Result> ATADevice::create(SharedPtr drive) { auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) ATADevice())); device->m_drive = drive; - TRY(DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, "cdrom", 0400)); + device->m_device_path = TRY(ATA::Drive::create_drive_name(drive)); + TRY(DeviceRegistry::register_special_device(DeviceRegistry::Disk, next_minor++, device, + device->m_device_path.chars(), 0400)); return (SharedPtr)device; } diff --git a/kernel/src/arch/x86_64/disk/ATA.h b/kernel/src/arch/x86_64/disk/ATA.h index 9ceabcf5..53207b0a 100644 --- a/kernel/src/arch/x86_64/disk/ATA.h +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -159,6 +159,8 @@ namespace ATA Result read_lba(u64 lba, void* out, usize nblocks); + static Result create_drive_name(SharedPtr drive); + private: bool identify_ata(); @@ -289,7 +291,7 @@ class ATADevice : public Device { public: // Initializer for DeviceRegistry. - static Result create(SharedPtr drive); + static Result> create(SharedPtr drive); Result read(u8*, usize, usize) const override; @@ -313,9 +315,20 @@ class ATADevice : public Device return m_drive->capacity(); } + Result block_size() const override + { + return m_drive->block_size(); + } + + StringView device_path() const override + { + return m_device_path.view(); + } + virtual ~ATADevice() = default; private: ATADevice() = default; SharedPtr m_drive; + String m_device_path; }; diff --git a/kernel/src/fs/MBR.cpp b/kernel/src/fs/MBR.cpp index c3d5da52..0a585abe 100644 --- a/kernel/src/fs/MBR.cpp +++ b/kernel/src/fs/MBR.cpp @@ -1,19 +1,66 @@ #include "fs/MBR.h" #include "Log.h" +#include + +static Result create_partition_name(SharedPtr host_device, u32 partition_index) +{ + auto host_path = host_device->device_path(); + + char last = host_path[host_path.length() - 1]; + + if (_isdigit(last)) return String::format("%sp%d"_sv, host_path.chars(), partition_index); + + return String::format("%s%d"_sv, host_path.chars(), partition_index); +} namespace MBR { + Result PartitionDevice::create(SharedPtr host_device, usize start_block, usize num_blocks, + u32 partition_index) + { + static u32 next_minor = 0; + auto device = TRY(adopt_shared_if_nonnull(new (std::nothrow) PartitionDevice())); + device->m_host_device = host_device; + device->m_start_offset = start_block * device->m_block_size; + device->m_num_blocks = num_blocks; + device->m_device_path = TRY(create_partition_name(host_device, partition_index)); + return DeviceRegistry::register_special_device(DeviceRegistry::DiskPartition, next_minor++, device, + device->m_device_path.chars(), 0400); + } + + Result PartitionDevice::read(u8* buf, usize offset, usize length) const + { + if (length == 0) return 0; + + if (offset > size()) return 0; + if (offset + length > size()) length = size() - offset; + + return m_host_device->read(buf, m_start_offset + offset, length); + } + + Result PartitionDevice::write(const u8* buf, usize offset, usize length) + { + if (length == 0) return 0; + + if (offset > size()) return 0; + if (offset + length > size()) length = size() - offset; + + return m_host_device->write(buf, m_start_offset + offset, length); + } + Result identify(SharedPtr device) { // Cannot read a partition table from a character device! Who is even coming up with this silliness? if (!device->is_block_device()) return false; DiskHeader hdr; - usize nread = TRY(device->read((u8*)&hdr, 0, sizeof(hdr))); + const usize nread = TRY(device->read((u8*)&hdr, 0, sizeof(hdr))); check(nread == 512); if (hdr.signature[0] != MBR_SIGNATURE_1 || hdr.signature[1] != MBR_SIGNATURE_2) return false; + u32 partition_index = 0; + for (int i = 0; i < 4; i++) { const auto& part = hdr.partitions[i]; @@ -22,6 +69,8 @@ namespace MBR bool bootable = part.attributes & MBR_BOOTABLE; kinfoln("mbr: Partition #%d is active: bootable=%d, type=%x, start=%d, sectors=%d", i, bootable, part.partition_type, part.start_lba, part.num_sectors); + + TRY(PartitionDevice::create(device, part.start_lba, part.num_sectors, partition_index++)); } return true; diff --git a/kernel/src/fs/MBR.h b/kernel/src/fs/MBR.h index b361ea78..7203febb 100644 --- a/kernel/src/fs/MBR.h +++ b/kernel/src/fs/MBR.h @@ -1,6 +1,7 @@ #pragma once #include "fs/devices/DeviceRegistry.h" +#include #include #define MBR_BOOTABLE 0x80 @@ -29,6 +30,53 @@ namespace MBR u8 signature[2]; }; + class PartitionDevice : public Device + { + public: + // Initializer for DeviceRegistry. + static Result create(SharedPtr host_device, usize start_block, usize num_blocks, + u32 partition_index); + + Result read(u8*, usize, usize) const override; + + Result write(const u8* buf, usize offset, usize length) override; + + bool blocking() const override + { + return false; + } + + bool is_block_device() const override + { + return true; + } + + usize size() const override + { + return m_num_blocks * m_block_size; + } + + Result block_size() const override + { + return m_block_size; + } + + StringView device_path() const override + { + return m_device_path.view(); + } + + virtual ~PartitionDevice() = default; + + private: + PartitionDevice() = default; + SharedPtr m_host_device; + usize m_block_size { 512ul }; + usize m_start_offset; + usize m_num_blocks; + String m_device_path; + }; + static_assert(sizeof(DiskHeader) == 512ul); Result identify(SharedPtr device); diff --git a/kernel/src/fs/devices/ConsoleDevice.h b/kernel/src/fs/devices/ConsoleDevice.h index 4a17f832..cf428ef2 100644 --- a/kernel/src/fs/devices/ConsoleDevice.h +++ b/kernel/src/fs/devices/ConsoleDevice.h @@ -17,5 +17,10 @@ class ConsoleDevice : public Device Result ioctl(int request, void* arg) override; + StringView device_path() const override + { + return "console"; + } + virtual ~ConsoleDevice() = default; }; diff --git a/kernel/src/fs/devices/Device.h b/kernel/src/fs/devices/Device.h index e2bb95db..e7e44f27 100644 --- a/kernel/src/fs/devices/Device.h +++ b/kernel/src/fs/devices/Device.h @@ -1,4 +1,5 @@ #pragma once +#include "Log.h" #include class Device @@ -23,6 +24,17 @@ class Device return false; } + virtual Result block_size() const + { + // Block devices should override this function. + kwarnln("Device::block_size() was called on a character device or block device without block size"); + + return err(ENOTSUP); + } + + // Path in devfs. + virtual StringView device_path() const = 0; + virtual bool blocking() const = 0; virtual ~Device() = default; diff --git a/kernel/src/fs/devices/DeviceRegistry.h b/kernel/src/fs/devices/DeviceRegistry.h index b8c01dfe..8ee64e9f 100644 --- a/kernel/src/fs/devices/DeviceRegistry.h +++ b/kernel/src/fs/devices/DeviceRegistry.h @@ -14,6 +14,7 @@ namespace DeviceRegistry Memory = 2, Framebuffer = 3, Disk = 4, + DiskPartition = 5, }; Result> fetch_special_device(u32 major, u32 minor); diff --git a/kernel/src/fs/devices/FramebufferDevice.h b/kernel/src/fs/devices/FramebufferDevice.h index ade9a913..4bf2928c 100644 --- a/kernel/src/fs/devices/FramebufferDevice.h +++ b/kernel/src/fs/devices/FramebufferDevice.h @@ -22,5 +22,10 @@ class FramebufferDevice : public Device usize size() const override; + StringView device_path() const override + { + return "fb0"; + } + virtual ~FramebufferDevice() = default; }; diff --git a/kernel/src/fs/devices/FullDevice.h b/kernel/src/fs/devices/FullDevice.h index 85830003..9102db20 100644 --- a/kernel/src/fs/devices/FullDevice.h +++ b/kernel/src/fs/devices/FullDevice.h @@ -24,5 +24,10 @@ class FullDevice : public Device return false; } + StringView device_path() const override + { + return "full"; + } + virtual ~FullDevice() = default; }; diff --git a/kernel/src/fs/devices/NullDevice.h b/kernel/src/fs/devices/NullDevice.h index 319aeb7e..9f434718 100644 --- a/kernel/src/fs/devices/NullDevice.h +++ b/kernel/src/fs/devices/NullDevice.h @@ -22,5 +22,10 @@ class NullDevice : public Device return false; } + StringView device_path() const override + { + return "null"; + } + virtual ~NullDevice() = default; }; diff --git a/kernel/src/fs/devices/ZeroDevice.h b/kernel/src/fs/devices/ZeroDevice.h index a2aa7b88..8b2fed80 100644 --- a/kernel/src/fs/devices/ZeroDevice.h +++ b/kernel/src/fs/devices/ZeroDevice.h @@ -24,5 +24,10 @@ class ZeroDevice : public Device return false; } + StringView device_path() const override + { + return "zero"; + } + virtual ~ZeroDevice() = default; }; -- 2.34.1 From ba46399bbda268dc97c72649951fbdb79f0b1b19 Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 16 Jun 2023 21:34:36 +0200 Subject: [PATCH 28/28] kernel/ATA: Remove debug messages --- kernel/src/arch/x86_64/disk/ATA.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/kernel/src/arch/x86_64/disk/ATA.cpp b/kernel/src/arch/x86_64/disk/ATA.cpp index 6c476f70..d30aca58 100644 --- a/kernel/src/arch/x86_64/disk/ATA.cpp +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -516,14 +516,6 @@ namespace ATA kinfoln("ata: Drive %d capacity information: Block Count=%lu, Block Size=%lu, Total Capacity=%lu", m_drive_index, m_block_count, m_block_size, total_capacity); - char buf[2048]; - if (atapi_read_pio(0, buf, sizeof(buf)).has_error()) return false; - - char readable[2049]; - for (int i = 0; i < 2048; i++) { readable[i] = _iscntrl(buf[i]) ? '.' : buf[i]; } - readable[2048] = 0; - kinfoln("ata: Read first sector from CD-ROM: %s", readable); - return true; } @@ -693,7 +685,6 @@ namespace ATA const usize blocks_per_page = ARCH_PAGE_SIZE / m_block_size; if (m_is_atapi) { - kdbgln("ata: Reading %zu blocks from ATAPI drive using PIO, at LBA %ld", nblocks, lba); while (nblocks > blocks_per_page) { TRY(atapi_read_pio(lba, out, ARCH_PAGE_SIZE)); -- 2.34.1