Kernel: Implement AHCI driver

SATA drives can now be used with banan-os. This allows much faster
disk access (writing 10 MiB from 30s to 1.5s). This can definitely
be optimized but the main slow down is probably the whole disk
structure in the os.

AHCI drive is now the default when running qemu.
This commit is contained in:
Bananymous 2023-10-12 21:53:48 +03:00
parent d3e5c8e0aa
commit a2b5e71654
9 changed files with 798 additions and 7 deletions

View File

@ -53,6 +53,8 @@ set(KERNEL_SOURCES
kernel/Semaphore.cpp
kernel/SpinLock.cpp
kernel/SSP.cpp
kernel/Storage/ATA/AHCI/Controller.cpp
kernel/Storage/ATA/AHCI/Device.cpp
kernel/Storage/ATA/ATABus.cpp
kernel/Storage/ATA/ATAController.cpp
kernel/Storage/ATA/ATADevice.cpp

View File

@ -0,0 +1,43 @@
#pragma once
#include <BAN/Array.h>
#include <BAN/RefPtr.h>
#include <kernel/InterruptController.h>
#include <kernel/Memory/DMARegion.h>
#include <kernel/PCI.h>
#include <kernel/Storage/ATA/AHCI/Definitions.h>
namespace Kernel
{
class AHCIController final : public StorageController, public Interruptable
{
BAN_NON_COPYABLE(AHCIController);
BAN_NON_MOVABLE(AHCIController);
public:
~AHCIController();
virtual void handle_irq() override;
uint32_t command_slot_count() const { return m_command_slot_count; }
private:
AHCIController(PCI::Device& pci_device)
: m_pci_device(pci_device)
{ }
BAN::ErrorOr<void> initialize();
BAN::Optional<AHCIPortType> check_port_type(volatile HBAPortMemorySpace&);
private:
PCI::Device& m_pci_device;
BAN::UniqPtr<PCI::BarRegion> m_abar;
BAN::Array<AHCIDevice*, 32> m_devices;
uint32_t m_command_slot_count { 0 };
friend class ATAController;
};
}

View File

@ -0,0 +1,294 @@
#pragma once
#include <stdint.h>
#define FIS_TYPE_REGISTER_H2D 0x27
#define FIS_TYPE_REGISTER_D2H 0x34
#define FIS_TYPE_DMA_ACT 0x39
#define FIS_TYPE_DMA_SETUP 0x41
#define FIS_TYPE_DATA 0x46
#define FIS_TYPE_BIST 0x58
#define FIS_TYPE_PIO_SETUP 0x5F
#define FIS_TYPE_SET_DEVIVE_BITS 0xA1
#define SATA_CAP_SUPPORTS64 (1 << 31)
#define SATA_GHC_AHCI_ENABLE (1 << 31)
#define SATA_GHC_INTERRUPT_ENABLE (1 << 1)
#define SATA_SIG_ATA 0x00000101
#define SATA_SIG_ATAPI 0xEB140101
#define SATA_SIG_SEMB 0xC33C0101
#define SATA_SIG_PM 0x96690101
#define HBA_PORT_IPM_ACTIVE 1
#define HBA_PORT_DET_PRESENT 3
#define HBA_PxCMD_ST 0x0001
#define HBA_PxCMD_FRE 0x0010
#define HBA_PxCMD_FR 0x4000
#define HBA_PxCMD_CR 0x8000
namespace Kernel
{
static constexpr uint32_t s_hba_prdt_count { 8 };
struct FISRegisterH2D
{
uint8_t fis_type; // FIS_TYPE_REGISTER_H2D
uint8_t pm_port : 4; // Port multiplier
uint8_t __reserved0 : 3;
uint8_t c : 1; // 1: Command, 0: Control
uint8_t command;
uint8_t feature_lo; // Feature register, 7:0
uint8_t lba0; // LBA low register, 7:0
uint8_t lba1; // LBA mid register, 15:8
uint8_t lba2; // LBA high register, 23:16
uint8_t device;
uint8_t lba3; // LBA register, 31:24
uint8_t lba4; // LBA register, 39:32
uint8_t lba5; // LBA register, 47:40
uint8_t feature_hi; // Feature register, 15:8
uint8_t count_lo; // Count register, 7:0
uint8_t count_hi; // Count register, 15:8
uint8_t icc; // Isochronous command completion
uint8_t control;
uint8_t __reserved1[4];
} __attribute__((packed));
struct FISRegisterD2H
{
uint8_t fis_type; // FIS_TYPE_REGISTER_D2H
uint8_t pm_port : 4; // Port multiplier
uint8_t __reserved0 : 2;
uint8_t i : 1; // Interrupt bit
uint8_t __reserved1 : 1;
uint8_t status;
uint8_t error;
uint8_t lba0; // LBA low register, 7:0
uint8_t lba1; // LBA mid register, 15:8
uint8_t lba2; // LBA high register, 23:16
uint8_t device;
uint8_t lba3; // LBA register, 31:24
uint8_t lba4; // LBA register, 39:32
uint8_t lba5; // LBA register, 47:40
uint8_t __reserved2;
uint8_t count_lo; // Count register, 7:0
uint8_t count_hi; // Count register, 15:8
uint8_t __reserved3[2];
uint8_t __reserved4[4];
} __attribute__((packed));
struct FISDataBI
{
uint8_t fis_type; // FIS_TYPE_DATA
uint8_t pm_port : 4; // Port multiplier
uint8_t __reserved0 : 4;
uint8_t __reserved1[2];
uint32_t data[0]; // Payload (1 - 2048 dwords)
} __attribute__((packed));
struct SetDeviceBitsD2H
{
uint8_t fis_type; // FIS_TYPE_SET_DEVICE_BITS
uint8_t pm_port : 4; // Port multiplier
uint8_t __reserved0 : 2;
uint8_t i : 1; // Interrupt bit
uint8_t n : 1; // Notification bit
uint8_t status;
uint8_t error;
uint32_t __reserved1;
} __attribute__((packed));
struct PIOSetupD2H
{
uint8_t fis_type; // FIS_TYPE_PIO_SETUP
uint8_t pm_port : 4; // Port multiplier
uint8_t __reserved0 : 1;
uint8_t d : 1; // Data transfer direction, 1 - device to host
uint8_t i : 1; // Interrupt bit
uint8_t __reserved1 : 1;
uint8_t status;
uint8_t error;
uint8_t lba0; // LBA low register, 7:0
uint8_t lba1; // LBA mid register, 15:8
uint8_t lba2; // LBA high register, 23:16
uint8_t device;
uint8_t lba3; // LBA register, 31:24
uint8_t lba4; // LBA register, 39:32
uint8_t lba5; // LBA register, 47:40
uint8_t __reserved2;
uint8_t count_lo; // Count register, 7:0
uint8_t count_hi; // Count register, 15:8
uint8_t __reserved3;
uint8_t e_status; // New value of status register
uint16_t tc; // Transfer count
uint8_t __reserved4[2];
} __attribute__((packed));
struct DMASetupBI
{
uint8_t fis_type; // FIS_TYPE_DMA_SETUP
uint8_t pm_port : 4; // Port multiplier
uint8_t __reserved0 : 1;
uint8_t d : 1; // Data transfer direction, 1 - device to host
uint8_t i : 1; // Interrupt bit
uint8_t a : 1; // Auto-activate. Specifies if DMA Activate FIS is needed
uint8_t __reserved1[2];
uint64_t dma_buffer_id; // DMA Buffer Identifier. Used to Identify DMA buffer in host memory.
// SATA Spec says host specific and not in Spec. Trying AHCI spec might work.
uint32_t __reserved2;
uint32_t dma_buffer_offset; // Byte offset into buffer. First 2 bits must be 0
uint32_t dma_transfer_count; // Number of bytes to transfer. Bit 0 must be 0
uint32_t __reserved3;
} __attribute__((packed));
struct HBAPortMemorySpace
{
uint32_t clb; // command list base address, 1K-byte aligned
uint32_t clbu; // command list base address upper 32 bits
uint32_t fb; // FIS base address, 256-byte aligned
uint32_t fbu; // FIS base address upper 32 bits
uint32_t is; // interrupt status
uint32_t ie; // interrupt enable
uint32_t cmd; // command and status
uint32_t __reserved0;
uint32_t tfd; // task file data
uint32_t sig; // signature
uint32_t ssts; // SATA status (SCR0:SStatus)
uint32_t sctl; // SATA control (SCR2:SControl)
uint32_t serr; // SATA error (SCR1:SError)
uint32_t sact; // SATA active (SCR3:SActive)
uint32_t ci; // command issue
uint32_t sntf; // SATA notification (SCR4:SNotification)
uint32_t fbs; // FIS-based switch control
uint32_t __reserved1[11];
uint32_t vendor[4];
} __attribute__((packed));
struct HBAGeneralMemorySpace
{
uint32_t cap; // Host capability
uint32_t ghc; // Global host control
uint32_t is; // Interrupt status
uint32_t pi; // Port implemented
uint32_t vs; // Version
uint32_t ccc_ctl; // Command completion coalescing control
uint32_t ccc_pts; // Command completion coalescing ports
uint32_t em_loc; // 0x1C, Enclosure management location
uint32_t em_ctl; // 0x20, Enclosure management control
uint32_t cap2; // 0x24, Host capabilities extended
uint32_t bohc; // 0x28, BIOS/OS handoff control and status
uint8_t __reserved0[0xA0-0x2C];
uint8_t vendor[0x100-0xA0];
HBAPortMemorySpace ports[0]; // 1 - 32 ports
} __attribute__((packed));
struct ReceivedFIS
{
DMASetupBI dsfis;
uint8_t pad0[4];
PIOSetupD2H psfis;
uint8_t pad1[12];
FISRegisterD2H rfis;
uint8_t pad2[4];
SetDeviceBitsD2H sdbfis;
uint8_t ufis[64];
uint8_t __reserved[0x100-0xA0];
} __attribute__((packed));
struct HBACommandHeader
{
uint8_t cfl : 5; // Command FIS length in DWORDS, 2 ~ 16
uint8_t a : 1; // ATAPI
uint8_t w : 1; // Write, 1: H2D, 0: D2H
uint8_t p : 1; // Prefetchable
uint8_t r : 1; // Reset
uint8_t b : 1; // BIST
uint8_t c : 1; // Clear busy upon R_OK
uint8_t __reserved0 : 1;
uint8_t pmp : 4; // Port multiplier port
uint16_t prdtl; // Physical region descriptor table length in entries
volatile uint32_t prdbc; // Physical region descriptor byte count transferred
uint32_t ctba; // Command table descriptor base address
uint32_t ctbau; // Command table descriptor base address upper 32 bits
uint32_t __reserved1[4];
} __attribute__((packed));
struct HBAPRDTEntry
{
uint32_t dba; // Data base address
uint32_t dbau; // Data base address upper 32 bits
uint32_t __reserved0;
uint32_t dbc : 22; // Byte count, 4M max
uint32_t __reserved1 : 9;
uint32_t i : 1; // Interrupt on completion
} __attribute__((packed));
struct HBACommandTable
{
uint8_t cfis[64];
uint8_t acmd[16];
uint8_t __reserved[48];
HBAPRDTEntry prdt_entry[s_hba_prdt_count];
} __attribute__((packed));
enum class AHCIPortType
{
NONE,
SATA,
SATAPI,
SEMB,
PM
};
class AHCIController;
class AHCIDevice;
}

View File

@ -0,0 +1,49 @@
#pragma once
#include <kernel/Semaphore.h>
#include <kernel/Storage/ATA/AHCI/Definitions.h>
#include <kernel/Storage/ATA/ATADevice.h>
namespace Kernel
{
class AHCIDevice final : public detail::ATABaseDevice
{
public:
static BAN::ErrorOr<BAN::RefPtr<AHCIDevice>> create(BAN::RefPtr<AHCIController>, volatile HBAPortMemorySpace*);
~AHCIDevice() = default;
private:
AHCIDevice(BAN::RefPtr<AHCIController> controller, volatile HBAPortMemorySpace* port)
: m_controller(controller)
, m_port(port)
{ }
BAN::ErrorOr<void> initialize();
BAN::ErrorOr<void> allocate_buffers();
BAN::ErrorOr<void> rebase();
BAN::ErrorOr<void> read_identify_data();
virtual BAN::ErrorOr<void> read_sectors_impl(uint64_t lba, uint64_t sector_count, uint8_t* buffer) override;
virtual BAN::ErrorOr<void> write_sectors_impl(uint64_t lba, uint64_t sector_count, const uint8_t* buffer) override;
BAN::ErrorOr<void> send_command_and_block(uint64_t lba, uint64_t sector_count, Command command);
BAN::Optional<uint32_t> find_free_command_slot();
void handle_irq();
void block_until_irq();
private:
BAN::RefPtr<AHCIController> m_controller;
volatile HBAPortMemorySpace* const m_port;
BAN::UniqPtr<DMARegion> m_dma_region;
// Intermediate read/write buffer
// TODO: can we read straight to user buffer?
BAN::UniqPtr<DMARegion> m_data_dma_region;
volatile bool m_has_got_irq { false };
friend class AHCIController;
};
}

View File

@ -33,7 +33,11 @@
#define ATA_STATUS_BSY 0x80
#define ATA_COMMAND_READ_SECTORS 0x20
#define ATA_COMMAND_READ_DMA 0xC8
#define ATA_COMMAND_READ_DMA_EXT 0x25
#define ATA_COMMAND_WRITE_SECTORS 0x30
#define ATA_COMMAND_WRITE_DMA 0xCA
#define ATA_COMMAND_WRITE_DMA_EXT 0x35
#define ATA_COMMAND_IDENTIFY_PACKET 0xA1
#define ATA_COMMAND_CACHE_FLUSH 0xE7
#define ATA_COMMAND_IDENTIFY 0xEC

View File

@ -0,0 +1,120 @@
#include <kernel/Memory/Heap.h>
#include <kernel/Memory/PageTable.h>
#include <kernel/Storage/ATA/AHCI/Controller.h>
#include <kernel/Storage/ATA/AHCI/Definitions.h>
#include <kernel/Storage/ATA/AHCI/Device.h>
namespace Kernel
{
BAN::ErrorOr<void> AHCIController::initialize()
{
m_abar = TRY(m_pci_device.allocate_bar_region(5));
if (m_abar->type() != PCI::BarType::MEM)
{
dprintln("ABAR not MMIO");
return BAN::Error::from_errno(EINVAL);
}
auto& abar_mem = *(volatile HBAGeneralMemorySpace*)m_abar->vaddr();
if (!(abar_mem.ghc & SATA_GHC_AHCI_ENABLE))
{
dprintln("Controller not in AHCI mode");
return BAN::Error::from_errno(EINVAL);
}
// Enable interrupts and bus mastering
m_pci_device.enable_bus_mastering();
m_pci_device.enable_pin_interrupts();
set_irq(TRY(m_pci_device.get_irq()));
enable_interrupt();
abar_mem.ghc = abar_mem.ghc | SATA_GHC_INTERRUPT_ENABLE;
m_command_slot_count = ((abar_mem.cap >> 8) & 0x1F) + 1;
uint32_t pi = abar_mem.pi;
for (uint32_t i = 0; i < 32 && pi; i++, pi >>= 1)
{
// Verify we don't access abar outside of its bounds
if (sizeof(HBAGeneralMemorySpace) + i * sizeof(HBAPortMemorySpace) > m_abar->size())
break;
if (!(pi & 1))
continue;
auto type = check_port_type(abar_mem.ports[i]);
if (!type.has_value())
continue;
if (type.value() != AHCIPortType::SATA)
{
dprintln("Non-SATA devices not supported");
continue;
}
auto device = AHCIDevice::create(this, &abar_mem.ports[i]);
if (device.is_error())
{
dprintln("{}", device.error());
continue;
}
m_devices[i] = device.value().ptr();
if (auto ret = m_devices[i]->initialize(); ret.is_error())
{
dprintln("{}", ret.error());
m_devices[i] = nullptr;
}
}
return {};
}
AHCIController::~AHCIController()
{
}
void AHCIController::handle_irq()
{
auto& abar_mem = *(volatile HBAGeneralMemorySpace*)m_abar->vaddr();
uint32_t is = abar_mem.is;
for (uint8_t i = 0; i < 32; i++)
{
if (is & (1 << i))
{
ASSERT(m_devices[i]);
m_devices[i]->handle_irq();
}
}
abar_mem.is = is;
}
BAN::Optional<AHCIPortType> AHCIController::check_port_type(volatile HBAPortMemorySpace& port)
{
uint32_t ssts = port.ssts;
uint8_t ipm = (ssts >> 8) & 0x0F;
uint8_t det = (ssts >> 0) & 0x0F;
if (det != HBA_PORT_DET_PRESENT)
return {};
if (ipm != HBA_PORT_IPM_ACTIVE)
return {};
switch (port.sig)
{
case SATA_SIG_ATA:
return AHCIPortType::SATA;
case SATA_SIG_ATAPI:
return AHCIPortType::SATAPI;
case SATA_SIG_PM:
return AHCIPortType::PM;
case SATA_SIG_SEMB:
return AHCIPortType::SEMB;
}
return {};
}
}

View File

@ -0,0 +1,276 @@
#include <kernel/Storage/ATA/AHCI/Controller.h>
#include <kernel/Storage/ATA/AHCI/Device.h>
#include <kernel/Storage/ATA/ATADefinitions.h>
#include <kernel/Thread.h>
#include <kernel/Timer/Timer.h>
namespace Kernel
{
static void start_cmd(volatile HBAPortMemorySpace* port)
{
while (port->cmd & HBA_PxCMD_CR)
continue;
port->cmd = port->cmd | HBA_PxCMD_FRE;
port->cmd = port->cmd | HBA_PxCMD_ST;
}
static void stop_cmd(volatile HBAPortMemorySpace* port)
{
port->cmd = port->cmd & ~HBA_PxCMD_ST;
port->cmd = port->cmd & ~HBA_PxCMD_FRE;
while (port->cmd & (HBA_PxCMD_FR | HBA_PxCMD_CR))
continue;
}
BAN::ErrorOr<BAN::RefPtr<AHCIDevice>> AHCIDevice::create(BAN::RefPtr<AHCIController> controller, volatile HBAPortMemorySpace* port)
{
auto* device_ptr = new AHCIDevice(controller, port);
if (device_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
return BAN::RefPtr<AHCIDevice>::adopt(device_ptr);
}
BAN::ErrorOr<void> AHCIDevice::initialize()
{
TRY(allocate_buffers());
TRY(rebase());
// enable interrupts
m_port->ie = 0xFFFFFFFF;
TRY(read_identify_data());
TRY(detail::ATABaseDevice::initialize({ (const uint16_t*)m_data_dma_region->vaddr(), m_data_dma_region->size() }));
return {};
}
BAN::ErrorOr<void> AHCIDevice::allocate_buffers()
{
uint32_t command_slot_count = m_controller->command_slot_count();
size_t needed_bytes = (sizeof(HBACommandHeader) + sizeof(HBACommandTable)) * command_slot_count + sizeof(ReceivedFIS);
m_dma_region = TRY(DMARegion::create(needed_bytes));
memset((void*)m_dma_region->vaddr(), 0x00, m_dma_region->size());
m_data_dma_region = TRY(DMARegion::create(PAGE_SIZE));
memset((void*)m_data_dma_region->vaddr(), 0x00, m_data_dma_region->size());
return {};
}
BAN::ErrorOr<void> AHCIDevice::rebase()
{
ASSERT(m_dma_region);
uint32_t command_slot_count = m_controller->command_slot_count();
stop_cmd(m_port);
paddr_t fis_paddr = m_dma_region->paddr();
m_port->fb = fis_paddr & 0xFFFFFFFF;
m_port->fbu = fis_paddr >> 32;
paddr_t command_list_paddr = fis_paddr + sizeof(ReceivedFIS);
m_port->clb = command_list_paddr & 0xFFFFFFFF;
m_port->clbu = command_list_paddr >> 32;
auto* command_headers = (HBACommandHeader*)m_dma_region->paddr_to_vaddr(command_list_paddr);
paddr_t command_table_paddr = command_list_paddr + command_slot_count * sizeof(HBACommandHeader);
for (uint32_t i = 0; i < command_slot_count; i++)
{
uint64_t command_table_entry_paddr = command_table_paddr + i * sizeof(HBACommandTable);
command_headers[i].prdtl = s_hba_prdt_count;
command_headers[i].ctba = command_table_entry_paddr & 0xFFFFFFFF;
command_headers[i].ctbau = command_table_entry_paddr >> 32;
}
start_cmd(m_port);
return {};
}
BAN::ErrorOr<void> AHCIDevice::read_identify_data()
{
ASSERT(m_data_dma_region);
m_port->is = ~(uint32_t)0;
auto slot = find_free_command_slot();
ASSERT(slot.has_value());
auto& command_header = ((HBACommandHeader*)m_dma_region->paddr_to_vaddr(m_port->clb))[slot.value()];
command_header.cfl = sizeof(FISRegisterH2D) / sizeof(uint32_t);
command_header.w = 0;
command_header.prdtl = 1;
auto& command_table = *(HBACommandTable*)m_dma_region->paddr_to_vaddr(command_header.ctba);
memset(&command_table, 0x00, sizeof(HBACommandTable));
command_table.prdt_entry[0].dba = m_data_dma_region->paddr();
command_table.prdt_entry[0].dbc = 511;
command_table.prdt_entry[0].i = 1;
auto& command = *(FISRegisterH2D*)command_table.cfis;
command.fis_type = FIS_TYPE_REGISTER_H2D;
command.c = 1;
command.command = ATA_COMMAND_IDENTIFY;
while (m_port->tfd & (ATA_STATUS_BSY | ATA_STATUS_DRQ))
continue;
m_port->ci = 1 << slot.value();
// FIXME: timeout
do { block_until_irq(); } while (m_port->ci & (1 << slot.value()));
return {};
}
static void print_error(uint16_t error)
{
dprintln("Disk error:");
if (error & (1 << 11))
dprintln(" Internal Error");
if (error & (1 << 10))
dprintln(" Protocol Error");
if (error & (1 << 9))
dprintln(" Persistent Communication or Data Integrity Error");
if (error & (1 << 8))
dprintln(" Transient Data Integrity Error");
if (error & (1 << 1))
dprintln(" Recovered Communications Error");
if (error & (1 << 0))
dprintln(" Recovered Data Integrity Error");
}
void AHCIDevice::handle_irq()
{
ASSERT(!m_has_got_irq);
uint16_t err = m_port->serr & 0xFFFF;
if (err)
print_error(err);
m_has_got_irq = true;
}
void AHCIDevice::block_until_irq()
{
while (!__sync_bool_compare_and_swap(&m_has_got_irq, true, false))
__builtin_ia32_pause();
}
BAN::ErrorOr<void> AHCIDevice::read_sectors_impl(uint64_t lba, uint64_t sector_count, uint8_t* buffer)
{
const size_t sectors_per_page = PAGE_SIZE / sector_size();
for (uint64_t sector_off = 0; sector_off < sector_count; sector_off += sectors_per_page)
{
uint64_t to_read = BAN::Math::min<uint64_t>(sector_count - sector_off, sectors_per_page);
TRY(send_command_and_block(lba + sector_off, to_read, Command::Read));
memcpy(buffer + sector_off * sector_size(), (void*)m_data_dma_region->vaddr(), to_read * sector_size());
}
return {};
}
BAN::ErrorOr<void> AHCIDevice::write_sectors_impl(uint64_t lba, uint64_t sector_count, const uint8_t* buffer)
{
const size_t sectors_per_page = PAGE_SIZE / sector_size();
for (uint64_t sector_off = 0; sector_off < sector_count; sector_off += sectors_per_page)
{
uint64_t to_read = BAN::Math::min<uint64_t>(sector_count - sector_off, sectors_per_page);
memcpy((void*)m_data_dma_region->vaddr(), buffer + sector_off * sector_size(), to_read * sector_size());
TRY(send_command_and_block(lba + sector_off, to_read, Command::Write));
}
return {};
}
BAN::ErrorOr<void> AHCIDevice::send_command_and_block(uint64_t lba, uint64_t sector_count, Command command)
{
ASSERT(m_dma_region);
ASSERT(m_data_dma_region);
ASSERT(0 < sector_count && sector_count <= 0xFFFF + 1);
ASSERT(sector_count * sector_size() <= m_data_dma_region->size());
m_port->is = ~(uint32_t)0;
auto slot = find_free_command_slot();
ASSERT(slot.has_value());
auto& command_header = ((HBACommandHeader*)m_dma_region->paddr_to_vaddr(m_port->clb))[slot.value()];
command_header.cfl = sizeof(FISRegisterH2D) / sizeof(uint32_t);
command_header.prdtl = 1;
switch (command)
{
case Command::Read:
command_header.w = 0;
break;
case Command::Write:
command_header.w = 1;
break;
default:
ASSERT_NOT_REACHED();
}
auto& command_table = *(HBACommandTable*)m_dma_region->paddr_to_vaddr(command_header.ctba);
memset(&command_table, 0x00, sizeof(HBACommandTable));
command_table.prdt_entry[0].dba = m_data_dma_region->paddr() & 0xFFFFFFFF;
command_table.prdt_entry[0].dbau = m_data_dma_region->paddr() >> 32;
command_table.prdt_entry[0].dbc = sector_count * sector_size() - 1;
command_table.prdt_entry[0].i = 1;
auto& fis_command = *(FISRegisterH2D*)command_table.cfis;
memset(&fis_command, 0x00, sizeof(FISRegisterH2D));
fis_command.fis_type = FIS_TYPE_REGISTER_H2D;
fis_command.c = 1;
bool need_extended = lba >= (1 << 28) || sector_count > 0xFF;
ASSERT (!need_extended || (m_command_set & ATA_COMMANDSET_LBA48_SUPPORTED));
switch (command)
{
case Command::Read:
fis_command.command = need_extended ? ATA_COMMAND_READ_DMA_EXT : ATA_COMMAND_READ_DMA;
break;
case Command::Write:
fis_command.command = need_extended ? ATA_COMMAND_WRITE_DMA_EXT : ATA_COMMAND_WRITE_DMA;
break;
default:
ASSERT_NOT_REACHED();
}
fis_command.lba0 = (lba >> 0) & 0xFF;
fis_command.lba1 = (lba >> 8) & 0xFF;
fis_command.lba2 = (lba >> 16) & 0xFF;
fis_command.device = 1 << 6; // LBA mode
fis_command.lba3 = (lba >> 24) & 0xFF;
fis_command.lba4 = (lba >> 32) & 0xFF;
fis_command.lba5 = (lba >> 40) & 0xFF;
fis_command.count_lo = (sector_count >> 0) & 0xFF;
fis_command.count_hi = (sector_count >> 8) & 0xFF;
while (m_port->tfd & (ATA_STATUS_BSY | ATA_STATUS_DRQ))
continue;
m_port->ci = 1 << slot.value();
// FIXME: timeout
do { block_until_irq(); } while (m_port->ci & (1 << slot.value()));
return {};
}
BAN::Optional<uint32_t> AHCIDevice::find_free_command_slot()
{
uint32_t slots = m_port->sact | m_port->ci;
for (uint32_t i = 0; i < m_controller->command_slot_count(); i++, slots >>= 1)
if (!(slots & 1))
return i;
return {};
}
}

View File

@ -1,4 +1,5 @@
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/Storage/ATA/AHCI/Controller.h>
#include <kernel/Storage/ATA/ATABus.h>
#include <kernel/Storage/ATA/ATAController.h>
#include <kernel/Storage/ATA/ATADefinitions.h>
@ -20,8 +21,8 @@ namespace Kernel
dwarnln("unsupported DMA ATA Controller");
return BAN::Error::from_errno(ENOTSUP);
case 0x06:
dwarnln("unsupported SATA Controller");
return BAN::Error::from_errno(ENOTSUP);
controller_ptr = new AHCIController(pci_device);
break;
default:
ASSERT_NOT_REACHED();
}

12
qemu.sh
View File

@ -1,8 +1,10 @@
#!/bin/bash
set -e
qemu-system-$BANAN_ARCH \
-m 128 \
-smp 2 \
-drive format=raw,media=disk,file=${DISK_IMAGE_PATH} \
$@ \
qemu-system-$BANAN_ARCH \
-m 128 \
-smp 2 \
-drive format=raw,id=disk,file=${DISK_IMAGE_PATH},if=none \
-device ahci,id=ahci \
-device ide-hd,drive=disk,bus=ahci.0 \
$@ \