From a2b5e71654a29e8660a17144189934416d4935e1 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Thu, 12 Oct 2023 21:53:48 +0300 Subject: [PATCH] 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. --- kernel/CMakeLists.txt | 2 + .../kernel/Storage/ATA/AHCI/Controller.h | 43 +++ .../kernel/Storage/ATA/AHCI/Definitions.h | 294 ++++++++++++++++++ .../include/kernel/Storage/ATA/AHCI/Device.h | 49 +++ .../kernel/Storage/ATA/ATADefinitions.h | 4 + kernel/kernel/Storage/ATA/AHCI/Controller.cpp | 120 +++++++ kernel/kernel/Storage/ATA/AHCI/Device.cpp | 276 ++++++++++++++++ kernel/kernel/Storage/ATA/ATAController.cpp | 5 +- qemu.sh | 12 +- 9 files changed, 798 insertions(+), 7 deletions(-) create mode 100644 kernel/include/kernel/Storage/ATA/AHCI/Controller.h create mode 100644 kernel/include/kernel/Storage/ATA/AHCI/Definitions.h create mode 100644 kernel/include/kernel/Storage/ATA/AHCI/Device.h create mode 100644 kernel/kernel/Storage/ATA/AHCI/Controller.cpp create mode 100644 kernel/kernel/Storage/ATA/AHCI/Device.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index c5a2c033..2c29fa36 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -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 diff --git a/kernel/include/kernel/Storage/ATA/AHCI/Controller.h b/kernel/include/kernel/Storage/ATA/AHCI/Controller.h new file mode 100644 index 00000000..7a697bc6 --- /dev/null +++ b/kernel/include/kernel/Storage/ATA/AHCI/Controller.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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 initialize(); + BAN::Optional check_port_type(volatile HBAPortMemorySpace&); + + private: + PCI::Device& m_pci_device; + BAN::UniqPtr m_abar; + + BAN::Array m_devices; + + uint32_t m_command_slot_count { 0 }; + + friend class ATAController; + }; + +} diff --git a/kernel/include/kernel/Storage/ATA/AHCI/Definitions.h b/kernel/include/kernel/Storage/ATA/AHCI/Definitions.h new file mode 100644 index 00000000..a2727bd4 --- /dev/null +++ b/kernel/include/kernel/Storage/ATA/AHCI/Definitions.h @@ -0,0 +1,294 @@ +#pragma once + +#include + +#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; + +} diff --git a/kernel/include/kernel/Storage/ATA/AHCI/Device.h b/kernel/include/kernel/Storage/ATA/AHCI/Device.h new file mode 100644 index 00000000..4e4c29fa --- /dev/null +++ b/kernel/include/kernel/Storage/ATA/AHCI/Device.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +namespace Kernel +{ + + class AHCIDevice final : public detail::ATABaseDevice + { + public: + static BAN::ErrorOr> create(BAN::RefPtr, volatile HBAPortMemorySpace*); + ~AHCIDevice() = default; + + private: + AHCIDevice(BAN::RefPtr controller, volatile HBAPortMemorySpace* port) + : m_controller(controller) + , m_port(port) + { } + BAN::ErrorOr initialize(); + BAN::ErrorOr allocate_buffers(); + BAN::ErrorOr rebase(); + BAN::ErrorOr read_identify_data(); + + virtual BAN::ErrorOr read_sectors_impl(uint64_t lba, uint64_t sector_count, uint8_t* buffer) override; + virtual BAN::ErrorOr write_sectors_impl(uint64_t lba, uint64_t sector_count, const uint8_t* buffer) override; + BAN::ErrorOr send_command_and_block(uint64_t lba, uint64_t sector_count, Command command); + + BAN::Optional find_free_command_slot(); + + void handle_irq(); + void block_until_irq(); + + private: + BAN::RefPtr m_controller; + volatile HBAPortMemorySpace* const m_port; + + BAN::UniqPtr m_dma_region; + // Intermediate read/write buffer + // TODO: can we read straight to user buffer? + BAN::UniqPtr m_data_dma_region; + + volatile bool m_has_got_irq { false }; + + friend class AHCIController; + }; + +} \ No newline at end of file diff --git a/kernel/include/kernel/Storage/ATA/ATADefinitions.h b/kernel/include/kernel/Storage/ATA/ATADefinitions.h index 37686d34..e02697fe 100644 --- a/kernel/include/kernel/Storage/ATA/ATADefinitions.h +++ b/kernel/include/kernel/Storage/ATA/ATADefinitions.h @@ -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 diff --git a/kernel/kernel/Storage/ATA/AHCI/Controller.cpp b/kernel/kernel/Storage/ATA/AHCI/Controller.cpp new file mode 100644 index 00000000..23f81e00 --- /dev/null +++ b/kernel/kernel/Storage/ATA/AHCI/Controller.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include + +namespace Kernel +{ + + BAN::ErrorOr 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 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 {}; + } + +} diff --git a/kernel/kernel/Storage/ATA/AHCI/Device.cpp b/kernel/kernel/Storage/ATA/AHCI/Device.cpp new file mode 100644 index 00000000..8b0c2521 --- /dev/null +++ b/kernel/kernel/Storage/ATA/AHCI/Device.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include + +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> AHCIDevice::create(BAN::RefPtr controller, volatile HBAPortMemorySpace* port) + { + auto* device_ptr = new AHCIDevice(controller, port); + if (device_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + return BAN::RefPtr::adopt(device_ptr); + } + + BAN::ErrorOr 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 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 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 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 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(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 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(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 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 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 {}; + } + +} diff --git a/kernel/kernel/Storage/ATA/ATAController.cpp b/kernel/kernel/Storage/ATA/ATAController.cpp index 5f968566..8a451a18 100644 --- a/kernel/kernel/Storage/ATA/ATAController.cpp +++ b/kernel/kernel/Storage/ATA/ATAController.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -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(); } diff --git a/qemu.sh b/qemu.sh index 00b38fba..f92acf2c 100755 --- a/qemu.sh +++ b/qemu.sh @@ -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 \ + $@ \