灵符lingfu

ling-lang

↓ 0 downloads

Ling - The Omniglot Systems Language

Keywords systems, polyglot, ai, crypto, game
lingfu install ling-lang
LicenseApache-2.0 OR MIT
AuthorsLing Lin, Sanny Lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (995)

crates/ling-kernel/src/drivers/ahci.rs
//! AHCI (SATA) disk driver — the modern block-storage interface most
//! current hardware and hypervisors default to (VirtualBox's default disk
//! controller is SATA/AHCI, not legacy IDE — confirmed as the actual cause
//! of `lingfs` working in QEMU, where the test disk was explicitly
//! attached via legacy IDE, but not in a VirtualBox VM whose disk was on
//! SATA: `ata.rs`'s PIO driver can only ever see an IDE-attached disk).
//!
//! Found via PCI (class 0x01 mass-storage, subclass 0x06 SATA, prog-if
//! 0x01 AHCI) rather than a fixed port range, since its MMIO register
//! window (the "ABAR") is a PCI BAR assigned by firmware, typically well
//! above the first 1GiB of RAM (near the top of 32-bit MMIO space) — see
//! `boot32.rs`'s 4GiB identity map, added specifically so this can reach
//! it without a proper memory manager. Single port, single command slot:
//! this is a "basic" kernel with one disk, not a general AHCI stack.
use crate::arch::pci;
use core::ptr;

pub const SECTOR_SIZE: usize = 512;

// HBA (host bus adapter) generic registers, byte offsets from ABAR.
const GHC: usize = 0x04;
const PI: usize = 0x0C;
const PORT_BASE: usize = 0x100;
const PORT_SIZE: usize = 0x80;

// Per-port register byte offsets, from PORT_BASE + port*PORT_SIZE.
const PX_CLB: usize = 0x00;
const PX_CLBU: usize = 0x04;
const PX_FB: usize = 0x08;
const PX_FBU: usize = 0x0C;
const PX_IS: usize = 0x10;
const PX_CMD: usize = 0x18;
const PX_TFD: usize = 0x20;
const PX_SIG: usize = 0x24;
const PX_SSTS: usize = 0x28;
const PX_CI: usize = 0x38;

const CMD_ST: u32 = 1 << 0;
const CMD_FRE: u32 = 1 << 4;
const CMD_FR: u32 = 1 << 14;
const CMD_CR: u32 = 1 << 15;

const ATA_CMD_READ_DMA_EXT: u8 = 0x25;
const ATA_CMD_WRITE_DMA_EXT: u8 = 0x35;
const FIS_TYPE_REG_H2D: u8 = 0x27;

const SATA_SIG_ATA: u32 = 0x0000_0101;

#[repr(C, align(1024))]
struct CommandList([u8; 32 * 32]);
#[repr(C, align(256))]
struct FisReceive([u8; 256]);
#[repr(C, align(128))]
struct CommandTable([u8; 0x80 + 16]); // FIS+ATAPI+reserved header, + 1 PRDT entry

static mut CMD_LIST: CommandList = CommandList([0; 32 * 32]);
static mut FIS_RECV: FisReceive = FisReceive([0; 256]);
static mut CMD_TABLE: CommandTable = CommandTable([0; 0x80 + 16]);

static mut ABAR: usize = 0;
static mut PORT: usize = usize::MAX;

fn reg_read(offset: usize) -> u32 {
    unsafe { ptr::read_volatile((ABAR + offset) as *const u32) }
}

fn reg_write(offset: usize, val: u32) {
    unsafe { ptr::write_volatile((ABAR + offset) as *mut u32, val) };
}

fn port_reg_read(offset: usize) -> u32 {
    reg_read(PORT_BASE + unsafe { PORT } * PORT_SIZE + offset)
}

fn port_reg_write(offset: usize, val: u32) {
    reg_write(PORT_BASE + unsafe { PORT } * PORT_SIZE + offset, val);
}

