summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Kernel/CMakeLists.txt4
-rw-r--r--Kernel/Debug.h.in4
-rw-r--r--Kernel/Storage/AHCI.h536
-rw-r--r--Kernel/Storage/AHCIController.cpp198
-rw-r--r--Kernel/Storage/AHCIController.h77
-rw-r--r--Kernel/Storage/AHCIPort.cpp622
-rw-r--r--Kernel/Storage/AHCIPort.h132
-rw-r--r--Kernel/Storage/AHCIPortHandler.cpp103
-rw-r--r--Kernel/Storage/AHCIPortHandler.h93
-rw-r--r--Kernel/Storage/ATA.h2
-rw-r--r--Kernel/Storage/SATADiskDevice.cpp66
-rw-r--r--Kernel/Storage/SATADiskDevice.h64
-rw-r--r--Kernel/Storage/StorageManagement.cpp6
-rw-r--r--Meta/CMake/all_the_debug_macros.cmake1
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)