Add ATA drive support #27

Merged
apio merged 28 commits from please-read-my-ata-drive into main 2023-06-16 19:40:11 +00:00
3 changed files with 229 additions and 5 deletions
Showing only changes of commit e118c9ea0d - Show all commits

View File

@ -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 <luna/Vector.h>
@ -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<Channel>) : 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;
}
}
}

View File

@ -4,6 +4,7 @@
#include "lib/KMutex.h"
#include <luna/Atomic.h>
#include <luna/SharedPtr.h>
#include <luna/StaticString.h>
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<Channel>);
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<SERIAL_LEN> m_serial;
StaticString<REVISION_LEN> m_revision;
StaticString<MODEL_LEN> m_model;
};
class Channel
@ -46,10 +90,14 @@ namespace ATA
Channel(Controller* controller, u8 channel_index, Badge<Controller>);
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<Drive> m_drives[2];
};
class Controller

View File

@ -1,5 +1,6 @@
#pragma once
#include <luna/CString.h>
#include <luna/StringView.h>
#include <luna/Types.h>
template <usize Size> class StaticString
@ -24,12 +25,27 @@ template <usize Size> 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<Size>& operator=(const char* string)
{
adopt(string);
return *this;
}
StaticString<Size>& operator=(StringView string)
{
adopt(string);
return *this;
}
template <usize OtherSize> StaticString<Size>& operator=(const StaticString<OtherSize>& string)
{
if constexpr (OtherSize == Size)
@ -51,6 +67,23 @@ template <usize Size> 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 };