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
2 changed files with 227 additions and 24 deletions
Showing only changes of commit 2fa11a5ae3 - Show all commits

View File

@ -4,6 +4,7 @@
#include "arch/Timer.h"
#include "arch/x86_64/IO.h"
#include "memory/MemoryManager.h"
#include <luna/CType.h>
#include <luna/SafeArithmetic.h>
#include <luna/Vector.h>
@ -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<void*>(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<void> 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<void*>(m_dma_mem), response_size);
return {};
}
Result<void> 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<void> 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<void> 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);
}
}
}

View File

@ -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<void> send_packet_atapi_pio(const atapi_packet* packet, void* out, u16 response_size);
#if 0
Result<void> send_packet_atapi_dma(const atapi_packet* packet, void* out, u16 response_size);
Result<void> do_dma_command(u8 command, u16 count, bool write);
Result<void> do_dma_transfer();
#endif
Result<void> 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<Drive> m_drives[2];