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 \ + $@ \