From e118c9ea0d0639d3cf7340596e237e35c2595712 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 13 May 2023 16:13:26 +0200 Subject: [PATCH] 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 };