1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 07:17:35 +00:00

Kernel: Move the Storage directory to be a new directory under Devices

The Storage subsystem, like the Audio and HID subsystems, exposes Unix
device files (for example, in the /dev directory). To ensure consistency
across the repository, we should make the Storage subsystem to reside in
the Kernel/Devices directory like the two other mentioned subsystems.
This commit is contained in:
Liav A 2023-03-18 13:32:12 +02:00 committed by Jelle Raaijmakers
parent f3a58f3a5a
commit 500b7b08d6
59 changed files with 133 additions and 133 deletions

View file

@ -0,0 +1,231 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Atomic.h>
#include <AK/BuiltinWrappers.h>
#include <AK/OwnPtr.h>
#include <AK/Types.h>
#include <Kernel/Arch/Delay.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/CommandLine.h>
#include <Kernel/Devices/Storage/ATA/AHCI/Controller.h>
#include <Kernel/Devices/Storage/ATA/AHCI/InterruptHandler.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Memory/MemoryManager.h>
namespace Kernel {
UNMAP_AFTER_INIT ErrorOr<NonnullRefPtr<AHCIController>> AHCIController::initialize(PCI::DeviceIdentifier const& pci_device_identifier)
{
auto controller = adopt_ref_if_nonnull(new (nothrow) AHCIController(pci_device_identifier)).release_nonnull();
TRY(controller->initialize_hba(pci_device_identifier));
return controller;
}
ErrorOr<void> AHCIController::reset()
{
dmesgln_pci(*this, "{}: AHCI controller reset", device_identifier().address());
{
SpinlockLocker locker(m_hba_control_lock);
hba().control_regs.ghc = 1;
dbgln_if(AHCI_DEBUG, "{}: AHCI Controller reset", device_identifier().address());
full_memory_barrier();
size_t retry = 0;
// Note: The HBA is locked or hung if we waited more than 1 second!
while (true) {
if (retry > 1000)
return Error::from_errno(ETIMEDOUT);
if (!(hba().control_regs.ghc & 1))
break;
microseconds_delay(1000);
retry++;
}
// Note: Turn on AHCI HBA and Global HBA Interrupts.
full_memory_barrier();
hba().control_regs.ghc = (1 << 31) | (1 << 1);
full_memory_barrier();
}
// Note: According to the AHCI spec the PI register indicates which ports are exposed by the HBA.
// It is loaded by the BIOS. It indicates which ports that the HBA supports are available for software to use.
// For example, on an HBA that supports 6 ports as indicated in CAP.NP, only ports 1 and 3 could be available,
// with ports 0, 2, 4, and 5 being unavailable.
// Which means that even without clearing the AHCI ports array, we are never able to encounter
// a case that we would have stale left-over ports in there. We still clear the array
// for the sake of clarity and completeness, as it doesn't harm anything anyway.
m_ports.fill({});
auto implemented_ports = AHCI::MaskedBitField((u32 volatile&)(hba().control_regs.pi));
for (auto index : implemented_ports.to_vector()) {
auto port = AHCIPort::create(*this, m_hba_capabilities, static_cast<volatile AHCI::PortRegisters&>(hba().port_regs[index]), index).release_value_but_fixme_should_propagate_errors();
m_ports[index] = port;
port->reset();
}
return {};
}
ErrorOr<void> AHCIController::shutdown()
{
return Error::from_errno(ENOTIMPL);
}
size_t AHCIController::devices_count() const
{
SpinlockLocker locker(m_hba_control_lock);
size_t count = 0;
for (auto port : m_ports) {
if (port && port->connected_device())
count++;
}
return count;
}
void AHCIController::start_request(ATADevice const& device, AsyncBlockDeviceRequest& request)
{
auto port = m_ports[device.ata_address().port];
VERIFY(port);
port->start_request(request);
}
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 const_cast<AHCI::HBA&>(*m_hba_mapping);
}
UNMAP_AFTER_INIT AHCIController::AHCIController(PCI::DeviceIdentifier const& pci_device_identifier)
: ATAController()
, PCI::Device(const_cast<PCI::DeviceIdentifier&>(pci_device_identifier))
{
}
UNMAP_AFTER_INIT AHCI::HBADefinedCapabilities AHCIController::capabilities() const
{
u32 capabilities = hba().control_regs.cap;
u32 extended_capabilities = hba().control_regs.cap2;
dbgln_if(AHCI_DEBUG, "{}: AHCI Controller Capabilities = {:#08x}, Extended Capabilities = {:#08x}", device_identifier().address(), capabilities, extended_capabilities);
return (AHCI::HBADefinedCapabilities) {
(capabilities & 0b11111) + 1,
((capabilities >> 8) & 0b11111) + 1,
(u8)((capabilities >> 20) & 0b1111),
(capabilities & (u32)(AHCI::HBACapabilities::SXS)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::EMS)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::CCCS)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::PSC)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SSC)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::PMD)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::FBSS)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SPM)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SAM)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SCLO)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SAL)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SALP)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SSS)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SMPS)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SSNTF)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::SNCQ)) != 0,
(capabilities & (u32)(AHCI::HBACapabilities::S64A)) != 0,
(extended_capabilities & (u32)(AHCI::HBACapabilitiesExtended::BOH)) != 0,
(extended_capabilities & (u32)(AHCI::HBACapabilitiesExtended::NVMP)) != 0,
(extended_capabilities & (u32)(AHCI::HBACapabilitiesExtended::APST)) != 0,
(extended_capabilities & (u32)(AHCI::HBACapabilitiesExtended::SDS)) != 0,
(extended_capabilities & (u32)(AHCI::HBACapabilitiesExtended::SADM)) != 0,
(extended_capabilities & (u32)(AHCI::HBACapabilitiesExtended::DESO)) != 0
};
}
UNMAP_AFTER_INIT ErrorOr<Memory::TypedMapping<AHCI::HBA volatile>> AHCIController::map_default_hba_region(PCI::DeviceIdentifier const& pci_device_identifier)
{
return Memory::map_typed_writable<AHCI::HBA volatile>(PhysicalAddress(PCI::get_BAR5(pci_device_identifier)));
}
AHCIController::~AHCIController() = default;
UNMAP_AFTER_INIT ErrorOr<void> AHCIController::initialize_hba(PCI::DeviceIdentifier const& pci_device_identifier)
{
m_hba_mapping = TRY(map_default_hba_region(pci_device_identifier));
m_hba_capabilities = capabilities();
u32 version = hba().control_regs.version;
hba().control_regs.ghc = 0x80000000; // Ensure that HBA knows we are AHCI aware.
PCI::enable_bus_mastering(device_identifier());
TRY(reserve_irqs(1, true));
auto irq = MUST(allocate_irq(0));
enable_global_interrupts();
auto implemented_ports = AHCI::MaskedBitField((u32 volatile&)(hba().control_regs.pi));
m_irq_handler = AHCIInterruptHandler::create(*this, irq, implemented_ports).release_value_but_fixme_should_propagate_errors();
TRY(reset());
dbgln_if(AHCI_DEBUG, "{}: AHCI Controller Version = {:#08x}", device_identifier().address(), version);
dbgln("{}: AHCI command list entries count - {}", device_identifier().address(), m_hba_capabilities.max_command_list_entries_count);
return {};
}
void AHCIController::handle_interrupt_for_port(Badge<AHCIInterruptHandler>, u32 port_index) const
{
auto port = m_ports[port_index];
VERIFY(port);
port->handle_interrupt();
}
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);
}
LockRefPtr<StorageDevice> AHCIController::device_by_port(u32 port_index) const
{
SpinlockLocker locker(m_hba_control_lock);
auto port = m_ports[port_index];
if (!port)
return {};
SpinlockLocker port_hard_locker(port->m_hard_lock);
return port->connected_device();
}
LockRefPtr<StorageDevice> AHCIController::device(u32 index) const
{
Vector<NonnullLockRefPtr<StorageDevice>> connected_devices;
u32 pi = hba().control_regs.pi;
u32 bit = bit_scan_forward(pi);
while (bit) {
dbgln_if(AHCI_DEBUG, "Checking implemented port {}, pi {:b}", bit - 1, pi);
pi &= ~(1u << (bit - 1));
auto checked_device = device_by_port(bit - 1);
bit = bit_scan_forward(pi);
if (checked_device.is_null())
continue;
connected_devices.append(checked_device.release_nonnull());
}
dbgln_if(AHCI_DEBUG, "Connected device count: {}, Index: {}", connected_devices.size(), index);
if (index >= connected_devices.size())
return nullptr;
return connected_devices[index];
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/Types.h>
#include <Kernel/Devices/Storage/ATA/AHCI/Definitions.h>
#include <Kernel/Devices/Storage/ATA/ATAController.h>
#include <Kernel/Devices/Storage/StorageDevice.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Memory/TypedMapping.h>
#include <Kernel/Sections.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class AHCIInterruptHandler;
class AHCIPort;
class AHCIController final : public ATAController
, public PCI::Device {
friend class AHCIInterruptHandler;
public:
static ErrorOr<NonnullRefPtr<AHCIController>> initialize(PCI::DeviceIdentifier const& pci_device_identifier);
virtual ~AHCIController() override;
virtual StringView device_name() const override { return "AHCI"sv; }
virtual LockRefPtr<StorageDevice> device(u32 index) const override;
virtual ErrorOr<void> reset() override;
virtual ErrorOr<void> shutdown() override;
virtual size_t devices_count() const override;
virtual void start_request(ATADevice const&, AsyncBlockDeviceRequest&) override;
virtual void complete_current_request(AsyncDeviceRequest::RequestResult) override;
void handle_interrupt_for_port(Badge<AHCIInterruptHandler>, u32 port_index) const;
private:
void disable_global_interrupts() const;
void enable_global_interrupts() const;
explicit AHCIController(PCI::DeviceIdentifier const&);
ErrorOr<void> initialize_hba(PCI::DeviceIdentifier const&);
AHCI::HBADefinedCapabilities capabilities() const;
LockRefPtr<StorageDevice> device_by_port(u32 index) const;
volatile AHCI::PortRegisters& port(size_t port_number) const;
ErrorOr<Memory::TypedMapping<AHCI::HBA volatile>> map_default_hba_region(PCI::DeviceIdentifier const&);
volatile AHCI::HBA& hba() const;
Array<LockRefPtr<AHCIPort>, 32> m_ports;
Memory::TypedMapping<AHCI::HBA volatile> m_hba_mapping;
AHCI::HBADefinedCapabilities m_hba_capabilities;
// FIXME: There could be multiple IRQ (MSI) handlers for AHCI. Find a way to use all of them.
OwnPtr<AHCIInterruptHandler> m_irq_handler;
// Note: This lock is intended to be locked when doing changes to HBA registers
// that affect its core functionality in a manner that controls all attached storage devices
// to the HBA SATA ports.
mutable Spinlock<LockRank::None> m_hba_control_lock {};
};
}

View file

