diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 2e2e40d2..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 @@ -65,6 +66,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/CPU.h b/kernel/src/arch/CPU.h index 4e994339..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); @@ -29,5 +32,8 @@ 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/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 9157c4aa..ecd43f81 100644 --- a/kernel/src/arch/PCI.h +++ b/kernel/src/arch/PCI.h @@ -17,7 +17,67 @@ namespace PCI HeaderType = 0x0e, - SecondaryBus = 0x19 + BAR0 = 0x10, + BAR1 = 0x14, + BAR2 = 0x18, + BAR3 = 0x1c, + + SecondaryBus = 0x19, + + 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: + 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 @@ -42,6 +102,8 @@ namespace PCI u32 function; }; + BAR getBAR(u8 index) const; + ID id; Type type; Address address; 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..4c4e519c 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -22,12 +22,23 @@ 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(); static Thread* g_io_thread; +typedef void (*interrupt_handler_t)(Registers*, void*); + +struct InterruptHandler +{ + interrupt_handler_t function; + void* context; +}; + +static InterruptHandler irq_handlers[16]; + void FPData::save() { asm volatile("fxsave (%0)" : : "r"(m_data)); @@ -150,21 +161,34 @@ void io_thread() } } +static void timer_interrupt(Registers* regs, void*) +{ + Timer::tick(); + if (should_invoke_scheduler()) Scheduler::invoke(regs); +} + +static void keyboard_interrupt(Registers*, void*) +{ + 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->irq; + auto handler = irq_handlers[irq]; + if (!handler.function) + { + kwarnln("Unhandled IRQ catched! Halting."); + CPU::efficient_halt(); + } + + handler.function(regs, handler.context); pic_eoi(regs); } else if (regs->isr == 66) // System call @@ -174,7 +198,7 @@ extern "C" void arch_interrupt_entry(Registers* regs) } else { - kwarnln("IRQ catched! Halting."); + kwarnln("Unhandled interrupt catched! Halting."); CPU::efficient_halt(); } } @@ -233,6 +257,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, nullptr); + register_interrupt(1, keyboard_interrupt, nullptr); } void platform_finish_init() @@ -241,6 +269,8 @@ namespace CPU .expect_value("Could not create the IO background thread!"); remap_pic(); + + sync_interrupts(); } void enable_interrupts() @@ -253,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"); @@ -353,6 +397,35 @@ namespace CPU __get_cpuid(1, &unused, &ebx, &unused, &unused); return (u16)(ebx >> 24); } + + bool register_interrupt(u8 interrupt, interrupt_handler_t handler, void* context) + { + if (irq_handlers[interrupt].function) return false; + + 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/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/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); } } 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..d30aca58 --- /dev/null +++ b/kernel/src/arch/x86_64/disk/ATA.cpp @@ -0,0 +1,788 @@ +#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 "fs/MBR.h" +#include "memory/MemoryManager.h" +#include +#include +#include +#include + +SharedPtr g_controller; + +static void irq_handler(Registers* regs, void* ctx) +{ + ((ATA::Controller*)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() + { + // 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 }); + + if (!g_controller) kwarnln("ata: No ATA controller found."); + + return {}; + } + + 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(); + } + + 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, {}) + { + } + + Channel::Channel(Controller* controller, u8 channel_index, Badge) + : m_controller(controller), m_channel_index(channel_index) + { + } + + u8 Channel::read_register(Register reg) + { + return IO::inb(m_io_base + (u16)reg); + } + + u16 Channel::read_data() + { + 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); + } + + 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); + } + + 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. + for (int i = 0; i < 14; i++) { [[maybe_unused]] volatile u8 val = 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; + } + + void Channel::irq_handler(Registers*) + { + 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(); + + 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() + { + if (!m_irq_called) kernel_wait_for_event(); + + m_irq_called = false; + } + + bool Channel::wait_for_irq_or_timeout(u64 timeout) + { + if (!m_irq_called) + { + kernel_sleep(timeout); + m_irq_called = false; + return m_thread->sleep_ticks_left; + } + + m_irq_called = false; + return true; + } + + bool Channel::wait_for_reg_set(Register reg, u8 value, u64 timeout) + { + u64 begin = Timer::ticks_ms(); + while (true) + { + 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); + } + } + + bool Channel::wait_for_reg_clear(Register reg, u8 value, u64 timeout) + { + u64 begin = Timer::ticks_ms(); + while (true) + { + 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); + } + } + + 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_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, + m_current_drive); + return err(EIO); + } + + return {}; + } + + bool Channel::initialize() + { + int offset = m_channel_index ? 2 : 0; + m_is_pci_native_mode = m_controller->device().type.prog_if & (1 << offset); + + u16 control_port_base_address; + u16 io_base_address; + + if (m_is_pci_native_mode) + { + 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() + 2; + } + else + { + io_base_address = m_channel_index ? 0x170 : 0x1f0; + control_port_base_address = m_channel_index ? 0x376 : 0x3f6; + } + + m_io_base = io_base_address; + m_control_base = control_port_base_address; + + 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; + + write_control(ControlRegister::DeviceControl, 0); + + 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); + + 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; + } + } + + CPU::register_interrupt(m_interrupt_line, ::irq_handler, m_controller); + + for (u8 drive = 0; drive < 2; drive++) + { + if (m_drives[drive]) + { + if (!m_drives[drive]->post_initialize()) + { + m_drives[drive] = {}; + return false; + } + + 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; + } + + auto device = rc.release_value(); + MBR::identify(device); + } + } + + 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.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(" "); + m_model.trim(" "); + + 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); + + memset(m_dma_prdt, 0, ARCH_PAGE_SIZE); + + 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); + + 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; + 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; + } + + 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); + + 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; + } + + 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 {}; + } + +#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); + } + + 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) + { + 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. + u8 status = m_channel->read_register(Register::Status); + + if (status & SR_Error) + { + 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); + } + } +} + +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; + 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; +} + +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 new file mode 100644 index 00000000..53207b0a --- /dev/null +++ b/kernel/src/arch/x86_64/disk/ATA.h @@ -0,0 +1,334 @@ +#pragma once +#include "arch/PCI.h" +#include "fs/devices/DeviceRegistry.h" +#include "lib/KMutex.h" +#include +#include +#include + +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 = 7, + }; + + enum class ControlRegister : u16 + { + AltStatus = 0, + DeviceControl = 0, + DriveAddress = 1, + }; + + enum class BusmasterRegister : u16 + { + Command = 0, + Status = 2, + PRDTAddress = 4, + }; + + enum StatusRegister : u8 + { + 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 : u8 + { + CMD_Identify = 0xec, + CMD_Packet = 0xa0, + CMD_Identify_Packet = 0xa1 + }; + + enum BusMasterStatus : u8 + { + BMS_SimplexOnly = 0x80, + BMS_SlaveInit = 0x40, + BMS_MasterInit = 0x20, + BMS_IRQPending = 0x4, + BMS_DMAFailure = 0x2, + BMS_DMAMode = 0x1 + }; + + enum BusMasterCommand : u8 + { + BMC_ReadWrite = 0x8, + BMC_StartStop = 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, + ATAPI_Read = 0xa8, + }; + + class Controller; + class Channel; + + struct prdt_entry + { + u32 address; + u16 count; + 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 + { + public: + Drive(Channel* channel, u8 drive_index, Badge); + + bool initialize(); + + bool post_initialize(); + + 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); + + static Result create_drive_name(SharedPtr drive); + + 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; + + u8 m_drive_index; + union { + u16 m_identify_words[256]; + 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; + + 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; + constexpr static usize MODEL_LEN = 40; + + StaticString m_serial; + StaticString m_revision; + StaticString m_model; + }; + + class Channel + { + public: + Channel(Controller* controller, u8 channel_index, Badge); + + 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); + + 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); + + Result wait_until_ready(); + + void delay_400ns(); + + void prepare_for_irq(); + + void wait_for_irq(); + 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(); + + private: + Controller* m_controller; + u8 m_channel_index; + bool m_is_pci_native_mode; + + u8 m_interrupt_line; + + KMutex<100> m_lock {}; + + 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]; + }; + + class Controller + { + public: + static Result scan(); + + const PCI::Device& device() const + { + return m_device; + } + + bool initialize(); + + void irq_handler(Registers*); + + private: + Controller(const PCI::Device& device); + PCI::Device m_device; + Channel m_primary_channel; + Channel m_secondary_channel; + }; + +} + +class ATADevice : public Device +{ + public: + // Initializer for DeviceRegistry. + static Result> create(SharedPtr drive); + + Result read(u8*, usize, usize) const override; + + Result write(const u8*, usize, usize) override + { + return err(ENOTSUP); + } + + bool blocking() const override + { + return false; + } + + bool is_block_device() const override + { + return true; + } + + usize size() const override + { + 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/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..03a82fce 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) @@ -49,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)); } diff --git a/kernel/src/fs/MBR.cpp b/kernel/src/fs/MBR.cpp new file mode 100644 index 00000000..0a585abe --- /dev/null +++ b/kernel/src/fs/MBR.cpp @@ -0,0 +1,78 @@ +#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; + 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]; + 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); + + 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 new file mode 100644 index 00000000..7203febb --- /dev/null +++ b/kernel/src/fs/MBR.h @@ -0,0 +1,83 @@ +#pragma once + +#include "fs/devices/DeviceRegistry.h" +#include +#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]; + }; + + 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 552a78c9..8ee64e9f 100644 --- a/kernel/src/fs/devices/DeviceRegistry.h +++ b/kernel/src/fs/devices/DeviceRegistry.h @@ -13,6 +13,8 @@ namespace DeviceRegistry Console = 1, 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; }; diff --git a/kernel/src/lib/KMutex.h b/kernel/src/lib/KMutex.h new file mode 100644 index 00000000..8929de06 --- /dev/null +++ b/kernel/src/lib/KMutex.h @@ -0,0 +1,77 @@ +#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 }; +}; 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); diff --git a/libluna/include/luna/StaticString.h b/libluna/include/luna/StaticString.h index b038ed8c..33e79b11 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) @@ -46,11 +62,38 @@ 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; } + 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 };