From 2fa11a5ae360fbcaed4284e51836a0e40e1334cb Mon Sep 17 00:00:00 2001 From: apio Date: Sun, 14 May 2023 22:39:29 +0200 Subject: [PATCH] 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];