kernel/ATA: Implement enough to send an IDENTIFY command and read the model number :)
This commit is contained in:
parent
ee691bbb0f
commit
e118c9ea0d
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 };
|
||||
|
Loading…
Reference in New Issue
Block a user