/// Find the AHCI controller (if any) via PCI, pick the first present SATA
/// (non-ATAPI) port, and bring it up: identity-mapped command list, FIS
/// receive area, and command table (all fixed, static — no heap). Returns
/// `Err` if no AHCI controller or no usable port was found, so callers can
/// fall back to `ata.rs`.
pub fn init() -> Result<(), ()> {
    let loc = pci::find(0x01, 0x06, 0x01).ok_or(())?;
    let abar = loc.bar(5);
    if abar == 0 {
        return Err(());
    }
    unsafe { ABAR = abar as usize };

    // AE: AHCI Enable — some controllers boot in a legacy-compatible mode
    // until this is set.
    reg_write(GHC, reg_read(GHC) | (1 << 31));

    let pi = reg_read(PI);
    let mut found_port = None;
    for port in 0..32usize {
        if pi & (1 << port) == 0 {
            continue;
        }
        unsafe { PORT = port };
        let ssts = port_reg_read(PX_SSTS);
        let det = ssts & 0x0F;
        if det != 3 {
            continue; // no device presence + PHY communication on this port
        }
        if port_reg_read(PX_SIG) != SATA_SIG_ATA {
            continue; // ATAPI or something else — not a plain disk
        }
        found_port = Some(port);
        break;
    }
    let Some(port) = found_port else {
        unsafe { PORT = usize::MAX };
        return Err(());
    };
    unsafe { PORT = port };

    stop_command_engine();

    let clb = &raw const CMD_LIST as u32;
    let fb = &raw const FIS_RECV as u32;
    port_reg_write(PX_CLB, clb);
    port_reg_write(PX_CLBU, 0);
    port_reg_write(PX_FB, fb);
    port_reg_write(PX_FBU, 0);

    start_command_engine();
    Ok(())
}

/// Bound on every AHCI register-bit poll below — a controller that never
/// clears FR/CR/BSY/DRQ (removed mid-command, or simply not present behind
/// a misdetected PCI BAR) must not hang the caller forever the way an
/// unbounded `while ... { pause() }` would. See `ata.rs`'s `ATA_TIMEOUT_US`
/// for the same reasoning on the legacy PIO path.
const AHCI_TIMEOUT_US: u64 = 5_000_000;

fn stop_command_engine() {
    let mut cmd = port_reg_read(PX_CMD);
    cmd &= !CMD_ST;
    cmd &= !CMD_FRE;
    port_reg_write(PX_CMD, cmd);
    crate::arch::timer::poll_until(AHCI_TIMEOUT_US, || {
        port_reg_read(PX_CMD) & (CMD_FR | CMD_CR) == 0
    });
}

fn start_command_engine() {
    crate::arch::timer::poll_until(AHCI_TIMEOUT_US, || port_reg_read(PX_CMD) & CMD_CR == 0);
    let mut cmd = port_reg_read(PX_CMD);
    cmd |= CMD_FRE;
    cmd |= CMD_ST;
    port_reg_write(PX_CMD, cmd);
}

