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);