@ -0,0 +1,429 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
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(u32 volatile& bitfield_register)
: m_bitfield(bitfield_register)
, m_bit_mask(0xffffffff)
{
}
MaskedBitField(u32 volatile& bitfield_register, u32 bit_mask)
: m_bitfield(bitfield_register)
, m_bit_mask(bit_mask)
{
}
void set_at(u8 index) const
{
VERIFY(((1u << index) & m_bit_mask) != 0);
m_bitfield = m_bitfield | ((1u << index) & m_bit_mask);
}
void set_all() const
{
m_bitfield = m_bitfield | (0xffffffff & m_bit_mask);
}
bool is_set_at(u8 port_index) const
{
return m_bitfield & ((1u << port_index) & m_bit_mask);
}
bool is_zeroed() const
{
return (m_bitfield & m_bit_mask) == 0;
}
Vector<u8> to_vector() const
{
// FIXME: Add a sync mechanism!
Vector<u8> indices;
u32 bitfield = m_bitfield & m_bit_mask;
for (size_t index = 0; index < 32; index++) {
if (bitfield & 1) {
indices.append(index);
}
bitfield >>= 1;
}
return indices;
}
u32 bit_mask() const { return m_bit_mask; };
// Disable default implementations that would use surprising integer promotion.
bool operator==(MaskedBitField const&) const = delete;
bool operator<=(MaskedBitField const&) const = delete;
bool operator>=(MaskedBitField const&) const = delete;
bool operator<(MaskedBitField const&) const = delete;
bool operator>(MaskedBitField const&) const = delete;
private:
u32 volatile& 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 HBACapabilities : 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 */
};
enum HBACapabilitiesExtended : u32 {
DESO = 1 << 5, /* DevSleep Entrance from Slumber Only */
SADM = 1 << 4, /* Supports Aggressive Device Sleep Management */
SDS = 1 << 3, /* Supports Device Sleep */
APST = 1 << 2, /* Automatic Partial to Slumber Transitions */
NVMP = 1 << 1, /* NVMHCI Present */
BOH = 1 << 0, /* BIOS/OS Handoff */
};
// 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_multiplier_supported : 1 { false };
bool ahci_mode_only : 1 { true };
bool command_list_override_supported : 1 { false };
bool activity_led_supported : 1 { false };
bool aggressive_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 };
bool bios_os_handoff : 1 { false };
bool nvmhci_present : 1 { false };
bool automatic_partial_to_slumber_transitions : 1 { false };
bool device_sleep_supported : 1 { false };
bool aggressive_device_sleep_management_supported : 1 { false };
bool devsleep_entrance_from_slumber_only : 1 { false };
};
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 */
};
enum SErr : u32 {
DIAG_X = 1 << 26, /* Exchanged */
DIAG_F = 1 << 25, /* Unknown FIS Type */
DIAG_T = 1 << 24, /* Transport state transition error */
DIAG_S = 1 << 23, /* Link sequence error */
DIAG_H = 1 << 22, /* Handshake error */
DIAG_C = 1 << 21, /* CRC error */
DIAG_D = 1 << 20, /* Disparity error */
DIAG_B = 1 << 19, /* 10B to 8B decode error */
DIAG_W = 1 << 18, /* Comm Wake */
DIAG_I = 1 << 17, /* Phy Internal Error */
DIAG_N = 1 << 16, /* PhyRdy Change */
ERR_E = 1 << 11, /* Internal error */
ERR_P = 1 << 10, /* Protocol error */
ERR_C = 1 << 9, /* Persistent communication or data integrity error */
ERR_T = 1 << 8, /* Transient data integrity error */
ERR_M = 1 << 1, /* Received communications error */
ERR_I = 1 << 0, /* Recovered data integrity error */
};
class PortInterruptStatusBitField {
public:
explicit PortInterruptStatusBitField(u32 volatile& 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==(MaskedBitField const&) const = delete;
bool operator<=(MaskedBitField const&) const = delete;
bool operator>=(MaskedBitField const&) const = delete;
bool operator<(MaskedBitField const&) const = delete;
bool operator>(MaskedBitField const&) const = delete;
private:
u32 volatile& m_bitfield;
};
class PortInterruptEnableBitField {
public:
explicit PortInterruptEnableBitField(u32 volatile& bitfield_register)
: m_bitfield(bitfield_register)
{
}
u32 raw_value() const { return m_bitfield; }
bool is_set(PortInterruptFlag flag) { return m_bitfield & (u32)flag; }
void set_at(PortInterruptFlag flag) { m_bitfield = m_bitfield | static_cast<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==(MaskedBitField const&) const = delete;
bool operator<=(MaskedBitField const&) const = delete;
bool operator>=(MaskedBitField const&) const = delete;
bool operator<(MaskedBitField const&) const = delete;
bool operator>(MaskedBitField const&) const = delete;
private:
u32 volatile& 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[];
};
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/Storage/ATA/AHCI/InterruptHandler.h>
namespace Kernel {
UNMAP_AFTER_INIT ErrorOr<NonnullOwnPtr<AHCIInterruptHandler>> AHCIInterruptHandler::create(AHCIController& controller, u8 irq, AHCI::MaskedBitField taken_ports)
{
auto port_handler = TRY(adopt_nonnull_own_or_enomem(new (nothrow) AHCIInterruptHandler(controller, irq, taken_ports)));
port_handler->allocate_resources_and_initialize_ports();
return port_handler;
}
void AHCIInterruptHandler::allocate_resources_and_initialize_ports()
{
// Clear pending interrupts, if there are any!
m_pending_ports_interrupts.set_all();
enable_irq();
}
UNMAP_AFTER_INIT AHCIInterruptHandler::AHCIInterruptHandler(AHCIController& controller, u8 irq, AHCI::MaskedBitField taken_ports)
: PCIIRQHandler(controller, irq)
, m_parent_controller(controller)
, m_taken_ports(taken_ports)
, m_pending_ports_interrupts(create_pending_ports_interrupts_bitfield())
{
dbgln_if(AHCI_DEBUG, "AHCI Port Handler: IRQ {}", irq);
}
AHCI::MaskedBitField AHCIInterruptHandler::create_pending_ports_interrupts_bitfield() const
{
return AHCI::MaskedBitField((u32 volatile&)m_parent_controller->hba().control_regs.is, m_taken_ports.bit_mask());
}
AHCIInterruptHandler::~AHCIInterruptHandler() = default;
bool AHCIInterruptHandler::handle_irq(RegisterState const&)
{
dbgln_if(AHCI_DEBUG, "AHCI Port Handler: IRQ received");
if (m_pending_ports_interrupts.is_zeroed())
return false;
for (auto port_index : m_pending_ports_interrupts.to_vector()) {
dbgln_if(AHCI_DEBUG, "AHCI Port Handler: Handling IRQ for port {}", port_index);
m_parent_controller->handle_interrupt_for_port({}, port_index);
// We do this to clear the pending interrupt after we handled it.
m_pending_ports_interrupts.set_at(port_index);
}
return true;
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/Device.h>
#include <Kernel/Devices/Storage/ATA/AHCI/Controller.h>
#include <Kernel/Devices/Storage/ATA/AHCI/Port.h>
#include <Kernel/Devices/Storage/StorageDevice.h>
#include <Kernel/Interrupts/PCIIRQHandler.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Locking/Mutex.h>
#include <Kernel/Memory/PhysicalPage.h>
#include <Kernel/PhysicalAddress.h>
#include <Kernel/Random.h>
#include <Kernel/Sections.h>
#include <Kernel/WaitQueue.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class AHCIController;
class AHCIPort;
class AHCIInterruptHandler final : public PCIIRQHandler {
friend class AHCIController;
public:
static ErrorOr<NonnullOwnPtr<AHCIInterruptHandler>> create(AHCIController&, u8 irq, AHCI::MaskedBitField taken_ports);
virtual ~AHCIInterruptHandler() override;
virtual StringView purpose() const override { return "SATA IRQ Handler"sv; }
bool is_responsible_for_port_index(u32 port_index) const { return m_taken_ports.is_set_at(port_index); }
private:
AHCIInterruptHandler(AHCIController&, u8 irq, AHCI::MaskedBitField taken_ports);
void allocate_resources_and_initialize_ports();
//^ IRQHandler
virtual bool handle_irq(RegisterState const&) override;
enum class Direction : u8 {
Read,
Write,
};
AHCI::MaskedBitField create_pending_ports_interrupts_bitfield() const;
// Data members
NonnullLockRefPtr<AHCIController> m_parent_controller;
AHCI::MaskedBitField m_taken_ports;
AHCI::MaskedBitField m_pending_ports_interrupts;
};
}

View file

@ -0,0 +1,815 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
// For more information about locking in this code
// please look at Documentation/Kernel/AHCILocking.md
#include <AK/Atomic.h>
#include <Kernel/Arch/Delay.h>
#include <Kernel/Devices/Storage/ATA/AHCI/Port.h>
#include <Kernel/Devices/Storage/ATA/ATADiskDevice.h>
#include <Kernel/Devices/Storage/ATA/Definitions.h>
#include <Kernel/Devices/Storage/StorageManagement.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/MemoryManager.h>
#include <Kernel/Memory/ScatterGatherList.h>
#include <Kernel/Memory/TypedMapping.h>
#include <Kernel/WorkQueue.h>
namespace Kernel {
UNMAP_AFTER_INIT ErrorOr<NonnullLockRefPtr<AHCIPort>> AHCIPort::create(AHCIController const& controller, AHCI::HBADefinedCapabilities hba_capabilities, volatile AHCI::PortRegisters& registers, u32 port_index)
{
auto identify_buffer_page = MUST(MM.allocate_physical_page());
auto port = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) AHCIPort(controller, move(identify_buffer_page), hba_capabilities, registers, port_index)));
TRY(port->allocate_resources_and_initialize_ports());
return port;
}
ErrorOr<void> AHCIPort::allocate_resources_and_initialize_ports()
{
if (is_interface_disabled()) {
m_disabled_by_firmware = true;
return {};
}
m_fis_receive_page = TRY(MM.allocate_physical_page());
for (size_t index = 0; index < 1; index++) {
auto dma_page = TRY(MM.allocate_physical_page());
m_dma_buffers.append(move(dma_page));
}
for (size_t index = 0; index < 1; index++) {
auto command_table_page = TRY(MM.allocate_physical_page());
m_command_table_pages.append(move(command_table_page));
}
m_command_list_region = TRY(MM.allocate_dma_buffer_page("AHCI Port Command List"sv, Memory::Region::Access::ReadWrite, m_command_list_page));
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Command list page at {}", representative_port_index(), m_command_list_page->paddr());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: FIS receive page at {}", representative_port_index(), m_fis_receive_page->paddr());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Command list region at {}", representative_port_index(), m_command_list_region->vaddr());
return {};
}
UNMAP_AFTER_INIT AHCIPort::AHCIPort(AHCIController const& controller, NonnullRefPtr<Memory::PhysicalPage> identify_buffer_page, AHCI::HBADefinedCapabilities hba_capabilities, volatile AHCI::PortRegisters& registers, u32 port_index)
: m_port_index(port_index)
, m_hba_capabilities(hba_capabilities)
, m_identify_buffer_page(move(identify_buffer_page))
, m_port_registers(registers)
, m_parent_controller(controller)
, m_interrupt_status((u32 volatile&)m_port_registers.is)
, m_interrupt_enable((u32 volatile&)m_port_registers.ie)
{
}
void AHCIPort::clear_sata_error_register() const
{
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Clearing SATA error register.", representative_port_index());
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)) {
clear_sata_error_register();
if ((m_port_registers.ssts & 0xf) != 3 && m_connected_device) {
m_connected_device->prepare_for_unplug();
StorageManagement::the().remove_device(*m_connected_device);
auto work_item_creation_result = g_io_work->try_queue([this]() {
m_connected_device.clear();
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
} else {
auto work_item_creation_result = g_io_work->try_queue([this]() {
reset();
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
}
return;
}
if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::PRC)) {
clear_sata_error_register();
}
if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::INF)) {
// We need to defer the reset, because we can receive interrupts when
// resetting the device.
auto work_item_creation_result = g_io_work->try_queue([this]() {
reset();
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
return;
}
if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::IF) || m_interrupt_status.is_set(AHCI::PortInterruptFlag::TFE) || m_interrupt_status.is_set(AHCI::PortInterruptFlag::HBD) || m_interrupt_status.is_set(AHCI::PortInterruptFlag::HBF)) {
auto work_item_creation_result = g_io_work->try_queue([this]() {
recover_from_fatal_error();
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
return;
}
if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::DHR) || m_interrupt_status.is_set(AHCI::PortInterruptFlag::PS)) {
m_wait_for_completion = false;
// Now schedule reading/writing the buffer as soon as we leave the irq handler.
// This is important so that we can safely access the buffers, which could
// trigger page faults
if (!m_current_request) {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request handled, probably identify request", representative_port_index());
} else {
auto work_item_creation_result = g_io_work->try_queue([this]() {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request handled", representative_port_index());
MutexLocker locker(m_lock);
VERIFY(m_current_request);
VERIFY(m_current_scatter_list);
if (!m_connected_device) {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request success", representative_port_index());
complete_current_request(AsyncDeviceRequest::Failure);
return;
}
if (m_current_request->request_type() == AsyncBlockDeviceRequest::Read) {
if (auto result = m_current_request->write_to_buffer(m_current_request->buffer(), m_current_scatter_list->dma_region().as_ptr(), m_connected_device->block_size() * m_current_request->block_count()); result.is_error()) {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request failure, memory fault occurred when reading in data.", representative_port_index());
m_current_scatter_list = nullptr;
complete_current_request(AsyncDeviceRequest::MemoryFault);
return;
}
}
m_current_scatter_list = nullptr;
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request success", representative_port_index());
complete_current_request(AsyncDeviceRequest::Success);
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
}
}
m_interrupt_status.clear();
}
bool AHCIPort::is_interrupts_enabled() const
{
return !m_interrupt_enable.is_cleared();
}
void AHCIPort::recover_from_fatal_error()
{
MutexLocker locker(m_lock);
SpinlockLocker lock(m_hard_lock);
LockRefPtr<AHCIController> controller = m_parent_controller.strong_ref();
if (!controller) {
dmesgln("AHCI Port {}: fatal error, controller not available", representative_port_index());
return;
}
dmesgln("{}: AHCI Port {} fatal error, shutting down!", controller->device_identifier().address(), representative_port_index());
dmesgln("{}: AHCI Port {} fatal error, SError {}", controller->device_identifier().address(), representative_port_index(), (u32)m_port_registers.serr);
stop_command_list_processing();
stop_fis_receiving();
m_interrupt_enable.clear();
}
bool AHCIPort::reset()
{
MutexLocker locker(m_lock);
SpinlockLocker lock(m_hard_lock);
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Resetting", representative_port_index());
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;
}
return initialize();
}
UNMAP_AFTER_INIT bool AHCIPort::initialize_without_reset()
{
MutexLocker locker(m_lock);
SpinlockLocker lock(m_hard_lock);
dmesgln("AHCI Port {}: {}", representative_port_index(), try_disambiguate_sata_status());
return initialize();
}
bool AHCIPort::initialize()
{
VERIFY(m_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Initialization. Signature = {:#08x}", representative_port_index(), static_cast<u32>(m_port_registers.sig));
if (!is_phy_enabled()) {
// Note: If PHY is not enabled, just clear the interrupt status and enable interrupts, in case
// we are going to hotplug a device later.
m_interrupt_status.clear();
m_interrupt_enable.set_all();
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Bailing initialization, Phy is not enabled.", representative_port_index());
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;
u64 max_addressable_sector = 0;
if (identify_device()) {
auto identify_block = Memory::map_typed<ATAIdentifyBlock>(m_identify_buffer_page->paddr()).release_value_but_fixme_should_propagate_errors();
// 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 {}: Device found, Capacity={}, Bytes per logical sector={}, Bytes per physical sector={}", representative_port_index(), max_addressable_sector * logical_sector_size, 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()) {
LockRefPtr<AHCIController> controller = m_parent_controller.strong_ref();
if (!controller) {
dmesgln("AHCI Port {}: Device found, but parent controller is not available, abort.", representative_port_index());
return false;
}
m_connected_device = ATADiskDevice::create(*controller, { m_port_index, 0 }, 0, logical_sector_size, max_addressable_sector);
} else {
dbgln("AHCI Port {}: Ignoring ATAPI devices as we don't support them.", representative_port_index());
}
}
return true;
}
char const* 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::try_disambiguate_sata_error()
{
dmesgln("AHCI Port {}: SErr breakdown:", representative_port_index());
dmesgln("AHCI Port {}: Diagnostics:", representative_port_index());
constexpr u32 diagnostics_bitfield = 0xFFFF0000;
if ((m_port_registers.serr & diagnostics_bitfield) > 0) {
if (m_port_registers.serr & AHCI::SErr::DIAG_X)
dmesgln("AHCI Port {}: - Exchanged", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_F)
dmesgln("AHCI Port {}: - Unknown FIS Type", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_T)
dmesgln("AHCI Port {}: - Transport state transition error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_S)
dmesgln("AHCI Port {}: - Link sequence error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_H)
dmesgln("AHCI Port {}: - Handshake error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_C)
dmesgln("AHCI Port {}: - CRC error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_D)
dmesgln("AHCI Port {}: - Disparity error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_B)
dmesgln("AHCI Port {}: - 10B to 8B decode error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_W)
dmesgln("AHCI Port {}: - Comm Wake", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_I)
dmesgln("AHCI Port {}: - Phy Internal Error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::DIAG_N)
dmesgln("AHCI Port {}: - PhyRdy Change", representative_port_index());
} else {
dmesgln("AHCI Port {}: - No diagnostic information provided.", representative_port_index());
}
dmesgln("AHCI Port {}: Error(s):", representative_port_index());
constexpr u32 error_bitfield = 0xFFFF;
if ((m_port_registers.serr & error_bitfield) > 0) {
if (m_port_registers.serr & AHCI::SErr::ERR_E)
dmesgln("AHCI Port {}: - Internal error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::ERR_P)
dmesgln("AHCI Port {}: - Protocol error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::ERR_C)
dmesgln("AHCI Port {}: - Persistent communication or data integrity error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::ERR_T)
dmesgln("AHCI Port {}: - Transient data integrity error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::ERR_M)
dmesgln("AHCI Port {}: - Recovered communications error", representative_port_index());
if (m_port_registers.serr & AHCI::SErr::ERR_I)
dmesgln("AHCI Port {}: - Recovered data integrity error", representative_port_index());
} else {
dmesgln("AHCI Port {}: - No error information provided.", representative_port_index());
}
}
void AHCIPort::rebase()
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
VERIFY(!m_command_list_page.is_null() && !m_fis_receive_page.is_null());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Rebasing.", representative_port_index());
full_memory_barrier();
stop_command_list_processing();
stop_fis_receiving();
full_memory_barrier();
// Try to wait 1 second for HBA to clear Command List Running and FIS Receive Running
wait_until_condition_met_or_timeout(1000, 1000, [this]() -> bool {
return !(m_port_registers.cmd & (1 << 15)) && !(m_port_registers.cmd & (1 << 14));
});
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());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Switching to active state.", representative_port_index());
m_port_registers.cmd = (m_port_registers.cmd & 0x0ffffff) | (1 << 28);
}
void AHCIPort::set_sleep_state() const
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
m_port_registers.cmd = (m_port_registers.cmd & 0x0ffffff) | (0b1000 << 28);
}
size_t AHCIPort::calculate_descriptors_count(size_t block_count) const
{
VERIFY(m_connected_device);
size_t needed_dma_regions_count = Memory::page_round_up((block_count * m_connected_device->block_size())).value() / PAGE_SIZE;
VERIFY(needed_dma_regions_count <= m_dma_buffers.size());
return needed_dma_regions_count;
}
Optional<AsyncDeviceRequest::RequestResult> AHCIPort::prepare_and_set_scatter_list(AsyncBlockDeviceRequest& request)
{
VERIFY(m_lock.is_locked());
VERIFY(request.block_count() > 0);
Vector<NonnullRefPtr<Memory::PhysicalPage>> allocated_dma_regions;
for (size_t index = 0; index < calculate_descriptors_count(request.block_count()); index++) {
allocated_dma_regions.append(m_dma_buffers.at(index));
}
m_current_scatter_list = Memory::ScatterGatherList::try_create(request, allocated_dma_regions.span(), m_connected_device->block_size(), "AHCI Scattered DMA"sv).release_value_but_fixme_should_propagate_errors();
if (!m_current_scatter_list)
return AsyncDeviceRequest::Failure;
if (request.request_type() == AsyncBlockDeviceRequest::Write) {
if (auto result = request.read_from_buffer(request.buffer(), m_current_scatter_list->dma_region().as_ptr(), m_connected_device->block_size() * request.block_count()); result.is_error()) {
return AsyncDeviceRequest::MemoryFault;
}
}
return {};
}
void AHCIPort::start_request(AsyncBlockDeviceRequest& request)
{
MutexLocker locker(m_lock);
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request start", representative_port_index());
VERIFY(!m_current_request);
VERIFY(!m_current_scatter_list);
m_current_request = request;
auto result = prepare_and_set_scatter_list(request);
if (result.has_value()) {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request failure.", representative_port_index());
locker.unlock();
complete_current_request(result.value());
return;
}
auto success = access_device(request.request_type(), request.block_index(), request.block_count());
if (!success) {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Request failure.", representative_port_index());
locker.unlock();
complete_current_request(AsyncDeviceRequest::Failure);
return;
}
}
void AHCIPort::complete_current_request(AsyncDeviceRequest::RequestResult result)
{
VERIFY(m_current_request);
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(result);
}
bool AHCIPort::spin_until_ready() const
{
VERIFY(m_lock.is_locked());
size_t spin = 0;
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Spinning until ready.", representative_port_index());
while ((m_port_registers.tfd & (ATA_SR_BSY | ATA_SR_DRQ)) && spin <= 100) {
microseconds_delay(1000);
spin++;
}
if (spin == 100) {
dbgln_if(AHCI_DEBUG, "AHCI Port {}: SPIN exceeded 100 milliseconds threshold", representative_port_index());
return false;
}
return true;
}
bool AHCIPort::access_device(AsyncBlockDeviceRequest::RequestType direction, u64 lba, u8 block_count)
{
VERIFY(m_connected_device);
VERIFY(is_operable());
VERIFY(m_lock.is_locked());
VERIFY(m_current_scatter_list);
SpinlockLocker lock(m_hard_lock);
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 = 0;
command_list_entries[unused_command_header.value()].prdtl = m_current_scatter_list->scatters_count();
// 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 | (is_atapi_attached() ? AHCI::CommandHeaderAttributes::A : 0) | (direction == AsyncBlockDeviceRequest::RequestType::Write ? AHCI::CommandHeaderAttributes::W : 0);
dbgln_if(AHCI_DEBUG, "AHCI Port {}: CLE: ctba={:#08x}, ctbau={:#08x}, prdbc={:#08x}, prdtl={:#04x}, attributes={:#04x}", representative_port_index(), (u32)command_list_entries[unused_command_header.value()].ctba, (u32)command_list_entries[unused_command_header.value()].ctbau, (u32)command_list_entries[unused_command_header.value()].prdbc, (u16)command_list_entries[unused_command_header.value()].prdtl, (u16)command_list_entries[unused_command_header.value()].attributes);
auto command_table_region = MM.allocate_kernel_region(m_command_table_pages[unused_command_header.value()]->paddr().page_base(), Memory::page_round_up(sizeof(AHCI::CommandTable)).value(), "AHCI Command Table"sv, Memory::Region::Access::ReadWrite, Memory::Region::Cacheable::No).release_value();
auto& command_table = *(volatile AHCI::CommandTable*)command_table_region->vaddr().as_ptr();
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Allocated command table at {}", representative_port_index(), command_table_region->vaddr());
memset(const_cast<u8*>(command_table.command_fis), 0, 64);
size_t scatter_entry_index = 0;
size_t data_transfer_count = (block_count * m_connected_device->block_size());
for (auto scatter_page : m_current_scatter_list->vmobject().physical_pages()) {
VERIFY(data_transfer_count != 0);
VERIFY(scatter_page);
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Add a transfer scatter entry @ {}", representative_port_index(), scatter_page->paddr());
command_table.descriptors[scatter_entry_index].base_high = 0;
command_table.descriptors[scatter_entry_index].base_low = scatter_page->paddr().get();
if (data_transfer_count <= PAGE_SIZE) {
command_table.descriptors[scatter_entry_index].byte_count = data_transfer_count - 1;
data_transfer_count = 0;
} else {
command_table.descriptors[scatter_entry_index].byte_count = PAGE_SIZE - 1;
data_transfer_count -= PAGE_SIZE;
}
scatter_entry_index++;
}
command_table.descriptors[scatter_entry_index].byte_count = (PAGE_SIZE - 1) | (1 << 31);
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();
mark_command_header_ready_to_process(unused_command_header.value());
full_memory_barrier();
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;
LockRefPtr<AHCIController> controller = m_parent_controller.strong_ref();
if (!controller)
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;
auto command_table_region = MM.allocate_kernel_region(m_command_table_pages[unused_command_header.value()]->paddr().page_base(), Memory::page_round_up(sizeof(AHCI::CommandTable)).value(), "AHCI Command Table"sv, Memory::Region::Access::ReadWrite).release_value();
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_identify_buffer_page->paddr().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 == ATA::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;
// Just in case we have a pending interrupt.
m_interrupt_enable.clear();
m_interrupt_status.clear();
full_memory_barrier();
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Marking command header at index {} as ready to identify device", representative_port_index(), unused_command_header.value());
m_port_registers.ci = 1 << unused_command_header.value();
full_memory_barrier();
size_t time_elapsed = 0;
bool success = false;
while (1) {
// Note: We allow it to spin for 256 milliseconds, which should be enough for a device to respond.
if (time_elapsed >= 256) {
break;
}
if (m_port_registers.serr != 0) {
dbgln("AHCI Port {}: Identify failed, SError {:#08x}", representative_port_index(), (u32)m_port_registers.serr);
try_disambiguate_sata_error();
break;
}
if (!(m_port_registers.ci & (1 << unused_command_header.value()))) {
success = true;
break;
}
microseconds_delay(1000); // delay with 1 milliseconds
time_elapsed++;
}
// Note: We probably ended up triggering an interrupt but we don't really want to handle it,
// so just get rid of it.
// FIXME: Do that in a better way so we don't need to actually remember this every time
// we need to do this.
m_interrupt_status.clear();
m_interrupt_enable.set_all();
return success;
}
void AHCIPort::wait_until_condition_met_or_timeout(size_t delay_in_microseconds, size_t retries, Function<bool(void)> condition_being_met) const
{
size_t retry = 0;
while (retry < retries) {
if (condition_being_met())
break;
microseconds_delay(delay_in_microseconds);
retry++;
}
}
bool AHCIPort::shutdown()
{
MutexLocker locker(m_lock);
SpinlockLocker lock(m_hard_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(m_hard_lock.is_locked());
VERIFY(is_operable());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Starting command list processing.", representative_port_index());
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(m_hard_lock.is_locked());
VERIFY(is_operable());
VERIFY(!m_wait_for_completion);
m_wait_for_completion = true;
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Marking command header at index {} as ready to process.", representative_port_index(), command_header_index);
m_port_registers.ci = 1 << command_header_index;
}
void AHCIPort::stop_command_list_processing() const
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Stopping command list processing.", representative_port_index());
m_port_registers.cmd = m_port_registers.cmd & 0xfffffffe;
}
void AHCIPort::start_fis_receiving() const
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Starting FIS receiving.", representative_port_index());
m_port_registers.cmd = m_port_registers.cmd | (1 << 4);
}
void AHCIPort::power_on() const
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Power on. Cold presence detection? {}", representative_port_index(), (bool)(m_port_registers.cmd & (1 << 20)));
if (!(m_port_registers.cmd & (1 << 20)))
return;
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Powering on device.", representative_port_index());
m_port_registers.cmd = m_port_registers.cmd | (1 << 2);
}
void AHCIPort::spin_up() const
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Spin up. Staggered spin up? {}", representative_port_index(), m_hba_capabilities.staggered_spin_up_supported);
if (!m_hba_capabilities.staggered_spin_up_supported)
return;
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Spinning up device.", representative_port_index());
m_port_registers.cmd = m_port_registers.cmd | (1 << 1);
}
void AHCIPort::stop_fis_receiving() const
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Stopping FIS receiving.", representative_port_index());
m_port_registers.cmd = m_port_registers.cmd & 0xFFFFFFEF;
}
bool AHCIPort::initiate_sata_reset()
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
dbgln_if(AHCI_DEBUG, "AHCI Port {}: Initiate SATA reset", representative_port_index());
stop_command_list_processing();
full_memory_barrier();
// Note: The AHCI specification says to wait now a 500 milliseconds
// Try to wait 1 second for HBA to clear Command List Running
wait_until_condition_met_or_timeout(100, 5000, [this]() -> bool {
return !(m_port_registers.cmd & (1 << 15));
});
full_memory_barrier();
spin_up();
full_memory_barrier();
set_interface_state(AHCI::DeviceDetectionInitialization::PerformInterfaceInitializationSequence);
// The AHCI specification says to wait now a 1 millisecond
microseconds_delay(1000);
full_memory_barrier();
set_interface_state(AHCI::DeviceDetectionInitialization::NoActionRequested);
full_memory_barrier();
wait_until_condition_met_or_timeout(10, 1000, [this]() -> bool {
return is_phy_enabled();
});
dmesgln("AHCI Port {}: {}", representative_port_index(), try_disambiguate_sata_status());
full_memory_barrier();
clear_sata_error_register();
return (m_port_registers.ssts & 0xf) == 3;
}
void AHCIPort::set_interface_state(AHCI::DeviceDetectionInitialization requested_action)
{
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();
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) 2021-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/RefPtr.h>
#include <Kernel/Devices/Device.h>
#include <Kernel/Devices/Storage/ATA/AHCI/Definitions.h>
#include <Kernel/Devices/Storage/ATA/AHCI/InterruptHandler.h>
#include <Kernel/Devices/Storage/ATA/ATADevice.h>
#include <Kernel/Devices/Storage/ATA/Definitions.h>
#include <Kernel/Interrupts/IRQHandler.h>
#include <Kernel/Library/LockWeakPtr.h>
#include <Kernel/Library/LockWeakable.h>
#include <Kernel/Locking/Mutex.h>
#include <Kernel/Locking/Spinlock.h>
#include <Kernel/Memory/AnonymousVMObject.h>
#include <Kernel/Memory/PhysicalPage.h>
#include <Kernel/Memory/ScatterGatherList.h>
#include <Kernel/PhysicalAddress.h>
#include <Kernel/Random.h>
#include <Kernel/Sections.h>
#include <Kernel/WaitQueue.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class AHCIInterruptHandler;
class AHCIPort
: public AtomicRefCounted<AHCIPort>
, public LockWeakable<AHCIPort> {
friend class AHCIController;
public:
static ErrorOr<NonnullLockRefPtr<AHCIPort>> create(AHCIController const&, AHCI::HBADefinedCapabilities, 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_atapi_attached() const { return m_port_registers.sig == (u32)ATA::DeviceSignature::ATAPI; };
LockRefPtr<StorageDevice> connected_device() const { return m_connected_device; }
bool reset();
bool initialize_without_reset();
void handle_interrupt();
private:
ErrorOr<void> allocate_resources_and_initialize_ports();
bool is_phy_enabled() const { return (m_port_registers.ssts & 0xf) == 3; }
bool initialize();
AHCIPort(AHCIController const&, NonnullRefPtr<Memory::PhysicalPage> identify_buffer_page, AHCI::HBADefinedCapabilities, volatile AHCI::PortRegisters&, u32 port_index);
ALWAYS_INLINE void clear_sata_error_register() const;
char const* try_disambiguate_sata_status();
void try_disambiguate_sata_error();
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);
size_t calculate_descriptors_count(size_t block_count) const;
[[nodiscard]] Optional<AsyncDeviceRequest::RequestResult> prepare_and_set_scatter_list(AsyncBlockDeviceRequest& request);
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; };
ALWAYS_INLINE void wait_until_condition_met_or_timeout(size_t delay_in_microseconds, size_t retries, Function<bool(void)> condition_being_met) const;
// Data members
EntropySource m_entropy_source;
LockRefPtr<AsyncBlockDeviceRequest> m_current_request;
Spinlock<LockRank::None> m_hard_lock {};
Mutex m_lock { "AHCIPort"sv };
mutable bool m_wait_for_completion { false };
Vector<NonnullRefPtr<Memory::PhysicalPage>> m_dma_buffers;
Vector<NonnullRefPtr<Memory::PhysicalPage>> m_command_table_pages;
RefPtr<Memory::PhysicalPage> m_command_list_page;
OwnPtr<Memory::Region> m_command_list_region;
RefPtr<Memory::PhysicalPage> m_fis_receive_page;
LockRefPtr<ATADevice> m_connected_device;
u32 m_port_index;
// Note: Ideally the AHCIController should be the only object to hold this data
// but because using the m_parent_controller means we need to take a strong ref,
// it's probably better to just "cache" this here instead.
AHCI::HBADefinedCapabilities const m_hba_capabilities;
NonnullRefPtr<Memory::PhysicalPage> const m_identify_buffer_page;
volatile AHCI::PortRegisters& m_port_registers;
LockWeakPtr<AHCIController> m_parent_controller;
AHCI::PortInterruptStatusBitField m_interrupt_status;
AHCI::PortInterruptEnableBitField m_interrupt_enable;
LockRefPtr<Memory::ScatterGatherList> m_current_scatter_list;
bool m_disabled_by_firmware { false };
};
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Devices/Storage/ATA/ATAController.h>
#include <Kernel/Devices/Storage/StorageManagement.h>
namespace Kernel {
ATAController::ATAController()
: StorageController(StorageManagement::generate_relative_ata_controller_id({}))
{
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/Types.h>
#include <Kernel/Devices/BlockDevice.h>
#include <Kernel/Devices/Storage/StorageController.h>
#include <Kernel/Library/LockRefPtr.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class ATADevice;
class ATAController
: public StorageController
, public LockWeakable<ATAController> {
public:
virtual void start_request(ATADevice const&, AsyncBlockDeviceRequest&) = 0;
protected:
ATAController();
};
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringView.h>
#include <Kernel/Devices/Storage/ATA/ATADevice.h>
#include <Kernel/Devices/Storage/StorageManagement.h>
#include <Kernel/Sections.h>
namespace Kernel {
static StorageDevice::LUNAddress convert_ata_address_to_lun_address(ATAController const& controller, ATADevice::Address ata_address)
{
return StorageDevice::LUNAddress { controller.controller_id(), ata_address.port, ata_address.subport };
}
ATADevice::ATADevice(ATAController const& controller, ATADevice::Address ata_address, u16 capabilities, u16 logical_sector_size, u64 max_addressable_block)
: StorageDevice(convert_ata_address_to_lun_address(controller, ata_address), controller.hardware_relative_controller_id(), logical_sector_size, max_addressable_block)
, m_controller(controller)
, m_ata_address(ata_address)
, m_capabilities(capabilities)
{
}
ATADevice::~ATADevice() = default;
void ATADevice::start_request(AsyncBlockDeviceRequest& request)
{
auto controller = m_controller.strong_ref();
VERIFY(controller);
controller->start_request(*this, request);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/Storage/ATA/ATAController.h>
#include <Kernel/Devices/Storage/StorageDevice.h>
#include <Kernel/Interrupts/IRQHandler.h>
#include <Kernel/Locking/Mutex.h>
namespace Kernel {
class ATADevice : public StorageDevice {
public:
// Note: For IDE drives, port means Primary or Secondary (0 or 1),
// and subport means Master or Slave (0 or 1).
// For SATA drives (AHCI driven HBAs), a port can be a number from 0 to 31,
// and subport can be a number from 0 to 14 (only 15 devices are allowed to
// be connected to one SATA port multiplier).
struct Address {
// FIXME: u32 for this value is wasteful, because even AHCI only support 32 ports
u32 port;
u8 subport;
};
public:
virtual ~ATADevice() override;
// ^BlockDevice
virtual void start_request(AsyncBlockDeviceRequest&) override;
u16 ata_capabilites() const { return m_capabilities; }
Address const& ata_address() const { return m_ata_address; }
protected:
ATADevice(ATAController const&, Address, u16, u16, u64);
LockWeakPtr<ATAController> m_controller;
const Address m_ata_address;
const u16 m_capabilities;
};
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringView.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/Storage/ATA/ATADiskDevice.h>
#include <Kernel/Devices/Storage/StorageManagement.h>
#include <Kernel/Sections.h>
namespace Kernel {
NonnullLockRefPtr<ATADiskDevice> ATADiskDevice::create(ATAController const& controller, ATADevice::Address ata_address, u16 capabilities, u16 logical_sector_size, u64 max_addressable_block)
{
auto disk_device_or_error = DeviceManagement::try_create_device<ATADiskDevice>(controller, ata_address, capabilities, logical_sector_size, max_addressable_block);
// FIXME: Find a way to propagate errors
VERIFY(!disk_device_or_error.is_error());
return disk_device_or_error.release_value();
}
ATADiskDevice::ATADiskDevice(ATAController const& controller, ATADevice::Address ata_address, u16 capabilities, u16 logical_sector_size, u64 max_addressable_block)
: ATADevice(controller, ata_address, capabilities, logical_sector_size, max_addressable_block)
{
}
ATADiskDevice::~ATADiskDevice() = default;
StringView ATADiskDevice::class_name() const
{
return "ATADiskDevice"sv;
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/Storage/ATA/ATADevice.h>
#include <Kernel/Interrupts/IRQHandler.h>
#include <Kernel/Locking/Mutex.h>
namespace Kernel {
class IDEController;
class ATADiskDevice final : public ATADevice {
friend class IDEController;
friend class DeviceManagement;
public:
static NonnullLockRefPtr<ATADiskDevice> create(ATAController const&, ATADevice::Address, u16 capabilities, u16 logical_sector_size, u64 max_addressable_block);
virtual ~ATADiskDevice() override;
// ^StorageDevice
virtual CommandSet command_set() const override { return CommandSet::ATA; }
private:
ATADiskDevice(ATAController const&, Address, u16, u16, u64);
// ^DiskDevice
virtual StringView class_name() const override;
};
}

View file

@ -0,0 +1,520 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Arch/CPU.h>
#include <Kernel/Arch/Delay.h>
#include <Kernel/Devices/Storage/ATA/ATADiskDevice.h>
#include <Kernel/Devices/Storage/ATA/ATAPort.h>
#include <Kernel/Devices/Storage/ATA/Definitions.h>
#include <Kernel/WorkQueue.h>
namespace Kernel {
class ATAPortInterruptDisabler {
public:
ATAPortInterruptDisabler(ATAPort& port)
: m_port(port)
{
(void)port.disable_interrupts();
}
~ATAPortInterruptDisabler()
{
(void)m_port->enable_interrupts();
};
private:
LockRefPtr<ATAPort> m_port;
};
class ATAPortInterruptCleaner {
public:
ATAPortInterruptCleaner(ATAPort& port)
: m_port(port)
{
}
~ATAPortInterruptCleaner()
{
(void)m_port->force_clear_interrupts();
};
private:
LockRefPtr<ATAPort> m_port;
};
void ATAPort::fix_name_string_in_identify_device_block()
{
VERIFY(m_lock.is_locked());
auto* wbuf = (u16*)m_ata_identify_data_buffer->data();
auto* bbuf = m_ata_identify_data_buffer->data() + 27 * 2;
for (size_t word_index = 27; word_index < 47; word_index++) {
u16 data = wbuf[word_index];
*(bbuf++) = MSB(data);
*(bbuf++) = LSB(data);
}
}
ErrorOr<void> ATAPort::detect_connected_devices()
{
MutexLocker locker(m_lock);
for (size_t device_index = 0; device_index < max_possible_devices_connected(); device_index++) {
TRY(device_select(device_index));
auto device_presence = TRY(detect_presence_on_selected_device());
if (!device_presence)
continue;
TaskFile identify_taskfile;
memset(&identify_taskfile, 0, sizeof(TaskFile));
identify_taskfile.command = ATA_CMD_IDENTIFY;
auto buffer = UserOrKernelBuffer::for_kernel_buffer(m_ata_identify_data_buffer->data());
{
auto result = execute_polled_command(TransactionDirection::Read, LBAMode::None, identify_taskfile, buffer, 0, 256, 100, 100);
if (result.is_error()) {
continue;
}
}
ATAIdentifyBlock volatile& identify_block = (ATAIdentifyBlock volatile&)(*m_ata_identify_data_buffer->data());
u16 capabilities = identify_block.capabilities[0];
StringView device_name = StringView((char const*)const_cast<u16*>(identify_block.model_number), 40);
fix_name_string_in_identify_device_block();
u64 max_addressable_block = identify_block.max_28_bit_addressable_logical_sector;
dbgln("ATAPort: device found: Name={}, Capacity={}, Capabilities={:#04x}", device_name.trim_whitespace(), max_addressable_block * 512, capabilities);
// If the drive is so old that it doesn't support LBA, ignore it.
if (!(capabilities & ATA_CAP_LBA)) {
dbgln("ATAPort: device found but without LBA support (what kind of dinosaur we see here?)");
continue;
}
// if we support 48-bit LBA, use that value instead.
if (identify_block.commands_and_feature_sets_supported[1] & (1 << 10))
max_addressable_block = identify_block.user_addressable_logical_sectors_count;
// FIXME: Don't assume all drives will have logical sector size of 512 bytes.
ATADevice::Address address = { m_port_index, static_cast<u8>(device_index) };
m_ata_devices.append(ATADiskDevice::create(m_parent_ata_controller, address, capabilities, 512, max_addressable_block));
}
return {};
}
LockRefPtr<StorageDevice> ATAPort::connected_device(size_t device_index) const
{
MutexLocker locker(m_lock);
if (m_ata_devices.size() > device_index)
return m_ata_devices[device_index];
return {};
}
ErrorOr<void> ATAPort::start_request(ATADevice const& associated_device, AsyncBlockDeviceRequest& request)
{
MutexLocker locker(m_lock);
VERIFY(m_current_request.is_null());
VERIFY(pio_capable() || dma_capable());
dbgln_if(ATA_DEBUG, "ATAPort::start_request");
m_current_request = request;
m_current_request_block_index = 0;
m_current_request_flushing_cache = false;
if (dma_capable()) {
TRY(prepare_and_initiate_dma_transaction(associated_device));
return {};
}
TRY(prepare_and_initiate_pio_transaction(associated_device));
return {};
}
void ATAPort::complete_pio_transaction(AsyncDeviceRequest::RequestResult result)
{
VERIFY(m_current_request);
// Now schedule reading back the buffer as soon as we leave the irq handler.
// This is important so that we can safely write the buffer back,
// which could cause page faults. Note that this may be called immediately
// before Processor::deferred_call_queue returns!
auto work_item_creation_result = g_io_work->try_queue([this, result]() {
dbgln_if(ATA_DEBUG, "ATAPort::complete_pio_transaction result: {}", (int)result);
MutexLocker locker(m_lock);
VERIFY(m_current_request);
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(result);
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
}
void ATAPort::complete_dma_transaction(AsyncDeviceRequest::RequestResult result)
{
// NOTE: this may be called from the interrupt handler!
VERIFY(m_current_request);
VERIFY(m_lock.is_locked());
// Now schedule reading back the buffer as soon as we leave the irq handler.
// This is important so that we can safely write the buffer back,
// which could cause page faults. Note that this may be called immediately
// before Processor::deferred_call_queue returns!
auto work_item_creation_result = g_io_work->try_queue([this, result]() {
dbgln_if(ATA_DEBUG, "ATAPort::complete_dma_transaction result: {}", (int)result);
MutexLocker locker(m_lock);
if (!m_current_request)
return;
auto current_request = m_current_request;
m_current_request.clear();
if (result == AsyncDeviceRequest::Success) {
{
auto result = force_busmastering_status_clean();
if (result.is_error()) {
locker.unlock();
current_request->complete(AsyncDeviceRequest::Failure);
return;
}
}
if (current_request->request_type() == AsyncBlockDeviceRequest::Read) {
if (auto result = current_request->write_to_buffer(current_request->buffer(), m_dma_buffer_region->vaddr().as_ptr(), 512 * current_request->block_count()); result.is_error()) {
locker.unlock();
current_request->complete(AsyncDeviceRequest::MemoryFault);
return;
}
}
}
locker.unlock();
current_request->complete(result);
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
}
}
static void print_ata_status(u8 status)
{
dbgln("ATAPort: print_status: DRQ={} BSY={}, DRDY={}, DSC={}, DF={}, CORR={}, IDX={}, ERR={}",
(status & ATA_SR_DRQ) != 0,
(status & ATA_SR_BSY) != 0,
(status & ATA_SR_DRDY) != 0,
(status & ATA_SR_DSC) != 0,
(status & ATA_SR_DF) != 0,
(status & ATA_SR_CORR) != 0,
(status & ATA_SR_IDX) != 0,
(status & ATA_SR_ERR) != 0);
}
static void try_disambiguate_ata_error(u8 error)
{
dbgln("ATAPort: Error cause:");
switch (error) {
case ATA_ER_BBK:
dbgln("ATAPort: - Bad block");
break;
case ATA_ER_UNC:
dbgln("ATAPort: - Uncorrectable data");
break;
case ATA_ER_MC:
dbgln("ATAPort: - Media changed");
break;
case ATA_ER_IDNF:
dbgln("ATAPort: - ID mark not found");
break;
case ATA_ER_MCR:
dbgln("ATAPort: - Media change request");
break;
case ATA_ER_ABRT:
dbgln("ATAPort: - Command aborted");
break;
case ATA_ER_TK0NF:
dbgln("ATAPort: - Track 0 not found");
break;
case ATA_ER_AMNF:
dbgln("ATAPort: - No address mark");
break;
default:
dbgln("ATAPort: - No one knows");
break;
}
}
ErrorOr<bool> ATAPort::handle_interrupt_after_dma_transaction()
{
if (!dma_capable())
return false;
u8 bstatus = TRY(busmastering_status());
if (!(bstatus & 0x4)) {
// interrupt not from this device, ignore
dbgln_if(ATA_DEBUG, "ATAPort: ignore interrupt");
return false;
}
auto work_item_creation_result = g_ata_work->try_queue([this]() -> void {
MutexLocker locker(m_lock);
u8 status = task_file_status().release_value();
m_entropy_source.add_random_event(status);
// clear bus master interrupt status
{
auto result = force_busmastering_status_clean();
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
SpinlockLocker lock(m_hard_lock);
dbgln_if(ATA_DEBUG, "ATAPort: interrupt: DRQ={}, BSY={}, DRDY={}",
(status & ATA_SR_DRQ) != 0,
(status & ATA_SR_BSY) != 0,
(status & ATA_SR_DRDY) != 0);
if (!m_current_request) {
dbgln("ATAPort: IRQ but no pending request!");
return;
}
if (status & ATA_SR_ERR) {
print_ata_status(status);
auto device_error = task_file_error().release_value();
dbgln("ATAPort: Error {:#02x}!", (u8)device_error);
try_disambiguate_ata_error(device_error);
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
complete_dma_transaction(AsyncDeviceRequest::Success);
return;
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
return Error::from_errno(ENOMEM);
}
return true;
}
ErrorOr<void> ATAPort::prepare_and_initiate_dma_transaction(ATADevice const& associated_device)
{
VERIFY(m_lock.is_locked());
VERIFY(!m_current_request.is_null());
VERIFY(m_current_request->block_count() <= 256);
// Note: We might be called here from an interrupt handler (like the page fault handler), so queue a read afterwards.
auto work_item_creation_result = g_ata_work->try_queue([this, &associated_device]() -> void {
MutexLocker locker(m_lock);
dbgln_if(ATA_DEBUG, "ATAPort::prepare_and_initiate_dma_transaction ({} x {})", m_current_request->block_index(), m_current_request->block_count());
VERIFY(!m_current_request.is_null());
VERIFY(m_current_request->block_count() <= 256);
{
auto result = device_select(associated_device.ata_address().subport);
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
if (m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write) {
if (auto result = m_current_request->read_from_buffer(m_current_request->buffer(), m_dma_buffer_region->vaddr().as_ptr(), 512 * m_current_request->block_count()); result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::MemoryFault);
return;
}
}
prdt().offset = m_dma_buffer_page->paddr().get();
prdt().size = 512 * m_current_request->block_count();
VERIFY(prdt().size <= PAGE_SIZE);
SpinlockLocker hard_lock_locker(m_hard_lock);
{
auto result = stop_busmastering();
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
if (m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write) {
auto result = prepare_transaction_with_busmastering(TransactionDirection::Write, m_prdt_page->paddr());
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
} else {
auto result = prepare_transaction_with_busmastering(TransactionDirection::Read, m_prdt_page->paddr());
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
TaskFile taskfile;
LBAMode lba_mode = LBAMode::TwentyEightBit;
auto lba = m_current_request->block_index();
if ((lba + m_current_request->block_count()) >= 0x10000000) {
lba_mode = LBAMode::FortyEightBit;
}
memset(&taskfile, 0, sizeof(TaskFile));
taskfile.lba_low[0] = (lba & 0x000000FF) >> 0;
taskfile.lba_low[1] = (lba & 0x0000FF00) >> 8;
taskfile.lba_low[2] = (lba & 0x00FF0000) >> 16;
taskfile.lba_high[0] = (lba & 0xFF000000) >> 24;
taskfile.lba_high[1] = (lba & 0xFF00000000ull) >> 32;
taskfile.lba_high[2] = (lba & 0xFF0000000000ull) >> 40;
taskfile.count = m_current_request->block_count();
if (lba_mode == LBAMode::TwentyEightBit)
taskfile.command = m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write ? ATA_CMD_WRITE_DMA : ATA_CMD_READ_DMA;
else
taskfile.command = m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write ? ATA_CMD_WRITE_DMA_EXT : ATA_CMD_READ_DMA_EXT;
{
auto result = load_taskfile_into_registers(taskfile, lba_mode, 1000);
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
if (m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write) {
auto result = start_busmastering(TransactionDirection::Write);
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
else {
auto result = start_busmastering(TransactionDirection::Read);
if (result.is_error()) {
complete_dma_transaction(AsyncDeviceRequest::Failure);
return;
}
}
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
return Error::from_errno(ENOMEM);
}
return {};
}
ErrorOr<void> ATAPort::prepare_and_initiate_pio_transaction(ATADevice const& associated_device)
{
VERIFY(m_lock.is_locked());
VERIFY(!m_current_request.is_null());
VERIFY(m_current_request->block_count() <= 256);
dbgln_if(ATA_DEBUG, "ATAPort::prepare_and_initiate_pio_transaction ({} x {})", m_current_request->block_index(), m_current_request->block_count());
// Note: We might be called here from an interrupt handler (like the page fault handler), so queue a read afterwards.
auto work_item_creation_result = g_ata_work->try_queue([this, &associated_device]() -> void {
MutexLocker locker(m_lock);
{
auto result = device_select(associated_device.ata_address().subport);
if (result.is_error()) {
complete_pio_transaction(AsyncDeviceRequest::Failure);
return;
}
}
for (size_t block_index = 0; block_index < m_current_request->block_count(); block_index++) {
TaskFile taskfile;
LBAMode lba_mode = LBAMode::TwentyEightBit;
auto lba = m_current_request->block_index() + block_index;
if (lba >= 0x10000000) {
lba_mode = LBAMode::FortyEightBit;
}
memset(&taskfile, 0, sizeof(TaskFile));
taskfile.lba_low[0] = (lba & 0x000000FF) >> 0;
taskfile.lba_low[1] = (lba & 0x0000FF00) >> 8;
taskfile.lba_low[2] = (lba & 0x00FF0000) >> 16;
taskfile.lba_high[0] = (lba & 0xFF000000) >> 24;
taskfile.lba_high[1] = (lba & 0xFF00000000ull) >> 32;
taskfile.lba_high[2] = (lba & 0xFF0000000000ull) >> 40;
taskfile.count = 1;
if (lba_mode == LBAMode::TwentyEightBit)
taskfile.command = m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write ? ATA_CMD_WRITE_PIO : ATA_CMD_READ_PIO;
else
taskfile.command = m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Write ? ATA_CMD_WRITE_PIO_EXT : ATA_CMD_READ_PIO_EXT;
if (m_current_request->request_type() == AsyncBlockDeviceRequest::RequestType::Read) {
auto result = execute_polled_command(TransactionDirection::Read, lba_mode, taskfile, m_current_request->buffer(), block_index, 256, 100, 100);
if (result.is_error()) {
complete_pio_transaction(AsyncDeviceRequest::Failure);
return;
}
} else {
auto result = execute_polled_command(TransactionDirection::Write, lba_mode, taskfile, m_current_request->buffer(), block_index, 256, 100, 100);
if (result.is_error()) {
complete_pio_transaction(AsyncDeviceRequest::Failure);
return;
}
}
}
complete_pio_transaction(AsyncDeviceRequest::Success);
});
if (work_item_creation_result.is_error()) {
auto current_request = m_current_request;
m_current_request.clear();
current_request->complete(AsyncDeviceRequest::OutOfMemory);
return Error::from_errno(ENOMEM);
}
return {};
}
ErrorOr<void> ATAPort::execute_polled_command(TransactionDirection direction, LBAMode lba_mode, TaskFile const& taskfile, UserOrKernelBuffer& buffer, size_t block_offset, size_t words_count, size_t preparation_timeout_in_milliseconds, size_t completion_timeout_in_milliseconds)
{
// Disable interrupts temporarily, just in case we have that enabled,
// remember the value to re-enable (and clean) later if needed.
ATAPortInterruptDisabler disabler(*this);
ATAPortInterruptCleaner cleaner(*this);
MutexLocker locker(m_lock);
{
SpinlockLocker hard_locker(m_hard_lock);
// Wait for device to be not busy or timeout
TRY(wait_if_busy_until_timeout(preparation_timeout_in_milliseconds));
// Send command, wait for result or timeout
TRY(load_taskfile_into_registers(taskfile, lba_mode, preparation_timeout_in_milliseconds));
size_t milliseconds_elapsed = 0;
for (;;) {
if (milliseconds_elapsed > completion_timeout_in_milliseconds)
break;
u8 status = task_file_status().release_value();
if (status & ATA_SR_ERR) {
return Error::from_errno(EINVAL);
}
if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) {
break;
}
microseconds_delay(1000);
milliseconds_elapsed++;
}
if (milliseconds_elapsed > completion_timeout_in_milliseconds) {
critical_dmesgln("ATAPort: device state unknown. Timeout exceeded.");
return Error::from_errno(EINVAL);
}
}
VERIFY_INTERRUPTS_ENABLED();
if (direction == TransactionDirection::Read)
TRY(read_pio_data_to_buffer(buffer, block_offset, words_count));
else
TRY(write_pio_data_from_buffer(buffer, block_offset, words_count));
return {};
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/Devices/Storage/ATA/ATADevice.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class ATAPort
: public AtomicRefCounted<ATAPort>
, public LockWeakable<ATAPort> {
friend class ATAPortInterruptDisabler;
friend class ATAPortInterruptCleaner;
public:
struct TaskFile {
u8 command;
u8 lba_low[3];
u8 device;
u8 lba_high[3];
u8 features_high;
u16 count;
u8 icc;
u8 control;
u32 reserved;
};
enum class TransactionDirection : u8 {
Read,
Write,
};
struct [[gnu::packed]] PhysicalRegionDescriptor {
u32 offset;
u16 size { 0 };
u16 end_of_table { 0 };
};
enum class LBAMode : u8 {
None,
TwentyEightBit,
FortyEightBit,
};
public:
LockRefPtr<StorageDevice> connected_device(size_t device_index) const;
virtual ~ATAPort() = default;
virtual ErrorOr<void> disable() = 0;
virtual ErrorOr<void> power_on() = 0;
ErrorOr<void> detect_connected_devices();
ErrorOr<bool> handle_interrupt_after_dma_transaction();
ErrorOr<void> start_request(ATADevice const& associated_device, AsyncBlockDeviceRequest&);
// Note: Generic (P)ATA IDE "ports" are tied to the IDE channel link (cable), and trying to
// reset the master port or slave port and vice versa requires to actually reset
// both at once...
// This is due to the fact that IDE devices can be connected together (master-slave)
// with one 80 pin cable which forms one (primary/secondary) "ATA bus".
// Intel AHCI controllers generally allow individual phy port reset. The caller
// of this method should know this in advance...
// Note: ATAPI devices are an exception to this, so even if we initiate a
// a port reset, there's no guarantee that ATAPI devices will reset anyway,
// so resetting them requires to actually send the ATA "DEVICE RESET" command.
virtual ErrorOr<void> port_phy_reset() = 0;
// Note: Software reset means individual reset to a selected device on the "bus" (port).
// This means that this will likely work for devices that indicate support for
// PACKET commands (ATAPI devices) that also support DEVICE RESET. For other devices
// there's no other method to reset them besides (full) PHY reset.
// For devices that don't support this feature, just return ENOTSUP.
virtual ErrorOr<void> soft_reset() { return Error::from_errno(ENOTSUP); }
ErrorOr<void> execute_polled_command(TransactionDirection direction, LBAMode lba_mode, TaskFile const& taskfile, UserOrKernelBuffer&, size_t block_offset, size_t words_count, size_t preparation_timeout_in_milliseconds, size_t completion_timeout_in_milliseconds);
virtual bool has_sata_capabilities() { return false; }
virtual bool pio_capable() const = 0;
virtual bool dma_capable() const = 0;
virtual size_t max_possible_devices_connected() const = 0;
private:
ErrorOr<void> prepare_and_initiate_dma_transaction(ATADevice const& associated_device);
ErrorOr<void> prepare_and_initiate_pio_transaction(ATADevice const& associated_device);
void complete_dma_transaction(AsyncDeviceRequest::RequestResult result);
void complete_pio_transaction(AsyncDeviceRequest::RequestResult result);
void fix_name_string_in_identify_device_block();
protected:
virtual ErrorOr<u8> task_file_status() = 0;
virtual ErrorOr<u8> task_file_error() = 0;
virtual ErrorOr<void> wait_if_busy_until_timeout(size_t timeout_in_milliseconds) = 0;
virtual ErrorOr<void> device_select(size_t device_index) = 0;
virtual ErrorOr<bool> detect_presence_on_selected_device() = 0;
virtual ErrorOr<void> enable_interrupts() = 0;
virtual ErrorOr<void> disable_interrupts() = 0;
virtual ErrorOr<void> stop_busmastering() = 0;
virtual ErrorOr<void> start_busmastering(TransactionDirection) = 0;
virtual ErrorOr<void> force_busmastering_status_clean() = 0;
virtual ErrorOr<u8> busmastering_status() = 0;
virtual ErrorOr<void> prepare_transaction_with_busmastering(TransactionDirection, PhysicalAddress prdt_buffer) = 0;
virtual ErrorOr<void> initiate_transaction(TransactionDirection) = 0;
virtual ErrorOr<void> force_clear_interrupts() = 0;
// Note: This method assume we already selected the correct device!
virtual ErrorOr<void> load_taskfile_into_registers(TaskFile const&, LBAMode lba_mode, size_t completion_timeout_in_milliseconds) = 0;
virtual ErrorOr<void> read_pio_data_to_buffer(UserOrKernelBuffer&, size_t block_offset, size_t words_count) = 0;
virtual ErrorOr<void> write_pio_data_from_buffer(UserOrKernelBuffer const&, size_t block_offset, size_t words_count) = 0;
PhysicalRegionDescriptor& prdt() { return *reinterpret_cast<PhysicalRegionDescriptor*>(m_prdt_region->vaddr().as_ptr()); }
ATAPort(ATAController const& parent_controller, u8 port_index, NonnullOwnPtr<KBuffer> ata_identify_data_buffer)
: m_port_index(port_index)
, m_ata_identify_data_buffer(move(ata_identify_data_buffer))
, m_parent_ata_controller(parent_controller)
{
}
mutable Mutex m_lock;
Spinlock<LockRank::None> m_hard_lock {};
EntropySource m_entropy_source;
LockRefPtr<AsyncBlockDeviceRequest> m_current_request;
u64 m_current_request_block_index { 0 };
bool m_current_request_flushing_cache { false };
OwnPtr<Memory::Region> m_prdt_region;
OwnPtr<Memory::Region> m_dma_buffer_region;
RefPtr<Memory::PhysicalPage> m_prdt_page;
RefPtr<Memory::PhysicalPage> m_dma_buffer_page;
const u8 m_port_index;
Vector<NonnullLockRefPtr<ATADevice>> m_ata_devices;
NonnullOwnPtr<KBuffer> m_ata_identify_data_buffer;
NonnullLockRefPtr<ATAController> m_parent_ata_controller;
};
}

View file

@ -0,0 +1,212 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
namespace Kernel::ATA {
enum DeviceSignature : u32 {
ATA = 0x00000101,
ATAPI = 0xEB140101,
EnclosureManagementBridge = 0xC33C0101,
PortMultiplier = 0x96690101,
Unconnected = 0xFFFFFFFF
};
}
#define ATA_SR_BSY 0x80
#define ATA_SR_DRDY 0x40
#define ATA_SR_DF 0x20
#define ATA_SR_DSC 0x10
#define ATA_SR_DRQ 0x08
#define ATA_SR_CORR 0x04
#define ATA_SR_IDX 0x02
#define ATA_SR_ERR 0x01
#define ATA_ER_BBK 0x80
#define ATA_ER_UNC 0x40
#define ATA_ER_MC 0x20
#define ATA_ER_IDNF 0x10
#define ATA_ER_MCR 0x08
#define ATA_ER_ABRT 0x04
#define ATA_ER_TK0NF 0x02
#define ATA_ER_AMNF 0x01
#define ATA_CMD_READ_PIO 0x20
#define ATA_CMD_READ_PIO_EXT 0x24
#define ATA_CMD_READ_DMA 0xC8
#define ATA_CMD_READ_DMA_EXT 0x25
#define ATA_CMD_WRITE_PIO 0x30
#define ATA_CMD_WRITE_PIO_EXT 0x34
#define ATA_CMD_WRITE_DMA 0xCA
#define ATA_CMD_WRITE_DMA_EXT 0x35
#define ATA_CMD_CACHE_FLUSH 0xE7
#define ATA_CMD_CACHE_FLUSH_EXT 0xEA
#define ATA_CMD_PACKET 0xA0
#define ATA_CMD_IDENTIFY_PACKET 0xA1
#define ATA_CMD_IDENTIFY 0xEC
#define ATAPI_CMD_READ 0xA8
#define ATAPI_CMD_EJECT 0x1B
#define ATA_IDENT_DEVICETYPE 0
#define ATA_IDENT_CYLINDERS 2
#define ATA_IDENT_HEADS 6
#define ATA_IDENT_SECTORS 12
#define ATA_IDENT_SERIAL 20
#define ATA_IDENT_MODEL 54
#define ATA_IDENT_CAPABILITIES 98
#define ATA_IDENT_FIELDVALID 106
#define ATA_IDENT_MAX_LBA 120
#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
#define ATA_REG_DATA 0x00
#define ATA_REG_ERROR 0x01
#define ATA_REG_FEATURES 0x01
#define ATA_REG_SECCOUNT0 0x02
#define ATA_REG_LBA0 0x03
#define ATA_REG_LBA1 0x04
#define ATA_REG_LBA2 0x05
#define ATA_REG_HDDEVSEL 0x06
#define ATA_REG_COMMAND 0x07
#define ATA_REG_STATUS 0x07
#define ATA_REG_SECCOUNT1 0x08
#define ATA_REG_LBA3 0x09
#define ATA_REG_LBA4 0x0A
#define ATA_REG_LBA5 0x0B
#define ATA_CTL_CONTROL 0x00
#define ATA_CTL_ALTSTATUS 0x00
#define ATA_CTL_DEVADDRESS 0x01
#define ATA_CAP_LBA 0x200
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 capabilities[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;
};
};

View file

@ -0,0 +1,335 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteBuffer.h>
#include <AK/Singleton.h>
#include <AK/StringView.h>
#include <Kernel/Arch/Delay.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Devices/Storage/ATA/ATADiskDevice.h>
#include <Kernel/Devices/Storage/ATA/Definitions.h>
#include <Kernel/Devices/Storage/ATA/GenericIDE/Channel.h>
#include <Kernel/Devices/Storage/ATA/GenericIDE/Controller.h>
#include <Kernel/IOWindow.h>
#include <Kernel/Memory/MemoryManager.h>
#include <Kernel/Process.h>
#include <Kernel/Sections.h>
#include <Kernel/WorkQueue.h>
namespace Kernel {
#define PATA_PRIMARY_IRQ 14
#define PATA_SECONDARY_IRQ 15
UNMAP_AFTER_INIT ErrorOr<NonnullRefPtr<IDEChannel>> IDEChannel::create(IDEController const& controller, IOWindowGroup io_window_group, ChannelType type)
{
auto ata_identify_data_buffer = KBuffer::try_create_with_size("ATA Identify Page"sv, 4096, Memory::Region::Access::ReadWrite, AllocationStrategy::AllocateNow).release_value();
return adopt_nonnull_ref_or_enomem(new (nothrow) IDEChannel(controller, move(io_window_group), type, move(ata_identify_data_buffer)));
}
UNMAP_AFTER_INIT ErrorOr<NonnullRefPtr<IDEChannel>> IDEChannel::create(IDEController const& controller, u8 irq, IOWindowGroup io_window_group, ChannelType type)
{
auto ata_identify_data_buffer = KBuffer::try_create_with_size("ATA Identify Page"sv, 4096, Memory::Region::Access::ReadWrite, AllocationStrategy::AllocateNow).release_value();
return adopt_nonnull_ref_or_enomem(new (nothrow) IDEChannel(controller, irq, move(io_window_group), type, move(ata_identify_data_buffer)));
}
StringView IDEChannel::channel_type_string() const
{
if (m_channel_type == ChannelType::Primary)
return "Primary"sv;
return "Secondary"sv;
}
bool IDEChannel::select_device_and_wait_until_not_busy(DeviceType device_type, size_t milliseconds_timeout)
{
microseconds_delay(20);
u8 slave = device_type == DeviceType::Slave;
m_io_window_group.io_window().write8(ATA_REG_HDDEVSEL, 0xA0 | (slave << 4)); // First, we need to select the drive itself
microseconds_delay(20);
size_t time_elapsed = 0;
while (m_io_window_group.control_window().read8(0) & ATA_SR_BSY && time_elapsed <= milliseconds_timeout) {
microseconds_delay(1000);
time_elapsed++;
}
return time_elapsed <= milliseconds_timeout;
}
ErrorOr<void> IDEChannel::port_phy_reset()
{
MutexLocker locker(m_lock);
SpinlockLocker hard_locker(m_hard_lock);
// reset the channel
u8 device_control = m_io_window_group.control_window().read8(0);
// Wait 30 milliseconds
microseconds_delay(30000);
m_io_window_group.control_window().write8(0, device_control | (1 << 2));
// Wait 30 milliseconds
microseconds_delay(30000);
m_io_window_group.control_window().write8(0, device_control);
// Wait up to 30 seconds before failing
if (!select_device_and_wait_until_not_busy(DeviceType::Master, 30000)) {
dbgln("IDEChannel: reset failed, busy flag on master stuck");
return Error::from_errno(EBUSY);
}
// Wait up to 30 seconds before failing
if (!select_device_and_wait_until_not_busy(DeviceType::Slave, 30000)) {
dbgln("IDEChannel: reset failed, busy flag on slave stuck");
return Error::from_errno(EBUSY);
}
return {};
}
#if ARCH(X86_64)
ErrorOr<void> IDEChannel::allocate_resources_for_pci_ide_controller(Badge<PCIIDELegacyModeController>, bool force_pio)
{
return allocate_resources(force_pio);
}
ErrorOr<void> IDEChannel::allocate_resources_for_isa_ide_controller(Badge<ISAIDEController>)
{
return allocate_resources(true);
}
#endif
UNMAP_AFTER_INIT ErrorOr<void> IDEChannel::allocate_resources(bool force_pio)
{
dbgln_if(PATA_DEBUG, "IDEChannel: {} IO base: {}", channel_type_string(), m_io_window_group.io_window());
dbgln_if(PATA_DEBUG, "IDEChannel: {} control base: {}", channel_type_string(), m_io_window_group.control_window());
if (m_io_window_group.bus_master_window())
dbgln_if(PATA_DEBUG, "IDEChannel: {} bus master base: {}", channel_type_string(), m_io_window_group.bus_master_window());
else
dbgln_if(PATA_DEBUG, "IDEChannel: {} bus master base disabled", channel_type_string());
if (!force_pio) {
m_dma_enabled = true;
VERIFY(m_io_window_group.bus_master_window());
// Let's try to set up DMA transfers.
m_prdt_region = TRY(MM.allocate_dma_buffer_page("IDE PRDT"sv, Memory::Region::Access::ReadWrite, m_prdt_page));
VERIFY(!m_prdt_page.is_null());
m_dma_buffer_region = TRY(MM.allocate_dma_buffer_page("IDE DMA region"sv, Memory::Region::Access::ReadWrite, m_dma_buffer_page));
VERIFY(!m_dma_buffer_page.is_null());
prdt().end_of_table = 0x8000;
// clear bus master interrupt status
m_io_window_group.bus_master_window()->write8(2, m_io_window_group.bus_master_window()->read8(2) | 4);
}
return {};
}
UNMAP_AFTER_INIT IDEChannel::IDEChannel(IDEController const& controller, u8 irq, IOWindowGroup io_group, ChannelType type, NonnullOwnPtr<KBuffer> ata_identify_data_buffer)
: ATAPort(controller, (type == ChannelType::Primary ? 0 : 1), move(ata_identify_data_buffer))
, IRQHandler(irq)
, m_channel_type(type)
, m_io_window_group(move(io_group))
{
}
UNMAP_AFTER_INIT IDEChannel::IDEChannel(IDEController const& controller, IOWindowGroup io_group, ChannelType type, NonnullOwnPtr<KBuffer> ata_identify_data_buffer)
: ATAPort(controller, (type == ChannelType::Primary ? 0 : 1), move(ata_identify_data_buffer))
, IRQHandler(type == ChannelType::Primary ? PATA_PRIMARY_IRQ : PATA_SECONDARY_IRQ)
, m_channel_type(type)
, m_io_window_group(move(io_group))
{
}
UNMAP_AFTER_INIT IDEChannel::~IDEChannel() = default;
bool IDEChannel::handle_irq(RegisterState const&)
{
auto result = handle_interrupt_after_dma_transaction();
// FIXME: Propagate errors properly
VERIFY(!result.is_error());
return result.release_value();
}
ErrorOr<void> IDEChannel::stop_busmastering()
{
VERIFY(m_lock.is_locked());
VERIFY(m_io_window_group.bus_master_window());
m_io_window_group.bus_master_window()->write8(0, 0);
return {};
}
ErrorOr<void> IDEChannel::start_busmastering(TransactionDirection direction)
{
VERIFY(m_lock.is_locked());
VERIFY(m_io_window_group.bus_master_window());
m_io_window_group.bus_master_window()->write8(0, (direction != TransactionDirection::Write ? 0x9 : 0x1));
return {};
}
ErrorOr<void> IDEChannel::force_busmastering_status_clean()
{
VERIFY(m_lock.is_locked());
VERIFY(m_io_window_group.bus_master_window());
m_io_window_group.bus_master_window()->write8(2, m_io_window_group.bus_master_window()->read8(2) | 4);
return {};
}
ErrorOr<u8> IDEChannel::busmastering_status()
{
VERIFY(m_io_window_group.bus_master_window());
return m_io_window_group.bus_master_window()->read8(2);
}
ErrorOr<void> IDEChannel::prepare_transaction_with_busmastering(TransactionDirection direction, PhysicalAddress prdt_buffer)
{
VERIFY(m_lock.is_locked());
m_io_window_group.bus_master_window()->write32(4, prdt_buffer.get());
m_io_window_group.bus_master_window()->write8(0, direction != TransactionDirection::Write ? 0x8 : 0);
// Turn on "Interrupt" and "Error" flag. The error flag should be cleared by hardware.
m_io_window_group.bus_master_window()->write8(2, m_io_window_group.bus_master_window()->read8(2) | 0x6);
return {};
}
ErrorOr<void> IDEChannel::initiate_transaction(TransactionDirection)
{
VERIFY(m_lock.is_locked());
return {};
}
ErrorOr<u8> IDEChannel::task_file_status()
{
VERIFY(m_lock.is_locked());
return m_io_window_group.control_window().read8(0);
}
ErrorOr<u8> IDEChannel::task_file_error()
{
VERIFY(m_lock.is_locked());
return m_io_window_group.io_window().read8(ATA_REG_ERROR);
}
ErrorOr<bool> IDEChannel::detect_presence_on_selected_device()
{
VERIFY(m_lock.is_locked());
m_io_window_group.io_window().write8(ATA_REG_SECCOUNT0, 0x55);
m_io_window_group.io_window().write8(ATA_REG_LBA0, 0xAA);
m_io_window_group.io_window().write8(ATA_REG_SECCOUNT0, 0xAA);
m_io_window_group.io_window().write8(ATA_REG_LBA0, 0x55);
m_io_window_group.io_window().write8(ATA_REG_SECCOUNT0, 0x55);
m_io_window_group.io_window().write8(ATA_REG_LBA0, 0xAA);
auto nsectors_value = m_io_window_group.io_window().read8(ATA_REG_SECCOUNT0);
auto lba0 = m_io_window_group.io_window().read8(ATA_REG_LBA0);
if (lba0 == 0xAA && nsectors_value == 0x55)
return true;
return false;
}
ErrorOr<void> IDEChannel::wait_if_busy_until_timeout(size_t timeout_in_milliseconds)
{
size_t time_elapsed = 0;
while (m_io_window_group.control_window().read8(0) & ATA_SR_BSY && time_elapsed <= timeout_in_milliseconds) {
microseconds_delay(1000);
time_elapsed++;
}
if (time_elapsed <= timeout_in_milliseconds)
return {};
return Error::from_errno(EBUSY);
}
ErrorOr<void> IDEChannel::force_clear_interrupts()
{
VERIFY(m_lock.is_locked());
m_io_window_group.io_window().read8(ATA_REG_STATUS);
return {};
}
ErrorOr<void> IDEChannel::load_taskfile_into_registers(ATAPort::TaskFile const& task_file, LBAMode lba_mode, size_t completion_timeout_in_milliseconds)
{
VERIFY(m_lock.is_locked());
VERIFY(m_hard_lock.is_locked());
u8 head = 0;
if (lba_mode == LBAMode::FortyEightBit) {
head = 0;
} else if (lba_mode == LBAMode::TwentyEightBit) {
head = (task_file.lba_high[0] & 0x0F);
}
// Note: Preserve the selected drive, always use LBA addressing
auto driver_register = ((m_io_window_group.io_window().read8(ATA_REG_HDDEVSEL) & (1 << 4)) | (head | (1 << 5) | (1 << 6)));
m_io_window_group.io_window().write8(ATA_REG_HDDEVSEL, driver_register);
microseconds_delay(50);
if (lba_mode == LBAMode::FortyEightBit) {
m_io_window_group.io_window().write8(ATA_REG_SECCOUNT1, (task_file.count >> 8) & 0xFF);
m_io_window_group.io_window().write8(ATA_REG_LBA3, task_file.lba_high[0]);
m_io_window_group.io_window().write8(ATA_REG_LBA4, task_file.lba_high[1]);
m_io_window_group.io_window().write8(ATA_REG_LBA5, task_file.lba_high[2]);
}
m_io_window_group.io_window().write8(ATA_REG_SECCOUNT0, task_file.count & 0xFF);
m_io_window_group.io_window().write8(ATA_REG_LBA0, task_file.lba_low[0]);
m_io_window_group.io_window().write8(ATA_REG_LBA1, task_file.lba_low[1]);
m_io_window_group.io_window().write8(ATA_REG_LBA2, task_file.lba_low[2]);
// FIXME: Set a timeout here?
size_t time_elapsed = 0;
for (;;) {
if (time_elapsed > completion_timeout_in_milliseconds)
return Error::from_errno(EBUSY);
// FIXME: Use task_file_status method
auto status = m_io_window_group.control_window().read8(0);
if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY))
break;
microseconds_delay(1000);
time_elapsed++;
}
m_io_window_group.io_window().write8(ATA_REG_COMMAND, task_file.command);
return {};
}
ErrorOr<void> IDEChannel::device_select(size_t device_index)
{
VERIFY(m_lock.is_locked());
if (device_index > 1)
return Error::from_errno(EINVAL);
microseconds_delay(20);
m_io_window_group.io_window().write8(ATA_REG_HDDEVSEL, (0xA0 | ((device_index) << 4)));
microseconds_delay(20);
return {};
}
ErrorOr<void> IDEChannel::enable_interrupts()
{
VERIFY(m_lock.is_locked());
m_io_window_group.control_window().write8(0, 0);
m_interrupts_enabled = true;
return {};
}
ErrorOr<void> IDEChannel::disable_interrupts()
{
VERIFY(m_lock.is_locked());
m_io_window_group.control_window().write8(0, 1 << 1);
m_interrupts_enabled = false;
return {};
}
ErrorOr<void> IDEChannel::read_pio_data_to_buffer(UserOrKernelBuffer& buffer, size_t block_offset, size_t words_count)
{
VERIFY(m_lock.is_locked());
VERIFY(words_count == 256);
for (u32 i = 0; i < 256; ++i) {
u16 data = m_io_window_group.io_window().read16(ATA_REG_DATA);
// FIXME: Don't assume 512 bytes sector
TRY(buffer.write(&data, block_offset * 512 + (i * 2), 2));
}
return {};
}
ErrorOr<void> IDEChannel::write_pio_data_from_buffer(UserOrKernelBuffer const& buffer, size_t block_offset, size_t words_count)
{
VERIFY(m_lock.is_locked());
VERIFY(words_count == 256);
for (u32 i = 0; i < 256; ++i) {
u16 buf;
// FIXME: Don't assume 512 bytes sector
TRY(buffer.read(&buf, block_offset * 512 + (i * 2), 2));
m_io_window_group.io_window().write16(ATA_REG_DATA, buf);
}
return {};
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
//
// Parallel ATA (PATA) controller driver
//
// This driver describes a logical PATA Channel. Each channel can connect up to 2
// IDE Hard Disk Drives. The drives themselves can be either the master drive (hd0)
// or the slave drive (hd1).
//
// More information about the ATA spec for PATA can be found here:
// ftp://ftp.seagate.com/acrobat/reference/111-1c.pdf
//
#pragma once
#include <AK/Error.h>
#include <Kernel/Devices/Device.h>
#include <Kernel/Devices/Storage/ATA/ATADevice.h>
#include <Kernel/Devices/Storage/ATA/ATAPort.h>
#include <Kernel/Devices/Storage/StorageDevice.h>
#include <Kernel/IOWindow.h>
#include <Kernel/Interrupts/IRQHandler.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Locking/Mutex.h>
#include <Kernel/Memory/PhysicalPage.h>
#include <Kernel/PhysicalAddress.h>
#include <Kernel/Random.h>
#include <Kernel/WaitQueue.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class IDEController;
#if ARCH(X86_64)
class PCIIDELegacyModeController;
class ISAIDEController;
#endif
class IDEChannel
: public ATAPort
, public IRQHandler {
friend class IDEController;
public:
enum class ChannelType : u8 {
Primary,
Secondary
};
enum class DeviceType : u8 {
Master,
Slave,
};
struct IOWindowGroup {
IOWindowGroup(NonnullOwnPtr<IOWindow> io_window, NonnullOwnPtr<IOWindow> control_window, NonnullOwnPtr<IOWindow> m_bus_master_window)
: m_io_window(move(io_window))
, m_control_window(move(control_window))
, m_bus_master_window(move(m_bus_master_window))
{
}
IOWindowGroup(NonnullOwnPtr<IOWindow> io_window, NonnullOwnPtr<IOWindow> control_window)
: m_io_window(move(io_window))
, m_control_window(move(control_window))
{
}
// Disable default implementations that would use surprising integer promotion.
bool operator==(IOWindowGroup const&) const = delete;
bool operator<=(IOWindowGroup const&) const = delete;
bool operator>=(IOWindowGroup const&) const = delete;
bool operator<(IOWindowGroup const&) const = delete;
bool operator>(IOWindowGroup const&) const = delete;
IOWindow& io_window() const { return *m_io_window; };
IOWindow& control_window() const { return *m_control_window; }
IOWindow* bus_master_window() const { return m_bus_master_window.ptr(); }
private:
mutable NonnullOwnPtr<IOWindow> m_io_window;
mutable NonnullOwnPtr<IOWindow> m_control_window;
mutable OwnPtr<IOWindow> m_bus_master_window;
};
public:
static ErrorOr<NonnullRefPtr<IDEChannel>> create(IDEController const&, IOWindowGroup, ChannelType type);
static ErrorOr<NonnullRefPtr<IDEChannel>> create(IDEController const&, u8 irq, IOWindowGroup, ChannelType type);
virtual ~IDEChannel() override;
virtual StringView purpose() const override { return "PATA Channel"sv; }
#if ARCH(X86_64)
ErrorOr<void> allocate_resources_for_pci_ide_controller(Badge<PCIIDELegacyModeController>, bool force_pio);
ErrorOr<void> allocate_resources_for_isa_ide_controller(Badge<ISAIDEController>);
#endif
private:
static constexpr size_t m_logical_sector_size = 512;
ErrorOr<void> allocate_resources(bool force_pio);
StringView channel_type_string() const;
virtual ErrorOr<void> disable() override { TODO(); }
virtual ErrorOr<void> power_on() override { TODO(); }
virtual ErrorOr<void> port_phy_reset() override;
bool select_device_and_wait_until_not_busy(DeviceType, size_t milliseconds_timeout);
virtual bool pio_capable() const override { return true; }
virtual bool dma_capable() const override { return m_dma_enabled; }
virtual size_t max_possible_devices_connected() const override { return 2; }
virtual ErrorOr<void> stop_busmastering() override;
virtual ErrorOr<void> start_busmastering(TransactionDirection) override;
virtual ErrorOr<void> force_busmastering_status_clean() override;
virtual ErrorOr<u8> busmastering_status() override;
virtual ErrorOr<void> prepare_transaction_with_busmastering(TransactionDirection, PhysicalAddress prdt_buffer) override;
virtual ErrorOr<void> initiate_transaction(TransactionDirection) override;
virtual ErrorOr<u8> task_file_status() override;
virtual ErrorOr<u8> task_file_error() override;
virtual ErrorOr<void> wait_if_busy_until_timeout(size_t timeout_in_milliseconds) override;
virtual ErrorOr<void> device_select(size_t device_index) override;
virtual ErrorOr<bool> detect_presence_on_selected_device() override;
virtual ErrorOr<void> enable_interrupts() override;
virtual ErrorOr<void> disable_interrupts() override;
virtual ErrorOr<void> force_clear_interrupts() override;
virtual ErrorOr<void> load_taskfile_into_registers(TaskFile const&, LBAMode lba_mode, size_t completion_timeout_in_milliseconds) override;
virtual ErrorOr<void> read_pio_data_to_buffer(UserOrKernelBuffer&, size_t block_offset, size_t words_count) override;
virtual ErrorOr<void> write_pio_data_from_buffer(UserOrKernelBuffer const&, size_t block_offset, size_t words_count) override;
IDEChannel(IDEController const&, IOWindowGroup, ChannelType type, NonnullOwnPtr<KBuffer> ata_identify_data_buffer);
IDEChannel(IDEController const&, u8 irq, IOWindowGroup, ChannelType type, NonnullOwnPtr<KBuffer> ata_identify_data_buffer);
//^ IRQHandler
virtual bool handle_irq(RegisterState const&) override;
// Data members
ChannelType m_channel_type { ChannelType::Primary };
bool m_dma_enabled { false };
bool m_interrupts_enabled { true };
IOWindowGroup m_io_window_group;
};
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2020-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/OwnPtr.h>
#include <AK/Types.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Devices/Storage/ATA/ATADiskDevice.h>
#include <Kernel/Devices/Storage/ATA/GenericIDE/Channel.h>
#include <Kernel/Devices/Storage/ATA/GenericIDE/Controller.h>
#include <Kernel/Library/LockRefPtr.h>
#include <Kernel/Sections.h>
namespace Kernel {
ErrorOr<void> IDEController::reset()
{
return Error::from_errno(ENOTIMPL);
}
ErrorOr<void> IDEController::shutdown()
{
return Error::from_errno(ENOTIMPL);
}
size_t IDEController::devices_count() const
{
size_t count = 0;
for (u32 index = 0; index < 4; index++) {
if (!device(index).is_null())
count++;
}
return count;
}
void IDEController::start_request(ATADevice const& device, AsyncBlockDeviceRequest& request)
{
auto& address = device.ata_address();
VERIFY(address.subport < 2);
switch (address.port) {
case 0: {
auto result = m_channels[0]->start_request(device, request);
// FIXME: Propagate errors properly
VERIFY(!result.is_error());
return;
}
case 1: {
auto result = m_channels[1]->start_request(device, request);
// FIXME: Propagate errors properly
VERIFY(!result.is_error());
return;
}
}
VERIFY_NOT_REACHED();
}
void IDEController::complete_current_request(AsyncDeviceRequest::RequestResult)
{
VERIFY_NOT_REACHED();
}
UNMAP_AFTER_INIT IDEController::IDEController() = default;
UNMAP_AFTER_INIT IDEController::~IDEController() = default;
LockRefPtr<StorageDevice> IDEController::device_by_channel_and_position(u32 index) const
{
switch (index) {
case 0:
return m_channels[0]->connected_device(0);
case 1:
return m_channels[0]->connected_device(1);
case 2:
return m_channels[1]->connected_device(0);
case 3:
return m_channels[1]->connected_device(1);
}
VERIFY_NOT_REACHED();
}
LockRefPtr<StorageDevice> IDEController::device(u32 index) const
{
Vector<NonnullLockRefPtr<StorageDevice>> connected_devices;
for (size_t index = 0; index < 4; index++) {
auto checked_device = device_by_channel_and_position(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];
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020-2022, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/OwnPtr.h>
#include <AK/Types.h>
#include <Kernel/Devices/Storage/ATA/ATAController.h>
#include <Kernel/Devices/Storage/StorageDevice.h>
#include <Kernel/Library/LockRefPtr.h>
namespace Kernel {
class AsyncBlockDeviceRequest;
class IDEChannel;
class IDEController : public ATAController {
public:
virtual ~IDEController() override;
virtual LockRefPtr<StorageDevice> device(u32 index) const override final;
virtual ErrorOr<void> reset() override final;
virtual ErrorOr<void> shutdown() override final;
virtual size_t devices_count() const override final;
virtual void start_request(ATADevice const&, AsyncBlockDeviceRequest&) override final;
virtual void complete_current_request(AsyncDeviceRequest::RequestResult) override final;
protected:
IDEController();
LockRefPtr<StorageDevice> device_by_channel_and_position(u32 index) const;
Array<RefPtr<IDEChannel>, 2> m_channels;
};
}