/// Issue a single-sector READ DMA EXT (`write` = false) or WRITE DMA EXT
/// (`write` = true) at slot 0 against `buf` (exactly `SECTOR_SIZE` bytes,
/// physical == virtual since everything's identity-mapped). Polls
/// PxCI/task-file status rather than using interrupts, matching this
/// kernel's no-interrupt-handler model elsewhere (`ata.rs`, `keyboard.rs`).
fn issue(lba: u32, buf_ptr: *mut u8, write: bool) -> Result<(), ()> {
    if unsafe { PORT } == usize::MAX {
        return Err(());
    }

    unsafe {
        // Command header (slot 0): 32 bytes at CMD_LIST[0..32].
        let hdr = (&raw mut CMD_LIST).cast::<u8>();
        let cfl_w_prdtl: u32 = 5 // CFL: 20-byte FIS / 4 = 5 dwords
            | if write { 1 << 6 } else { 0 }
            | (1u32 << 16); // PRDTL = 1
        ptr::write_volatile(hdr as *mut u32, cfl_w_prdtl);
        ptr::write_volatile(hdr.add(4) as *mut u32, 0); // PRDBC, set by controller
        let ctba = &raw const CMD_TABLE as u32;
        ptr::write_volatile(hdr.add(8) as *mut u32, ctba);
        ptr::write_volatile(hdr.add(12) as *mut u32, 0);

        // Command FIS (Register H2D, 20 bytes) at the start of the command table.
        let cfis = (&raw mut CMD_TABLE).cast::<u8>();
        for i in 0..20 {
            ptr::write_volatile(cfis.add(i), 0);
        }
        ptr::write_volatile(cfis, FIS_TYPE_REG_H2D);
        ptr::write_volatile(cfis.add(1), 1 << 7); // bit7 = command (not control)
        ptr::write_volatile(
            cfis.add(2),
            if write {
                ATA_CMD_WRITE_DMA_EXT
            } else {
                ATA_CMD_READ_DMA_EXT
            },
        );
        ptr::write_volatile(cfis.add(4), lba as u8);
        ptr::write_volatile(cfis.add(5), (lba >> 8) as u8);
        ptr::write_volatile(cfis.add(6), (lba >> 16) as u8);
        ptr::write_volatile(cfis.add(7), 1 << 6); // device: LBA mode
        ptr::write_volatile(cfis.add(8), (lba >> 24) as u8);
        ptr::write_volatile(cfis.add(9), 0);
        ptr::write_volatile(cfis.add(10), 0);
        ptr::write_volatile(cfis.add(12), 1); // sector count low = 1
        ptr::write_volatile(cfis.add(13), 0);

        // PRDT entry 0, at command-table offset 0x80.
        let prdt = cfis.add(0x80);
        ptr::write_volatile(prdt as *mut u32, buf_ptr as u32);
        ptr::write_volatile(prdt.add(4) as *mut u32, 0);
        ptr::write_volatile(prdt.add(8) as *mut u32, 0);
        ptr::write_volatile(
            prdt.add(12) as *mut u32,
            (SECTOR_SIZE as u32 - 1) | (1 << 31),
        );
    }

    // Wait for the port to be idle (BSY/DRQ clear in the task-file data
    // register) before issuing — mirrors ata.rs's wait_ready/wait_drq
    // discipline for the same reason: don't step on an in-flight command.
    // Bounded (see `AHCI_TIMEOUT_US`'s doc) rather than trusting a port
    // that's wedged, or was never actually backed by a real drive, to ever
    // clear these bits on its own.
    if !crate::arch::timer::poll_until(AHCI_TIMEOUT_US, || port_reg_read(PX_TFD) & 0x88 == 0) {
        return Err(());
    }

    port_reg_write(PX_IS, u32::MAX); // clear any stale interrupt-status bits
    port_reg_write(PX_CI, 1); // issue slot 0

    let mut task_file_error = false;
    let completed = crate::arch::timer::poll_until(AHCI_TIMEOUT_US, || {
        if port_reg_read(PX_IS) & (1 << 30) != 0 {
            task_file_error = true;
            return true; // stop polling; caller distinguishes error below
        }
        port_reg_read(PX_CI) & 1 == 0
    });
    if task_file_error || !completed {
        return Err(());
    }
    Ok(())
}

pub fn read_sector(lba: u32, buf: &mut [u8; SECTOR_SIZE]) -> Result<(), ()> {
    issue(lba, buf.as_mut_ptr(), false)
}

pub fn write_sector(lba: u32, buf: &[u8; SECTOR_SIZE]) -> Result<(), ()> {
    issue(lba, buf.as_ptr() as *mut u8, true)
}

pub fn available() -> bool {
    unsafe { PORT != usize::MAX }
}

← Back to index