diff options
author | Liav A <liavalb@gmail.com> | 2021-02-26 06:25:45 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-03-05 11:29:34 +0100 |
commit | c4463cb5df75dedc04a198860809eb3967a0a5fd (patch) | |
tree | 48fbe34c99d2c31eaa32195c9428ea9ee155bf9b /Kernel | |
parent | 4a5cf8c789aefc247f94bd6a91313dab3b06036d (diff) | |
download | serenity-c4463cb5df75dedc04a198860809eb3967a0a5fd.zip |
Kernel: Add basic AHCI functionality
The hierarchy is AHCIController, AHCIPortHandler, AHCIPort and
SATADiskDevice. Each AHCIController has at least one AHCIPortHandler.
An AHCIPortHandler is an interrupt handler that takes care of
enumeration of handled AHCI ports when an interrupt occurs. Each
AHCIPort takes care of one SATADiskDevice, and later on we can add
support for Port multiplier.
When we implement support of Message signalled interrupts, we can spawn
many AHCIPortHandlers, and allow each one of them to be responsible for
a set of AHCIPorts.
Diffstat (limited to 'Kernel')
-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 |
13 files changed, 1907 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; } |