diff options
-rw-r--r-- | Kernel/CMakeLists.txt | 4 | ||||
-rw-r--r-- | Kernel/Debug.h.in | 4 | ||||
-rw-r--r-- | Kernel/Storage/AHCI.h | 536 | ||||
-rw-r--r-- | Kernel/Storage/AHCIController.cpp | 198 | ||||
-rw-r--r-- | Kernel/Storage/AHCIController.h | 77 | ||||
-rw-r--r-- | Kernel/Storage/AHCIPort.cpp | 622 | ||||
-rw-r--r-- | Kernel/Storage/AHCIPort.h | 132 | ||||
-rw-r--r-- | Kernel/Storage/AHCIPortHandler.cpp | 103 | ||||
-rw-r--r-- | Kernel/Storage/AHCIPortHandler.h | 93 | ||||
-rw-r--r-- | Kernel/Storage/ATA.h | 2 | ||||
-rw-r--r-- | Kernel/Storage/SATADiskDevice.cpp | 66 | ||||
-rw-r--r-- | Kernel/Storage/SATADiskDevice.h | 64 | ||||
-rw-r--r-- | Kernel/Storage/StorageManagement.cpp | 6 | ||||
-rw-r--r-- | Meta/CMake/all_the_debug_macros.cmake | 1 |
14 files changed, 1908 insertions, 0 deletions
diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index cd74fddb66..9283a1740c 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -55,6 +55,10 @@ set(KERNEL_SOURCES Storage/Partition/MBRPartitionTable.cpp Storage/Partition/PartitionTable.cpp Storage/StorageDevice.cpp + Storage/AHCIController.cpp + Storage/AHCIPort.cpp + Storage/AHCIPortHandler.cpp + Storage/SATADiskDevice.cpp Storage/IDEController.cpp Storage/IDEChannel.cpp Storage/PATADiskDevice.cpp diff --git a/Kernel/Debug.h.in b/Kernel/Debug.h.in index 38008f4096..9b652ef8d8 100644 --- a/Kernel/Debug.h.in +++ b/Kernel/Debug.h.in @@ -198,6 +198,10 @@ #cmakedefine01 PATA_DEBUG #endif +#ifndef AHCI_DEBUG +#cmakedefine01 AHCI_DEBUG +#endif + #ifndef PCI_DEBUG #cmakedefine01 PCI_DEBUG #endif diff --git a/Kernel/Storage/AHCI.h b/Kernel/Storage/AHCI.h new file mode 100644 index 0000000000..5be8fc3054 --- /dev/null +++ b/Kernel/Storage/AHCI.h @@ -0,0 +1,536 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/Types.h> + +namespace Kernel { +struct [[gnu::packed]] ATAIdentifyBlock { + u16 general_configuration; + u16 obsolete; + u16 specific_configuration; + + u16 obsolete2; + u16 retired[2]; + u16 obsolete3; + + u16 reserved_for_cfa[2]; + u16 retired2; + u16 serial_number[10]; + + u16 retired3[2]; + u16 obsolete4; + + u16 firmware_revision[4]; + u16 model_number[20]; + + u16 maximum_logical_sectors_per_drq; + u16 trusted_computing_features; + u16 capabilites[2]; + u16 obsolete5[2]; + u16 validity_flags; + u16 obsolete6[5]; + + u16 security_features; + + u32 max_28_bit_addressable_logical_sector; + u16 obsolete7; + u16 dma_modes; + u16 pio_modes; + + u16 minimum_multiword_dma_transfer_cycle; + u16 recommended_multiword_dma_transfer_cycle; + + u16 minimum_multiword_pio_transfer_cycle_without_flow_control; + u16 minimum_multiword_pio_transfer_cycle_with_flow_control; + + u16 additional_supported; + u16 reserved3[5]; + u16 queue_depth; + + u16 serial_ata_capabilities; + u16 serial_ata_additional_capabilities; + u16 serial_ata_features_supported; + u16 serial_ata_features_enabled; + u16 major_version_number; + u16 minor_version_number; + u16 commands_and_feature_sets_supported[3]; + u16 commands_and_feature_sets_supported_or_enabled[3]; + u16 ultra_dma_modes; + + u16 timing_for_security_features[2]; + u16 apm_level; + u16 master_password_id; + + u16 hardware_reset_results; + u16 obsolete8; + + u16 stream_minimum_request_time; + u16 streaming_transfer_time_for_dma; + u16 streaming_access_latency; + u16 streaming_performance_granularity[2]; + + u64 user_addressable_logical_sectors_count; + + u16 streaming_transfer_time_for_pio; + u16 max_512_byte_blocks_per_data_set_management_command; + u16 physical_sector_size_to_logical_sector_size; + u16 inter_seek_delay_for_acoustic_testing; + u16 world_wide_name[4]; + u16 reserved4[4]; + u16 obsolete9; + + u32 logical_sector_size; + + u16 commands_and_feature_sets_supported2; + u16 commands_and_feature_sets_supported_or_enabled2; + + u16 reserved_for_expanded_supported_and_enabled_settings[6]; + u16 obsolete10; + + u16 security_status; + u16 vendor_specific[31]; + u16 reserved_for_cfa2[8]; + u16 device_nominal_form_factor; + u16 data_set_management_command_support; + u16 additional_product_id[4]; + u16 reserved5[2]; + u16 current_media_serial_number[30]; + u16 sct_command_transport; + u16 reserved6[2]; + + u16 logical_sectors_alignment_within_physical_sector; + + u32 write_read_verify_sector_mode_3_count; + u32 write_read_verify_sector_mode_2_count; + + u16 obsolete11[3]; + u16 nominal_media_rotation_rate; + u16 reserved7; + u16 obsolete12; + u16 write_read_verify_feature_set_current_mode; + u16 reserved8; + u16 transport_major_version_number; + u16 transport_minor_version_number; + u16 reserved9[6]; + + u64 extended_user_addressable_logical_sectors_count; + + u16 minimum_512_byte_data_blocks_per_download_microcode_operation; + u16 max_512_byte_data_blocks_per_download_microcode_operation; + + u16 reserved10[19]; + u16 integrity; +}; +} + +namespace Kernel::FIS { + +enum class Type : u8 { + RegisterHostToDevice = 0x27, + RegisterDeviceToHost = 0x34, + DMAActivate = 0x39, + DMASetup = 0x41, + Data = 0x46, + BISTActivate = 0x58, + PIOSetup = 0x5F, + SetDeviceBits = 0xA1 +}; + +enum class DwordCount : size_t { + RegisterHostToDevice = 5, + RegisterDeviceToHost = 5, + DMAActivate = 1, + DMASetup = 7, + PIOSetup = 5, + SetDeviceBits = 2 +}; + +enum HeaderAttributes : u8 { + C = (1 << 7), /* Updates Command register */ +}; + +struct [[gnu::packed]] Header { + u8 fis_type; + u8 port_muliplier; +}; + +} + +namespace Kernel::FIS::HostToDevice { + +struct [[gnu::packed]] Register { + Header header; + u8 command; + u8 features_low; + u8 lba_low[3]; + u8 device; + u8 lba_high[3]; + u8 features_high; + u16 count; + u8 icc; /* Isochronous Command Completion */ + u8 control; + u32 reserved; +}; + +}; + +namespace Kernel::FIS::DeviceToHost { + +struct [[gnu::packed]] Register { + Header header; + u8 status; + u8 error; + u8 lba_low[3]; + u8 device; + u8 lba_high[3]; + u8 reserved; + u16 count; + u8 reserved2[6]; +}; + +struct [[gnu::packed]] SetDeviceBits { + Header header; + u8 status; + u8 error; + u32 protocol_specific; +}; + +struct [[gnu::packed]] DMAActivate { + Header header; + u16 reserved; +}; + +struct [[gnu::packed]] PIOSetup { + Header header; + u8 status; + u8 error; + u8 lba_low[3]; + u8 device; + u8 lba_high[3]; + u8 reserved; + u16 count; + u8 reserved2; + u8 e_status; + u16 transfer_count; + u16 reserved3; +}; + +} + +namespace Kernel::FIS::BiDirectional { + +struct [[gnu::packed]] Data { + Header header; + u16 reserved; + u32 data[]; +}; + +struct [[gnu::packed]] BISTActivate { +}; +struct [[gnu::packed]] DMASetup { + Header header; + u16 reserved; + u32 dma_buffer_identifier_low; + u32 dma_buffer_identifier_high; + u32 reserved2; + u32 dma_buffer_offset; + u32 dma_transfer_count; + u32 reserved3; +}; + +} + +namespace Kernel::AHCI { + +class MaskedBitField { + +public: + explicit MaskedBitField(volatile u32& bitfield_register) + : m_bitfield(bitfield_register) + , m_bit_mask(0xffffffff) + { + } + + MaskedBitField(volatile u32& bitfield_register, u32 bit_mask) + : m_bitfield(bitfield_register) + , m_bit_mask(bit_mask) + { + } + + void set_at(u8 index) const + { + VERIFY(((1 << index) & m_bit_mask) != 0); + m_bitfield = m_bitfield | ((1 << index) & m_bit_mask); + } + + void set_all() const + { + m_bitfield = m_bitfield | (0xffffffff & m_bit_mask); + } + + bool is_set_at(u32 port_index) const + { + return m_bitfield & ((1 << port_index) & m_bit_mask); + } + + Vector<u8> to_vector() const + { + // FIXME: Add a sync mechanism! + Vector<u8> indexes; + u32 bitfield = m_bitfield & m_bit_mask; + for (size_t index = 0; index < 32; index++) { + if (bitfield & 1) { + indexes.append(index); + } + bitfield >>= 1; + } + return indexes; + } + + u32 bit_mask() const { return m_bit_mask; }; + + // Disable default implementations that would use surprising integer promotion. + bool operator==(const MaskedBitField&) const = delete; + bool operator<=(const MaskedBitField&) const = delete; + bool operator>=(const MaskedBitField&) const = delete; + bool operator<(const MaskedBitField&) const = delete; + bool operator>(const MaskedBitField&) const = delete; + +private: + volatile u32& m_bitfield; + const u32 m_bit_mask; +}; + +enum Limits : u16 { + MaxPorts = 32, + MaxCommands = 32, + MaxMultiplierConnectedPorts = 16, +}; + +enum CommandHeaderAttributes : u16 { + C = (1 << 10), /* Clear Busy upon R_OK */ + P = (1 << 7), /* Prefetchable */ + W = (1 << 6), /* Write */ + A = (1 << 5), /* ATAPI */ + R = (1 << 8) /* Reset */ +}; + +enum HBACapabilites : u32 { + S64A = (u32)1 << 31, /* Supports 64-bit Addressing */ + SNCQ = 1 << 30, /* Supports Native Command Queuing */ + SSNTF = 1 << 29, /* Supports SNotification Register */ + SMPS = 1 << 28, /* Supports Mechanical Presence Switch */ + SSS = 1 << 27, /* Supports Staggered Spin-up */ + SALP = 1 << 26, /* Supports Aggressive Link Power Management */ + SAL = 1 << 25, /* Supports Activity LED */ + SCLO = 1 << 24, /* Supports Command List Override */ + SAM = 1 << 18, /* Supports AHCI mode only */ + SPM = 1 << 17, /* Supports Port Multiplier */ + FBSS = 1 << 16, /* FIS-based Switching Supported */ + PMD = 1 << 15, /* PIO Multiple DRQ Block */ + SSC = 1 << 14, /* Slumber State Capable */ + PSC = 1 << 13, /* Partial State Capable */ + CCCS = 1 << 7, /* Command Completion Coalescing Supported */ + EMS = 1 << 6, /* Enclosure Management Supported */ + SXS = 1 << 5 /* Supports External SATA */ +}; + +// This structure is not defined by the AHCI spec, but is used within the code +struct [[gnu::packed]] HBADefinedCapabilities { + size_t ports_count { 1 }; + size_t max_command_list_entries_count { 1 }; + u8 interface_speed_generation { 1 }; + bool external_sata_supported : 1 { false }; + bool enclosure_management_supported : 1 { false }; + bool command_completion_coalescing_supported : 1 { false }; + bool partial_state_capable : 1 { false }; + bool slumber_state_capable : 1 { false }; + bool pio_multiple_drq_block : 1 { false }; + bool fis_based_switching_supported : 1 { false }; + bool port_multilier_supported : 1 { false }; + bool ahci_mode_only : 1 { true }; + bool command_list_override_supported : 1 { false }; + bool activity_led_supported : 1 { false }; + bool aggerssive_link_power_management_supported : 1 { false }; + bool staggered_spin_up_supported : 1 { false }; + bool mechanical_presence_switch_supported : 1 { false }; + bool snotification_register_supported : 1 { false }; + bool native_command_queuing_supported : 1 { false }; + bool addressing_64_bit_supported : 1 { false }; +}; + +enum DeviceSignature : u32 { + ATA = 0x00000101, + ATAPI = 0xEB140101, + EnclosureManagementBridge = 0xC33C0101, + PortMultiplier = 0x96690101, + Unconnected = 0xFFFFFFFF +}; + +enum class DeviceDetectionInitialization { + NoActionRequested, + PerformInterfaceInitializationSequence, + DisableInterface +}; + +enum PortInterruptFlag : u32 { + CPD = (u32)1 << 31, /* Cold Port Detect */ + TFE = 1 << 30, /* Task File Error */ + HBF = 1 << 29, /* Host Bus Fatal Error */ + HBD = 1 << 28, /* Host Bus Data Error */ + IF = 1 << 27, /* Interface Fatal Error */ + INF = 1 << 26, /* Interface Non-fatal Error */ + OF = 1 << 24, /* Overflow */ + IPM = 1 << 23, /* Incorrect Port Multiplier */ + PRC = 1 << 22, /* PhyRdy Change */ + DMP = 1 << 7, /* Device Mechanical Presence */ + PC = 1 << 6, /* Port Connect Change */ + DP = 1 << 5, /* Descriptor Processed */ + UF = 1 << 4, /* Unknown FIS */ + SDB = 1 << 3, /* Set Device FIS */ + DS = 1 << 2, /* DMA Setup FIS */ + PS = 1 << 1, /* PIO Setup FIS */ + DHR = 1 << 0 /* Device to Host Register FIS */ +}; + +class PortInterruptStatusBitField { + +public: + explicit PortInterruptStatusBitField(volatile u32& bitfield_register) + : m_bitfield(bitfield_register) + { + } + + u32 raw_value() const { return m_bitfield; } + bool is_set(PortInterruptFlag flag) const { return m_bitfield & (u32)flag; } + void clear() { m_bitfield = 0xffffffff; } + + // Disable default implementations that would use surprising integer promotion. + bool operator==(const MaskedBitField&) const = delete; + bool operator<=(const MaskedBitField&) const = delete; + bool operator>=(const MaskedBitField&) const = delete; + bool operator<(const MaskedBitField&) const = delete; + bool operator>(const MaskedBitField&) const = delete; + +private: + volatile u32& m_bitfield; +}; + +class PortInterruptEnableBitField { + +public: + explicit PortInterruptEnableBitField(volatile u32& bitfield_register) + : m_bitfield(bitfield_register) + { + } + + bool is_set(PortInterruptFlag flag) { return m_bitfield & (u32)flag; } + void set_at(PortInterruptFlag flag) { m_bitfield = m_bitfield | (1 << (u32)flag); } + void clear() { m_bitfield = 0; } + bool is_cleared() const { return m_bitfield == 0; } + void set_all() { m_bitfield = 0xffffffff; } + + // Disable default implementations that would use surprising integer promotion. + bool operator==(const MaskedBitField&) const = delete; + bool operator<=(const MaskedBitField&) const = delete; + bool operator>=(const MaskedBitField&) const = delete; + bool operator<(const MaskedBitField&) const = delete; + bool operator>(const MaskedBitField&) const = delete; + +private: + volatile u32& m_bitfield; +}; + +struct [[gnu::packed]] PortRegisters { + u32 clb; /* Port x Command List Base Address */ + u32 clbu; /* Port x Command List Base Address Upper 32-Bits */ + u32 fb; /* Port x FIS Base Address */ + u32 fbu; /* Port x FIS Base Address Upper 32-Bits */ + u32 is; /* Port x Interrupt Status */ + u32 ie; /* Port x Interrupt Enable */ + u32 cmd; /* Port x Command and Status */ + u32 reserved; + u32 tfd; /* Port x Task File Data */ + u32 sig; /* Port x Signature */ + u32 ssts; /* Port x Serial ATA Status (SCR0: SStatus) */ + u32 sctl; /* Port x Serial ATA Control (SCR2: SControl) */ + u32 serr; /* Port x Serial ATA Error (SCR1: SError) */ + u32 sact; /* Port x Serial ATA Active (SCR3: SActive) */ + u32 ci; /* Port x Command Issue */ + u32 sntf; /* Port x Serial ATA Notification (SCR4: SNotification) */ + u32 fbs; /* Port x FIS-based Switching Control */ + u32 devslp; /* Port x Device Sleep */ + u8 reserved2[0x70 - 0x48]; + u8 vs[16]; /* Port x Vendor Specific */ +}; + +struct [[gnu::packed]] GenericHostControl { + u32 cap; /* Host Capabilities */ + u32 ghc; /* Global Host Control */ + u32 is; /* Interrupt Status */ + u32 pi; /* Ports Implemented */ + u32 version; + u32 ccc_ctl; /* Command Completion Coalescing Control */ + u32 ccc_ports; /* Command Completion Coalsecing Ports */ + u32 em_loc; /* Enclosure Management Location */ + u32 em_ctl; /* Enclosure Management Control */ + u32 cap2; /* Host Capabilities Extended */ + u32 bohc; /* BIOS/OS Handoff Control and Status */ +}; + +struct [[gnu::packed]] HBA { + GenericHostControl control_regs; + u8 reserved[52]; + u8 nvmhci[64]; + u8 vendor_specific[96]; + PortRegisters port_regs[32]; +}; + +struct [[gnu::packed]] CommandHeader { + u16 attributes; + u16 prdtl; /* Physical Region Descriptor Table Length */ + u32 prdbc; /* Physical Region Descriptor Byte Count */ + u32 ctba; /* Command Table Descriptor Base Address */ + u32 ctbau; /* Command Table Descriptor Base Address Upper 32-bits */ + u32 reserved[4]; +}; + +struct [[gnu::packed]] PhysicalRegionDescriptor { + u32 base_low; + u32 base_high; + u32 reserved; + u32 byte_count; /* Bit 31 - Interrupt completion, Bit 0 to 21 - Data Byte Count */ +}; + +struct [[gnu::packed]] CommandTable { + u8 command_fis[64]; + u8 atapi_command[32]; + u8 reserved[32]; + PhysicalRegionDescriptor descriptors[]; +}; +} diff --git a/Kernel/Storage/AHCIController.cpp b/Kernel/Storage/AHCIController.cpp new file mode 100644 index 0000000000..1068ea104a --- /dev/null +++ b/Kernel/Storage/AHCIController.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Atomic.h> +#include <AK/OwnPtr.h> +#include <AK/RefPtr.h> +#include <AK/Types.h> +#include <Kernel/Storage/AHCIController.h> +#include <Kernel/Storage/SATADiskDevice.h> +#include <Kernel/VM/MemoryManager.h> + +namespace Kernel { + +NonnullRefPtr<AHCIController> AHCIController::initialize(PCI::Address address) +{ + return adopt(*new AHCIController(address)); +} + +bool AHCIController::reset() +{ + hba().control_regs.ghc = 1; + + full_memory_barrier(); + size_t retry = 0; + + while (true) { + if (retry > 1000) + return false; + if (!(hba().control_regs.ghc & 1)) + break; + IO::delay(1000); + retry++; + } + // The HBA is locked or hung if we waited more than 1 second! + return true; +} + +bool AHCIController::shutdown() +{ + TODO(); +} + +size_t AHCIController::devices_count() const +{ + size_t count = 0; + for (auto& port_handler : m_handlers) { + port_handler.enumerate_ports([&](const AHCIPort& port) { + if (port.connected_device()) + count++; + }); + } + return count; +} + +void AHCIController::start_request(const StorageDevice&, AsyncBlockDeviceRequest&) +{ + VERIFY_NOT_REACHED(); +} + +void AHCIController::complete_current_request(AsyncDeviceRequest::RequestResult) +{ + VERIFY_NOT_REACHED(); +} + +volatile AHCI::PortRegisters& AHCIController::port(size_t port_number) const +{ + VERIFY(port_number < (size_t)AHCI::Limits::MaxPorts); + return static_cast<volatile AHCI::PortRegisters&>(hba().port_regs[port_number]); +} + +volatile AHCI::HBA& AHCIController::hba() const +{ + return static_cast<volatile AHCI::HBA&>(*(volatile AHCI::HBA*)(m_hba_region->vaddr().as_ptr())); +} + +AHCIController::AHCIController(PCI::Address address) + : StorageController() + , PCI::DeviceController(address) + , m_hba_region(hba_region()) + , m_capabilities(capabilities()) +{ + initialize(); +} + +AHCI::HBADefinedCapabilities AHCIController::capabilities() const +{ + u32 capabilities = hba().control_regs.cap; + return (AHCI::HBADefinedCapabilities) { + (capabilities & 0b11111) + 1, + ((capabilities >> 8) & 0b11111) + 1, + (u8)((capabilities >> 20) & 0b1111), + (capabilities & (u32)(AHCI::HBACapabilites::SXS)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::EMS)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::CCCS)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::PSC)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SSC)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::PMD)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::FBSS)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SPM)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SAM)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SCLO)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SAL)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SALP)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SSS)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SMPS)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SSNTF)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::SNCQ)) != 0, + (capabilities & (u32)(AHCI::HBACapabilites::S64A)) != 0 + }; +} + +NonnullOwnPtr<Region> AHCIController::hba_region() const +{ + auto region = MM.allocate_kernel_region(PhysicalAddress(PCI::get_BAR5(pci_address())).page_base(), page_round_up(sizeof(AHCI::HBA)), "AHCI HBA", Region::Access::Read | Region::Access::Write); + return region.release_nonnull(); +} + +AHCIController::~AHCIController() +{ +} + +void AHCIController::initialize() +{ + if (!reset()) { + dmesgln("{}: AHCI controller reset failed", pci_address()); + return; + } + dmesgln("{}: AHCI controller reset", pci_address()); + dbgln("{}: AHCI command list entries count - {}", pci_address(), hba_capabilities().max_command_list_entries_count); + hba().control_regs.ghc = 0x80000000; // Ensure that HBA knows we are AHCI aware. + PCI::enable_interrupt_line(pci_address()); + PCI::enable_bus_mastering(pci_address()); + enable_global_interrupts(); + m_handlers.append(AHCIPortHandler::create(*this, PCI::get_interrupt_line(pci_address()), + AHCI::MaskedBitField((volatile u32&)(hba().control_regs.pi)))); +} + +void AHCIController::disable_global_interrupts() const +{ + hba().control_regs.ghc = hba().control_regs.ghc & 0xfffffffd; +} +void AHCIController::enable_global_interrupts() const +{ + hba().control_regs.ghc = hba().control_regs.ghc | (1 << 1); +} + +RefPtr<StorageDevice> AHCIController::device_by_port(u32 port_index) const +{ + for (auto& port_handler : m_handlers) { + if (!port_handler.is_responsible_for_port_index(port_index)) + continue; + + auto port = port_handler.port_at_index(port_index); + if (!port) + return nullptr; + return port->connected_device(); + } + return nullptr; +} + +RefPtr<StorageDevice> AHCIController::device(u32 index) const +{ + NonnullRefPtrVector<StorageDevice> connected_devices; + for (size_t index = 0; index < (size_t)(hba().control_regs.cap & 0x1F); index++) { + auto checked_device = device_by_port(index); + if (checked_device.is_null()) + continue; + connected_devices.append(checked_device.release_nonnull()); + } + if (index >= connected_devices.size()) + return nullptr; + return connected_devices[index]; +} + +} diff --git a/Kernel/Storage/AHCIController.h b/Kernel/Storage/AHCIController.h new file mode 100644 index 0000000000..73714c0e47 --- /dev/null +++ b/Kernel/Storage/AHCIController.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/OwnPtr.h> +#include <AK/RefPtr.h> +#include <AK/Types.h> +#include <Kernel/Storage/AHCI.h> +#include <Kernel/Storage/StorageController.h> +#include <Kernel/Storage/StorageDevice.h> + +namespace Kernel { + +class AsyncBlockDeviceRequest; +class AHCIPortHandler; +class AHCIController final : public StorageController + , public PCI::DeviceController { + friend class AHCIPortHandler; + AK_MAKE_ETERNAL +public: +public: + UNMAP_AFTER_INIT static NonnullRefPtr<AHCIController> initialize(PCI::Address address); + virtual ~AHCIController() override; + + virtual Type type() const override { return Type::AHCI; } + virtual RefPtr<StorageDevice> device(u32 index) const override; + virtual bool reset() override; + virtual bool shutdown() override; + virtual size_t devices_count() const override; + virtual void start_request(const StorageDevice&, AsyncBlockDeviceRequest&) override; + virtual void complete_current_request(AsyncDeviceRequest::RequestResult) override; + + const AHCI::HBADefinedCapabilities& hba_capabilities() const { return m_capabilities; }; + +private: + void disable_global_interrupts() const; + void enable_global_interrupts() const; + + UNMAP_AFTER_INIT explicit AHCIController(PCI::Address address); + UNMAP_AFTER_INIT void initialize(); + + AHCI::HBADefinedCapabilities capabilities() const; + RefPtr<StorageDevice> device_by_port(u32 index) const; + + volatile AHCI::PortRegisters& port(size_t port_number) const; + NonnullOwnPtr<Region> hba_region() const; + volatile AHCI::HBA& hba() const; + + NonnullOwnPtr<Region> m_hba_region; + AHCI::HBADefinedCapabilities m_capabilities; + NonnullRefPtrVector<AHCIPortHandler> m_handlers; +}; +} diff --git a/Kernel/Storage/AHCIPort.cpp b/Kernel/Storage/AHCIPort.cpp new file mode 100644 index 0000000000..8c07d11c50 --- /dev/null +++ b/Kernel/Storage/AHCIPort.cpp @@ -0,0 +1,622 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Atomic.h> +#include <Kernel/SpinLock.h> +#include <Kernel/Storage/AHCIPort.h> +#include <Kernel/Storage/ATA.h> +#include <Kernel/Storage/SATADiskDevice.h> +#include <Kernel/VM/MemoryManager.h> +#include <Kernel/VM/TypedMapping.h> + +namespace Kernel { + +NonnullRefPtr<AHCIPort> AHCIPort::create(const AHCIPortHandler& handler, volatile AHCI::PortRegisters& registers, u32 port_index) +{ + return adopt(*new AHCIPort(handler, registers, port_index)); +} + +AHCIPort::AHCIPort(const AHCIPortHandler& handler, volatile AHCI::PortRegisters& registers, u32 port_index) + : m_port_index(port_index) + , m_port_registers(registers) + , m_parent_handler(handler) + , m_interrupt_status((volatile u32&)m_port_registers.is) + , m_interrupt_enable((volatile u32&)m_port_registers.ie) +{ + if (is_interface_disabled()) { + m_disabled_by_firmware = true; + return; + } + + m_command_list_page = MM.allocate_supervisor_physical_page(); + m_fis_receive_page = MM.allocate_supervisor_physical_page(); + if (m_command_list_page.is_null() || m_fis_receive_page.is_null()) + return; + for (size_t index = 0; index < 1; index++) { + m_dma_buffers.append(MM.allocate_supervisor_physical_page().release_nonnull()); + } + for (size_t index = 0; index < 1; index++) { + m_command_table_pages.append(MM.allocate_supervisor_physical_page().release_nonnull()); + } + m_command_list_region = MM.allocate_kernel_region(m_command_list_page->paddr(), PAGE_SIZE, "AHCI Port Command List", Region::Access::Read | Region::Access::Write, Region::Cacheable::No); + m_interrupt_enable.set_all(); +} + +void AHCIPort::clear_sata_error_register() const +{ + m_port_registers.serr = m_port_registers.serr; +} + +void AHCIPort::handle_interrupt() +{ + dbgln_if(AHCI_DEBUG, "AHCI Port {}: Interrupt handled, PxIS {}", representative_port_index(), m_interrupt_status.raw_value()); + if (m_interrupt_status.raw_value() == 0) { + return; + } + if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::PRC) || m_interrupt_status.is_set(AHCI::PortInterruptFlag::PC)) { + reset(); + return; + } + if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::INF)) { + reset(); + return; + } + if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::IF)) { + recover_from_fatal_error(); + } + m_interrupt_status.clear(); +} + +bool AHCIPort::is_interrupts_enabled() const +{ + return !m_interrupt_enable.is_cleared(); +} + +void AHCIPort::recover_from_fatal_error() +{ + ScopedSpinLock lock(m_lock); + dmesgln("{}: AHCI Port {} fatal error, shutting down!", m_parent_handler->hba_controller()->pci_address(), representative_port_index()); + stop_command_list_processing(); + stop_fis_receiving(); + m_interrupt_enable.clear(); +} + +void AHCIPort::eject() +{ + // FIXME: This operation (meant to be used on optical drives) doesn't work yet when I tested it on real hardware + TODO(); + + VERIFY(m_lock.is_locked()); + VERIFY(is_atapi_attached()); + VERIFY(is_operable()); + clear_sata_error_register(); + + if (!spin_until_ready()) + return; + + auto unused_command_header = try_to_find_unused_command_header(); + VERIFY(unused_command_header.has_value()); + auto* command_list_entries = (volatile AHCI::CommandHeader*)m_command_list_region->vaddr().as_ptr(); + command_list_entries[unused_command_header.value()].ctba = m_command_table_pages[unused_command_header.value()].paddr().get(); + command_list_entries[unused_command_header.value()].ctbau = 0; + command_list_entries[unused_command_header.value()].prdbc = 0; + command_list_entries[unused_command_header.value()].prdtl = 0; + + // Note: we must set the correct Dword count in this register. Real hardware + // AHCI controllers do care about this field! QEMU doesn't care if we don't + // set the correct CFL field in this register, real hardware will set an + // handshake error bit in PxSERR register if CFL is incorrect. + command_list_entries[unused_command_header.value()].attributes = (size_t)FIS::DwordCount::RegisterHostToDevice | AHCI::CommandHeaderAttributes::P | AHCI::CommandHeaderAttributes::C | AHCI::CommandHeaderAttributes::A; + + auto command_table_region = MM.allocate_kernel_region(m_command_table_pages[unused_command_header.value()].paddr().page_base(), page_round_up(sizeof(AHCI::CommandTable)), "AHCI Command Table", Region::Access::Read | Region::Access::Write, Region::Cacheable::No); + auto& command_table = *(volatile AHCI::CommandTable*)command_table_region->vaddr().as_ptr(); + memset(const_cast<u8*>(command_table.command_fis), 0, 64); + auto& fis = *(volatile FIS::HostToDevice::Register*)command_table.command_fis; + fis.header.fis_type = (u8)FIS::Type::RegisterHostToDevice; + fis.command = ATA_CMD_PACKET; + + full_memory_barrier(); + memset(const_cast<u8*>(command_table.atapi_command), 0, 32); + + full_memory_barrier(); + command_table.atapi_command[0] = ATAPI_CMD_EJECT; + command_table.atapi_command[1] = 0; + command_table.atapi_command[2] = 0; + command_table.atapi_command[3] = 0; + command_table.atapi_command[4] = 0b10; + command_table.atapi_command[5] = 0; + command_table.atapi_command[6] = 0; + command_table.atapi_command[7] = 0; + command_table.atapi_command[8] = 0; + command_table.atapi_command[9] = 0; + command_table.atapi_command[10] = 0; + command_table.atapi_command[11] = 0; + fis.device = 0; + fis.header.port_muliplier = fis.header.port_muliplier | (u8)FIS::HeaderAttributes::C; + + // The below loop waits until the port is no longer busy before issuing a new command + if (!spin_until_ready()) + return; + + full_memory_barrier(); + mark_command_header_ready_to_process(unused_command_header.value()); + + full_memory_barrier(); + + while (1) { + if (m_port_registers.serr != 0) { + dbgln_if(AHCI_DEBUG, "AHCI Port {}: Eject Drive failed, SError {}", representative_port_index(), (u32)m_port_registers.serr); + VERIFY_NOT_REACHED(); + } + if ((m_port_registers.ci & (1 << unused_command_header.value())) == 0) + break; + } + dbgln("AHCI Port {}: Eject Drive", representative_port_index()); + return; +} + +bool AHCIPort::reset() +{ + ScopedSpinLock lock(m_lock); + + if (m_disabled_by_firmware) { + dmesgln("AHCI Port {}: Disabled by firmware ", representative_port_index()); + return false; + } + full_memory_barrier(); + m_interrupt_enable.clear(); + m_interrupt_status.clear(); + full_memory_barrier(); + start_fis_receiving(); + full_memory_barrier(); + clear_sata_error_register(); + full_memory_barrier(); + if (!initiate_sata_reset()) { + return false; + } + rebase(); + power_on(); + spin_up(); + clear_sata_error_register(); + start_fis_receiving(); + set_active_state(); + m_interrupt_status.clear(); + m_interrupt_enable.set_all(); + + full_memory_barrier(); + // This actually enables the port... + start_command_list_processing(); + full_memory_barrier(); + + size_t logical_sector_size = 512; + size_t physical_sector_size = 512; + size_t max_addressable_sector = 0; + if (identify_device()) { + auto identify_block = map_typed<ATAIdentifyBlock>(m_parent_handler->get_identify_metadata_physical_region(m_port_index)); + // Check if word 106 is valid before using it! + if ((identify_block->physical_sector_size_to_logical_sector_size >> 14) == 1) { + if (identify_block->physical_sector_size_to_logical_sector_size & (1 << 12)) { + VERIFY(identify_block->logical_sector_size != 0); + logical_sector_size = identify_block->logical_sector_size; + } + if (identify_block->physical_sector_size_to_logical_sector_size & (1 << 13)) { + physical_sector_size = logical_sector_size << (identify_block->physical_sector_size_to_logical_sector_size & 0xf); + } + } + // Check if the device supports LBA48 mode + if (identify_block->commands_and_feature_sets_supported[1] & (1 << 10)) { + max_addressable_sector = identify_block->user_addressable_logical_sectors_count; + } else { + max_addressable_sector = identify_block->max_28_bit_addressable_logical_sector; + } + if (is_atapi_attached()) { + m_port_registers.cmd = m_port_registers.cmd | (1 << 24); + } + + dmesgln("AHCI Port {}: max lba {:x}, L/P sector size - {}/{} ", representative_port_index(), max_addressable_sector, logical_sector_size, physical_sector_size); + + // FIXME: We don't support ATAPI devices yet, so for now we don't "create" them + if (!is_atapi_attached()) { + m_connected_device = SATADiskDevice::create(m_parent_handler->hba_controller(), *this, logical_sector_size, max_addressable_sector); + } + } + return true; +} + +const char* AHCIPort::try_disambiguate_sata_status() +{ + switch (m_port_registers.ssts & 0xf) { + case 0: + return "Device not detected, Phy not enabled"; + case 1: + return "Device detected, Phy disabled"; + case 3: + return "Device detected, Phy enabled"; + case 4: + return "interface disabled"; + } + VERIFY_NOT_REACHED(); +} + +void AHCIPort::rebase() +{ + VERIFY(m_lock.is_locked()); + VERIFY(!m_command_list_page.is_null() && !m_fis_receive_page.is_null()); + full_memory_barrier(); + stop_command_list_processing(); + stop_fis_receiving(); + full_memory_barrier(); + size_t retry = 0; + // Try to wait 1 second for HBA to clear Command List Running and FIS Receive Running + while (retry < 1000) { + if (!(m_port_registers.cmd & (1 << 15)) && !(m_port_registers.cmd & (1 << 14))) + break; + IO::delay(1000); + retry++; + } + full_memory_barrier(); + m_port_registers.clbu = 0; + m_port_registers.clb = m_command_list_page->paddr().get(); + m_port_registers.fbu = 0; + m_port_registers.fb = m_fis_receive_page->paddr().get(); +} + +bool AHCIPort::is_operable() const +{ + // Note: The definition of "operable" is somewhat ambiguous, but we determine it + // by 3 parameters as shown below. + return (!m_command_list_page.is_null()) + && (!m_fis_receive_page.is_null()) + && ((m_port_registers.cmd & (1 << 14)) != 0); +} + +void AHCIPort::set_active_state() const +{ + VERIFY(m_lock.is_locked()); + m_port_registers.cmd = (m_port_registers.cmd & 0x0ffffff) | (1 << 28); +} + +void AHCIPort::set_sleep_state() const +{ + VERIFY(m_lock.is_locked()); + m_port_registers.cmd = (m_port_registers.cmd & 0x0ffffff) | (0b1000 << 28); +} + +void AHCIPort::start_request(AsyncBlockDeviceRequest& request) +{ + ScopedSpinLock lock(m_lock); + auto dma_region = MM.allocate_kernel_region(m_dma_buffers[0].paddr(), PAGE_SIZE, "AHCI Mapped DMA", Region::Access::Read | Region::Access::Write); + + // FIXME: Allow more than 8 blocks of 512 bytes to be processed! + VERIFY(request.block_count() < 9); + + if (request.request_type() == AsyncBlockDeviceRequest::Write) { + if (!request.read_from_buffer(request.buffer(), dma_region->vaddr().as_ptr(), 512 * request.block_count())) { + request.complete(AsyncDeviceRequest::MemoryFault); + return; + } + } + + auto success = access_device(request.request_type(), request.block_index(), request.block_count()); + if (!success) { + request.complete(AsyncDeviceRequest::Failure); + return; + } + if (request.request_type() == AsyncBlockDeviceRequest::Read) { + if (!request.write_to_buffer(request.buffer(), dma_region->vaddr().as_ptr(), 512 * request.block_count())) { + request.complete(AsyncDeviceRequest::MemoryFault); + return; + } + } + dbgln_if(AHCI_DEBUG, "AHCI Port {}: Reqeust success", representative_port_index()); + request.complete(AsyncDeviceRequest::Success); +} + +void AHCIPort::complete_current_request(AsyncDeviceRequest::RequestResult) +{ + VERIFY(m_lock.is_locked()); +} + +bool AHCIPort::spin_until_ready() const +{ + VERIFY(m_lock.is_locked()); + size_t spin = 0; + while ((m_port_registers.tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && spin <= 100) { + IO::delay(1000); + spin++; + } + if (spin == 100) { + dbgln_if(AHCI_DEBUG, "AHCI Port {}: SPIN exceeded 100 miliseconds threshold", representative_port_index()); + return false; + } + return true; +} + +bool AHCIPort::access_device(AsyncBlockDeviceRequest::RequestType direction, u64 lba, u8 block_count) +{ + VERIFY(m_lock.is_locked()); + VERIFY(is_operable()); + + dbgln_if(AHCI_DEBUG, "AHCI Port {}: Do a {}, lba {}, block count {}", representative_port_index(), direction == AsyncBlockDeviceRequest::RequestType::Write ? "write" : "read", lba, block_count); + + if (!spin_until_ready()) + return false; + + auto unused_command_header = try_to_find_unused_command_header(); + VERIFY(unused_command_header.has_value()); + auto* command_list_entries = (volatile AHCI::CommandHeader*)m_command_list_region->vaddr().as_ptr(); + command_list_entries[unused_command_header.value()].ctba = m_command_table_pages[unused_command_header.value()].paddr().get(); + command_list_entries[unused_command_header.value()].ctbau = 0; + command_list_entries[unused_command_header.value()].prdbc = (block_count * 512); + command_list_entries[unused_command_header.value()].prdtl = 1; + + // Note: we must set the correct Dword count in this register. Real hardware + // AHCI controllers do care about this field! QEMU doesn't care if we don't + // set the correct CFL field in this register, real hardware will set an + // handshake error bit in PxSERR register if CFL is incorrect. + command_list_entries[unused_command_header.value()].attributes = (size_t)FIS::DwordCount::RegisterHostToDevice | AHCI::CommandHeaderAttributes::P | AHCI::CommandHeaderAttributes::C | (is_atapi_attached() ? AHCI::CommandHeaderAttributes::A : 0) | (direction == AsyncBlockDeviceRequest::RequestType::Write ? AHCI::CommandHeaderAttributes::W : 0); + + auto command_table_region = MM.allocate_kernel_region(m_command_table_pages[unused_command_header.value()].paddr().page_base(), page_round_up(sizeof(AHCI::CommandTable)), "AHCI Command Table", Region::Access::Read | Region::Access::Write, Region::Cacheable::No); + auto& command_table = *(volatile AHCI::CommandTable*)command_table_region->vaddr().as_ptr(); + memset(const_cast<u8*>(command_table.command_fis), 0, 64); + command_table.descriptors[0].base_high = 0; + command_table.descriptors[0].base_low = m_dma_buffers[0].paddr().get(); + command_table.descriptors[0].byte_count = (block_count * 512) - 1; + memset(const_cast<u8*>(command_table.atapi_command), 0, 32); + + auto& fis = *(volatile FIS::HostToDevice::Register*)command_table.command_fis; + fis.header.fis_type = (u8)FIS::Type::RegisterHostToDevice; + if (is_atapi_attached()) { + fis.command = ATA_CMD_PACKET; + TODO(); + } else { + if (direction == AsyncBlockDeviceRequest::RequestType::Write) + fis.command = ATA_CMD_WRITE_DMA_EXT; + else + fis.command = ATA_CMD_READ_DMA_EXT; + } + + full_memory_barrier(); + + fis.device = ATA_USE_LBA_ADDRESSING; + fis.header.port_muliplier = (u8)FIS::HeaderAttributes::C; + + fis.lba_high[0] = (lba >> 24) & 0xff; + fis.lba_high[1] = (lba >> 32) & 0xff; + fis.lba_high[2] = (lba >> 40) & 0xff; + fis.lba_low[0] = lba & 0xff; + fis.lba_low[1] = (lba >> 8) & 0xff; + fis.lba_low[2] = (lba >> 16) & 0xff; + fis.count = (block_count); + + // The below loop waits until the port is no longer busy before issuing a new command + if (!spin_until_ready()) + return false; + + full_memory_barrier(); + start_command_list_processing(); + full_memory_barrier(); + mark_command_header_ready_to_process(unused_command_header.value()); + full_memory_barrier(); + + while (1) { + if ((m_port_registers.ci & (1 << unused_command_header.value())) == 0) + break; + } + dbgln_if(AHCI_DEBUG, "AHCI Port {}: Do a {}, lba {}, block count {} @ {}, ended", representative_port_index(), direction == AsyncBlockDeviceRequest::RequestType::Write ? "write" : "read", lba, block_count, m_dma_buffers[0].paddr()); + return true; +} + +bool AHCIPort::identify_device() +{ + VERIFY(m_lock.is_locked()); + VERIFY(is_operable()); + if (!spin_until_ready()) + return false; + + auto unused_command_header = try_to_find_unused_command_header(); + VERIFY(unused_command_header.has_value()); + auto* command_list_entries = (volatile AHCI::CommandHeader*)m_command_list_region->vaddr().as_ptr(); + command_list_entries[unused_command_header.value()].ctba = m_command_table_pages[unused_command_header.value()].paddr().get(); + command_list_entries[unused_command_header.value()].ctbau = 0; + command_list_entries[unused_command_header.value()].prdbc = 512; + command_list_entries[unused_command_header.value()].prdtl = 1; + + // Note: we must set the correct Dword count in this register. Real hardware AHCI controllers do care about this field! + // QEMU doesn't care if we don't set the correct CFL field in this register, real hardware will set an handshake error bit in PxSERR register. + command_list_entries[unused_command_header.value()].attributes = (size_t)FIS::DwordCount::RegisterHostToDevice | AHCI::CommandHeaderAttributes::P | AHCI::CommandHeaderAttributes::C; + + auto command_table_region = MM.allocate_kernel_region(m_command_table_pages[unused_command_header.value()].paddr().page_base(), page_round_up(sizeof(AHCI::CommandTable)), "AHCI Command Table", Region::Access::Read | Region::Access::Write); + auto& command_table = *(volatile AHCI::CommandTable*)command_table_region->vaddr().as_ptr(); + memset(const_cast<u8*>(command_table.command_fis), 0, 64); + command_table.descriptors[0].base_high = 0; + command_table.descriptors[0].base_low = m_parent_handler->get_identify_metadata_physical_region(m_port_index).get(); + command_table.descriptors[0].byte_count = 512 - 1; + auto& fis = *(volatile FIS::HostToDevice::Register*)command_table.command_fis; + fis.header.fis_type = (u8)FIS::Type::RegisterHostToDevice; + fis.command = m_port_registers.sig == AHCI::DeviceSignature::ATAPI ? ATA_CMD_IDENTIFY_PACKET : ATA_CMD_IDENTIFY; + fis.device = 0; + fis.header.port_muliplier = fis.header.port_muliplier | (u8)FIS::HeaderAttributes::C; + + // The below loop waits until the port is no longer busy before issuing a new command + if (!spin_until_ready()) + return false; + + full_memory_barrier(); + mark_command_header_ready_to_process(unused_command_header.value()); + full_memory_barrier(); + + while (1) { + if (m_port_registers.serr != 0) { + dbgln("AHCI Port {}: Identify failed, SError {}", representative_port_index(), (u32)m_port_registers.serr); + return false; + } + if ((m_port_registers.ci & (1 << unused_command_header.value())) == 0) + break; + } + return true; +} + +bool AHCIPort::shutdown() +{ + ScopedSpinLock lock(m_lock); + rebase(); + set_interface_state(AHCI::DeviceDetectionInitialization::DisableInterface); + return true; +} + +Optional<u8> AHCIPort::try_to_find_unused_command_header() +{ + VERIFY(m_lock.is_locked()); + u32 commands_issued = m_port_registers.ci; + for (size_t index = 0; index < 32; index++) { + if (!(commands_issued & 1)) { + dbgln_if(AHCI_DEBUG, "AHCI Port {}: unused command header at index {}", representative_port_index(), index); + return index; + } + commands_issued >>= 1; + } + return {}; +} + +void AHCIPort::start_command_list_processing() const +{ + VERIFY(m_lock.is_locked()); + VERIFY(is_operable()); + m_port_registers.cmd = m_port_registers.cmd | 1; +} + +void AHCIPort::mark_command_header_ready_to_process(u8 command_header_index) const +{ + VERIFY(m_lock.is_locked()); + VERIFY(is_operable()); + m_port_registers.ci = 1 << command_header_index; +} + +void AHCIPort::stop_command_list_processing() const +{ + VERIFY(m_lock.is_locked()); + m_port_registers.cmd = m_port_registers.cmd & 0xfffffffe; +} + +void AHCIPort::start_fis_receiving() const +{ + VERIFY(m_lock.is_locked()); + m_port_registers.cmd = m_port_registers.cmd | (1 << 4); +} + +void AHCIPort::power_on() const +{ + VERIFY(m_lock.is_locked()); + if (!(m_port_registers.cmd & (1 << 20))) + return; + m_port_registers.cmd = m_port_registers.cmd | (1 << 2); +} + +void AHCIPort::spin_up() const +{ + VERIFY(m_lock.is_locked()); + dbgln_if(AHCI_DEBUG, "AHCI Port {}, staggered spin up? {}", representative_port_index(), m_parent_handler->hba_capabilities().staggered_spin_up_supported); + if (!m_parent_handler->hba_capabilities().staggered_spin_up_supported) + return; + m_port_registers.cmd = m_port_registers.cmd | (1 << 1); +} + +void AHCIPort::stop_fis_receiving() const +{ + VERIFY(m_lock.is_locked()); + m_port_registers.cmd = m_port_registers.cmd & 0xFFFFFFEF; +} + +bool AHCIPort::initiate_sata_reset() +{ + VERIFY(m_lock.is_locked()); + stop_command_list_processing(); + full_memory_barrier(); + size_t retry = 0; + // Try to wait 1 second for HBA to clear Command List Running + while (retry < 500) { + if (!(m_port_registers.cmd & (1 << 15))) + break; + // The AHCI specification says to wait now a 500 milliseconds + IO::delay(1000); + retry++; + } + full_memory_barrier(); + spin_up(); + full_memory_barrier(); + set_interface_state(AHCI::DeviceDetectionInitialization::PerformInterfaceInitializationSequence); + // The AHCI specification says to wait now a 1 millisecond, we wait 2 ms + IO::delay(10000); + full_memory_barrier(); + set_interface_state(AHCI::DeviceDetectionInitialization::NoActionRequested); + full_memory_barrier(); + + retry = 0; + while (retry < 5) { + if (!((m_port_registers.ssts & 0xf) == 0)) + break; + + IO::delay(10000); + retry++; + } + + // If device presence detected and Phy communication established, wait for signature to update + if ((m_port_registers.ssts & 0xf) == 3) { + retry = 0; + while (retry < 30) { + if (!(m_port_registers.tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && m_port_registers.sig != 0xffffffff) + break; + IO::delay(10000); + retry++; + } + } + + dmesgln("AHCI Port {}: {}", representative_port_index(), try_disambiguate_sata_status()); + + full_memory_barrier(); + clear_sata_error_register(); + return m_port_registers.sig != AHCI::DeviceSignature::Unconnected; +} + +void AHCIPort::set_interface_state(AHCI::DeviceDetectionInitialization requested_action) +{ + VERIFY(m_lock.is_locked()); + switch (requested_action) { + case AHCI::DeviceDetectionInitialization::NoActionRequested: + m_port_registers.sctl = (m_port_registers.sctl & 0xfffffff0); + return; + case AHCI::DeviceDetectionInitialization::PerformInterfaceInitializationSequence: + m_port_registers.sctl = (m_port_registers.sctl & 0xfffffff0) | 1; + return; + case AHCI::DeviceDetectionInitialization::DisableInterface: + m_port_registers.sctl = (m_port_registers.sctl & 0xfffffff0) | 4; + return; + } + VERIFY_NOT_REACHED(); +} + +} diff --git a/Kernel/Storage/AHCIPort.h b/Kernel/Storage/AHCIPort.h new file mode 100644 index 0000000000..8dca678a3a --- /dev/null +++ b/Kernel/Storage/AHCIPort.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/OwnPtr.h> +#include <AK/RefPtr.h> +#include <Kernel/Devices/Device.h> +#include <Kernel/IO.h> +#include <Kernel/Interrupts/IRQHandler.h> +#include <Kernel/Lock.h> +#include <Kernel/PhysicalAddress.h> +#include <Kernel/Random.h> +#include <Kernel/Storage/AHCI.h> +#include <Kernel/Storage/AHCIPortHandler.h> +#include <Kernel/Storage/StorageDevice.h> +#include <Kernel/VM/PhysicalPage.h> +#include <Kernel/WaitQueue.h> + +namespace Kernel { + +class AsyncBlockDeviceRequest; + +class AHCIPortHandler; +class SATADiskDevice; +class AHCIPort : public RefCounted<AHCIPort> { + friend class AHCIPortHandler; + friend class SATADiskDevice; + +public: + UNMAP_AFTER_INIT static NonnullRefPtr<AHCIPort> create(const AHCIPortHandler&, volatile AHCI::PortRegisters&, u32 port_index); + + u32 port_index() const { return m_port_index; } + u32 representative_port_index() const { return port_index() + 1; } + bool is_operable() const; + bool is_hot_pluggable() const; + bool is_atapi_attached() const { return m_port_registers.sig == (u32)AHCI::DeviceSignature::ATAPI; }; + + RefPtr<StorageDevice> connected_device() const { return m_connected_device; } + + bool reset(); + void handle_interrupt(); + +private: + UNMAP_AFTER_INIT AHCIPort(const AHCIPortHandler&, volatile AHCI::PortRegisters&, u32 port_index); + + ALWAYS_INLINE void clear_sata_error_register() const; + + void eject(); + + const char* try_disambiguate_sata_status(); + + bool initiate_sata_reset(); + void rebase(); + void recover_from_fatal_error(); + bool shutdown(); + ALWAYS_INLINE void spin_up() const; + ALWAYS_INLINE void power_on() const; + + void start_request(AsyncBlockDeviceRequest&); + void complete_current_request(AsyncDeviceRequest::RequestResult); + bool access_device(AsyncBlockDeviceRequest::RequestType, u64 lba, u8 block_count); + + ALWAYS_INLINE bool is_interrupts_enabled() const; + + bool spin_until_ready() const; + + bool identify_device(); + + ALWAYS_INLINE void start_command_list_processing() const; + ALWAYS_INLINE void mark_command_header_ready_to_process(u8 command_header_index) const; + ALWAYS_INLINE void stop_command_list_processing() const; + + ALWAYS_INLINE void start_fis_receiving() const; + ALWAYS_INLINE void stop_fis_receiving() const; + + ALWAYS_INLINE void set_active_state() const; + ALWAYS_INLINE void set_sleep_state() const; + + void set_interface_state(AHCI::DeviceDetectionInitialization); + + Optional<u8> try_to_find_unused_command_header(); + + ALWAYS_INLINE bool is_interface_disabled() const { return (m_port_registers.ssts & 0xf) == 4; }; + + // Data members + + EntropySource m_entropy_source; + AsyncBlockDeviceRequest* m_current_request { nullptr }; + u32 m_current_request_block_index { 0 }; + bool m_current_request_uses_dma { false }; + bool m_current_request_flushing_cache { false }; + SpinLock<u8> m_lock; + + NonnullRefPtrVector<PhysicalPage> m_dma_buffers; + NonnullRefPtrVector<PhysicalPage> m_command_table_pages; + RefPtr<PhysicalPage> m_command_list_page; + OwnPtr<Region> m_command_list_region; + RefPtr<PhysicalPage> m_fis_receive_page; + RefPtr<StorageDevice> m_connected_device; + + u32 m_port_index; + volatile AHCI::PortRegisters& m_port_registers; + NonnullRefPtr<AHCIPortHandler> m_parent_handler; + AHCI::PortInterruptStatusBitField m_interrupt_status; + AHCI::PortInterruptEnableBitField m_interrupt_enable; + bool m_disabled_by_firmware { false }; +}; +} diff --git a/Kernel/Storage/AHCIPortHandler.cpp b/Kernel/Storage/AHCIPortHandler.cpp new file mode 100644 index 0000000000..953078b19e --- /dev/null +++ b/Kernel/Storage/AHCIPortHandler.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <Kernel/Storage/AHCIPortHandler.h> + +namespace Kernel { + +NonnullRefPtr<AHCIPortHandler> AHCIPortHandler::create(AHCIController& controller, u8 irq, AHCI::MaskedBitField taken_ports) +{ + return adopt(*new AHCIPortHandler(controller, irq, taken_ports)); +} + +AHCIPortHandler::AHCIPortHandler(AHCIController& controller, u8 irq, AHCI::MaskedBitField taken_ports) + : IRQHandler(irq) + , m_parent_controller(controller) + , m_taken_ports(taken_ports) + , m_pending_ports_interrupts(create_pending_ports_interrupts_bitfield()) +{ + // FIXME: Use the number of taken ports to determine how many pages we should allocate. + for (size_t index = 0; index < (((size_t)AHCI::Limits::MaxPorts * 512) / PAGE_SIZE); index++) { + m_identify_metadata_pages.append(MM.allocate_supervisor_physical_page().release_nonnull()); + } + // Clear pending interrupts, if there are any! + m_pending_ports_interrupts.set_all(); + enable_irq(); + for (auto index : taken_ports.to_vector()) { + auto port = AHCIPort::create(*this, static_cast<volatile AHCI::PortRegisters&>(controller.hba().port_regs[index]), index); + m_handled_ports.set(index, port); + port->reset(); + } +} + +void AHCIPortHandler::enumerate_ports(Function<void(const AHCIPort&)> callback) const +{ + for (auto& port : m_handled_ports) { + callback(*port.value); + } +} + +RefPtr<AHCIPort> AHCIPortHandler::port_at_index(u32 port_index) const +{ + VERIFY(m_taken_ports.is_set_at(port_index)); + auto it = m_handled_ports.find(port_index); + if (it == m_handled_ports.end()) + return nullptr; + return (*it).value; +} + +PhysicalAddress AHCIPortHandler::get_identify_metadata_physical_region(u32 port_index) const +{ + dbgln_if(AHCI_DEBUG, "AHCI Port Handler: Get identify metadata physical address of port {} - {}", port_index, (port_index * 512) / PAGE_SIZE); + return m_identify_metadata_pages[(port_index * 512) / PAGE_SIZE].paddr().offset((port_index * 512) % PAGE_SIZE); +} + +AHCI::MaskedBitField AHCIPortHandler::create_pending_ports_interrupts_bitfield() const +{ + return AHCI::MaskedBitField((volatile u32&)m_parent_controller->hba().control_regs.is, m_taken_ports.bit_mask()); +} + +AHCI::HBADefinedCapabilities AHCIPortHandler::hba_capabilities() const +{ + return m_parent_controller->hba_capabilities(); +} + +AHCIPortHandler::~AHCIPortHandler() +{ +} + +void AHCIPortHandler::handle_irq(const RegisterState&) +{ + for (auto port_index : m_pending_ports_interrupts.to_vector()) { + auto port = m_handled_ports.get(port_index); + VERIFY(port.has_value()); + port.value()->handle_interrupt(); + // We do this to clear the pending interrupt after we handled it. + m_pending_ports_interrupts.set_at(port_index); + } +} + +} diff --git a/Kernel/Storage/AHCIPortHandler.h b/Kernel/Storage/AHCIPortHandler.h new file mode 100644 index 0000000000..bc0034eeac --- /dev/null +++ b/Kernel/Storage/AHCIPortHandler.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/OwnPtr.h> +#include <AK/RefPtr.h> +#include <Kernel/Devices/Device.h> +#include <Kernel/IO.h> +#include <Kernel/Interrupts/IRQHandler.h> +#include <Kernel/Lock.h> +#include <Kernel/PhysicalAddress.h> +#include <Kernel/Random.h> +#include <Kernel/Storage/AHCIController.h> +#include <Kernel/Storage/AHCIPort.h> +#include <Kernel/Storage/StorageDevice.h> +#include <Kernel/VM/PhysicalPage.h> +#include <Kernel/WaitQueue.h> + +namespace Kernel { + +class AsyncBlockDeviceRequest; + +class AHCIController; +class AHCIPort; +class AHCIPortHandler final : public RefCounted<AHCIPortHandler> + , public IRQHandler { + friend class AHCIController; + friend class SATADiskDevice; + +public: + UNMAP_AFTER_INIT static NonnullRefPtr<AHCIPortHandler> create(AHCIController&, u8 irq, AHCI::MaskedBitField taken_ports); + virtual ~AHCIPortHandler() override; + + RefPtr<StorageDevice> device_at_port(size_t port_index) const; + virtual const char* purpose() const override { return "SATA Port Handler"; } + + AHCI::HBADefinedCapabilities hba_capabilities() const; + NonnullRefPtr<AHCIController> hba_controller() const { return m_parent_controller; } + PhysicalAddress get_identify_metadata_physical_region(u32 port_index) const; + + bool is_responsible_for_port_index(u32 port_index) const { return m_taken_ports.is_set_at(port_index); } + +private: + UNMAP_AFTER_INIT AHCIPortHandler(AHCIController&, u8 irq, AHCI::MaskedBitField taken_ports); + + //^ IRQHandler + virtual void handle_irq(const RegisterState&) override; + + enum class Direction : u8 { + Read, + Write, + }; + + AHCI::MaskedBitField create_pending_ports_interrupts_bitfield() const; + + void start_request(AsyncBlockDeviceRequest&, bool, bool, u16); + void complete_current_request(AsyncDeviceRequest::RequestResult); + + void enumerate_ports(Function<void(const AHCIPort&)> callback) const; + RefPtr<AHCIPort> port_at_index(u32 port_index) const; + + // Data members + HashMap<u32, NonnullRefPtr<AHCIPort>> m_handled_ports; + NonnullRefPtr<AHCIController> m_parent_controller; + NonnullRefPtrVector<PhysicalPage> m_identify_metadata_pages; + AHCI::MaskedBitField m_taken_ports; + AHCI::MaskedBitField m_pending_ports_interrupts; +}; +} diff --git a/Kernel/Storage/ATA.h b/Kernel/Storage/ATA.h index 1c38fae68f..44c2c8d8b8 100644 --- a/Kernel/Storage/ATA.h +++ b/Kernel/Storage/ATA.h @@ -73,6 +73,8 @@ #define ATA_IDENT_COMMANDSETS 164 #define ATA_IDENT_MAX_LBA_EXT 200 +#define ATA_USE_LBA_ADDRESSING (1 << 6) + #define IDE_ATA 0x00 #define IDE_ATAPI 0x01 diff --git a/Kernel/Storage/SATADiskDevice.cpp b/Kernel/Storage/SATADiskDevice.cpp new file mode 100644 index 0000000000..0c7f4f10d7 --- /dev/null +++ b/Kernel/Storage/SATADiskDevice.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/Memory.h> +#include <AK/StringView.h> +#include <Kernel/FileSystem/FileDescription.h> +#include <Kernel/Storage/AHCIController.h> +#include <Kernel/Storage/IDEChannel.h> +#include <Kernel/Storage/SATADiskDevice.h> + +namespace Kernel { + +NonnullRefPtr<SATADiskDevice> SATADiskDevice::create(const AHCIController& controller, const AHCIPort& port, size_t sector_size, size_t max_addressable_block) +{ + return adopt(*new SATADiskDevice(controller, port, sector_size, max_addressable_block)); +} + +SATADiskDevice::SATADiskDevice(const AHCIController& controller, const AHCIPort& port, size_t sector_size, size_t max_addressable_block) + : StorageDevice(controller, sector_size, max_addressable_block) + , m_port(port) +{ +} + +SATADiskDevice::~SATADiskDevice() +{ +} + +const char* SATADiskDevice::class_name() const +{ + return "SATADiskDevice"; +} + +void SATADiskDevice::start_request(AsyncBlockDeviceRequest& request) +{ + m_port->start_request(request); +} + +String SATADiskDevice::device_name() const +{ + return String::formatted("hd{:c}", 'a' + minor()); +} + +} diff --git a/Kernel/Storage/SATADiskDevice.h b/Kernel/Storage/SATADiskDevice.h new file mode 100644 index 0000000000..d9df0446a9 --- /dev/null +++ b/Kernel/Storage/SATADiskDevice.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <Kernel/Interrupts/IRQHandler.h> +#include <Kernel/Lock.h> +#include <Kernel/Storage/AHCIPort.h> +#include <Kernel/Storage/StorageDevice.h> + +namespace Kernel { + +class AHCIController; +class SATADiskDevice final : public StorageDevice { + friend class AHCIController; + +public: + enum class InterfaceType : u8 { + SATA, + SATAPI, + }; + +public: + static NonnullRefPtr<SATADiskDevice> create(const AHCIController&, const AHCIPort&, size_t sector_size, size_t max_addressable_block); + virtual ~SATADiskDevice() override; + + // ^StorageDevice + virtual Type type() const override { return StorageDevice::Type::SATA; } + // ^BlockDevice + virtual void start_request(AsyncBlockDeviceRequest&) override; + virtual String device_name() const override; + +private: + SATADiskDevice(const AHCIController&, const AHCIPort&, size_t sector_size, size_t max_addressable_block); + + // ^DiskDevice + virtual const char* class_name() const override; + NonnullRefPtr<AHCIPort> m_port; +}; + +} diff --git a/Kernel/Storage/StorageManagement.cpp b/Kernel/Storage/StorageManagement.cpp index 297a0dd6ad..a9ca89e858 100644 --- a/Kernel/Storage/StorageManagement.cpp +++ b/Kernel/Storage/StorageManagement.cpp @@ -30,6 +30,7 @@ #include <Kernel/FileSystem/Ext2FileSystem.h> #include <Kernel/PCI/Access.h> #include <Kernel/Panic.h> +#include <Kernel/Storage/AHCIController.h> #include <Kernel/Storage/IDEController.h> #include <Kernel/Storage/Partition/EBRPartitionTable.h> #include <Kernel/Storage/Partition/GUIDPartitionTable.h> @@ -71,6 +72,11 @@ UNMAP_AFTER_INIT NonnullRefPtrVector<StorageController> StorageManagement::enume } }); } + PCI::enumerate([&](const PCI::Address& address, PCI::ID) { + if (PCI::get_class(address) == 0x1 && PCI::get_subclass(address) == 0x6 && PCI::get_programming_interface(address) == 0x1) { + controllers.append(AHCIController::initialize(address)); + } + }); controllers.append(RamdiskController::initialize()); return controllers; } diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 05e975802a..f6f1e62ca8 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -28,6 +28,7 @@ set(SOCKET_DEBUG ON) set(TCP_SOCKET_DEBUG ON) set(PCI_DEBUG ON) set(PATA_DEBUG ON) +set(AHCI_DEBUG ON) set(IO_DEBUG ON) set(FORK_DEBUG ON) set(POLL_SELECT_DEBUG ON) |