Skip to main content

strat9_kernel/hardware/storage/
ahci.rs

1//! AHCI (Advanced Host Controller Interface) driver : AHCI spec 1.3.1
2//!
3//! PCI: class=0x01 (Mass Storage), subclass=0x06 (SATA), prog_if=0x01
4//! MMIO base: BAR5 (ABAR)
5//!
6//! Per-port memory layout (packed into one 4 KB page):
7//!   [0x000..0x3FF]  Command List   (1024 B, 32 × 32-byte headers)
8//!   [0x400..0x4FF]  FIS receive    (256 B)
9//!   [0x500..0x5FF]  Command table  (128 B header + 1 × 16-byte PRDT)
10//!
11//! ## IRQ-driven completion (DRV-02 v2)
12//!
13//! When a task context exists (`current_task_id()` is `Some`), commands are
14//! completed via interrupt + `WaitQueue::wait_until()` so the issuing task
15//! blocks without busy-spinning.  During early boot (no task yet) the legacy
16//! polling path is used as a fallback.
17//!
18//! Per-port statics (indexed by `port_num 0..32`) are used so the IRQ handler
19//! can signal completion without acquiring any slow lock:
20//!   - `PORT_VIRT[n]`       : MMIO virtual address of port n registers
21//!   - `PORT_SLOT0_DONE[n]` : set by IRQ handler when slot-0 completes
22//!   - `PORT_SLOT0_ERROR[n]`: set by IRQ handler when a task-file error fires
23//!   - `PORT_WQ[n]`         : WaitQueue; issuing task blocks here
24
25use crate::{
26    dma::DmaBuffer,
27    hardware::pci_client::{self as pci, ProbeCriteria},
28    memory,
29    memory::{phys_to_virt, UserSliceRead, UserSliceWrite},
30    sync::{SpinLock, WaitQueue},
31};
32use alloc::{boxed::Box, vec::Vec};
33use core::{
34    ptr,
35    sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicU8, Ordering},
36};
37
38pub use super::virtio_block::{BlockDevice, BlockError, SECTOR_SIZE};
39
40// ========== HBA generic registers (at ABAR) ======================================================
41const HBA_GHC: u64 = 0x04;
42const HBA_IS: u64 = 0x08;
43const HBA_PI: u64 = 0x0C;
44
45const GHC_AE: u32 = 1 << 31; // AHCI Enable
46const GHC_IE: u32 = 1 << 1; // Global Interrupt Enable
47const GHC_HR: u32 = 1 << 0; // HBA Reset
48
49// ========== Port register offsets (relative to port base = ABAR + 0x100 + n*0x80)
50const PORT_CLB: u64 = 0x00;
51const PORT_CLBU: u64 = 0x04;
52const PORT_FB: u64 = 0x08;
53const PORT_FBU: u64 = 0x0C;
54const PORT_IS: u64 = 0x10;
55const PORT_IE: u64 = 0x14; // PxIE : port interrupt enable
56const PORT_CMD: u64 = 0x18;
57const PORT_TFD: u64 = 0x20;
58const PORT_SIG: u64 = 0x24;
59const PORT_SSTS: u64 = 0x28;
60const PORT_SERR: u64 = 0x30;
61const PORT_CI: u64 = 0x38;
62
63const CMD_ST: u32 = 1 << 0; // Start
64const CMD_FRE: u32 = 1 << 4; // FIS Receive Enable
65const CMD_FR: u32 = 1 << 14; // FIS Receive Running
66const CMD_CR: u32 = 1 << 15; // Command List Running
67
68const TFD_BSY: u32 = 1 << 7;
69const TFD_DRQ: u32 = 1 << 3;
70
71const SSTS_DET_COMM: u32 = 3;
72const SSTS_DET_MASK: u32 = 0xF;
73
74const SIG_SATA: u32 = 0x0000_0101;
75
76// PxIE bits
77const PXIE_DHRE: u32 = 1 << 0; // D2H Register FIS Received Enable (normal DMA completion)
78const PXIE_TFEE: u32 = 1 << 30; // Task File Error Enable
79
80// ========== Per-port memory layout offsets ========================================================
81const CLB_OFF: u64 = 0x000; // Command List (1024 B)
82const FB_OFF: u64 = 0x400; // FIS buffer   (256 B)
83const CTAB_OFF: u64 = 0x500; // Command Table (128 B header + 16 B PRDT)
84
85// Command header field byte offsets within a 32-byte slot
86const CMDH_FLAGS: usize = 0; // u16: cfl[4:0] | a | w | p | r | b | c
87const CMDH_PRDTL: usize = 2; // u16
88const CMDH_CTBA: usize = 8; // u32
89const CMDH_CTBAU: usize = 12; // u32
90
91// Command table FIS and PRDT offsets
92const CTAB_CFIS: usize = 0x00; // H2D FIS (64 B allocated)
93const CTAB_PRDT: usize = 0x80; // PRDT entries
94
95// H2D FIS field offsets (FIS type 0x27, Register Host-to-Device)
96const FIS_TYPE: usize = 0;
97const FIS_FLAGS: usize = 1; // PM port [3:0] | C [7]
98const FIS_CMD: usize = 2;
99const FIS_LBA0: usize = 4;
100const FIS_LBA1: usize = 5;
101const FIS_LBA2: usize = 6;
102const FIS_DEVICE: usize = 7;
103const FIS_LBA3: usize = 8;
104const FIS_LBA4: usize = 9;
105const FIS_LBA5: usize = 10;
106const FIS_CNT_LO: usize = 12;
107const FIS_CNT_HI: usize = 13;
108
109const FIS_TYPE_H2D: u8 = 0x27;
110const FIS_C_BIT: u8 = 0x80; // command (not control)
111const FIS_LBA_MODE: u8 = 1 << 6;
112
113// ATA commands (48-bit LBA)
114const ATA_IDENTIFY: u8 = 0xEC;
115const ATA_READ_DMA_EXT: u8 = 0x25;
116const ATA_WRITE_DMA_EXT: u8 = 0x35;
117
118const EFAULT: i32 = -14;
119
120// PxIS bit 30 = Task File Error Status
121const PXIS_TFES: u32 = 1 << 30;
122
123// ========== Error type ==============================
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
126pub enum AhciError {
127    #[error("no AHCI controller on PCI bus")]
128    NoController,
129    #[error("invalid BAR5 (ABAR)")]
130    BadAbar,
131    #[error("physical memory allocation failed")]
132    Alloc,
133    #[error("port BSY/DRQ set")]
134    Busy,
135    #[error("command timed out")]
136    Timeout,
137    #[error("device reported task-file error")]
138    DeviceError,
139    #[error("invalid sector number")]
140    InvalidSector,
141    #[error("buffer too small (need ≥ SECTOR_SIZE bytes)")]
142    BufferTooSmall,
143    #[error("invalid userspace buffer")]
144    InvalidUserBuffer,
145    #[error("no usable SATA port found")]
146    NoPort,
147}
148
149// ========== Internal port handle ======================================================================
150
151struct AhciPort {
152    port_num: u8,
153    port_virt: u64, // virtual address of port registers
154    mem_phys: u64,  // physical base of the per-port CLB/FB/CTAB frame
155    mem_virt: u64,  // HHDM virtual address of that frame
156    sector_count: u64,
157}
158
159// ========== Controller ==============================
160
161pub struct AhciController {
162    #[allow(dead_code)]
163    abar_virt: u64,
164    ports: Vec<AhciPort>,
165}
166
167// SAFETY: AhciController is only accessed behind SpinLock<Option<...>>
168unsafe impl Send for AhciController {}
169unsafe impl Sync for AhciController {}
170
171// ========== Per-port IRQ completion state ===========================================================
172// These statics are accessed from the IRQ handler without locks.
173// Indexed by port_num (0..32).
174
175/// AHCI ABAR virtual address : written once during init, read by IRQ handler.
176static AHCI_ABAR_VIRT: AtomicU64 = AtomicU64::new(0);
177
178/// PCI interrupt line used by this controller.
179pub static AHCI_IRQ_LINE: AtomicU8 = AtomicU8::new(0xFF);
180
181/// Per-port MMIO virtual addresses : written once during init.
182static PORT_VIRT: [AtomicU64; 32] = {
183    const INIT: AtomicU64 = AtomicU64::new(0);
184    [INIT; 32]
185};
186
187/// Per-port slot-0 completion flags : set by IRQ handler, cleared by consumer.
188static PORT_SLOT0_DONE: [AtomicBool; 32] = {
189    const INIT: AtomicBool = AtomicBool::new(false);
190    [INIT; 32]
191};
192
193/// Per-port slot-0 error flags : set by IRQ handler on task-file error.
194static PORT_SLOT0_ERROR: [AtomicBool; 32] = {
195    const INIT: AtomicBool = AtomicBool::new(false);
196    [INIT; 32]
197};
198
199/// Per-port wait queues : tasks block here while waiting for IRQ completion.
200static PORT_WQ: [WaitQueue; 32] = {
201    const INIT: WaitQueue = WaitQueue::new();
202    [INIT; 32]
203};
204
205// ========== Per-port async operation metadata  ====================================================================
206//
207// These statics bridge AHCI IRQ completions to the async I/O ring
208// subsystem.  Before issuing a command in async mode, the submit path
209// stores the ring_id / user_data / buffer address in the per-port
210// arrays below.  When the IRQ fires, `handle_interrupt()` reads them
211// back, pushes a CQE into the ring, and drops the DMA buffer.
212//
213// Only slot 0 is used (one in-flight op per port).
214
215/// Ring id to push the completion CQE into (0 = no async pending).
216static PORT_ASYNC_RING_ID: [AtomicU64; 32] = {
217    const INIT: AtomicU64 = AtomicU64::new(0);
218    [INIT; 32]
219};
220
221/// User-data token echoed in the completion CQE.
222static PORT_ASYNC_USER_DATA: [AtomicU64; 32] = {
223    const INIT: AtomicU64 = AtomicU64::new(0);
224    [INIT; 32]
225};
226
227/// User virtual address where read data must be copied (0 = write op).
228static PORT_ASYNC_BUF_VADDR: [AtomicU64; 32] = {
229    const INIT: AtomicU64 = AtomicU64::new(0);
230    [INIT; 32]
231};
232
233/// Number of bytes transferred.
234static PORT_ASYNC_LEN: [AtomicU32; 32] = {
235    const INIT: AtomicU32 = AtomicU32::new(0);
236    [INIT; 32]
237};
238
239/// Whether the in-flight async command is a write operation.
240static PORT_ASYNC_IS_WRITE: [AtomicBool; 32] = {
241    const INIT: AtomicBool = AtomicBool::new(false);
242    [INIT; 32]
243};
244
245/// Raw pointer to a leaked `Box<DmaBuffer>` held until IRQ completion.
246/// 0 means no buffer.  Freed inside `handle_interrupt()` via
247/// `Box::from_raw()`.
248static PORT_ASYNC_DMA_PTR: [AtomicU64; 32] = {
249    const INIT: AtomicU64 = AtomicU64::new(0);
250    [INIT; 32]
251};
252
253/// Whether an async operation is in-flight on this port's slot 0.
254static PORT_ASYNC_ACTIVE: [AtomicBool; 32] = {
255    const INIT: AtomicBool = AtomicBool::new(false);
256    [INIT; 32]
257};
258
259struct PendingAsyncReadCompletion {
260    ring_id: u64,
261    user_data: u64,
262    user_buf_vaddr: u64,
263    dma_buf: DmaBuffer,
264    len: usize,
265    result: i32,
266}
267
268static PENDING_ASYNC_READ_COMPLETIONS: SpinLock<Vec<PendingAsyncReadCompletion>> =
269    SpinLock::new(Vec::new());
270
271// ========== MMIO helpers ==============================
272
273/// Performs the rd32 operation.
274#[inline]
275unsafe fn rd32(base: u64, off: u64) -> u32 {
276    ptr::read_volatile((base + off) as *const u32)
277}
278
279/// Performs the wr32 operation.
280#[inline]
281unsafe fn wr32(base: u64, off: u64, val: u32) {
282    ptr::write_volatile((base + off) as *mut u32, val);
283}
284
285// ========== Port start/stop ====================
286
287/// Performs the port stop operation.
288fn port_stop(pvirt: u64) {
289    // SAFETY: pvirt is a valid MMIO virtual address for this port's registers
290    unsafe {
291        let mut cmd = rd32(pvirt, PORT_CMD);
292        cmd &= !(CMD_ST | CMD_FRE);
293        wr32(pvirt, PORT_CMD, cmd);
294        // Spec mandates waiting ≤ 500 ms for FR and CR to clear
295        for _ in 0..500_000u32 {
296            if rd32(pvirt, PORT_CMD) & (CMD_FR | CMD_CR) == 0 {
297                return;
298            }
299            core::hint::spin_loop();
300        }
301        log::warn!("AHCI: port stop timed out (port registers @ {:#x})", pvirt);
302    }
303}
304
305/// Performs the port start operation.
306fn port_start(pvirt: u64) {
307    // SAFETY: pvirt is a valid MMIO virtual address
308    unsafe {
309        // Ensure Command List Running is clear before asserting ST
310        let mut timeout = 500_000u32;
311        while rd32(pvirt, PORT_CMD) & CMD_CR != 0 {
312            timeout = timeout.saturating_sub(1);
313            if timeout == 0 {
314                log::warn!("AHCI: port start wait for CR clear timed out");
315                break;
316            }
317            core::hint::spin_loop();
318        }
319        let mut cmd = rd32(pvirt, PORT_CMD);
320        cmd |= CMD_FRE | CMD_ST;
321        wr32(pvirt, PORT_CMD, cmd);
322    }
323}
324
325/// Rebase port: assign our CLB/FB buffers then start the port.
326fn port_rebase(pvirt: u64, phys: u64) {
327    port_stop(pvirt);
328    // SAFETY: pvirt valid MMIO; phys is our allocated frame
329    unsafe {
330        let clb = phys + CLB_OFF;
331        let fb = phys + FB_OFF;
332        wr32(pvirt, PORT_CLB, (clb & 0xFFFF_FFFF) as u32);
333        wr32(pvirt, PORT_CLBU, (clb >> 32) as u32);
334        wr32(pvirt, PORT_FB, (fb & 0xFFFF_FFFF) as u32);
335        wr32(pvirt, PORT_FBU, (fb >> 32) as u32);
336        // Clear any stale interrupt/error status
337        wr32(pvirt, PORT_IS, 0xFFFF_FFFF);
338        wr32(pvirt, PORT_SERR, 0xFFFF_FFFF);
339    }
340    port_start(pvirt);
341}
342
343/// Enable interrupts for a port (DHRE = DMA completion, TFEE = errors).
344fn port_enable_irq(pvirt: u64) {
345    // SAFETY: pvirt is a valid MMIO port register address
346    unsafe {
347        wr32(pvirt, PORT_IE, PXIE_DHRE | PXIE_TFEE);
348    }
349}
350
351// ========== DMA buffer management ======================================================================
352//
353// Replaced the old `Bounce` hand-rolled struct with the kernel-wide
354// `DmaBuffer` abstraction
355// DmaBuffer pins frames, preventing the buddy allocator from recycling
356// them while a transfer is in flight, and automatically unpins on Drop.
357
358// ========== Command submission ==========
359//
360// Two completion strategies:
361//   1. Task context (current_task_id() is Some): IRQ + WaitQueue : the issuing
362//      task is blocked by the scheduler until the IRQ fires and wakes it.
363//   2. Boot context (no task yet): legacy busy-poll with timeout.
364
365/// Performs the submit cmd operation.
366fn submit_cmd(
367    port: &AhciPort,
368    lba: u64,
369    count: u16,
370    buf: &mut [u8],
371    write: bool,
372    ata_cmd: u8,
373) -> Result<(), AhciError> {
374    let nbytes = (count as usize) * SECTOR_SIZE;
375    if buf.len() < nbytes {
376        return Err(AhciError::BufferTooSmall);
377    }
378
379    // SAFETY: MMIO read to check device readiness
380    let tfd = unsafe { rd32(port.port_virt, PORT_TFD) };
381    if tfd & (TFD_BSY | TFD_DRQ) != 0 {
382        return Err(AhciError::Busy);
383    }
384
385    let dma_buf = DmaBuffer::alloc(nbytes).map_err(|_| AhciError::Alloc)?;
386
387    if write {
388        // SAFETY: dma_buf virtual addr is valid for nbytes; buf.len() >= nbytes
389        unsafe {
390            ptr::copy_nonoverlapping(buf.as_ptr(), dma_buf.virt_addr() as *mut u8, nbytes);
391        }
392    }
393
394    let ctab_phys = port.mem_phys + CTAB_OFF;
395    let cmdh_virt = port.mem_virt + CLB_OFF; // slot 0 = first 32 bytes of CLB
396    let ctab_virt = port.mem_virt + CTAB_OFF;
397
398    // SAFETY: cmdh_virt and ctab_virt point to our allocated frame (physically valid)
399    unsafe {
400        // --- Command header (slot 0, 32 bytes) ---
401        let h = cmdh_virt as *mut u8;
402        ptr::write_bytes(h, 0, 32);
403
404        // CFL = 5 (H2D FIS = 20 B = 5 DWORDs); W bit set for writes
405        let flags: u16 = 5u16 | (if write { 1 << 6 } else { 0 });
406        ptr::write_unaligned(h.add(CMDH_FLAGS) as *mut u16, flags.to_le());
407        ptr::write_unaligned(h.add(CMDH_PRDTL) as *mut u16, 1u16.to_le()); // 1 PRDT entry
408        ptr::write_unaligned(
409            h.add(CMDH_CTBA) as *mut u32,
410            (ctab_phys & 0xFFFF_FFFF) as u32,
411        );
412        ptr::write_unaligned(h.add(CMDH_CTBAU) as *mut u32, (ctab_phys >> 32) as u32);
413
414        // --- Command table ---
415        let t = ctab_virt as *mut u8;
416        ptr::write_bytes(t, 0, CTAB_PRDT + 16);
417
418        // H2D Register FIS (20 bytes at CFIS offset)
419        let f = t.add(CTAB_CFIS);
420        *f.add(FIS_TYPE) = FIS_TYPE_H2D;
421        *f.add(FIS_FLAGS) = FIS_C_BIT;
422        *f.add(FIS_CMD) = ata_cmd;
423        *f.add(FIS_LBA0) = (lba & 0xFF) as u8;
424        *f.add(FIS_LBA1) = ((lba >> 8) & 0xFF) as u8;
425        *f.add(FIS_LBA2) = ((lba >> 16) & 0xFF) as u8;
426        *f.add(FIS_DEVICE) = FIS_LBA_MODE; // LBA addressing, device 0
427        *f.add(FIS_LBA3) = ((lba >> 24) & 0xFF) as u8;
428        *f.add(FIS_LBA4) = ((lba >> 32) & 0xFF) as u8;
429        *f.add(FIS_LBA5) = ((lba >> 40) & 0xFF) as u8;
430        *f.add(FIS_CNT_LO) = (count & 0xFF) as u8;
431        *f.add(FIS_CNT_HI) = (count >> 8) as u8;
432
433        // PRDT entry 0 (16 bytes)
434        let p = t.add(CTAB_PRDT);
435        // DBA: physical address of DMA buffer
436        let dma_phys = dma_buf.dma_addr().as_u64();
437        ptr::write_unaligned(p.add(0) as *mut u32, (dma_phys & 0xFFFF_FFFF) as u32);
438        ptr::write_unaligned(p.add(4) as *mut u32, (dma_phys >> 32) as u32);
439        ptr::write_unaligned(p.add(8) as *mut u32, 0u32);
440        // DBC: byte_count - 1; bit 31 = interrupt on completion
441        let dbc = ((nbytes as u32).saturating_sub(1)) | (1 << 31);
442        ptr::write_unaligned(p.add(12) as *mut u32, dbc);
443    }
444
445    let idx = port.port_num as usize;
446
447    // Clear any stale completion state before issuing the command
448    PORT_SLOT0_DONE[idx].store(false, Ordering::Release);
449    PORT_SLOT0_ERROR[idx].store(false, Ordering::Release);
450
451    // Issue command in slot 0
452    // SAFETY: MMIO write to PxCI
453    unsafe { wr32(port.port_virt, PORT_CI, 1) };
454
455    // Completion strategy ================================================
456    if crate::process::current_task_id().is_some() {
457        // Task context: block until the IRQ handler signals DONE.
458        // WaitQueue::wait_until() atomically checks the condition under the
459        // waiters SpinLock (which disables IRQs via CLI), so the completion
460        // interrupt cannot be lost between the check and the block.
461        PORT_WQ[idx].wait_until(|| {
462            if PORT_SLOT0_DONE[idx].load(Ordering::Acquire) {
463                // Consume the flag atomically before returning
464                PORT_SLOT0_DONE[idx].store(false, Ordering::Release);
465                Some(())
466            } else {
467                None
468            }
469        });
470
471        // Check whether the IRQ reported an error
472        if PORT_SLOT0_ERROR[idx].load(Ordering::Acquire) {
473            return Err(AhciError::DeviceError);
474        }
475    } else {
476        // Boot context (no task): fall back to busy-poll with ≈5 s timeout.
477        let mut tries = 5_000_000u32;
478        loop {
479            // SAFETY: MMIO reads
480            let ci = unsafe { rd32(port.port_virt, PORT_CI) };
481            let is = unsafe { rd32(port.port_virt, PORT_IS) };
482
483            if is & PXIS_TFES != 0 {
484                // SAFETY: MMIO writes to clear error status
485                unsafe {
486                    wr32(port.port_virt, PORT_IS, 0xFFFF_FFFF);
487                    wr32(port.port_virt, PORT_SERR, 0xFFFF_FFFF);
488                }
489                return Err(AhciError::DeviceError);
490            }
491
492            if ci & 1 == 0 {
493                break; // slot 0 completed
494            }
495
496            tries = tries.saturating_sub(1);
497            if tries == 0 {
498                return Err(AhciError::Timeout);
499            }
500            core::hint::spin_loop();
501        }
502
503        // SAFETY: MMIO write to clear port interrupt status
504        unsafe { wr32(port.port_virt, PORT_IS, 0xFFFF_FFFF) };
505    }
506
507    if !write {
508        // SAFETY: dma_buf virtual addr valid, nbytes ≤ allocated
509        unsafe {
510            ptr::copy_nonoverlapping(dma_buf.virt_addr() as *const u8, buf.as_mut_ptr(), nbytes);
511        }
512    }
513    Ok(())
514}
515
516// ========== Async command submission =======================================
517//
518// Non-blocking variant of `submit_cmd`.  Stores per-port async metadata
519// before issuing the command, so the IRQ handler can push a CQE directly
520// into the caller's async ring without any further kernel involvement.
521
522/// Submit a command asynchronously ; returns immediately without blocking.
523///
524/// The caller **must** have already set `PORT_ASYNC_ACTIVE[idx]` to `true`
525/// and stored the ring / user-data / buffer info in the per-port statics.
526///
527/// On success the operation is in-flight; the IRQ handler will deliver the
528/// CQE.  On failure (device busy, allocation error) the metadata is cleared
529/// and the caller must push an error CQE itself.
530fn submit_async_cmd(
531    port: &AhciPort,
532    lba: u64,
533    count: u16,
534    write: bool,
535    ata_cmd: u8,
536    dma_buf: DmaBuffer,
537) -> Result<(), AhciError> {
538    let nbytes = (count as usize) * SECTOR_SIZE;
539    let idx = port.port_num as usize;
540
541    // SAFETY: MMIO read to check device readiness
542    let tfd = unsafe { rd32(port.port_virt, PORT_TFD) };
543    if tfd & (TFD_BSY | TFD_DRQ) != 0 {
544        return Err(AhciError::Busy);
545    }
546
547    // For writes: copy user data into the DMA buffer before issuing
548    if write {
549        let user_vaddr = PORT_ASYNC_BUF_VADDR[idx].load(Ordering::Acquire);
550        if user_vaddr != 0 {
551            // SAFETY: user buffer vaddr was validated at dispatch time,
552            // nbytes ≤ allocated DMA buffer capacity.
553            unsafe {
554                ptr::copy_nonoverlapping(
555                    user_vaddr as *const u8,
556                    dma_buf.virt_addr() as *mut u8,
557                    nbytes,
558                );
559            }
560        }
561    }
562
563    let ctab_phys = port.mem_phys + CTAB_OFF;
564    let cmdh_virt = port.mem_virt + CLB_OFF;
565    let ctab_virt = port.mem_virt + CTAB_OFF;
566
567    // SAFETY: all addresses point into our pre-allocated per-port frame.
568    unsafe {
569        // --- Command header (slot 0, 32 bytes) ---
570        let h = cmdh_virt as *mut u8;
571        ptr::write_bytes(h, 0, 32);
572
573        let flags: u16 = 5u16 | (if write { 1 << 6 } else { 0 });
574        ptr::write_unaligned(h.add(CMDH_FLAGS) as *mut u16, flags.to_le());
575        ptr::write_unaligned(h.add(CMDH_PRDTL) as *mut u16, 1u16.to_le());
576        ptr::write_unaligned(
577            h.add(CMDH_CTBA) as *mut u32,
578            (ctab_phys & 0xFFFF_FFFF) as u32,
579        );
580        ptr::write_unaligned(h.add(CMDH_CTBAU) as *mut u32, (ctab_phys >> 32) as u32);
581
582        // --- Command table ---
583        let t = ctab_virt as *mut u8;
584        ptr::write_bytes(t, 0, CTAB_PRDT + 16);
585
586        // H2D Register FIS
587        let f = t.add(CTAB_CFIS);
588        *f.add(FIS_TYPE) = FIS_TYPE_H2D;
589        *f.add(FIS_FLAGS) = FIS_C_BIT;
590        *f.add(FIS_CMD) = ata_cmd;
591        *f.add(FIS_LBA0) = (lba & 0xFF) as u8;
592        *f.add(FIS_LBA1) = ((lba >> 8) & 0xFF) as u8;
593        *f.add(FIS_LBA2) = ((lba >> 16) & 0xFF) as u8;
594        *f.add(FIS_DEVICE) = FIS_LBA_MODE;
595        *f.add(FIS_LBA3) = ((lba >> 24) & 0xFF) as u8;
596        *f.add(FIS_LBA4) = ((lba >> 32) & 0xFF) as u8;
597        *f.add(FIS_LBA5) = ((lba >> 40) & 0xFF) as u8;
598        *f.add(FIS_CNT_LO) = (count & 0xFF) as u8;
599        *f.add(FIS_CNT_HI) = (count >> 8) as u8;
600
601        // PRDT entry 0 (16 bytes)
602        let p = t.add(CTAB_PRDT);
603        let dma_phys = dma_buf.dma_addr().as_u64();
604        ptr::write_unaligned(p.add(0) as *mut u32, (dma_phys & 0xFFFF_FFFF) as u32);
605        ptr::write_unaligned(p.add(4) as *mut u32, (dma_phys >> 32) as u32);
606        ptr::write_unaligned(p.add(8) as *mut u32, 0u32);
607        let dbc = ((nbytes as u32).saturating_sub(1)) | (1 << 31);
608        ptr::write_unaligned(p.add(12) as *mut u32, dbc);
609    }
610
611    // Leak the DmaBuffer so it stays alive until the IRQ consumes it.
612    let dma_ptr = Box::into_raw(Box::new(dma_buf));
613    PORT_ASYNC_DMA_PTR[idx].store(dma_ptr as u64, Ordering::Release);
614
615    // Clear stale flags and issue the command
616    PORT_SLOT0_DONE[idx].store(false, Ordering::Release);
617    PORT_SLOT0_ERROR[idx].store(false, Ordering::Release);
618
619    // SAFETY: MMIO write to PxCI issues slot 0
620    unsafe { wr32(port.port_virt, PORT_CI, 1) };
621
622    Ok(())
623}
624
625/// Public entry point called from `async_io::dispatch`.
626///
627/// Validates the port index, looks up the AHCI device, allocates a DMA
628/// buffer, stores async metadata, and issues the command.
629///
630/// On success the caller should increment the ring's in-flight counter.
631/// On failure an error CQE is returned so dispatch can push it immediately.
632#[allow(dead_code)]
633pub(crate) fn submit_async_storage_op(
634    port_idx: u8,
635    lba: u64,
636    byte_count: u32,
637    user_buf_vaddr: u64,
638    write: bool,
639    ring_id: u64,
640    user_data: u64,
641) -> Result<(), AhciError> {
642    let controller = get_device().ok_or(AhciError::NoController)?;
643    let port = controller.port_by_num(port_idx).ok_or(AhciError::NoPort)?;
644
645    if lba >= port.sector_count {
646        return Err(AhciError::InvalidSector);
647    }
648
649    let count = ((byte_count as usize + SECTOR_SIZE - 1) / SECTOR_SIZE) as u16;
650    if count == 0 {
651        return Err(AhciError::BufferTooSmall);
652    }
653    let nbytes = (count as usize) * SECTOR_SIZE;
654
655    if write {
656        UserSliceRead::new(user_buf_vaddr, nbytes).map_err(|_| AhciError::InvalidUserBuffer)?;
657    } else {
658        UserSliceWrite::new(user_buf_vaddr, nbytes).map_err(|_| AhciError::InvalidUserBuffer)?;
659    }
660
661    let dma_buf = DmaBuffer::alloc(nbytes).map_err(|_| AhciError::Alloc)?;
662
663    let idx = port.port_num as usize;
664    let ata_cmd = if write {
665        ATA_WRITE_DMA_EXT
666    } else {
667        ATA_READ_DMA_EXT
668    };
669
670    // Store async metadata *before* issuing the command.
671    PORT_ASYNC_RING_ID[idx].store(ring_id, Ordering::Release);
672    PORT_ASYNC_USER_DATA[idx].store(user_data, Ordering::Release);
673    PORT_ASYNC_BUF_VADDR[idx].store(user_buf_vaddr, Ordering::Release);
674    PORT_ASYNC_LEN[idx].store(nbytes as u32, Ordering::Release);
675    PORT_ASYNC_IS_WRITE[idx].store(write, Ordering::Release);
676    PORT_ASYNC_ACTIVE[idx].store(true, Ordering::Release);
677
678    match submit_async_cmd(port, lba, count, write, ata_cmd, dma_buf) {
679        Ok(()) => Ok(()),
680        Err(e) => {
681            // Roll back async metadata on submission failure.
682            PORT_ASYNC_ACTIVE[idx].store(false, Ordering::Release);
683            PORT_ASYNC_RING_ID[idx].store(0, Ordering::Release);
684            PORT_ASYNC_USER_DATA[idx].store(0, Ordering::Release);
685            PORT_ASYNC_BUF_VADDR[idx].store(0, Ordering::Release);
686            PORT_ASYNC_LEN[idx].store(0, Ordering::Release);
687            PORT_ASYNC_IS_WRITE[idx].store(false, Ordering::Release);
688            PORT_ASYNC_DMA_PTR[idx].store(0, Ordering::Release);
689            Err(e)
690        }
691    }
692}
693
694pub(crate) fn flush_deferred_async_read_completions(ring_id: u64) -> u32 {
695    let Some(task) = crate::process::current_task_clone() else {
696        return 0;
697    };
698    let Some(ring) = crate::async_io::ring::find_ring(ring_id) else {
699        return discard_deferred_async_read_completions(ring_id);
700    };
701    if ring.owner_pid != task.pid {
702        return 0;
703    }
704    // If the ring was destroyed, discard all deferred reads for it rather
705    // than trying to push completions into a dead ring.
706    if ring.destroyed.load(core::sync::atomic::Ordering::Acquire) != 0 {
707        return discard_deferred_async_read_completions(ring_id);
708    }
709
710    let pending = {
711        let mut guard = PENDING_ASYNC_READ_COMPLETIONS.lock();
712        // Extract only entries for this ring_id; leave others in place
713        // (avoids the allocate + deallocate overhead of take + put-back).
714        let mut pending = Vec::new();
715        let mut i = 0;
716        while i < guard.len() {
717            if guard[i].ring_id == ring_id {
718                // swap_remove is O(1); completion ordering across rings
719                // is not guaranteed to userspace.
720                pending.push(guard.swap_remove(i));
721            } else {
722                i += 1;
723            }
724        }
725        pending
726    };
727
728    let mut flushed = 0;
729
730    for mut completion in pending {
731        // All entries in `pending` belong to `ring_id` (extracted by retain above).
732
733        if completion.result >= 0 {
734            completion.result = match UserSliceWrite::new(completion.user_buf_vaddr, completion.len)
735            {
736                Ok(user_buf) => {
737                    /*
738                    Original code before assembly TEST
739                                  let src = unsafe {
740                                            core::slice::from_raw_parts(
741                                                completion.dma_buf.virt_addr() as *const u8,
742                                                completion.len,
743                                            )
744                                        };
745                                        user_buf.copy_from(src);
746
747                    */
748
749                    let src = completion.dma_buf.virt_addr() as *const u8;
750                    let dst = user_buf.as_ptr() as *mut u8;
751                    let n = completion.len;
752
753                    if n >= 128 {
754                        // Fast path for sector-aligned copies : `rep movsb`
755                        // uses a single front-end uop and leverages ERMSB/FSRM hardware on modern x86-64 (Ivy Bridge+ /
756                        // Ice Lake+), outperforming a scalar loop for buffers >= 128 bytes.
757                        unsafe {
758                            core::arch::asm!(
759                                "rep movsb",
760                                inout("rcx") n => _,
761                                inout("rsi") src => _,
762                                inout("rdi") dst => _,
763                                options(nostack),
764                            );
765                        }
766                    } else {
767                        user_buf
768                            .copy_from(unsafe { core::slice::from_raw_parts(src, completion.len) });
769                    }
770                    completion.len as i32
771                }
772                Err(_) => EFAULT,
773            };
774        }
775
776        if crate::async_io::complete::push_completion_for_ring(
777            &ring,
778            completion.user_data,
779            completion.result,
780            0,
781        ) {
782            flushed += 1;
783        } else {
784            // Ring was destroyed between our check and the push : drop silently.
785        }
786    }
787
788    flushed
789}
790
791pub(crate) fn discard_deferred_async_read_completions(ring_id: u64) -> u32 {
792    let mut guard = PENDING_ASYNC_READ_COMPLETIONS.lock();
793    let before = guard.len();
794    guard.retain(|completion| completion.ring_id != ring_id);
795    (before - guard.len()) as u32
796}
797
798// ========== IRQ handler ==============================
799
800/// Called from the IDT AHCI IRQ handler.
801///
802/// Reads `HBA_IS` to find which ports raised an interrupt, reads and clears
803/// `PxIS` per port, then signals the per-port `WaitQueue` so that any task
804/// blocked in `submit_cmd` can resume.
805///
806/// # Safety of concurrent access
807/// All per-port statics (`PORT_SLOT0_DONE`, `PORT_SLOT0_ERROR`, `PORT_WQ`) are
808/// accessed via atomics or briefly-held SpinLocks (which disable IRQs).  The
809/// IRQ handler itself is not re-entrant (x86 APIC level-triggered delivery
810/// ensures this for the same vector).
811pub fn handle_interrupt() {
812    let abar = AHCI_ABAR_VIRT.load(Ordering::Relaxed);
813    if abar == 0 {
814        return; // controller not yet initialised
815    }
816
817    // SAFETY: abar is the MMIO-mapped AHCI base set during init
818    let global_is = unsafe { rd32(abar, HBA_IS) };
819    if global_is == 0 {
820        return; // spurious
821    }
822
823    for port_num in 0..32u8 {
824        if global_is & (1 << port_num) == 0 {
825            continue;
826        }
827
828        let pvirt = PORT_VIRT[port_num as usize].load(Ordering::Relaxed);
829        if pvirt == 0 {
830            continue; // port not in use
831        }
832
833        // SAFETY: pvirt is the valid MMIO address for this port, set during init
834        let pxis = unsafe { rd32(pvirt, PORT_IS) };
835
836        // Determine outcome and record in the error flag
837        if pxis & PXIS_TFES != 0 {
838            PORT_SLOT0_ERROR[port_num as usize].store(true, Ordering::Release);
839            // SAFETY: MMIO writes to clear error state
840            unsafe {
841                wr32(pvirt, PORT_IS, pxis);
842                wr32(pvirt, PORT_SERR, 0xFFFF_FFFF);
843            }
844        } else {
845            PORT_SLOT0_ERROR[port_num as usize].store(false, Ordering::Release);
846            // SAFETY: W1C : write back PxIS to clear all set bits
847            unsafe { wr32(pvirt, PORT_IS, pxis) };
848        }
849
850        // Clear this port's bit in global IS (W1C)
851        // SAFETY: MMIO write to HBA_IS
852        unsafe { wr32(abar, HBA_IS, 1 << port_num) };
853
854        // Signal command completion (always, for sync waiters)
855        PORT_SLOT0_DONE[port_num as usize].store(true, Ordering::Release);
856
857        // Async path : if an async operation was in-flight on this port,
858        // push a CQE into the registered ring and drop the DMA buffer.
859        let idx = port_num as usize;
860        if PORT_ASYNC_ACTIVE[idx].load(Ordering::Acquire) {
861            let ring_id = PORT_ASYNC_RING_ID[idx].load(Ordering::Acquire);
862            let user_data = PORT_ASYNC_USER_DATA[idx].load(Ordering::Acquire);
863            let buf_vaddr = PORT_ASYNC_BUF_VADDR[idx].load(Ordering::Acquire);
864            let nbytes = PORT_ASYNC_LEN[idx].load(Ordering::Acquire) as usize;
865            let is_write = PORT_ASYNC_IS_WRITE[idx].load(Ordering::Acquire);
866            let dma_ptr_val = PORT_ASYNC_DMA_PTR[idx].load(Ordering::Acquire);
867
868            // Clear the active flag : this port is now idle.
869            PORT_ASYNC_ACTIVE[idx].store(false, Ordering::Release);
870            PORT_ASYNC_RING_ID[idx].store(0, Ordering::Release);
871            PORT_ASYNC_USER_DATA[idx].store(0, Ordering::Release);
872            PORT_ASYNC_BUF_VADDR[idx].store(0, Ordering::Release);
873            PORT_ASYNC_LEN[idx].store(0, Ordering::Release);
874            PORT_ASYNC_IS_WRITE[idx].store(false, Ordering::Release);
875            PORT_ASYNC_DMA_PTR[idx].store(0, Ordering::Release);
876
877            if dma_ptr_val != 0 {
878                // Take ownership of the leaked DmaBuffer back.
879                let dma_buf = unsafe { *Box::from_raw(dma_ptr_val as *mut DmaBuffer) };
880
881                let result = if PORT_SLOT0_ERROR[idx].load(Ordering::Acquire) {
882                    -5i32 // EIO
883                } else {
884                    nbytes as i32
885                };
886
887                if is_write || result < 0 {
888                    crate::async_io::complete::push_completion(ring_id, user_data, result, 0);
889                } else {
890                    PENDING_ASYNC_READ_COMPLETIONS
891                        .lock()
892                        .push(PendingAsyncReadCompletion {
893                            ring_id,
894                            user_data,
895                            user_buf_vaddr: buf_vaddr,
896                            dma_buf,
897                            len: nbytes,
898                            result,
899                        });
900
901                    if let Some(ring) = crate::async_io::ring::find_ring(ring_id) {
902                        ring.wq.wake_all();
903                    }
904                }
905            }
906        } else {
907            PORT_WQ[idx].wake_one();
908        }
909    }
910}
911
912// ========== BlockDevice impl for AhciController ========================================================================================================================
913
914impl AhciController {
915    fn port_by_num(&self, port_num: u8) -> Option<&AhciPort> {
916        self.ports.iter().find(|port| port.port_num == port_num)
917    }
918
919    /// Probe and initialise an AHCI controller from the PCI bus.
920    ///
921    /// # Safety
922    /// Must be called once during single-threaded kernel init (MMIO mapping).
923    pub unsafe fn init() -> Result<Self, AhciError> {
924        // AHCI: class=0x01, subclass=0x06 (SATA), prog_if=0x01 (AHCI 1.0)
925        let pci_dev = pci::probe_first(ProbeCriteria {
926            class_code: Some(pci::class::MASS_STORAGE),
927            subclass: Some(pci::storage_subclass::SATA),
928            prog_if: Some(pci::sata_progif::AHCI),
929            ..ProbeCriteria::any()
930        })
931        .ok_or(AhciError::NoController)?;
932
933        log::info!("AHCI: found controller at {:?}", pci_dev.address);
934
935        // Enable bus-mastering and memory-space access (required for DMA)
936        pci_dev.enable_bus_master();
937        pci_dev.enable_memory_space();
938
939        // Read PCI interrupt line before we need it later
940        let irq_line = pci_dev.read_config_u8(pci::config::INTERRUPT_LINE);
941
942        // BAR5 = ABAR (AHCI Base Memory Register)
943        let abar_phys = pci_dev.read_bar_raw(5).ok_or(AhciError::BadAbar)?;
944        if abar_phys == 0 {
945            return Err(AhciError::BadAbar);
946        }
947
948        // Map the entire HBA register space (0x100 + 32 ports * 0x80 = 0x1100 bytes)
949        crate::memory::paging::ensure_identity_map_range(abar_phys, 0x1200);
950        let abar_virt = phys_to_virt(abar_phys);
951
952        // SAFETY: abar_virt is now a mapped MMIO virtual address
953        // Enable AHCI mode
954        let ghc = rd32(abar_virt, HBA_GHC);
955        if ghc & GHC_AE == 0 {
956            wr32(abar_virt, HBA_GHC, ghc | GHC_AE);
957        }
958
959        // Perform HBA reset sequence (HR), then re-enable AHCI.
960        let mut ghc_after = rd32(abar_virt, HBA_GHC) | GHC_AE;
961        wr32(abar_virt, HBA_GHC, ghc_after | GHC_HR);
962        let mut reset_timeout = 1_000_000u32;
963        while rd32(abar_virt, HBA_GHC) & GHC_HR != 0 {
964            reset_timeout = reset_timeout.saturating_sub(1);
965            if reset_timeout == 0 {
966                log::warn!("AHCI: HBA reset timed out, continuing with current state");
967                break;
968            }
969            core::hint::spin_loop();
970        }
971        ghc_after = rd32(abar_virt, HBA_GHC) | GHC_AE;
972        wr32(abar_virt, HBA_GHC, ghc_after);
973        // Clear global pending interrupts before per-port setup.
974        wr32(abar_virt, HBA_IS, 0xFFFF_FFFF);
975
976        log::debug!(
977            "AHCI: ABAR phys={:#x} virt={:#x}  GHC={:#010x}",
978            abar_phys,
979            abar_virt,
980            rd32(abar_virt, HBA_GHC)
981        );
982
983        let pi = rd32(abar_virt, HBA_PI); // bitmask of implemented ports
984        log::debug!("AHCI: ports implemented mask = {:#010x}", pi);
985
986        let mut ports: Vec<AhciPort> = Vec::new();
987
988        for port_num in 0..32u8 {
989            if pi & (1 << port_num) == 0 {
990                continue;
991            }
992
993            let pvirt = abar_virt + 0x100 + (port_num as u64) * 0x80;
994
995            // Check DET: only accept DET=3 (device present + communication)
996            let ssts = rd32(pvirt, PORT_SSTS);
997            let det = ssts & SSTS_DET_MASK;
998            if det != SSTS_DET_COMM {
999                log::debug!("AHCI: port {} DET={} : no device, skipping", port_num, det);
1000                continue;
1001            }
1002
1003            // Only handle plain SATA (signature 0x00000101)
1004            let sig = rd32(pvirt, PORT_SIG);
1005            if sig != SIG_SATA {
1006                log::debug!(
1007                    "AHCI: port {} sig={:#010x} : not plain SATA, skipping",
1008                    port_num,
1009                    sig
1010                );
1011                continue;
1012            }
1013
1014            // Allocate one 4 KB frame for CLB + FIS + CTAB
1015            let frame = crate::sync::with_irqs_disabled(|token| memory::allocate_frame(token))
1016                .map_err(|_| AhciError::Alloc)?;
1017
1018            let mem_phys = frame.start_address.as_u64();
1019            let mem_virt = phys_to_virt(mem_phys);
1020
1021            // Zero the frame so HBA sees clean structures
1022            // SAFETY: mem_virt is valid HHDM-mapped physical memory, 4096 bytes
1023            ptr::write_bytes(mem_virt as *mut u8, 0, 4096);
1024
1025            port_rebase(pvirt, mem_phys);
1026
1027            // Enable per-port interrupts (DHRE + TFEE)
1028            port_enable_irq(pvirt);
1029
1030            // Register this port's MMIO address in the per-port static table
1031            // so the IRQ handler can access it without holding the controller lock.
1032            PORT_VIRT[port_num as usize].store(pvirt, Ordering::Relaxed);
1033
1034            // Identify device to read sector count
1035            let mut port = AhciPort {
1036                port_num,
1037                port_virt: pvirt,
1038                mem_phys,
1039                mem_virt,
1040                sector_count: 0,
1041            };
1042
1043            let mut id_buf = [0u8; SECTOR_SIZE];
1044            match submit_cmd(&port, 0, 1, &mut id_buf, false, ATA_IDENTIFY) {
1045                Ok(()) => {
1046                    // Words 100-103 (bytes 200-207): 48-bit LBA native max address
1047                    let w0 = u16::from_le_bytes([id_buf[200], id_buf[201]]) as u64;
1048                    let w1 = u16::from_le_bytes([id_buf[202], id_buf[203]]) as u64;
1049                    let w2 = u16::from_le_bytes([id_buf[204], id_buf[205]]) as u64;
1050                    let w3 = u16::from_le_bytes([id_buf[206], id_buf[207]]) as u64;
1051                    port.sector_count = w0 | (w1 << 16) | (w2 << 32) | (w3 << 48);
1052                    log::info!(
1053                        "AHCI: port {} SATA : {} sectors ({} MiB)",
1054                        port_num,
1055                        port.sector_count,
1056                        (port.sector_count * SECTOR_SIZE as u64) / (1024 * 1024)
1057                    );
1058                }
1059                Err(e) => {
1060                    log::warn!("AHCI: port {} IDENTIFY failed: {}", port_num, e);
1061                }
1062            }
1063
1064            ports.push(port);
1065        }
1066
1067        if ports.is_empty() {
1068            return Err(AhciError::NoPort);
1069        }
1070
1071        // Store the ABAR virtual address and IRQ line in statics so the
1072        // interrupt handler can reach them without going through the controller lock.
1073        AHCI_ABAR_VIRT.store(abar_virt, Ordering::Relaxed);
1074        AHCI_IRQ_LINE.store(irq_line, Ordering::Relaxed);
1075
1076        // Enable global HBA interrupts (GHC.IE)
1077        // SAFETY: MMIO write : all port interrupts already enabled above
1078        let ghc = rd32(abar_virt, HBA_GHC);
1079        wr32(abar_virt, HBA_GHC, ghc | GHC_IE);
1080
1081        log::info!("AHCI: global interrupts enabled (IRQ line {})", irq_line);
1082
1083        Ok(AhciController { abar_virt, ports })
1084    }
1085
1086    /// Return sector count of the first port.
1087    pub fn sector_count(&self) -> u64 {
1088        self.ports.first().map(|p| p.sector_count).unwrap_or(0)
1089    }
1090
1091    /// Return the logical port number of the first usable port.
1092    pub fn first_port_num(&self) -> Option<u8> {
1093        self.ports.first().map(|port| port.port_num)
1094    }
1095
1096    /// Performs the first port operation.
1097    fn first_port(&self) -> Option<&AhciPort> {
1098        self.ports.first()
1099    }
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104    use super::{AhciController, AhciPort};
1105    use alloc::vec;
1106
1107    fn fake_port(port_num: u8) -> AhciPort {
1108        AhciPort {
1109            port_num,
1110            port_virt: 0,
1111            mem_phys: 0,
1112            mem_virt: 0,
1113            sector_count: 1024,
1114        }
1115    }
1116
1117    #[test]
1118    fn selects_requested_port_number() {
1119        let controller = AhciController {
1120            abar_virt: 0,
1121            ports: vec![fake_port(2), fake_port(5), fake_port(7)],
1122        };
1123
1124        assert_eq!(controller.port_by_num(5).map(|port| port.port_num), Some(5));
1125        assert_eq!(controller.port_by_num(1).map(|port| port.port_num), None);
1126        assert_eq!(controller.first_port_num(), Some(2));
1127    }
1128}
1129
1130impl BlockDevice for AhciController {
1131    /// Reads sector.
1132    fn read_sector(&self, sector: u64, buf: &mut [u8]) -> Result<(), BlockError> {
1133        let port = self.first_port().ok_or(BlockError::NotReady)?;
1134        if sector >= port.sector_count {
1135            return Err(BlockError::InvalidSector);
1136        }
1137        if buf.len() < SECTOR_SIZE {
1138            return Err(BlockError::BufferTooSmall);
1139        }
1140        submit_cmd(port, sector, 1, buf, false, ATA_READ_DMA_EXT).map_err(|_| BlockError::IoError)
1141    }
1142
1143    /// Writes sector.
1144    fn write_sector(&self, sector: u64, buf: &[u8]) -> Result<(), BlockError> {
1145        let port = self.first_port().ok_or(BlockError::NotReady)?;
1146        if sector >= port.sector_count {
1147            return Err(BlockError::InvalidSector);
1148        }
1149        if buf.len() < SECTOR_SIZE {
1150            return Err(BlockError::BufferTooSmall);
1151        }
1152        // submit_cmd needs &mut [u8]; copy to a mutable staging buffer
1153        let mut tmp = [0u8; SECTOR_SIZE];
1154        tmp.copy_from_slice(&buf[..SECTOR_SIZE]);
1155        submit_cmd(port, sector, 1, &mut tmp, true, ATA_WRITE_DMA_EXT)
1156            .map_err(|_| BlockError::IoError)
1157    }
1158
1159    /// Performs the sector count operation.
1160    fn sector_count(&self) -> u64 {
1161        self.sector_count()
1162    }
1163}
1164
1165// ========== Global singleton + public API ============================================================================================================================================
1166
1167static AHCI: SpinLock<Option<Box<AhciController>>> = SpinLock::new(None);
1168
1169/// Scan the PCI bus for an AHCI controller and initialise it.
1170///
1171/// Called once during kernel boot from `hardware::init()`.
1172pub fn init() {
1173    log::info!("AHCI: scanning PCI bus...");
1174
1175    match unsafe { AhciController::init() } {
1176        Ok(ctrl) => {
1177            *AHCI.lock() = Some(Box::new(ctrl));
1178            log::info!("AHCI: controller ready");
1179
1180            // Register IRQ handler in the IDT now that the controller is live.
1181            let irq = AHCI_IRQ_LINE.load(Ordering::Relaxed);
1182            crate::arch::x86_64::idt::register_ahci_irq(irq);
1183        }
1184        Err(AhciError::NoController) => {
1185            log::info!("AHCI: no controller found (not a SATA system?)");
1186        }
1187        Err(e) => {
1188            log::error!("AHCI: init failed: {}", e);
1189        }
1190    }
1191}
1192
1193/// Return a reference to the first usable AHCI controller, if any.
1194pub fn get_device() -> Option<&'static AhciController> {
1195    // SAFETY: the global Option is only ever set during init and never cleared
1196    unsafe {
1197        let lock = AHCI.lock();
1198        lock.as_ref().map(|b| {
1199            let ptr = b.as_ref() as *const AhciController;
1200            &*ptr
1201        })
1202    }
1203}