From 127b9d87b27cbcab50928718a2ad89db281df2af Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Thu, 12 Mar 2026 20:44:02 +0100 Subject: [PATCH 1/8] getting it on da thinkpad --- bootloader/src/storage.rs | 372 +++++++++++++++++------ compd/src/islands/input.rs | 2 +- network/src/boot/block_probe.rs | 40 ++- network/src/driver/ahci/mod.rs | 68 ++++- network/src/driver/intel/init.rs | 5 +- network/src/driver/intel/mod.rs | 10 +- network/src/lib.rs | 7 +- persistent/src/pe/embedded_reloc_data.rs | 164 +++++----- setup-dev.sh | 23 +- shelld/src/islands/launcher.rs | 4 +- 10 files changed, 488 insertions(+), 207 deletions(-) diff --git a/bootloader/src/storage.rs b/bootloader/src/storage.rs index ab217b83..39c7cab7 100644 --- a/bootloader/src/storage.rs +++ b/bootloader/src/storage.rs @@ -30,8 +30,8 @@ use morpheus_hwinit::paging::is_paging_initialized; use morpheus_hwinit::serial::{log_error, log_info, log_ok, log_warn, puts}; use morpheus_hwinit::{kmap_mmio, pci_cfg_read16, pci_cfg_read32, PciAddr}; use morpheus_network::{ - create_unified_from_detected, scan_all_block_devices, BlockDmaConfig, BlockDriver, - DetectedBlockDevice, UnifiedBlockDevice, UnifiedBlockIo, + create_unified_from_detected, create_unified_from_detected_ahci_port, scan_all_block_devices, + BlockDmaConfig, BlockDriver, DetectedBlockDevice, UnifiedBlockDevice, UnifiedBlockIo, }; // DMA LAYOUT CONSTANTS @@ -207,10 +207,145 @@ static mut STORAGE_DMA: Option = None; /// TSC frequency for timeout computation. static mut STORAGE_TSC_FREQ: u64 = 0; +/// Start LBA of selected persistent region (0 = whole disk). +static mut STORAGE_LBA_BASE: u64 = 0; +/// Sector count of selected persistent region. +static mut STORAGE_REGION_SECTORS: u64 = 0; /// Whether persistent storage was successfully initialized. static mut PERSISTENT_READY: bool = false; +const GPT_SIG: &[u8; 8] = b"EFI PART"; +// EFI System Partition type GUID on disk (little-endian fields). +const GPT_TYPE_ESP: [u8; 16] = [ + 0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11, 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, + 0x3B, +]; + +#[derive(Clone, Copy)] +struct DataRegion { + lba_start: u64, + sectors: u64, +} + +#[inline(always)] +fn le_u32(buf: &[u8], off: usize) -> u32 { + u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]) +} + +#[inline(always)] +fn le_u64(buf: &[u8], off: usize) -> u64 { + u64::from_le_bytes([ + buf[off], + buf[off + 1], + buf[off + 2], + buf[off + 3], + buf[off + 4], + buf[off + 5], + buf[off + 6], + buf[off + 7], + ]) +} + +/// Select a writable data region from the currently active block device. +/// +/// Policy: +/// - GPT disk: pick first non-ESP partition +/// - MBR-only disk: skip (treated as boot/system for now) +/// - No partition table: whole disk is data region +unsafe fn select_data_region(sector_size: u32, total_sectors: u64) -> Option { + let dev = BLOCK_DEVICE.as_mut()?; + let dma = STORAGE_DMA.as_ref()?; + + let io_cpu = dma.cpu_base().add(OFF_IO_BUFFER); + let io_phys = dma.bus_at(OFF_IO_BUFFER); + let io_buf = core::slice::from_raw_parts_mut(io_cpu, IO_BUFFER_SIZE); + let timeout = STORAGE_TSC_FREQ * 5; + + let mut bio = UnifiedBlockIo::new(dev, io_buf, io_phys, timeout).ok()?; + + use morpheus_network::GptBlockIo; + use morpheus_network::GptLba; + + let mut first_two = alloc::vec![0u8; (sector_size as usize) * 2]; + if bio.read_blocks(GptLba(0), &mut first_two).is_err() { + return None; + } + + let has_mbr = first_two.len() >= 512 && first_two[510] == 0x55 && first_two[511] == 0xAA; + let gpt_off = sector_size as usize; + let has_gpt = first_two.len() >= gpt_off + 8 && &first_two[gpt_off..gpt_off + 8] == GPT_SIG; + + if has_gpt { + let hdr = &first_two[gpt_off..gpt_off + sector_size as usize]; + let entries_lba = le_u64(hdr, 72); + let num_entries = le_u32(hdr, 80) as usize; + let entry_size = le_u32(hdr, 84) as usize; + + if entry_size < 56 || num_entries == 0 { + return None; + } + + let entries_per_sector = (sector_size as usize) / entry_size; + if entries_per_sector == 0 { + return None; + } + + let mut sec = alloc::vec![0u8; sector_size as usize]; + + for idx in 0..num_entries { + let sector_delta = idx / entries_per_sector; + let idx_in_sector = idx % entries_per_sector; + let lba = entries_lba + sector_delta as u64; + + if bio.read_blocks(GptLba(lba), &mut sec).is_err() { + return None; + } + + let off = idx_in_sector * entry_size; + let ent = &sec[off..off + entry_size]; + + // Empty partition entry if type GUID is all zero. + if ent[..16].iter().all(|b| *b == 0) { + continue; + } + + // Skip ESP partition. + if ent[..16] == GPT_TYPE_ESP { + continue; + } + + let first_lba = le_u64(ent, 32); + let last_lba = le_u64(ent, 40); + if first_lba == 0 || last_lba < first_lba { + continue; + } + + let sectors = last_lba - first_lba + 1; + if sectors == 0 { + continue; + } + + return Some(DataRegion { + lba_start: first_lba, + sectors, + }); + } + + return None; + } + + if has_mbr { + // Keep conservative behavior for plain MBR disks until partition parser exists. + return None; + } + + Some(DataRegion { + lba_start: 0, + sectors: total_sectors, + }) +} + // spinner static mut SPIN_ACTIVE: bool = false; @@ -340,8 +475,7 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { // Try each device: skip boot disks (GPT/MBR), use the first blank or HelixFS one. let mut found_data_disk = false; - #[allow(clippy::needless_range_loop)] - for i in 0..dev_count { + 'device_scan: for i in 0..dev_count { let detected = match &devices[i] { Some(d) => d, None => continue, @@ -352,115 +486,158 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { map_virtio_bars(pci_addr.bus, pci_addr.device, pci_addr.function); } - let device = match create_unified_from_detected(detected, &config) { - Ok(dev) => dev, - Err(_) => { - log_warn("STORAGE", 825, "driver init failed for one candidate; skipping"); - continue; + let is_ahci = matches!(detected, DetectedBlockDevice::Ahci(_)); + let mut ahci_port_attempted = false; + let mut ahci_port_found = false; + + let max_ports = if is_ahci { 32 } else { 1 }; + + for port in 0..max_ports { + let device = if is_ahci { + ahci_port_attempted = true; + match create_unified_from_detected_ahci_port(detected, &config, port as u32) { + Ok(dev) => dev, + Err(_) => continue, + } + } else { + match create_unified_from_detected(detected, &config) { + Ok(dev) => dev, + Err(_) => { + log_warn("STORAGE", 825, "driver init failed for one candidate; skipping"); + break; + } + } + }; + + if is_ahci { + ahci_port_found = true; } - }; - let info = device.info(); + let info = device.info(); - // Store temporarily to check if it's a boot disk. - BLOCK_DEVICE = Some(device); + // Store temporarily to check if it's a boot disk. + BLOCK_DEVICE = Some(device); - if is_boot_disk(info.sector_size) { - log_info("STORAGE", 826, "boot/system disk detected; skipping"); - BLOCK_DEVICE = None; - continue; - } + let region = match select_data_region(info.sector_size, info.total_sectors) { + Some(r) => r, + None => { + log_info("STORAGE", 826, "boot/system disk detected; skipping"); + BLOCK_DEVICE = None; + continue; + } + }; - // This device is NOT a boot disk — use it for HelixFS. - log_ok("STORAGE", 827, "selected data disk"); - found_data_disk = true; - - // try to recover or format helixfs - let mut raw_dev = make_raw_block_device(); - - let needs_format = { - let mut probe_dev = make_raw_block_device(); - match morpheus_helix::log::recovery::recover_superblock( - &mut probe_dev, - 0, - info.sector_size, - ) { - Ok(sb) => { - // Version mismatch: v2 changed the log payload format. - if sb.version != morpheus_helix::types::HELIX_VERSION { - log_warn("STORAGE", 828, "helixfs version mismatch; reformat required"); - true - } else { - false + STORAGE_LBA_BASE = region.lba_start; + STORAGE_REGION_SECTORS = region.sectors; + + if region.lba_start != 0 { + log_info("STORAGE", 837, "selected GPT data partition region"); + } + + if region.sectors == 0 { + BLOCK_DEVICE = None; + continue; + } + + // This device is NOT a boot disk — use it for HelixFS. + log_ok("STORAGE", 827, "selected data disk"); + found_data_disk = true; + + // try to recover or format helixfs + let mut raw_dev = make_raw_block_device(); + + let needs_format = { + let mut probe_dev = make_raw_block_device(); + match morpheus_helix::log::recovery::recover_superblock( + &mut probe_dev, + 0, + info.sector_size, + ) { + Ok(sb) => { + // Version mismatch: v2 changed the log payload format. + if sb.version != morpheus_helix::types::HELIX_VERSION { + log_warn("STORAGE", 828, "helixfs version mismatch; reformat required"); + true + } else { + false + } + } + Err(_) => true, + } + }; + + if needs_format { + log_info("STORAGE", 829, "no valid helixfs; formatting disk"); + + let uuid = [ + 0x4Du8, 0x58, 0x52, 0x4F, 0x4F, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + ]; + spinner_start(); + match morpheus_helix::format::format_helix( + &mut raw_dev, + 0, + info.total_sectors, + info.sector_size, + "root", + uuid, + ) { + Ok(_sb) => { + spinner_done(); + log_ok("STORAGE", 830, "format completed"); + } + Err(e) => { + spinner_done(); + let _ = e; + log_error("STORAGE", 831, "format failed"); + BLOCK_DEVICE = None; + continue; } } - Err(_) => true, - } - }; - if needs_format { - log_info("STORAGE", 829, "no valid helixfs; formatting disk"); + // Verify format succeeded + match morpheus_helix::log::recovery::recover_superblock( + &mut raw_dev, + 0, + info.sector_size, + ) { + Ok(_sb) => {} + Err(e) => { + let _ = e; + log_warn("STORAGE", 832, "superblock readback failed after format"); + } + } + } else { + log_info("STORAGE", 833, "valid helixfs found; mounting"); + } - let uuid = [ - 0x4Du8, 0x58, 0x52, 0x4F, 0x4F, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, - ]; + // Now do the actual replace_root_device + // If we already formatted above, pass do_format=false to avoid double-format + let mount_dev = make_raw_block_device(); spinner_start(); - match morpheus_helix::format::format_helix( - &mut raw_dev, - 0, - info.total_sectors, - info.sector_size, - "root", - uuid, - ) { - Ok(_sb) => { + match morpheus_helix::vfs::global::replace_root_device(mount_dev, false) { + Ok(()) => { spinner_done(); - log_ok("STORAGE", 830, "format completed"); + PERSISTENT_READY = true; + log_ok("STORAGE", 834, "persistent root filesystem mounted at /"); + break 'device_scan; } Err(e) => { spinner_done(); let _ = e; - log_error("STORAGE", 831, "format failed"); + log_error("STORAGE", 835, "failed to mount persistent filesystem"); BLOCK_DEVICE = None; - break; + continue; } } - - // Verify format succeeded - match morpheus_helix::log::recovery::recover_superblock( - &mut raw_dev, - 0, - info.sector_size, - ) { - Ok(_sb) => {} - Err(e) => { - let _ = e; - log_warn("STORAGE", 832, "superblock readback failed after format"); - } - } - } else { - log_info("STORAGE", 833, "valid helixfs found; mounting"); } - // Now do the actual replace_root_device - // If we already formatted above, pass do_format=false to avoid double-format - let mount_dev = make_raw_block_device(); - spinner_start(); - match morpheus_helix::vfs::global::replace_root_device(mount_dev, false) { - Ok(()) => { - spinner_done(); - PERSISTENT_READY = true; - log_ok("STORAGE", 834, "persistent root filesystem mounted at /"); - } - Err(e) => { - spinner_done(); - let _ = e; - log_error("STORAGE", 835, "failed to mount persistent filesystem"); - BLOCK_DEVICE = None; + if is_ahci { + if ahci_port_attempted && !ahci_port_found { + log_warn("STORAGE", 825, "driver init failed for one candidate; skipping"); } + continue; } - break; } if !found_data_disk { @@ -576,10 +753,15 @@ pub fn create_init_directories() { unsafe fn make_raw_block_device() -> RawBlockDevice { let dev = BLOCK_DEVICE.as_ref().unwrap(); let info = dev.info(); + let total = if STORAGE_REGION_SECTORS != 0 { + STORAGE_REGION_SECTORS + } else { + info.total_sectors + }; RawBlockDevice::new( core::ptr::null_mut(), // ctx unused — we access statics directly - info.total_sectors, + total, info.sector_size, raw_read, raw_write, @@ -616,7 +798,8 @@ unsafe fn raw_read(_ctx: *mut u8, lba: u64, dst: *mut u8, len: usize) -> bool { use morpheus_network::GptBlockIo; use morpheus_network::GptLba; - bio.read_blocks(GptLba(lba), dst_slice).is_ok() + bio.read_blocks(GptLba(lba + STORAGE_LBA_BASE), dst_slice) + .is_ok() } /// Write callback for `RawBlockDevice`. @@ -645,7 +828,8 @@ unsafe fn raw_write(_ctx: *mut u8, lba: u64, src: *const u8, len: usize) -> bool use morpheus_network::GptBlockIo; use morpheus_network::GptLba; - bio.write_blocks(GptLba(lba), src_slice).is_ok() + bio.write_blocks(GptLba(lba + STORAGE_LBA_BASE), src_slice) + .is_ok() } /// Flush callback for `RawBlockDevice`. diff --git a/compd/src/islands/input.rs b/compd/src/islands/input.rs index c4967f4e..5f06d79b 100644 --- a/compd/src/islands/input.rs +++ b/compd/src/islands/input.rs @@ -115,7 +115,7 @@ fn route_mouse_spatial(state: &mut CompState, msg: MouseSpatialMsg) { match region { HitRegion::Close => { if let Some(ref win) = state.windows[idx] { - let _ = process::kill(win.pid, process::signal::SIGKILL); + let _ = process::kill(win.pid, process::signal::SIGTERM); } state.capture = None; route_to_child = false; diff --git a/network/src/boot/block_probe.rs b/network/src/boot/block_probe.rs index 696fb8eb..f65c3db2 100644 --- a/network/src/boot/block_probe.rs +++ b/network/src/boot/block_probe.rs @@ -69,8 +69,11 @@ const VIRTIO_BLK_DEVICE_LEGACY: u16 = 0x1001; /// VirtIO-blk device ID (modern) const VIRTIO_BLK_DEVICE_MODERN: u16 = 0x1042; -/// PCI Class code for SATA AHCI controller -const PCI_CLASS_SATA_AHCI: u32 = 0x0106; +/// PCI subclass/prog-if for SATA AHCI controller (0x06/0x01). +/// +/// We compare against `(class_code >> 8) & 0xFFFF`, which yields +/// subclass:prog_if (not class:subclass). +const PCI_CLASS_SATA_AHCI: u32 = 0x0601; // ═══════════════════════════════════════════════════════════════════════════ // PROBE ERRORS @@ -824,6 +827,39 @@ pub unsafe fn create_unified_from_detected( } } +/// Create a unified block device from a specific AHCI port on a detected controller. +/// +/// Returns `NoDevice` for non-AHCI devices. +/// +/// # Safety +/// Same as `probe_unified_block_device`. +pub unsafe fn create_unified_from_detected_ahci_port( + detected: &DetectedBlockDevice, + config: &BlockDmaConfig, + port_num: u32, +) -> Result { + match *detected { + DetectedBlockDevice::Ahci(info) => { + enable_pci_device(info.pci_addr); + let ahci_config = AhciConfig { + tsc_freq: config.tsc_freq, + cmd_list_cpu: config.ahci_cmd_list_cpu, + cmd_list_phys: config.ahci_cmd_list_phys, + fis_cpu: config.ahci_fis_cpu, + fis_phys: config.ahci_fis_phys, + cmd_tables_cpu: config.ahci_cmd_tables_cpu, + cmd_tables_phys: config.ahci_cmd_tables_phys, + identify_cpu: config.ahci_identify_cpu, + identify_phys: config.ahci_identify_phys, + }; + let driver = AhciDriver::new_on_port(info.abar, ahci_config, port_num) + .map_err(|_| UnifiedBlockError::NoDevice)?; + Ok(UnifiedBlockDevice::Ahci(driver)) + } + _ => Err(UnifiedBlockError::NoDevice), + } +} + // ═══════════════════════════════════════════════════════════════════════════ // DEVICE TYPE DETECTION // ═══════════════════════════════════════════════════════════════════════════ diff --git a/network/src/driver/ahci/mod.rs b/network/src/driver/ahci/mod.rs index 70becfed..15b8d22a 100644 --- a/network/src/driver/ahci/mod.rs +++ b/network/src/driver/ahci/mod.rs @@ -143,6 +143,7 @@ pub const AHCI_DEVICE_WPT_LP: u16 = 0x9C83; /// Other supported AHCI device IDs pub const AHCI_DEVICE_IDS: &[u16] = &[ + 0x2922, // ICH9 AHCI (QEMU ich9-ahci) 0x9C83, // Wildcat Point-LP (T450s) 0x9C03, // Lynx Point-LP 0x8C02, // 8 Series/C220 @@ -237,6 +238,28 @@ impl AhciDriver { /// - `abar` must be valid AHCI MMIO address (from BAR5) /// - `config` must contain valid DMA memory pointers/addresses pub unsafe fn new(abar: u64, config: AhciConfig) -> Result { + Self::new_inner(abar, config, None) + } + + /// Create and initialize AHCI driver on a specific port. + /// + /// # Safety + /// - `abar` must be valid AHCI MMIO address (from BAR5) + /// - `config` must contain valid DMA memory pointers/addresses + /// - `port_num` must be an AHCI port index (0..31) + pub unsafe fn new_on_port( + abar: u64, + config: AhciConfig, + port_num: u32, + ) -> Result { + Self::new_inner(abar, config, Some(port_num)) + } + + unsafe fn new_inner( + abar: u64, + config: AhciConfig, + forced_port: Option, + ) -> Result { // Validate config if config.cmd_list_cpu.is_null() || config.fis_cpu.is_null() { return Err(AhciInitError::InvalidConfig); @@ -268,29 +291,44 @@ impl AhciDriver { asm_ahci_disable_interrupts(abar); // ═══════════════════════════════════════════════════════════════════ - // STEP 4: Find first port with an attached ATA device + // STEP 4: Select ATA port // ═══════════════════════════════════════════════════════════════════ - let mut active_port: Option = None; - - for port in 0..32u32 { - if (ports_impl & (1 << port)) == 0 { - continue; // Port not implemented + let port_num = if let Some(port) = forced_port { + if port >= 32 || (ports_impl & (1 << port)) == 0 { + return Err(AhciInitError::NoDeviceFound); } - let det = asm_ahci_port_detect(abar, port); if det != DET_PHY_COMM { - continue; // No device or not ready + return Err(AhciInitError::NoDeviceFound); } - - // Check signature let sig = asm_ahci_port_read_sig(abar, port); - if sig == SIG_ATA { - active_port = Some(port); - break; + if sig != SIG_ATA { + return Err(AhciInitError::NoDeviceFound); + } + port + } else { + let mut active_port: Option = None; + + for port in 0..32u32 { + if (ports_impl & (1 << port)) == 0 { + continue; // Port not implemented + } + + let det = asm_ahci_port_detect(abar, port); + if det != DET_PHY_COMM { + continue; // No device or not ready + } + + // Check signature + let sig = asm_ahci_port_read_sig(abar, port); + if sig == SIG_ATA { + active_port = Some(port); + break; + } } - } - let port_num = active_port.ok_or(AhciInitError::NoDeviceFound)?; + active_port.ok_or(AhciInitError::NoDeviceFound)? + }; // ═══════════════════════════════════════════════════════════════════ // STEP 5: Stop port and configure DMA structures diff --git a/network/src/driver/intel/init.rs b/network/src/driver/intel/init.rs index 99f5b222..0b785c9a 100644 --- a/network/src/driver/intel/init.rs +++ b/network/src/driver/intel/init.rs @@ -287,9 +287,8 @@ pub unsafe fn init_e1000e( write32(mmio_base + regs::TDT as u64, 0); let _ = read32(mmio_base + regs::STATUS as u64); // flush - // Clear RAR[0] - don't trust firmware MAC, will reprogram later - write32(mmio_base + regs::RAL0 as u64, 0); - write32(mmio_base + regs::RAH0 as u64, 0); + // Keep RAR[0] intact until Phase 8 MAC read. + // Some parts expose MAC via RAL/RAH + AV and EEPROM path can be fragile. // Clear loopback mode explicitly let rctl = read32(mmio_base + regs::RCTL as u64); diff --git a/network/src/driver/intel/mod.rs b/network/src/driver/intel/mod.rs index e259f002..c522224c 100644 --- a/network/src/driver/intel/mod.rs +++ b/network/src/driver/intel/mod.rs @@ -30,8 +30,10 @@ pub const INTEL_VENDOR_ID: u16 = 0x8086; /// This list covers the most common Intel GbE controllers found in /// ThinkPads and QEMU emulation. pub const E1000E_DEVICE_IDS: &[u16] = &[ + 0x100E, // 82540EM (QEMU e1000) 0x1502, // I218-LM (ThinkPad T450s, T440s, X240, etc.) 0x1503, // I218-V (Consumer variant) + 0x155A, // I218-LM (ThinkPad T450s variant) 0x10D3, // 82574L (QEMU e1000e emulation) 0x10EA, // 82577LM 0x10EB, // 82577LC @@ -55,11 +57,11 @@ pub fn is_supported_device(vendor_id: u16, device_id: u16) -> bool { vendor_id == INTEL_VENDOR_ID && E1000E_DEVICE_IDS.contains(&device_id) } -/// PCI class code for Ethernet controller. -pub const PCI_CLASS_NETWORK_ETHERNET: u32 = 0x020000; +/// PCI class code for Ethernet controller (class:subclass at bits 31:16). +pub const PCI_CLASS_NETWORK_ETHERNET: u32 = 0x0200_0000; -/// Mask for PCI class code (ignore revision). -pub const PCI_CLASS_MASK: u32 = 0xFFFF00; +/// Mask for PCI class/subclass (ignore prog-if and revision). +pub const PCI_CLASS_MASK: u32 = 0xFFFF_0000; use crate::pci::config::{offset, pci_cfg_read16, pci_cfg_read32, PciAddr}; diff --git a/network/src/lib.rs b/network/src/lib.rs index 66039c5f..c29cd07f 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -132,9 +132,10 @@ pub use gpt_disk_types::{BlockSize as GptBlockSize, Lba as GptLba}; // Block probe pub use boot::block_probe::{ - create_unified_from_detected, detect_block_device_type, probe_and_create_block_driver, - probe_unified_block_device, scan_all_block_devices, BlockDeviceType, BlockDmaConfig, - BlockProbeError, BlockProbeResult, DetectedBlockDevice, + create_unified_from_detected, create_unified_from_detected_ahci_port, + detect_block_device_type, probe_and_create_block_driver, probe_unified_block_device, + scan_all_block_devices, BlockDeviceType, BlockDmaConfig, BlockProbeError, BlockProbeResult, + DetectedBlockDevice, }; // Client diff --git a/persistent/src/pe/embedded_reloc_data.rs b/persistent/src/pe/embedded_reloc_data.rs index a185047e..ddd5e3c8 100644 --- a/persistent/src/pe/embedded_reloc_data.rs +++ b/persistent/src/pe/embedded_reloc_data.rs @@ -17,19 +17,19 @@ pub const RELOC_SIZE: u32 = 0x00000464; pub const ORIGINAL_IMAGE_BASE: u64 = 0x0000004001000000; /// Hardcoded .reloc section data (1124 bytes) -/// Extracted from morpheus-bootloader.efi at file offset 0x001f4a00 +/// Extracted from morpheus-bootloader.efi at file offset 0x001f4e00 #[allow(dead_code)] pub const RELOC_DATA: [u8; 1124] = [ - 0x00, 0xb0, 0x06, 0x00, 0x64, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, + 0x00, 0xb0, 0x06, 0x00, 0x38, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, - 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0xe8, 0xa7, - 0x00, 0xa8, 0x18, 0xa8, 0x30, 0xa8, 0x48, 0xa8, 0x60, 0xa8, 0x78, 0xa8, - 0x90, 0xa8, 0xa8, 0xa8, 0x28, 0xa9, 0x40, 0xa9, 0x58, 0xa9, 0x70, 0xa9, - 0x88, 0xa9, 0xa0, 0xa9, 0xb8, 0xa9, 0xd0, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, - 0x18, 0xaa, 0x30, 0xaa, 0x48, 0xaa, 0x60, 0xaa, 0x78, 0xaa, 0x90, 0xaa, - 0xa8, 0xaa, 0x28, 0xab, 0x40, 0xab, 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, - 0x08, 0xac, 0x78, 0xaf, 0x00, 0xc0, 0x06, 0x00, 0x38, 0x00, 0x00, 0x00, - 0xe8, 0xa3, 0x80, 0xa6, 0x98, 0xa6, 0xb0, 0xa6, 0xf8, 0xa8, 0x00, 0xaa, + 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0xd0, 0xa9, + 0xd8, 0xaa, 0x20, 0xaf, 0x38, 0xaf, 0x50, 0xaf, 0x68, 0xaf, 0x80, 0xaf, + 0x98, 0xaf, 0xb0, 0xaf, 0xc8, 0xaf, 0xe0, 0xaf, 0x00, 0xc0, 0x06, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x60, 0xa0, 0x78, 0xa0, 0x90, 0xa0, 0xa8, 0xa0, + 0xc0, 0xa0, 0xd8, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, 0x20, 0xa1, 0x38, 0xa1, + 0x50, 0xa1, 0x68, 0xa1, 0x80, 0xa1, 0x98, 0xa1, 0xb0, 0xa1, 0xc8, 0xa1, + 0xe0, 0xa1, 0x60, 0xa2, 0x78, 0xa2, 0x90, 0xa2, 0xa8, 0xa2, 0xc0, 0xa2, + 0x40, 0xa3, 0xc8, 0xa4, 0x60, 0xa7, 0x78, 0xa7, 0x90, 0xa7, 0xa8, 0xa9, 0xe8, 0xab, 0x00, 0xac, 0x18, 0xac, 0x70, 0xac, 0x88, 0xac, 0xa0, 0xac, 0x20, 0xad, 0x38, 0xad, 0x50, 0xad, 0x50, 0xae, 0x68, 0xae, 0x80, 0xae, 0x98, 0xae, 0xb0, 0xae, 0xc8, 0xae, 0x48, 0xaf, 0x60, 0xaf, 0xe8, 0xaf, @@ -37,78 +37,78 @@ pub const RELOC_DATA: [u8; 1124] = [ 0x78, 0xa0, 0xd8, 0xa0, 0x58, 0xa1, 0x70, 0xa1, 0x88, 0xa1, 0xc8, 0xa1, 0xd8, 0xa1, 0x20, 0xa2, 0x38, 0xa2, 0x50, 0xa2, 0x68, 0xa2, 0x08, 0xa3, 0xd8, 0xa5, 0xf0, 0xa5, 0x30, 0xa6, 0x48, 0xa6, 0x60, 0xa6, 0x78, 0xa6, - 0x00, 0xa7, 0x18, 0xa7, 0xd8, 0xa7, 0x08, 0xa8, 0x68, 0xa8, 0x80, 0xa8, - 0xe0, 0xa8, 0xf8, 0xa8, 0x10, 0xa9, 0x28, 0xa9, 0x40, 0xa9, 0x58, 0xa9, - 0x90, 0xa9, 0xc0, 0xaa, 0xf8, 0xab, 0x10, 0xac, 0x60, 0xac, 0xd8, 0xac, - 0xf0, 0xac, 0x08, 0xad, 0x20, 0xad, 0x38, 0xad, 0x50, 0xad, 0x68, 0xad, - 0x80, 0xad, 0x98, 0xad, 0xb0, 0xad, 0xc8, 0xad, 0xe0, 0xad, 0xf8, 0xad, - 0x10, 0xae, 0x28, 0xae, 0x90, 0xae, 0xa8, 0xae, 0xc0, 0xae, 0xd8, 0xae, - 0xf0, 0xae, 0x08, 0xaf, 0x20, 0xaf, 0x38, 0xaf, 0x50, 0xaf, 0x68, 0xaf, - 0x80, 0xaf, 0xd0, 0xaf, 0xe0, 0xaf, 0x00, 0x00, 0x00, 0xe0, 0x06, 0x00, - 0x8c, 0x00, 0x00, 0x00, 0x38, 0xa0, 0x48, 0xa0, 0x28, 0xa2, 0x40, 0xa2, - 0x58, 0xa2, 0x70, 0xa2, 0xf0, 0xa2, 0x08, 0xa3, 0x20, 0xa3, 0x38, 0xa3, - 0x50, 0xa3, 0x68, 0xa3, 0x80, 0xa3, 0x98, 0xa3, 0xb0, 0xa3, 0xe0, 0xa3, - 0x80, 0xa4, 0x98, 0xa4, 0x18, 0xa5, 0x98, 0xa5, 0xb0, 0xa5, 0xc8, 0xa5, - 0xe0, 0xa5, 0xf8, 0xa5, 0x10, 0xa6, 0x98, 0xa6, 0xc0, 0xa7, 0xd0, 0xa7, - 0xe0, 0xa7, 0x58, 0xa8, 0x70, 0xa8, 0x88, 0xa8, 0xa0, 0xa8, 0xb8, 0xa8, - 0xd0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0x30, 0xa9, 0x48, 0xa9, - 0x60, 0xa9, 0x78, 0xa9, 0x90, 0xa9, 0xa8, 0xa9, 0xc0, 0xa9, 0x68, 0xab, - 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, 0xe0, 0xab, 0xf8, 0xab, - 0x10, 0xac, 0x28, 0xac, 0xb0, 0xac, 0xf0, 0xac, 0x00, 0xad, 0xc0, 0xad, - 0x50, 0xae, 0x90, 0xae, 0xd8, 0xae, 0x70, 0xaf, 0xb8, 0xaf, 0xc8, 0xaf, - 0xd8, 0xaf, 0xe8, 0xaf, 0x00, 0xf0, 0x06, 0x00, 0x5c, 0x00, 0x00, 0x00, - 0x68, 0xa0, 0x80, 0xa0, 0x00, 0xa1, 0xa0, 0xa1, 0xb0, 0xa1, 0xc0, 0xa1, - 0x58, 0xa2, 0x90, 0xa2, 0xa8, 0xa2, 0xc0, 0xa2, 0xd8, 0xa2, 0x58, 0xa3, - 0x98, 0xa3, 0xb0, 0xa3, 0xf0, 0xa4, 0x20, 0xa5, 0x38, 0xa5, 0x90, 0xa5, - 0xa8, 0xa5, 0xf8, 0xa5, 0x10, 0xa6, 0x28, 0xa6, 0x60, 0xa6, 0x90, 0xa6, - 0xc8, 0xa6, 0xc0, 0xa8, 0xd8, 0xa8, 0x70, 0xa9, 0x88, 0xa9, 0xa0, 0xa9, - 0xb8, 0xa9, 0xd0, 0xa9, 0xe0, 0xaa, 0x40, 0xab, 0xb8, 0xab, 0xb0, 0xac, - 0xc8, 0xac, 0xe0, 0xac, 0xf8, 0xac, 0x10, 0xad, 0x40, 0xad, 0x60, 0xad, - 0x00, 0x00, 0x07, 0x00, 0x50, 0x00, 0x00, 0x00, 0xa8, 0xa2, 0xc0, 0xa2, - 0xd8, 0xa2, 0xf0, 0xa2, 0x08, 0xa3, 0x20, 0xa3, 0x40, 0xa4, 0x58, 0xa4, - 0x70, 0xa4, 0xe8, 0xa4, 0x20, 0xa5, 0x90, 0xa7, 0xa8, 0xa7, 0xc0, 0xa7, - 0xd8, 0xa7, 0xf0, 0xa7, 0x08, 0xa8, 0x20, 0xa8, 0x58, 0xa8, 0x68, 0xa9, - 0xa8, 0xaa, 0x10, 0xab, 0x30, 0xab, 0x48, 0xab, 0x60, 0xab, 0x78, 0xab, - 0x90, 0xab, 0x98, 0xac, 0x08, 0xad, 0x20, 0xad, 0x38, 0xad, 0x50, 0xad, - 0x68, 0xad, 0x80, 0xad, 0xc0, 0xad, 0x50, 0xae, 0x00, 0x10, 0x07, 0x00, - 0x50, 0x00, 0x00, 0x00, 0xe8, 0xa8, 0x18, 0xaa, 0x70, 0xab, 0x80, 0xab, - 0x90, 0xab, 0xa0, 0xab, 0xb0, 0xab, 0xc0, 0xab, 0xd0, 0xab, 0xe0, 0xab, - 0xf0, 0xab, 0x00, 0xac, 0x10, 0xac, 0x20, 0xac, 0x30, 0xac, 0x40, 0xac, - 0x50, 0xac, 0x60, 0xac, 0x70, 0xac, 0x80, 0xac, 0x90, 0xac, 0xa0, 0xac, - 0xb0, 0xac, 0xc0, 0xac, 0xd0, 0xac, 0xe0, 0xac, 0xf0, 0xac, 0x00, 0xad, - 0x10, 0xad, 0x20, 0xad, 0x30, 0xad, 0x40, 0xad, 0x50, 0xad, 0x60, 0xad, - 0x40, 0xaf, 0x00, 0x00, 0x00, 0x30, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x20, 0xa1, 0x78, 0xa1, 0xe8, 0xa2, 0x00, 0xa3, 0x50, 0xa3, 0x60, 0xa3, - 0x70, 0xa3, 0x80, 0xa3, 0x90, 0xa3, 0xc0, 0xa3, 0xf8, 0xa3, 0x40, 0xa4, - 0x90, 0xa4, 0xc8, 0xa4, 0xe0, 0xa4, 0x28, 0xa5, 0xb0, 0xa5, 0x00, 0x00, - 0x00, 0x40, 0x07, 0x00, 0x54, 0x00, 0x00, 0x00, 0x68, 0xa2, 0x80, 0xa2, - 0xb8, 0xa2, 0xe8, 0xa2, 0x00, 0xa3, 0x18, 0xa3, 0x30, 0xa3, 0x48, 0xa3, - 0x60, 0xa3, 0x78, 0xa3, 0x90, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, - 0x18, 0xa4, 0x30, 0xa4, 0x68, 0xa4, 0x98, 0xa4, 0xb0, 0xa4, 0xc8, 0xa4, - 0xe0, 0xa4, 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, - 0x90, 0xa5, 0x20, 0xa6, 0x68, 0xac, 0x78, 0xac, 0xa8, 0xac, 0xc0, 0xac, - 0x08, 0xad, 0x18, 0xad, 0x28, 0xad, 0x50, 0xad, 0x88, 0xad, 0x00, 0x00, - 0x00, 0x50, 0x07, 0x00, 0x50, 0x00, 0x00, 0x00, 0xc8, 0xa2, 0xd8, 0xa2, - 0xe8, 0xa2, 0xf8, 0xa2, 0x48, 0xa3, 0x58, 0xa3, 0x68, 0xa3, 0x78, 0xa3, - 0x88, 0xa3, 0xb0, 0xa3, 0xc0, 0xa3, 0xd0, 0xa3, 0x38, 0xa4, 0x48, 0xa4, - 0x58, 0xa4, 0xa0, 0xa4, 0xb0, 0xa4, 0xe8, 0xa4, 0xf8, 0xa4, 0x20, 0xa5, - 0x30, 0xa5, 0x68, 0xa5, 0x80, 0xa5, 0xd8, 0xa5, 0x48, 0xac, 0x60, 0xac, - 0x98, 0xac, 0xe8, 0xac, 0x30, 0xad, 0x40, 0xad, 0xc8, 0xae, 0x00, 0xaf, - 0x18, 0xaf, 0xe0, 0xaf, 0xf8, 0xaf, 0x00, 0x00, 0x00, 0x60, 0x07, 0x00, - 0x90, 0x00, 0x00, 0x00, 0xa8, 0xa0, 0xc0, 0xa0, 0x10, 0xa1, 0x20, 0xa1, - 0x50, 0xa1, 0x88, 0xa1, 0x98, 0xa1, 0xe8, 0xa1, 0xf8, 0xa1, 0x40, 0xa2, - 0x50, 0xa2, 0xe0, 0xa2, 0xf0, 0xa2, 0x28, 0xa3, 0x38, 0xa3, 0x68, 0xa3, - 0x78, 0xa3, 0x90, 0xa3, 0xd0, 0xa3, 0xe0, 0xa3, 0xf8, 0xa3, 0x50, 0xa4, - 0x60, 0xa4, 0x78, 0xa4, 0x90, 0xa4, 0xa8, 0xa4, 0xe0, 0xa4, 0xf8, 0xa4, - 0x10, 0xa5, 0x28, 0xa5, 0xc0, 0xa5, 0xd8, 0xa5, 0xf0, 0xa5, 0x08, 0xa6, - 0x20, 0xa6, 0x38, 0xa6, 0x50, 0xa6, 0x20, 0xa8, 0x38, 0xa8, 0x78, 0xa8, - 0x90, 0xa8, 0xa8, 0xa8, 0xc0, 0xa8, 0x00, 0xa9, 0x80, 0xa9, 0x98, 0xa9, - 0xb0, 0xa9, 0xc8, 0xa9, 0xe0, 0xa9, 0xf8, 0xa9, 0x10, 0xaa, 0x28, 0xaa, - 0x40, 0xaa, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xf0, 0xac, - 0x08, 0xad, 0x00, 0xae, 0xa8, 0xae, 0x58, 0xaf, 0x70, 0xaf, 0x88, 0xaf, - 0xa0, 0xaf, 0xb8, 0xaf, 0xd0, 0xaf, 0xe8, 0xaf, 0x00, 0x70, 0x07, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x50, 0xa0, 0x68, 0xa0, 0x80, 0xa0, 0xf0, 0xa0, - 0x20, 0xa1, 0x00, 0x00, 0x00, 0x90, 0x07, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0xa7, 0x18, 0xa7, 0xd8, 0xa7, 0x08, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, + 0xc0, 0xa8, 0xd8, 0xa8, 0xf0, 0xa8, 0x08, 0xa9, 0x40, 0xa9, 0x70, 0xaa, + 0xa8, 0xab, 0xc0, 0xab, 0x10, 0xac, 0x88, 0xac, 0xa0, 0xac, 0xb8, 0xac, + 0xd0, 0xac, 0xe8, 0xac, 0x00, 0xad, 0x18, 0xad, 0x30, 0xad, 0x48, 0xad, + 0x60, 0xad, 0x78, 0xad, 0x90, 0xad, 0xa8, 0xad, 0xc0, 0xad, 0xd8, 0xad, + 0x40, 0xae, 0x58, 0xae, 0x70, 0xae, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, + 0xd0, 0xae, 0xe8, 0xae, 0x00, 0xaf, 0x18, 0xaf, 0x30, 0xaf, 0x80, 0xaf, + 0x90, 0xaf, 0xe8, 0xaf, 0xf8, 0xaf, 0x00, 0x00, 0x00, 0xe0, 0x06, 0x00, + 0x88, 0x00, 0x00, 0x00, 0xd8, 0xa1, 0xf0, 0xa1, 0x08, 0xa2, 0x20, 0xa2, + 0xa0, 0xa2, 0xb8, 0xa2, 0xd0, 0xa2, 0xe8, 0xa2, 0x00, 0xa3, 0x18, 0xa3, + 0x30, 0xa3, 0x48, 0xa3, 0x60, 0xa3, 0x90, 0xa3, 0x30, 0xa4, 0x48, 0xa4, + 0xc8, 0xa4, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, 0x90, 0xa5, 0xa8, 0xa5, + 0xc0, 0xa5, 0x48, 0xa6, 0x70, 0xa7, 0x80, 0xa7, 0x90, 0xa7, 0x08, 0xa8, + 0x20, 0xa8, 0x38, 0xa8, 0x50, 0xa8, 0x68, 0xa8, 0x80, 0xa8, 0x98, 0xa8, + 0xb0, 0xa8, 0xc8, 0xa8, 0xe0, 0xa8, 0xf8, 0xa8, 0x10, 0xa9, 0x28, 0xa9, + 0x40, 0xa9, 0x58, 0xa9, 0x70, 0xa9, 0x18, 0xab, 0x30, 0xab, 0x48, 0xab, + 0x60, 0xab, 0x78, 0xab, 0x90, 0xab, 0xa8, 0xab, 0xc0, 0xab, 0xd8, 0xab, + 0x60, 0xac, 0xa0, 0xac, 0xb0, 0xac, 0x70, 0xad, 0x00, 0xae, 0x40, 0xae, + 0x88, 0xae, 0x20, 0xaf, 0x68, 0xaf, 0x78, 0xaf, 0x88, 0xaf, 0x98, 0xaf, + 0x00, 0xf0, 0x06, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x18, 0xa0, 0x30, 0xa0, + 0xb0, 0xa0, 0x50, 0xa1, 0x60, 0xa1, 0x70, 0xa1, 0x08, 0xa2, 0x40, 0xa2, + 0x58, 0xa2, 0x70, 0xa2, 0x88, 0xa2, 0x08, 0xa3, 0x48, 0xa3, 0x60, 0xa3, + 0xa0, 0xa4, 0xd0, 0xa4, 0xe8, 0xa4, 0x40, 0xa5, 0x58, 0xa5, 0xa8, 0xa5, + 0xc0, 0xa5, 0xd8, 0xa5, 0x10, 0xa6, 0x40, 0xa6, 0x78, 0xa6, 0x70, 0xa8, + 0x88, 0xa8, 0x20, 0xa9, 0x38, 0xa9, 0x50, 0xa9, 0x68, 0xa9, 0x80, 0xa9, + 0x90, 0xaa, 0xf0, 0xaa, 0x68, 0xab, 0x60, 0xac, 0x78, 0xac, 0x90, 0xac, + 0xa8, 0xac, 0xc0, 0xac, 0xf0, 0xac, 0x10, 0xad, 0x00, 0x00, 0x07, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x58, 0xa2, 0x70, 0xa2, 0x88, 0xa2, 0xa0, 0xa2, + 0xb8, 0xa2, 0xd0, 0xa2, 0xf0, 0xa3, 0x08, 0xa4, 0x20, 0xa4, 0x98, 0xa4, + 0xd0, 0xa4, 0x40, 0xa7, 0x58, 0xa7, 0x70, 0xa7, 0x88, 0xa7, 0xa0, 0xa7, + 0xb8, 0xa7, 0xd0, 0xa7, 0x08, 0xa8, 0x18, 0xa9, 0x58, 0xaa, 0xc0, 0xaa, + 0xe0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x48, 0xac, + 0xb8, 0xac, 0xd0, 0xac, 0xe8, 0xac, 0x00, 0xad, 0x18, 0xad, 0x30, 0xad, + 0x70, 0xad, 0x00, 0xae, 0x00, 0x10, 0x07, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x98, 0xa8, 0xc8, 0xa9, 0x20, 0xab, 0x30, 0xab, 0x40, 0xab, 0x50, 0xab, + 0x60, 0xab, 0x70, 0xab, 0x80, 0xab, 0x90, 0xab, 0xa0, 0xab, 0xb0, 0xab, + 0xc0, 0xab, 0xd0, 0xab, 0xe0, 0xab, 0xf0, 0xab, 0x00, 0xac, 0x10, 0xac, + 0x20, 0xac, 0x30, 0xac, 0x40, 0xac, 0x50, 0xac, 0x60, 0xac, 0x70, 0xac, + 0x80, 0xac, 0x90, 0xac, 0xa0, 0xac, 0xb0, 0xac, 0xc0, 0xac, 0xd0, 0xac, + 0xe0, 0xac, 0xf0, 0xac, 0x00, 0xad, 0x10, 0xad, 0xf0, 0xae, 0x00, 0x00, + 0x00, 0x30, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xd0, 0xa0, 0x28, 0xa1, + 0x98, 0xa2, 0xb0, 0xa2, 0x00, 0xa3, 0x10, 0xa3, 0x20, 0xa3, 0x30, 0xa3, + 0x40, 0xa3, 0x70, 0xa3, 0xa8, 0xa3, 0xf0, 0xa3, 0x40, 0xa4, 0x78, 0xa4, + 0x90, 0xa4, 0xd8, 0xa4, 0x60, 0xa5, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x18, 0xa2, 0x30, 0xa2, 0x68, 0xa2, 0x98, 0xa2, + 0xb0, 0xa2, 0xc8, 0xa2, 0xe0, 0xa2, 0xf8, 0xa2, 0x10, 0xa3, 0x28, 0xa3, + 0x40, 0xa3, 0x80, 0xa3, 0x98, 0xa3, 0xb0, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, + 0x18, 0xa4, 0x48, 0xa4, 0x60, 0xa4, 0x78, 0xa4, 0x90, 0xa4, 0xc8, 0xa4, + 0xe0, 0xa4, 0xf8, 0xa4, 0x10, 0xa5, 0x28, 0xa5, 0x40, 0xa5, 0xd0, 0xa5, + 0x18, 0xac, 0x28, 0xac, 0x58, 0xac, 0x70, 0xac, 0xb8, 0xac, 0xc8, 0xac, + 0xd8, 0xac, 0x00, 0xad, 0x38, 0xad, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x78, 0xa2, 0x88, 0xa2, 0x98, 0xa2, 0xa8, 0xa2, + 0xf8, 0xa2, 0x08, 0xa3, 0x18, 0xa3, 0x28, 0xa3, 0x38, 0xa3, 0x60, 0xa3, + 0x70, 0xa3, 0x80, 0xa3, 0xe8, 0xa3, 0xf8, 0xa3, 0x08, 0xa4, 0x50, 0xa4, + 0x60, 0xa4, 0x98, 0xa4, 0xa8, 0xa4, 0xd0, 0xa4, 0xe0, 0xa4, 0x18, 0xa5, + 0x30, 0xa5, 0x88, 0xa5, 0xf8, 0xab, 0x10, 0xac, 0x48, 0xac, 0x98, 0xac, + 0xe0, 0xac, 0xf0, 0xac, 0x78, 0xae, 0xc8, 0xae, 0xd8, 0xae, 0x08, 0xaf, + 0x40, 0xaf, 0x50, 0xaf, 0xa0, 0xaf, 0xb0, 0xaf, 0xf8, 0xaf, 0x00, 0x00, + 0x00, 0x60, 0x07, 0x00, 0x78, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x40, 0xa4, + 0x58, 0xa4, 0x68, 0xa5, 0x80, 0xa5, 0x30, 0xa6, 0x48, 0xa6, 0x80, 0xa6, + 0x98, 0xa6, 0xe8, 0xa6, 0xf8, 0xa6, 0x30, 0xa7, 0x40, 0xa7, 0x70, 0xa7, + 0x80, 0xa7, 0x98, 0xa7, 0xd8, 0xa7, 0xe8, 0xa7, 0x00, 0xa8, 0x58, 0xa8, + 0x68, 0xa8, 0x80, 0xa8, 0x98, 0xa8, 0xb0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, + 0x18, 0xa9, 0x30, 0xa9, 0xc8, 0xa9, 0xe0, 0xa9, 0xf8, 0xa9, 0x10, 0xaa, + 0x28, 0xaa, 0x40, 0xaa, 0x58, 0xaa, 0x28, 0xac, 0x40, 0xac, 0x80, 0xac, + 0x98, 0xac, 0xb0, 0xac, 0xc8, 0xac, 0x08, 0xad, 0x88, 0xad, 0xa0, 0xad, + 0xb8, 0xad, 0xd0, 0xad, 0xe8, 0xad, 0x00, 0xae, 0x18, 0xae, 0x30, 0xae, + 0x48, 0xae, 0x60, 0xae, 0x78, 0xae, 0x90, 0xae, 0xa8, 0xae, 0x00, 0x00, + 0x00, 0x70, 0x07, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf8, 0xa0, 0x10, 0xa1, + 0x08, 0xa2, 0xb0, 0xa2, 0x60, 0xa3, 0x78, 0xa3, 0x90, 0xa3, 0xa8, 0xa3, + 0xc0, 0xa3, 0xd8, 0xa3, 0xf0, 0xa3, 0x58, 0xa4, 0x70, 0xa4, 0x88, 0xa4, + 0xf8, 0xa4, 0x28, 0xa5, 0x00, 0x90, 0x07, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0xa6, 0xf8, 0xa6, 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x00, 0x30, 0x1e, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x90, 0xa8, 0x98, 0xa8, 0xa0, 0xa8, 0xa8, 0xa8, 0x00, 0x00, 0x00, 0x50, 0x1f, 0x00, diff --git a/setup-dev.sh b/setup-dev.sh index 2c90726f..1c7c6e6f 100755 --- a/setup-dev.sh +++ b/setup-dev.sh @@ -796,6 +796,25 @@ do_launch_thinkpad() { fi if [[ -f "${TESTING_DIR}/test-disk-50g.img" ]]; then + # Build/refresh the boot ESP image that UEFI will load BOOTX64.EFI from. + # The data disk (test-disk-50g.img) is raw HelixFS with no partition table + # so the kernel won't mistake it for the boot disk. + local esp_img="${TESTING_DIR}/esp-temp.img" + if [[ ! -f "$esp_img" ]] || [[ "${ESP_DIR}/EFI/BOOT/BOOTX64.EFI" -nt "$esp_img" ]] || [[ "${FORCE_MODE}" == "true" ]]; then + log_info "Refreshing boot ESP image..." + local esp_size + esp_size=$(du -sb "${ESP_DIR}" | awk '{print int(($1 / 1024 / 1024) + 50)}') + dd if=/dev/zero of="$esp_img" bs=1M count="$esp_size" status=none 2>/dev/null + mkfs.vfat -F 32 -n ESP "$esp_img" >/dev/null 2>&1 + local mnt + mnt=$(mktemp -d) + sudo mount -o loop "$esp_img" "$mnt" + sudo rsync -a "${ESP_DIR}/" "$mnt/" 2>/dev/null || true + sudo umount "$mnt" + rmdir "$mnt" + fi + # Disk 0 (SATA port 0): ESP FAT32 image — UEFI boots BOOTX64.EFI from here + # Disk 1 (SATA port 1): raw HelixFS image — kernel mounts this as data storage if ! run_qemu_command qemu-system-x86_64 \ -enable-kvm \ -machine q35,accel=kvm,i8042=on,usb=off \ @@ -806,9 +825,11 @@ do_launch_thinkpad() { -smbios type=2,manufacturer=LENOVO,product=20BWS0XX00 \ -smbios type=3,manufacturer=LENOVO \ -object iothread,id=iothread0 \ - -drive file="${TESTING_DIR}/test-disk-50g.img",format=raw,if=none,id=disk0,cache=writeback \ + -drive file="$esp_img",format=raw,if=none,id=disk0,cache=writeback \ -device ich9-ahci,id=ahci0 \ -device ide-hd,drive=disk0,bus=ahci0.0,bootindex=1 \ + -drive file="${TESTING_DIR}/test-disk-50g.img",format=raw,if=none,id=disk1,cache=writeback \ + -device ide-hd,drive=disk1,bus=ahci0.1 \ "${net_args[@]}" \ -smp 8 \ -m 12G \ diff --git a/shelld/src/islands/launcher.rs b/shelld/src/islands/launcher.rs index 7a4cfaf8..14030fdf 100644 --- a/shelld/src/islands/launcher.rs +++ b/shelld/src/islands/launcher.rs @@ -125,8 +125,8 @@ pub fn handle_click(state: &mut ShellState, mx: i32, my: i32) -> bool { Ok(_pid) => { io::println("shelld: spawned msh"); } - Err(_) => { - io::println("shelld: failed to spawn msh"); + Err(e) => { + libmorpheus::println!("shelld: failed to spawn msh err=0x{:x}", e); } } // close launcher after spawning From bec55d186668cb6d58d295d2fc180a3e0270a617 Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Thu, 12 Mar 2026 23:44:59 +0100 Subject: [PATCH 2/8] feat: implement unified disk image layout with ESP and HelixFS partitions - Updated `setup-dev.sh` to create a unified GPT disk image with an EFI System Partition (ESP) and a HelixFS data partition. - Added functions to handle the injection of applications into the HelixFS partition and to sync the ESP contents. - Introduced a new command `flash` to write the unified image layout to physical media, ensuring only necessary files are copied. - Removed the previous method of creating a raw sparse image without a partition table. - Updated `Cargo.toml` in the `shelld` directory to remove release profile optimizations. --- cli/src/main.rs | 8 +- compd/Cargo.toml | 4 - hwinit/src/cpu/ap_boot.rs | 60 ++++- hwinit/src/cpu/apic.rs | 43 +++- hwinit/src/paging/mod.rs | 23 +- hwinit/src/platform.rs | 8 + init/Cargo.toml | 3 - persistent/src/pe/embedded_reloc_data.rs | 195 +++++++------- setup-dev.sh | 315 +++++++++++++++++++---- shelld/Cargo.toml | 3 - 10 files changed, 487 insertions(+), 175 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 4b7cb94b..677f77a4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -51,8 +51,12 @@ struct FileBlockDevice { impl FileBlockDevice { fn open(path: &str) -> io::Result { - let file = OpenOptions::new().read(true).write(true).open(path)?; - let len = file.metadata()?.len(); + let mut file = OpenOptions::new().read(true).write(true).open(path)?; + let mut len = file.metadata()?.len(); + if len == 0 { + // Block devices may report 0 via metadata; use seek-to-end as fallback. + len = file.seek(SeekFrom::End(0))?; + } let total_sectors = len / SECTOR_SIZE as u64; Ok(Self { file, diff --git a/compd/Cargo.toml b/compd/Cargo.toml index d44f9a1a..7d179cbb 100644 --- a/compd/Cargo.toml +++ b/compd/Cargo.toml @@ -12,7 +12,3 @@ libmorpheus = { path = "../libmorpheus" } morpheus-display = { path = "../display", features = ["framebuffer-backend"] } morpheus-gfx3d = { path = "../gfx3d" } channel = { path = "../channel" } - -[profile.release] -opt-level = 3 -lto = true diff --git a/hwinit/src/cpu/ap_boot.rs b/hwinit/src/cpu/ap_boot.rs index 4cb8d313..b3f8aa2c 100644 --- a/hwinit/src/cpu/ap_boot.rs +++ b/hwinit/src/cpu/ap_boot.rs @@ -28,6 +28,22 @@ const AP_TRAMPOLINE_PAGE: u8 = (AP_TRAMPOLINE_PHYS / 0x1000) as u8; /// AP kernel stack size (64 KiB per core). const AP_STACK_SIZE: u64 = 64 * 1024; +// Brute-force LAPIC probing is a fallback path. Keep it bounded so one bad +// topology guess doesn't make boot look dead on real hardware. +const AP_WAIT_STEP_US: u64 = 50; +const AP_WAIT_TIMEOUT_US: u64 = 20_000; +const AP_BRUTE_SCAN_BUDGET_US: u64 = 3_000_000; + +#[inline(always)] +fn ap_dbg_probe(tag: &str, lapic_id: u32) { + crate::serial::checkpoint(tag); + crate::serial::puts("[APDBG] "); + crate::serial::puts(tag); + crate::serial::puts(" lapic="); + crate::serial::put_hex32(lapic_id); + crate::serial::puts("\r\n"); +} + // ── Trampoline data area offsets ───────────────────────────────────────── // These must match the .data section layout at the end of ap_trampoline.s. // The trampoline data block starts at AP_TRAMPOLINE_PHYS + TRAMPOLINE_DATA_OFFSET. @@ -59,6 +75,7 @@ static AP_TRAMPOLINE_BIN: &[u8] = &[]; /// /// Returns false if the trampoline page is unavailable (reserved by firmware). unsafe fn setup_trampoline() -> bool { + crate::serial::checkpoint("ap-tramp-setup-begin"); // validate the trampoline page is usable before stomping it. // on exotic firmware 0x8000 might be reserved/MMIO. match global_registry_mut().allocate_pages( @@ -69,6 +86,7 @@ unsafe fn setup_trampoline() -> bool { Ok(_) => {} Err(_) => { log_error("AP", 500, "trampoline page 0x8000 unavailable in memory map"); + crate::serial::checkpoint("ap-tramp-setup-no-page"); return false; } } @@ -95,6 +113,8 @@ unsafe fn setup_trampoline() -> bool { *((AP_TRAMPOLINE_PHYS + TD_CR3) as *mut u64) = kernel_cr3; *((AP_TRAMPOLINE_PHYS + TD_ENTRY64) as *mut u64) = ap_rust_entry as u64; + crate::serial::checkpoint("ap-tramp-setup-done"); + true } @@ -103,6 +123,8 @@ unsafe fn setup_trampoline() -> bool { /// Returns true if the AP responded within the timeout. /// On failure, frees the allocated stack — no leak. unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { + ap_dbg_probe("ap-probe-begin", lapic_id); + // allocate a kernel stack for this AP let stack_pages = AP_STACK_SIZE / PAGE_SIZE; let stack_base = match global_registry_mut().allocate_pages( @@ -113,10 +135,12 @@ unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { Ok(base) => base, Err(_) => { log_error("AP", 501, "stack allocation failed"); + crate::serial::checkpoint("ap-probe-stack-alloc-fail"); return false; } }; let stack_top = stack_base + AP_STACK_SIZE; + ap_dbg_probe("ap-probe-stack-alloc-ok", lapic_id); // fill per-AP trampoline data *((AP_TRAMPOLINE_PHYS + TD_STACK) as *mut u64) = stack_top; @@ -128,31 +152,40 @@ unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { let before = per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst); // INIT IPI + ap_dbg_probe("ap-probe-init-send", lapic_id); apic::send_init_ipi(lapic_id); + ap_dbg_probe("ap-probe-init-sent", lapic_id); apic::delay_us(10_000); // 10ms // SIPI #1 + ap_dbg_probe("ap-probe-sipi1-send", lapic_id); apic::send_sipi(lapic_id, AP_TRAMPOLINE_PAGE); + ap_dbg_probe("ap-probe-sipi1-sent", lapic_id); apic::delay_us(200); // 200µs // SIPI #2 (per Intel spec, send twice for reliability) + ap_dbg_probe("ap-probe-sipi2-send", lapic_id); apic::send_sipi(lapic_id, AP_TRAMPOLINE_PAGE); + ap_dbg_probe("ap-probe-sipi2-sent", lapic_id); apic::delay_us(200); - // wait for AP to come online (up to 100ms) - let mut waited = 0u32; + // wait for AP to come online (bounded; fallback path must stay snappy) + ap_dbg_probe("ap-probe-online-wait", lapic_id); + let mut waited_us = 0u64; while per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst) <= before { - apic::delay_us(100); - waited += 1; - if waited > 1000 { + apic::delay_us(AP_WAIT_STEP_US); + waited_us += AP_WAIT_STEP_US; + if waited_us >= AP_WAIT_TIMEOUT_US { break; } } if per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst) > before { + ap_dbg_probe("ap-probe-online-ok", lapic_id); true } else { log_warn("AP", 502, "ap did not respond to INIT+SIPI; freeing stack"); + ap_dbg_probe("ap-probe-online-timeout", lapic_id); // don't leak 64KB per ghost core let _ = global_registry_mut().free_pages(stack_base, stack_pages); false @@ -218,6 +251,7 @@ pub unsafe fn start_aps() { let bsp_lapic_id = apic::read_lapic_id(); log_warn("AP", 508, "starting AP bring-up via brute-force LAPIC scan"); + crate::serial::checkpoint("ap-scan-begin"); if !setup_trampoline() { return; @@ -225,6 +259,7 @@ pub unsafe fn start_aps() { let mut core_idx: u32 = 1; // 0 = BSP let mut online: u32 = 0; + let mut scan_budget_used_us: u64 = 0; for lapic_id in 0u32..256 { if lapic_id == bsp_lapic_id { continue; @@ -237,13 +272,28 @@ pub unsafe fn start_aps() { break; } + if scan_budget_used_us >= AP_BRUTE_SCAN_BUDGET_US { + log_warn("AP", 510, "brute-force AP scan budget exhausted; continuing with discovered cores"); + crate::serial::checkpoint("ap-scan-budget-exhausted"); + break; + } + + if (lapic_id & 0x1F) == 0 { + log_info("AP", 511, "AP scan progress checkpoint"); + ap_dbg_probe("ap-scan-progress", lapic_id); + } + if boot_single_ap(core_idx, lapic_id) { // only consume a core slot on success core_idx += 1; online += 1; } + + // upper bound for one probe: INIT delay + SIPI delays + wait timeout + scan_budget_used_us += 10_400 + AP_WAIT_TIMEOUT_US; } + crate::serial::checkpoint("ap-scan-done"); log_ok("AP", 509, "brute-force AP bring-up pass complete"); } diff --git a/hwinit/src/cpu/apic.rs b/hwinit/src/cpu/apic.rs index fac57b21..235868a0 100644 --- a/hwinit/src/cpu/apic.rs +++ b/hwinit/src/cpu/apic.rs @@ -10,6 +10,13 @@ use crate::cpu::per_cpu; use crate::cpu::pio::outb; use crate::serial::{log_info, log_ok, log_warn}; +#[inline(always)] +fn apic_dbg(msg: &str) { + crate::serial::puts("[APICDBG] "); + crate::serial::puts(msg); + crate::serial::puts("\r\n"); +} + // ── LAPIC register offsets ─────────────────────────────────────────────── const LAPIC_ID: u32 = 0x020; @@ -265,10 +272,12 @@ pub unsafe fn disable_pic8259() { /// Send an INIT IPI to target APIC ID. pub unsafe fn send_init_ipi(target_apic_id: u32) { let base = lapic_base(); + apic_dbg("init-ipi-begin"); // INIT assert lapic_write(base, LAPIC_ICR_HI, target_apic_id << 24); lapic_write(base, LAPIC_ICR_LO, ICR_INIT | ICR_LEVEL_ASSERT); + apic_dbg("init-ipi-assert"); wait_icr_idle(base); // INIT deassert — trigger mode MUST be level (bit 15) with level=0. @@ -279,7 +288,9 @@ pub unsafe fn send_init_ipi(target_apic_id: u32) { LAPIC_ICR_LO, ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT, ); + apic_dbg("init-ipi-deassert"); wait_icr_idle(base); + apic_dbg("init-ipi-done"); } /// Send a Startup IPI (SIPI) to target APIC ID. @@ -288,23 +299,30 @@ pub unsafe fn send_init_ipi(target_apic_id: u32) { /// Must be below 1 MiB and page-aligned (e.g., 0x8000 → start_page = 8). pub unsafe fn send_sipi(target_apic_id: u32, start_page: u8) { let base = lapic_base(); + apic_dbg("sipi-begin"); lapic_write(base, LAPIC_ICR_HI, target_apic_id << 24); lapic_write(base, LAPIC_ICR_LO, ICR_STARTUP | start_page as u32); + apic_dbg("sipi-write"); wait_icr_idle(base); + apic_dbg("sipi-done"); } /// Wait for the ICR delivery status bit to clear. #[inline] unsafe fn wait_icr_idle(base: u64) { - let mut timeout = 100_000u32; + // Keep this tightly bounded. Some hardware can leave delivery status set + // long enough to look like a deadlock if we spin too generously. + let mut timeout = 2_000u32; while lapic_read(base, LAPIC_ICR_LO) & ICR_DELIVERY_STATUS != 0 { core::hint::spin_loop(); timeout -= 1; if timeout == 0 { log_warn("LAPIC", 726, "ICR delivery timeout"); + apic_dbg("icr-wait-timeout"); break; } } + apic_dbg("icr-wait-done"); } // ── Delay helper ───────────────────────────────────────────────────────── @@ -313,11 +331,30 @@ unsafe fn wait_icr_idle(base: u64) { /// Falls back to a dumb loop if TSC freq is unknown. pub unsafe fn delay_us(us: u64) { let freq = crate::process::scheduler::tsc_frequency(); - if freq > 0 { + // Sanity-window the TSC frequency. Bad calibration here turns a 10ms AP + // bring-up delay into a geological epoch on some firmware. + if (1_000_000..=10_000_000_000).contains(&freq) { + let cycles_per_us = freq / 1_000_000; + if cycles_per_us == 0 { + for _ in 0..(us * 1000) { + core::hint::spin_loop(); + } + return; + } + let start = crate::cpu::tsc::read_tsc(); - let target = start + (freq / 1_000_000) * us; + let delta = cycles_per_us.saturating_mul(us); + let target = start.saturating_add(delta); + + // Watchdog the spin so a broken TSC path can't wedge bring-up forever. + let mut spins = 0u32; while crate::cpu::tsc::read_tsc() < target { core::hint::spin_loop(); + spins = spins.wrapping_add(1); + if spins == 50_000_000 { + log_warn("LAPIC", 727, "delay_us watchdog tripped; falling back"); + break; + } } } else { // dumb fallback: ~1µs per iteration at ~1GHz diff --git a/hwinit/src/paging/mod.rs b/hwinit/src/paging/mod.rs index f417119d..90363315 100644 --- a/hwinit/src/paging/mod.rs +++ b/hwinit/src/paging/mod.rs @@ -7,7 +7,7 @@ pub mod table; pub use entry::{PageFlags, PageTable, PageTableEntry}; pub use table::{MappedPageSize, PageTableManager, VirtAddr}; -use crate::serial::{log_info, log_ok}; +use crate::serial::{log_info, log_ok, log_warn}; use crate::sync::SpinLock; // kernel page table singleton — SMP-safe via SpinLock @@ -174,8 +174,10 @@ pub unsafe fn kmark_uncacheable(virt: u64) -> Result<(), &'static str> { /// - up to ~24 PD pages (one per 1 GiB) /// - PT pages only if 4 KiB region exists (firmware code, MMIO, etc.) /// -/// 512 entries is vastly more than needed but fits comfortably on the stack. -const MAX_PT_PAGES: usize = 512; +/// On some firmware builds, low memory and MMIO windows are mapped with many +/// 4 KiB leaves, which can produce far more than a few hundred PT pages. +/// Keep this comfortably large so reclaim never misses live paging structures. +const MAX_PT_PAGES: usize = 8192; /// Walk the active CR3 page table hierarchy and collect the physical /// addresses of **every** page that is itself a page table page (PML4, @@ -197,6 +199,7 @@ pub unsafe fn collect_page_table_pages() -> ([u64; MAX_PT_PAGES], usize) { let mut pages = [0u64; MAX_PT_PAGES]; let mut count = 0usize; + let mut truncated = false; // Helper closure (inlined) to add a page if not already seen. macro_rules! add_page { @@ -209,9 +212,13 @@ pub unsafe fn collect_page_table_pages() -> ([u64; MAX_PT_PAGES], usize) { break; } } - if !seen && count < MAX_PT_PAGES { - pages[count] = p; - count += 1; + if !seen { + if count < MAX_PT_PAGES { + pages[count] = p; + count += 1; + } else { + truncated = true; + } } }; } @@ -255,6 +262,10 @@ pub unsafe fn collect_page_table_pages() -> ([u64; MAX_PT_PAGES], usize) { } } + if truncated { + log_warn("PAGING", 733, "page-table page collection truncated"); + } + (pages, count) } diff --git a/hwinit/src/platform.rs b/hwinit/src/platform.rs index 090cf70d..9f329f91 100644 --- a/hwinit/src/platform.rs +++ b/hwinit/src/platform.rs @@ -423,6 +423,9 @@ pub unsafe fn platform_init_selfcontained( // Collect them first, sort, and pass as an exclusion set. log_info("BOOT", 111, "phase 10.5/12: reclaim boot services ram"); checkpoint("phase10.5-reclaim-begin"); + // Keep this whole critical section non-preemptible. The timer IRQ is live + // after phase 10; reclaim/reserve mutates allocator + paging metadata. + crate::cpu::idt::disable_interrupts(); { let (mut pt_pages, pt_count) = crate::paging::collect_page_table_pages(); @@ -448,6 +451,7 @@ pub unsafe fn platform_init_selfcontained( let reg = global_registry_mut(); reg.validate_free_lists(); } + crate::cpu::idt::enable_interrupts(); checkpoint("phase10.5-done"); // phase 11: filesystem @@ -504,7 +508,9 @@ pub unsafe fn platform_init_selfcontained( if madt_result.count > 0 { per_cpu::set_cpu_count(madt_result.count as u32 + 1); // +1 for BSP checkpoint("phase12-start-aps-madt"); + crate::cpu::idt::disable_interrupts(); ap_boot::start_aps_from_list(&madt_result.ids[..madt_result.count]); + crate::cpu::idt::enable_interrupts(); } else { // fallback: CPUID-based count + brute-force LAPIC ID enumeration. // works but slow on sparse topologies. @@ -515,7 +521,9 @@ pub unsafe fn platform_init_selfcontained( if cpu_count > 1 { checkpoint("phase12-start-aps-cpuid"); + crate::cpu::idt::disable_interrupts(); ap_boot::start_aps(); + crate::cpu::idt::enable_interrupts(); } else { log_info("SMP", 114, "single-core detected; no AP startup"); } diff --git a/init/Cargo.toml b/init/Cargo.toml index 89d8ef99..9677ca91 100644 --- a/init/Cargo.toml +++ b/init/Cargo.toml @@ -10,6 +10,3 @@ path = "src/main.rs" [dependencies] libmorpheus = { path = "../libmorpheus" } -[profile.release] -opt-level = 3 -lto = true diff --git a/persistent/src/pe/embedded_reloc_data.rs b/persistent/src/pe/embedded_reloc_data.rs index ddd5e3c8..3dca98a4 100644 --- a/persistent/src/pe/embedded_reloc_data.rs +++ b/persistent/src/pe/embedded_reloc_data.rs @@ -8,111 +8,114 @@ //! Run: ./tools/extract-reloc-data.sh after each build /// Original .reloc section RVA -pub const RELOC_RVA: u32 = 0x00633000; +pub const RELOC_RVA: u32 = 0x0063e000; /// Original .reloc section size -pub const RELOC_SIZE: u32 = 0x00000464; +pub const RELOC_SIZE: u32 = 0x00000484; /// Original ImageBase from linker script pub const ORIGINAL_IMAGE_BASE: u64 = 0x0000004001000000; -/// Hardcoded .reloc section data (1124 bytes) -/// Extracted from morpheus-bootloader.efi at file offset 0x001f4e00 +/// Hardcoded .reloc section data (1156 bytes) +/// Extracted from morpheus-bootloader.efi at file offset 0x00200a00 #[allow(dead_code)] -pub const RELOC_DATA: [u8; 1124] = [ - 0x00, 0xb0, 0x06, 0x00, 0x38, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, +pub const RELOC_DATA: [u8; 1156] = [ + 0x00, 0x60, 0x07, 0x00, 0x44, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, - 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0xd0, 0xa9, - 0xd8, 0xaa, 0x20, 0xaf, 0x38, 0xaf, 0x50, 0xaf, 0x68, 0xaf, 0x80, 0xaf, - 0x98, 0xaf, 0xb0, 0xaf, 0xc8, 0xaf, 0xe0, 0xaf, 0x00, 0xc0, 0x06, 0x00, - 0x64, 0x00, 0x00, 0x00, 0x60, 0xa0, 0x78, 0xa0, 0x90, 0xa0, 0xa8, 0xa0, + 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0xf8, 0xa7, + 0x10, 0xa8, 0x28, 0xa8, 0x30, 0xa9, 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, + 0x40, 0xab, 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, 0xa0, 0xab, 0xb8, 0xab, + 0xd0, 0xab, 0xe8, 0xab, 0x00, 0xac, 0x00, 0x00, 0x00, 0x70, 0x07, 0x00, + 0x68, 0x00, 0x00, 0x00, 0xd0, 0xa1, 0xe8, 0xa1, 0x00, 0xa2, 0x18, 0xa2, + 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, 0x78, 0xa2, 0x90, 0xa2, 0x10, 0xa3, + 0x28, 0xa3, 0x40, 0xa3, 0x58, 0xa3, 0x70, 0xa3, 0x88, 0xa3, 0xa0, 0xa3, + 0xb8, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, 0x18, 0xa4, 0x30, 0xa4, + 0x48, 0xa4, 0x60, 0xa4, 0x78, 0xa4, 0x90, 0xa4, 0x10, 0xa5, 0x28, 0xa5, + 0x40, 0xa5, 0x58, 0xa5, 0x70, 0xa5, 0xf0, 0xa5, 0x98, 0xa7, 0xb0, 0xa7, + 0xc8, 0xa7, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, + 0x28, 0xab, 0x60, 0xab, 0x18, 0xac, 0xc0, 0xae, 0xf0, 0xae, 0x90, 0xaf, + 0xa8, 0xaf, 0xc0, 0xaf, 0x00, 0x80, 0x07, 0x00, 0x84, 0x00, 0x00, 0x00, 0xc0, 0xa0, 0xd8, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, 0x20, 0xa1, 0x38, 0xa1, - 0x50, 0xa1, 0x68, 0xa1, 0x80, 0xa1, 0x98, 0xa1, 0xb0, 0xa1, 0xc8, 0xa1, - 0xe0, 0xa1, 0x60, 0xa2, 0x78, 0xa2, 0x90, 0xa2, 0xa8, 0xa2, 0xc0, 0xa2, - 0x40, 0xa3, 0xc8, 0xa4, 0x60, 0xa7, 0x78, 0xa7, 0x90, 0xa7, 0xa8, 0xa9, - 0xe8, 0xab, 0x00, 0xac, 0x18, 0xac, 0x70, 0xac, 0x88, 0xac, 0xa0, 0xac, - 0x20, 0xad, 0x38, 0xad, 0x50, 0xad, 0x50, 0xae, 0x68, 0xae, 0x80, 0xae, - 0x98, 0xae, 0xb0, 0xae, 0xc8, 0xae, 0x48, 0xaf, 0x60, 0xaf, 0xe8, 0xaf, - 0x00, 0xd0, 0x06, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x48, 0xa0, 0x60, 0xa0, - 0x78, 0xa0, 0xd8, 0xa0, 0x58, 0xa1, 0x70, 0xa1, 0x88, 0xa1, 0xc8, 0xa1, - 0xd8, 0xa1, 0x20, 0xa2, 0x38, 0xa2, 0x50, 0xa2, 0x68, 0xa2, 0x08, 0xa3, - 0xd8, 0xa5, 0xf0, 0xa5, 0x30, 0xa6, 0x48, 0xa6, 0x60, 0xa6, 0x78, 0xa6, - 0x00, 0xa7, 0x18, 0xa7, 0xd8, 0xa7, 0x08, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, - 0xc0, 0xa8, 0xd8, 0xa8, 0xf0, 0xa8, 0x08, 0xa9, 0x40, 0xa9, 0x70, 0xaa, - 0xa8, 0xab, 0xc0, 0xab, 0x10, 0xac, 0x88, 0xac, 0xa0, 0xac, 0xb8, 0xac, - 0xd0, 0xac, 0xe8, 0xac, 0x00, 0xad, 0x18, 0xad, 0x30, 0xad, 0x48, 0xad, - 0x60, 0xad, 0x78, 0xad, 0x90, 0xad, 0xa8, 0xad, 0xc0, 0xad, 0xd8, 0xad, - 0x40, 0xae, 0x58, 0xae, 0x70, 0xae, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, - 0xd0, 0xae, 0xe8, 0xae, 0x00, 0xaf, 0x18, 0xaf, 0x30, 0xaf, 0x80, 0xaf, - 0x90, 0xaf, 0xe8, 0xaf, 0xf8, 0xaf, 0x00, 0x00, 0x00, 0xe0, 0x06, 0x00, - 0x88, 0x00, 0x00, 0x00, 0xd8, 0xa1, 0xf0, 0xa1, 0x08, 0xa2, 0x20, 0xa2, - 0xa0, 0xa2, 0xb8, 0xa2, 0xd0, 0xa2, 0xe8, 0xa2, 0x00, 0xa3, 0x18, 0xa3, - 0x30, 0xa3, 0x48, 0xa3, 0x60, 0xa3, 0x90, 0xa3, 0x30, 0xa4, 0x48, 0xa4, - 0xc8, 0xa4, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, 0x90, 0xa5, 0xa8, 0xa5, - 0xc0, 0xa5, 0x48, 0xa6, 0x70, 0xa7, 0x80, 0xa7, 0x90, 0xa7, 0x08, 0xa8, - 0x20, 0xa8, 0x38, 0xa8, 0x50, 0xa8, 0x68, 0xa8, 0x80, 0xa8, 0x98, 0xa8, - 0xb0, 0xa8, 0xc8, 0xa8, 0xe0, 0xa8, 0xf8, 0xa8, 0x10, 0xa9, 0x28, 0xa9, - 0x40, 0xa9, 0x58, 0xa9, 0x70, 0xa9, 0x18, 0xab, 0x30, 0xab, 0x48, 0xab, - 0x60, 0xab, 0x78, 0xab, 0x90, 0xab, 0xa8, 0xab, 0xc0, 0xab, 0xd8, 0xab, - 0x60, 0xac, 0xa0, 0xac, 0xb0, 0xac, 0x70, 0xad, 0x00, 0xae, 0x40, 0xae, - 0x88, 0xae, 0x20, 0xaf, 0x68, 0xaf, 0x78, 0xaf, 0x88, 0xaf, 0x98, 0xaf, - 0x00, 0xf0, 0x06, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x18, 0xa0, 0x30, 0xa0, - 0xb0, 0xa0, 0x50, 0xa1, 0x60, 0xa1, 0x70, 0xa1, 0x08, 0xa2, 0x40, 0xa2, - 0x58, 0xa2, 0x70, 0xa2, 0x88, 0xa2, 0x08, 0xa3, 0x48, 0xa3, 0x60, 0xa3, - 0xa0, 0xa4, 0xd0, 0xa4, 0xe8, 0xa4, 0x40, 0xa5, 0x58, 0xa5, 0xa8, 0xa5, - 0xc0, 0xa5, 0xd8, 0xa5, 0x10, 0xa6, 0x40, 0xa6, 0x78, 0xa6, 0x70, 0xa8, - 0x88, 0xa8, 0x20, 0xa9, 0x38, 0xa9, 0x50, 0xa9, 0x68, 0xa9, 0x80, 0xa9, - 0x90, 0xaa, 0xf0, 0xaa, 0x68, 0xab, 0x60, 0xac, 0x78, 0xac, 0x90, 0xac, - 0xa8, 0xac, 0xc0, 0xac, 0xf0, 0xac, 0x10, 0xad, 0x00, 0x00, 0x07, 0x00, - 0x50, 0x00, 0x00, 0x00, 0x58, 0xa2, 0x70, 0xa2, 0x88, 0xa2, 0xa0, 0xa2, - 0xb8, 0xa2, 0xd0, 0xa2, 0xf0, 0xa3, 0x08, 0xa4, 0x20, 0xa4, 0x98, 0xa4, - 0xd0, 0xa4, 0x40, 0xa7, 0x58, 0xa7, 0x70, 0xa7, 0x88, 0xa7, 0xa0, 0xa7, - 0xb8, 0xa7, 0xd0, 0xa7, 0x08, 0xa8, 0x18, 0xa9, 0x58, 0xaa, 0xc0, 0xaa, - 0xe0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x48, 0xac, - 0xb8, 0xac, 0xd0, 0xac, 0xe8, 0xac, 0x00, 0xad, 0x18, 0xad, 0x30, 0xad, - 0x70, 0xad, 0x00, 0xae, 0x00, 0x10, 0x07, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x98, 0xa8, 0xc8, 0xa9, 0x20, 0xab, 0x30, 0xab, 0x40, 0xab, 0x50, 0xab, - 0x60, 0xab, 0x70, 0xab, 0x80, 0xab, 0x90, 0xab, 0xa0, 0xab, 0xb0, 0xab, - 0xc0, 0xab, 0xd0, 0xab, 0xe0, 0xab, 0xf0, 0xab, 0x00, 0xac, 0x10, 0xac, - 0x20, 0xac, 0x30, 0xac, 0x40, 0xac, 0x50, 0xac, 0x60, 0xac, 0x70, 0xac, - 0x80, 0xac, 0x90, 0xac, 0xa0, 0xac, 0xb0, 0xac, 0xc0, 0xac, 0xd0, 0xac, - 0xe0, 0xac, 0xf0, 0xac, 0x00, 0xad, 0x10, 0xad, 0xf0, 0xae, 0x00, 0x00, - 0x00, 0x30, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xd0, 0xa0, 0x28, 0xa1, - 0x98, 0xa2, 0xb0, 0xa2, 0x00, 0xa3, 0x10, 0xa3, 0x20, 0xa3, 0x30, 0xa3, - 0x40, 0xa3, 0x70, 0xa3, 0xa8, 0xa3, 0xf0, 0xa3, 0x40, 0xa4, 0x78, 0xa4, - 0x90, 0xa4, 0xd8, 0xa4, 0x60, 0xa5, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, - 0x54, 0x00, 0x00, 0x00, 0x18, 0xa2, 0x30, 0xa2, 0x68, 0xa2, 0x98, 0xa2, - 0xb0, 0xa2, 0xc8, 0xa2, 0xe0, 0xa2, 0xf8, 0xa2, 0x10, 0xa3, 0x28, 0xa3, - 0x40, 0xa3, 0x80, 0xa3, 0x98, 0xa3, 0xb0, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, - 0x18, 0xa4, 0x48, 0xa4, 0x60, 0xa4, 0x78, 0xa4, 0x90, 0xa4, 0xc8, 0xa4, - 0xe0, 0xa4, 0xf8, 0xa4, 0x10, 0xa5, 0x28, 0xa5, 0x40, 0xa5, 0xd0, 0xa5, - 0x18, 0xac, 0x28, 0xac, 0x58, 0xac, 0x70, 0xac, 0xb8, 0xac, 0xc8, 0xac, - 0xd8, 0xac, 0x00, 0xad, 0x38, 0xad, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, - 0x58, 0x00, 0x00, 0x00, 0x78, 0xa2, 0x88, 0xa2, 0x98, 0xa2, 0xa8, 0xa2, - 0xf8, 0xa2, 0x08, 0xa3, 0x18, 0xa3, 0x28, 0xa3, 0x38, 0xa3, 0x60, 0xa3, - 0x70, 0xa3, 0x80, 0xa3, 0xe8, 0xa3, 0xf8, 0xa3, 0x08, 0xa4, 0x50, 0xa4, - 0x60, 0xa4, 0x98, 0xa4, 0xa8, 0xa4, 0xd0, 0xa4, 0xe0, 0xa4, 0x18, 0xa5, - 0x30, 0xa5, 0x88, 0xa5, 0xf8, 0xab, 0x10, 0xac, 0x48, 0xac, 0x98, 0xac, - 0xe0, 0xac, 0xf0, 0xac, 0x78, 0xae, 0xc8, 0xae, 0xd8, 0xae, 0x08, 0xaf, - 0x40, 0xaf, 0x50, 0xaf, 0xa0, 0xaf, 0xb0, 0xaf, 0xf8, 0xaf, 0x00, 0x00, - 0x00, 0x60, 0x07, 0x00, 0x78, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x40, 0xa4, - 0x58, 0xa4, 0x68, 0xa5, 0x80, 0xa5, 0x30, 0xa6, 0x48, 0xa6, 0x80, 0xa6, - 0x98, 0xa6, 0xe8, 0xa6, 0xf8, 0xa6, 0x30, 0xa7, 0x40, 0xa7, 0x70, 0xa7, - 0x80, 0xa7, 0x98, 0xa7, 0xd8, 0xa7, 0xe8, 0xa7, 0x00, 0xa8, 0x58, 0xa8, - 0x68, 0xa8, 0x80, 0xa8, 0x98, 0xa8, 0xb0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, - 0x18, 0xa9, 0x30, 0xa9, 0xc8, 0xa9, 0xe0, 0xa9, 0xf8, 0xa9, 0x10, 0xaa, - 0x28, 0xaa, 0x40, 0xaa, 0x58, 0xaa, 0x28, 0xac, 0x40, 0xac, 0x80, 0xac, - 0x98, 0xac, 0xb0, 0xac, 0xc8, 0xac, 0x08, 0xad, 0x88, 0xad, 0xa0, 0xad, + 0xb8, 0xa1, 0xd0, 0xa1, 0x58, 0xa2, 0xb8, 0xa2, 0xd0, 0xa2, 0xe8, 0xa2, + 0x48, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, 0xf8, 0xa3, 0x38, 0xa4, 0x48, 0xa4, + 0x90, 0xa4, 0xa8, 0xa4, 0xc0, 0xa4, 0xd8, 0xa4, 0x80, 0xa5, 0x98, 0xa5, + 0xd8, 0xa5, 0xf0, 0xa5, 0x08, 0xa6, 0x20, 0xa6, 0xa8, 0xa6, 0xc0, 0xa6, + 0x30, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, 0x18, 0xaa, 0x30, 0xaa, 0x48, 0xaa, + 0x60, 0xaa, 0x98, 0xaa, 0xc8, 0xab, 0x00, 0xad, 0x18, 0xad, 0x68, 0xad, + 0xe0, 0xad, 0xf8, 0xad, 0x10, 0xae, 0x28, 0xae, 0x40, 0xae, 0x58, 0xae, + 0x70, 0xae, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, 0xd0, 0xae, 0xe8, 0xae, + 0x00, 0xaf, 0x18, 0xaf, 0x30, 0xaf, 0x98, 0xaf, 0xb0, 0xaf, 0xc8, 0xaf, + 0xe0, 0xaf, 0xf8, 0xaf, 0x00, 0x90, 0x07, 0x00, 0x94, 0x00, 0x00, 0x00, + 0x10, 0xa0, 0x28, 0xa0, 0x40, 0xa0, 0x58, 0xa0, 0x70, 0xa0, 0x88, 0xa0, + 0xd8, 0xa0, 0xe8, 0xa0, 0x40, 0xa1, 0x50, 0xa1, 0x30, 0xa3, 0x48, 0xa3, + 0x60, 0xa3, 0x78, 0xa3, 0xf8, 0xa3, 0x10, 0xa4, 0x28, 0xa4, 0x40, 0xa4, + 0x58, 0xa4, 0x70, 0xa4, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xe8, 0xa4, + 0x88, 0xa5, 0xa0, 0xa5, 0x20, 0xa6, 0xa0, 0xa6, 0xb8, 0xa6, 0xd0, 0xa6, + 0xe8, 0xa6, 0x00, 0xa7, 0x18, 0xa7, 0xa0, 0xa7, 0xc8, 0xa8, 0xd8, 0xa8, + 0xe8, 0xa8, 0x60, 0xa9, 0x78, 0xa9, 0x90, 0xa9, 0xa8, 0xa9, 0xc0, 0xa9, + 0xd8, 0xa9, 0xf0, 0xa9, 0x08, 0xaa, 0x20, 0xaa, 0x38, 0xaa, 0x50, 0xaa, + 0x68, 0xaa, 0x80, 0xaa, 0x98, 0xaa, 0xb0, 0xaa, 0xc8, 0xaa, 0x70, 0xac, + 0x88, 0xac, 0xa0, 0xac, 0xb8, 0xac, 0xd0, 0xac, 0xe8, 0xac, 0x00, 0xad, + 0x18, 0xad, 0x30, 0xad, 0xb8, 0xad, 0xf8, 0xad, 0x08, 0xae, 0xc8, 0xae, + 0x58, 0xaf, 0x98, 0xaf, 0xe0, 0xaf, 0x00, 0x00, 0x00, 0xa0, 0x07, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x78, 0xa0, 0xc0, 0xa0, 0xd0, 0xa0, 0xe0, 0xa0, + 0xf0, 0xa0, 0x70, 0xa1, 0x88, 0xa1, 0x08, 0xa2, 0xa8, 0xa2, 0xb8, 0xa2, + 0xc8, 0xa2, 0x60, 0xa3, 0x98, 0xa3, 0xb0, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, + 0x60, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xf8, 0xa5, 0x28, 0xa6, 0x40, 0xa6, + 0x98, 0xa6, 0xb0, 0xa6, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x88, 0xa7, + 0xb8, 0xa7, 0xb0, 0xa9, 0xc8, 0xa9, 0x60, 0xaa, 0x78, 0xaa, 0x90, 0xaa, + 0xa8, 0xaa, 0xc0, 0xaa, 0xd0, 0xab, 0x30, 0xac, 0xa8, 0xac, 0xa0, 0xad, + 0xb8, 0xad, 0xd0, 0xad, 0xe8, 0xad, 0x00, 0xae, 0x30, 0xae, 0x50, 0xae, + 0xa8, 0xae, 0xb8, 0xaf, 0x00, 0xb0, 0x07, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x90, 0xa0, 0xa8, 0xa0, 0xc0, 0xa0, 0x30, 0xa3, 0x48, 0xa3, 0x60, 0xa3, + 0x78, 0xa3, 0x90, 0xa3, 0xa8, 0xa3, 0xc0, 0xa3, 0x80, 0xa4, 0xe8, 0xa4, + 0x08, 0xa5, 0x20, 0xa5, 0x38, 0xa5, 0x50, 0xa5, 0x68, 0xa5, 0x70, 0xa6, + 0xe0, 0xa6, 0xf8, 0xa6, 0x10, 0xa7, 0x28, 0xa7, 0x40, 0xa7, 0x58, 0xa7, + 0x98, 0xa7, 0x28, 0xa8, 0x00, 0xc0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, + 0x10, 0xa3, 0x48, 0xa3, 0x48, 0xa9, 0x60, 0xa9, 0x78, 0xa9, 0x90, 0xa9, + 0xa8, 0xa9, 0xc0, 0xa9, 0xe0, 0xaa, 0xa8, 0xab, 0x00, 0xad, 0x10, 0xad, + 0x20, 0xad, 0x30, 0xad, 0x40, 0xad, 0x50, 0xad, 0x60, 0xad, 0x70, 0xad, + 0x80, 0xad, 0x90, 0xad, 0xa0, 0xad, 0xb0, 0xad, 0xc0, 0xad, 0xd0, 0xad, + 0xe0, 0xad, 0xf0, 0xad, 0x00, 0xae, 0x10, 0xae, 0x20, 0xae, 0x30, 0xae, + 0x40, 0xae, 0x50, 0xae, 0x60, 0xae, 0x70, 0xae, 0x80, 0xae, 0x90, 0xae, + 0xa0, 0xae, 0xb0, 0xae, 0xc0, 0xae, 0xd0, 0xae, 0xe0, 0xae, 0xf0, 0xae, + 0x00, 0xd0, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xd0, 0xa0, 0x00, 0x00, + 0x00, 0xe0, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x70, 0xa4, 0xc0, 0xa4, + 0x18, 0xa5, 0x88, 0xa6, 0xa0, 0xa6, 0xf0, 0xa6, 0x00, 0xa7, 0x10, 0xa7, + 0x20, 0xa7, 0x30, 0xa7, 0x60, 0xa7, 0x98, 0xa7, 0xe0, 0xa7, 0x30, 0xa8, + 0x68, 0xa8, 0x80, 0xa8, 0xc8, 0xa8, 0x50, 0xa9, 0x00, 0xf0, 0x07, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x08, 0xa6, 0x20, 0xa6, 0x58, 0xa6, 0x88, 0xa6, + 0xa0, 0xa6, 0xb8, 0xa6, 0xd0, 0xa6, 0xe8, 0xa6, 0x00, 0xa7, 0x18, 0xa7, + 0x30, 0xa7, 0x70, 0xa7, 0x88, 0xa7, 0xa0, 0xa7, 0xb8, 0xa7, 0xd0, 0xa7, + 0x08, 0xa8, 0x38, 0xa8, 0x50, 0xa8, 0x68, 0xa8, 0x80, 0xa8, 0xb8, 0xa8, + 0xd0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0x30, 0xa9, 0xc0, 0xa9, + 0x00, 0x00, 0x08, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x18, 0xa0, + 0x48, 0xa0, 0x60, 0xa0, 0xa8, 0xa0, 0xb8, 0xa0, 0xc8, 0xa0, 0xf0, 0xa0, + 0x28, 0xa1, 0x68, 0xa6, 0x78, 0xa6, 0x88, 0xa6, 0x98, 0xa6, 0xe8, 0xa6, + 0xf8, 0xa6, 0x08, 0xa7, 0x18, 0xa7, 0x28, 0xa7, 0x50, 0xa7, 0x60, 0xa7, + 0x70, 0xa7, 0xd8, 0xa7, 0xe8, 0xa7, 0xf8, 0xa7, 0x40, 0xa8, 0x50, 0xa8, + 0x88, 0xa8, 0x98, 0xa8, 0xc0, 0xa8, 0xd0, 0xa8, 0x08, 0xa9, 0x20, 0xa9, + 0x78, 0xa9, 0xe8, 0xaf, 0x00, 0x10, 0x08, 0x00, 0x6c, 0x00, 0x00, 0x00, + 0x00, 0xa0, 0x38, 0xa0, 0x88, 0xa0, 0xd0, 0xa0, 0xe0, 0xa0, 0x68, 0xa2, + 0xb8, 0xa2, 0xc8, 0xa2, 0xf8, 0xa2, 0x30, 0xa3, 0x40, 0xa3, 0x90, 0xa3, + 0xa0, 0xa3, 0xe8, 0xa3, 0xf8, 0xa3, 0x30, 0xa8, 0x48, 0xa8, 0x58, 0xa9, + 0x70, 0xa9, 0x20, 0xaa, 0x38, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xd8, 0xaa, + 0xe8, 0xaa, 0x20, 0xab, 0x30, 0xab, 0x60, 0xab, 0x70, 0xab, 0x88, 0xab, + 0xc8, 0xab, 0xd8, 0xab, 0xf0, 0xab, 0x48, 0xac, 0x58, 0xac, 0x70, 0xac, + 0x88, 0xac, 0xa0, 0xac, 0xd8, 0xac, 0xf0, 0xac, 0x08, 0xad, 0x20, 0xad, 0xb8, 0xad, 0xd0, 0xad, 0xe8, 0xad, 0x00, 0xae, 0x18, 0xae, 0x30, 0xae, - 0x48, 0xae, 0x60, 0xae, 0x78, 0xae, 0x90, 0xae, 0xa8, 0xae, 0x00, 0x00, - 0x00, 0x70, 0x07, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf8, 0xa0, 0x10, 0xa1, - 0x08, 0xa2, 0xb0, 0xa2, 0x60, 0xa3, 0x78, 0xa3, 0x90, 0xa3, 0xa8, 0xa3, - 0xc0, 0xa3, 0xd8, 0xa3, 0xf0, 0xa3, 0x58, 0xa4, 0x70, 0xa4, 0x88, 0xa4, - 0xf8, 0xa4, 0x28, 0xa5, 0x00, 0x90, 0x07, 0x00, 0x14, 0x00, 0x00, 0x00, - 0xf0, 0xa6, 0xf8, 0xa6, 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, - 0x00, 0x30, 0x1e, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x90, 0xa8, - 0x98, 0xa8, 0xa0, 0xa8, 0xa8, 0xa8, 0x00, 0x00, 0x00, 0x50, 0x1f, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0xe0, 0xae, 0x00, 0x00, 0x00, 0x20, 0x63, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x00, 0x00, + 0x48, 0xae, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x18, 0xa0, 0x30, 0xa0, 0x70, 0xa0, 0x88, 0xa0, 0xa0, 0xa0, 0xb8, 0xa0, + 0xf8, 0xa0, 0x78, 0xa1, 0x90, 0xa1, 0xa8, 0xa1, 0xc0, 0xa1, 0xd8, 0xa1, + 0xf0, 0xa1, 0x08, 0xa2, 0x20, 0xa2, 0x38, 0xa2, 0x50, 0xa2, 0x68, 0xa2, + 0x80, 0xa2, 0x98, 0xa2, 0xe8, 0xa4, 0x00, 0xa5, 0xf8, 0xa5, 0x10, 0xa6, + 0x28, 0xa6, 0x78, 0xa6, 0x10, 0xa7, 0x90, 0xa7, 0x40, 0xa8, 0x58, 0xa8, + 0x70, 0xa8, 0x88, 0xa8, 0xa0, 0xa8, 0xb8, 0xa8, 0xd0, 0xa8, 0x10, 0xa9, + 0x00, 0x40, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0xa6, 0xf8, 0xa6, + 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x00, 0xe0, 0x1e, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x80, 0xa8, 0x88, 0xa8, 0x90, 0xa8, 0x98, 0xa8, + 0xb0, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0xe8, 0xae, 0x00, 0x00, 0x00, 0xd0, 0x63, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x20, 0xa0, 0x00, 0x00, ]; diff --git a/setup-dev.sh b/setup-dev.sh index 1c7c6e6f..afead4e0 100755 --- a/setup-dev.sh +++ b/setup-dev.sh @@ -447,6 +447,24 @@ do_inject_apps() { log_info "Building morpheus-cli (host)..." cargo build --release -p morpheus-cli 2>&1 || { log_error "morpheus-cli build failed"; return 1; } + # Unified image layout: p1=ESP, p2=HelixFS data region. + local loopdev="" + local data_part="" + loopdev=$(sudo losetup --find --show --partscan "$disk_img") || { + log_error "Failed to attach loop device for unified image" + return 1 + } + data_part="${loopdev}p2" + for _ in {1..50}; do + [[ -b "$data_part" ]] && break + sleep 0.1 + done + if [[ ! -b "$data_part" ]]; then + sudo losetup -d "$loopdev" 2>/dev/null || true + log_error "Unified image data partition not found (${data_part})" + return 1 + fi + local injected=0 for entry in "${USER_APPS[@]}"; do local pkg="${entry%%,*}" @@ -458,17 +476,88 @@ do_inject_apps() { continue fi - log_info "Injecting ${pkg} → ${dest} (main disk HelixFS)..." - cargo run --release -p morpheus-cli -- inject "$disk_img" "$elf" --dest "$dest" 2>&1 \ - || { log_error "Inject failed for ${pkg}"; return 1; } + log_info "Injecting ${pkg} → ${dest} (HelixFS partition)..." + sudo "${PROJECT_ROOT}/target/release/morpheus-cli" inject "$data_part" "$elf" --dest "$dest" 2>&1 \ + || { + sudo losetup -d "$loopdev" 2>/dev/null || true + log_error "Inject failed for ${pkg}" + return 1 + } injected=$((injected + 1)) done + sudo losetup -d "$loopdev" 2>/dev/null || true + [[ $injected -gt 0 ]] \ && log_success "${injected} app(s) deployed to HelixFS — boot MorpheusX and run 'exec '" \ || log_warn "No apps injected" } +sync_unified_esp() { + local disk_img="${TESTING_DIR}/test-disk-50g.img" + [[ -f "$disk_img" ]] || return 1 + + set +e + local rc=0 + local loopdev="" + local esp_part="" + local mnt="" + + loopdev=$(sudo losetup --find --show --partscan "$disk_img") + if [[ -z "$loopdev" ]]; then + rc=1 + fi + + if [[ $rc -eq 0 ]]; then + esp_part="${loopdev}p1" + for _ in {1..50}; do + [[ -b "$esp_part" ]] && break + sleep 0.1 + done + [[ -b "$esp_part" ]] || rc=1 + fi + + if [[ $rc -eq 0 ]]; then + mnt=$(mktemp -d) + if ! sudo mount -t vfat "$esp_part" "$mnt"; then + log_warn "ESP mount failed; reformatting partition 1 as FAT32" + sudo partprobe "$loopdev" >/dev/null 2>&1 || true + has_cmd udevadm && sudo udevadm settle >/dev/null 2>&1 || true + + if ! sudo mkfs.vfat -F 32 -n MORPHEUS "$esp_part" >/dev/null 2>&1; then + rc=1 + else + # Retry mount a few times in case kernel uevents lag after mkfs. + local mounted=0 + for _ in {1..10}; do + if sudo mount -t vfat "$esp_part" "$mnt"; then + mounted=1 + break + fi + sleep 0.1 + done + [[ $mounted -eq 1 ]] || rc=1 + fi + fi + fi + + if [[ $rc -eq 0 ]]; then + sudo mkdir -p "$mnt/EFI/BOOT" || rc=1 + fi + + if [[ $rc -eq 0 ]]; then + sudo rsync -rtD --delete "${ESP_DIR}/" "$mnt/" || rc=1 + sync + fi + + [[ -n "$mnt" ]] && sudo umount "$mnt" >/dev/null 2>&1 + [[ -n "$mnt" ]] && rmdir "$mnt" >/dev/null 2>&1 + [[ -n "$loopdev" ]] && sudo losetup -d "$loopdev" >/dev/null 2>&1 + + set -e + return $rc +} + do_create_disk() { log_step "Creating Test Disk" @@ -479,15 +568,38 @@ do_create_disk() { local disk_img="${TESTING_DIR}/test-disk-50g.img" - # Create a raw sparse image — no partition table. - # The kernel's storage layer skips GPT/MBR disks as "boot disks", so the - # HelixFS data disk must be partition-table-free. morpheus-cli inject will - # format HelixFS on first write; no pre-formatting needed. - log_info "Creating 50GB sparse HelixFS data disk..." + # Unified image layout: + # p1: EFI System Partition (FAT32, BOOTX64.EFI) + # p2: HelixFS data partition (raw, formatted by morpheus-cli inject) + log_info "Creating 50GB unified GPT disk (ESP + HelixFS)..." rm -f "$disk_img" truncate -s 50G "$disk_img" - log_success "Test disk ready (50GB sparse, will be formatted by inject)" + parted "$disk_img" --script mklabel gpt + parted "$disk_img" --script mkpart ESP fat32 1MiB 513MiB + parted "$disk_img" --script set 1 esp on + parted "$disk_img" --script set 1 boot on + parted "$disk_img" --script mkpart HELIX 513MiB 100% + + local loopdev + loopdev=$(sudo losetup --find --show --partscan "$disk_img") || die "Failed to attach unified disk loop device" + local esp_part="${loopdev}p1" + for _ in {1..50}; do + [[ -b "$esp_part" ]] && break + sleep 0.1 + done + [[ -b "$esp_part" ]] || { sudo losetup -d "$loopdev" 2>/dev/null || true; die "ESP partition node not found"; } + + sudo mkfs.vfat -F 32 -n MORPHEUS "$esp_part" >/dev/null 2>&1 || { + sudo losetup -d "$loopdev" 2>/dev/null || true + die "Failed to format ESP partition" + } + + sudo losetup -d "$loopdev" 2>/dev/null || true + + sync_unified_esp || die "Failed to populate ESP partition from testing/esp" + + log_success "Unified disk ready (GPT: p1 ESP, p2 HelixFS)" } do_launch_qemu() { @@ -520,35 +632,17 @@ do_launch_qemu() { fi if [[ -f "${TESTING_DIR}/test-disk-50g.img" ]]; then - # Build/refresh the boot ESP image that UEFI will load BOOTX64.EFI from. - # The data disk (test-disk-50g.img) is raw HelixFS with no partition table - # so the kernel won't mistake it for the boot disk. - local esp_img="${TESTING_DIR}/esp-temp.img" - if [[ ! -f "$esp_img" ]] || [[ "${ESP_DIR}/EFI/BOOT/BOOTX64.EFI" -nt "$esp_img" ]] || [[ "${FORCE_MODE}" == "true" ]]; then - log_info "Refreshing boot ESP image..." - local esp_size - esp_size=$(du -sb "${ESP_DIR}" | awk '{print int(($1 / 1024 / 1024) + 50)}') - dd if=/dev/zero of="$esp_img" bs=1M count="$esp_size" status=none 2>/dev/null - mkfs.vfat -F 32 -n ESP "$esp_img" >/dev/null 2>&1 - local mnt - mnt=$(mktemp -d) - sudo mount -o loop "$esp_img" "$mnt" - sudo rsync -a "${ESP_DIR}/" "$mnt/" 2>/dev/null || true - sudo umount "$mnt" - rmdir "$mnt" - fi - # Disk 0 (virtio): ESP FAT32 image — UEFI boots BOOTX64.EFI from here - # Disk 1 (virtio): raw HelixFS image — kernel mounts this as data storage + sync_unified_esp || die "Failed to refresh unified ESP partition" + + # Disk 0 (virtio): unified GPT image (p1 ESP + p2 HelixFS) if ! run_qemu_command qemu-system-x86_64 \ -enable-kvm \ -machine q35,accel=kvm,i8042=on,usb=off \ -cpu host \ -bios "$ovmf_path" \ -object iothread,id=iothread0 \ - -drive file="$esp_img",format=raw,if=none,id=disk0,cache=writeback \ + -drive file="${TESTING_DIR}/test-disk-50g.img",format=raw,if=none,id=disk0,cache=writeback \ -device virtio-blk-pci,drive=disk0,disable-legacy=on,bootindex=1 \ - -drive file="${TESTING_DIR}/test-disk-50g.img",format=raw,if=none,id=disk1,cache=writeback \ - -device virtio-blk-pci,drive=disk1,disable-legacy=on,iothread=iothread0 \ "${net_args[@]}" \ -smp 8 \ -m 12G \ @@ -796,25 +890,9 @@ do_launch_thinkpad() { fi if [[ -f "${TESTING_DIR}/test-disk-50g.img" ]]; then - # Build/refresh the boot ESP image that UEFI will load BOOTX64.EFI from. - # The data disk (test-disk-50g.img) is raw HelixFS with no partition table - # so the kernel won't mistake it for the boot disk. - local esp_img="${TESTING_DIR}/esp-temp.img" - if [[ ! -f "$esp_img" ]] || [[ "${ESP_DIR}/EFI/BOOT/BOOTX64.EFI" -nt "$esp_img" ]] || [[ "${FORCE_MODE}" == "true" ]]; then - log_info "Refreshing boot ESP image..." - local esp_size - esp_size=$(du -sb "${ESP_DIR}" | awk '{print int(($1 / 1024 / 1024) + 50)}') - dd if=/dev/zero of="$esp_img" bs=1M count="$esp_size" status=none 2>/dev/null - mkfs.vfat -F 32 -n ESP "$esp_img" >/dev/null 2>&1 - local mnt - mnt=$(mktemp -d) - sudo mount -o loop "$esp_img" "$mnt" - sudo rsync -a "${ESP_DIR}/" "$mnt/" 2>/dev/null || true - sudo umount "$mnt" - rmdir "$mnt" - fi - # Disk 0 (SATA port 0): ESP FAT32 image — UEFI boots BOOTX64.EFI from here - # Disk 1 (SATA port 1): raw HelixFS image — kernel mounts this as data storage + sync_unified_esp || die "Failed to refresh unified ESP partition" + + # Disk 0 (AHCI): unified GPT image (p1 ESP + p2 HelixFS) if ! run_qemu_command qemu-system-x86_64 \ -enable-kvm \ -machine q35,accel=kvm,i8042=on,usb=off \ @@ -825,11 +903,9 @@ do_launch_thinkpad() { -smbios type=2,manufacturer=LENOVO,product=20BWS0XX00 \ -smbios type=3,manufacturer=LENOVO \ -object iothread,id=iothread0 \ - -drive file="$esp_img",format=raw,if=none,id=disk0,cache=writeback \ + -drive file="${TESTING_DIR}/test-disk-50g.img",format=raw,if=none,id=disk0,cache=writeback \ -device ich9-ahci,id=ahci0 \ -device ide-hd,drive=disk0,bus=ahci0.0,bootindex=1 \ - -drive file="${TESTING_DIR}/test-disk-50g.img",format=raw,if=none,id=disk1,cache=writeback \ - -device ide-hd,drive=disk1,bus=ahci0.1 \ "${net_args[@]}" \ -smp 8 \ -m 12G \ @@ -911,6 +987,135 @@ cmd_thinkpad() { do_launch_thinkpad } +# ══════════════════════════════════════════════════════════════════════════════ +# Flash to Physical Media +# Provisions a real block device (USB stick, SD card) with the two-partition +# layout used by the unified image — but only copies what's actually needed. +# No 50 GB dd. Just ESP contents + app injection. +# ══════════════════════════════════════════════════════════════════════════════ + +cmd_flash() { + local target="${1:-}" + print_banner + log_step "Flash to Physical Media" + + [[ -n "$target" ]] || die "Usage: $0 flash (e.g. $0 flash /dev/sdd)" + + log_step "Refreshing build artifacts" + FORCE_MODE=true + do_build + do_build_user_apps + + # Refuse to flash the device that contains the current root fs. + local root_dev + root_dev=$(findmnt -n -o SOURCE / 2>/dev/null | sed 's/[0-9]*$//' || true) + [[ -z "$root_dev" ]] && root_dev=$(df / 2>/dev/null | awk 'NR==2{print $1}' | sed 's/[0-9]*$//' || true) + if [[ -n "$root_dev" && "$(realpath "$target")" == "$(realpath "$root_dev")" ]]; then + die "Refusing to flash to the system root device (${root_dev})" + fi + + # Only write to block devices. + [[ -b "$target" ]] || die "${target} is not a block device" + + # Removable-only guard (RM flag in sysfs). If sysfs says 0 it's internal. + local sysfs_name + sysfs_name=$(basename "$target") + local rm_flag=1 + [[ -r "/sys/block/${sysfs_name}/removable" ]] && rm_flag=$(cat "/sys/block/${sysfs_name}/removable") + if [[ "$rm_flag" != "1" ]]; then + log_warn "${target} does not report as removable (sysfs RM=0)" + printf "${C_RED}${C_BOLD}This looks like an internal disk. Type CONFIRM to proceed anyway: ${C_RESET}" + read -r answer + [[ "$answer" == "CONFIRM" ]] || die "Aborted" + fi + + # Show what we're about to do and ask for confirmation. + local dev_size + dev_size=$(lsblk -dno SIZE "$target" 2>/dev/null || echo "?") + local dev_model + dev_model=$(lsblk -dno MODEL "$target" 2>/dev/null | xargs || echo "unknown") + printf "\n" + printf " ${C_BOLD}Target :${C_RESET} ${C_CYAN}%s${C_RESET}\n" "$target" + printf " ${C_BOLD}Model :${C_RESET} %s\n" "$dev_model" + printf " ${C_BOLD}Size :${C_RESET} %s\n" "$dev_size" + printf " ${C_BOLD}Action :${C_RESET} repartition → p1 ESP (512 MiB FAT32) + p2 HELIX (rest)\n" + printf " copy ESP files only + inject apps. No full dd.\n" + printf "\n" + printf "${C_YELLOW}${C_BOLD}ALL DATA ON ${target} WILL BE LOST. Type YES to continue: ${C_RESET}" + read -r answer + [[ "$answer" == "YES" ]] || die "Aborted" + + log_step "Unmounting partitions on ${target}" + for part in $(lsblk -lno NAME "$target" | tail -n +2); do + sudo umount "/dev/${part}" 2>/dev/null && log_info "Unmounted /dev/${part}" || true + done + + log_step "Partitioning ${target}" + sudo parted "$target" --script mklabel gpt + sudo parted "$target" --script mkpart ESP fat32 1MiB 513MiB + sudo parted "$target" --script set 1 esp on + sudo parted "$target" --script set 1 boot on + sudo parted "$target" --script mkpart HELIX 513MiB 100% + sudo partprobe "$target" 2>/dev/null || true + has_cmd udevadm && sudo udevadm settle 2>/dev/null || sleep 1 + + # Resolve partition nodes (handles both /dev/sddN and /dev/mmcblkXpN). + local esp_part helix_part + if [[ -b "${target}p1" ]]; then + esp_part="${target}p1" + helix_part="${target}p2" + else + esp_part="${target}1" + helix_part="${target}2" + fi + + # Wait for nodes to appear. + for _ in {1..50}; do [[ -b "$esp_part" ]] && break; sleep 0.1; done + [[ -b "$esp_part" ]] || die "ESP partition node did not appear (${esp_part})" + for _ in {1..50}; do [[ -b "$helix_part" ]] && break; sleep 0.1; done + [[ -b "$helix_part" ]] || die "HELIX partition node did not appear (${helix_part})" + + log_step "Formatting ESP (${esp_part})" + sudo mkfs.vfat -F 32 -n MORPHEUS "$esp_part" >/dev/null \ + || die "Failed to format ESP partition" + + log_step "Copying ESP contents" + check_bootloader || die "Bootloader not built — run: $0 build" + local mnt + mnt=$(mktemp -d) + sudo mount -t vfat "$esp_part" "$mnt" || die "Failed to mount ESP on ${esp_part}" + sudo mkdir -p "$mnt/EFI/BOOT" + sudo rsync -rtD --delete "${ESP_DIR}/" "$mnt/" \ + || { sudo umount "$mnt"; rmdir "$mnt"; die "rsync to ESP failed"; } + sudo umount "$mnt" + rmdir "$mnt" + log_success "ESP written to ${esp_part}" + + log_step "Injecting apps into HELIX (${helix_part})" + # Build morpheus-cli first. + cargo build --release -p morpheus-cli 2>&1 \ + || die "morpheus-cli build failed" + + local injected=0 + for entry in "${USER_APPS[@]}"; do + local pkg="${entry%%,*}" + local dest="${entry##*,}" + local elf="target/x86_64-morpheus/release/${pkg}" + if [[ ! -f "$elf" ]]; then + log_warn "${pkg}: ELF not found (build first), skipping" + continue + fi + log_info "Injecting ${pkg} → ${dest}" + sudo "${PROJECT_ROOT}/target/release/morpheus-cli" inject "$helix_part" "$elf" --dest "$dest" 2>&1 \ + || { log_error "Inject failed for ${pkg}"; continue; } + injected=$((injected + 1)) + done + + sudo sync + log_success "Flash complete — ${injected} app(s) on ${helix_part}" + printf "\n Eject the card and boot the ThinkPad from it.\n\n" +} + cmd_clean() { print_banner log_step "Cleaning" @@ -970,6 +1175,7 @@ usage() { printf " ${C_CYAN}disk${C_RESET} [target] Create disk image (50g|info)\n" printf " ${C_CYAN}run${C_RESET} Launch QEMU (VirtIO devices)\n" printf " ${C_CYAN}thinkpad${C_RESET} Launch QEMU with ThinkPad T450s hardware\n" + printf " ${C_CYAN}flash${C_RESET} Write to physical media (SD/USB) — no full dd\n" printf " ${C_CYAN}deploy${C_RESET} Build & inject user apps into HelixFS\n" printf " ${C_CYAN}status${C_RESET} Show environment status\n" printf " ${C_CYAN}clean${C_RESET} Remove artifacts\n" @@ -989,6 +1195,8 @@ usage() { printf " %s build\n\n" "$(basename "$0")" printf " ${C_DIM}# Test with ThinkPad T450s hardware (AHCI + Intel e1000)${C_RESET}\n" printf " %s thinkpad\n\n" "$(basename "$0")" + printf " ${C_DIM}# Flash to real SD card / USB stick (fast — ESP files + apps only)${C_RESET}\n" + printf " %s flash /dev/sdd\n\n" "$(basename "$0")" } main() { @@ -1029,6 +1237,7 @@ main() { disk|image) cmd_disk "${args[@]:-}" ;; run|start|qemu) cmd_run ;; thinkpad|t450s) cmd_thinkpad ;; + flash) cmd_flash "${args[0]:-}" ;; status|info) cmd_status ;; clean|purge) cmd_clean ;; *) die "Unknown command: $cmd" ;; diff --git a/shelld/Cargo.toml b/shelld/Cargo.toml index 92c3ad82..9cb032eb 100644 --- a/shelld/Cargo.toml +++ b/shelld/Cargo.toml @@ -12,6 +12,3 @@ libmorpheus = { path = "../libmorpheus" } channel = { path = "../channel" } display = { path = "../display", features = ["framebuffer-backend"], package = "morpheus-display" } -[profile.release] -opt-level = 3 -lto = true From 8ddbfb3b6a9a3193be18464f2bb98a0eb56f3ed8 Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Fri, 13 Mar 2026 16:40:44 +0100 Subject: [PATCH 3/8] Add SDHCI and USB mass-storage driver scaffolds with assembly primitives - Implement SDHCI initialization primitives in assembly, including capabilities reading, card presence checking, controller reset, basic power and clock setup, and block read via PIO. - Define SDHCI register constants in a separate assembly file for better organization. - Create a Rust module for the SDHCI block device driver, establishing the BlockDriver interface and handling initialization and command submission. - Introduce a USB mass-storage driver scaffold with basic probing and reset functionality, reserving the interface for future implementation. --- bootloader/asm/keyboard/ps2.s | 2 + bootloader/src/storage.rs | 764 +++++++++++++++++++---- bootloader/src/tui/desktop.rs | 4 +- bootloader/src/tui/input.rs | 128 +++- bootloader/src/tui/mouse.rs | 17 +- hwinit/src/cpu/ap_boot.rs | 40 +- hwinit/src/cpu/apic.rs | 102 ++- hwinit/src/platform.rs | 2 +- network/asm/drivers/sdhci/init.s | 290 +++++++++ network/asm/drivers/sdhci/regs.s | 39 ++ network/asm/drivers/usb/init.s | 35 ++ network/build.rs | 6 + network/src/boot/block_probe.rs | 393 ++++++++++-- network/src/device/mod.rs | 48 ++ network/src/driver/ahci/mod.rs | 226 ++++--- network/src/driver/mod.rs | 4 + network/src/driver/sdhci/mod.rs | 354 +++++++++++ network/src/driver/usb_msd/mod.rs | 136 ++++ network/src/lib.rs | 2 + persistent/src/pe/embedded_reloc_data.rs | 207 +++--- 20 files changed, 2367 insertions(+), 432 deletions(-) create mode 100644 network/asm/drivers/sdhci/init.s create mode 100644 network/asm/drivers/sdhci/regs.s create mode 100644 network/asm/drivers/usb/init.s create mode 100644 network/src/driver/sdhci/mod.rs create mode 100644 network/src/driver/usb_msd/mod.rs diff --git a/bootloader/asm/keyboard/ps2.s b/bootloader/asm/keyboard/ps2.s index 4e22d274..c5abc370 100644 --- a/bootloader/asm/keyboard/ps2.s +++ b/bootloader/asm/keyboard/ps2.s @@ -34,6 +34,7 @@ asm_ps2_write_cmd: pause dec r8 jnz .wcmd_ibf_wait + ret .wcmd_send: mov al, cl out PS2_CMD, al @@ -49,6 +50,7 @@ asm_ps2_write_data: pause dec r8 jnz .wdat_ibf_wait + ret .wdat_send: mov al, cl out PS2_DATA, al diff --git a/bootloader/src/storage.rs b/bootloader/src/storage.rs index 39c7cab7..e569de3a 100644 --- a/bootloader/src/storage.rs +++ b/bootloader/src/storage.rs @@ -24,15 +24,20 @@ //! Total ≈ 128 KB (well within 2 MB) //! ``` -use morpheus_helix::device::RawBlockDevice; +use morpheus_helix::device::{MemBlockDevice, RawBlockDevice}; +/// In-RAM copy of the selected Helix partition (if RAM staging succeeds). +static mut RAM_HELIX_DEVICE: Option = None; use morpheus_hwinit::dma::DmaRegion; +use morpheus_hwinit::memory::{global_registry_mut, AllocateType, MemoryType}; use morpheus_hwinit::paging::is_paging_initialized; use morpheus_hwinit::serial::{log_error, log_info, log_ok, log_warn, puts}; use morpheus_hwinit::{kmap_mmio, pci_cfg_read16, pci_cfg_read32, PciAddr}; use morpheus_network::{ - create_unified_from_detected, create_unified_from_detected_ahci_port, scan_all_block_devices, - BlockDmaConfig, BlockDriver, DetectedBlockDevice, UnifiedBlockDevice, UnifiedBlockIo, + AhciInitError, SdhciInitError, UsbMsdInitError, VirtioBlkInitError, + create_unified_from_detected, scan_all_block_devices, BlockDmaConfig, BlockDriver, + DetectedBlockDevice, UnifiedBlockDevice, UnifiedBlockIo, }; +use morpheus_network::device::UnifiedBlockError; // DMA LAYOUT CONSTANTS @@ -54,6 +59,7 @@ const OFF_AHCI_IDENTIFY: usize = 0x0_4800; // I/O transfer buffer — used by UnifiedBlockIo for synchronous read/write const OFF_IO_BUFFER: usize = 0x1_0000; const IO_BUFFER_SIZE: usize = 64 * 1024; // 64 KB = UnifiedBlockIo::MAX_TRANSFER_SIZE +const UNKNOWN_TOTAL_SECTORS: u64 = u32::MAX as u64; // PCI BUS DUMP (diagnostic) @@ -215,6 +221,9 @@ static mut STORAGE_REGION_SECTORS: u64 = 0; /// Whether persistent storage was successfully initialized. static mut PERSISTENT_READY: bool = false; +const RAM_STAGE_MAX_BYTES: u64 = 512 * 1024 * 1024; +static mut RAM_STAGE_LAST_REASON: &'static str = "none"; + const GPT_SIG: &[u8; 8] = b"EFI PART"; // EFI System Partition type GUID on disk (little-endian fields). const GPT_TYPE_ESP: [u8; 16] = [ @@ -228,6 +237,13 @@ struct DataRegion { sectors: u64, } +#[derive(Clone, Copy)] +struct GptPartition { + type_guid: [u8; 16], + first_lba: u64, + last_lba: u64, +} + #[inline(always)] fn le_u32(buf: &[u8], off: usize) -> u32 { u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]]) @@ -247,11 +263,74 @@ fn le_u64(buf: &[u8], off: usize) -> u64 { ]) } +#[inline(always)] +fn select_largest_gpt_free_region( + first_usable: u64, + last_usable: u64, + used_ranges: &mut alloc::vec::Vec<(u64, u64)>, + sector_size: u32, +) -> Option { + if first_usable == 0 || last_usable < first_usable { + return None; + } + + used_ranges.sort_unstable_by_key(|(start, _)| *start); + + let mut cursor = first_usable; + let mut best_start = 0u64; + let mut best_sectors = 0u64; + + for (raw_start, raw_end) in used_ranges.iter().copied() { + if raw_end < first_usable || raw_start > last_usable { + continue; + } + + let start = raw_start.max(first_usable); + let end = raw_end.min(last_usable); + + if start > cursor { + let gap = start - cursor; + if gap > best_sectors { + best_start = cursor; + best_sectors = gap; + } + } + + let next = end.saturating_add(1); + if next > cursor { + cursor = next; + } + + if cursor > last_usable { + break; + } + } + + if cursor <= last_usable { + let tail = (last_usable - cursor) + 1; + if tail > best_sectors { + best_start = cursor; + best_sectors = tail; + } + } + + let min_free_sectors = ((64u64 * 1024 * 1024) + (sector_size as u64 - 1)) / sector_size as u64; + if best_sectors >= min_free_sectors { + Some(DataRegion { + lba_start: best_start, + sectors: best_sectors, + }) + } else { + None + } +} + /// Select a writable data region from the currently active block device. /// /// Policy: -/// - GPT disk: pick first non-ESP partition -/// - MBR-only disk: skip (treated as boot/system for now) +/// - GPT disk: prefer partition immediately after ESP on same disk +/// - MBR-only disk: prefer partition immediately after EFI partition entry +/// - Fallback: largest free GPT region, then first non-ESP partition, then largest MBR primary /// - No partition table: whole disk is data region unsafe fn select_data_region(sector_size: u32, total_sectors: u64) -> Option { let dev = BLOCK_DEVICE.as_mut()?; @@ -278,6 +357,8 @@ unsafe fn select_data_region(sector_size: u32, total_sectors: u64) -> Option Option::new(); + let mut partitions = alloc::vec::Vec::::new(); + let mut first_non_esp: Option = None; for idx in 0..num_entries { let sector_delta = idx / entries_per_sector; @@ -310,33 +394,176 @@ unsafe fn select_data_region(sector_size: u32, total_sectors: u64) -> Option = None; + for part in partitions.iter().skip(boot_idx + 1) { + if part.type_guid == GPT_TYPE_ESP { + continue; + } + if part.first_lba <= boot_part.last_lba { + continue; + } + + let sectors = part.last_lba - part.first_lba + 1; + if sectors == 0 { + continue; + } + + if part.first_lba == boot_part.last_lba.saturating_add(1) { + return Some(DataRegion { + lba_start: part.first_lba, + sectors, + }); + } + + if next_after_boot.is_none() { + next_after_boot = Some(DataRegion { + lba_start: part.first_lba, + sectors, + }); + } + } + + if let Some(region) = next_after_boot { + return Some(region); + } + } + + // Legacy fallback when explicit ESP pairing is unavailable. + if let Some(region) = + select_largest_gpt_free_region(first_usable, last_usable, &mut used_ranges, sector_size) + { + return Some(region); + } + + if let Some(region) = first_non_esp { + return Some(region); + } + + return None; + } + + if has_mbr { + // Prefer the partition entry after EFI partition entry in MBR layout. + const MBR_PART_OFF: usize = 446; + const MBR_PART_SIZE: usize = 16; + const MBR_PARTS: usize = 4; + let mut mbr_parts: [Option<(u8, u64, u64)>; MBR_PARTS] = [None, None, None, None]; + + let mut best_start = 0u64; + let mut best_sectors = 0u64; + + for i in 0..MBR_PARTS { + let off = MBR_PART_OFF + (i * MBR_PART_SIZE); + let ptype = first_two[off + 4]; + + // 0x00 empty, 0xEE GPT protective, 0xEF EFI system partition. + if ptype == 0x00 || ptype == 0xEE || ptype == 0xEF { + continue; + } + + // Extended partition containers are not directly writable data regions. + if ptype == 0x05 || ptype == 0x0F || ptype == 0x85 { + continue; + } + + let start = le_u32(&first_two, off + 8) as u64; + let sectors = le_u32(&first_two, off + 12) as u64; + + if start == 0 || sectors == 0 { + continue; + } + + if total_sectors != UNKNOWN_TOTAL_SECTORS + && start.saturating_add(sectors) > total_sectors + { + continue; + } + + mbr_parts[i] = Some((ptype, start, sectors)); + + if sectors > best_sectors { + best_start = start; + best_sectors = sectors; + } + } + + if let Some((boot_idx, _)) = mbr_parts + .iter() + .enumerate() + .find(|(_, p)| matches!(p, Some((0xEF, _, _)))) + { + for part in mbr_parts.iter().skip(boot_idx + 1) { + if let Some((ptype, start, sectors)) = *part { + if ptype == 0x00 || ptype == 0xEE || ptype == 0xEF { + continue; + } + if ptype == 0x05 || ptype == 0x0F || ptype == 0x85 { + continue; + } + if start == 0 || sectors == 0 { + continue; + } + + return Some(DataRegion { + lba_start: start, + sectors, + }); + } + } + } + + if best_sectors != 0 { return Some(DataRegion { - lba_start: first_lba, - sectors, + lba_start: best_start, + sectors: best_sectors, }); } return None; } - if has_mbr { - // Keep conservative behavior for plain MBR disks until partition parser exists. + if total_sectors == UNKNOWN_TOTAL_SECTORS { return None; } @@ -484,164 +711,293 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { // Map high-address MMIO BARs before driver init touches them if let DetectedBlockDevice::VirtIO { pci_addr, .. } = detected { map_virtio_bars(pci_addr.bus, pci_addr.device, pci_addr.function); + } else if let DetectedBlockDevice::Ahci(info) = detected { + if is_paging_initialized() { + // ABAR covers generic HBA regs + up to 32 ports × 0x80 = 0x1100. + // 0x2000 rounds to 2 pages; UC flags set by kmap_mmio. + let _ = kmap_mmio(info.abar, 0x2000); + } + } else if let DetectedBlockDevice::Sdhci(info) = detected { + if is_paging_initialized() { + // SDHCI register space is typically < 4KB, map one page. + let _ = kmap_mmio(info.mmio_base, 0x1000); + } + } else if let DetectedBlockDevice::UsbMsd(info) = detected { + if is_paging_initialized() { + // xHCI operational + runtime windows vary by controller; map a conservative range. + let _ = kmap_mmio(info.mmio_base, 0x4000); + } } let is_ahci = matches!(detected, DetectedBlockDevice::Ahci(_)); - let mut ahci_port_attempted = false; - let mut ahci_port_found = false; - - let max_ports = if is_ahci { 32 } else { 1 }; - - for port in 0..max_ports { - let device = if is_ahci { - ahci_port_attempted = true; - match create_unified_from_detected_ahci_port(detected, &config, port as u32) { - Ok(dev) => dev, - Err(_) => continue, - } - } else { - match create_unified_from_detected(detected, &config) { - Ok(dev) => dev, - Err(_) => { - log_warn("STORAGE", 825, "driver init failed for one candidate; skipping"); - break; + let device = match create_unified_from_detected(detected, &config) { + Ok(dev) => dev, + Err(err) => { + match err { + UnifiedBlockError::AhciError(e) => { + match e { + AhciInitError::InvalidConfig => { + log_warn("STORAGE", 825, "AHCI init failed: invalid config"); + } + AhciInitError::ResetFailed => { + log_warn("STORAGE", 825, "AHCI init failed: HBA reset timeout"); + } + AhciInitError::NoDeviceFound => { + log_warn("STORAGE", 825, "AHCI init failed: no SATA device found"); + } + AhciInitError::PortStopTimeout => { + log_warn("STORAGE", 825, "AHCI init failed: port stop timeout"); + } + AhciInitError::PortStartFailed => { + log_warn("STORAGE", 825, "AHCI init failed: port start failed"); + } + AhciInitError::IdentifyFailed => { + log_warn("STORAGE", 825, "AHCI init failed: IDENTIFY failed"); + } + AhciInitError::No64BitSupport => { + log_warn("STORAGE", 825, "AHCI init failed: no 64-bit DMA support"); + } + AhciInitError::DeviceNotResponding => { + log_warn("STORAGE", 825, "AHCI init failed: device not responding"); + } + AhciInitError::DmaSetupFailed => { + log_warn("STORAGE", 825, "AHCI init failed: DMA setup failed"); + } + } + log_warn("STORAGE", 825, "AHCI candidate skipped"); + } + UnifiedBlockError::VirtioError(e) => { + match e { + VirtioBlkInitError::ResetFailed => { + log_warn("STORAGE", 825, "VirtIO init failed: reset failed"); + } + VirtioBlkInitError::FeatureNegotiationFailed => { + log_warn("STORAGE", 825, "VirtIO init failed: feature negotiation failed"); + } + VirtioBlkInitError::QueueSetupFailed => { + log_warn("STORAGE", 825, "VirtIO init failed: queue setup failed"); + } + VirtioBlkInitError::DeviceFailed => { + log_warn("STORAGE", 825, "VirtIO init failed: device failed status"); + } + VirtioBlkInitError::InvalidConfig => { + log_warn("STORAGE", 825, "VirtIO init failed: invalid config"); + } + VirtioBlkInitError::TransportError => { + log_warn("STORAGE", 825, "VirtIO init failed: transport error"); + } + } + log_warn("STORAGE", 825, "VirtIO candidate skipped"); + } + UnifiedBlockError::NoDevice => { + if is_ahci { + log_warn("STORAGE", 825, "AHCI controller init failed; skipping candidate"); + } else { + log_warn("STORAGE", 825, "driver init failed for one candidate; skipping"); + } + } + UnifiedBlockError::SdhciError(e) => { + match e { + SdhciInitError::InvalidConfig => { + log_warn("STORAGE", 825, "SDHCI init failed: invalid config"); + } + SdhciInitError::ControllerResetFailed => { + log_warn("STORAGE", 825, "SDHCI init failed: controller reset failed"); + } + SdhciInitError::NoCardPresent => { + log_warn("STORAGE", 825, "SDHCI init failed: no card present"); + } + SdhciInitError::VoltageSwitchFailed => { + log_warn("STORAGE", 825, "SDHCI init failed: voltage switch failed"); + } + SdhciInitError::ClockSetupFailed => { + log_warn("STORAGE", 825, "SDHCI init failed: clock setup failed"); + } + SdhciInitError::CommandTimeout => { + log_warn("STORAGE", 825, "SDHCI init failed: command timeout"); + } + SdhciInitError::DataTimeout => { + log_warn("STORAGE", 825, "SDHCI init failed: data timeout"); + } + SdhciInitError::IoError => { + log_warn("STORAGE", 825, "SDHCI init failed: I/O error"); + } + SdhciInitError::NotImplemented => { + log_warn("STORAGE", 825, "SDHCI init failed: not implemented"); + } + } + log_warn("STORAGE", 825, "SDHCI candidate skipped"); + } + UnifiedBlockError::UsbMsdError(e) => { + match e { + UsbMsdInitError::InvalidConfig => { + log_warn("STORAGE", 825, "USB-MSD init failed: invalid config"); + } + UsbMsdInitError::ControllerInitFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: controller init failed"); + } + UsbMsdInitError::DeviceEnumerationFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: device enumeration failed"); + } + UsbMsdInitError::TransportInitFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: transport init failed"); + } + UsbMsdInitError::NoMedia => { + log_warn("STORAGE", 825, "USB-MSD init failed: no media"); + } + UsbMsdInitError::CommandTimeout => { + log_warn("STORAGE", 825, "USB-MSD init failed: command timeout"); + } + UsbMsdInitError::IoError => { + log_warn("STORAGE", 825, "USB-MSD init failed: I/O error"); + } + UsbMsdInitError::NotImplemented => { + log_warn("STORAGE", 825, "USB-MSD init failed: not implemented"); + } + } + log_warn("STORAGE", 825, "USB-MSD candidate skipped"); } } - }; - - if is_ahci { - ahci_port_found = true; + continue; } + }; - let info = device.info(); - - // Store temporarily to check if it's a boot disk. - BLOCK_DEVICE = Some(device); - - let region = match select_data_region(info.sector_size, info.total_sectors) { - Some(r) => r, - None => { - log_info("STORAGE", 826, "boot/system disk detected; skipping"); - BLOCK_DEVICE = None; - continue; - } - }; - - STORAGE_LBA_BASE = region.lba_start; - STORAGE_REGION_SECTORS = region.sectors; + let info = device.info(); - if region.lba_start != 0 { - log_info("STORAGE", 837, "selected GPT data partition region"); - } + // Store temporarily to check if it's a boot disk. + BLOCK_DEVICE = Some(device); - if region.sectors == 0 { + let region = match select_data_region(info.sector_size, info.total_sectors) { + Some(r) => r, + None => { + log_info("STORAGE", 826, "boot/system disk detected; skipping"); BLOCK_DEVICE = None; continue; } + }; - // This device is NOT a boot disk — use it for HelixFS. - log_ok("STORAGE", 827, "selected data disk"); - found_data_disk = true; - - // try to recover or format helixfs - let mut raw_dev = make_raw_block_device(); - - let needs_format = { - let mut probe_dev = make_raw_block_device(); - match morpheus_helix::log::recovery::recover_superblock( - &mut probe_dev, - 0, - info.sector_size, - ) { - Ok(sb) => { - // Version mismatch: v2 changed the log payload format. - if sb.version != morpheus_helix::types::HELIX_VERSION { - log_warn("STORAGE", 828, "helixfs version mismatch; reformat required"); - true - } else { - false - } - } - Err(_) => true, - } - }; - - if needs_format { - log_info("STORAGE", 829, "no valid helixfs; formatting disk"); - - let uuid = [ - 0x4Du8, 0x58, 0x52, 0x4F, 0x4F, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, - ]; - spinner_start(); - match morpheus_helix::format::format_helix( - &mut raw_dev, - 0, - info.total_sectors, - info.sector_size, - "root", - uuid, - ) { - Ok(_sb) => { - spinner_done(); - log_ok("STORAGE", 830, "format completed"); - } - Err(e) => { - spinner_done(); - let _ = e; - log_error("STORAGE", 831, "format failed"); - BLOCK_DEVICE = None; - continue; - } - } + STORAGE_LBA_BASE = region.lba_start; + STORAGE_REGION_SECTORS = region.sectors; - // Verify format succeeded - match morpheus_helix::log::recovery::recover_superblock( - &mut raw_dev, - 0, - info.sector_size, - ) { - Ok(_sb) => {} - Err(e) => { - let _ = e; - log_warn("STORAGE", 832, "superblock readback failed after format"); + if region.lba_start != 0 { + log_info("STORAGE", 837, "selected non-zero LBA data region"); + } + + if region.sectors == 0 { + BLOCK_DEVICE = None; + continue; + } + + // This device is NOT a boot disk — use it for HelixFS. + log_ok("STORAGE", 827, "selected data disk"); + found_data_disk = true; + + // try to recover or format helixfs + let mut raw_dev = make_raw_block_device(); + + let needs_format = { + let mut probe_dev = make_raw_block_device(); + match morpheus_helix::log::recovery::recover_superblock( + &mut probe_dev, + 0, + info.sector_size, + ) { + Ok(sb) => { + // Version mismatch: v2 changed the log payload format. + if sb.version != morpheus_helix::types::HELIX_VERSION { + log_warn("STORAGE", 828, "helixfs version mismatch; reformat required"); + true + } else { + false } } - } else { - log_info("STORAGE", 833, "valid helixfs found; mounting"); + Err(_) => true, } + }; - // Now do the actual replace_root_device - // If we already formatted above, pass do_format=false to avoid double-format - let mount_dev = make_raw_block_device(); + if needs_format { + log_info("STORAGE", 829, "no valid helixfs; formatting disk"); + + let uuid = [ + 0x4Du8, 0x58, 0x52, 0x4F, 0x4F, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, + ]; spinner_start(); - match morpheus_helix::vfs::global::replace_root_device(mount_dev, false) { - Ok(()) => { + match morpheus_helix::format::format_helix( + &mut raw_dev, + 0, + STORAGE_REGION_SECTORS, + info.sector_size, + "root", + uuid, + ) { + Ok(_sb) => { spinner_done(); - PERSISTENT_READY = true; - log_ok("STORAGE", 834, "persistent root filesystem mounted at /"); - break 'device_scan; + log_ok("STORAGE", 830, "format completed"); } Err(e) => { spinner_done(); let _ = e; - log_error("STORAGE", 835, "failed to mount persistent filesystem"); + log_error("STORAGE", 831, "format failed"); BLOCK_DEVICE = None; continue; } } + + // Verify format succeeded + match morpheus_helix::log::recovery::recover_superblock(&mut raw_dev, 0, info.sector_size) + { + Ok(_sb) => {} + Err(e) => { + let _ = e; + log_warn("STORAGE", 832, "superblock readback failed after format"); + } + } + } else { + log_info("STORAGE", 833, "valid helixfs found; mounting"); } - if is_ahci { - if ahci_port_attempted && !ahci_port_found { - log_warn("STORAGE", 825, "driver init failed for one candidate; skipping"); + let mount_dev = match stage_selected_region_to_ram(info.sector_size) { + Some(mem_dev) => { + log_ok("STORAGE", 838, "helix partition staged into RAM"); + mem_dev + } + None => { + let reason = RAM_STAGE_LAST_REASON; + if reason == "none" { + log_warn("STORAGE", 838, "RAM staging failed; mounting directly from media"); + } else { + log_warn("STORAGE", 838, reason); + } + make_raw_block_device() + } + }; + + spinner_start(); + match morpheus_helix::vfs::global::replace_root_device(mount_dev, false) { + Ok(()) => { + spinner_done(); + PERSISTENT_READY = true; + log_ok("STORAGE", 834, "persistent root filesystem mounted at /"); + break 'device_scan; + } + Err(e) => { + spinner_done(); + let _ = e; + log_error("STORAGE", 835, "failed to mount persistent filesystem"); + BLOCK_DEVICE = None; + continue; } - continue; } } if !found_data_disk { log_warn("STORAGE", 836, "no suitable data disk; using RAM-disk fallback"); + log_warn( + "STORAGE", + 839, + "runtime persistent backends currently support AHCI/VirtIO/SDHCI (USB/NVMe pending)", + ); } } @@ -706,6 +1062,142 @@ unsafe fn is_boot_disk(sector_size: u32) -> bool { false } +/// Copy selected Helix partition content into RAM and expose it as RawBlockDevice. +unsafe fn stage_selected_region_to_ram(sector_size: u32) -> Option { + RAM_STAGE_LAST_REASON = "none"; + log_info("STORAGE", 843, "RAM stage attempt begin"); + + let mut probe = make_raw_block_device(); + let sb = match morpheus_helix::log::recovery::recover_superblock(&mut probe, 0, sector_size) { + Ok(sb) => sb, + Err(_) => { + RAM_STAGE_LAST_REASON = "RAM stage: superblock probe failed; mounting directly from media"; + return None; + } + }; + + // Stage only live filesystem footprint, not full partition capacity. + let mut stage_blocks = 2u64; + let log_hi = sb.log_end_block.saturating_add(1); + if log_hi > stage_blocks { + stage_blocks = log_hi; + } + + let data_hi = sb.data_start_block.saturating_add(sb.blocks_used); + if data_hi > stage_blocks { + stage_blocks = data_hi; + } + + if stage_blocks > sb.total_blocks { + stage_blocks = sb.total_blocks; + } + + if stage_blocks == 0 { + RAM_STAGE_LAST_REASON = "RAM stage: empty footprint; mounting directly from media"; + return None; + } + + let fs_bytes = match stage_blocks.checked_mul(sb.block_size as u64) { + Some(v) => v, + None => { + RAM_STAGE_LAST_REASON = "RAM stage: byte size overflow; mounting directly from media"; + return None; + } + }; + if fs_bytes == 0 { + return None; + } + + if fs_bytes > RAM_STAGE_MAX_BYTES { + RAM_STAGE_LAST_REASON = "RAM stage: footprint exceeds RAM cap; mounting directly from media"; + return None; + } + + if fs_bytes > usize::MAX as u64 { + RAM_STAGE_LAST_REASON = "RAM stage: size exceeds usize; mounting directly from media"; + return None; + } + + let sector_bytes = sector_size as usize; + let mut copy_bytes = fs_bytes as usize; + let rem = copy_bytes % sector_bytes; + if rem != 0 { + copy_bytes = match copy_bytes.checked_add(sector_bytes - rem) { + Some(v) => v, + None => { + RAM_STAGE_LAST_REASON = "RAM stage: alignment overflow; mounting directly from media"; + return None; + } + }; + } + + let region_bytes = match (STORAGE_REGION_SECTORS as usize).checked_mul(sector_bytes) { + Some(v) => v, + None => { + RAM_STAGE_LAST_REASON = "RAM stage: region size overflow; mounting directly from media"; + return None; + } + }; + if copy_bytes > region_bytes { + RAM_STAGE_LAST_REASON = + "RAM stage: footprint exceeds selected region; mounting directly from media"; + return None; + } + + let stage_pages = copy_bytes.div_ceil(4096); + log_info("STORAGE", 843, "RAM stage: allocating page-backed image"); + let stage_base = { + let mut registry = global_registry_mut(); + match registry.allocate_pages( + AllocateType::AnyPages, + MemoryType::LoaderData, + stage_pages as u64, + ) { + Ok(p) => p, + Err(_) => { + RAM_STAGE_LAST_REASON = + "RAM stage: page allocation failed; mounting directly from media"; + return None; + } + } + }; + + if stage_base == 0 { + RAM_STAGE_LAST_REASON = "RAM stage: zero allocation base; mounting directly from media"; + return None; + } + + let image = core::slice::from_raw_parts_mut(stage_base as *mut u8, copy_bytes); + + const CHUNK_SECTORS: usize = 256; + let chunk_bytes = CHUNK_SECTORS * sector_bytes; + let mut lba = 0u64; + let mut off = 0usize; + + while off < copy_bytes { + let this_chunk = core::cmp::min(chunk_bytes, copy_bytes - off); + if !raw_read( + core::ptr::null_mut(), + lba, + image.as_mut_ptr().add(off), + this_chunk, + ) { + RAM_STAGE_LAST_REASON = "RAM stage: media read failed; mounting directly from media"; + return None; + } + + off += this_chunk; + lba = lba.saturating_add((this_chunk / sector_bytes) as u64); + } + + let base = image.as_mut_ptr(); + let size = image.len(); + + RAM_HELIX_DEVICE = Some(MemBlockDevice::new(base, size, sector_size)); + let mem_dev = RAM_HELIX_DEVICE.as_mut()?; + Some(MemBlockDevice::into_raw(mem_dev)) +} + /// Create the standard initFS directory structure. /// /// Idempotent — silently ignores directories that already exist. diff --git a/bootloader/src/tui/desktop.rs b/bootloader/src/tui/desktop.rs index 557d5d42..0a38c200 100644 --- a/bootloader/src/tui/desktop.rs +++ b/bootloader/src/tui/desktop.rs @@ -95,8 +95,8 @@ pub fn run_desktop(_display_info: &FramebufferInfo) -> ! { log_info("KERNEL", 926, "preparing to launch /bin/init"); let mut keyboard = Keyboard::new(); - let mut mouse = super::mouse::Mouse::new(); show_boot_log_screen(&mut keyboard); + let mut mouse = super::mouse::Mouse::new(); // Load and spawn the userland init supervisor let elf_data = match load_elf_from_fs("init") { @@ -149,7 +149,7 @@ pub fn run_desktop(_display_info: &FramebufferInfo) -> ! { let device = (raw >> 8) & 0xFF; let byte = (raw & 0xFF) as u8; - if device == 0x03 { + if device == 0x03 && !keyboard.aux_as_kbd() { // Mouse byte if let Some(pkt) = mouse.feed(byte) { morpheus_hwinit::mouse::accumulate(pkt.dx, pkt.dy, pkt.buttons); diff --git a/bootloader/src/tui/input.rs b/bootloader/src/tui/input.rs index bcc1b2fe..00cc7e6f 100644 --- a/bootloader/src/tui/input.rs +++ b/bootloader/src/tui/input.rs @@ -301,6 +301,8 @@ pub struct Keyboard { initialized: bool, /// Active keymap layout: KeyLayout, + /// Some controllers mis-tag keyboard bytes as AUX; fallback mode accepts both tags. + aux_as_kbd: bool, } impl Keyboard { @@ -328,6 +330,7 @@ impl Keyboard { extended: false, initialized: false, layout: KeyLayout::Us, + aux_as_kbd: false, }; unsafe { kb.init_controller() }; @@ -354,20 +357,38 @@ impl Keyboard { self.alt } + pub fn aux_as_kbd(&self) -> bool { + self.aux_as_kbd + } + unsafe fn init_controller(&mut self) { - // OVMF already configured the 8042 — don't send 0xAA/0xFF as they clear - // Translation (bit 6), causing set-2 bytes to arrive instead of set-1. - // RMW the config byte: keep translation on, disable IRQs (we poll). + // i8042 bring-up on real hardware is messy; probe and normalize hard. + morpheus_hwinit::serial::log_info("INPUT", 935, "keyboard controller init begin"); asm_ps2_flush(); + // Disable port 1 while we reconfigure. asm_ps2_write_cmd(0xAD); // disable port 1 scanning Self::io_delay(); asm_ps2_flush(); + // Controller self-test (0xAA -> 0x55 expected). + asm_ps2_write_cmd(0xAA); + let ctl_self = self.wait_response_tagged(100_000).map(|(_, b)| b); + if ctl_self != Some(0x55) { + morpheus_hwinit::serial::log_warn("INPUT", 936, "8042 self-test unexpected response"); + } + + // Interface test for first PS/2 port (0xAB -> 0x00 expected). + asm_ps2_write_cmd(0xAB); + let port1_test = self.wait_response_tagged(100_000).map(|(_, b)| b); + if port1_test != Some(0x00) { + morpheus_hwinit::serial::log_warn("INPUT", 937, "8042 port1 test unexpected response"); + } + asm_ps2_write_cmd(0x20); // read config byte - let config = Self::wait_response(50_000).unwrap_or(0x45); - let new_config = (config | 0x40) & !0x03; // set translation, clear IRQs + let config = self.wait_response_tagged(50_000).map(|(_, b)| b).unwrap_or(0x45); + let new_config = (config | 0x41) & !0x30; // translation on, IRQ1 on, both PS/2 clocks enabled asm_ps2_write_cmd(0x60); // write config byte asm_ps2_write_data(new_config); @@ -375,9 +396,62 @@ impl Keyboard { asm_ps2_write_cmd(0xAE); // re-enable port 1 Self::io_delay(); + asm_ps2_write_cmd(0xA8); // keep aux port enabled too (some firmware paths tag keyboard via AUX) + Self::io_delay(); + + // Reset keyboard and wait for BAT completion. + asm_ps2_write_data(0xFF); + let ack_ff = self.wait_response_tagged(100_000); + let bat = self.wait_response_tagged(200_000); + if matches!(ack_ff, Some((0x300, _))) || matches!(bat, Some((0x300, _))) { + self.aux_as_kbd = true; + morpheus_hwinit::serial::log_warn("INPUT", 939, "keyboard bytes tagged as AUX; enabling fallback"); + } + let ack_ff_b = ack_ff.map(|(_, b)| b); + let bat_b = bat.map(|(_, b)| b); + if ack_ff_b != Some(0xFA) || bat_b != Some(0xAA) { + self.aux_as_kbd = true; + morpheus_hwinit::serial::log_warn( + "INPUT", + 939, + "keyboard reset/BAT failed; forcing AUX-tag keyboard fallback", + ); + morpheus_hwinit::serial::log_warn("INPUT", 938, "keyboard reset/BAT incomplete"); + } + + // Force keyboard scan set 1 so decoder matches real hardware output. + asm_ps2_write_data(0xF5); // disable scanning + let _ = self.wait_response_tagged(100_000); // ACK best-effort + asm_ps2_write_data(0xF6); // set defaults + let _ = self.wait_response_tagged(100_000); + asm_ps2_write_data(0xF0); // set/get scancode set command + let ack_f0 = self.wait_response_tagged(100_000); + asm_ps2_write_data(0x01); // set 1 + let ack_set1 = self.wait_response_tagged(100_000); asm_ps2_write_data(0xF4); // enable scanning - let _ = Self::wait_response(50_000); + let ack_f4 = self.wait_response_tagged(100_000); + + if matches!(ack_f0, Some((0x300, _))) + || matches!(ack_set1, Some((0x300, _))) + || matches!(ack_f4, Some((0x300, _))) + { + self.aux_as_kbd = true; + morpheus_hwinit::serial::log_warn("INPUT", 939, "keyboard bytes tagged as AUX; enabling fallback"); + } + + if ack_f0.map(|(_, b)| b) != Some(0xFA) + || ack_set1.map(|(_, b)| b) != Some(0xFA) + || ack_f4.map(|(_, b)| b) != Some(0xFA) + { + self.aux_as_kbd = true; + morpheus_hwinit::serial::log_warn( + "INPUT", + 939, + "set-1 handshake failed; forcing AUX-tag keyboard fallback", + ); + morpheus_hwinit::serial::log_warn("INPUT", 931, "keyboard set-1 handshake incomplete"); + } asm_ps2_flush(); @@ -386,11 +460,12 @@ impl Keyboard { } /// Spin-wait for a response byte from port 0x60, with bounded timeout. - unsafe fn wait_response(max_spins: u32) -> Option { + unsafe fn wait_response_tagged(&mut self, max_spins: u32) -> Option<(u16, u8)> { for _ in 0..max_spins { - let r = asm_ps2_poll(); - if r & 0x100 != 0 { - return Some((r & 0xFF) as u8); + let r = asm_ps2_poll_any(); + let tag = (r & 0x300) as u16; + if tag == 0x100 || tag == 0x300 { + return Some((tag, (r & 0xFF) as u8)); } core::hint::spin_loop(); } @@ -414,8 +489,9 @@ impl Keyboard { /// key-press event was decoded, `None` if the buffer was empty or /// only a modifier/break code was processed. pub fn read_key(&mut self) -> Option { - let raw = unsafe { asm_ps2_poll() }; - if raw & 0x100 == 0 { + let raw = unsafe { asm_ps2_poll_any() }; + let tag = raw & 0x300; + if tag != 0x100 && !(self.aux_as_kbd && tag == 0x300) { return None; } let byte = (raw & 0xFF) as u8; @@ -435,14 +511,28 @@ impl Keyboard { /// near-zero power draw while waiting and immediate wakeup on keypress. pub fn wait_for_key(&mut self) -> InputKey { loop { - if let Some(key) = self.read_key() { - return key; + let raw = unsafe { asm_ps2_poll_any() }; + let tag = raw & 0x300; + if tag == 0x100 || (self.aux_as_kbd && tag == 0x300) { + let byte = (raw & 0xFF) as u8; + + if let Some(key) = self.decode(byte) { + return key; + } + + // Boot gate should continue on any non-break keyboard make byte, + // even if it's a modifier or currently unmapped key. + if byte != EXTENDED_PREFIX && (byte & BREAK_FLAG) == 0 { + return InputKey { + scan_code: SCAN_NULL, + unicode_char: 0, + }; + } } - // Halt the CPU until the next interrupt (IRQ1 = PS/2 keyboard). - // Eliminates busy-waiting with no latency cost on actual keypresses. - // SAFETY: sti/hlt/cli is safe in bare-metal bootloader context. - unsafe { - core::arch::asm!("sti", "hlt", "cli", options(nostack, nomem)); + + // Do not rely on interrupt wakeups at this stage; poll with short backoff. + for _ in 0..4096 { + core::hint::spin_loop(); } } } diff --git a/bootloader/src/tui/mouse.rs b/bootloader/src/tui/mouse.rs index 5dd2d829..635f5056 100644 --- a/bootloader/src/tui/mouse.rs +++ b/bootloader/src/tui/mouse.rs @@ -53,8 +53,9 @@ impl Mouse { // RMW config: enable aux IRQ (bit 1), clear aux clock-disable (bit 5) asm_ps2_write_cmd(0x20); - let config = wait_data(50_000); - let new_config = (config | 0x02) & !0x20; + let config = wait_kbd_data(50_000).unwrap_or(0x45); + // Keep translation on and keyboard IRQ enabled; just bring mouse path up. + let new_config = (config | 0x43) & !0x30; asm_ps2_write_cmd(0x60); asm_ps2_write_data(new_config); io_delay(); @@ -195,6 +196,18 @@ unsafe fn wait_data(max_spins: u32) -> u8 { 0 } +/// Spin-read one keyboard-tagged byte (0x1xx) from port 0x60. +unsafe fn wait_kbd_data(max_spins: u32) -> Option { + for _ in 0..max_spins { + let r = asm_ps2_poll_any(); + if (r & 0x300) == 0x100 { + return Some((r & 0xFF) as u8); + } + core::hint::spin_loop(); + } + None +} + /// Spin-read one aux-port byte (mouse channel, tagged as 0x3xx). unsafe fn wait_aux_data(max_spins: u32) -> u8 { for _ in 0..max_spins { diff --git a/hwinit/src/cpu/ap_boot.rs b/hwinit/src/cpu/ap_boot.rs index b3f8aa2c..935095c2 100644 --- a/hwinit/src/cpu/ap_boot.rs +++ b/hwinit/src/cpu/ap_boot.rs @@ -16,7 +16,6 @@ use crate::cpu::per_cpu::{self, MAX_CPUS}; use crate::memory::{global_registry_mut, AllocateType, MemoryType, PAGE_SIZE}; use crate::serial::{log_error, log_info, log_ok, log_warn}; use core::sync::atomic::Ordering; - /// Physical address where the AP trampoline is copied. /// Must be page-aligned, below 1 MiB, and not in use by anything else. /// 0x8000 is the traditional choice (page 8). @@ -34,18 +33,7 @@ const AP_WAIT_STEP_US: u64 = 50; const AP_WAIT_TIMEOUT_US: u64 = 20_000; const AP_BRUTE_SCAN_BUDGET_US: u64 = 3_000_000; -#[inline(always)] -fn ap_dbg_probe(tag: &str, lapic_id: u32) { - crate::serial::checkpoint(tag); - crate::serial::puts("[APDBG] "); - crate::serial::puts(tag); - crate::serial::puts(" lapic="); - crate::serial::put_hex32(lapic_id); - crate::serial::puts("\r\n"); -} - -// ── Trampoline data area offsets ───────────────────────────────────────── -// These must match the .data section layout at the end of ap_trampoline.s. +// These must match the .data section layout at the end of ap_trampoline.s.lapic_id // The trampoline data block starts at AP_TRAMPOLINE_PHYS + TRAMPOLINE_DATA_OFFSET. const TRAMPOLINE_DATA_OFFSET: u64 = 0xF00; // within the 4K page const TD_CR3: u64 = TRAMPOLINE_DATA_OFFSET + 0x00; @@ -75,7 +63,6 @@ static AP_TRAMPOLINE_BIN: &[u8] = &[]; /// /// Returns false if the trampoline page is unavailable (reserved by firmware). unsafe fn setup_trampoline() -> bool { - crate::serial::checkpoint("ap-tramp-setup-begin"); // validate the trampoline page is usable before stomping it. // on exotic firmware 0x8000 might be reserved/MMIO. match global_registry_mut().allocate_pages( @@ -86,7 +73,6 @@ unsafe fn setup_trampoline() -> bool { Ok(_) => {} Err(_) => { log_error("AP", 500, "trampoline page 0x8000 unavailable in memory map"); - crate::serial::checkpoint("ap-tramp-setup-no-page"); return false; } } @@ -113,8 +99,6 @@ unsafe fn setup_trampoline() -> bool { *((AP_TRAMPOLINE_PHYS + TD_CR3) as *mut u64) = kernel_cr3; *((AP_TRAMPOLINE_PHYS + TD_ENTRY64) as *mut u64) = ap_rust_entry as u64; - crate::serial::checkpoint("ap-tramp-setup-done"); - true } @@ -123,8 +107,6 @@ unsafe fn setup_trampoline() -> bool { /// Returns true if the AP responded within the timeout. /// On failure, frees the allocated stack — no leak. unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { - ap_dbg_probe("ap-probe-begin", lapic_id); - // allocate a kernel stack for this AP let stack_pages = AP_STACK_SIZE / PAGE_SIZE; let stack_base = match global_registry_mut().allocate_pages( @@ -135,12 +117,10 @@ unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { Ok(base) => base, Err(_) => { log_error("AP", 501, "stack allocation failed"); - crate::serial::checkpoint("ap-probe-stack-alloc-fail"); return false; } }; let stack_top = stack_base + AP_STACK_SIZE; - ap_dbg_probe("ap-probe-stack-alloc-ok", lapic_id); // fill per-AP trampoline data *((AP_TRAMPOLINE_PHYS + TD_STACK) as *mut u64) = stack_top; @@ -152,25 +132,18 @@ unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { let before = per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst); // INIT IPI - ap_dbg_probe("ap-probe-init-send", lapic_id); apic::send_init_ipi(lapic_id); - ap_dbg_probe("ap-probe-init-sent", lapic_id); apic::delay_us(10_000); // 10ms // SIPI #1 - ap_dbg_probe("ap-probe-sipi1-send", lapic_id); apic::send_sipi(lapic_id, AP_TRAMPOLINE_PAGE); - ap_dbg_probe("ap-probe-sipi1-sent", lapic_id); - apic::delay_us(200); // 200µs + apic::delay_us(10_000); // 10ms // SIPI #2 (per Intel spec, send twice for reliability) - ap_dbg_probe("ap-probe-sipi2-send", lapic_id); apic::send_sipi(lapic_id, AP_TRAMPOLINE_PAGE); - ap_dbg_probe("ap-probe-sipi2-sent", lapic_id); - apic::delay_us(200); + apic::delay_us(10_000); // 10ms // wait for AP to come online (bounded; fallback path must stay snappy) - ap_dbg_probe("ap-probe-online-wait", lapic_id); let mut waited_us = 0u64; while per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst) <= before { apic::delay_us(AP_WAIT_STEP_US); @@ -181,11 +154,8 @@ unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { } if per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst) > before { - ap_dbg_probe("ap-probe-online-ok", lapic_id); true } else { - log_warn("AP", 502, "ap did not respond to INIT+SIPI; freeing stack"); - ap_dbg_probe("ap-probe-online-timeout", lapic_id); // don't leak 64KB per ghost core let _ = global_registry_mut().free_pages(stack_base, stack_pages); false @@ -251,7 +221,6 @@ pub unsafe fn start_aps() { let bsp_lapic_id = apic::read_lapic_id(); log_warn("AP", 508, "starting AP bring-up via brute-force LAPIC scan"); - crate::serial::checkpoint("ap-scan-begin"); if !setup_trampoline() { return; @@ -274,13 +243,11 @@ pub unsafe fn start_aps() { if scan_budget_used_us >= AP_BRUTE_SCAN_BUDGET_US { log_warn("AP", 510, "brute-force AP scan budget exhausted; continuing with discovered cores"); - crate::serial::checkpoint("ap-scan-budget-exhausted"); break; } if (lapic_id & 0x1F) == 0 { log_info("AP", 511, "AP scan progress checkpoint"); - ap_dbg_probe("ap-scan-progress", lapic_id); } if boot_single_ap(core_idx, lapic_id) { @@ -293,7 +260,6 @@ pub unsafe fn start_aps() { scan_budget_used_us += 10_400 + AP_WAIT_TIMEOUT_US; } - crate::serial::checkpoint("ap-scan-done"); log_ok("AP", 509, "brute-force AP bring-up pass complete"); } diff --git a/hwinit/src/cpu/apic.rs b/hwinit/src/cpu/apic.rs index 235868a0..d03dd47e 100644 --- a/hwinit/src/cpu/apic.rs +++ b/hwinit/src/cpu/apic.rs @@ -9,13 +9,10 @@ use crate::cpu::per_cpu; use crate::cpu::pio::outb; use crate::serial::{log_info, log_ok, log_warn}; +use core::sync::atomic::{AtomicBool, Ordering}; + + -#[inline(always)] -fn apic_dbg(msg: &str) { - crate::serial::puts("[APICDBG] "); - crate::serial::puts(msg); - crate::serial::puts("\r\n"); -} // ── LAPIC register offsets ─────────────────────────────────────────────── @@ -41,7 +38,7 @@ const TIMER_MASKED: u32 = 1 << 16; // ICR delivery modes const ICR_INIT: u32 = 0x500; -const ICR_STARTUP: u32 = 0x600; +const ICR_STARTUP: u32 = 0x8000; const ICR_LEVEL_ASSERT: u32 = 0x4000; const ICR_LEVEL_DEASSERT: u32 = 0x0000; const ICR_TRIGGER_LEVEL: u32 = 1 << 15; @@ -55,9 +52,55 @@ pub const TIMER_VECTOR: u8 = 0x20; /// IA32_APIC_BASE MSR — contains the actual LAPIC physical base address. const IA32_APIC_BASE_MSR: u32 = 0x1B; +const IA32_X2APIC_ICR_MSR: u32 = 0x830; +const IA32_X2APIC_ID_MSR: u32 = 0x802; /// Actual LAPIC base, probed from MSR 0x1B. written once during BSP init. static mut LAPIC_BASE_ACTUAL: u64 = DEFAULT_LAPIC_BASE; +static X2APIC_ENABLED: AtomicBool = AtomicBool::new(false); + +#[inline(always)] +unsafe fn rdmsr(msr: u32) -> u64 { + let lo: u32; + let hi: u32; + core::arch::asm!( + "rdmsr", + in("ecx") msr, + out("eax") lo, + out("edx") hi, + options(nostack, nomem), + ); + ((hi as u64) << 32) | lo as u64 +} + +#[inline(always)] +unsafe fn wrmsr(msr: u32, val: u64) { + let lo = val as u32; + let hi = (val >> 32) as u32; + core::arch::asm!( + "wrmsr", + in("ecx") msr, + in("eax") lo, + in("edx") hi, + options(nostack, nomem), + ); +} + +#[inline(always)] +unsafe fn wrmsr_parts(msr: u32, lo: u32, hi: u32) { + core::arch::asm!( + "wrmsr", + in("ecx") msr, + in("eax") lo, + in("edx") hi, + options(nostack, nomem), + ); +} + +#[inline(always)] +unsafe fn x2apic_enabled() -> bool { + X2APIC_ENABLED.load(Ordering::Relaxed) +} /// Probe the LAPIC base address from IA32_APIC_BASE MSR. /// Stores the result for all subsequent LAPIC access. @@ -76,6 +119,8 @@ pub unsafe fn probe_lapic_base() -> u64 { options(nostack, nomem), ); let raw = (hi as u64) << 32 | lo as u64; + let x2 = (raw & (1 << 10)) != 0; + X2APIC_ENABLED.store(x2, Ordering::Relaxed); // bits [12:N] = base physical address, bits [0:11] = flags let base = raw & 0xFFFF_FFFF_FFFF_F000; LAPIC_BASE_ACTUAL = base; @@ -112,7 +157,11 @@ unsafe fn lapic_write(base: u64, reg: u32, val: u32) { /// Read the current CPU's LAPIC ID from hardware. pub unsafe fn read_lapic_id() -> u32 { - lapic_read(lapic_base(), LAPIC_ID) >> 24 + if x2apic_enabled() { + rdmsr(IA32_X2APIC_ID_MSR) as u32 + } else { + lapic_read(lapic_base(), LAPIC_ID) >> 24 + } } /// Check if APIC is available via CPUID. @@ -272,12 +321,23 @@ pub unsafe fn disable_pic8259() { /// Send an INIT IPI to target APIC ID. pub unsafe fn send_init_ipi(target_apic_id: u32) { let base = lapic_base(); - apic_dbg("init-ipi-begin"); + + if x2apic_enabled() { + // x2APIC uses MSR 0x830 for ICR writes; MMIO ICR can wedge on some hw. + let init_assert = (target_apic_id as u64) << 32 | (ICR_INIT | ICR_LEVEL_ASSERT) as u64; + wrmsr(IA32_X2APIC_ICR_MSR, init_assert); + wait_icr_idle(base); + + let init_deassert = (target_apic_id as u64) << 32 + | (ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT) as u64; + wrmsr(IA32_X2APIC_ICR_MSR, init_deassert); + wait_icr_idle(base); + return; + } // INIT assert lapic_write(base, LAPIC_ICR_HI, target_apic_id << 24); lapic_write(base, LAPIC_ICR_LO, ICR_INIT | ICR_LEVEL_ASSERT); - apic_dbg("init-ipi-assert"); wait_icr_idle(base); // INIT deassert — trigger mode MUST be level (bit 15) with level=0. @@ -288,9 +348,7 @@ pub unsafe fn send_init_ipi(target_apic_id: u32) { LAPIC_ICR_LO, ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT, ); - apic_dbg("init-ipi-deassert"); wait_icr_idle(base); - apic_dbg("init-ipi-done"); } /// Send a Startup IPI (SIPI) to target APIC ID. @@ -299,12 +357,16 @@ pub unsafe fn send_init_ipi(target_apic_id: u32) { /// Must be below 1 MiB and page-aligned (e.g., 0x8000 → start_page = 8). pub unsafe fn send_sipi(target_apic_id: u32, start_page: u8) { let base = lapic_base(); - apic_dbg("sipi-begin"); + if x2apic_enabled() { + let icr_lo = ICR_STARTUP | start_page as u32; + let icr_hi = target_apic_id; + wrmsr_parts(IA32_X2APIC_ICR_MSR, icr_lo, icr_hi); + wait_icr_idle(base); + return; + } lapic_write(base, LAPIC_ICR_HI, target_apic_id << 24); lapic_write(base, LAPIC_ICR_LO, ICR_STARTUP | start_page as u32); - apic_dbg("sipi-write"); wait_icr_idle(base); - apic_dbg("sipi-done"); } /// Wait for the ICR delivery status bit to clear. @@ -313,16 +375,20 @@ unsafe fn wait_icr_idle(base: u64) { // Keep this tightly bounded. Some hardware can leave delivery status set // long enough to look like a deadlock if we spin too generously. let mut timeout = 2_000u32; - while lapic_read(base, LAPIC_ICR_LO) & ICR_DELIVERY_STATUS != 0 { + while { + if x2apic_enabled() { + (rdmsr(IA32_X2APIC_ICR_MSR) as u32 & ICR_DELIVERY_STATUS) != 0 + } else { + (lapic_read(base, LAPIC_ICR_LO) & ICR_DELIVERY_STATUS) != 0 + } + } { core::hint::spin_loop(); timeout -= 1; if timeout == 0 { log_warn("LAPIC", 726, "ICR delivery timeout"); - apic_dbg("icr-wait-timeout"); break; } } - apic_dbg("icr-wait-done"); } // ── Delay helper ───────────────────────────────────────────────────────── diff --git a/hwinit/src/platform.rs b/hwinit/src/platform.rs index 9f329f91..048bb260 100644 --- a/hwinit/src/platform.rs +++ b/hwinit/src/platform.rs @@ -474,7 +474,7 @@ pub unsafe fn platform_init_selfcontained( core::ptr::write_bytes(root_fs_base as *mut u8, 0, ROOT_FS_SIZE); checkpoint("phase11-fs-mount"); match morpheus_helix::vfs::global::init_root_fs(root_fs_base as *mut u8, ROOT_FS_SIZE) { - Ok(()) => log_ok("FS", 112, "helixfs mounted at /"), + Ok(()) => log_ok("FS", 112, "bootstrap RAM helixfs mounted at /"), Err(_) => { log_warn("FS", 412, "root fs init failed; continuing without fs"); // Non-fatal — system continues without FS. diff --git a/network/asm/drivers/sdhci/init.s b/network/asm/drivers/sdhci/init.s new file mode 100644 index 00000000..911907f9 --- /dev/null +++ b/network/asm/drivers/sdhci/init.s @@ -0,0 +1,290 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; SDHCI initialization primitives +; ABI: Microsoft x64 (RCX, RDX, R8, R9, stack) +; ═══════════════════════════════════════════════════════════════════════════ + +section .data + %include "asm/drivers/sdhci/regs.s" + +section .text + +extern asm_mmio_read32 +extern asm_mmio_write32 +extern asm_mmio_write16 +extern asm_mmio_read8 +extern asm_mmio_write8 +extern asm_tsc_read +extern asm_bar_mfence + +global asm_sdhci_read_caps +global asm_sdhci_card_present +global asm_sdhci_controller_reset +global asm_sdhci_basic_power_clock +global asm_sdhci_read_block_pio + +; RCX = mmio_base +; EAX = capabilities +asm_sdhci_read_caps: + sub rsp, 32 + lea rcx, [rcx + SDHCI_REG_CAPABILITIES] + call asm_mmio_read32 + add rsp, 32 + ret + +; RCX = mmio_base +; EAX = 1 if card inserted, else 0 +asm_sdhci_card_present: + sub rsp, 32 + lea rcx, [rcx + SDHCI_REG_PRESENT_STATE] + call asm_mmio_read32 + and eax, SDHCI_PRESENT_CARD_INSERTED + xor edx, edx + test eax, eax + setnz dl + mov eax, edx + add rsp, 32 + ret + +; RCX = mmio_base, RDX = tsc_freq +; EAX = 0 success, 1 timeout +asm_sdhci_controller_reset: + push rbx + push r12 + push r13 + sub rsp, 32 + + mov r12, rcx ; base + mov r13, rdx ; tsc_freq + + ; Write software reset all. + lea rcx, [r12 + SDHCI_REG_SOFTWARE_RESET] + mov dl, SDHCI_RESET_ALL + call asm_mmio_write8 + call asm_bar_mfence + + ; Timeout ~100ms. + call asm_tsc_read + mov rbx, rax + mov rax, r13 + xor rdx, rdx + mov rcx, 10 + div rcx ; tsc_freq / 10 + mov r13, rax + +.reset_wait: + lea rcx, [r12 + SDHCI_REG_SOFTWARE_RESET] + call asm_mmio_read8 + test al, SDHCI_RESET_ALL + jz .ok + + call asm_tsc_read + sub rax, rbx + cmp rax, r13 + jb .reset_wait + + mov eax, 1 + jmp .out + +.ok: + xor eax, eax + +.out: + add rsp, 32 + pop r13 + pop r12 + pop rbx + ret + +; RCX = mmio_base +; EAX = 0 on success (minimal setup) +asm_sdhci_basic_power_clock: + sub rsp, 32 + + ; Best-effort minimal power enable for bring-up path. + lea rcx, [rcx + SDHCI_REG_POWER_CTRL] + mov dl, 0x0F ; bus power + nominal voltage bits + call asm_mmio_write8 + + ; Enable internal clock + SD clock (controller-specific, minimal). + lea rcx, [rcx + SDHCI_REG_CLOCK_CTRL] + mov dx, 0x0005 + call asm_mmio_write16 + + xor eax, eax + add rsp, 32 + ret + +; RCX = mmio_base, RDX = lba, R8 = dst_ptr, R9 = tsc_freq +; EAX = 0 success +; 1 timeout waiting inhibit clear +; 2 command phase error/timeout +; 3 buffer-read phase error/timeout +; 4 transfer-complete phase error/timeout +asm_sdhci_read_block_pio: + push rbx + push r12 + push r13 + push r14 + push r15 + sub rsp, 32 + + mov r12, rcx ; base + mov r13, rdx ; lba + mov r14, r8 ; dst + + ; 100ms timeout ticks + mov rax, r9 + xor rdx, rdx + mov rcx, 10 + div rcx + mov r15, rax + + ; wait until command/data lines are idle + call asm_tsc_read + mov rbx, rax +.wait_idle: + lea rcx, [r12 + SDHCI_REG_PRESENT_STATE] + call asm_mmio_read32 + mov edx, SDHCI_PRESENT_CMD_INHIBIT | SDHCI_PRESENT_DAT_INHIBIT + test eax, edx + jz .idle_ok + call asm_tsc_read + sub rax, rbx + cmp rax, r15 + jb .wait_idle + mov eax, 1 + jmp .out + +.idle_ok: + ; clear stale interrupts + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, 0xFFFF_FFFF + call asm_mmio_write32 + + ; block size = 512, block count = 1 + lea rcx, [r12 + SDHCI_REG_BLOCK_SIZE] + mov dx, 512 + call asm_mmio_write16 + + lea rcx, [r12 + SDHCI_REG_BLOCK_COUNT] + mov dx, 1 + call asm_mmio_write16 + + ; timeout control: max + lea rcx, [r12 + SDHCI_REG_TIMEOUT_CTRL] + mov dl, 0x0E + call asm_mmio_write8 + + ; argument = LBA (SDHC/SDXC block addressing) + lea rcx, [r12 + SDHCI_REG_ARGUMENT] + mov edx, r13d + call asm_mmio_write32 + + ; transfer mode: read + block count enable + lea rcx, [r12 + SDHCI_REG_TRANSFER_MODE] + mov dx, SDHCI_TRNS_READ | SDHCI_TRNS_BLK_CNT_EN + call asm_mmio_write16 + + ; CMD17 READ_SINGLE_BLOCK, short resp + crc + index + data + lea rcx, [r12 + SDHCI_REG_COMMAND] + mov dx, (17 << 8) | SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_INDEX | SDHCI_CMD_DATA + call asm_mmio_write16 + + ; wait command complete + call asm_tsc_read + mov rbx, rax +.wait_cmd: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + call asm_mmio_read32 + test eax, SDHCI_INT_ERROR + jnz .cmd_err + test eax, SDHCI_INT_CMD_COMPLETE + jnz .cmd_ok + call asm_tsc_read + sub rax, rbx + cmp rax, r15 + jb .wait_cmd +.cmd_err: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, 0xFFFF_FFFF + call asm_mmio_write32 + mov eax, 2 + jmp .out + +.cmd_ok: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, SDHCI_INT_CMD_COMPLETE + call asm_mmio_write32 + + ; wait buffer read ready + call asm_tsc_read + mov rbx, rax +.wait_buf: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + call asm_mmio_read32 + test eax, SDHCI_INT_ERROR + jnz .buf_err + test eax, SDHCI_INT_BUF_READ_READY + jnz .buf_ok + call asm_tsc_read + sub rax, rbx + cmp rax, r15 + jb .wait_buf +.buf_err: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, 0xFFFF_FFFF + call asm_mmio_write32 + mov eax, 3 + jmp .out + +.buf_ok: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, SDHCI_INT_BUF_READ_READY + call asm_mmio_write32 + + ; read 512 bytes (128 dwords) + xor r11d, r11d +.copy_loop: + lea rcx, [r12 + SDHCI_REG_BUFFER_DATA] + call asm_mmio_read32 + mov [r14 + r11*4], eax + inc r11d + cmp r11d, 128 + jb .copy_loop + + ; wait transfer complete + call asm_tsc_read + mov rbx, rax +.wait_xfer: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + call asm_mmio_read32 + test eax, SDHCI_INT_ERROR + jnz .xfer_err + test eax, SDHCI_INT_XFER_COMPLETE + jnz .xfer_ok + call asm_tsc_read + sub rax, rbx + cmp rax, r15 + jb .wait_xfer +.xfer_err: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, 0xFFFF_FFFF + call asm_mmio_write32 + mov eax, 4 + jmp .out + +.xfer_ok: + lea rcx, [r12 + SDHCI_REG_INT_STATUS] + mov edx, SDHCI_INT_XFER_COMPLETE + call asm_mmio_write32 + + xor eax, eax + +.out: + add rsp, 32 + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + ret diff --git a/network/asm/drivers/sdhci/regs.s b/network/asm/drivers/sdhci/regs.s new file mode 100644 index 00000000..44f55240 --- /dev/null +++ b/network/asm/drivers/sdhci/regs.s @@ -0,0 +1,39 @@ +; SDHCI register definitions (SD Host Controller Spec) + +%ifndef SDHCI_REGS_INCLUDED +%define SDHCI_REGS_INCLUDED + +SDHCI_REG_CAPABILITIES equ 0x40 +SDHCI_REG_BLOCK_SIZE equ 0x04 +SDHCI_REG_BLOCK_COUNT equ 0x06 +SDHCI_REG_ARGUMENT equ 0x08 +SDHCI_REG_TRANSFER_MODE equ 0x0C +SDHCI_REG_COMMAND equ 0x0E +SDHCI_REG_BUFFER_DATA equ 0x20 +SDHCI_REG_PRESENT_STATE equ 0x24 +SDHCI_REG_POWER_CTRL equ 0x29 +SDHCI_REG_CLOCK_CTRL equ 0x2C +SDHCI_REG_TIMEOUT_CTRL equ 0x2E +SDHCI_REG_SOFTWARE_RESET equ 0x2F +SDHCI_REG_INT_STATUS equ 0x30 + +SDHCI_PRESENT_CARD_INSERTED equ (1 << 16) +SDHCI_PRESENT_CMD_INHIBIT equ (1 << 0) +SDHCI_PRESENT_DAT_INHIBIT equ (1 << 1) + +SDHCI_RESET_ALL equ 0x01 + +SDHCI_INT_CMD_COMPLETE equ (1 << 0) +SDHCI_INT_XFER_COMPLETE equ (1 << 1) +SDHCI_INT_BUF_READ_READY equ (1 << 5) +SDHCI_INT_ERROR equ (1 << 15) + +SDHCI_TRNS_BLK_CNT_EN equ (1 << 1) +SDHCI_TRNS_READ equ (1 << 4) + +SDHCI_CMD_RESP_SHORT equ 0x02 +SDHCI_CMD_CRC equ 0x08 +SDHCI_CMD_INDEX equ 0x10 +SDHCI_CMD_DATA equ 0x20 + +%endif diff --git a/network/asm/drivers/usb/init.s b/network/asm/drivers/usb/init.s new file mode 100644 index 00000000..475c0a7f --- /dev/null +++ b/network/asm/drivers/usb/init.s @@ -0,0 +1,35 @@ +; ═══════════════════════════════════════════════════════════════════════════ +; USB host controller probe/reset primitives (scaffold) +; ABI: Microsoft x64 (RCX, RDX, R8, R9, stack) +; ═══════════════════════════════════════════════════════════════════════════ + +section .text + +extern asm_mmio_read8 +extern asm_tsc_read + +global asm_usb_host_probe +global asm_usb_host_reset + +; RCX = mmio_base +; EAX = 0 success, 1 invalid controller signature +asm_usb_host_probe: + sub rsp, 32 + ; Read capability length register (common on xHCI-compatible mmio layout). + call asm_mmio_read8 + test al, al + jz .bad + xor eax, eax + jmp .out +.bad: + mov eax, 1 +.out: + add rsp, 32 + ret + +; RCX = mmio_base, RDX = tsc_freq +; EAX = 0 success (stub reset sequence placeholder) +asm_usb_host_reset: + ; Reserved for full xHCI reset sequence in next phase. + xor eax, eax + ret diff --git a/network/build.rs b/network/build.rs index ae88fd1b..7660e13a 100644 --- a/network/build.rs +++ b/network/build.rs @@ -52,6 +52,10 @@ const ASM_AHCI: &[&str] = &[ "asm/drivers/ahci/io.s", ]; +const ASM_SDHCI: &[&str] = &["asm/drivers/sdhci/init.s"]; + +const ASM_USB: &[&str] = &["asm/drivers/usb/init.s"]; + const ASM_PHY: &[&str] = &["asm/phy/mdio.s", "asm/phy/mii.s", "asm/phy/link.s"]; fn main() { @@ -114,6 +118,8 @@ fn main() { ("virtio", ASM_VIRTIO), ("intel", ASM_INTEL), ("ahci", ASM_AHCI), + ("sdhci", ASM_SDHCI), + ("usb", ASM_USB), ("phy", ASM_PHY), ]; diff --git a/network/src/boot/block_probe.rs b/network/src/boot/block_probe.rs index f65c3db2..a0cfc0bd 100644 --- a/network/src/boot/block_probe.rs +++ b/network/src/boot/block_probe.rs @@ -20,9 +20,9 @@ //! ``` use crate::device::{UnifiedBlockDevice, UnifiedBlockError}; -use crate::driver::ahci::{ - AhciConfig, AhciDriver, AhciInitError, AHCI_DEVICE_IDS, INTEL_VENDOR_ID, -}; +use crate::driver::ahci::{AhciConfig, AhciDriver, AhciInitError, INTEL_VENDOR_ID}; +use crate::driver::sdhci::{SdhciConfig, SdhciDriver, SdhciInitError}; +use crate::driver::usb_msd::{UsbMsdConfig, UsbMsdDriver, UsbMsdInitError}; use crate::driver::virtio::transport::{PciModernConfig, VirtioTransport}; use crate::driver::virtio_blk::{VirtioBlkConfig, VirtioBlkDriver, VirtioBlkInitError}; use crate::pci::capability::probe_virtio_caps; @@ -74,6 +74,10 @@ const VIRTIO_BLK_DEVICE_MODERN: u16 = 0x1042; /// We compare against `(class_code >> 8) & 0xFFFF`, which yields /// subclass:prog_if (not class:subclass). const PCI_CLASS_SATA_AHCI: u32 = 0x0601; +/// PCI subclass/prog-if for SD Host Controller (SDHCI): 0x05/0x01. +const PCI_CLASS_SDHCI: u32 = 0x0501; +/// PCI subclass/prog-if for USB xHCI: 0x03/0x30. +const PCI_CLASS_USB_XHCI: u32 = 0x0330; // ═══════════════════════════════════════════════════════════════════════════ // PROBE ERRORS @@ -88,6 +92,10 @@ pub enum BlockProbeError { VirtioInitFailed, /// AHCI initialization failed AhciInitFailed, + /// SDHCI initialization failed + SdhciInitFailed, + /// USB mass-storage initialization failed + UsbMsdInitFailed, /// BAR mapping failed BarMappingFailed, /// Device not responding @@ -106,6 +114,18 @@ impl From for BlockProbeError { } } +impl From for BlockProbeError { + fn from(_: SdhciInitError) -> Self { + BlockProbeError::SdhciInitFailed + } +} + +impl From for BlockProbeError { + fn from(_: UsbMsdInitError) -> Self { + BlockProbeError::UsbMsdInitFailed + } +} + // ═══════════════════════════════════════════════════════════════════════════ // DETECTED DEVICE INFO // ═══════════════════════════════════════════════════════════════════════════ @@ -117,6 +137,10 @@ pub enum DetectedBlockDevice { VirtIO { pci_addr: PciAddr, mmio_base: u64 }, /// AHCI SATA controller Ahci(AhciInfo), + /// SDHCI controller + Sdhci(SdhciInfo), + /// USB xHCI controller candidate for USB MSD path + UsbMsd(UsbMsdInfo), } /// Information about detected AHCI controller. @@ -130,12 +154,38 @@ pub struct AhciInfo { pub device_id: u16, } +/// Information about detected SDHCI controller. +#[derive(Debug, Clone, Copy)] +pub struct SdhciInfo { + /// PCI address + pub pci_addr: PciAddr, + /// MMIO base from BAR0 + pub mmio_base: u64, + /// Device ID + pub device_id: u16, +} + +/// Information about detected USB xHCI controller. +#[derive(Debug, Clone, Copy)] +pub struct UsbMsdInfo { + /// PCI address + pub pci_addr: PciAddr, + /// MMIO base from BAR0 + pub mmio_base: u64, + /// Device ID + pub device_id: u16, +} + /// Result of successful probe and initialization. pub enum BlockProbeResult { /// VirtIO-blk driver VirtIO(VirtioBlkDriver), /// AHCI SATA driver Ahci(AhciDriver), + /// SDHCI block driver + Sdhci(SdhciDriver), + /// USB mass-storage block driver + UsbMsd(UsbMsdDriver), } // ═══════════════════════════════════════════════════════════════════════════ @@ -155,6 +205,16 @@ pub fn scan_for_block_device() -> Option { return Some(DetectedBlockDevice::Ahci(info)); } + // Then try SDHCI (SD card host) + if let Some(info) = find_sdhci_controller() { + return Some(DetectedBlockDevice::Sdhci(info)); + } + + // Then try USB xHCI for USB mass-storage path + if let Some(info) = find_usb_xhci_controller() { + return Some(DetectedBlockDevice::UsbMsd(info)); + } + // Fall back to VirtIO-blk (QEMU, VMs) if let Some((pci_addr, mmio_base)) = find_virtio_blk() { return Some(DetectedBlockDevice::VirtIO { @@ -195,24 +255,12 @@ pub fn scan_all_block_devices() -> ([Option; MAX_BLOCK_DEVI } continue; } - if vendor_id != INTEL_VENDOR_ID { - if function == 0 { - let header = pci_cfg_read16(addr, offset::HEADER_TYPE) & 0x80; - if header == 0 { - break; - } - } - continue; - } let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); let class = (class_code >> 8) & 0xFFFF; if class != PCI_CLASS_SATA_AHCI { continue; } let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); - if !AHCI_DEVICE_IDS.contains(&device_id) { - continue; - } let bar5 = pci_cfg_read32(addr, offset::BAR5); if bar5 == 0 || (bar5 & 0x01) != 0 { continue; @@ -234,6 +282,103 @@ pub fn scan_all_block_devices() -> ([Option; MAX_BLOCK_DEVI } } + // Collect all VirtIO-blk devices. + // Collect all SDHCI controllers. + for bus in 0..=255u8 { + if count >= MAX_BLOCK_DEVICES { + break; + } + for device in 0..32u8 { + if count >= MAX_BLOCK_DEVICES { + break; + } + for function in 0..8u8 { + if count >= MAX_BLOCK_DEVICES { + break; + } + let addr = PciAddr::new(bus, device, function); + let vendor_id = pci_cfg_read16(addr, offset::VENDOR_ID); + if vendor_id == 0xFFFF { + if function == 0 { + break; + } + continue; + } + let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); + let class = (class_code >> 8) & 0xFFFF; + if class != PCI_CLASS_SDHCI { + continue; + } + let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); + let bar0 = pci_cfg_read32(addr, offset::BAR0); + if bar0 == 0 || (bar0 & 0x01) != 0 { + continue; + } + let is_64bit = (bar0 & 0x06) == 0x04; + let mmio_base = if is_64bit { + let bar1 = pci_cfg_read32(addr, offset::BAR1); + ((bar1 as u64) << 32) | ((bar0 & 0xFFFFFFF0) as u64) + } else { + (bar0 & 0xFFFFFFF0) as u64 + }; + result[count] = Some(DetectedBlockDevice::Sdhci(SdhciInfo { + pci_addr: addr, + mmio_base, + device_id, + })); + count += 1; + } + } + } + + // Collect all USB xHCI controllers. + for bus in 0..=255u8 { + if count >= MAX_BLOCK_DEVICES { + break; + } + for device in 0..32u8 { + if count >= MAX_BLOCK_DEVICES { + break; + } + for function in 0..8u8 { + if count >= MAX_BLOCK_DEVICES { + break; + } + let addr = PciAddr::new(bus, device, function); + let vendor_id = pci_cfg_read16(addr, offset::VENDOR_ID); + if vendor_id == 0xFFFF { + if function == 0 { + break; + } + continue; + } + let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); + let class = (class_code >> 8) & 0xFFFF; + if class != PCI_CLASS_USB_XHCI { + continue; + } + let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); + let bar0 = pci_cfg_read32(addr, offset::BAR0); + if bar0 == 0 || (bar0 & 0x01) != 0 { + continue; + } + let is_64bit = (bar0 & 0x06) == 0x04; + let mmio_base = if is_64bit { + let bar1 = pci_cfg_read32(addr, offset::BAR1); + ((bar1 as u64) << 32) | ((bar0 & 0xFFFFFFF0) as u64) + } else { + (bar0 & 0xFFFFFFF0) as u64 + }; + result[count] = Some(DetectedBlockDevice::UsbMsd(UsbMsdInfo { + pci_addr: addr, + mmio_base, + device_id, + })); + count += 1; + } + } + } + // Collect all VirtIO-blk devices. for bus in 0..=255u8 { if count >= MAX_BLOCK_DEVICES { @@ -306,17 +451,6 @@ pub fn find_ahci_controller() -> Option { continue; } - // Check for Intel - if vendor_id != INTEL_VENDOR_ID { - if function == 0 { - let header = pci_cfg_read16(addr, offset::HEADER_TYPE) & 0x80; - if header == 0 { - break; - } - } - continue; - } - // Check class code for SATA AHCI let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); let class = (class_code >> 8) & 0xFFFF; @@ -326,11 +460,6 @@ pub fn find_ahci_controller() -> Option { let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); - // Verify it's a known AHCI device - if !AHCI_DEVICE_IDS.contains(&device_id) { - continue; - } - // Read BAR5 (ABAR - AHCI Base Address Register) // AHCI uses BAR5 for MMIO let bar5 = pci_cfg_read32(addr, offset::BAR5); @@ -359,6 +488,100 @@ pub fn find_ahci_controller() -> Option { None } +/// Scan for SDHCI controller. +pub fn find_sdhci_controller() -> Option { + for bus in 0..=255u8 { + for device in 0..32u8 { + for function in 0..8u8 { + let addr = PciAddr::new(bus, device, function); + + let vendor_id = pci_cfg_read16(addr, offset::VENDOR_ID); + if vendor_id == 0xFFFF { + if function == 0 { + break; + } + continue; + } + + let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); + let class = (class_code >> 8) & 0xFFFF; + if class != PCI_CLASS_SDHCI { + continue; + } + + let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); + let bar0 = pci_cfg_read32(addr, offset::BAR0); + if bar0 == 0 || (bar0 & 0x01) != 0 { + continue; + } + + let is_64bit = (bar0 & 0x06) == 0x04; + let mmio_base = if is_64bit { + let bar1 = pci_cfg_read32(addr, offset::BAR1); + ((bar1 as u64) << 32) | ((bar0 & 0xFFFFFFF0) as u64) + } else { + (bar0 & 0xFFFFFFF0) as u64 + }; + + return Some(SdhciInfo { + pci_addr: addr, + mmio_base, + device_id, + }); + } + } + } + + None +} + +/// Scan for USB xHCI controller (candidate for USB mass-storage backend). +pub fn find_usb_xhci_controller() -> Option { + for bus in 0..=255u8 { + for device in 0..32u8 { + for function in 0..8u8 { + let addr = PciAddr::new(bus, device, function); + + let vendor_id = pci_cfg_read16(addr, offset::VENDOR_ID); + if vendor_id == 0xFFFF { + if function == 0 { + break; + } + continue; + } + + let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); + let class = (class_code >> 8) & 0xFFFF; + if class != PCI_CLASS_USB_XHCI { + continue; + } + + let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); + let bar0 = pci_cfg_read32(addr, offset::BAR0); + if bar0 == 0 || (bar0 & 0x01) != 0 { + continue; + } + + let is_64bit = (bar0 & 0x06) == 0x04; + let mmio_base = if is_64bit { + let bar1 = pci_cfg_read32(addr, offset::BAR1); + ((bar1 as u64) << 32) | ((bar0 & 0xFFFFFFF0) as u64) + } else { + (bar0 & 0xFFFFFFF0) as u64 + }; + + return Some(UsbMsdInfo { + pci_addr: addr, + mmio_base, + device_id, + }); + } + } + } + + None +} + /// Scan for VirtIO-blk device. fn find_virtio_blk() -> Option<(PciAddr, u64)> { for bus in 0..=255u8 { @@ -477,6 +700,50 @@ fn enable_pci_device(addr: PciAddr) { pci_cfg_write16(addr, offset::COMMAND, cmd | 0x06); } +/// Intel AHCI quirk: firmware sometimes leaves PCS port-enable bits unset. +/// Mirror PORTS_IMPL into PCS_6 so AHCI ports are actually visible to software. +fn intel_ahci_pcs_quirk(addr: PciAddr, abar: u64) { + const PCS_6: u8 = 0x92; + const PCS_7: u8 = 0x94; + const AHCI_CAP_OFF: u64 = 0x00; + const AHCI_PI_OFF: u64 = 0x0C; + + if pci_cfg_read16(addr, offset::VENDOR_ID) != INTEL_VENDOR_ID { + return; + } + + let mut port_map = unsafe { core::ptr::read_volatile((abar + AHCI_PI_OFF) as *const u32) }; + if port_map == 0 { + let cap = unsafe { core::ptr::read_volatile((abar + AHCI_CAP_OFF) as *const u32) }; + let n_ports = ((cap & 0x1F) + 1).min(32); + port_map = if n_ports >= 32 { + u32::MAX + } else { + (1u32 << n_ports) - 1 + }; + } + + if port_map == 0 { + return; + } + + let lo = (port_map & 0xFFFF) as u16; + if lo != 0 { + let pcs6 = pci_cfg_read16(addr, PCS_6); + if (pcs6 & lo) != lo { + pci_cfg_write16(addr, PCS_6, pcs6 | lo); + } + } + + let hi = ((port_map >> 16) & 0xFFFF) as u16; + if hi != 0 { + let pcs7 = pci_cfg_read16(addr, PCS_7); + if (pcs7 & hi) != hi { + pci_cfg_write16(addr, PCS_7, pcs7 | hi); + } + } +} + /// Probe for block device and create appropriate driver. /// /// # Safety @@ -491,6 +758,7 @@ pub unsafe fn probe_and_create_block_driver( DetectedBlockDevice::Ahci(info) => { // Enable device enable_pci_device(info.pci_addr); + intel_ahci_pcs_quirk(info.pci_addr, info.abar); // Create AHCI config let ahci_config = AhciConfig { @@ -510,6 +778,32 @@ pub unsafe fn probe_and_create_block_driver( Ok(BlockProbeResult::Ahci(driver)) } + DetectedBlockDevice::Sdhci(info) => { + enable_pci_device(info.pci_addr); + + let sdhci_config = SdhciConfig { + tsc_freq: config.tsc_freq, + dma_phys: 0, + dma_size: 0, + }; + + let driver = SdhciDriver::new(info.mmio_base, sdhci_config)?; + Ok(BlockProbeResult::Sdhci(driver)) + } + + DetectedBlockDevice::UsbMsd(info) => { + enable_pci_device(info.pci_addr); + + let usb_config = UsbMsdConfig { + tsc_freq: config.tsc_freq, + dma_phys: 0, + dma_size: 0, + }; + + let driver = UsbMsdDriver::new(info.mmio_base, usb_config)?; + Ok(BlockProbeResult::UsbMsd(driver)) + } + DetectedBlockDevice::VirtIO { pci_addr, mmio_base, @@ -569,9 +863,13 @@ pub unsafe fn probe_unified_block_device( match probe_and_create_block_driver(config) { Ok(BlockProbeResult::VirtIO(driver)) => Ok(UnifiedBlockDevice::VirtIO(driver)), Ok(BlockProbeResult::Ahci(driver)) => Ok(UnifiedBlockDevice::Ahci(driver)), + Ok(BlockProbeResult::Sdhci(driver)) => Ok(UnifiedBlockDevice::Sdhci(driver)), + Ok(BlockProbeResult::UsbMsd(driver)) => Ok(UnifiedBlockDevice::UsbMsd(driver)), Err(BlockProbeError::NoDevice) => Err(UnifiedBlockError::NoDevice), Err(BlockProbeError::VirtioInitFailed) => Err(UnifiedBlockError::NoDevice), Err(BlockProbeError::AhciInitFailed) => Err(UnifiedBlockError::NoDevice), + Err(BlockProbeError::SdhciInitFailed) => Err(UnifiedBlockError::NoDevice), + Err(BlockProbeError::UsbMsdInitFailed) => Err(UnifiedBlockError::NoDevice), Err(_) => Err(UnifiedBlockError::NoDevice), } } @@ -590,6 +888,7 @@ pub unsafe fn create_unified_from_detected( match *detected { DetectedBlockDevice::Ahci(info) => { enable_pci_device(info.pci_addr); + intel_ahci_pcs_quirk(info.pci_addr, info.abar); let ahci_config = AhciConfig { tsc_freq: config.tsc_freq, cmd_list_cpu: config.ahci_cmd_list_cpu, @@ -601,10 +900,31 @@ pub unsafe fn create_unified_from_detected( identify_cpu: config.ahci_identify_cpu, identify_phys: config.ahci_identify_phys, }; - let driver = - AhciDriver::new(info.abar, ahci_config).map_err(|_| UnifiedBlockError::NoDevice)?; + let driver = AhciDriver::new(info.abar, ahci_config).map_err(UnifiedBlockError::AhciError)?; Ok(UnifiedBlockDevice::Ahci(driver)) } + DetectedBlockDevice::Sdhci(info) => { + enable_pci_device(info.pci_addr); + let sdhci_config = SdhciConfig { + tsc_freq: config.tsc_freq, + dma_phys: 0, + dma_size: 0, + }; + let driver = SdhciDriver::new(info.mmio_base, sdhci_config) + .map_err(UnifiedBlockError::SdhciError)?; + Ok(UnifiedBlockDevice::Sdhci(driver)) + } + DetectedBlockDevice::UsbMsd(info) => { + enable_pci_device(info.pci_addr); + let usb_config = UsbMsdConfig { + tsc_freq: config.tsc_freq, + dma_phys: 0, + dma_size: 0, + }; + let driver = UsbMsdDriver::new(info.mmio_base, usb_config) + .map_err(UnifiedBlockError::UsbMsdError)?; + Ok(UnifiedBlockDevice::UsbMsd(driver)) + } DetectedBlockDevice::VirtIO { pci_addr, mmio_base, @@ -810,7 +1130,7 @@ pub unsafe fn create_unified_from_detected( VirtioBlkInitError::TransportError => dbg_str("TransportError"), } dbg_str("\n"); - Err(UnifiedBlockError::NoDevice) + Err(UnifiedBlockError::VirtioError(e)) } } } else { @@ -820,7 +1140,7 @@ pub unsafe fn create_unified_from_detected( legacy_config.notify_addr = config.virtio_notify_addr; legacy_config.transport_type = 0; let driver = VirtioBlkDriver::new(mmio_base, legacy_config) - .map_err(|_| UnifiedBlockError::NoDevice)?; + .map_err(UnifiedBlockError::VirtioError)?; Ok(UnifiedBlockDevice::VirtIO(driver)) } } @@ -841,6 +1161,7 @@ pub unsafe fn create_unified_from_detected_ahci_port( match *detected { DetectedBlockDevice::Ahci(info) => { enable_pci_device(info.pci_addr); + intel_ahci_pcs_quirk(info.pci_addr, info.abar); let ahci_config = AhciConfig { tsc_freq: config.tsc_freq, cmd_list_cpu: config.ahci_cmd_list_cpu, @@ -853,7 +1174,7 @@ pub unsafe fn create_unified_from_detected_ahci_port( identify_phys: config.ahci_identify_phys, }; let driver = AhciDriver::new_on_port(info.abar, ahci_config, port_num) - .map_err(|_| UnifiedBlockError::NoDevice)?; + .map_err(UnifiedBlockError::AhciError)?; Ok(UnifiedBlockDevice::Ahci(driver)) } _ => Err(UnifiedBlockError::NoDevice), diff --git a/network/src/device/mod.rs b/network/src/device/mod.rs index a3d515cd..ce7248a5 100644 --- a/network/src/device/mod.rs +++ b/network/src/device/mod.rs @@ -235,6 +235,8 @@ impl NetworkDevice for UnifiedNetDevice { use crate::driver::ahci::{AhciDriver, AhciInitError}; use crate::driver::block_traits::{BlockCompletion, BlockDeviceInfo, BlockDriver, BlockError}; +use crate::driver::sdhci::{SdhciDriver, SdhciInitError}; +use crate::driver::usb_msd::{UsbMsdDriver, UsbMsdInitError}; use crate::driver::virtio_blk::{VirtioBlkDriver, VirtioBlkInitError}; /// Unified block device that works with both VirtIO-blk and AHCI. @@ -264,6 +266,10 @@ pub enum UnifiedBlockDevice { VirtIO(VirtioBlkDriver), /// AHCI SATA driver (real hardware - ThinkPad T450s) Ahci(AhciDriver), + /// SDHCI block driver (SD/MMC host path) + Sdhci(SdhciDriver), + /// USB mass-storage block driver + UsbMsd(UsbMsdDriver), } /// Errors from unified block device operations. @@ -275,6 +281,10 @@ pub enum UnifiedBlockError { VirtioError(VirtioBlkInitError), /// AHCI initialization failed AhciError(AhciInitError), + /// SDHCI initialization failed + SdhciError(SdhciInitError), + /// USB MSD initialization failed + UsbMsdError(UsbMsdInitError), } impl From for UnifiedBlockError { @@ -289,12 +299,26 @@ impl From for UnifiedBlockError { } } +impl From for UnifiedBlockError { + fn from(e: SdhciInitError) -> Self { + UnifiedBlockError::SdhciError(e) + } +} + +impl From for UnifiedBlockError { + fn from(e: UsbMsdInitError) -> Self { + UnifiedBlockError::UsbMsdError(e) + } +} + impl UnifiedBlockDevice { /// Get which driver type is being used. pub fn driver_type(&self) -> &'static str { match self { UnifiedBlockDevice::VirtIO(_) => "VirtIO-blk", UnifiedBlockDevice::Ahci(_) => "AHCI SATA", + UnifiedBlockDevice::Sdhci(_) => "SDHCI", + UnifiedBlockDevice::UsbMsd(_) => "USB-MSD", } } @@ -303,6 +327,8 @@ impl UnifiedBlockDevice { match self { UnifiedBlockDevice::VirtIO(_) => true, // VirtIO always ready after init UnifiedBlockDevice::Ahci(d) => d.link_up(), + UnifiedBlockDevice::Sdhci(_) => true, + UnifiedBlockDevice::UsbMsd(_) => true, } } } @@ -312,6 +338,8 @@ impl BlockDriver for UnifiedBlockDevice { match self { UnifiedBlockDevice::VirtIO(d) => d.info(), UnifiedBlockDevice::Ahci(d) => d.info(), + UnifiedBlockDevice::Sdhci(d) => d.info(), + UnifiedBlockDevice::UsbMsd(d) => d.info(), } } @@ -319,6 +347,8 @@ impl BlockDriver for UnifiedBlockDevice { match self { UnifiedBlockDevice::VirtIO(d) => d.can_submit(), UnifiedBlockDevice::Ahci(d) => d.can_submit(), + UnifiedBlockDevice::Sdhci(d) => d.can_submit(), + UnifiedBlockDevice::UsbMsd(d) => d.can_submit(), } } @@ -336,6 +366,12 @@ impl BlockDriver for UnifiedBlockDevice { UnifiedBlockDevice::Ahci(d) => { d.submit_read(sector, buffer_phys, num_sectors, request_id) } + UnifiedBlockDevice::Sdhci(d) => { + d.submit_read(sector, buffer_phys, num_sectors, request_id) + } + UnifiedBlockDevice::UsbMsd(d) => { + d.submit_read(sector, buffer_phys, num_sectors, request_id) + } } } @@ -353,6 +389,12 @@ impl BlockDriver for UnifiedBlockDevice { UnifiedBlockDevice::Ahci(d) => { d.submit_write(sector, buffer_phys, num_sectors, request_id) } + UnifiedBlockDevice::Sdhci(d) => { + d.submit_write(sector, buffer_phys, num_sectors, request_id) + } + UnifiedBlockDevice::UsbMsd(d) => { + d.submit_write(sector, buffer_phys, num_sectors, request_id) + } } } @@ -360,6 +402,8 @@ impl BlockDriver for UnifiedBlockDevice { match self { UnifiedBlockDevice::VirtIO(d) => d.poll_completion(), UnifiedBlockDevice::Ahci(d) => d.poll_completion(), + UnifiedBlockDevice::Sdhci(d) => d.poll_completion(), + UnifiedBlockDevice::UsbMsd(d) => d.poll_completion(), } } @@ -367,6 +411,8 @@ impl BlockDriver for UnifiedBlockDevice { match self { UnifiedBlockDevice::VirtIO(d) => d.notify(), UnifiedBlockDevice::Ahci(d) => d.notify(), + UnifiedBlockDevice::Sdhci(d) => d.notify(), + UnifiedBlockDevice::UsbMsd(d) => d.notify(), } } @@ -374,6 +420,8 @@ impl BlockDriver for UnifiedBlockDevice { match self { UnifiedBlockDevice::VirtIO(d) => d.flush(), UnifiedBlockDevice::Ahci(d) => d.flush(), + UnifiedBlockDevice::Sdhci(d) => d.flush(), + UnifiedBlockDevice::UsbMsd(d) => d.flush(), } } } diff --git a/network/src/driver/ahci/mod.rs b/network/src/driver/ahci/mod.rs index 15b8d22a..e5b8003a 100644 --- a/network/src/driver/ahci/mod.rs +++ b/network/src/driver/ahci/mod.rs @@ -36,6 +36,7 @@ pub mod regs; use crate::driver::block_traits::{ BlockCompletion, BlockDeviceInfo, BlockDriver, BlockDriverInit, BlockError, }; +use crate::boot::read_tsc_raw; use core::ptr; // Re-exports @@ -268,8 +269,11 @@ impl AhciDriver { let tsc_freq = config.tsc_freq; // ═══════════════════════════════════════════════════════════════════ - // STEP 1: Enable AHCI mode + // STEP 1: Reset HBA to known state, then enable AHCI mode // ═══════════════════════════════════════════════════════════════════ + if asm_ahci_hba_reset(abar, tsc_freq) != 0 { + return Err(AhciInitError::ResetFailed); + } asm_ahci_enable(abar); // ═══════════════════════════════════════════════════════════════════ @@ -291,110 +295,174 @@ impl AhciDriver { asm_ahci_disable_interrupts(abar); // ═══════════════════════════════════════════════════════════════════ - // STEP 4: Select ATA port + // STEP 4: Build candidate port list (strict ATA first, then fallback) // ═══════════════════════════════════════════════════════════════════ - let port_num = if let Some(port) = forced_port { - if port >= 32 || (ports_impl & (1 << port)) == 0 { + let mut strict_ports = [u32::MAX; 32]; + let mut strict_count = 0usize; + let mut fallback_ports = [u32::MAX; 32]; + let mut fallback_count = 0usize; + + if let Some(port) = forced_port { + if port >= 32 { return Err(AhciInitError::NoDeviceFound); } - let det = asm_ahci_port_detect(abar, port); - if det != DET_PHY_COMM { - return Err(AhciInitError::NoDeviceFound); + let mut impl_mask = ports_impl; + if impl_mask == 0 { + let n_ports = asm_ahci_get_num_ports(abar).min(32); + impl_mask = if n_ports >= 32 { + u32::MAX + } else { + (1u32 << n_ports) - 1 + }; } - let sig = asm_ahci_port_read_sig(abar, port); - if sig != SIG_ATA { + if (impl_mask & (1 << port)) == 0 { return Err(AhciInitError::NoDeviceFound); } - port + strict_ports[0] = port; + strict_count = 1; } else { - let mut active_port: Option = None; + let mut impl_mask = ports_impl; + if impl_mask == 0 { + let n_ports = asm_ahci_get_num_ports(abar).min(32); + impl_mask = if n_ports >= 32 { + u32::MAX + } else { + (1u32 << n_ports) - 1 + }; + } + let settle_ticks = (tsc_freq / 1000).saturating_mul(200); for port in 0..32u32 { - if (ports_impl & (1 << port)) == 0 { - continue; // Port not implemented + if (impl_mask & (1 << port)) == 0 { + continue; } - let det = asm_ahci_port_detect(abar, port); - if det != DET_PHY_COMM { - continue; // No device or not ready + let start = read_tsc_raw(); + let mut strict = false; + let mut fallback = false; + + while read_tsc_raw().wrapping_sub(start) < settle_ticks { + let det = asm_ahci_port_detect(abar, port); + let sig = asm_ahci_port_read_sig(abar, port); + let ssts = asm_ahci_port_read_ssts(abar, port); + let ipm = (ssts >> 8) & 0x0F; + + if (det == DET_PHY_COMM || det == DET_PRESENT) && sig == SIG_ATA { + strict = true; + break; + } + + if det != DET_NONE || sig != 0 || ipm != 0 { + fallback = true; + } + + core::hint::spin_loop(); } - // Check signature - let sig = asm_ahci_port_read_sig(abar, port); - if sig == SIG_ATA { - active_port = Some(port); - break; + if strict && strict_count < strict_ports.len() { + strict_ports[strict_count] = port; + strict_count += 1; + } else if fallback && fallback_count < fallback_ports.len() { + fallback_ports[fallback_count] = port; + fallback_count += 1; } } + } - active_port.ok_or(AhciInitError::NoDeviceFound)? - }; + if strict_count == 0 && fallback_count == 0 { + return Err(AhciInitError::NoDeviceFound); + } + + let mut candidate_ports = [u32::MAX; 32]; + let mut candidate_count = 0usize; + for i in 0..strict_count { + candidate_ports[candidate_count] = strict_ports[i]; + candidate_count += 1; + } + for i in 0..fallback_count { + if candidate_count >= candidate_ports.len() { + break; + } + candidate_ports[candidate_count] = fallback_ports[i]; + candidate_count += 1; + } // ═══════════════════════════════════════════════════════════════════ - // STEP 5: Stop port and configure DMA structures + // STEP 5-7: Try candidates until one IDENTIFY succeeds // ═══════════════════════════════════════════════════════════════════ - let stop_result = asm_ahci_port_stop(abar, port_num, tsc_freq); - if stop_result != 0 { - return Err(AhciInitError::PortStopTimeout); - } + let mut last_err = AhciInitError::NoDeviceFound; - // Setup command list and FIS receive buffer - asm_ahci_port_setup(abar, port_num, config.cmd_list_phys, config.fis_phys); + for i in 0..candidate_count { + let port_num = candidate_ports[i]; - // Clear any pending errors - asm_ahci_port_clear_errors(abar, port_num); - asm_ahci_port_disable_interrupts(abar, port_num); + let stop_result = asm_ahci_port_stop(abar, port_num, tsc_freq); + if stop_result != 0 { + last_err = AhciInitError::PortStopTimeout; + continue; + } - // ═══════════════════════════════════════════════════════════════════ - // STEP 6: Start port - // ═══════════════════════════════════════════════════════════════════ - asm_ahci_port_start(abar, port_num); + asm_ahci_port_setup(abar, port_num, config.cmd_list_phys, config.fis_phys); + asm_ahci_port_clear_errors(abar, port_num); + asm_ahci_port_disable_interrupts(abar, port_num); + asm_ahci_port_start(abar, port_num); + + // Give link/signature a brief settle window after engine start. + let link_ticks = (tsc_freq / 1000).saturating_mul(50); + let link_start = read_tsc_raw(); + while read_tsc_raw().wrapping_sub(link_start) < link_ticks { + let det = asm_ahci_port_detect(abar, port_num); + let sig = asm_ahci_port_read_sig(abar, port_num); + if det != DET_NONE && (sig == SIG_ATA || sig == 0) { + break; + } + core::hint::spin_loop(); + } - // ═══════════════════════════════════════════════════════════════════ - // STEP 7: Issue IDENTIFY DEVICE to get capacity - // ═══════════════════════════════════════════════════════════════════ - let identify_result = asm_ahci_identify_device( - abar, - port_num, - config.identify_phys, - config.cmd_list_cpu as u64, - config.cmd_tables_cpu as u64, - config.cmd_tables_phys, - tsc_freq, - ); - - if identify_result != 0 { - return Err(AhciInitError::IdentifyFailed); - } + let identify_result = asm_ahci_identify_device( + abar, + port_num, + config.identify_phys, + config.cmd_list_cpu as u64, + config.cmd_tables_cpu as u64, + config.cmd_tables_phys, + tsc_freq, + ); - // Parse IDENTIFY data - let total_sectors = asm_ahci_get_identify_capacity(config.identify_cpu as u64); - let sector_size = asm_ahci_get_identify_sector_size(config.identify_cpu as u64); + if identify_result != 0 { + last_err = AhciInitError::IdentifyFailed; + continue; + } - let info = BlockDeviceInfo { - total_sectors, - sector_size, - max_sectors_per_request: 256, // Conservative for DMA - read_only: false, - }; + let total_sectors = asm_ahci_get_identify_capacity(config.identify_cpu as u64); + let sector_size = asm_ahci_get_identify_sector_size(config.identify_cpu as u64); + + let info = BlockDeviceInfo { + total_sectors, + sector_size, + max_sectors_per_request: 256, + read_only: false, + }; + + return Ok(Self { + abar, + port_num, + tsc_freq, + info, + num_slots, + in_flight: [InFlightRequest::default(); MAX_CMD_SLOTS], + next_slot: 0, + cmd_list_cpu: config.cmd_list_cpu, + cmd_list_phys: config.cmd_list_phys, + fis_cpu: config.fis_cpu, + fis_phys: config.fis_phys, + cmd_tables_cpu: config.cmd_tables_cpu, + cmd_tables_phys: config.cmd_tables_phys, + identify_cpu: config.identify_cpu, + identify_phys: config.identify_phys, + }); + } - Ok(Self { - abar, - port_num, - tsc_freq, - info, - num_slots, - in_flight: [InFlightRequest::default(); MAX_CMD_SLOTS], - next_slot: 0, - cmd_list_cpu: config.cmd_list_cpu, - cmd_list_phys: config.cmd_list_phys, - fis_cpu: config.fis_cpu, - fis_phys: config.fis_phys, - cmd_tables_cpu: config.cmd_tables_cpu, - cmd_tables_phys: config.cmd_tables_phys, - identify_cpu: config.identify_cpu, - identify_phys: config.identify_phys, - }) + Err(last_err) } /// Get command header pointer for a slot diff --git a/network/src/driver/mod.rs b/network/src/driver/mod.rs index 04929ba5..7efbcbdc 100644 --- a/network/src/driver/mod.rs +++ b/network/src/driver/mod.rs @@ -24,9 +24,11 @@ pub mod ahci; pub mod block_io_adapter; pub mod block_traits; pub mod intel; +pub mod sdhci; pub mod traits; pub mod unified; pub mod unified_block_io; +pub mod usb_msd; pub mod virtio; pub mod virtio_blk; // Future: @@ -51,6 +53,8 @@ pub use virtio_blk::{VirtioBlkConfig, VirtioBlkDriver, VirtioBlkInitError}; // Re-exports - Block (AHCI/SATA for real hardware) pub use ahci::{AhciConfig, AhciDriver, AhciInitError}; +pub use sdhci::{SdhciConfig, SdhciDriver, SdhciInitError}; +pub use usb_msd::{UsbMsdConfig, UsbMsdDriver, UsbMsdInitError}; // Re-exports - BlockIo adapters (for filesystem compatibility) pub use block_io_adapter::{BlockIoError, VirtioBlkBlockIo}; diff --git a/network/src/driver/sdhci/mod.rs b/network/src/driver/sdhci/mod.rs new file mode 100644 index 00000000..32bcf797 --- /dev/null +++ b/network/src/driver/sdhci/mod.rs @@ -0,0 +1,354 @@ +//! SDHCI block device driver scaffold. +//! +//! This module defines the BlockDriver-facing contract for the upcoming +//! ASM-backed SDHCI implementation. Hardware-touching logic will be added in +//! assembly primitives; Rust keeps orchestration/state/error surfaces stable. + +use crate::driver::block_traits::{ + BlockCompletion, BlockDeviceInfo, BlockDriver, BlockDriverInit, BlockError, +}; +use crate::asm::core::mmio; +use crate::asm::core::tsc; + +extern "win64" { + fn asm_sdhci_read_caps(mmio_base: u64) -> u32; + fn asm_sdhci_card_present(mmio_base: u64) -> u32; + fn asm_sdhci_controller_reset(mmio_base: u64, tsc_freq: u64) -> u32; + fn asm_sdhci_basic_power_clock(mmio_base: u64) -> u32; + fn asm_sdhci_read_block_pio(mmio_base: u64, lba: u64, dst: u64, tsc_freq: u64) -> u32; +} + +/// PCI base class / subclass / prog-if for SD Host Controller. +pub const PCI_CLASS_SDHCI: u32 = 0x080501; + +/// SDHCI configuration. +#[derive(Debug, Clone)] +pub struct SdhciConfig { + /// TSC frequency for timeout calculations. + pub tsc_freq: u64, + /// Optional DMA bounce buffer base (physical). + pub dma_phys: u64, + /// Optional DMA bounce buffer size. + pub dma_size: usize, +} + +/// SDHCI initialization errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SdhciInitError { + InvalidConfig, + ControllerResetFailed, + NoCardPresent, + VoltageSwitchFailed, + ClockSetupFailed, + CommandTimeout, + DataTimeout, + IoError, + NotImplemented, +} + +impl core::fmt::Display for SdhciInitError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidConfig => write!(f, "Invalid SDHCI configuration"), + Self::ControllerResetFailed => write!(f, "SDHCI controller reset failed"), + Self::NoCardPresent => write!(f, "No SD card present"), + Self::VoltageSwitchFailed => write!(f, "SDHCI voltage switch failed"), + Self::ClockSetupFailed => write!(f, "SDHCI clock setup failed"), + Self::CommandTimeout => write!(f, "SDHCI command timeout"), + Self::DataTimeout => write!(f, "SDHCI data timeout"), + Self::IoError => write!(f, "SDHCI I/O error"), + Self::NotImplemented => write!(f, "SDHCI driver not implemented yet"), + } + } +} + +/// SDHCI driver state (scaffold). +pub struct SdhciDriver { + mmio_base: u64, + tsc_freq: u64, + _caps: u32, + info: BlockDeviceInfo, + high_capacity: bool, + rca: u16, + last_completion: Option, +} + +const REG_ARGUMENT: u64 = 0x08; +const REG_COMMAND: u64 = 0x0E; +const REG_RESPONSE0: u64 = 0x10; +const REG_PRESENT_STATE: u64 = 0x24; +const REG_INT_STATUS: u64 = 0x30; + +const PRESENT_CMD_INHIBIT: u32 = 1 << 0; +const PRESENT_DAT_INHIBIT: u32 = 1 << 1; + +const INT_CMD_COMPLETE: u32 = 1 << 0; +const INT_ERROR: u32 = 1 << 15; + +const CMD_RESP_NONE: u16 = 0x00; +const CMD_RESP_LONG: u16 = 0x01; +const CMD_RESP_SHORT: u16 = 0x02; +const CMD_RESP_SHORT_BUSY: u16 = 0x03; +const CMD_CRC: u16 = 0x08; +const CMD_INDEX: u16 = 0x10; + +const ACMD41_OCR: u32 = 0x40FF_8000; + +impl SdhciDriver { + #[inline(always)] + unsafe fn reg32(&self, off: u64) -> u64 { + self.mmio_base + off + } + + fn timeout_ticks(&self, ms: u64) -> u64 { + self.tsc_freq.saturating_mul(ms) / 1000 + } + + unsafe fn wait_not_inhibit(&self, timeout_ms: u64) -> Result<(), SdhciInitError> { + let start = tsc::read_tsc(); + let timeout = self.timeout_ticks(timeout_ms); + loop { + let ps = mmio::read32(self.reg32(REG_PRESENT_STATE)); + if (ps & (PRESENT_CMD_INHIBIT | PRESENT_DAT_INHIBIT)) == 0 { + return Ok(()); + } + if tsc::read_tsc().wrapping_sub(start) > timeout { + return Err(SdhciInitError::CommandTimeout); + } + core::hint::spin_loop(); + } + } + + unsafe fn clear_ints(&self) { + mmio::write32(self.reg32(REG_INT_STATUS), 0xFFFF_FFFF); + } + + unsafe fn send_cmd( + &self, + index: u8, + arg: u32, + flags: u16, + timeout_ms: u64, + ) -> Result { + self.wait_not_inhibit(timeout_ms)?; + self.clear_ints(); + + mmio::write32(self.reg32(REG_ARGUMENT), arg); + let cmd = ((index as u16) << 8) | flags; + mmio::write16(self.reg32(REG_COMMAND), cmd); + + let start = tsc::read_tsc(); + let timeout = self.timeout_ticks(timeout_ms); + loop { + let st = mmio::read32(self.reg32(REG_INT_STATUS)); + if (st & INT_ERROR) != 0 { + self.clear_ints(); + return Err(SdhciInitError::IoError); + } + if (st & INT_CMD_COMPLETE) != 0 { + mmio::write32(self.reg32(REG_INT_STATUS), INT_CMD_COMPLETE); + return Ok(mmio::read32(self.reg32(REG_RESPONSE0))); + } + if tsc::read_tsc().wrapping_sub(start) > timeout { + self.clear_ints(); + return Err(SdhciInitError::CommandTimeout); + } + core::hint::spin_loop(); + } + } + + unsafe fn init_card(&mut self) -> Result<(), SdhciInitError> { + // CMD0: idle + let _ = self.send_cmd(0, 0, CMD_RESP_NONE, 100)?; + + // CMD8: interface condition; if it fails, assume legacy SDSC. + let mut supports_v2 = false; + if let Ok(r7) = self.send_cmd(8, 0x0000_01AA, CMD_RESP_SHORT | CMD_CRC | CMD_INDEX, 100) + { + supports_v2 = (r7 & 0xFFF) == 0x1AA; + } + + // ACMD41 loop until card powers up. + let mut ocr: u32; + let hcs = if supports_v2 { 1u32 << 30 } else { 0 }; + let start = tsc::read_tsc(); + let timeout = self.timeout_ticks(2000); + loop { + let _ = self.send_cmd(55, 0, CMD_RESP_SHORT | CMD_CRC | CMD_INDEX, 100)?; + ocr = self.send_cmd(41, ACMD41_OCR | hcs, CMD_RESP_SHORT, 100)?; + if (ocr & (1u32 << 31)) != 0 { + break; + } + if tsc::read_tsc().wrapping_sub(start) > timeout { + return Err(SdhciInitError::CommandTimeout); + } + } + self.high_capacity = (ocr & (1u32 << 30)) != 0; + + // CMD2: CID + let _ = self.send_cmd(2, 0, CMD_RESP_LONG | CMD_CRC, 200)?; + + // CMD3: RCA assign + let rca_resp = self.send_cmd(3, 0, CMD_RESP_SHORT | CMD_CRC | CMD_INDEX, 200)?; + self.rca = (rca_resp >> 16) as u16; + if self.rca == 0 { + return Err(SdhciInitError::IoError); + } + + // CMD7: select card + let _ = self.send_cmd( + 7, + (self.rca as u32) << 16, + CMD_RESP_SHORT_BUSY | CMD_CRC | CMD_INDEX, + 200, + )?; + + // CMD16: set block length for SDSC cards. + if !self.high_capacity { + let _ = self.send_cmd(16, 512, CMD_RESP_SHORT | CMD_CRC | CMD_INDEX, 100)?; + } + + Ok(()) + } + + /// Create a new SDHCI driver instance. + /// + /// Phase-1 scaffold returns NotImplemented until ASM primitives land. + pub unsafe fn new(mmio_base: u64, config: SdhciConfig) -> Result { + if mmio_base == 0 || config.tsc_freq == 0 { + return Err(SdhciInitError::InvalidConfig); + } + + if asm_sdhci_controller_reset(mmio_base, config.tsc_freq) != 0 { + return Err(SdhciInitError::ControllerResetFailed); + } + + if asm_sdhci_basic_power_clock(mmio_base) != 0 { + return Err(SdhciInitError::ClockSetupFailed); + } + + if asm_sdhci_card_present(mmio_base) == 0 { + return Err(SdhciInitError::NoCardPresent); + } + + let caps = asm_sdhci_read_caps(mmio_base); + + let mut this = Self { + mmio_base, + tsc_freq: config.tsc_freq, + _caps: caps, + info: BlockDeviceInfo { + total_sectors: u32::MAX as u64, + sector_size: 512, + max_sectors_per_request: 128, + read_only: true, + }, + high_capacity: true, + rca: 0, + last_completion: None, + }; + + this.init_card()?; + let _ = config; + Ok(this) + } +} + +impl BlockDriverInit for SdhciDriver { + type Error = SdhciInitError; + type Config = SdhciConfig; + + fn supported_vendors() -> &'static [u16] { + &[] + } + + fn supported_devices() -> &'static [u16] { + &[] + } + + unsafe fn create(mmio_base: u64, config: Self::Config) -> Result { + Self::new(mmio_base, config) + } +} + +impl BlockDriver for SdhciDriver { + fn info(&self) -> BlockDeviceInfo { + self.info + } + + fn can_submit(&self) -> bool { + self.last_completion.is_none() + } + + fn submit_read( + &mut self, + sector: u64, + buffer_phys: u64, + num_sectors: u32, + request_id: u32, + ) -> Result<(), BlockError> { + if self.last_completion.is_some() { + return Err(BlockError::QueueFull); + } + if num_sectors == 0 || num_sectors > self.info.max_sectors_per_request { + return Err(BlockError::RequestTooLarge); + } + if buffer_phys == 0 { + return Err(BlockError::InvalidSector); + } + + let end_sector = sector + .checked_add(num_sectors as u64) + .ok_or(BlockError::InvalidSector)?; + if end_sector > self.info.total_sectors { + return Err(BlockError::InvalidSector); + } + + let mut curr_sector = sector; + let mut curr_dst = buffer_phys; + for _ in 0..num_sectors { + let arg = if self.high_capacity { + curr_sector + } else { + curr_sector + .checked_mul(self.info.sector_size as u64) + .ok_or(BlockError::InvalidSector)? + }; + let rc = unsafe { + asm_sdhci_read_block_pio(self.mmio_base, arg, curr_dst, self.tsc_freq) + }; + if rc != 0 { + return Err(match rc { + 1 | 2 | 3 | 4 => BlockError::Timeout, + _ => BlockError::IoError, + }); + } + + curr_sector = curr_sector.wrapping_add(1); + curr_dst = curr_dst.wrapping_add(self.info.sector_size as u64); + } + + self.last_completion = Some(BlockCompletion { + request_id, + status: 0, + bytes_transferred: num_sectors * self.info.sector_size, + }); + Ok(()) + } + + fn submit_write( + &mut self, + _sector: u64, + _buffer_phys: u64, + _num_sectors: u32, + _request_id: u32, + ) -> Result<(), BlockError> { + Err(BlockError::Unsupported) + } + + fn poll_completion(&mut self) -> Option { + self.last_completion.take() + } + + fn notify(&mut self) {} +} diff --git a/network/src/driver/usb_msd/mod.rs b/network/src/driver/usb_msd/mod.rs new file mode 100644 index 00000000..000d775a --- /dev/null +++ b/network/src/driver/usb_msd/mod.rs @@ -0,0 +1,136 @@ +//! USB mass-storage block driver scaffold. +//! +//! This module reserves the BlockDriver interface for the future ASM-backed +//! USB host + mass-storage transport path (read-focused milestone first). + +use crate::driver::block_traits::{ + BlockCompletion, BlockDeviceInfo, BlockDriver, BlockDriverInit, BlockError, +}; + +extern "win64" { + fn asm_usb_host_probe(mmio_base: u64) -> u32; + fn asm_usb_host_reset(mmio_base: u64, tsc_freq: u64) -> u32; +} + +/// USB mass-storage configuration. +#[derive(Debug, Clone)] +pub struct UsbMsdConfig { + /// TSC frequency for timeout calculations. + pub tsc_freq: u64, + /// Optional DMA bounce buffer base (physical). + pub dma_phys: u64, + /// Optional DMA bounce buffer size. + pub dma_size: usize, +} + +/// USB mass-storage init errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UsbMsdInitError { + InvalidConfig, + ControllerInitFailed, + DeviceEnumerationFailed, + TransportInitFailed, + NoMedia, + CommandTimeout, + IoError, + NotImplemented, +} + +impl core::fmt::Display for UsbMsdInitError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidConfig => write!(f, "Invalid USB MSD configuration"), + Self::ControllerInitFailed => write!(f, "USB controller init failed"), + Self::DeviceEnumerationFailed => write!(f, "USB device enumeration failed"), + Self::TransportInitFailed => write!(f, "USB mass-storage transport init failed"), + Self::NoMedia => write!(f, "USB mass-storage media not present"), + Self::CommandTimeout => write!(f, "USB mass-storage command timeout"), + Self::IoError => write!(f, "USB mass-storage I/O error"), + Self::NotImplemented => write!(f, "USB mass-storage driver not implemented yet"), + } + } +} + +/// USB mass-storage driver state (scaffold). +pub struct UsbMsdDriver { + mmio_base: u64, + _tsc_freq: u64, + info: BlockDeviceInfo, +} + +impl UsbMsdDriver { + /// Create a new USB mass-storage driver instance. + /// + /// Phase-1 scaffold returns NotImplemented until ASM primitives land. + pub unsafe fn new(mmio_base: u64, config: UsbMsdConfig) -> Result { + if mmio_base == 0 || config.tsc_freq == 0 { + return Err(UsbMsdInitError::InvalidConfig); + } + + if asm_usb_host_probe(mmio_base) != 0 { + return Err(UsbMsdInitError::ControllerInitFailed); + } + + if asm_usb_host_reset(mmio_base, config.tsc_freq) != 0 { + return Err(UsbMsdInitError::ControllerInitFailed); + } + + // Probe/reset path is wired, but BOT/SCSI read transport is not yet implemented. + // Fail fast so higher layers skip this backend instead of selecting a non-functional disk. + return Err(UsbMsdInitError::NotImplemented); + } +} + +impl BlockDriverInit for UsbMsdDriver { + type Error = UsbMsdInitError; + type Config = UsbMsdConfig; + + fn supported_vendors() -> &'static [u16] { + &[] + } + + fn supported_devices() -> &'static [u16] { + &[] + } + + unsafe fn create(mmio_base: u64, config: Self::Config) -> Result { + Self::new(mmio_base, config) + } +} + +impl BlockDriver for UsbMsdDriver { + fn info(&self) -> BlockDeviceInfo { + self.info + } + + fn can_submit(&self) -> bool { + true + } + + fn submit_read( + &mut self, + _sector: u64, + _buffer_phys: u64, + _num_sectors: u32, + _request_id: u32, + ) -> Result<(), BlockError> { + let _ = self.mmio_base; + Err(BlockError::DeviceNotReady) + } + + fn submit_write( + &mut self, + _sector: u64, + _buffer_phys: u64, + _num_sectors: u32, + _request_id: u32, + ) -> Result<(), BlockError> { + Err(BlockError::Unsupported) + } + + fn poll_completion(&mut self) -> Option { + None + } + + fn notify(&mut self) {} +} diff --git a/network/src/lib.rs b/network/src/lib.rs index c29cd07f..0bef8df1 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -120,6 +120,8 @@ pub use driver::block_traits::{BlockCompletion, BlockDeviceInfo, BlockDriver, Bl // Block drivers pub use driver::ahci::{AhciConfig, AhciDriver, AhciInitError}; +pub use driver::sdhci::{SdhciConfig, SdhciDriver, SdhciInitError}; +pub use driver::usb_msd::{UsbMsdConfig, UsbMsdDriver, UsbMsdInitError}; pub use driver::virtio_blk::{VirtioBlkConfig, VirtioBlkDriver, VirtioBlkInitError}; // BlockIo adapters (for filesystem compatibility) diff --git a/persistent/src/pe/embedded_reloc_data.rs b/persistent/src/pe/embedded_reloc_data.rs index 3dca98a4..39ab1385 100644 --- a/persistent/src/pe/embedded_reloc_data.rs +++ b/persistent/src/pe/embedded_reloc_data.rs @@ -8,114 +8,117 @@ //! Run: ./tools/extract-reloc-data.sh after each build /// Original .reloc section RVA -pub const RELOC_RVA: u32 = 0x0063e000; +pub const RELOC_RVA: u32 = 0x0063a000; /// Original .reloc section size -pub const RELOC_SIZE: u32 = 0x00000484; +pub const RELOC_SIZE: u32 = 0x000004b0; /// Original ImageBase from linker script pub const ORIGINAL_IMAGE_BASE: u64 = 0x0000004001000000; -/// Hardcoded .reloc section data (1156 bytes) -/// Extracted from morpheus-bootloader.efi at file offset 0x00200a00 +/// Hardcoded .reloc section data (1200 bytes) +/// Extracted from morpheus-bootloader.efi at file offset 0x001fc400 #[allow(dead_code)] -pub const RELOC_DATA: [u8; 1156] = [ - 0x00, 0x60, 0x07, 0x00, 0x44, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, - 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, - 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0xf8, 0xa7, - 0x10, 0xa8, 0x28, 0xa8, 0x30, 0xa9, 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, - 0x40, 0xab, 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, 0xa0, 0xab, 0xb8, 0xab, - 0xd0, 0xab, 0xe8, 0xab, 0x00, 0xac, 0x00, 0x00, 0x00, 0x70, 0x07, 0x00, - 0x68, 0x00, 0x00, 0x00, 0xd0, 0xa1, 0xe8, 0xa1, 0x00, 0xa2, 0x18, 0xa2, - 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, 0x78, 0xa2, 0x90, 0xa2, 0x10, 0xa3, - 0x28, 0xa3, 0x40, 0xa3, 0x58, 0xa3, 0x70, 0xa3, 0x88, 0xa3, 0xa0, 0xa3, - 0xb8, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, 0x18, 0xa4, 0x30, 0xa4, - 0x48, 0xa4, 0x60, 0xa4, 0x78, 0xa4, 0x90, 0xa4, 0x10, 0xa5, 0x28, 0xa5, - 0x40, 0xa5, 0x58, 0xa5, 0x70, 0xa5, 0xf0, 0xa5, 0x98, 0xa7, 0xb0, 0xa7, - 0xc8, 0xa7, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, - 0x28, 0xab, 0x60, 0xab, 0x18, 0xac, 0xc0, 0xae, 0xf0, 0xae, 0x90, 0xaf, - 0xa8, 0xaf, 0xc0, 0xaf, 0x00, 0x80, 0x07, 0x00, 0x84, 0x00, 0x00, 0x00, - 0xc0, 0xa0, 0xd8, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, 0x20, 0xa1, 0x38, 0xa1, - 0xb8, 0xa1, 0xd0, 0xa1, 0x58, 0xa2, 0xb8, 0xa2, 0xd0, 0xa2, 0xe8, 0xa2, - 0x48, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, 0xf8, 0xa3, 0x38, 0xa4, 0x48, 0xa4, - 0x90, 0xa4, 0xa8, 0xa4, 0xc0, 0xa4, 0xd8, 0xa4, 0x80, 0xa5, 0x98, 0xa5, - 0xd8, 0xa5, 0xf0, 0xa5, 0x08, 0xa6, 0x20, 0xa6, 0xa8, 0xa6, 0xc0, 0xa6, - 0x30, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, 0x18, 0xaa, 0x30, 0xaa, 0x48, 0xaa, - 0x60, 0xaa, 0x98, 0xaa, 0xc8, 0xab, 0x00, 0xad, 0x18, 0xad, 0x68, 0xad, - 0xe0, 0xad, 0xf8, 0xad, 0x10, 0xae, 0x28, 0xae, 0x40, 0xae, 0x58, 0xae, - 0x70, 0xae, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, 0xd0, 0xae, 0xe8, 0xae, - 0x00, 0xaf, 0x18, 0xaf, 0x30, 0xaf, 0x98, 0xaf, 0xb0, 0xaf, 0xc8, 0xaf, - 0xe0, 0xaf, 0xf8, 0xaf, 0x00, 0x90, 0x07, 0x00, 0x94, 0x00, 0x00, 0x00, - 0x10, 0xa0, 0x28, 0xa0, 0x40, 0xa0, 0x58, 0xa0, 0x70, 0xa0, 0x88, 0xa0, - 0xd8, 0xa0, 0xe8, 0xa0, 0x40, 0xa1, 0x50, 0xa1, 0x30, 0xa3, 0x48, 0xa3, - 0x60, 0xa3, 0x78, 0xa3, 0xf8, 0xa3, 0x10, 0xa4, 0x28, 0xa4, 0x40, 0xa4, - 0x58, 0xa4, 0x70, 0xa4, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xe8, 0xa4, - 0x88, 0xa5, 0xa0, 0xa5, 0x20, 0xa6, 0xa0, 0xa6, 0xb8, 0xa6, 0xd0, 0xa6, - 0xe8, 0xa6, 0x00, 0xa7, 0x18, 0xa7, 0xa0, 0xa7, 0xc8, 0xa8, 0xd8, 0xa8, - 0xe8, 0xa8, 0x60, 0xa9, 0x78, 0xa9, 0x90, 0xa9, 0xa8, 0xa9, 0xc0, 0xa9, - 0xd8, 0xa9, 0xf0, 0xa9, 0x08, 0xaa, 0x20, 0xaa, 0x38, 0xaa, 0x50, 0xaa, - 0x68, 0xaa, 0x80, 0xaa, 0x98, 0xaa, 0xb0, 0xaa, 0xc8, 0xaa, 0x70, 0xac, - 0x88, 0xac, 0xa0, 0xac, 0xb8, 0xac, 0xd0, 0xac, 0xe8, 0xac, 0x00, 0xad, - 0x18, 0xad, 0x30, 0xad, 0xb8, 0xad, 0xf8, 0xad, 0x08, 0xae, 0xc8, 0xae, - 0x58, 0xaf, 0x98, 0xaf, 0xe0, 0xaf, 0x00, 0x00, 0x00, 0xa0, 0x07, 0x00, - 0x68, 0x00, 0x00, 0x00, 0x78, 0xa0, 0xc0, 0xa0, 0xd0, 0xa0, 0xe0, 0xa0, - 0xf0, 0xa0, 0x70, 0xa1, 0x88, 0xa1, 0x08, 0xa2, 0xa8, 0xa2, 0xb8, 0xa2, - 0xc8, 0xa2, 0x60, 0xa3, 0x98, 0xa3, 0xb0, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, - 0x60, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xf8, 0xa5, 0x28, 0xa6, 0x40, 0xa6, - 0x98, 0xa6, 0xb0, 0xa6, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x88, 0xa7, - 0xb8, 0xa7, 0xb0, 0xa9, 0xc8, 0xa9, 0x60, 0xaa, 0x78, 0xaa, 0x90, 0xaa, - 0xa8, 0xaa, 0xc0, 0xaa, 0xd0, 0xab, 0x30, 0xac, 0xa8, 0xac, 0xa0, 0xad, - 0xb8, 0xad, 0xd0, 0xad, 0xe8, 0xad, 0x00, 0xae, 0x30, 0xae, 0x50, 0xae, - 0xa8, 0xae, 0xb8, 0xaf, 0x00, 0xb0, 0x07, 0x00, 0x3c, 0x00, 0x00, 0x00, - 0x90, 0xa0, 0xa8, 0xa0, 0xc0, 0xa0, 0x30, 0xa3, 0x48, 0xa3, 0x60, 0xa3, - 0x78, 0xa3, 0x90, 0xa3, 0xa8, 0xa3, 0xc0, 0xa3, 0x80, 0xa4, 0xe8, 0xa4, - 0x08, 0xa5, 0x20, 0xa5, 0x38, 0xa5, 0x50, 0xa5, 0x68, 0xa5, 0x70, 0xa6, - 0xe0, 0xa6, 0xf8, 0xa6, 0x10, 0xa7, 0x28, 0xa7, 0x40, 0xa7, 0x58, 0xa7, - 0x98, 0xa7, 0x28, 0xa8, 0x00, 0xc0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, - 0x10, 0xa3, 0x48, 0xa3, 0x48, 0xa9, 0x60, 0xa9, 0x78, 0xa9, 0x90, 0xa9, - 0xa8, 0xa9, 0xc0, 0xa9, 0xe0, 0xaa, 0xa8, 0xab, 0x00, 0xad, 0x10, 0xad, - 0x20, 0xad, 0x30, 0xad, 0x40, 0xad, 0x50, 0xad, 0x60, 0xad, 0x70, 0xad, - 0x80, 0xad, 0x90, 0xad, 0xa0, 0xad, 0xb0, 0xad, 0xc0, 0xad, 0xd0, 0xad, - 0xe0, 0xad, 0xf0, 0xad, 0x00, 0xae, 0x10, 0xae, 0x20, 0xae, 0x30, 0xae, - 0x40, 0xae, 0x50, 0xae, 0x60, 0xae, 0x70, 0xae, 0x80, 0xae, 0x90, 0xae, - 0xa0, 0xae, 0xb0, 0xae, 0xc0, 0xae, 0xd0, 0xae, 0xe0, 0xae, 0xf0, 0xae, - 0x00, 0xd0, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xd0, 0xa0, 0x00, 0x00, - 0x00, 0xe0, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x70, 0xa4, 0xc0, 0xa4, - 0x18, 0xa5, 0x88, 0xa6, 0xa0, 0xa6, 0xf0, 0xa6, 0x00, 0xa7, 0x10, 0xa7, - 0x20, 0xa7, 0x30, 0xa7, 0x60, 0xa7, 0x98, 0xa7, 0xe0, 0xa7, 0x30, 0xa8, - 0x68, 0xa8, 0x80, 0xa8, 0xc8, 0xa8, 0x50, 0xa9, 0x00, 0xf0, 0x07, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x08, 0xa6, 0x20, 0xa6, 0x58, 0xa6, 0x88, 0xa6, - 0xa0, 0xa6, 0xb8, 0xa6, 0xd0, 0xa6, 0xe8, 0xa6, 0x00, 0xa7, 0x18, 0xa7, - 0x30, 0xa7, 0x70, 0xa7, 0x88, 0xa7, 0xa0, 0xa7, 0xb8, 0xa7, 0xd0, 0xa7, - 0x08, 0xa8, 0x38, 0xa8, 0x50, 0xa8, 0x68, 0xa8, 0x80, 0xa8, 0xb8, 0xa8, - 0xd0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0x30, 0xa9, 0xc0, 0xa9, - 0x00, 0x00, 0x08, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x18, 0xa0, - 0x48, 0xa0, 0x60, 0xa0, 0xa8, 0xa0, 0xb8, 0xa0, 0xc8, 0xa0, 0xf0, 0xa0, - 0x28, 0xa1, 0x68, 0xa6, 0x78, 0xa6, 0x88, 0xa6, 0x98, 0xa6, 0xe8, 0xa6, - 0xf8, 0xa6, 0x08, 0xa7, 0x18, 0xa7, 0x28, 0xa7, 0x50, 0xa7, 0x60, 0xa7, - 0x70, 0xa7, 0xd8, 0xa7, 0xe8, 0xa7, 0xf8, 0xa7, 0x40, 0xa8, 0x50, 0xa8, - 0x88, 0xa8, 0x98, 0xa8, 0xc0, 0xa8, 0xd0, 0xa8, 0x08, 0xa9, 0x20, 0xa9, - 0x78, 0xa9, 0xe8, 0xaf, 0x00, 0x10, 0x08, 0x00, 0x6c, 0x00, 0x00, 0x00, - 0x00, 0xa0, 0x38, 0xa0, 0x88, 0xa0, 0xd0, 0xa0, 0xe0, 0xa0, 0x68, 0xa2, - 0xb8, 0xa2, 0xc8, 0xa2, 0xf8, 0xa2, 0x30, 0xa3, 0x40, 0xa3, 0x90, 0xa3, - 0xa0, 0xa3, 0xe8, 0xa3, 0xf8, 0xa3, 0x30, 0xa8, 0x48, 0xa8, 0x58, 0xa9, - 0x70, 0xa9, 0x20, 0xaa, 0x38, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xd8, 0xaa, - 0xe8, 0xaa, 0x20, 0xab, 0x30, 0xab, 0x60, 0xab, 0x70, 0xab, 0x88, 0xab, - 0xc8, 0xab, 0xd8, 0xab, 0xf0, 0xab, 0x48, 0xac, 0x58, 0xac, 0x70, 0xac, - 0x88, 0xac, 0xa0, 0xac, 0xd8, 0xac, 0xf0, 0xac, 0x08, 0xad, 0x20, 0xad, - 0xb8, 0xad, 0xd0, 0xad, 0xe8, 0xad, 0x00, 0xae, 0x18, 0xae, 0x30, 0xae, - 0x48, 0xae, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x18, 0xa0, 0x30, 0xa0, 0x70, 0xa0, 0x88, 0xa0, 0xa0, 0xa0, 0xb8, 0xa0, - 0xf8, 0xa0, 0x78, 0xa1, 0x90, 0xa1, 0xa8, 0xa1, 0xc0, 0xa1, 0xd8, 0xa1, - 0xf0, 0xa1, 0x08, 0xa2, 0x20, 0xa2, 0x38, 0xa2, 0x50, 0xa2, 0x68, 0xa2, - 0x80, 0xa2, 0x98, 0xa2, 0xe8, 0xa4, 0x00, 0xa5, 0xf8, 0xa5, 0x10, 0xa6, - 0x28, 0xa6, 0x78, 0xa6, 0x10, 0xa7, 0x90, 0xa7, 0x40, 0xa8, 0x58, 0xa8, - 0x70, 0xa8, 0x88, 0xa8, 0xa0, 0xa8, 0xb8, 0xa8, 0xd0, 0xa8, 0x10, 0xa9, - 0x00, 0x40, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, 0xf0, 0xa6, 0xf8, 0xa6, - 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x00, 0xe0, 0x1e, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x80, 0xa8, 0x88, 0xa8, 0x90, 0xa8, 0x98, 0xa8, - 0xb0, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0xe8, 0xae, 0x00, 0x00, 0x00, 0xd0, 0x63, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x20, 0xa0, 0x00, 0x00, +pub const RELOC_DATA: [u8; 1200] = [ + 0x00, 0x10, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, + 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0x10, 0xa7, 0x28, 0xa7, + 0x48, 0xa7, 0x50, 0xa8, 0x00, 0x20, 0x07, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x58, 0xa0, 0x98, 0xa2, 0xb0, 0xa2, 0xc8, 0xa2, 0xe0, 0xa2, 0xf8, 0xa2, + 0x10, 0xa3, 0x28, 0xa3, 0x40, 0xa3, 0x58, 0xa3, 0x70, 0xa3, 0x88, 0xa3, + 0xa0, 0xa3, 0xb8, 0xa3, 0xc0, 0xa3, 0xc8, 0xa3, 0xd0, 0xa3, 0xd8, 0xa3, + 0xe0, 0xa3, 0xe8, 0xa3, 0xf0, 0xa3, 0xe0, 0xa4, 0xf8, 0xa4, 0x10, 0xa5, + 0x28, 0xa5, 0x40, 0xa5, 0x58, 0xa5, 0x70, 0xa5, 0xa8, 0xa5, 0x28, 0xa8, + 0x40, 0xa8, 0x58, 0xa8, 0x70, 0xa8, 0x88, 0xa8, 0xa0, 0xa8, 0x20, 0xa9, + 0x38, 0xa9, 0x50, 0xa9, 0x68, 0xa9, 0x80, 0xa9, 0x98, 0xa9, 0xb0, 0xa9, + 0xc8, 0xa9, 0xe0, 0xa9, 0x60, 0xaa, 0x78, 0xaa, 0xd8, 0xaa, 0xf0, 0xaa, + 0x08, 0xab, 0xb0, 0xab, 0x08, 0xac, 0x20, 0xac, 0x38, 0xac, 0x00, 0x00, + 0x00, 0x30, 0x07, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x18, 0xa0, + 0x30, 0xa0, 0x70, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, 0x20, 0xa1, 0x60, 0xa1, + 0x70, 0xa1, 0xb8, 0xa1, 0xd0, 0xa1, 0xe8, 0xa1, 0x00, 0xa2, 0x80, 0xa2, + 0x98, 0xa2, 0xb0, 0xa2, 0xa0, 0xa5, 0xb8, 0xa5, 0xd0, 0xa5, 0x90, 0xa6, + 0xc0, 0xa6, 0x60, 0xa7, 0x78, 0xa7, 0x90, 0xa7, 0xa8, 0xa7, 0xc0, 0xa7, + 0xd8, 0xa7, 0xf0, 0xa7, 0x08, 0xa8, 0x20, 0xa8, 0xa0, 0xa8, 0x20, 0xa9, + 0xc0, 0xaf, 0xd8, 0xaf, 0xf0, 0xaf, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, + 0x90, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x20, 0xa0, 0xa8, 0xa0, 0xc0, 0xa0, + 0x00, 0xa1, 0x18, 0xa1, 0x30, 0xa1, 0x48, 0xa1, 0xd0, 0xa1, 0xe8, 0xa1, + 0x70, 0xa4, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xd0, 0xa4, 0xe8, 0xa4, + 0x80, 0xa5, 0x98, 0xa5, 0xb0, 0xa5, 0xc8, 0xa5, 0xe0, 0xa5, 0x20, 0xa7, + 0x58, 0xa8, 0x70, 0xa8, 0xc0, 0xa8, 0x38, 0xa9, 0x50, 0xa9, 0x68, 0xa9, + 0x80, 0xa9, 0x98, 0xa9, 0xb0, 0xa9, 0xc8, 0xa9, 0xe0, 0xa9, 0xf8, 0xa9, + 0x10, 0xaa, 0x28, 0xaa, 0x40, 0xaa, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, + 0xf0, 0xaa, 0x08, 0xab, 0x20, 0xab, 0x38, 0xab, 0x50, 0xab, 0x68, 0xab, + 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, 0xe0, 0xab, 0x30, 0xac, + 0x40, 0xac, 0x98, 0xac, 0xa8, 0xac, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, + 0xd0, 0xae, 0x50, 0xaf, 0x68, 0xaf, 0x80, 0xaf, 0x98, 0xaf, 0xb0, 0xaf, + 0xc8, 0xaf, 0xe0, 0xaf, 0xf8, 0xaf, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, + 0x8c, 0x00, 0x00, 0x00, 0x10, 0xa0, 0x40, 0xa0, 0xe0, 0xa0, 0xf8, 0xa0, + 0x78, 0xa1, 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, 0x40, 0xa2, 0x58, 0xa2, + 0x70, 0xa2, 0xf8, 0xa2, 0x20, 0xa4, 0x30, 0xa4, 0x40, 0xa4, 0xb8, 0xa4, + 0xd0, 0xa4, 0xe8, 0xa4, 0x00, 0xa5, 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, + 0x60, 0xa5, 0x78, 0xa5, 0x90, 0xa5, 0xa8, 0xa5, 0xc0, 0xa5, 0xd8, 0xa5, + 0xf0, 0xa5, 0x08, 0xa6, 0x20, 0xa6, 0xc8, 0xa7, 0xe0, 0xa7, 0xf8, 0xa7, + 0x10, 0xa8, 0x28, 0xa8, 0x40, 0xa8, 0x58, 0xa8, 0x70, 0xa8, 0x88, 0xa8, + 0x10, 0xa9, 0x50, 0xa9, 0x60, 0xa9, 0x20, 0xaa, 0xb0, 0xaa, 0xf0, 0xaa, + 0x38, 0xab, 0xd0, 0xab, 0x18, 0xac, 0x28, 0xac, 0x38, 0xac, 0x48, 0xac, + 0xc8, 0xac, 0xe0, 0xac, 0x60, 0xad, 0x00, 0xae, 0x10, 0xae, 0x20, 0xae, + 0xb8, 0xae, 0xf0, 0xae, 0x08, 0xaf, 0x20, 0xaf, 0x38, 0xaf, 0xb8, 0xaf, + 0xf8, 0xaf, 0x00, 0x00, 0x00, 0x60, 0x07, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x10, 0xa0, 0x50, 0xa1, 0xf0, 0xa1, 0x10, 0xa2, 0xa8, 0xa2, 0xe0, 0xa2, + 0xa0, 0xa3, 0xb8, 0xa3, 0x10, 0xa4, 0x28, 0xa4, 0x98, 0xa4, 0xb0, 0xa4, + 0xc8, 0xa4, 0x00, 0xa5, 0x30, 0xa5, 0x60, 0xa5, 0x78, 0xa5, 0x10, 0xa6, + 0x28, 0xa6, 0x40, 0xa6, 0x58, 0xa6, 0x70, 0xa6, 0x80, 0xa7, 0xe0, 0xa7, + 0x58, 0xa8, 0x68, 0xa9, 0x80, 0xa9, 0x98, 0xa9, 0xb0, 0xa9, 0xc8, 0xa9, + 0x20, 0xaa, 0x38, 0xaa, 0x50, 0xaa, 0x68, 0xac, 0x80, 0xac, 0x98, 0xac, + 0xb0, 0xac, 0xc8, 0xac, 0xe0, 0xac, 0xf8, 0xac, 0x30, 0xad, 0x40, 0xae, + 0x58, 0xae, 0x00, 0x00, 0x00, 0x70, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x58, 0xa1, 0xc0, 0xa1, 0xe0, 0xa1, 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, + 0x40, 0xa2, 0x48, 0xa3, 0xb8, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, + 0x18, 0xa4, 0x30, 0xa4, 0x70, 0xa4, 0x00, 0xa5, 0x98, 0xaf, 0x00, 0x00, + 0x00, 0x80, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0xa5, + 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, 0xe8, 0xa6, + 0x40, 0xa8, 0x50, 0xa8, 0x60, 0xa8, 0x70, 0xa8, 0x80, 0xa8, 0x90, 0xa8, + 0xa0, 0xa8, 0xb0, 0xa8, 0xc0, 0xa8, 0xd0, 0xa8, 0xe0, 0xa8, 0xf0, 0xa8, + 0x00, 0xa9, 0x10, 0xa9, 0x20, 0xa9, 0x30, 0xa9, 0x40, 0xa9, 0x50, 0xa9, + 0x60, 0xa9, 0x70, 0xa9, 0x80, 0xa9, 0x90, 0xa9, 0xa0, 0xa9, 0xb0, 0xa9, + 0xc0, 0xa9, 0xd0, 0xa9, 0xe0, 0xa9, 0xf0, 0xa9, 0x00, 0xaa, 0x10, 0xaa, + 0x20, 0xaa, 0x30, 0xaa, 0x10, 0xac, 0x00, 0x00, 0x00, 0x90, 0x07, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x28, 0xae, 0x80, 0xae, 0xf0, 0xaf, 0x00, 0x00, + 0x00, 0xa0, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x58, 0xa0, + 0x68, 0xa0, 0x78, 0xa0, 0x88, 0xa0, 0x98, 0xa0, 0xc8, 0xa0, 0x00, 0xa1, + 0x48, 0xa1, 0x98, 0xa1, 0xd0, 0xa1, 0xe8, 0xa1, 0x30, 0xa2, 0xb8, 0xa2, + 0xf8, 0xae, 0x88, 0xaf, 0xa0, 0xaf, 0xd8, 0xaf, 0x00, 0xb0, 0x07, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x20, 0xa0, 0x38, 0xa0, 0x50, 0xa0, + 0x68, 0xa0, 0x80, 0xa0, 0x98, 0xa0, 0xb0, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, + 0x20, 0xa1, 0x38, 0xa1, 0x50, 0xa1, 0x88, 0xa1, 0xb8, 0xa1, 0xd0, 0xa1, + 0xe8, 0xa1, 0x00, 0xa2, 0x38, 0xa2, 0x50, 0xa2, 0x68, 0xa2, 0x80, 0xa2, + 0x98, 0xa2, 0xb0, 0xa2, 0x40, 0xa3, 0x88, 0xa9, 0x98, 0xa9, 0xc8, 0xa9, + 0xe0, 0xa9, 0x28, 0xaa, 0x38, 0xaa, 0x48, 0xaa, 0x70, 0xaa, 0xa8, 0xaa, + 0xe8, 0xaf, 0xf8, 0xaf, 0x00, 0xc0, 0x07, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x08, 0xa0, 0x18, 0xa0, 0x68, 0xa0, 0x78, 0xa0, 0x88, 0xa0, 0x98, 0xa0, + 0xa8, 0xa0, 0xd0, 0xa0, 0xe0, 0xa0, 0xf0, 0xa0, 0x58, 0xa1, 0x68, 0xa1, + 0x78, 0xa1, 0xd8, 0xa1, 0x18, 0xa2, 0x68, 0xa2, 0x78, 0xa2, 0xb0, 0xa2, + 0xc0, 0xa2, 0xe8, 0xa2, 0xf8, 0xa2, 0x30, 0xa3, 0x48, 0xa3, 0xa0, 0xa3, + 0x10, 0xaa, 0x28, 0xaa, 0x60, 0xaa, 0xb0, 0xaa, 0xf8, 0xaa, 0x08, 0xab, + 0x18, 0xac, 0x28, 0xac, 0x60, 0xac, 0x70, 0xac, 0xa0, 0xac, 0xb0, 0xac, + 0xc8, 0xac, 0x08, 0xad, 0x18, 0xad, 0x30, 0xad, 0x88, 0xad, 0x98, 0xad, + 0xb0, 0xad, 0xc8, 0xad, 0xe0, 0xad, 0xa8, 0xae, 0xf8, 0xae, 0x08, 0xaf, + 0x38, 0xaf, 0x70, 0xaf, 0x80, 0xaf, 0xd0, 0xaf, 0xe0, 0xaf, 0x00, 0x00, + 0x00, 0xd0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x28, 0xa0, 0x38, 0xa0, + 0x70, 0xa0, 0xf0, 0xa4, 0x08, 0xa5, 0xd0, 0xa5, 0xe8, 0xa5, 0x98, 0xa6, + 0xb0, 0xa6, 0xe8, 0xa6, 0x00, 0xa7, 0x18, 0xa7, 0x30, 0xa7, 0xc8, 0xa7, + 0xe0, 0xa7, 0xf8, 0xa7, 0x10, 0xa8, 0x28, 0xa8, 0x40, 0xa8, 0x58, 0xa8, + 0x28, 0xaa, 0x40, 0xaa, 0x80, 0xaa, 0x98, 0xaa, 0xb0, 0xaa, 0xc8, 0xaa, + 0x08, 0xab, 0x88, 0xab, 0xa0, 0xab, 0xb8, 0xab, 0xd0, 0xab, 0xe8, 0xab, + 0x00, 0xac, 0x18, 0xac, 0x30, 0xac, 0x48, 0xac, 0x60, 0xac, 0x78, 0xac, + 0x90, 0xac, 0xa8, 0xac, 0xf8, 0xae, 0x10, 0xaf, 0x00, 0xe0, 0x07, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x20, 0xa0, 0x38, 0xa0, 0x88, 0xa0, + 0x20, 0xa1, 0xa0, 0xa1, 0x28, 0xa2, 0x80, 0xa2, 0x98, 0xa2, 0xb0, 0xa2, + 0xc8, 0xa2, 0xe0, 0xa2, 0xf8, 0xa2, 0x10, 0xa3, 0x00, 0xf0, 0x07, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0xe0, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, + 0x20, 0xa7, 0x28, 0xa7, 0x00, 0xa0, 0x1e, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x80, 0xa7, 0x90, 0xa8, 0x98, 0xa8, 0xa0, 0xa8, 0xb0, 0xa8, 0x00, 0x00, + 0x00, 0xc0, 0x1f, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xe8, 0xae, 0x00, 0x00, + 0x00, 0x90, 0x63, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x00, 0x00, ]; From 62b0debbb279c398b3eb19cce417ce9ea3709e1d Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Fri, 13 Mar 2026 18:35:32 +0100 Subject: [PATCH 4/8] Add xHCI register definitions for USB controller This commit introduces a new assembly file containing the register definitions for the eXtensible Host Controller Interface (xHCI) as specified in version 1.2 of the specification. The definitions include capability registers, operational registers, command and status bits, port status control, extended capability IDs, and legacy support registers. This addition will facilitate the implementation of USB functionality in the system. --- bootloader/src/storage.rs | 128 ++- bootloader/src/tui/desktop.rs | 3 +- bootloader/src/tui/input.rs | 176 ++-- network/asm/drivers/usb/init.s | 251 ++++- network/asm/drivers/usb/regs.s | 49 + network/src/boot/block_probe.rs | 14 +- network/src/driver/usb_msd/mod.rs | 1059 +++++++++++++++++++++- persistent/src/pe/embedded_reloc_data.rs | 207 +++-- 8 files changed, 1612 insertions(+), 275 deletions(-) create mode 100644 network/asm/drivers/usb/regs.s diff --git a/bootloader/src/storage.rs b/bootloader/src/storage.rs index e569de3a..765fa8b3 100644 --- a/bootloader/src/storage.rs +++ b/bootloader/src/storage.rs @@ -702,6 +702,7 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { // Try each device: skip boot disks (GPT/MBR), use the first blank or HelixFS one. let mut found_data_disk = false; + let mut saw_unimplemented_backend = false; 'device_scan: for i in 0..dev_count { let detected = match &devices[i] { Some(d) => d, @@ -823,6 +824,7 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { log_warn("STORAGE", 825, "SDHCI init failed: I/O error"); } SdhciInitError::NotImplemented => { + saw_unimplemented_backend = true; log_warn("STORAGE", 825, "SDHCI init failed: not implemented"); } } @@ -852,6 +854,7 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { log_warn("STORAGE", 825, "USB-MSD init failed: I/O error"); } UsbMsdInitError::NotImplemented => { + saw_unimplemented_backend = true; log_warn("STORAGE", 825, "USB-MSD init failed: not implemented"); } } @@ -888,9 +891,8 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { continue; } - // This device is NOT a boot disk — use it for HelixFS. - log_ok("STORAGE", 827, "selected data disk"); - found_data_disk = true; + // Candidate selected. We only commit once it proves to hold HelixFS. + log_ok("STORAGE", 827, "selected data-disk candidate"); // try to recover or format helixfs let mut raw_dev = make_raw_block_device(); @@ -916,50 +918,20 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { }; if needs_format { - log_info("STORAGE", 829, "no valid helixfs; formatting disk"); - - let uuid = [ - 0x4Du8, 0x58, 0x52, 0x4F, 0x4F, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, - ]; - spinner_start(); - match morpheus_helix::format::format_helix( - &mut raw_dev, - 0, - STORAGE_REGION_SECTORS, - info.sector_size, - "root", - uuid, - ) { - Ok(_sb) => { - spinner_done(); - log_ok("STORAGE", 830, "format completed"); - } - Err(e) => { - spinner_done(); - let _ = e; - log_error("STORAGE", 831, "format failed"); - BLOCK_DEVICE = None; - continue; - } - } - - // Verify format succeeded - match morpheus_helix::log::recovery::recover_superblock(&mut raw_dev, 0, info.sector_size) - { - Ok(_sb) => {} - Err(e) => { - let _ = e; - log_warn("STORAGE", 832, "superblock readback failed after format"); - } - } + // Bring-up safety: never auto-format random host disks at boot. + // Keep scanning until we find an existing HelixFS root. + log_warn("STORAGE", 829, "no valid helixfs on candidate; skipping disk"); + BLOCK_DEVICE = None; + continue; } else { log_info("STORAGE", 833, "valid helixfs found; mounting"); } + let mut mounted_from_ram = false; let mount_dev = match stage_selected_region_to_ram(info.sector_size) { Some(mem_dev) => { log_ok("STORAGE", 838, "helix partition staged into RAM"); + mounted_from_ram = true; mem_dev } None => { @@ -977,7 +949,67 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { match morpheus_helix::vfs::global::replace_root_device(mount_dev, false) { Ok(()) => { spinner_done(); + let mut root_has_init = root_path_exists("/bin/init"); + if mounted_from_ram && !root_path_exists("/bin/init") { + log_warn( + "STORAGE", + 844, + "RAM-staged root missing /bin/init; remounting directly from media", + ); + spinner_start(); + match morpheus_helix::vfs::global::replace_root_device( + make_raw_block_device(), + false, + ) { + Ok(()) => { + spinner_done(); + if root_path_exists("/bin/init") { + root_has_init = true; + log_ok("STORAGE", 845, "direct-media root remount restored /bin/init"); + } else { + root_has_init = false; + log_warn( + "STORAGE", + 846, + "direct-media root still missing /bin/init", + ); + } + } + Err(_) => { + spinner_done(); + root_has_init = false; + log_warn("STORAGE", 847, "direct-media root remount failed"); + } + } + } + + if !root_has_init { + log_warn( + "STORAGE", + 851, + "candidate root rejected: /bin/init missing; scanning next disk", + ); + BLOCK_DEVICE = None; + continue; + } + PERSISTENT_READY = true; + found_data_disk = true; + if root_path_exists("/bin/init") { + log_ok("STORAGE", 848, "root check: /bin/init present"); + } else { + log_warn("STORAGE", 848, "root check: /bin/init missing"); + } + if root_path_exists("/bin/compd") { + log_ok("STORAGE", 849, "root check: /bin/compd present"); + } else { + log_warn("STORAGE", 849, "root check: /bin/compd missing"); + } + if root_path_exists("/bin/shelld") { + log_ok("STORAGE", 850, "root check: /bin/shelld present"); + } else { + log_warn("STORAGE", 850, "root check: /bin/shelld missing"); + } log_ok("STORAGE", 834, "persistent root filesystem mounted at /"); break 'device_scan; } @@ -998,9 +1030,25 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { 839, "runtime persistent backends currently support AHCI/VirtIO/SDHCI (USB/NVMe pending)", ); + if saw_unimplemented_backend { + log_error( + "STORAGE", + 852, + "boot medium backend is scaffold-only (SDHCI/USB-MSD not implemented); /bin/init will be unavailable", + ); + } } } +fn root_path_exists(path: &str) -> bool { + let fs = match unsafe { morpheus_helix::vfs::global::fs_global_mut() } { + Some(f) => f, + None => return false, + }; + + morpheus_helix::vfs::vfs_stat(&fs.mount_table, path).is_ok() +} + /// Whether persistent storage is active (vs RAM-disk fallback). pub fn is_persistent() -> bool { unsafe { PERSISTENT_READY } diff --git a/bootloader/src/tui/desktop.rs b/bootloader/src/tui/desktop.rs index 0a38c200..618e03bc 100644 --- a/bootloader/src/tui/desktop.rs +++ b/bootloader/src/tui/desktop.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use morpheus_display::types::FramebufferInfo; -use morpheus_hwinit::serial::{clear_live_console_hook, log_error, log_info, log_ok, puts}; +use morpheus_hwinit::serial::{log_error, log_info, log_ok, puts}; use super::input::Keyboard; @@ -88,7 +88,6 @@ fn show_boot_log_screen(keyboard: &mut Keyboard) { puts("Press any key to launch msh..."); keyboard.wait_for_key(); puts("\n"); - clear_live_console_hook(); } pub fn run_desktop(_display_info: &FramebufferInfo) -> ! { diff --git a/bootloader/src/tui/input.rs b/bootloader/src/tui/input.rs index 00cc7e6f..7464b69e 100644 --- a/bootloader/src/tui/input.rs +++ b/bootloader/src/tui/input.rs @@ -362,116 +362,138 @@ impl Keyboard { } unsafe fn init_controller(&mut self) { - // i8042 bring-up on real hardware is messy; probe and normalize hard. + // Full i8042+keyboard reinit. No partial-trust path. morpheus_hwinit::serial::log_info("INPUT", 935, "keyboard controller init begin"); + self.aux_as_kbd = false; + self.initialized = false; - asm_ps2_flush(); - - // Disable port 1 while we reconfigure. - asm_ps2_write_cmd(0xAD); // disable port 1 scanning + // 1) Hard-stop both channels and drain everything. + asm_ps2_write_cmd(0xAD); // disable keyboard port + Self::io_delay(); + asm_ps2_write_cmd(0xA7); // disable aux port + Self::io_delay(); + Self::drain_all(512); + + // 2) Put controller in known config: IRQs off, clocks on, translation off. + asm_ps2_write_cmd(0x20); + let mut cfg = self.wait_kbd_byte(100_000).unwrap_or(0x00); + cfg &= !0x43; // clear IRQ1, IRQ12, translation + cfg &= !0x30; // enable both clocks (bits are disable flags) + asm_ps2_write_cmd(0x60); + asm_ps2_write_data(cfg); Self::io_delay(); - asm_ps2_flush(); + Self::drain_all(128); - // Controller self-test (0xAA -> 0x55 expected). + // 3) Controller and interface tests. asm_ps2_write_cmd(0xAA); - let ctl_self = self.wait_response_tagged(100_000).map(|(_, b)| b); - if ctl_self != Some(0x55) { - morpheus_hwinit::serial::log_warn("INPUT", 936, "8042 self-test unexpected response"); + let ctl_ok = self.wait_kbd_byte(200_000) == Some(0x55); + if !ctl_ok { + morpheus_hwinit::serial::log_warn("INPUT", 936, "8042 self-test failed"); } - // Interface test for first PS/2 port (0xAB -> 0x00 expected). + // Self-test may rewrite config on some controllers. Re-assert config. + asm_ps2_write_cmd(0x60); + asm_ps2_write_data(cfg); + Self::io_delay(); + asm_ps2_write_cmd(0xAB); - let port1_test = self.wait_response_tagged(100_000).map(|(_, b)| b); - if port1_test != Some(0x00) { - morpheus_hwinit::serial::log_warn("INPUT", 937, "8042 port1 test unexpected response"); + let port1_ok = self.wait_kbd_byte(100_000) == Some(0x00); + if !port1_ok { + morpheus_hwinit::serial::log_warn("INPUT", 937, "8042 port1 test failed"); } - asm_ps2_write_cmd(0x20); // read config byte - let config = self.wait_response_tagged(50_000).map(|(_, b)| b).unwrap_or(0x45); - let new_config = (config | 0x41) & !0x30; // translation on, IRQ1 on, both PS/2 clocks enabled - - asm_ps2_write_cmd(0x60); // write config byte - asm_ps2_write_data(new_config); + // 4) Enable keyboard port only for deterministic bring-up. + asm_ps2_write_cmd(0xAE); Self::io_delay(); - - asm_ps2_write_cmd(0xAE); // re-enable port 1 - Self::io_delay(); - asm_ps2_write_cmd(0xA8); // keep aux port enabled too (some firmware paths tag keyboard via AUX) + asm_ps2_write_cmd(0xA7); Self::io_delay(); - - // Reset keyboard and wait for BAT completion. - asm_ps2_write_data(0xFF); - let ack_ff = self.wait_response_tagged(100_000); - let bat = self.wait_response_tagged(200_000); - if matches!(ack_ff, Some((0x300, _))) || matches!(bat, Some((0x300, _))) { - self.aux_as_kbd = true; - morpheus_hwinit::serial::log_warn("INPUT", 939, "keyboard bytes tagged as AUX; enabling fallback"); + Self::drain_all(128); + + // 5) Reset keyboard with retries. + let mut reset_ok = false; + for _ in 0..3 { + asm_ps2_write_data(0xFF); + let ack = self.wait_kbd_byte(150_000); + let bat = self.wait_kbd_byte(300_000); + if ack == Some(0xFA) && bat == Some(0xAA) { + reset_ok = true; + break; + } + Self::drain_all(128); + asm_ps2_write_cmd(0xAD); + Self::io_delay(); + asm_ps2_write_cmd(0xAE); + Self::io_delay(); } - let ack_ff_b = ack_ff.map(|(_, b)| b); - let bat_b = bat.map(|(_, b)| b); - if ack_ff_b != Some(0xFA) || bat_b != Some(0xAA) { - self.aux_as_kbd = true; - morpheus_hwinit::serial::log_warn( - "INPUT", - 939, - "keyboard reset/BAT failed; forcing AUX-tag keyboard fallback", - ); - morpheus_hwinit::serial::log_warn("INPUT", 938, "keyboard reset/BAT incomplete"); + if !reset_ok { + morpheus_hwinit::serial::log_warn("INPUT", 938, "keyboard reset/BAT failed after retries"); } - // Force keyboard scan set 1 so decoder matches real hardware output. + // 6) Program known scan behavior (set 1) and verify. + let mut scan_ok = true; + asm_ps2_write_data(0xF5); // disable scanning - let _ = self.wait_response_tagged(100_000); // ACK best-effort - asm_ps2_write_data(0xF6); // set defaults - let _ = self.wait_response_tagged(100_000); - asm_ps2_write_data(0xF0); // set/get scancode set command - let ack_f0 = self.wait_response_tagged(100_000); + scan_ok &= self.wait_kbd_byte(100_000) == Some(0xFA); + + asm_ps2_write_data(0xF6); // defaults + scan_ok &= self.wait_kbd_byte(100_000) == Some(0xFA); + + asm_ps2_write_data(0xF0); // set scancode cmd + scan_ok &= self.wait_kbd_byte(100_000) == Some(0xFA); asm_ps2_write_data(0x01); // set 1 - let ack_set1 = self.wait_response_tagged(100_000); + scan_ok &= self.wait_kbd_byte(100_000) == Some(0xFA); + + // Query to verify set-1 actually latched. + asm_ps2_write_data(0xF0); + scan_ok &= self.wait_kbd_byte(100_000) == Some(0xFA); + asm_ps2_write_data(0x00); + scan_ok &= self.wait_kbd_byte(100_000) == Some(0xFA); + let set_id = self.wait_kbd_byte(100_000); + scan_ok &= set_id == Some(0x01); asm_ps2_write_data(0xF4); // enable scanning - let ack_f4 = self.wait_response_tagged(100_000); - - if matches!(ack_f0, Some((0x300, _))) - || matches!(ack_set1, Some((0x300, _))) - || matches!(ack_f4, Some((0x300, _))) - { - self.aux_as_kbd = true; - morpheus_hwinit::serial::log_warn("INPUT", 939, "keyboard bytes tagged as AUX; enabling fallback"); - } + let f4_ack = self.wait_kbd_byte(100_000); + scan_ok &= f4_ack == Some(0xFA); - if ack_f0.map(|(_, b)| b) != Some(0xFA) - || ack_set1.map(|(_, b)| b) != Some(0xFA) - || ack_f4.map(|(_, b)| b) != Some(0xFA) - { - self.aux_as_kbd = true; - morpheus_hwinit::serial::log_warn( - "INPUT", - 939, - "set-1 handshake failed; forcing AUX-tag keyboard fallback", - ); - morpheus_hwinit::serial::log_warn("INPUT", 931, "keyboard set-1 handshake incomplete"); + if !scan_ok { + morpheus_hwinit::serial::log_warn("INPUT", 931, "keyboard scan-set programming failed"); } - asm_ps2_flush(); + Self::drain_all(128); - self.initialized = true; - morpheus_hwinit::serial::log_ok("INPUT", 930, "PS/2 keyboard ready"); + self.initialized = ctl_ok && port1_ok && reset_ok && scan_ok; + if self.initialized { + morpheus_hwinit::serial::log_ok("INPUT", 930, "PS/2 keyboard ready (full reset path)"); + } else { + morpheus_hwinit::serial::log_warn("INPUT", 941, "PS/2 keyboard init failed (full reset path)"); + } } - /// Spin-wait for a response byte from port 0x60, with bounded timeout. - unsafe fn wait_response_tagged(&mut self, max_spins: u32) -> Option<(u16, u8)> { + unsafe fn wait_kbd_byte(&mut self, max_spins: u32) -> Option { for _ in 0..max_spins { let r = asm_ps2_poll_any(); - let tag = (r & 0x300) as u16; - if tag == 0x100 || tag == 0x300 { - return Some((tag, (r & 0xFF) as u8)); + let tag = r & 0x300; + if tag == 0x100 { + return Some((r & 0xFF) as u8); + } + if tag == 0x300 { + // mouse/aux byte while doing keyboard bring-up; drop it. + continue; } core::hint::spin_loop(); } None } + unsafe fn drain_all(max_reads: u32) { + for _ in 0..max_reads { + if asm_ps2_poll_any() == 0 { + break; + } + core::hint::spin_loop(); + } + } + /// Tiny delay between commands (write to unused port 0x80, like PIC does). #[inline(always)] unsafe fn io_delay() { diff --git a/network/asm/drivers/usb/init.s b/network/asm/drivers/usb/init.s index 475c0a7f..e03b0c3b 100644 --- a/network/asm/drivers/usb/init.s +++ b/network/asm/drivers/usb/init.s @@ -1,35 +1,250 @@ ; ═══════════════════════════════════════════════════════════════════════════ -; USB host controller probe/reset primitives (scaffold) +; USB xHCI host controller primitives — brutal init edition ; ABI: Microsoft x64 (RCX, RDX, R8, R9, stack) ; ═══════════════════════════════════════════════════════════════════════════ +section .data + %include "asm/drivers/usb/regs.s" + section .text extern asm_mmio_read8 +extern asm_mmio_read16 +extern asm_mmio_read32 +extern asm_mmio_write32 extern asm_tsc_read +extern asm_bar_mfence global asm_usb_host_probe -global asm_usb_host_reset +global asm_xhci_controller_reset +global asm_xhci_bios_handoff -; RCX = mmio_base -; EAX = 0 success, 1 invalid controller signature +; ─────────────────────────────────────────────────────────────────────────── +; asm_usb_host_probe +; ─────────────────────────────────────────────────────────────────────────── +; RCX = mmio_base (BAR0) +; Returns: +; EAX bits 7:0 = CAPLENGTH +; EAX bits 31:16 = HCIVERSION (should be >= 0x0100) +; EAX = 0 if bar reads back all-F (unmapped/dead) asm_usb_host_probe: - sub rsp, 32 - ; Read capability length register (common on xHCI-compatible mmio layout). - call asm_mmio_read8 - test al, al - jz .bad + sub rsp, 40 + push rbx + + mov rbx, rcx + + ; read dword at mmio_base+0: [7:0]=CAPLENGTH, [31:16]=HCIVERSION + call asm_mmio_read32 + cmp eax, 0xFFFFFFFF + je .probe_dead + ; sanity: CAPLENGTH must be ≥ 0x20 (minimum xHCI cap region) + movzx edx, al + cmp edx, 0x20 + jb .probe_dead + ; HCIVERSION must be ≥ 0x0100 + shr eax, 16 + cmp ax, 0x0100 + jb .probe_dead + + ; reconstruct: low byte = CAPLENGTH, high half = HCIVERSION + shl eax, 16 + or eax, edx + jmp .probe_out + +.probe_dead: xor eax, eax - jmp .out -.bad: - mov eax, 1 -.out: - add rsp, 32 +.probe_out: + pop rbx + add rsp, 40 ret -; RCX = mmio_base, RDX = tsc_freq -; EAX = 0 success (stub reset sequence placeholder) -asm_usb_host_reset: - ; Reserved for full xHCI reset sequence in next phase. +; ─────────────────────────────────────────────────────────────────────────── +; asm_xhci_bios_handoff +; ─────────────────────────────────────────────────────────────────────────── +; Claim xHCI from BIOS/SMM via USBLEGSUP extended capability. +; RCX = mmio_base +; RDX = hccparams1 (from mmio_base + 0x10) +; R8 = tsc_freq +; Returns: EAX = 0 success (or no legacy cap), 1 timeout waiting BIOS release +asm_xhci_bios_handoff: + push rbx + push r12 + push r13 + push r14 + sub rsp, 40 + + mov r12, rcx ; mmio_base + mov r14, r8 ; tsc_freq + + ; extended capability pointer from HCCPARAMS1 bits 31:16 (dword offset) + mov eax, edx + shr eax, 16 + and eax, 0xFFFF + shl eax, 2 ; *4 = byte offset from mmio_base + test eax, eax + jz .bios_none ; no extended caps + + ; walk extended capability list looking for ID=1 (legacy support) + lea r13, [r12 + rax] ; ext_cap_ptr +.bios_walk: + mov rcx, r13 + call asm_mmio_read32 + movzx edx, al ; cap ID + cmp dl, XHCI_EXT_CAP_LEGACY + je .bios_found + + ; next pointer = bits 15:8, shift left 2 + mov ecx, eax + shr ecx, 8 + and ecx, 0xFF + test ecx, ecx + jz .bios_none ; end of list + shl ecx, 2 + add r13, rcx + jmp .bios_walk + +.bios_found: + ; USBLEGSUP at r13. Set OS_OWNED bit (byte at offset +3) + mov rcx, r13 + call asm_mmio_read32 + or eax, XHCI_LEGSUP_OS_OWNED + mov edx, eax + mov rcx, r13 + call asm_mmio_write32 + call asm_bar_mfence + + ; wait for BIOS_OWNED to clear (1 second timeout because firmware is slow) + call asm_tsc_read + mov rbx, rax +.bios_wait: + mov rcx, r13 + call asm_mmio_read32 + test eax, XHCI_LEGSUP_BIOS_OWNED + jz .bios_claimed + call asm_tsc_read + sub rax, rbx + cmp rax, r14 ; 1 second = tsc_freq ticks + jb .bios_wait + + ; timeout — force it. clear BIOS bit, keep OS bit + mov rcx, r13 + call asm_mmio_read32 + and eax, ~XHCI_LEGSUP_BIOS_OWNED + or eax, XHCI_LEGSUP_OS_OWNED + mov edx, eax + mov rcx, r13 + call asm_mmio_write32 + call asm_bar_mfence + + ; also nuke the legacy control/status dword at offset +4 + ; disable all SMI sources so BIOS can't interfere + lea rcx, [r13 + 4] + xor edx, edx + call asm_mmio_write32 + call asm_bar_mfence + +.bios_claimed: +.bios_none: + xor eax, eax + add rsp, 40 + pop r14 + pop r13 + pop r12 + pop rbx + ret + +; ─────────────────────────────────────────────────────────────────────────── +; asm_xhci_controller_reset +; ─────────────────────────────────────────────────────────────────────────── +; RCX = op_base (mmio_base + CAPLENGTH) +; RDX = tsc_freq +; Returns: EAX = 0 success, 1 halt timeout, 2 reset timeout, 3 CNR timeout +; +; brutal reset: nuke interrupts, force-stop, HCRST, wait CNR. +; 1 second timeout per phase because real hardware is dramatic. +asm_xhci_controller_reset: + push rbx + push r12 + push r13 + push r14 + sub rsp, 40 + + mov r12, rcx ; op_base + mov r13, rdx ; tsc_freq + mov r14, r13 ; 1 second timeout = tsc_freq ticks + + ; ── step 0: nuke USBCMD — clear RS, INTE, everything ── + lea rcx, [r12 + XHCI_OP_USBCMD] + xor edx, edx + call asm_mmio_write32 + call asm_bar_mfence + + ; ── step 1: wait USBSTS.HCH (halted) ── + call asm_tsc_read + mov rbx, rax +.wait_halt: + lea rcx, [r12 + XHCI_OP_USBSTS] + call asm_mmio_read32 + test eax, XHCI_STS_HCH + jnz .halted + call asm_tsc_read + sub rax, rbx + cmp rax, r14 + jb .wait_halt + ; timeout — try HCRST anyway, some controllers only halt via reset + jmp .do_reset + +.halted: + ; ── clear all pending status bits ── + lea rcx, [r12 + XHCI_OP_USBSTS] + mov edx, 0xFFFFFFFF + call asm_mmio_write32 + call asm_bar_mfence + +.do_reset: + ; ── step 2: HCRST ── + lea rcx, [r12 + XHCI_OP_USBCMD] + mov edx, XHCI_CMD_HCRST + call asm_mmio_write32 + call asm_bar_mfence + + call asm_tsc_read + mov rbx, rax +.wait_reset: + lea rcx, [r12 + XHCI_OP_USBCMD] + call asm_mmio_read32 + test eax, XHCI_CMD_HCRST + jz .reset_done + call asm_tsc_read + sub rax, rbx + cmp rax, r14 + jb .wait_reset + mov eax, 2 + jmp .out + +.reset_done: + ; ── step 3: wait CNR clear ── + call asm_tsc_read + mov rbx, rax +.wait_cnr: + lea rcx, [r12 + XHCI_OP_USBSTS] + call asm_mmio_read32 + test eax, XHCI_STS_CNR + jz .ready + call asm_tsc_read + sub rax, rbx + cmp rax, r14 + jb .wait_cnr + mov eax, 3 + jmp .out + +.ready: xor eax, eax + +.out: + add rsp, 40 + pop r14 + pop r13 + pop r12 + pop rbx ret diff --git a/network/asm/drivers/usb/regs.s b/network/asm/drivers/usb/regs.s new file mode 100644 index 00000000..30f90f32 --- /dev/null +++ b/network/asm/drivers/usb/regs.s @@ -0,0 +1,49 @@ +; xHCI register definitions (eXtensible Host Controller Interface Spec 1.2) + +%ifndef XHCI_REGS_INCLUDED +%define XHCI_REGS_INCLUDED + +; ─── Capability Registers (offset from BAR0) ───────────────────────────── +XHCI_CAP_CAPLENGTH equ 0x00 +XHCI_CAP_HCIVERSION equ 0x02 +XHCI_CAP_HCSPARAMS1 equ 0x04 +XHCI_CAP_HCSPARAMS2 equ 0x08 +XHCI_CAP_HCCPARAMS1 equ 0x10 +XHCI_CAP_DBOFF equ 0x14 +XHCI_CAP_RTSOFF equ 0x18 + +; ─── Operational Registers (offset from op_base = BAR0 + CAPLENGTH) ────── +XHCI_OP_USBCMD equ 0x00 +XHCI_OP_USBSTS equ 0x04 +XHCI_OP_PAGESIZE equ 0x08 +XHCI_OP_CRCR_LO equ 0x18 +XHCI_OP_CRCR_HI equ 0x1C +XHCI_OP_DCBAAP_LO equ 0x30 +XHCI_OP_DCBAAP_HI equ 0x34 +XHCI_OP_CONFIG equ 0x38 + +; ─── USBCMD bits ───────────────────────────────────────────────────────── +XHCI_CMD_RS equ (1 << 0) +XHCI_CMD_HCRST equ (1 << 1) +XHCI_CMD_INTE equ (1 << 2) + +; ─── USBSTS bits ───────────────────────────────────────────────────────── +XHCI_STS_HCH equ (1 << 0) +XHCI_STS_CNR equ (1 << 11) + +; ─── PORTSC (at op_base + 0x400 + port*0x10) ───────────────────────────── +XHCI_PORTSC_CCS equ (1 << 0) +XHCI_PORTSC_PED equ (1 << 1) +XHCI_PORTSC_PR equ (1 << 4) +XHCI_PORTSC_PP equ (1 << 9) +XHCI_PORTSC_PRC equ (1 << 21) + +; ─── xHCI Extended Capability IDs ──────────────────────────────────────── +XHCI_EXT_CAP_LEGACY equ 1 +XHCI_EXT_CAP_PROTOCOL equ 2 + +; ─── USBLEGSUP register (legacy support handoff) ───────────────────────── +XHCI_LEGSUP_BIOS_OWNED equ (1 << 16) +XHCI_LEGSUP_OS_OWNED equ (1 << 24) + +%endif diff --git a/network/src/boot/block_probe.rs b/network/src/boot/block_probe.rs index a0cfc0bd..d9fc4433 100644 --- a/network/src/boot/block_probe.rs +++ b/network/src/boot/block_probe.rs @@ -74,8 +74,10 @@ const VIRTIO_BLK_DEVICE_MODERN: u16 = 0x1042; /// We compare against `(class_code >> 8) & 0xFFFF`, which yields /// subclass:prog_if (not class:subclass). const PCI_CLASS_SATA_AHCI: u32 = 0x0601; -/// PCI subclass/prog-if for SD Host Controller (SDHCI): 0x05/0x01. -const PCI_CLASS_SDHCI: u32 = 0x0501; +/// PCI class/subclass for SD Host Controller: 0x08/0x05. +/// +/// Prog-if differs across controller revisions, so do not pin it to one value. +const PCI_CLASS_SUBCLASS_SDHCI: u32 = 0x0805; /// PCI subclass/prog-if for USB xHCI: 0x03/0x30. const PCI_CLASS_USB_XHCI: u32 = 0x0330; @@ -305,8 +307,8 @@ pub fn scan_all_block_devices() -> ([Option; MAX_BLOCK_DEVI continue; } let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); - let class = (class_code >> 8) & 0xFFFF; - if class != PCI_CLASS_SDHCI { + let class_subclass = (class_code >> 16) & 0xFFFF; + if class_subclass != PCI_CLASS_SUBCLASS_SDHCI { continue; } let device_id = pci_cfg_read16(addr, offset::DEVICE_ID); @@ -504,8 +506,8 @@ pub fn find_sdhci_controller() -> Option { } let class_code = pci_cfg_read32(addr, offset::CLASS_CODE); - let class = (class_code >> 8) & 0xFFFF; - if class != PCI_CLASS_SDHCI { + let class_subclass = (class_code >> 16) & 0xFFFF; + if class_subclass != PCI_CLASS_SUBCLASS_SDHCI { continue; } diff --git a/network/src/driver/usb_msd/mod.rs b/network/src/driver/usb_msd/mod.rs index 000d775a..540cef0b 100644 --- a/network/src/driver/usb_msd/mod.rs +++ b/network/src/driver/usb_msd/mod.rs @@ -1,17 +1,140 @@ -//! USB mass-storage block driver scaffold. +//! USB mass-storage block driver — xHCI host + BOT transport + SCSI read. //! -//! This module reserves the BlockDriver interface for the future ASM-backed -//! USB host + mass-storage transport path (read-focused milestone first). +//! Finds the first USB mass storage device on the xHCI bus, initialises it +//! via Bulk-Only Transport, and exposes SCSI READ(10) through BlockDriver. +//! Read-only; write returns Unsupported. +use crate::asm::core::mmio; +use crate::asm::core::tsc; use crate::driver::block_traits::{ BlockCompletion, BlockDeviceInfo, BlockDriver, BlockDriverInit, BlockError, }; +// ═══════════════════════════════════════════════════════════════════════════ +// ASM externs +// ═══════════════════════════════════════════════════════════════════════════ + extern "win64" { + /// Reads CAPLENGTH + HCIVERSION. 0 = dead controller. + /// Low byte = CAPLENGTH, bits 31:16 = HCIVERSION. fn asm_usb_host_probe(mmio_base: u64) -> u32; - fn asm_usb_host_reset(mmio_base: u64, tsc_freq: u64) -> u32; + /// Stop + HCRST + wait CNR. 0 = ok, 1/2/3 = timeout at halt/reset/cnr. + fn asm_xhci_controller_reset(op_base: u64, tsc_freq: u64) -> u32; + /// Walk extended caps, claim ownership from BIOS/SMM. 0 = ok. + fn asm_xhci_bios_handoff(mmio_base: u64, hccparams1: u64, tsc_freq: u64) -> u32; } +// ═══════════════════════════════════════════════════════════════════════════ +// xHCI register offsets (operational, from op_base) +// ═══════════════════════════════════════════════════════════════════════════ + +const OP_USBCMD: u64 = 0x00; +const OP_USBSTS: u64 = 0x04; +const OP_CRCR: u64 = 0x18; +const OP_DCBAAP: u64 = 0x30; +const OP_CONFIG: u64 = 0x38; + +const PORT_REG_BASE: u64 = 0x400; +const PORT_REG_STRIDE: u64 = 0x10; + +// interrupter 0 offsets from rt_base +const IR0_IMAN: u64 = 0x20; +const IR0_IMOD: u64 = 0x24; +const IR0_ERSTSZ: u64 = 0x28; +const IR0_ERSTBA: u64 = 0x30; +const IR0_ERDP: u64 = 0x38; + +// USBCMD / USBSTS bits +const CMD_RS: u32 = 1 << 0; +const CMD_INTE: u32 = 1 << 2; +const STS_HCH: u32 = 1 << 0; + +// PORTSC bits +const PORTSC_CCS: u32 = 1 << 0; +const PORTSC_PED: u32 = 1 << 1; +const PORTSC_PR: u32 = 1 << 4; +const PORTSC_PP: u32 = 1 << 9; +const PORTSC_PRC: u32 = 1 << 21; +// RW1C mask: bits 17-23 must be written 0 to preserve, 1 to clear +const PORTSC_RW1C: u32 = 0x00FE_0000; +const PORTSC_SPEED_SHIFT: u32 = 10; + +// ═══════════════════════════════════════════════════════════════════════════ +// TRB types (pre-shifted to bits 15:10) +// ═══════════════════════════════════════════════════════════════════════════ + +const TRB_NORMAL: u32 = 1 << 10; +const TRB_SETUP: u32 = 2 << 10; +const TRB_DATA: u32 = 3 << 10; +const TRB_STATUS: u32 = 4 << 10; +const TRB_LINK: u32 = 6 << 10; +const TRB_ENABLE_SLOT: u32 = 9 << 10; +const TRB_DISABLE_SLOT: u32 = 10 << 10; +const TRB_ADDRESS_DEV: u32 = 11 << 10; +const TRB_CONFIGURE_EP: u32 = 12 << 10; +const TRB_XFER_EVENT: u32 = 32 << 10; +const TRB_CMD_COMPLETE: u32 = 33 << 10; + +// TRB control bits +const TRB_TC: u32 = 1 << 1; +const TRB_ISP: u32 = 1 << 2; +const TRB_IOC: u32 = 1 << 5; +const TRB_IDT: u32 = 1 << 6; +const TRB_DIR_IN: u32 = 1 << 16; +const TRB_TRT_IN: u32 = 3 << 16; + +const TRB_TYPE_MASK: u32 = 0x3F << 10; + +// ═══════════════════════════════════════════════════════════════════════════ +// USB / SCSI / BOT constants +// ═══════════════════════════════════════════════════════════════════════════ + +const USB_CLASS_MASS_STORAGE: u8 = 0x08; +const USB_SUBCLASS_SCSI: u8 = 0x06; +const USB_PROTOCOL_BOT: u8 = 0x50; + +const CBW_SIG: u32 = 0x4342_5355; +const CSW_SIG: u32 = 0x5342_5355; +const SCSI_TEST_UNIT_READY: u8 = 0x00; +const SCSI_READ_CAPACITY_10: u8 = 0x25; +const SCSI_READ_10: u8 = 0x28; + +// ═══════════════════════════════════════════════════════════════════════════ +// DMA region layout — all offsets 64-byte aligned inside a 64KB-aligned buf +// ═══════════════════════════════════════════════════════════════════════════ + +const DMA_SIZE: usize = 65536; +const CMD_RING_LEN: u8 = 32; +const EVT_RING_LEN: u8 = 32; +const XFER_RING_LEN: u8 = 16; + +const OFF_DCBAA: usize = 0x0000; // 2KB +const OFF_CMD_RING: usize = 0x1000; // 512B +const OFF_EVT_RING: usize = 0x1200; // 512B +const OFF_ERST: usize = 0x1400; // 16B +const OFF_OUT_CTX: usize = 0x2000; // 2KB (supports CSZ=1) +const OFF_IN_CTX: usize = 0x3000; // 2.5KB +const OFF_XFER_EP0: usize = 0x4000; // 256B +const OFF_XFER_BOUT: usize = 0x4100; // 256B +const OFF_XFER_BIN: usize = 0x4200; // 256B +const OFF_CBW: usize = 0x4400; // 64B +const OFF_CSW: usize = 0x4440; // 64B +const OFF_DESC: usize = 0x4480; // 256B +const OFF_DATA: usize = 0x5000; // 4KB sector bounce buffer (one page, no 64KB boundary crossing) +const DATA_BUF_SIZE: usize = 4096; +const OFF_SCRATCH_ARR: usize = 0x7000; // 64B +const OFF_SCRATCH_PG: usize = 0x8000; // up to 8 × 4KB pages +const MAX_SCRATCH: usize = 8; + +#[repr(C, align(4096))] +struct XhciDma([u8; DMA_SIZE]); + +static mut XHCI_DMA: XhciDma = XhciDma([0u8; DMA_SIZE]); + +// ═══════════════════════════════════════════════════════════════════════════ +// Public types (kept wire-compatible with scaffold) +// ═══════════════════════════════════════════════════════════════════════════ + /// USB mass-storage configuration. #[derive(Debug, Clone)] pub struct UsbMsdConfig { @@ -40,47 +163,890 @@ impl core::fmt::Display for UsbMsdInitError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::InvalidConfig => write!(f, "Invalid USB MSD configuration"), - Self::ControllerInitFailed => write!(f, "USB controller init failed"), + Self::ControllerInitFailed => write!(f, "USB xHCI controller init failed"), Self::DeviceEnumerationFailed => write!(f, "USB device enumeration failed"), - Self::TransportInitFailed => write!(f, "USB mass-storage transport init failed"), - Self::NoMedia => write!(f, "USB mass-storage media not present"), - Self::CommandTimeout => write!(f, "USB mass-storage command timeout"), - Self::IoError => write!(f, "USB mass-storage I/O error"), + Self::TransportInitFailed => write!(f, "USB BOT transport init failed"), + Self::NoMedia => write!(f, "No USB mass-storage device found"), + Self::CommandTimeout => write!(f, "USB command timeout"), + Self::IoError => write!(f, "USB I/O error"), Self::NotImplemented => write!(f, "USB mass-storage driver not implemented yet"), } } } -/// USB mass-storage driver state (scaffold). +// ═══════════════════════════════════════════════════════════════════════════ +// Transfer ring identifier +// ═══════════════════════════════════════════════════════════════════════════ + +#[derive(Clone, Copy)] +enum Ring { + Ep0, + BulkOut, + BulkIn, +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Driver state +// ═══════════════════════════════════════════════════════════════════════════ + pub struct UsbMsdDriver { - mmio_base: u64, - _tsc_freq: u64, + // xHCI register bases + op_base: u64, + rt_base: u64, + db_base: u64, + tsc_freq: u64, + max_ports: u8, + ctx_size: u8, // 32 or 64 + + dma_base: u64, + + // ring producer/consumer state + cmd_enq: u8, + cmd_cycle: u8, + evt_deq: u8, + evt_cycle: u8, + ep0_enq: u8, + ep0_cycle: u8, + bout_enq: u8, + bout_cycle: u8, + bin_enq: u8, + bin_cycle: u8, + + // USB device + slot_id: u8, + dci_bulk_in: u8, + dci_bulk_out: u8, + info: BlockDeviceInfo, + last_completion: Option, + bot_tag: u32, } +// ═══════════════════════════════════════════════════════════════════════════ +// Volatile helpers (DMA RAM, NOT mmio) +// ═══════════════════════════════════════════════════════════════════════════ + +#[inline(always)] +unsafe fn vr32(a: u64) -> u32 { + core::ptr::read_volatile(a as *const u32) +} +#[inline(always)] +unsafe fn vw32(a: u64, v: u32) { + core::ptr::write_volatile(a as *mut u32, v); +} +#[inline(always)] +unsafe fn vw64(a: u64, v: u64) { + vw32(a, v as u32); + vw32(a + 4, (v >> 32) as u32); +} + +/// Write a TRB at `base + idx*16`. Control word (with cycle bit) written last. +#[inline(always)] +unsafe fn write_trb(base: u64, idx: usize, param: u64, status: u32, ctrl: u32) { + let a = base + (idx as u64) * 16; + vw32(a, param as u32); + vw32(a + 4, (param >> 32) as u32); + vw32(a + 8, status); + vw32(a + 12, ctrl); +} + +// busy-wait delay using TSC. ms=0 is a no-op. +#[inline(always)] +unsafe fn tsc_delay(tsc_freq: u64, ms: u64) { + if ms == 0 { + return; + } + let ticks = tsc_freq / 1000 * ms; + let start = tsc::read_tsc(); + while tsc::read_tsc().wrapping_sub(start) < ticks { + core::hint::spin_loop(); + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Implementation +// ═══════════════════════════════════════════════════════════════════════════ + impl UsbMsdDriver { - /// Create a new USB mass-storage driver instance. - /// - /// Phase-1 scaffold returns NotImplemented until ASM primitives land. + // ─── public entry point ────────────────────────────────────────────── + + /// Initialise xHCI controller, enumerate first USB mass-storage device, + /// run SCSI READ CAPACITY. Returns a ready-to-read driver or an error. pub unsafe fn new(mmio_base: u64, config: UsbMsdConfig) -> Result { if mmio_base == 0 || config.tsc_freq == 0 { return Err(UsbMsdInitError::InvalidConfig); } - if asm_usb_host_probe(mmio_base) != 0 { + let mut drv = Self::init_controller(mmio_base, config.tsc_freq)?; + drv.enumerate_and_configure()?; + drv.scsi_init()?; + Ok(drv) + } + + // ─── phase 1: controller bring-up (brutal edition) ───────────────── + + unsafe fn init_controller(mmio_base: u64, tsc_freq: u64) -> Result { + // probe: low byte = CAPLENGTH, high half = HCIVERSION. 0 = dead. + let probe = asm_usb_host_probe(mmio_base); + if probe == 0 { + return Err(UsbMsdInitError::ControllerInitFailed); + } + let cap_len = (probe & 0xFF) as u64; + let op_base = mmio_base + cap_len; + + let hcsparams1 = mmio::read32(mmio_base + 0x04); + let hcsparams2 = mmio::read32(mmio_base + 0x08); + let hccparams1 = mmio::read32(mmio_base + 0x10); + let db_off = mmio::read32(mmio_base + 0x14) & !0x03; + let rts_off = mmio::read32(mmio_base + 0x18) & !0x1F; + + let max_slots = (hcsparams1 & 0xFF) as u8; + let max_ports = ((hcsparams1 >> 24) & 0xFF) as u8; + let ctx_size: u8 = if hccparams1 & (1 << 2) != 0 { 64 } else { 32 }; + let scratch_hi = ((hcsparams2 >> 21) & 0x1F) as u16; + let scratch_lo = ((hcsparams2 >> 27) & 0x1F) as u16; + let n_scratch = ((scratch_hi << 5) | scratch_lo) as usize; + + let rt_base = mmio_base + rts_off as u64; + let db_base = mmio_base + db_off as u64; + + // static DMA buffer, identity-mapped in UEFI + let dma_base = core::ptr::addr_of_mut!(XHCI_DMA) as u64; + core::ptr::write_bytes(dma_base as *mut u8, 0, DMA_SIZE); + + // ── rip controller from BIOS/UEFI/SMM ── + asm_xhci_bios_handoff(mmio_base, hccparams1 as u64, tsc_freq); + tsc_delay(tsc_freq, 10); + + // ── controller reset with 3 attempts because hardware lies ── + let mut reset_ok = false; + for attempt in 0..3u32 { + let rc = asm_xhci_controller_reset(op_base, tsc_freq); + if rc == 0 { + reset_ok = true; + break; + } + // increasing backoff: 100ms, 200ms, 300ms + tsc_delay(tsc_freq, 100 * (attempt as u64 + 1)); + } + if !reset_ok { return Err(UsbMsdInitError::ControllerInitFailed); } - if asm_usb_host_reset(mmio_base, config.tsc_freq) != 0 { + // post-reset settle + tsc_delay(tsc_freq, 50); + + // MaxSlotsEn + mmio::write32(op_base + OP_CONFIG, max_slots.min(16) as u32); + + // scratchpad buffers (controller refuses to start without them) + if n_scratch > MAX_SCRATCH { return Err(UsbMsdInitError::ControllerInitFailed); } + if n_scratch > 0 { + let arr = dma_base + OFF_SCRATCH_ARR as u64; + for i in 0..n_scratch { + let pg = dma_base + (OFF_SCRATCH_PG + i * 4096) as u64; + vw64(arr + (i as u64) * 8, pg); + } + // DCBAA[0] = scratchpad buffer array + vw64(dma_base + OFF_DCBAA as u64, arr); + } + + // DCBAAP + let dcbaa = dma_base + OFF_DCBAA as u64; + mmio::write32(op_base + OP_DCBAAP, dcbaa as u32); + mmio::write32(op_base + OP_DCBAAP + 4, (dcbaa >> 32) as u32); + + // command ring → CRCR (RCS = 1) + let cr = dma_base + OFF_CMD_RING as u64; + mmio::write32(op_base + OP_CRCR, (cr as u32 & !0x3F) | 1); + mmio::write32(op_base + OP_CRCR + 4, (cr >> 32) as u32); + + // event ring: ERST entry, then registers + let er = dma_base + OFF_EVT_RING as u64; + let erst = dma_base + OFF_ERST as u64; + vw32(erst, er as u32); + vw32(erst + 4, (er >> 32) as u32); + vw32(erst + 8, EVT_RING_LEN as u32); + vw32(erst + 12, 0); + + mmio::write32(rt_base + IR0_ERSTSZ, 1); + // ERDP must be written before ERSTBA per spec + mmio::write32(rt_base + IR0_ERDP, (er as u32 & !0xF) | 0x08); + mmio::write32(rt_base + IR0_ERDP + 4, (er >> 32) as u32); + mmio::write32(rt_base + IR0_ERSTBA, erst as u32); + mmio::write32(rt_base + IR0_ERSTBA + 4, (erst >> 32) as u32); + + // IMAN.IE — some controllers gate event generation on this + let iman = mmio::read32(rt_base + IR0_IMAN); + mmio::write32(rt_base + IR0_IMAN, iman | 0x02); + + // start controller: RS=1, INTE=1 + mmio::write32(op_base + OP_USBCMD, CMD_RS | CMD_INTE); + + // wait HCH to clear (controller running) — 1 second + let start = tsc::read_tsc(); + let timeout = tsc_freq; + loop { + if mmio::read32(op_base + OP_USBSTS) & STS_HCH == 0 { + break; + } + if tsc::read_tsc().wrapping_sub(start) > timeout { + return Err(UsbMsdInitError::ControllerInitFailed); + } + core::hint::spin_loop(); + } + + // ── port power cycle: turn off, wait, turn on, wait ── + for p in 0..max_ports { + let addr = op_base + PORT_REG_BASE + (p as u64) * PORT_REG_STRIDE; + let ps = mmio::read32(addr); + mmio::write32(addr, (ps & !PORTSC_RW1C) & !PORTSC_PP); + } + tsc_delay(tsc_freq, 50); + for p in 0..max_ports { + let addr = op_base + PORT_REG_BASE + (p as u64) * PORT_REG_STRIDE; + let ps = mmio::read32(addr); + mmio::write32(addr, (ps & !PORTSC_RW1C) | PORTSC_PP); + } + // let devices settle after power-on + tsc_delay(tsc_freq, 200); + + Ok(Self { + op_base, + rt_base, + db_base, + tsc_freq, + max_ports, + ctx_size, + dma_base, + cmd_enq: 0, + cmd_cycle: 1, + evt_deq: 0, + evt_cycle: 1, + ep0_enq: 0, + ep0_cycle: 1, + bout_enq: 0, + bout_cycle: 1, + bin_enq: 0, + bin_cycle: 1, + slot_id: 0, + dci_bulk_in: 0, + dci_bulk_out: 0, + info: BlockDeviceInfo { + total_sectors: 0, + sector_size: 512, + max_sectors_per_request: 8, + read_only: true, + }, + last_completion: None, + bot_tag: 1, + }) + } + + // ─── phase 2: USB device enumeration ───────────────────────────────── + + unsafe fn enumerate_and_configure(&mut self) -> Result<(), UsbMsdInitError> { + for port in 0..self.max_ports { + let ps = mmio::read32(self.portsc(port)); + if ps & PORTSC_CCS == 0 { + continue; + } + if ps & PORTSC_PP == 0 { + continue; + } + + // settle delay before touching connected device + tsc_delay(self.tsc_freq, 100); + self.reset_transfer_state(); + + match self.try_port(port) { + Ok(()) => return Ok(()), + Err(_) => continue, + } + } + Err(UsbMsdInitError::NoMedia) + } + + unsafe fn try_port(&mut self, port: u8) -> Result<(), UsbMsdInitError> { + let speed = self.port_reset(port)?; + let slot = self.cmd_enable_slot()?; + self.slot_id = slot; + + // wire output context into DCBAA + let out_ctx = self.dma_base + OFF_OUT_CTX as u64; + vw64( + self.dma_base + OFF_DCBAA as u64 + (slot as u64) * 8, + out_ctx, + ); + + self.cmd_address_device(port, speed)?; + + // GET_DESCRIPTOR device (18 bytes) + let _dev_desc = self.control_in(0x80, 0x06, 0x0100, 0, 18)?; + + // GET_DESCRIPTOR configuration (up to 255 bytes) + let _cfg = self.control_in(0x80, 0x06, 0x0200, 0, 255)?; + + // parse for mass-storage interface + bulk endpoints + let (cfg_val, ep_in, ep_out, mpkt_in, mpkt_out) = + self.parse_config_desc().ok_or(UsbMsdInitError::DeviceEnumerationFailed)?; + + // SET_CONFIGURATION + self.control_nodata(0x00, 0x09, cfg_val as u16, 0)?; + + // compute DCIs + let dci_in = ((ep_in & 0x0F) * 2) + ((ep_in >> 7) & 1); + let dci_out = ((ep_out & 0x0F) * 2) + ((ep_out >> 7) & 1); + self.dci_bulk_in = dci_in; + self.dci_bulk_out = dci_out; + + // configure endpoint command + self.cmd_configure_eps(dci_in, dci_out, mpkt_in, mpkt_out)?; + + Ok(()) + } + + // ─── phase 3: SCSI bring-up ────────────────────────────────────────── + + unsafe fn scsi_init(&mut self) -> Result<(), UsbMsdInitError> { + // TEST UNIT READY — absorb unit-attention condition, ignore errors + let _ = self.bot_command(&[SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0], 0, false); + + // READ CAPACITY(10) → 8 bytes response + let cap_cmd = [SCSI_READ_CAPACITY_10, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + self.bot_command(&cap_cmd, 8, true)?; + + let data = self.dma_base + OFF_DATA as u64; + let last_lba = u32::from_be(vr32(data)) as u64; + let blk_size = u32::from_be(vr32(data + 4)); + + self.info.total_sectors = last_lba + 1; + self.info.sector_size = if blk_size == 0 { 512 } else { blk_size }; + // max request limited by bounce buffer (4KB page, no 64KB boundary crossing) + let max_req = DATA_BUF_SIZE as u32 / self.info.sector_size; + self.info.max_sectors_per_request = max_req.max(1); + + Ok(()) + } + + // ─── port management ───────────────────────────────────────────────── + + #[inline(always)] + fn portsc(&self, port: u8) -> u64 { + self.op_base + PORT_REG_BASE + (port as u64) * PORT_REG_STRIDE + } + + unsafe fn port_reset(&self, port: u8) -> Result { + let addr = self.portsc(port); + // preserve non-RW1C bits, set PR + let ps = mmio::read32(addr); + mmio::write32(addr, (ps & !PORTSC_RW1C) | PORTSC_PR); + + let start = tsc::read_tsc(); + let timeout = self.tsc_freq / 2; + loop { + let ps = mmio::read32(addr); + if ps & PORTSC_PRC != 0 { + // clear PRC + mmio::write32(addr, (ps & !PORTSC_RW1C) | PORTSC_PRC); + if ps & PORTSC_PED != 0 { + let speed = ((ps >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + return Ok(speed); + } + return Err(UsbMsdInitError::DeviceEnumerationFailed); + } + if tsc::read_tsc().wrapping_sub(start) > timeout { + return Err(UsbMsdInitError::CommandTimeout); + } + core::hint::spin_loop(); + } + } + + // ─── command ring ──────────────────────────────────────────────────── + + unsafe fn cmd_enqueue(&mut self, param: u64, status: u32, ctrl: u32) { + let base = self.dma_base + OFF_CMD_RING as u64; + let c = (ctrl & !1) | (self.cmd_cycle as u32); + write_trb(base, self.cmd_enq as usize, param, status, c); + self.cmd_enq += 1; + if self.cmd_enq >= CMD_RING_LEN - 1 { + // link TRB wrapping back to start + let link_ctrl = TRB_LINK | TRB_TC | (self.cmd_cycle as u32); + write_trb(base, self.cmd_enq as usize, base, 0, link_ctrl); + self.cmd_enq = 0; + self.cmd_cycle ^= 1; + } + } + + #[inline(always)] + unsafe fn ring_cmd_doorbell(&self) { + mmio::write32(self.db_base, 0); + } + + /// Wait for a Command Completion Event. Returns (completion_code, slot_id). + unsafe fn wait_cmd(&mut self, timeout_ms: u64) -> Result<(u8, u8), UsbMsdInitError> { + let (_, status, ctrl) = self.wait_event(TRB_CMD_COMPLETE, timeout_ms)?; + let cc = (status >> 24) as u8; + let sid = (ctrl >> 24) as u8; + if cc != 1 { + return Err(UsbMsdInitError::IoError); + } + Ok((cc, sid)) + } + + unsafe fn cmd_enable_slot(&mut self) -> Result { + self.cmd_enqueue(0, 0, TRB_ENABLE_SLOT); + self.ring_cmd_doorbell(); + let (_, slot) = self.wait_cmd(2000)?; + if slot == 0 { + return Err(UsbMsdInitError::DeviceEnumerationFailed); + } + Ok(slot) + } + + unsafe fn cmd_address_device( + &mut self, + port: u8, + speed: u8, + ) -> Result<(), UsbMsdInitError> { + let cs = self.ctx_size as u64; + let in_ctx = self.dma_base + OFF_IN_CTX as u64; + + // zero input context + core::ptr::write_bytes(in_ctx as *mut u8, 0, (33 * cs) as usize); + + // input control context: add slot (A0) + EP0 (A1) + vw32(in_ctx + 4, 0x03); + + // slot context at index 1 + let slot_ctx = in_ctx + cs; + let max_pkt_ep0 = Self::ep0_max_packet(speed); + // dword 0: speed | context_entries=1 + vw32(slot_ctx, ((speed as u32) << 20) | (1u32 << 26)); + // dword 1: root hub port (1-based) + vw32(slot_ctx + 4, (port as u32 + 1) << 16); + + // EP0 context at index 2 + let ep0 = in_ctx + 2 * cs; + // dword 1: CErr=3, EP type=4 (Control), max packet size + vw32(ep0 + 4, (3u32 << 1) | (4u32 << 3) | ((max_pkt_ep0 as u32) << 16)); + // dword 2-3: TR dequeue pointer | DCS=1 + let ring_phys = self.dma_base + OFF_XFER_EP0 as u64; + vw32(ep0 + 8, (ring_phys as u32 & !0xF) | 1); + vw32(ep0 + 12, (ring_phys >> 32) as u32); + // dword 4: average TRB length + vw32(ep0 + 16, 8); + + // address device command + let ctrl = TRB_ADDRESS_DEV | ((self.slot_id as u32) << 24); + self.cmd_enqueue(in_ctx, 0, ctrl); + self.ring_cmd_doorbell(); + self.wait_cmd(2000)?; + Ok(()) + } + + unsafe fn cmd_configure_eps( + &mut self, + dci_in: u8, + dci_out: u8, + mpkt_in: u16, + mpkt_out: u16, + ) -> Result<(), UsbMsdInitError> { + let cs = self.ctx_size as u64; + let in_ctx = self.dma_base + OFF_IN_CTX as u64; + let max_dci = dci_in.max(dci_out); + + // zero input context + core::ptr::write_bytes(in_ctx as *mut u8, 0, ((max_dci as u64 + 2) * cs) as usize); + + // input control context: A0 (slot) | A(dci_in) | A(dci_out) + let add_flags = (1u32 << 0) | (1u32 << dci_in) | (1u32 << dci_out); + vw32(in_ctx + 4, add_flags); + + // slot context: update context entries + let slot_ctx = in_ctx + cs; + // read the current speed from output context + let out_slot = self.dma_base + OFF_OUT_CTX as u64; + let d0 = vr32(out_slot); + let speed_bits = d0 & (0xF << 20); + vw32(slot_ctx, speed_bits | ((max_dci as u32) << 26)); + // root hub port from output context + vw32(slot_ctx + 4, vr32(out_slot + 4)); + + // bulk-in endpoint context + let ep_in = in_ctx + ((dci_in as u64) + 1) * cs; + // EP type 6 = Bulk IN, CErr=3 + vw32(ep_in + 4, (3u32 << 1) | (6u32 << 3) | ((mpkt_in as u32) << 16)); + let ring_in = self.dma_base + OFF_XFER_BIN as u64; + vw32(ep_in + 8, (ring_in as u32 & !0xF) | 1); + vw32(ep_in + 12, (ring_in >> 32) as u32); + vw32(ep_in + 16, 1024); // average TRB length + + // bulk-out endpoint context + let ep_out = in_ctx + ((dci_out as u64) + 1) * cs; + // EP type 2 = Bulk OUT, CErr=3 + vw32(ep_out + 4, (3u32 << 1) | (2u32 << 3) | ((mpkt_out as u32) << 16)); + let ring_out = self.dma_base + OFF_XFER_BOUT as u64; + vw32(ep_out + 8, (ring_out as u32 & !0xF) | 1); + vw32(ep_out + 12, (ring_out >> 32) as u32); + vw32(ep_out + 16, 1024); + + let ctrl = TRB_CONFIGURE_EP | ((self.slot_id as u32) << 24); + self.cmd_enqueue(in_ctx, 0, ctrl); + self.ring_cmd_doorbell(); + self.wait_cmd(2000)?; + Ok(()) + } + + // ─── control transfers (EP0) ───────────────────────────────────────── + + /// IN control transfer: GET_DESCRIPTOR etc. + /// Returns slice into the descriptor buffer. + unsafe fn control_in( + &mut self, + req_type: u8, + request: u8, + value: u16, + index: u16, + len: u16, + ) -> Result<&[u8], UsbMsdInitError> { + let setup = (req_type as u64) + | ((request as u64) << 8) + | ((value as u64) << 16) + | ((index as u64) << 32) + | ((len as u64) << 48); + + let desc_buf = self.dma_base + OFF_DESC as u64; + + // setup stage: IDT=1, TRT=IN + self.xfer_enqueue(Ring::Ep0, setup, 8, TRB_SETUP | TRB_IDT | TRB_TRT_IN); + // data stage: DIR=IN + self.xfer_enqueue( + Ring::Ep0, + desc_buf, + len as u32, + TRB_DATA | TRB_DIR_IN | TRB_ISP, + ); + // status stage: DIR=OUT (no DIR_IN), IOC + self.xfer_enqueue(Ring::Ep0, 0, 0, TRB_STATUS | TRB_IOC); + + // ring EP0 doorbell (DCI=1) + mmio::write32(self.db_base + (self.slot_id as u64) * 4, 1); + self.wait_xfer(5000)?; + + Ok(core::slice::from_raw_parts( + desc_buf as *const u8, + len as usize, + )) + } + + /// No-data control transfer: SET_CONFIGURATION etc. + unsafe fn control_nodata( + &mut self, + req_type: u8, + request: u8, + value: u16, + index: u16, + ) -> Result<(), UsbMsdInitError> { + let setup = (req_type as u64) + | ((request as u64) << 8) + | ((value as u64) << 16) + | ((index as u64) << 32); + + // setup stage: no data + self.xfer_enqueue(Ring::Ep0, setup, 8, TRB_SETUP | TRB_IDT); + // status stage: DIR=IN, IOC + self.xfer_enqueue(Ring::Ep0, 0, 0, TRB_STATUS | TRB_IOC | TRB_DIR_IN); + + mmio::write32(self.db_base + (self.slot_id as u64) * 4, 1); + self.wait_xfer(5000)?; + Ok(()) + } + + // ─── transfer ring enqueue ─────────────────────────────────────────── + + unsafe fn xfer_enqueue(&mut self, ring: Ring, param: u64, status: u32, ctrl: u32) { + let (off, enq, cycle) = match ring { + Ring::Ep0 => (OFF_XFER_EP0, &mut self.ep0_enq, &mut self.ep0_cycle), + Ring::BulkOut => (OFF_XFER_BOUT, &mut self.bout_enq, &mut self.bout_cycle), + Ring::BulkIn => (OFF_XFER_BIN, &mut self.bin_enq, &mut self.bin_cycle), + }; + let base = self.dma_base + off as u64; + let c = (ctrl & !1) | (*cycle as u32); + write_trb(base, *enq as usize, param, status, c); + *enq += 1; + if *enq >= XFER_RING_LEN - 1 { + let link = TRB_LINK | TRB_TC | (*cycle as u32); + write_trb(base, *enq as usize, base, 0, link); + *enq = 0; + *cycle ^= 1; + } + } + + /// Wait for a Transfer Event. Returns remaining byte count. + unsafe fn wait_xfer(&mut self, timeout_ms: u64) -> Result { + let (_, status, _) = self.wait_event(TRB_XFER_EVENT, timeout_ms)?; + let cc = (status >> 24) as u8; + // 1 = success, 13 = short packet (ok for mass storage / descriptors) + if cc != 1 && cc != 13 { + return Err(UsbMsdInitError::IoError); + } + // remaining bytes in bits 23:0 + Ok(status & 0x00FF_FFFF) + } + + // ─── event ring ────────────────────────────────────────────────────── + + unsafe fn wait_event( + &mut self, + expected: u32, + timeout_ms: u64, + ) -> Result<(u64, u32, u32), UsbMsdInitError> { + let start = tsc::read_tsc(); + let timeout = self.tsc_freq.saturating_mul(timeout_ms) / 1000; + let base = self.dma_base + OFF_EVT_RING as u64; + + loop { + let a = base + (self.evt_deq as u64) * 16; + let ctrl = vr32(a + 12); + if (ctrl & 1) == self.evt_cycle as u32 { + let p_lo = vr32(a) as u64; + let p_hi = vr32(a + 4) as u64; + let param = p_lo | (p_hi << 32); + let status = vr32(a + 8); + + self.evt_deq += 1; + if self.evt_deq >= EVT_RING_LEN { + self.evt_deq = 0; + self.evt_cycle ^= 1; + } + // update ERDP, clear EHB + let new_erdp = base + (self.evt_deq as u64) * 16; + mmio::write32( + self.rt_base + IR0_ERDP, + (new_erdp as u32 & !0xF) | 0x08, + ); + mmio::write32(self.rt_base + IR0_ERDP + 4, (new_erdp >> 32) as u32); + + if (ctrl & TRB_TYPE_MASK) == expected { + return Ok((param, status, ctrl)); + } + // skip unexpected events (port status change, etc.) + continue; + } + if tsc::read_tsc().wrapping_sub(start) > timeout { + return Err(UsbMsdInitError::CommandTimeout); + } + core::hint::spin_loop(); + } + } + + // ─── BOT (Bulk-Only Transport) ─────────────────────────────────────── + + /// Send a SCSI command via BOT. + /// `scsi_cb` = command block (6-16 bytes). + /// `data_len` = expected data transfer length (0 = no data phase). + /// `data_in` = true for device→host data. + /// Data lands at OFF_DATA. + unsafe fn bot_command( + &mut self, + scsi_cb: &[u8], + data_len: u32, + data_in: bool, + ) -> Result { + let tag = self.bot_tag; + self.bot_tag = self.bot_tag.wrapping_add(1); + + // ── build CBW at OFF_CBW ── + let cbw = self.dma_base + OFF_CBW as u64; + core::ptr::write_bytes(cbw as *mut u8, 0, 31); + vw32(cbw, CBW_SIG); + vw32(cbw + 4, tag); + vw32(cbw + 8, data_len); + // flags: 0x80 = data-in, 0x00 = data-out/no-data + let flags: u8 = if data_in && data_len > 0 { 0x80 } else { 0x00 }; + core::ptr::write_volatile((cbw + 12) as *mut u8, flags); + // LUN = 0, CB length + core::ptr::write_volatile((cbw + 14) as *mut u8, scsi_cb.len().min(16) as u8); + // copy SCSI CDB + for (i, &b) in scsi_cb.iter().take(16).enumerate() { + core::ptr::write_volatile((cbw + 15 + i as u64) as *mut u8, b); + } + + // ── send CBW via bulk-out ── + self.xfer_enqueue(Ring::BulkOut, cbw, 31, TRB_NORMAL | TRB_IOC); + mmio::write32( + self.db_base + (self.slot_id as u64) * 4, + self.dci_bulk_out as u32, + ); + self.wait_xfer(5000)?; + + // ── data phase (if any) ── + let mut transferred = 0u32; + if data_len > 0 { + let data_buf = self.dma_base + OFF_DATA as u64; + if data_in { + self.xfer_enqueue(Ring::BulkIn, data_buf, data_len, TRB_NORMAL | TRB_IOC | TRB_ISP); + mmio::write32( + self.db_base + (self.slot_id as u64) * 4, + self.dci_bulk_in as u32, + ); + } else { + self.xfer_enqueue(Ring::BulkOut, data_buf, data_len, TRB_NORMAL | TRB_IOC); + mmio::write32( + self.db_base + (self.slot_id as u64) * 4, + self.dci_bulk_out as u32, + ); + } + let residue = self.wait_xfer(10000)?; + transferred = data_len.saturating_sub(residue); + } + + // ── receive CSW via bulk-in ── + let csw = self.dma_base + OFF_CSW as u64; + core::ptr::write_bytes(csw as *mut u8, 0, 13); + self.xfer_enqueue(Ring::BulkIn, csw, 13, TRB_NORMAL | TRB_IOC); + mmio::write32( + self.db_base + (self.slot_id as u64) * 4, + self.dci_bulk_in as u32, + ); + self.wait_xfer(5000)?; + + // verify CSW + let csw_sig = vr32(csw); + let csw_tag = vr32(csw + 4); + let csw_status = core::ptr::read_volatile((csw + 12) as *const u8); + if csw_sig != CSW_SIG || csw_tag != tag || csw_status != 0 { + return Err(UsbMsdInitError::IoError); + } + + Ok(transferred) + } + + /// SCSI READ(10) via BOT — reads `count` sectors at `lba` into OFF_DATA. + unsafe fn scsi_read_sectors( + &mut self, + lba: u64, + count: u32, + ) -> Result<(), UsbMsdInitError> { + let byte_count = count * self.info.sector_size; + let mut cmd = [0u8; 10]; + cmd[0] = SCSI_READ_10; + // LBA big-endian at offset 2 + cmd[2] = (lba >> 24) as u8; + cmd[3] = (lba >> 16) as u8; + cmd[4] = (lba >> 8) as u8; + cmd[5] = lba as u8; + // transfer length (blocks) big-endian at offset 7 + cmd[7] = (count >> 8) as u8; + cmd[8] = count as u8; + + self.bot_command(&cmd, byte_count, true)?; + Ok(()) + } + + // ─── descriptor parsing ────────────────────────────────────────────── + + /// Parse the configuration descriptor at OFF_DESC for a mass-storage + /// BOT interface. Returns (config_val, ep_in_addr, ep_out_addr, max_pkt_in, max_pkt_out). + unsafe fn parse_config_desc(&self) -> Option<(u8, u8, u8, u16, u16)> { + let d = self.dma_base + OFF_DESC as u64; + let total = u16::from_le_bytes([ + core::ptr::read_volatile((d + 2) as *const u8), + core::ptr::read_volatile((d + 3) as *const u8), + ]) as usize; + let cfg_val = core::ptr::read_volatile((d + 5) as *const u8); + let limit = total.min(255); + + let mut off = 0usize; + let mut in_msc = false; + let mut ep_in: u8 = 0; + let mut ep_out: u8 = 0; + let mut mp_in: u16 = 0; + let mut mp_out: u16 = 0; + + while off + 2 <= limit { + let blen = core::ptr::read_volatile((d + off as u64) as *const u8) as usize; + let btype = core::ptr::read_volatile((d + off as u64 + 1) as *const u8); + if blen < 2 { + break; + } + if off + blen > limit { + break; + } + if btype == 4 && blen >= 9 { + // interface descriptor + let cls = core::ptr::read_volatile((d + off as u64 + 5) as *const u8); + let sub = core::ptr::read_volatile((d + off as u64 + 6) as *const u8); + let proto = core::ptr::read_volatile((d + off as u64 + 7) as *const u8); + in_msc = + cls == USB_CLASS_MASS_STORAGE && sub == USB_SUBCLASS_SCSI && proto == USB_PROTOCOL_BOT; + } + if btype == 5 && blen >= 7 && in_msc { + // endpoint descriptor + let addr = core::ptr::read_volatile((d + off as u64 + 2) as *const u8); + let attr = core::ptr::read_volatile((d + off as u64 + 3) as *const u8); + let mpkt = u16::from_le_bytes([ + core::ptr::read_volatile((d + off as u64 + 4) as *const u8), + core::ptr::read_volatile((d + off as u64 + 5) as *const u8), + ]); + if attr & 0x03 == 0x02 { + // bulk + if addr & 0x80 != 0 { + ep_in = addr; + mp_in = mpkt; + } else { + ep_out = addr; + mp_out = mpkt; + } + } + } + off += blen; + } - // Probe/reset path is wired, but BOT/SCSI read transport is not yet implemented. - // Fail fast so higher layers skip this backend instead of selecting a non-functional disk. - return Err(UsbMsdInitError::NotImplemented); + if ep_in != 0 && ep_out != 0 { + Some((cfg_val, ep_in, ep_out, mp_in, mp_out)) + } else { + None + } + } + + // ─── helpers ───────────────────────────────────────────────────────── + + fn ep0_max_packet(speed: u8) -> u16 { + match speed { + 4 => 512, // SS + 3 => 64, // HS + 2 => 8, // LS + _ => 64, // FS / default + } + } + + /// Zero transfer ring buffers and reset ring indices for a fresh enumeration attempt. + unsafe fn reset_transfer_state(&mut self) { + // zero ring memory + core::ptr::write_bytes((self.dma_base + OFF_XFER_EP0 as u64) as *mut u8, 0, 256); + core::ptr::write_bytes((self.dma_base + OFF_XFER_BOUT as u64) as *mut u8, 0, 256); + core::ptr::write_bytes((self.dma_base + OFF_XFER_BIN as u64) as *mut u8, 0, 256); + // zero contexts + core::ptr::write_bytes((self.dma_base + OFF_OUT_CTX as u64) as *mut u8, 0, 2048); + core::ptr::write_bytes((self.dma_base + OFF_IN_CTX as u64) as *mut u8, 0, 2560); + // reset indices + self.ep0_enq = 0; + self.ep0_cycle = 1; + self.bout_enq = 0; + self.bout_cycle = 1; + self.bin_enq = 0; + self.bin_cycle = 1; } } +// ═══════════════════════════════════════════════════════════════════════════ +// BlockDriverInit +// ═══════════════════════════════════════════════════════════════════════════ + impl BlockDriverInit for UsbMsdDriver { type Error = UsbMsdInitError; type Config = UsbMsdConfig; @@ -98,24 +1064,61 @@ impl BlockDriverInit for UsbMsdDriver { } } +// ═══════════════════════════════════════════════════════════════════════════ +// BlockDriver +// ═══════════════════════════════════════════════════════════════════════════ + impl BlockDriver for UsbMsdDriver { fn info(&self) -> BlockDeviceInfo { self.info } fn can_submit(&self) -> bool { - true + self.last_completion.is_none() } fn submit_read( &mut self, - _sector: u64, - _buffer_phys: u64, - _num_sectors: u32, - _request_id: u32, + sector: u64, + buffer_phys: u64, + num_sectors: u32, + request_id: u32, ) -> Result<(), BlockError> { - let _ = self.mmio_base; - Err(BlockError::DeviceNotReady) + if self.last_completion.is_some() { + return Err(BlockError::QueueFull); + } + if num_sectors == 0 || num_sectors > self.info.max_sectors_per_request { + return Err(BlockError::RequestTooLarge); + } + if buffer_phys == 0 { + return Err(BlockError::InvalidSector); + } + let end = sector + .checked_add(num_sectors as u64) + .ok_or(BlockError::InvalidSector)?; + if end > self.info.total_sectors { + return Err(BlockError::InvalidSector); + } + + let byte_count = num_sectors as u64 * self.info.sector_size as u64; + unsafe { + self.scsi_read_sectors(sector, num_sectors) + .map_err(|_| BlockError::IoError)?; + + // copy bounce buffer → caller's buffer + core::ptr::copy_nonoverlapping( + (self.dma_base + OFF_DATA as u64) as *const u8, + buffer_phys as *mut u8, + byte_count as usize, + ); + } + + self.last_completion = Some(BlockCompletion { + request_id, + status: 0, + bytes_transferred: (byte_count as u32), + }); + Ok(()) } fn submit_write( @@ -129,7 +1132,7 @@ impl BlockDriver for UsbMsdDriver { } fn poll_completion(&mut self) -> Option { - None + self.last_completion.take() } fn notify(&mut self) {} diff --git a/persistent/src/pe/embedded_reloc_data.rs b/persistent/src/pe/embedded_reloc_data.rs index 39ab1385..1b3919a3 100644 --- a/persistent/src/pe/embedded_reloc_data.rs +++ b/persistent/src/pe/embedded_reloc_data.rs @@ -8,117 +8,116 @@ //! Run: ./tools/extract-reloc-data.sh after each build /// Original .reloc section RVA -pub const RELOC_RVA: u32 = 0x0063a000; +pub const RELOC_RVA: u32 = 0x0064d000; /// Original .reloc section size -pub const RELOC_SIZE: u32 = 0x000004b0; +pub const RELOC_SIZE: u32 = 0x000004a0; /// Original ImageBase from linker script pub const ORIGINAL_IMAGE_BASE: u64 = 0x0000004001000000; -/// Hardcoded .reloc section data (1200 bytes) -/// Extracted from morpheus-bootloader.efi at file offset 0x001fc400 +/// Hardcoded .reloc section data (1184 bytes) +/// Extracted from morpheus-bootloader.efi at file offset 0x001fe400 #[allow(dead_code)] -pub const RELOC_DATA: [u8; 1200] = [ - 0x00, 0x10, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, +pub const RELOC_DATA: [u8; 1184] = [ + 0x00, 0x30, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0x10, 0xa7, 0x28, 0xa7, - 0x48, 0xa7, 0x50, 0xa8, 0x00, 0x20, 0x07, 0x00, 0x74, 0x00, 0x00, 0x00, - 0x58, 0xa0, 0x98, 0xa2, 0xb0, 0xa2, 0xc8, 0xa2, 0xe0, 0xa2, 0xf8, 0xa2, - 0x10, 0xa3, 0x28, 0xa3, 0x40, 0xa3, 0x58, 0xa3, 0x70, 0xa3, 0x88, 0xa3, - 0xa0, 0xa3, 0xb8, 0xa3, 0xc0, 0xa3, 0xc8, 0xa3, 0xd0, 0xa3, 0xd8, 0xa3, - 0xe0, 0xa3, 0xe8, 0xa3, 0xf0, 0xa3, 0xe0, 0xa4, 0xf8, 0xa4, 0x10, 0xa5, - 0x28, 0xa5, 0x40, 0xa5, 0x58, 0xa5, 0x70, 0xa5, 0xa8, 0xa5, 0x28, 0xa8, - 0x40, 0xa8, 0x58, 0xa8, 0x70, 0xa8, 0x88, 0xa8, 0xa0, 0xa8, 0x20, 0xa9, - 0x38, 0xa9, 0x50, 0xa9, 0x68, 0xa9, 0x80, 0xa9, 0x98, 0xa9, 0xb0, 0xa9, - 0xc8, 0xa9, 0xe0, 0xa9, 0x60, 0xaa, 0x78, 0xaa, 0xd8, 0xaa, 0xf0, 0xaa, - 0x08, 0xab, 0xb0, 0xab, 0x08, 0xac, 0x20, 0xac, 0x38, 0xac, 0x00, 0x00, - 0x00, 0x30, 0x07, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x18, 0xa0, - 0x30, 0xa0, 0x70, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, 0x20, 0xa1, 0x60, 0xa1, - 0x70, 0xa1, 0xb8, 0xa1, 0xd0, 0xa1, 0xe8, 0xa1, 0x00, 0xa2, 0x80, 0xa2, - 0x98, 0xa2, 0xb0, 0xa2, 0xa0, 0xa5, 0xb8, 0xa5, 0xd0, 0xa5, 0x90, 0xa6, - 0xc0, 0xa6, 0x60, 0xa7, 0x78, 0xa7, 0x90, 0xa7, 0xa8, 0xa7, 0xc0, 0xa7, - 0xd8, 0xa7, 0xf0, 0xa7, 0x08, 0xa8, 0x20, 0xa8, 0xa0, 0xa8, 0x20, 0xa9, - 0xc0, 0xaf, 0xd8, 0xaf, 0xf0, 0xaf, 0x00, 0x00, 0x00, 0x40, 0x07, 0x00, - 0x90, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x20, 0xa0, 0xa8, 0xa0, 0xc0, 0xa0, - 0x00, 0xa1, 0x18, 0xa1, 0x30, 0xa1, 0x48, 0xa1, 0xd0, 0xa1, 0xe8, 0xa1, - 0x70, 0xa4, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xd0, 0xa4, 0xe8, 0xa4, - 0x80, 0xa5, 0x98, 0xa5, 0xb0, 0xa5, 0xc8, 0xa5, 0xe0, 0xa5, 0x20, 0xa7, - 0x58, 0xa8, 0x70, 0xa8, 0xc0, 0xa8, 0x38, 0xa9, 0x50, 0xa9, 0x68, 0xa9, - 0x80, 0xa9, 0x98, 0xa9, 0xb0, 0xa9, 0xc8, 0xa9, 0xe0, 0xa9, 0xf8, 0xa9, - 0x10, 0xaa, 0x28, 0xaa, 0x40, 0xaa, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, - 0xf0, 0xaa, 0x08, 0xab, 0x20, 0xab, 0x38, 0xab, 0x50, 0xab, 0x68, 0xab, - 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, 0xe0, 0xab, 0x30, 0xac, - 0x40, 0xac, 0x98, 0xac, 0xa8, 0xac, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, - 0xd0, 0xae, 0x50, 0xaf, 0x68, 0xaf, 0x80, 0xaf, 0x98, 0xaf, 0xb0, 0xaf, - 0xc8, 0xaf, 0xe0, 0xaf, 0xf8, 0xaf, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, - 0x8c, 0x00, 0x00, 0x00, 0x10, 0xa0, 0x40, 0xa0, 0xe0, 0xa0, 0xf8, 0xa0, - 0x78, 0xa1, 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, 0x40, 0xa2, 0x58, 0xa2, - 0x70, 0xa2, 0xf8, 0xa2, 0x20, 0xa4, 0x30, 0xa4, 0x40, 0xa4, 0xb8, 0xa4, - 0xd0, 0xa4, 0xe8, 0xa4, 0x00, 0xa5, 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, - 0x60, 0xa5, 0x78, 0xa5, 0x90, 0xa5, 0xa8, 0xa5, 0xc0, 0xa5, 0xd8, 0xa5, - 0xf0, 0xa5, 0x08, 0xa6, 0x20, 0xa6, 0xc8, 0xa7, 0xe0, 0xa7, 0xf8, 0xa7, - 0x10, 0xa8, 0x28, 0xa8, 0x40, 0xa8, 0x58, 0xa8, 0x70, 0xa8, 0x88, 0xa8, - 0x10, 0xa9, 0x50, 0xa9, 0x60, 0xa9, 0x20, 0xaa, 0xb0, 0xaa, 0xf0, 0xaa, - 0x38, 0xab, 0xd0, 0xab, 0x18, 0xac, 0x28, 0xac, 0x38, 0xac, 0x48, 0xac, - 0xc8, 0xac, 0xe0, 0xac, 0x60, 0xad, 0x00, 0xae, 0x10, 0xae, 0x20, 0xae, - 0xb8, 0xae, 0xf0, 0xae, 0x08, 0xaf, 0x20, 0xaf, 0x38, 0xaf, 0xb8, 0xaf, - 0xf8, 0xaf, 0x00, 0x00, 0x00, 0x60, 0x07, 0x00, 0x60, 0x00, 0x00, 0x00, - 0x10, 0xa0, 0x50, 0xa1, 0xf0, 0xa1, 0x10, 0xa2, 0xa8, 0xa2, 0xe0, 0xa2, - 0xa0, 0xa3, 0xb8, 0xa3, 0x10, 0xa4, 0x28, 0xa4, 0x98, 0xa4, 0xb0, 0xa4, - 0xc8, 0xa4, 0x00, 0xa5, 0x30, 0xa5, 0x60, 0xa5, 0x78, 0xa5, 0x10, 0xa6, - 0x28, 0xa6, 0x40, 0xa6, 0x58, 0xa6, 0x70, 0xa6, 0x80, 0xa7, 0xe0, 0xa7, - 0x58, 0xa8, 0x68, 0xa9, 0x80, 0xa9, 0x98, 0xa9, 0xb0, 0xa9, 0xc8, 0xa9, - 0x20, 0xaa, 0x38, 0xaa, 0x50, 0xaa, 0x68, 0xac, 0x80, 0xac, 0x98, 0xac, - 0xb0, 0xac, 0xc8, 0xac, 0xe0, 0xac, 0xf8, 0xac, 0x30, 0xad, 0x40, 0xae, - 0x58, 0xae, 0x00, 0x00, 0x00, 0x70, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x58, 0xa1, 0xc0, 0xa1, 0xe0, 0xa1, 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, - 0x40, 0xa2, 0x48, 0xa3, 0xb8, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, - 0x18, 0xa4, 0x30, 0xa4, 0x70, 0xa4, 0x00, 0xa5, 0x98, 0xaf, 0x00, 0x00, - 0x00, 0x80, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0xa5, - 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, 0xe8, 0xa6, - 0x40, 0xa8, 0x50, 0xa8, 0x60, 0xa8, 0x70, 0xa8, 0x80, 0xa8, 0x90, 0xa8, - 0xa0, 0xa8, 0xb0, 0xa8, 0xc0, 0xa8, 0xd0, 0xa8, 0xe0, 0xa8, 0xf0, 0xa8, - 0x00, 0xa9, 0x10, 0xa9, 0x20, 0xa9, 0x30, 0xa9, 0x40, 0xa9, 0x50, 0xa9, - 0x60, 0xa9, 0x70, 0xa9, 0x80, 0xa9, 0x90, 0xa9, 0xa0, 0xa9, 0xb0, 0xa9, - 0xc0, 0xa9, 0xd0, 0xa9, 0xe0, 0xa9, 0xf0, 0xa9, 0x00, 0xaa, 0x10, 0xaa, - 0x20, 0xaa, 0x30, 0xaa, 0x10, 0xac, 0x00, 0x00, 0x00, 0x90, 0x07, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x28, 0xae, 0x80, 0xae, 0xf0, 0xaf, 0x00, 0x00, - 0x00, 0xa0, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x58, 0xa0, - 0x68, 0xa0, 0x78, 0xa0, 0x88, 0xa0, 0x98, 0xa0, 0xc8, 0xa0, 0x00, 0xa1, - 0x48, 0xa1, 0x98, 0xa1, 0xd0, 0xa1, 0xe8, 0xa1, 0x30, 0xa2, 0xb8, 0xa2, - 0xf8, 0xae, 0x88, 0xaf, 0xa0, 0xaf, 0xd8, 0xaf, 0x00, 0xb0, 0x07, 0x00, - 0x50, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x20, 0xa0, 0x38, 0xa0, 0x50, 0xa0, - 0x68, 0xa0, 0x80, 0xa0, 0x98, 0xa0, 0xb0, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, - 0x20, 0xa1, 0x38, 0xa1, 0x50, 0xa1, 0x88, 0xa1, 0xb8, 0xa1, 0xd0, 0xa1, - 0xe8, 0xa1, 0x00, 0xa2, 0x38, 0xa2, 0x50, 0xa2, 0x68, 0xa2, 0x80, 0xa2, - 0x98, 0xa2, 0xb0, 0xa2, 0x40, 0xa3, 0x88, 0xa9, 0x98, 0xa9, 0xc8, 0xa9, - 0xe0, 0xa9, 0x28, 0xaa, 0x38, 0xaa, 0x48, 0xaa, 0x70, 0xaa, 0xa8, 0xaa, - 0xe8, 0xaf, 0xf8, 0xaf, 0x00, 0xc0, 0x07, 0x00, 0x74, 0x00, 0x00, 0x00, - 0x08, 0xa0, 0x18, 0xa0, 0x68, 0xa0, 0x78, 0xa0, 0x88, 0xa0, 0x98, 0xa0, - 0xa8, 0xa0, 0xd0, 0xa0, 0xe0, 0xa0, 0xf0, 0xa0, 0x58, 0xa1, 0x68, 0xa1, - 0x78, 0xa1, 0xd8, 0xa1, 0x18, 0xa2, 0x68, 0xa2, 0x78, 0xa2, 0xb0, 0xa2, - 0xc0, 0xa2, 0xe8, 0xa2, 0xf8, 0xa2, 0x30, 0xa3, 0x48, 0xa3, 0xa0, 0xa3, - 0x10, 0xaa, 0x28, 0xaa, 0x60, 0xaa, 0xb0, 0xaa, 0xf8, 0xaa, 0x08, 0xab, - 0x18, 0xac, 0x28, 0xac, 0x60, 0xac, 0x70, 0xac, 0xa0, 0xac, 0xb0, 0xac, - 0xc8, 0xac, 0x08, 0xad, 0x18, 0xad, 0x30, 0xad, 0x88, 0xad, 0x98, 0xad, - 0xb0, 0xad, 0xc8, 0xad, 0xe0, 0xad, 0xa8, 0xae, 0xf8, 0xae, 0x08, 0xaf, - 0x38, 0xaf, 0x70, 0xaf, 0x80, 0xaf, 0xd0, 0xaf, 0xe0, 0xaf, 0x00, 0x00, - 0x00, 0xd0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x28, 0xa0, 0x38, 0xa0, - 0x70, 0xa0, 0xf0, 0xa4, 0x08, 0xa5, 0xd0, 0xa5, 0xe8, 0xa5, 0x98, 0xa6, - 0xb0, 0xa6, 0xe8, 0xa6, 0x00, 0xa7, 0x18, 0xa7, 0x30, 0xa7, 0xc8, 0xa7, - 0xe0, 0xa7, 0xf8, 0xa7, 0x10, 0xa8, 0x28, 0xa8, 0x40, 0xa8, 0x58, 0xa8, - 0x28, 0xaa, 0x40, 0xaa, 0x80, 0xaa, 0x98, 0xaa, 0xb0, 0xaa, 0xc8, 0xaa, - 0x08, 0xab, 0x88, 0xab, 0xa0, 0xab, 0xb8, 0xab, 0xd0, 0xab, 0xe8, 0xab, - 0x00, 0xac, 0x18, 0xac, 0x30, 0xac, 0x48, 0xac, 0x60, 0xac, 0x78, 0xac, - 0x90, 0xac, 0xa8, 0xac, 0xf8, 0xae, 0x10, 0xaf, 0x00, 0xe0, 0x07, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x08, 0xa0, 0x20, 0xa0, 0x38, 0xa0, 0x88, 0xa0, - 0x20, 0xa1, 0xa0, 0xa1, 0x28, 0xa2, 0x80, 0xa2, 0x98, 0xa2, 0xb0, 0xa2, - 0xc8, 0xa2, 0xe0, 0xa2, 0xf8, 0xa2, 0x10, 0xa3, 0x00, 0xf0, 0x07, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0xe0, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, - 0x20, 0xa7, 0x28, 0xa7, 0x00, 0xa0, 0x1e, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x80, 0xa7, 0x90, 0xa8, 0x98, 0xa8, 0xa0, 0xa8, 0xb0, 0xa8, 0x00, 0x00, - 0x00, 0xc0, 0x1f, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xe8, 0xae, 0x00, 0x00, - 0x00, 0x90, 0x63, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x00, 0x00, + 0x48, 0xa7, 0x50, 0xa8, 0x00, 0x40, 0x07, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x48, 0xa2, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xd0, 0xa4, 0xe8, 0xa4, + 0x00, 0xa5, 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, + 0x90, 0xa5, 0xa8, 0xa5, 0xb0, 0xa5, 0xb8, 0xa5, 0xc0, 0xa5, 0xc8, 0xa5, + 0xd0, 0xa5, 0xd8, 0xa5, 0xe0, 0xa5, 0xf8, 0xa6, 0x10, 0xa7, 0x28, 0xa7, + 0x18, 0xa8, 0x30, 0xa8, 0x48, 0xa8, 0x88, 0xa8, 0x08, 0xa9, 0x20, 0xa9, + 0x38, 0xa9, 0x78, 0xa9, 0x88, 0xa9, 0xd0, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, + 0x18, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xb8, 0xaa, 0xd0, 0xaa, + 0xe8, 0xaa, 0x00, 0xab, 0x18, 0xad, 0xd8, 0xad, 0x08, 0xae, 0xa8, 0xae, + 0xc0, 0xae, 0xd8, 0xae, 0xf0, 0xae, 0x08, 0xaf, 0x20, 0xaf, 0xa0, 0xaf, + 0xb8, 0xaf, 0xd0, 0xaf, 0xe8, 0xaf, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x18, 0xa0, 0x30, 0xa0, 0x48, 0xa0, + 0x60, 0xa0, 0xe0, 0xa0, 0xf8, 0xa0, 0x58, 0xa1, 0x70, 0xa1, 0x88, 0xa1, + 0x18, 0xa2, 0x30, 0xa2, 0x48, 0xa2, 0x98, 0xa8, 0xb0, 0xa8, 0xc8, 0xa8, + 0xe0, 0xa8, 0xf8, 0xa8, 0x00, 0x60, 0x07, 0x00, 0x84, 0x00, 0x00, 0x00, + 0xe0, 0xa0, 0x60, 0xa1, 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, 0x78, 0xa2, + 0x90, 0xa2, 0xa8, 0xa2, 0xc0, 0xa2, 0xd8, 0xa2, 0xf0, 0xa2, 0x78, 0xa3, + 0x90, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, 0x18, 0xa4, 0xa0, 0xa4, + 0xb8, 0xa4, 0x80, 0xa5, 0x98, 0xa5, 0x80, 0xa6, 0x98, 0xa6, 0xb0, 0xa6, + 0xc8, 0xa6, 0xe0, 0xa6, 0xf8, 0xa6, 0x88, 0xa8, 0xd0, 0xa9, 0xe8, 0xa9, + 0x38, 0xaa, 0xb0, 0xaa, 0xc8, 0xaa, 0xe0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, + 0x28, 0xab, 0x40, 0xab, 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, 0xa0, 0xab, + 0xb8, 0xab, 0xd0, 0xab, 0xe8, 0xab, 0x00, 0xac, 0x68, 0xac, 0x80, 0xac, + 0x98, 0xac, 0xb0, 0xac, 0xc8, 0xac, 0xe0, 0xac, 0xf8, 0xac, 0x10, 0xad, + 0x28, 0xad, 0x40, 0xad, 0x58, 0xad, 0xa8, 0xad, 0xb8, 0xad, 0x10, 0xae, + 0x20, 0xae, 0x00, 0x00, 0x00, 0x70, 0x07, 0x00, 0x94, 0x00, 0x00, 0x00, + 0x00, 0xa0, 0x18, 0xa0, 0x30, 0xa0, 0x48, 0xa0, 0xc8, 0xa0, 0xe0, 0xa0, + 0xf8, 0xa0, 0x10, 0xa1, 0x28, 0xa1, 0x40, 0xa1, 0x58, 0xa1, 0x70, 0xa1, + 0x88, 0xa1, 0xb8, 0xa1, 0x58, 0xa2, 0x70, 0xa2, 0xf0, 0xa2, 0x70, 0xa3, + 0x88, 0xa3, 0xa0, 0xa3, 0xb8, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x70, 0xa4, + 0x98, 0xa5, 0xa8, 0xa5, 0xb8, 0xa5, 0x30, 0xa6, 0x48, 0xa6, 0x60, 0xa6, + 0x78, 0xa6, 0x90, 0xa6, 0xa8, 0xa6, 0xc0, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, + 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0x80, 0xa7, + 0x98, 0xa7, 0x40, 0xa9, 0x58, 0xa9, 0x70, 0xa9, 0x88, 0xa9, 0xa0, 0xa9, + 0xb8, 0xa9, 0xd0, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, 0x88, 0xaa, 0xc8, 0xaa, + 0xd8, 0xaa, 0x98, 0xab, 0x28, 0xac, 0x68, 0xac, 0xb0, 0xac, 0x48, 0xad, + 0x90, 0xad, 0xa0, 0xad, 0xb0, 0xad, 0xc0, 0xad, 0x40, 0xae, 0x58, 0xae, + 0xd8, 0xae, 0x78, 0xaf, 0x88, 0xaf, 0x98, 0xaf, 0x00, 0x80, 0x07, 0x00, + 0x6c, 0x00, 0x00, 0x00, 0x30, 0xa0, 0x68, 0xa0, 0x80, 0xa0, 0x98, 0xa0, + 0xb0, 0xa0, 0x30, 0xa1, 0x70, 0xa1, 0x88, 0xa1, 0xc8, 0xa2, 0x68, 0xa3, + 0x88, 0xa3, 0x20, 0xa4, 0x58, 0xa4, 0x18, 0xa5, 0x30, 0xa5, 0x88, 0xa5, + 0xa0, 0xa5, 0x10, 0xa6, 0x28, 0xa6, 0x40, 0xa6, 0x78, 0xa6, 0xa8, 0xa6, + 0xd8, 0xa6, 0xf0, 0xa6, 0x88, 0xa7, 0xa0, 0xa7, 0xb8, 0xa7, 0xd0, 0xa7, + 0xe8, 0xa7, 0xf8, 0xa8, 0x58, 0xa9, 0xd0, 0xa9, 0xe0, 0xaa, 0xf8, 0xaa, + 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, + 0xe0, 0xad, 0xf8, 0xad, 0x10, 0xae, 0x28, 0xae, 0x40, 0xae, 0x58, 0xae, + 0x70, 0xae, 0xa8, 0xae, 0xb8, 0xaf, 0xd0, 0xaf, 0x00, 0x90, 0x07, 0x00, + 0x28, 0x00, 0x00, 0x00, 0xd0, 0xa2, 0x38, 0xa3, 0x58, 0xa3, 0x70, 0xa3, + 0x88, 0xa3, 0xa0, 0xa3, 0xb8, 0xa3, 0xc0, 0xa4, 0x30, 0xa5, 0x48, 0xa5, + 0x60, 0xa5, 0x78, 0xa5, 0x90, 0xa5, 0xa8, 0xa5, 0xe8, 0xa5, 0x78, 0xa6, + 0x00, 0xa0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x10, 0xa1, 0x78, 0xa1, + 0x78, 0xa6, 0x90, 0xa6, 0xa8, 0xa6, 0xc0, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, + 0x60, 0xa8, 0xb8, 0xa9, 0xc8, 0xa9, 0xd8, 0xa9, 0xe8, 0xa9, 0xf8, 0xa9, + 0x08, 0xaa, 0x18, 0xaa, 0x28, 0xaa, 0x38, 0xaa, 0x48, 0xaa, 0x58, 0xaa, + 0x68, 0xaa, 0x78, 0xaa, 0x88, 0xaa, 0x98, 0xaa, 0xa8, 0xaa, 0xb8, 0xaa, + 0xc8, 0xaa, 0xd8, 0xaa, 0xe8, 0xaa, 0xf8, 0xaa, 0x08, 0xab, 0x18, 0xab, + 0x28, 0xab, 0x38, 0xab, 0x48, 0xab, 0x58, 0xab, 0x68, 0xab, 0x78, 0xab, + 0x88, 0xab, 0x98, 0xab, 0xa8, 0xab, 0x88, 0xad, 0x00, 0xb0, 0x07, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0xa0, 0xaf, 0xf8, 0xaf, 0x00, 0xc0, 0x07, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x68, 0xa1, 0x80, 0xa1, 0xd0, 0xa1, 0xe0, 0xa1, + 0xf0, 0xa1, 0x00, 0xa2, 0x10, 0xa2, 0x40, 0xa2, 0x78, 0xa2, 0xc0, 0xa2, + 0x10, 0xa3, 0x48, 0xa3, 0x60, 0xa3, 0xa8, 0xa3, 0x30, 0xa4, 0x00, 0x00, + 0x00, 0xd0, 0x07, 0x00, 0x54, 0x00, 0x00, 0x00, 0x70, 0xa0, 0x00, 0xa1, + 0x18, 0xa1, 0x50, 0xa1, 0x80, 0xa1, 0x98, 0xa1, 0xb0, 0xa1, 0xc8, 0xa1, + 0xe0, 0xa1, 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, 0x68, 0xa2, 0x80, 0xa2, + 0x98, 0xa2, 0xb0, 0xa2, 0xc8, 0xa2, 0x00, 0xa3, 0x30, 0xa3, 0x48, 0xa3, + 0x60, 0xa3, 0x78, 0xa3, 0xb0, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, 0xf8, 0xa3, + 0x10, 0xa4, 0x28, 0xa4, 0xb8, 0xa4, 0x00, 0xab, 0x10, 0xab, 0x40, 0xab, + 0x58, 0xab, 0xa0, 0xab, 0xb0, 0xab, 0xc0, 0xab, 0xe8, 0xab, 0x20, 0xac, + 0x00, 0xe0, 0x07, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0xa1, 0x70, 0xa1, + 0x80, 0xa1, 0x90, 0xa1, 0xe0, 0xa1, 0xf0, 0xa1, 0x00, 0xa2, 0x10, 0xa2, + 0x20, 0xa2, 0x48, 0xa2, 0x58, 0xa2, 0x68, 0xa2, 0xd0, 0xa2, 0xe0, 0xa2, + 0xf0, 0xa2, 0x50, 0xa3, 0x90, 0xa3, 0xe0, 0xa3, 0xf0, 0xa3, 0x28, 0xa4, + 0x38, 0xa4, 0x60, 0xa4, 0x70, 0xa4, 0xa8, 0xa4, 0xc0, 0xa4, 0x18, 0xa5, + 0x88, 0xab, 0xa0, 0xab, 0xd8, 0xab, 0x28, 0xac, 0x70, 0xac, 0x80, 0xac, + 0x08, 0xae, 0x40, 0xae, 0x78, 0xae, 0x90, 0xae, 0xd0, 0xae, 0xe8, 0xae, + 0x20, 0xaf, 0x38, 0xaf, 0x50, 0xaf, 0x68, 0xaf, 0x80, 0xaf, 0x00, 0x00, + 0x00, 0xf0, 0x07, 0x00, 0x7c, 0x00, 0x00, 0x00, 0xd0, 0xa3, 0xe0, 0xa3, + 0x18, 0xa4, 0x28, 0xa4, 0x58, 0xa4, 0x68, 0xa4, 0x80, 0xa4, 0xc0, 0xa4, + 0xd0, 0xa4, 0xe8, 0xa4, 0x40, 0xa5, 0x50, 0xa5, 0x68, 0xa5, 0x80, 0xa5, + 0x98, 0xa5, 0xe8, 0xa5, 0xf8, 0xa5, 0x28, 0xa6, 0x60, 0xa6, 0x70, 0xa6, + 0xc0, 0xa6, 0xd0, 0xa6, 0x18, 0xa7, 0x28, 0xa7, 0xa8, 0xa7, 0xc0, 0xa7, + 0xd8, 0xa7, 0xf0, 0xa7, 0x88, 0xa8, 0xa0, 0xa8, 0xb8, 0xa8, 0xd0, 0xa8, + 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0xe8, 0xaa, 0x00, 0xab, 0x40, 0xab, + 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, 0xc8, 0xab, 0x48, 0xac, 0x60, 0xac, + 0x78, 0xac, 0x90, 0xac, 0xa8, 0xac, 0xc0, 0xac, 0xd8, 0xac, 0xf0, 0xac, + 0x08, 0xad, 0x20, 0xad, 0x38, 0xad, 0x50, 0xad, 0x68, 0xad, 0xb8, 0xaf, + 0xd0, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x24, 0x00, 0x00, 0x00, + 0xc8, 0xa0, 0xe0, 0xa0, 0xf8, 0xa0, 0x48, 0xa1, 0xe0, 0xa1, 0x60, 0xa2, + 0xe8, 0xa2, 0x40, 0xa3, 0x58, 0xa3, 0x70, 0xa3, 0x88, 0xa3, 0xa0, 0xa3, + 0xb8, 0xa3, 0xd0, 0xa3, 0x00, 0x10, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0xe0, 0xa1, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x20, 0xa7, 0x28, 0xa7, + 0x00, 0xc0, 0x1e, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x90, 0xa8, + 0x98, 0xa8, 0xa0, 0xa8, 0xb0, 0xa8, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0xe8, 0xae, 0x00, 0x00, 0x00, 0xc0, 0x64, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x00, 0x00, ]; From e1cf324302b711b47953d6ef59c36f3f6297193b Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Fri, 13 Mar 2026 18:38:13 +0100 Subject: [PATCH 5/8] ughhh --- network/asm/drivers/usb/init.s | 8 ++++---- network/src/driver/usb_msd/mod.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/network/asm/drivers/usb/init.s b/network/asm/drivers/usb/init.s index e03b0c3b..69af8f52 100644 --- a/network/asm/drivers/usb/init.s +++ b/network/asm/drivers/usb/init.s @@ -28,8 +28,8 @@ global asm_xhci_bios_handoff ; EAX bits 31:16 = HCIVERSION (should be >= 0x0100) ; EAX = 0 if bar reads back all-F (unmapped/dead) asm_usb_host_probe: - sub rsp, 40 push rbx + sub rsp, 32 ; 1 push + ret = 16; +32 = 48; 48%16=0. aligned. mov rbx, rcx @@ -37,9 +37,9 @@ asm_usb_host_probe: call asm_mmio_read32 cmp eax, 0xFFFFFFFF je .probe_dead - ; sanity: CAPLENGTH must be ≥ 0x20 (minimum xHCI cap region) + ; sanity: CAPLENGTH must be ≥ 0x10 (relaxed — some controllers are small) movzx edx, al - cmp edx, 0x20 + cmp edx, 0x10 jb .probe_dead ; HCIVERSION must be ≥ 0x0100 shr eax, 16 @@ -54,8 +54,8 @@ asm_usb_host_probe: .probe_dead: xor eax, eax .probe_out: + add rsp, 32 pop rbx - add rsp, 40 ret ; ─────────────────────────────────────────────────────────────────────────── diff --git a/network/src/driver/usb_msd/mod.rs b/network/src/driver/usb_msd/mod.rs index 540cef0b..2a32d71a 100644 --- a/network/src/driver/usb_msd/mod.rs +++ b/network/src/driver/usb_msd/mod.rs @@ -267,19 +267,48 @@ unsafe fn tsc_delay(tsc_freq: u64, ms: u64) { // Implementation // ═══════════════════════════════════════════════════════════════════════════ +// serial hex dump for diagnostics. not pretty, don't care. +fn dbg(s: &str) { + crate::serial_str(s); +} +fn dbg_hex32(v: u32) { + const HEX: &[u8; 16] = b"0123456789abcdef"; + crate::serial_str("0x"); + for i in (0..8).rev() { + crate::serial_byte(HEX[((v >> (i * 4)) & 0xF) as usize]); + } +} +fn dbg_hex64(v: u64) { + const HEX: &[u8; 16] = b"0123456789abcdef"; + crate::serial_str("0x"); + for i in (0..16).rev() { + crate::serial_byte(HEX[((v >> (i * 4)) & 0xF) as usize]); + } +} + impl UsbMsdDriver { // ─── public entry point ────────────────────────────────────────────── /// Initialise xHCI controller, enumerate first USB mass-storage device, /// run SCSI READ CAPACITY. Returns a ready-to-read driver or an error. pub unsafe fn new(mmio_base: u64, config: UsbMsdConfig) -> Result { + dbg("[USB] new() mmio="); + dbg_hex64(mmio_base); + dbg(" tsc="); + dbg_hex64(config.tsc_freq); + dbg("\n"); + if mmio_base == 0 || config.tsc_freq == 0 { + dbg("[USB] FAIL: invalid config\n"); return Err(UsbMsdInitError::InvalidConfig); } let mut drv = Self::init_controller(mmio_base, config.tsc_freq)?; + dbg("[USB] controller init OK, enumerating...\n"); drv.enumerate_and_configure()?; + dbg("[USB] enumeration OK, SCSI init...\n"); drv.scsi_init()?; + dbg("[USB] SCSI init OK, driver ready\n"); Ok(drv) } From 5b3a0a8ef76bab20f80b2e932baf40fc5c482a2d Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Sun, 15 Mar 2026 13:58:16 +0100 Subject: [PATCH 6/8] stuff --- Cargo.lock | 1 + bootloader/asm/keyboard/ps2.s | 2 - bootloader/src/baremetal.rs | 284 +++++++- bootloader/src/storage.rs | 106 +++ bootloader/src/tui/desktop.rs | 9 +- cli/src/main.rs | 124 +++- hwinit/asm/cpu/context_switch.s | 15 +- hwinit/src/cpu/acpi.rs | 133 +++- hwinit/src/cpu/ap_boot.rs | 34 +- hwinit/src/cpu/apic.rs | 58 +- hwinit/src/cpu/gdt.rs | 24 +- hwinit/src/cpu/per_cpu.rs | 59 +- hwinit/src/memory.rs | 2 +- hwinit/src/paging/mod.rs | 7 + hwinit/src/platform.rs | 69 +- network/Cargo.toml | 1 + network/asm/drivers/usb/init.s | 89 +-- network/src/boot/block_probe.rs | 30 +- network/src/driver/usb_msd/mod.rs | 788 ++++++++++++++++++++--- persistent/src/pe/embedded_reloc_data.rs | 211 +++--- setup-dev.sh | 45 +- 21 files changed, 1743 insertions(+), 348 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f433f252..63a1956e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,6 +266,7 @@ dependencies = [ "linked_list_allocator", "morpheus-core", "morpheus-display", + "morpheus-hwinit", "smoltcp", "spin", ] diff --git a/bootloader/asm/keyboard/ps2.s b/bootloader/asm/keyboard/ps2.s index c5abc370..4e22d274 100644 --- a/bootloader/asm/keyboard/ps2.s +++ b/bootloader/asm/keyboard/ps2.s @@ -34,7 +34,6 @@ asm_ps2_write_cmd: pause dec r8 jnz .wcmd_ibf_wait - ret .wcmd_send: mov al, cl out PS2_CMD, al @@ -50,7 +49,6 @@ asm_ps2_write_data: pause dec r8 jnz .wdat_ibf_wait - ret .wdat_send: mov al, cl out PS2_DATA, al diff --git a/bootloader/src/baremetal.rs b/bootloader/src/baremetal.rs index 5bc20eb8..54b51cb6 100644 --- a/bootloader/src/baremetal.rs +++ b/bootloader/src/baremetal.rs @@ -50,6 +50,51 @@ static BAREMETAL_MODE: AtomicBool = AtomicBool::new(false); // After that every puts() in hwinit mirrors to the screen in real-time. static mut LIVE_CONSOLE: Option = None; +static mut PRE_EBS_HELIX_BASE: u64 = 0; +static mut PRE_EBS_HELIX_SIZE: usize = 0; +static mut PRE_EBS_HELIX_SECTOR_SIZE: u32 = 512; + +const LOADED_IMAGE_PROTOCOL_GUID: [u8; 16] = [ + 0xA1, 0x31, 0x1B, 0x5B, 0x62, 0x95, 0xD2, 0x11, 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, + 0x72, 0x3B, +]; +const SIMPLE_FILE_SYSTEM_PROTOCOL_GUID: [u8; 16] = [ + 0x22, 0x5B, 0x4E, 0x96, 0x59, 0x64, 0xD2, 0x11, 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, + 0x72, 0x3B, +]; +const ACPI_20_TABLE_GUID: [u8; 16] = [ + 0x71, 0xE8, 0x68, 0x88, 0xF1, 0xE4, 0xD3, 0x11, 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, + 0x88, 0x81, +]; +const ACPI_10_TABLE_GUID: [u8; 16] = [ + 0x30, 0x2D, 0x9D, 0xEB, 0x88, 0x2D, 0xD3, 0x11, 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, + 0xC1, 0x4D, +]; +const PRE_EBS_STAGE_MAX_BYTES: u64 = 512 * 1024 * 1024; +const HELIX_IMG_SECTOR_SIZE: u32 = 512; +const EFI_FILE_MODE_READ: u64 = 0x0000_0000_0000_0001; +const HELIX_IMG_PATH: [u16; 20] = [ + b'\\' as u16, + b'm' as u16, + b'o' as u16, + b'r' as u16, + b'p' as u16, + b'h' as u16, + b'e' as u16, + b'u' as u16, + b's' as u16, + b'\\' as u16, + b'h' as u16, + b'e' as u16, + b'l' as u16, + b'i' as u16, + b'x' as u16, + b'.' as u16, + b'i' as u16, + b'm' as u16, + b'g' as u16, + 0, +]; /// Called by the hwinit serial hook for every byte emitted via `puts()`. /// Writes directly to the TextConsole backed by the GOP framebuffer. @@ -97,6 +142,20 @@ pub fn get_framebuffer_info() -> Option { } } +pub unsafe fn take_pre_ebs_helix_image() -> Option<(u64, usize, u32)> { + if PRE_EBS_HELIX_BASE == 0 || PRE_EBS_HELIX_SIZE == 0 { + return None; + } + let out = ( + PRE_EBS_HELIX_BASE, + PRE_EBS_HELIX_SIZE, + PRE_EBS_HELIX_SECTOR_SIZE, + ); + PRE_EBS_HELIX_BASE = 0; + PRE_EBS_HELIX_SIZE = 0; + Some(out) +} + // BSoD CRASH HOOK — called by exception handlers in hwinit /// Callback invoked by hwinit exception handlers to display the crash screen. @@ -107,10 +166,59 @@ unsafe fn bsod_crash_hook(info: &morpheus_hwinit::CrashInfo) { crate::bsod::show_crash_screen(info); } +#[inline(always)] +unsafe fn pe_image_size(image_base: u64) -> u64 { + let pe_off_ptr = (image_base + 0x3C) as *const u32; + let pe_off = core::ptr::read_unaligned(pe_off_ptr) as u64; + // SizeOfImage is at offset 56 in the PE32+ optional header. + // PE signature (4) + COFF header (20) + offset 56 into optional header = +80. + let size_of_image_ptr = (image_base + pe_off + 4 + 20 + 56) as *const u32; + core::ptr::read_unaligned(size_of_image_ptr) as u64 +} + +#[repr(C)] +struct EfiLoadedImage { + _revision: u32, + _parent_handle: *mut (), + _system_table: *mut (), + device_handle: *mut (), +} + +#[repr(C)] +struct EfiSimpleFileSystem { + _revision: u64, + open_volume: + extern "efiapi" fn(*mut EfiSimpleFileSystem, *mut *mut EfiFileProtocol) -> usize, +} + +#[repr(C)] +struct EfiConfigurationTable { + vendor_guid: [u8; 16], + vendor_table: *const (), +} + +#[repr(C)] +struct EfiFileProtocol { + _revision: u64, + open: extern "efiapi" fn( + *mut EfiFileProtocol, + *mut *mut EfiFileProtocol, + *const u16, + u64, + u64, + ) -> usize, + close: extern "efiapi" fn(*mut EfiFileProtocol) -> usize, + _delete: usize, + read: extern "efiapi" fn(*mut EfiFileProtocol, *mut usize, *mut u8) -> usize, + _write: usize, + get_position: extern "efiapi" fn(*mut EfiFileProtocol, *mut u64) -> usize, + set_position: extern "efiapi" fn(*mut EfiFileProtocol, u64) -> usize, +} + // THE ENTRY POINT — NEVER RETURNS pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! { - use morpheus_hwinit::serial::{log_error, log_info, log_ok, puts}; + use morpheus_hwinit::serial::{log_error, log_info, log_ok, log_warn, puts}; // First serial output — if you see this, our binary loaded and ran. log_info("BOOT", 901, "enter baremetal"); @@ -138,6 +246,8 @@ pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! { _stderr: *const (), _runtime: *const (), boot_services: *const MinBootServices, + number_of_table_entries: usize, + configuration_table: *const EfiConfigurationTable, } #[repr(C)] @@ -151,15 +261,62 @@ pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! { extern "efiapi" fn(*mut usize, *mut u8, *mut usize, *mut usize, *mut u32) -> usize, _alloc_pool: usize, _free_pool: usize, - // slots 8..26 (create_event → unload_image) = 19 × 8 = 152 bytes - // puts exit_boot_services at offset 232, matching UEFI spec exactly. - _padding: [usize; 19], + _create_event: usize, + _set_timer: usize, + _wait_for_event: usize, + _signal_event: usize, + _close_event: usize, + _check_event: usize, + _install_protocol_interface: usize, + _reinstall_protocol_interface: usize, + _uninstall_protocol_interface: usize, + handle_protocol: + extern "efiapi" fn(*mut (), *const [u8; 16], *mut *mut ()) -> usize, + _reserved: usize, + _register_protocol_notify: usize, + _locate_handle: usize, + _locate_device_path: usize, + _install_configuration_table: usize, + _load_image: usize, + _start_image: usize, + _exit: usize, + _unload_image: usize, exit_boot_services: extern "efiapi" fn(*mut (), usize) -> usize, } let st = &*(config.system_table as *const MinSystemTable); let bs = &*st.boot_services; + let mut acpi_rsdp_phys = 0u64; + if !st.configuration_table.is_null() { + let tables = core::slice::from_raw_parts(st.configuration_table, st.number_of_table_entries); + for t in tables { + if t.vendor_guid == ACPI_20_TABLE_GUID { + acpi_rsdp_phys = t.vendor_table as u64; + break; + } + } + if acpi_rsdp_phys == 0 { + for t in tables { + if t.vendor_guid == ACPI_10_TABLE_GUID { + acpi_rsdp_phys = t.vendor_table as u64; + break; + } + } + } + } + if acpi_rsdp_phys != 0 { + log_ok("ACPI", 901, "found RSDP via UEFI config table"); + } else { + log_warn("ACPI", 901, "RSDP not present in UEFI config table"); + } + + // We still need this for hwinit image reservation post-EBS. + extern "C" { + static __ImageBase: u8; + } + let image_base = &__ImageBase as *const u8 as u64; + log_info("EBS", 902, "allocating kernel stack"); let mut stack_base: u64 = 0; let status = (bs.allocate_pages)(0, 2, STACK_PAGES, &mut stack_base); @@ -174,8 +331,105 @@ pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! { let stack_top = stack_base + STACK_SIZE as u64; log_info("EBS", 903, "stack ready; fetching memory map"); + // Pre-EBS media stage: load /morpheus/helix.img from ESP into RAM. + let mut loaded_image_ptr: *mut () = core::ptr::null_mut(); + let li_status = (bs.handle_protocol)( + config.image_handle, + &LOADED_IMAGE_PROTOCOL_GUID, + &mut loaded_image_ptr, + ); + if li_status == 0 && !loaded_image_ptr.is_null() { + let li = &*(loaded_image_ptr as *const EfiLoadedImage); + let mut sfs_ptr: *mut () = core::ptr::null_mut(); + let sfs_status = (bs.handle_protocol)( + li.device_handle, + &SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, + &mut sfs_ptr, + ); + if sfs_status == 0 && !sfs_ptr.is_null() { + let sfs = sfs_ptr as *mut EfiSimpleFileSystem; + let mut root: *mut EfiFileProtocol = core::ptr::null_mut(); + if ((*sfs).open_volume)(sfs, &mut root) == 0 && !root.is_null() { + let mut img: *mut EfiFileProtocol = core::ptr::null_mut(); + let open_rc = ((*root).open)( + root, + &mut img, + HELIX_IMG_PATH.as_ptr(), + EFI_FILE_MODE_READ, + 0, + ); + if open_rc == 0 && !img.is_null() { + // Position at EOF to query file size, then rewind. + let _ = ((*img).set_position)(img, u64::MAX); + let mut file_size = 0u64; + let _ = ((*img).get_position)(img, &mut file_size); + let _ = ((*img).set_position)(img, 0); + + if file_size > 0 && file_size <= PRE_EBS_STAGE_MAX_BYTES { + let mut alloc_bytes = file_size as usize; + let rem = alloc_bytes % HELIX_IMG_SECTOR_SIZE as usize; + if rem != 0 { + alloc_bytes += (HELIX_IMG_SECTOR_SIZE as usize) - rem; + } + let mut stage_base: u64 = 0; + let stage_pages = alloc_bytes.div_ceil(4096); + let alloc_rc = (bs.allocate_pages)(0, 2, stage_pages, &mut stage_base); + if alloc_rc == 0 && stage_base != 0 { + let mut off = 0usize; + let mut ok = true; + while off < file_size as usize { + let remaining = (file_size as usize) - off; + let mut want = core::cmp::min(1024 * 1024, remaining); + let rc = ((*img).read)( + img, + &mut want, + (stage_base as *mut u8).add(off), + ); + if rc != 0 { + ok = false; + break; + } + // want == 0 means EOF — nothing more to read. + if want == 0 { + break; + } + off += want; + // don't break on short reads — some UEFI firmware caps per-call + // transfer size. just keep looping with what we got. + } + + if ok && off > 0 { + let usable = off - (off % HELIX_IMG_SECTOR_SIZE as usize); + if usable > 0 { + PRE_EBS_HELIX_BASE = stage_base; + PRE_EBS_HELIX_SIZE = usable; + PRE_EBS_HELIX_SECTOR_SIZE = HELIX_IMG_SECTOR_SIZE; + log_ok("EBS", 901, "pre-EBS loaded /morpheus/helix.img to RAM"); + } else { + log_warn("EBS", 901, "helix.img size not sector-aligned"); + } + } else { + log_warn("EBS", 901, "failed reading /morpheus/helix.img"); + } + } else { + log_warn("EBS", 901, "pre-EBS stage alloc failed"); + } + } else { + log_warn("EBS", 901, "helix.img missing/empty/too-large"); + } + let _ = ((*img).close)(img); + } else { + log_warn("EBS", 901, "/morpheus/helix.img not found on ESP"); + } + let _ = ((*root).close)(root); + } + } + } + + log_info("EBS", 903, "fetching memory map"); + // memory map + exitbootservices - static mut MMAP_BUF: [u8; 32768] = [0u8; 32768]; + static mut MMAP_BUF: [u8; 65536] = [0u8; 65536]; static mut MMAP_SIZE: usize = 0; static mut DESC_SIZE: usize = 0; static mut DESC_VER: u32 = 0; @@ -233,28 +487,13 @@ pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! { // Switch allocator to our own heap (UEFI pool is gone) crate::uefi_allocator::switch_to_post_ebs(); - // switch stack + // Switch once, after EBS, onto our own loaderdata stack. core::arch::asm!("mov rsp, {0}", in(reg) stack_top, options(nostack)); log_ok("BOOT", 906, "machine ownership transferred"); // compute pe image bounds for buddy-allocator reservation - // __ImageBase is defined by LLD for every PE/COFF binary. From it we - // read the PE optional header's SizeOfImage to determine the full - // virtual extent (including BSS) that must be kept out of the free pool. - extern "C" { - static __ImageBase: u8; - } - let image_base = &__ImageBase as *const u8 as u64; - let image_pages = { - let pe_off_ptr = (image_base + 0x3C) as *const u32; - let pe_off = core::ptr::read_unaligned(pe_off_ptr) as u64; - // SizeOfImage is at offset 56 in the PE32+ optional header. - // PE signature (4) + COFF header (20) + offset 56 into optional header = +80. - let size_of_image_ptr = (image_base + pe_off + 4 + 20 + 56) as *const u32; - let size_of_image = core::ptr::read_unaligned(size_of_image_ptr) as u64; - size_of_image.div_ceil(4096) - }; + let image_pages = pe_image_size(image_base).div_ceil(4096); // hwinit — gdt, idt, pic, heap, tsc, dma, bus mastering let hwinit_cfg = morpheus_hwinit::SelfContainedConfig { @@ -266,6 +505,7 @@ pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! { image_pages, stack_base, stack_pages: STACK_PAGES as u64, + acpi_rsdp_phys, }; let platform = match morpheus_hwinit::platform_init_selfcontained(hwinit_cfg) { diff --git a/bootloader/src/storage.rs b/bootloader/src/storage.rs index 765fa8b3..7bb7278c 100644 --- a/bootloader/src/storage.rs +++ b/bootloader/src/storage.rs @@ -647,6 +647,37 @@ fn spinner_done() { pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { log_info("STORAGE", 822, "probing block devices"); + if let Some((base, size, sector_size)) = crate::baremetal::take_pre_ebs_helix_image() { + log_info("STORAGE", 822, "using pre-EBS staged Helix image"); + RAM_HELIX_DEVICE = Some(MemBlockDevice::new(base as *mut u8, size, sector_size)); + if let Some(mem_dev) = RAM_HELIX_DEVICE.as_mut() { + match morpheus_helix::vfs::global::replace_root_device( + MemBlockDevice::into_raw(mem_dev), + false, + ) { + Ok(()) => { + if root_path_exists("/bin/init") { + PERSISTENT_READY = true; + log_ok("STORAGE", 827, "mounted pre-EBS staged Helix root"); + return; + } + log_warn( + "STORAGE", + 827, + "pre-EBS staged root missing /bin/init; falling back to probe", + ); + } + Err(_) => { + log_warn( + "STORAGE", + 827, + "pre-EBS staged root mount failed; falling back to probe", + ); + } + } + } + } + // Dump PCI bus to serial for device identification diagnostics // (Commenting out PCI dump — enable if debugging PCI device discovery) // dump_pci_devices(); @@ -709,6 +740,21 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { None => continue, }; + match detected { + DetectedBlockDevice::Ahci(_) => { + log_info("STORAGE", 824, "probing AHCI candidate"); + } + DetectedBlockDevice::Sdhci(_) => { + log_info("STORAGE", 824, "probing SDHCI candidate"); + } + DetectedBlockDevice::UsbMsd(_) => { + log_info("STORAGE", 824, "probing USB xHCI/MSD candidate"); + } + DetectedBlockDevice::VirtIO { .. } => { + log_info("STORAGE", 824, "probing VirtIO-blk candidate"); + } + } + // Map high-address MMIO BARs before driver init touches them if let DetectedBlockDevice::VirtIO { pci_addr, .. } = detected { map_virtio_bars(pci_addr.bus, pci_addr.device, pci_addr.function); @@ -838,6 +884,66 @@ pub unsafe fn init_persistent_storage(dma: &DmaRegion, tsc_freq: u64) { UsbMsdInitError::ControllerInitFailed => { log_warn("STORAGE", 825, "USB-MSD init failed: controller init failed"); } + UsbMsdInitError::ControllerProbeFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: controller probe failed"); + } + UsbMsdInitError::ControllerResetFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: controller reset failed"); + } + UsbMsdInitError::ControllerScratchpadUnsupported => { + log_warn("STORAGE", 825, "USB-MSD init failed: scratchpad requirement unsupported"); + } + UsbMsdInitError::ControllerStartFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: controller start failed (HCH stuck)"); + } + UsbMsdInitError::HubUnsupported => { + log_warn("STORAGE", 825, "USB-MSD init failed: USB hub detected; downstream hub traversal not implemented"); + } + UsbMsdInitError::PortResetFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: port reset failed"); + } + UsbMsdInitError::PortResetTimeout => { + log_warn("STORAGE", 825, "USB-MSD init failed: port reset timeout"); + } + UsbMsdInitError::PortResetHotCmdTimeout => { + log_warn("STORAGE", 825, "USB-MSD init failed: hot reset command timeout"); + } + UsbMsdInitError::PortResetHotSettleTimeout => { + log_warn("STORAGE", 825, "USB-MSD init failed: hot reset settle timeout"); + } + UsbMsdInitError::PortResetWarmTimeout => { + log_warn("STORAGE", 825, "USB-MSD init failed: warm reset timeout"); + } + UsbMsdInitError::PortResetNoLink => { + log_warn("STORAGE", 825, "USB-MSD init failed: USB link not usable"); + } + UsbMsdInitError::EnableSlotFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: enable-slot command failed"); + } + UsbMsdInitError::AddressDeviceFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: address-device command failed"); + } + UsbMsdInitError::DeviceDescriptorFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: GET_DESCRIPTOR(device) failed"); + } + UsbMsdInitError::ConfigDescriptorFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: GET_DESCRIPTOR(config) failed"); + } + UsbMsdInitError::MassStorageProtocolUnsupported => { + log_warn("STORAGE", 825, "USB-MSD init failed: mass-storage protocol unsupported (need BOT)"); + } + UsbMsdInitError::NoBotMassStorageInterface => { + log_warn("STORAGE", 825, "USB-MSD init failed: no BOT mass-storage interface found"); + } + UsbMsdInitError::ActivePortsNoConnectedDevice => { + log_warn("STORAGE", 825, "USB-MSD init failed: root ports active but no connected device detected"); + } + UsbMsdInitError::SetConfigurationFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: SET_CONFIGURATION failed"); + } + UsbMsdInitError::ConfigureEndpointsFailed => { + log_warn("STORAGE", 825, "USB-MSD init failed: configure endpoint command failed"); + } UsbMsdInitError::DeviceEnumerationFailed => { log_warn("STORAGE", 825, "USB-MSD init failed: device enumeration failed"); } diff --git a/bootloader/src/tui/desktop.rs b/bootloader/src/tui/desktop.rs index 618e03bc..a861fed7 100644 --- a/bootloader/src/tui/desktop.rs +++ b/bootloader/src/tui/desktop.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use morpheus_display::types::FramebufferInfo; -use morpheus_hwinit::serial::{log_error, log_info, log_ok, puts}; +use morpheus_hwinit::serial::{clear_live_console_hook, log_error, log_info, log_ok, puts}; use super::input::Keyboard; @@ -88,6 +88,7 @@ fn show_boot_log_screen(keyboard: &mut Keyboard) { puts("Press any key to launch msh..."); keyboard.wait_for_key(); puts("\n"); + clear_live_console_hook(); } pub fn run_desktop(_display_info: &FramebufferInfo) -> ! { @@ -148,7 +149,7 @@ pub fn run_desktop(_display_info: &FramebufferInfo) -> ! { let device = (raw >> 8) & 0xFF; let byte = (raw & 0xFF) as u8; - if device == 0x03 && !keyboard.aux_as_kbd() { + if device == 0x03 { // Mouse byte if let Some(pkt) = mouse.feed(byte) { morpheus_hwinit::mouse::accumulate(pkt.dx, pkt.dy, pkt.buttons); @@ -156,6 +157,10 @@ pub fn run_desktop(_display_info: &FramebufferInfo) -> ! { continue; } + if device != 0x01 { + continue; + } + // Keyboard byte — feed through the decoder if let Some(input) = keyboard.feed_raw(byte) { // Accept any non-zero unicode_char that fits in a u8. diff --git a/cli/src/main.rs b/cli/src/main.rs index 677f77a4..fc3f7b6b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -115,6 +115,7 @@ fn usage() { eprintln!(); eprintln!("USAGE:"); eprintln!(" morpheus-cli inject [--dest /bin/name]"); + eprintln!(" morpheus-cli pack [--max-mb N]"); eprintln!(" morpheus-cli ls [path]"); eprintln!(" morpheus-cli mkbin "); eprintln!(); @@ -123,9 +124,96 @@ fn usage() { " morpheus-cli inject testing/helix-data.img \\\n target/x86_64-morpheus/release/syscall-e2e" ); eprintln!(" morpheus-cli inject testing/helix-data.img my-app --dest /bin/app"); + eprintln!(" morpheus-cli pack /dev/sdb2 testing/helix.img --max-mb 384"); eprintln!(" morpheus-cli ls testing/helix-data.img /bin"); } +fn cmd_pack(disk: &str, output: &str, max_mb: u64) -> Result<(), String> { + if max_mb == 0 { + return Err("--max-mb must be > 0".to_string()); + } + + let mut dev = + FileBlockDevice::open(disk).map_err(|e| format!("cannot open '{}': {}", disk, e))?; + + let sb = recover_superblock(&mut dev, 0, SECTOR_SIZE) + .map_err(|e| format!("recover_superblock: {:?}", e))?; + + let mut stage_blocks = 2u64; + let log_hi = sb.log_end_block.saturating_add(1); + if log_hi > stage_blocks { + stage_blocks = log_hi; + } + + let data_hi = sb.data_start_block.saturating_add(sb.blocks_used); + if data_hi > stage_blocks { + stage_blocks = data_hi; + } + + if stage_blocks > sb.total_blocks { + stage_blocks = sb.total_blocks; + } + + if stage_blocks == 0 { + return Err("empty Helix footprint".to_string()); + } + + let mut bytes = stage_blocks + .checked_mul(sb.block_size as u64) + .ok_or_else(|| "footprint byte overflow".to_string())?; + + let max_bytes = max_mb + .checked_mul(1024) + .and_then(|v| v.checked_mul(1024)) + .ok_or_else(|| "max size overflow".to_string())?; + + if bytes > max_bytes { + bytes = max_bytes; + } + + let rem = bytes % SECTOR_SIZE as u64; + if rem != 0 { + bytes = bytes.saturating_sub(rem); + } + + if bytes == 0 { + return Err("packed image size resolved to 0 bytes".to_string()); + } + + let mut out = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(output) + .map_err(|e| format!("cannot create '{}': {}", output, e))?; + + const CHUNK: usize = 1024 * 1024; + let mut buf = vec![0u8; CHUNK]; + let mut copied = 0u64; + + while copied < bytes { + let remaining = (bytes - copied) as usize; + let n = remaining.min(CHUNK); + dev.file + .seek(SeekFrom::Start(copied)) + .map_err(|_| "seek failed".to_string())?; + dev.file + .read_exact(&mut buf[..n]) + .map_err(|_| "read failed".to_string())?; + out.write_all(&buf[..n]) + .map_err(|_| "write failed".to_string())?; + copied += n as u64; + } + + out.flush().map_err(|e| format!("flush failed: {}", e))?; + + println!("[pack] source : {}", disk); + println!("[pack] output : {}", output); + println!("[pack] bytes : {}", bytes); + println!("[pack] done"); + Ok(()) +} + // Mount an existing or fresh HelixFS from a FileBlockDevice. // Returns (device, mount_table). fn mount(disk: &str) -> Result<(FileBlockDevice, MountTable), String> { @@ -311,6 +399,19 @@ fn cmd_ls(disk: &str, path: &str) -> Result<(), String> { // mkbin command — pre-create /bin directory // ────────────────────────────────────────────────────────────────────────────── +// ────────────────────────────────────────────────────────────────────────────── +// format command — wipe and reformat HelixFS partition +// ────────────────────────────────────────────────────────────────────────────── + +fn cmd_format(disk: &str) -> Result<(), String> { + let mut dev = + FileBlockDevice::open(disk).map_err(|e| format!("cannot open '{}': {}", disk, e))?; + println!("[format] wiping and reformatting {}", disk); + do_format(&mut dev)?; + println!("[format] done — clean HelixFS ready"); + Ok(()) +} + fn cmd_mkbin(disk: &str) -> Result<(), String> { let (_dev, mut mt) = mount(disk)?; match vfs::vfs_mkdir(&mut mt, "/bin", 0) { @@ -343,7 +444,6 @@ fn main() { let disk = &args[2]; let binary = &args[3]; - // Default dest = /bin/ let default_dest = format!( "/bin/{}", Path::new(binary) @@ -356,7 +456,6 @@ fn main() { .find(|w| w[0] == "--dest") .map(|w| w[1].as_str()) .unwrap_or(&default_dest); - cmd_inject(disk, binary, dest) } "ls" => { @@ -375,6 +474,27 @@ fn main() { } cmd_mkbin(&args[2]) } + "pack" => { + if args.len() < 4 { + eprintln!("Usage: morpheus-cli pack [--max-mb N]"); + std::process::exit(1); + } + let disk = &args[2]; + let output = &args[3]; + let max_mb = args + .windows(2) + .find(|w| w[0] == "--max-mb") + .and_then(|w| w[1].parse::().ok()) + .unwrap_or(512); + cmd_pack(disk, output, max_mb) + } + "format" => { + if args.len() < 3 { + eprintln!("Usage: morpheus-cli format "); + std::process::exit(1); + } + cmd_format(&args[2]) + } _ => { usage(); std::process::exit(1); diff --git a/hwinit/asm/cpu/context_switch.s b/hwinit/asm/cpu/context_switch.s index f06d6080..420568ca 100644 --- a/hwinit/asm/cpu/context_switch.s +++ b/hwinit/asm/cpu/context_switch.s @@ -61,8 +61,8 @@ bits 64 default rel -; LAPIC EOI register (identity-mapped) -%define LAPIC_EOI_ADDR 0xFEE000B0 +; LAPIC EOI register offset from base +%define LAPIC_EOI_OFF 0x0B0 section .text @@ -121,11 +121,12 @@ irq_timer_isr: .skip_fxsave: ; ── ACK LAPIC (write 0 to EOI register) ────────────────────────────── - ; 0xFEE000B0 has bit 31 set. [imm32] in 64-bit mode sign-extends to - ; 0xFFFFFFFFFEE000B0. load into r11d: zero-extends, gives correct ptr. - ; r11 is already saved at [rsp+0x50] so clobbering it here is safe. - mov r11d, LAPIC_EOI_ADDR ; r11 = 0x00000000FEE000B0 - mov dword [r11], 0 ; EOI + ; Read the actual LAPIC base from per-CPU data (gs:[0x38]) instead of + ; hardcoding 0xFEE00000. Firmware can relocate the LAPIC base via + ; IA32_APIC_BASE MSR; the probe runs during BSP/AP init and stores + ; the result in PerCpu.lapic_base. + mov r11, [gs:0x38] ; lapic_base from PerCpu + mov dword [r11 + LAPIC_EOI_OFF], 0 ; EOI ; ── Call scheduler_tick(current_ctx: *const CpuContext) ────────────── ; MS x64 ABI: first arg in RCX. Need 32-byte shadow space on stack. diff --git a/hwinit/src/cpu/acpi.rs b/hwinit/src/cpu/acpi.rs index 3f946e50..7ed1c199 100644 --- a/hwinit/src/cpu/acpi.rs +++ b/hwinit/src/cpu/acpi.rs @@ -8,6 +8,7 @@ use crate::cpu::per_cpu::MAX_CPUS; use crate::serial::{put_hex32, puts}; +use core::sync::atomic::{AtomicU64, Ordering}; // ── RSDP ───────────────────────────────────────────────────────────────── @@ -72,6 +73,17 @@ struct MadtLocalApic { flags: u32, } +/// MADT entry type 9: Processor Local x2APIC. +#[repr(C, packed)] +struct MadtLocalX2Apic { + entry_type: u8, // 9 + length: u8, // 16 + _reserved: u16, + x2apic_id: u32, + flags: u32, + _acpi_processor_uid: u32, +} + // MADT Local APIC flags const LAPIC_ENABLED: u32 = 1 << 0; const LAPIC_ONLINE_CAPABLE: u32 = 1 << 1; @@ -93,7 +105,29 @@ impl ApLapicIds { } } -// ── RSDP scanning ──────────────────────────────────────────────────────── +#[inline(always)] +fn push_apic_id(result: &mut ApLapicIds, apic_id: u32, bsp_lapic_id: u32) { + if apic_id == bsp_lapic_id || result.count >= MAX_CPUS { + return; + } + for i in 0..result.count { + if result.ids[i] == apic_id { + return; + } + } + result.ids[result.count] = apic_id; + result.count += 1; +} + +// ── RSDP source ────────────────────────────────────────────────────────── + +static RSDP_PHYS_OVERRIDE: AtomicU64 = AtomicU64::new(0); + +pub fn set_rsdp_phys(rsdp_phys: u64) { + RSDP_PHYS_OVERRIDE.store(rsdp_phys, Ordering::Release); +} + +// ── Legacy RSDP scan helpers (kept for diagnostics only) ──────────────── /// Scan a physical memory range for the RSDP signature. /// Returns the physical address of the RSDP, or 0 on failure. @@ -140,7 +174,21 @@ unsafe fn find_rsdp() -> u64 { } // ── SDT traversal ──────────────────────────────────────────────────────── - +/// Validate an ACPI SDT by checksumming its entire reported length. +/// Returns true if all bytes sum to 0 (valid per ACPI spec). +unsafe fn validate_sdt_checksum(table_phys: u64) -> bool { + let header = table_phys as *const SdtHeader; + let length = core::ptr::read_unaligned(core::ptr::addr_of!((*header).length)) as usize; + // Sanity: reject implausible lengths (0, or > 1 MiB). + if length < SDT_HEADER_SIZE || length > 0x10_0000 { + return false; + } + let mut sum: u8 = 0; + for i in 0..length { + sum = sum.wrapping_add(*((table_phys + i as u64) as *const u8)); + } + sum == 0 +} /// Find a table with the given 4-byte signature in the RSDT (32-bit pointers). unsafe fn find_table_rsdt(rsdt_phys: u64, sig: &[u8; 4]) -> u64 { let header = rsdt_phys as *const SdtHeader; @@ -211,6 +259,17 @@ unsafe fn parse_madt(madt_phys: u64, bsp_lapic_id: u32) -> ApLapicIds { put_hex32(reported_lapic_addr); puts("\n"); + // Cross-check MADT LAPIC address against the probed IA32_APIC_BASE. + // A mismatch indicates firmware is lying about one of them. + let probed_base = crate::cpu::apic::lapic_base() as u32; + if reported_lapic_addr != 0 && reported_lapic_addr != probed_base { + puts("[ACPI] WARN: MADT LAPIC addr "); + put_hex32(reported_lapic_addr); + puts(" != probed base "); + put_hex32(probed_base); + puts("\n"); + } + // walk variable-length entries after the fixed header let mut offset = fixed_offset; while offset + 2 <= total_len { @@ -222,7 +281,7 @@ unsafe fn parse_madt(madt_phys: u64, bsp_lapic_id: u32) -> ApLapicIds { break; // malformed — bail } - // type 0 = Processor Local APIC + // type 0 = Processor Local APIC (xAPIC) if entry_type == 0 && entry_len >= 8 { let lapic_entry = entry_ptr as *const MadtLocalApic; let apic_id = (*lapic_entry).apic_id as u32; @@ -231,9 +290,20 @@ unsafe fn parse_madt(madt_phys: u64, bsp_lapic_id: u32) -> ApLapicIds { // enabled or online-capable let usable = (flags & LAPIC_ENABLED) != 0 || (flags & LAPIC_ONLINE_CAPABLE) != 0; - if usable && apic_id != bsp_lapic_id && result.count < MAX_CPUS { - result.ids[result.count] = apic_id; - result.count += 1; + if usable { + push_apic_id(&mut result, apic_id, bsp_lapic_id); + } + } + + // type 9 = Processor Local x2APIC + if entry_type == 9 && entry_len >= 16 { + let x2_entry = entry_ptr as *const MadtLocalX2Apic; + let apic_id = (*x2_entry).x2apic_id; + let flags = (*x2_entry).flags; + + let usable = (flags & LAPIC_ENABLED) != 0 || (flags & LAPIC_ONLINE_CAPABLE) != 0; + if usable { + push_apic_id(&mut result, apic_id, bsp_lapic_id); } } @@ -254,9 +324,9 @@ unsafe fn parse_madt(madt_phys: u64, bsp_lapic_id: u32) -> ApLapicIds { /// Memory must be identity-mapped (true after UEFI ExitBootServices + our paging). /// The ACPI tables must not have been overwritten. Call before reclaiming AcpiReclaim. pub unsafe fn discover_ap_lapic_ids(bsp_lapic_id: u32) -> ApLapicIds { - let rsdp_phys = find_rsdp(); + let rsdp_phys = RSDP_PHYS_OVERRIDE.load(Ordering::Acquire); if rsdp_phys == 0 { - crate::serial::log_warn("ACPI", 760, "RSDP not found; MADT discovery unavailable"); + crate::serial::log_warn("ACPI", 760, "UEFI RSDP pointer unavailable; MADT discovery unavailable"); return ApLapicIds::empty(); } @@ -270,30 +340,57 @@ pub unsafe fn discover_ap_lapic_ids(bsp_lapic_id: u32) -> ApLapicIds { let rsdp2 = rsdp_phys as *const Rsdp2; let xsdt = (*rsdp2).xsdt_address; if xsdt != 0 { - let _ = xsdt; - let found = find_table_xsdt(xsdt, &MADT_SIG); - if found != 0 { - found + if !validate_sdt_checksum(xsdt) { + crate::serial::log_warn("ACPI", 764, "XSDT checksum invalid; trying RSDT"); + let rsdt = (*rsdp).rsdt_address as u64; + if rsdt != 0 && validate_sdt_checksum(rsdt) { + find_table_rsdt(rsdt, &MADT_SIG) + } else { + 0 + } } else { - // XSDT didn't have MADT, try RSDT - find_table_rsdt((*rsdp).rsdt_address as u64, &MADT_SIG) + let found = find_table_xsdt(xsdt, &MADT_SIG); + if found != 0 { + found + } else { + // XSDT didn't have MADT, try RSDT + find_table_rsdt((*rsdp).rsdt_address as u64, &MADT_SIG) + } } } else { - find_table_rsdt((*rsdp).rsdt_address as u64, &MADT_SIG) + let rsdt = (*rsdp).rsdt_address as u64; + if rsdt != 0 && validate_sdt_checksum(rsdt) { + find_table_rsdt(rsdt, &MADT_SIG) + } else { + 0 + } } } else { - find_table_rsdt((*rsdp).rsdt_address as u64, &MADT_SIG) + let rsdt = (*rsdp).rsdt_address as u64; + if rsdt != 0 && validate_sdt_checksum(rsdt) { + find_table_rsdt(rsdt, &MADT_SIG) + } else { + 0 + } }; if madt_phys == 0 { crate::serial::log_warn("ACPI", 761, "MADT not found in RSDT/XSDT"); return ApLapicIds::empty(); } - let _ = madt_phys; + + // Validate MADT checksum before trusting its contents. + if !validate_sdt_checksum(madt_phys) { + crate::serial::log_warn("ACPI", 765, "MADT checksum invalid; skipping AP discovery"); + return ApLapicIds::empty(); + } let result = parse_madt(madt_phys, bsp_lapic_id); - let _ = result.count; + if result.count == 0 { + crate::serial::log_warn("ACPI", 763, "MADT parsed but no usable AP LAPIC entries"); + } + crate::serial::log_ok("ACPI", 762, "MADT parsed"); result diff --git a/hwinit/src/cpu/ap_boot.rs b/hwinit/src/cpu/ap_boot.rs index 935095c2..addc21b1 100644 --- a/hwinit/src/cpu/ap_boot.rs +++ b/hwinit/src/cpu/ap_boot.rs @@ -32,6 +32,7 @@ const AP_STACK_SIZE: u64 = 64 * 1024; const AP_WAIT_STEP_US: u64 = 50; const AP_WAIT_TIMEOUT_US: u64 = 20_000; const AP_BRUTE_SCAN_BUDGET_US: u64 = 3_000_000; +const AP_MADT_SCAN_BUDGET_US: u64 = 1_000_000; // These must match the .data section layout at the end of ap_trampoline.s.lapic_id // The trampoline data block starts at AP_TRAMPOLINE_PHYS + TRAMPOLINE_DATA_OFFSET. @@ -89,6 +90,14 @@ unsafe fn setup_trampoline() -> bool { core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nostack, nomem)); let kernel_cr3 = cr3 & 0x000F_FFFF_FFFF_F000; + // The 32-bit protected mode trampoline loads CR3 via `mov eax, [0x8F00]` + // which only reads the low 32 bits. If the kernel page tables are above + // 4 GB, the AP would load a truncated CR3 and triple-fault immediately. + if kernel_cr3 > 0xFFFF_FFFF { + log_error("AP", 514, "kernel CR3 above 4GB; AP trampoline cannot load it in 32-bit mode"); + return false; + } + // read current GDT for APs to load (temporary, until per-core GDT) let mut gdt_buf = [0u8; 10]; core::arch::asm!("sgdt [{}]", in(reg) gdt_buf.as_mut_ptr(), options(nostack)); @@ -188,13 +197,30 @@ pub unsafe fn start_aps_from_list(ap_lapic_ids: &[u32]) { } let mut core_idx: u32 = 1; // 0 = BSP + let mut budget_used_us: u64 = 0; + let x2_mode = apic::is_x2apic_mode(); for &lapic_id in ap_lapic_ids { if core_idx as usize >= MAX_CPUS { break; } + + if !x2_mode && lapic_id > 0xFF { + // xAPIC destination field is 8-bit. without x2APIC mode this target is unreachable. + log_warn("AP", 513, "skipping MADT x2APIC ID in xAPIC mode"); + continue; + } + + if budget_used_us >= AP_MADT_SCAN_BUDGET_US { + log_warn("AP", 512, "MADT AP bring-up budget exhausted; continuing with discovered cores"); + break; + } + if boot_single_ap(core_idx, lapic_id) { core_idx += 1; } + + // same per-attempt upper bound used by brute-force path. + budget_used_us += 10_400 + AP_WAIT_TIMEOUT_US; } log_ok("AP", 505, "MADT AP bring-up pass complete"); @@ -282,10 +308,10 @@ pub unsafe fn start_aps() { #[no_mangle] pub unsafe extern "sysv64" fn ap_rust_entry(core_idx: u32, lapic_id: u32) -> ! { // 1. Set up per-core GDT + TSS - // get the current stack we're running on (allocated by BSP) - let rsp: u64; - core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nostack, nomem)); - let stack_top = (rsp + 0x1000) & !0xFFF; // round up to page boundary + // Read the exact stack_top from the trampoline data area. + // The BSP wrote the precise value to TD_STACK; rounding RSP up + // can overshoot if RSP is near a page boundary. + let stack_top = *((AP_TRAMPOLINE_PHYS + TD_STACK) as *const u64); gdt::init_gdt_for_ap(stack_top, core_idx); // 2. Load the shared IDT diff --git a/hwinit/src/cpu/apic.rs b/hwinit/src/cpu/apic.rs index d03dd47e..c66db193 100644 --- a/hwinit/src/cpu/apic.rs +++ b/hwinit/src/cpu/apic.rs @@ -38,7 +38,7 @@ const TIMER_MASKED: u32 = 1 << 16; // ICR delivery modes const ICR_INIT: u32 = 0x500; -const ICR_STARTUP: u32 = 0x8000; +const ICR_STARTUP: u32 = 0x600; const ICR_LEVEL_ASSERT: u32 = 0x4000; const ICR_LEVEL_DEASSERT: u32 = 0x0000; const ICR_TRIGGER_LEVEL: u32 = 1 << 15; @@ -98,10 +98,15 @@ unsafe fn wrmsr_parts(msr: u32, lo: u32, hi: u32) { } #[inline(always)] -unsafe fn x2apic_enabled() -> bool { +fn x2apic_enabled() -> bool { X2APIC_ENABLED.load(Ordering::Relaxed) } +#[inline(always)] +pub fn is_x2apic_mode() -> bool { + x2apic_enabled() +} + /// Probe the LAPIC base address from IA32_APIC_BASE MSR. /// Stores the result for all subsequent LAPIC access. /// Call once, early BSP init, before any other LAPIC operations. @@ -261,14 +266,18 @@ pub unsafe fn setup_timer(target_hz: u32) { outb(0x42, (PIT_TICKS & 0xFF) as u8); outb(0x42, ((PIT_TICKS >> 8) & 0xFF) as u8); - // start LAPIC timer with max initial count - lapic_write(base, LAPIC_TIMER_INIT, 0xFFFF_FFFF); - - // wait for PIT channel 2 to expire (bit 5 of port 0x61 goes high) - // re-arm the gate + // Re-arm the PIT gate BEFORE starting the LAPIC timer. + // On real hardware each I/O port access takes ~1µs. If the LAPIC + // timer starts first, the gate re-arm sequence inflates the measured + // LAPIC frequency by several microseconds → timer runs 5-15% fast + // → delay_us finishes early → AP SIPI delays too short. let v = crate::cpu::pio::inb(0x61) & 0xFE; - outb(0x61, v); - outb(0x61, v | 1); + outb(0x61, v); // gate LOW + outb(0x61, v | 1); // gate HIGH — PIT countdown restarts NOW + + // Start LAPIC timer immediately after PIT gate goes high so both + // timers run from the same instant. + lapic_write(base, LAPIC_TIMER_INIT, 0xFFFF_FFFF); // spin until PIT output goes high. if this hangs: PIT gate not armed, or // hardware has no PIT channel 2. the checkpoint tells us we entered the spin. @@ -372,16 +381,16 @@ pub unsafe fn send_sipi(target_apic_id: u32, start_page: u8) { /// Wait for the ICR delivery status bit to clear. #[inline] unsafe fn wait_icr_idle(base: u64) { - // Keep this tightly bounded. Some hardware can leave delivery status set - // long enough to look like a deadlock if we spin too generously. - let mut timeout = 2_000u32; - while { - if x2apic_enabled() { - (rdmsr(IA32_X2APIC_ICR_MSR) as u32 & ICR_DELIVERY_STATUS) != 0 - } else { - (lapic_read(base, LAPIC_ICR_LO) & ICR_DELIVERY_STATUS) != 0 - } - } { + // x2APIC: ICR writes via MSR 0x830 are performed synchronously by + // the CPU microcode. There is no delivery-status bit to poll, and + // reading the MSR back can #GP on some implementations. + if x2apic_enabled() { + return; + } + + // xAPIC: poll MMIO ICR_LO for delivery-status bit to clear. + let mut timeout = 10_000u32; + while (lapic_read(base, LAPIC_ICR_LO) & ICR_DELIVERY_STATUS) != 0 { core::hint::spin_loop(); timeout -= 1; if timeout == 0 { @@ -412,12 +421,19 @@ pub unsafe fn delay_us(us: u64) { let delta = cycles_per_us.saturating_mul(us); let target = start.saturating_add(delta); - // Watchdog the spin so a broken TSC path can't wedge bring-up forever. + // Scale watchdog with TSC frequency. Each loop iteration takes + // roughly 10–30 cycles (RDTSC + compare + PAUSE). Use a + // conservative estimate of 10 cycles/iteration so the watchdog + // never fires before the TSC target is actually reached. + let iters_per_us = (cycles_per_us / 10).max(1); + let max_spins_u64 = us.saturating_mul(iters_per_us) + .clamp(10_000, 1_000_000_000); + let max_spins = max_spins_u64 as u32; let mut spins = 0u32; while crate::cpu::tsc::read_tsc() < target { core::hint::spin_loop(); spins = spins.wrapping_add(1); - if spins == 50_000_000 { + if spins >= max_spins { log_warn("LAPIC", 727, "delay_us watchdog tripped; falling back"); break; } diff --git a/hwinit/src/cpu/gdt.rs b/hwinit/src/cpu/gdt.rs index 22cadda9..e68e4122 100644 --- a/hwinit/src/cpu/gdt.rs +++ b/hwinit/src/cpu/gdt.rs @@ -212,14 +212,24 @@ const IST1_STATIC_STACK_SIZE: usize = 32 * 1024; #[repr(C, align(16))] struct StaticStack([u8; IST1_STATIC_STACK_SIZE]); -/// The actual stack bytes. Zeroed by the BSS loader; no runtime init needed. -static mut IST1_STATIC_STACK: StaticStack = StaticStack([0; IST1_STATIC_STACK_SIZE]); +/// Per-core IST1 stacks. Each CPU needs its own IST1 so concurrent +/// double-faults on different cores don't corrupt each other's exception +/// frames. BSP uses index 0, APs use 1..MAX_CPUS-1. +/// Zeroed by the BSS loader; no runtime init needed. +static mut IST1_STACKS: [StaticStack; MAX_CPUS] = + [const { StaticStack([0; IST1_STATIC_STACK_SIZE]) }; MAX_CPUS]; + +/// Returns a pointer to the TOP of the per-core IST1 stack +/// (stacks grow downward on x86-64). +pub fn ist1_stack_top_for_core(core_idx: usize) -> u64 { + let idx = if core_idx < MAX_CPUS { core_idx } else { 0 }; + // SAFETY: read-only pointer arithmetic on a static array. + unsafe { IST1_STACKS[idx].0.as_ptr().add(IST1_STATIC_STACK_SIZE) as u64 } +} -/// Public accessor: returns a pointer to the TOP of the static IST1 stack -/// (stacks grow downward on x86-64). Written into TSS.IST[0] once at boot. +/// Backwards-compatible accessor for BSP (core 0). pub fn ist1_static_stack_top() -> u64 { - // SAFETY: read-only pointer arithmetic on a static array. - unsafe { IST1_STATIC_STACK.0.as_ptr().add(IST1_STATIC_STACK_SIZE) as u64 } + ist1_stack_top_for_core(0) } /// Our GDT (static, page-aligned for safety) @@ -388,7 +398,7 @@ pub unsafe fn init_gdt_for_ap(stack_top: u64, core_idx: u32) { // set up this AP's TSS AP_TSS[idx].rsp0 = stack_top; - AP_TSS[idx].ist[0] = ist1_static_stack_top(); // share the IST1 stack (double-fault only) + AP_TSS[idx].ist[0] = ist1_stack_top_for_core(idx); // per-core IST1 stack // point the GDT's TSS descriptor at this AP's TSS let tss_addr = &AP_TSS[idx] as *const Tss as u64; diff --git a/hwinit/src/cpu/per_cpu.rs b/hwinit/src/cpu/per_cpu.rs index 5050ce8b..3c9d27d4 100644 --- a/hwinit/src/cpu/per_cpu.rs +++ b/hwinit/src/cpu/per_cpu.rs @@ -16,6 +16,7 @@ use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; /// Maximum cores we support. 16 is generous for a desktop OS on QEMU. /// Increase if you're running on a Threadripper and hate yourself. pub const MAX_CPUS: usize = 16; +const LAPIC_ID_MAP_SIZE: usize = 1024; // ── ABI field offsets (must match struct layout below) ──────────────────── // These are used by context_switch.s and syscall.s via `gs:[OFFSET]`. @@ -74,6 +75,9 @@ pub struct PerCpu { pub park_state: u8, /// Last observed active TSC on this core. pub last_active_tsc: u64, + /// Sequential core index (0 = BSP). Cached here so + /// `current_core_index()` avoids the LAPIC-ID table lookup. + pub core_index: u32, } impl PerCpu { @@ -95,6 +99,7 @@ impl PerCpu { load_hint: 0, park_state: 0, last_active_tsc: 0, + core_index: 0, } } } @@ -106,7 +111,37 @@ impl PerCpu { static mut PER_CPU_ARRAY: [PerCpu; MAX_CPUS] = [const { PerCpu::zeroed() }; MAX_CPUS]; /// Maps LAPIC ID → sequential core index. Sparse (most entries 0xFF = unused). -static mut LAPIC_TO_IDX: [u8; 256] = [0xFF; 256]; +static mut LAPIC_TO_IDX: [u8; LAPIC_ID_MAP_SIZE] = [0xFF; LAPIC_ID_MAP_SIZE]; + +#[inline(always)] +unsafe fn map_lapic_id(lapic_id: u32, idx: u8) { + if (lapic_id as usize) < LAPIC_ID_MAP_SIZE { + LAPIC_TO_IDX[lapic_id as usize] = idx; + } else { + puts("[PERCPU] LAPIC ID out of fast-map range: "); + put_hex32(lapic_id); + puts("\n"); + } +} + +#[inline(always)] +unsafe fn lapic_id_to_index(lapic_id: u32) -> Option { + if (lapic_id as usize) < LAPIC_ID_MAP_SIZE { + let idx = LAPIC_TO_IDX[lapic_id as usize]; + if idx != 0xFF { + return Some(idx as u32); + } + } + + // fallback scan for sparse/high IDs. MAX_CPUS is tiny, so this is cheap. + for i in 0..MAX_CPUS { + let pcpu = &PER_CPU_ARRAY[i]; + if pcpu.online && pcpu.cpu_id == lapic_id { + return Some(i as u32); + } + } + None +} /// Number of cores that have completed init. pub static AP_ONLINE_COUNT: AtomicU32 = AtomicU32::new(0); @@ -177,10 +212,11 @@ pub unsafe fn init_bsp(lapic_id: u32, lapic_base: u64) { pcpu.cpu_id = lapic_id; pcpu.current_pid = 0; // kernel pcpu.lapic_base = lapic_base; + pcpu.core_index = idx as u32; pcpu.online = true; // map LAPIC ID → index - LAPIC_TO_IDX[lapic_id as usize] = idx as u8; + map_lapic_id(lapic_id, idx as u8); // load GS-base to point at this PerCpu let addr = pcpu as *const PerCpu as u64; @@ -210,6 +246,7 @@ pub unsafe fn init_ap(core_idx: u32, lapic_id: u32, lapic_base: u64) { pcpu.cpu_id = lapic_id; pcpu.current_pid = 0; // starts idle on kernel pcpu.lapic_base = lapic_base; + pcpu.core_index = core_idx; pcpu.online = true; // save the AP's kernel stack top so the scheduler can restore it @@ -218,7 +255,7 @@ pub unsafe fn init_ap(core_idx: u32, lapic_id: u32, lapic_base: u64) { core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nostack, nomem)); pcpu.boot_kernel_rsp = (rsp + 0x1000) & !0xFFF; - LAPIC_TO_IDX[lapic_id as usize] = idx as u8; + map_lapic_id(lapic_id, idx as u8); let addr = pcpu as *const PerCpu as u64; wrmsr(IA32_GS_BASE, addr); @@ -253,25 +290,13 @@ pub unsafe fn by_index(idx: u32) -> &'static mut PerCpu { /// Get PerCpu by LAPIC ID. #[inline(always)] pub unsafe fn by_lapic_id(lapic_id: u32) -> Option<&'static mut PerCpu> { - let idx = LAPIC_TO_IDX[lapic_id as usize]; - if idx == 0xFF { - None - } else { - Some(&mut PER_CPU_ARRAY[idx as usize]) - } + lapic_id_to_index(lapic_id).map(|idx| &mut PER_CPU_ARRAY[idx as usize]) } /// Current core's sequential index (0 = BSP). #[inline(always)] pub unsafe fn current_core_index() -> u32 { - let lapic_id: u32; - core::arch::asm!( - "mov {0:e}, gs:[0x08]", // cpu_id is u32 at offset 0x08. read 32-bit to avoid - // bleeding into current_pid at 0x0C. - out(reg) lapic_id, - options(nostack, readonly, preserves_flags), - ); - LAPIC_TO_IDX[lapic_id as usize] as u32 + current().core_index } /// Current core's LAPIC ID. diff --git a/hwinit/src/memory.rs b/hwinit/src/memory.rs index 6442a77d..354f9b35 100644 --- a/hwinit/src/memory.rs +++ b/hwinit/src/memory.rs @@ -11,7 +11,7 @@ pub const PAGE_SHIFT: u32 = 12; /// one constant to rule them all. 26 → 256 GiB, 28 → 1 TiB, 30 → 4 TiB. const MAX_ORDER: usize = 26; -const MAX_MAP: usize = 384; // UEFI snapshot slots +const MAX_MAP: usize = 768; // UEFI snapshot slots — real hardware can have 400+ // memory types (UEFI + our own tags) diff --git a/hwinit/src/paging/mod.rs b/hwinit/src/paging/mod.rs index 90363315..7d75ad8d 100644 --- a/hwinit/src/paging/mod.rs +++ b/hwinit/src/paging/mod.rs @@ -44,6 +44,13 @@ pub unsafe fn init_kernel_page_table() { options(nomem, nostack), ); log_info("PAGING", 731, "cleared CR0.WP"); + + // Flush TLB so stale entries that cached R/W=0 under WP=1 are + // discarded. Without this, writes to UEFI-owned PT pages can + // still fault on real hardware that cached the old permission. + let cr3_val: u64; + core::arch::asm!("mov {}, cr3", out(reg) cr3_val, options(nomem, nostack)); + core::arch::asm!("mov cr3, {}", in(reg) cr3_val, options(nostack)); } *KERNEL_PT.lock() = Some(mgr); diff --git a/hwinit/src/platform.rs b/hwinit/src/platform.rs index 048bb260..ff4ade10 100644 --- a/hwinit/src/platform.rs +++ b/hwinit/src/platform.rs @@ -18,7 +18,7 @@ use crate::memory::{ PhysicalAllocator, }; use crate::paging::init_kernel_page_table; -use crate::pci::{offset, pci_cfg_read16, pci_cfg_write16, PciAddr}; +use crate::pci::{offset, pci_cfg_read8, pci_cfg_read16, pci_cfg_write16, PciAddr}; use crate::process::scheduler::init_scheduler; use crate::serial::{checkpoint, log_error, log_info, log_ok, log_warn}; use crate::syscall::init_syscall; @@ -61,6 +61,9 @@ pub struct SelfContainedConfig { pub stack_base: u64, /// Number of 4 KiB pages in the boot stack allocation. pub stack_pages: u64, + /// Physical address of the ACPI RSDP from UEFI configuration table. + /// 0 means unavailable. + pub acpi_rsdp_phys: u64, } /// Platform configuration input (legacy - externally allocated). @@ -119,6 +122,11 @@ pub unsafe fn platform_init_selfcontained( in(reg) cr0 & !(1u64 << 16), options(nomem, nostack), ); + // Flush TLB so no stale entries carry the old WP=1 permission + // model. Some real CPUs cache the WP bit in TLB entries. + let cr3: u64; + core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack)); + core::arch::asm!("mov cr3, {}", in(reg) cr3, options(nostack)); } } @@ -333,6 +341,41 @@ pub unsafe fn platform_init_selfcontained( return Err(InitError::TscCalibrationFailed); } + // Check for invariant TSC (CPUID.80000007H:EDX[8]). + // Without it, CPU frequency scaling (P-states, C-states) causes TSC + // drift — delay_us and scheduler timing will be inaccurate on real HW. + { + let max_ext: u32; + core::arch::asm!( + "push rbx", + "mov eax, 0x80000000", + "cpuid", + "pop rbx", + out("eax") max_ext, + out("ecx") _, + out("edx") _, + options(nostack), + ); + if max_ext >= 0x80000007 { + let edx: u32; + core::arch::asm!( + "push rbx", + "mov eax, 0x80000007", + "cpuid", + "pop rbx", + out("edx") edx, + out("eax") _, + out("ecx") _, + options(nostack), + ); + if edx & (1 << 8) == 0 { + log_warn("TSC", 551, "CPU lacks invariant TSC; timing may drift with P-state changes"); + } + } else { + log_warn("TSC", 552, "CPUID extended leaf 0x80000007 unavailable; cannot verify invariant TSC"); + } + } + checkpoint("phase5-done"); crate::process::scheduler::set_tsc_frequency(tsc_freq); @@ -344,7 +387,7 @@ pub unsafe fn platform_init_selfcontained( let dma_phys = { let mut dma_reg = global_registry_mut(); dma_reg - .allocate_pages(AllocateType::AnyPages, MemoryType::AllocatedDma, dma_pages) + .allocate_pages(AllocateType::MaxAddress(0xFFFF_FFFF), MemoryType::AllocatedDma, dma_pages) .map_err(|_| InitError::NoFreeMemory)? }; @@ -497,6 +540,9 @@ pub unsafe fn platform_init_selfcontained( { use crate::cpu::{acpi, ap_boot, apic, per_cpu}; + // UEFI gave us the authoritative ACPI root. no BIOS scavenger hunt. + acpi::set_rsdp_phys(config.acpi_rsdp_phys); + let bsp_lapic_id = apic::read_lapic_id(); checkpoint("phase12-madt-scan"); @@ -597,8 +643,8 @@ unsafe fn enable_all_pci_devices() -> usize { continue; } - // Enable this device - enable_bus_mastering(addr); + // Enable this device (if it's not a bridge) + maybe_enable_bus_mastering(addr); count += 1; // Check for multi-function @@ -608,7 +654,7 @@ unsafe fn enable_all_pci_devices() -> usize { let faddr = PciAddr::new(bus, device, function); let v = pci_cfg_read16(faddr, offset::VENDOR_ID); if v != 0xFFFF && v != 0x0000 { - enable_bus_mastering(faddr); + maybe_enable_bus_mastering(faddr); count += 1; } } @@ -619,6 +665,19 @@ unsafe fn enable_all_pci_devices() -> usize { count } +/// Check class code and skip bridge devices. Blindly enabling bus mastering +/// on host bridges, PCI-PCI bridges, and ISA bridges can trigger IOMMU +/// faults or stray DMA from shadow BARs on real hardware. +fn maybe_enable_bus_mastering(addr: PciAddr) { + let class = pci_cfg_read8(addr, 0x0B); // base class at offset 0x0B + if class == 0x06 { + // 0x06 = Bridge device (host, ISA, PCI-PCI, CardBus, etc.) + // Don't toggle bus mastering — the firmware configured these. + return; + } + enable_bus_mastering(addr); +} + /// Enable memory space and bus mastering on a device. fn enable_bus_mastering(addr: PciAddr) { let cmd = pci_cfg_read16(addr, offset::COMMAND); diff --git a/network/Cargo.toml b/network/Cargo.toml index f12351e6..0a58c115 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -12,6 +12,7 @@ display = ["morpheus-display"] # Enable framebuffer display for post-EBS debug [dependencies] morpheus-core = { workspace = true } +morpheus-hwinit = { workspace = true } morpheus-display = { path = "../display", features = ["framebuffer-backend"], optional = true } # GPT disk I/O trait for filesystem compatibility diff --git a/network/asm/drivers/usb/init.s b/network/asm/drivers/usb/init.s index 69af8f52..73496e82 100644 --- a/network/asm/drivers/usb/init.s +++ b/network/asm/drivers/usb/init.s @@ -16,7 +16,7 @@ extern asm_tsc_read extern asm_bar_mfence global asm_usb_host_probe -global asm_xhci_controller_reset +global asm_xhci_controller_soft_restart global asm_xhci_bios_handoff ; ─────────────────────────────────────────────────────────────────────────── @@ -125,27 +125,13 @@ asm_xhci_bios_handoff: sub rax, rbx cmp rax, r14 ; 1 second = tsc_freq ticks jb .bios_wait - - ; timeout — force it. clear BIOS bit, keep OS bit - mov rcx, r13 - call asm_mmio_read32 - and eax, ~XHCI_LEGSUP_BIOS_OWNED - or eax, XHCI_LEGSUP_OS_OWNED - mov edx, eax - mov rcx, r13 - call asm_mmio_write32 - call asm_bar_mfence - - ; also nuke the legacy control/status dword at offset +4 - ; disable all SMI sources so BIOS can't interfere - lea rcx, [r13 + 4] - xor edx, edx - call asm_mmio_write32 - call asm_bar_mfence + mov eax, 1 ; timeout waiting owner release + jmp .bios_out .bios_claimed: .bios_none: xor eax, eax +.bios_out: add rsp, 40 pop r14 pop r13 @@ -154,15 +140,14 @@ asm_xhci_bios_handoff: ret ; ─────────────────────────────────────────────────────────────────────────── -; asm_xhci_controller_reset +; asm_xhci_controller_soft_restart ; ─────────────────────────────────────────────────────────────────────────── +; Restart xHCI without hard reset (preserves port state from UEFI handoff). +; Assumes UEFI left controller in a valid state. We just stop and restart. ; RCX = op_base (mmio_base + CAPLENGTH) ; RDX = tsc_freq -; Returns: EAX = 0 success, 1 halt timeout, 2 reset timeout, 3 CNR timeout -; -; brutal reset: nuke interrupts, force-stop, HCRST, wait CNR. -; 1 second timeout per phase because real hardware is dramatic. -asm_xhci_controller_reset: +; Returns: EAX = 0 success, 1 halt timeout, 2 start timeout +asm_xhci_controller_soft_restart: push rbx push r12 push r13 @@ -173,75 +158,59 @@ asm_xhci_controller_reset: mov r13, rdx ; tsc_freq mov r14, r13 ; 1 second timeout = tsc_freq ticks - ; ── step 0: nuke USBCMD — clear RS, INTE, everything ── + ; ── step 1: stop controller (RS = 0) ── lea rcx, [r12 + XHCI_OP_USBCMD] xor edx, edx call asm_mmio_write32 call asm_bar_mfence - ; ── step 1: wait USBSTS.HCH (halted) ── + ; ── wait for HCH (halted) ── call asm_tsc_read mov rbx, rax -.wait_halt: +.wait_halt_soft: lea rcx, [r12 + XHCI_OP_USBSTS] call asm_mmio_read32 test eax, XHCI_STS_HCH - jnz .halted + jnz .halted_soft call asm_tsc_read sub rax, rbx cmp rax, r14 - jb .wait_halt - ; timeout — try HCRST anyway, some controllers only halt via reset - jmp .do_reset + jb .wait_halt_soft + mov eax, 1 ; halt timeout + jmp .out_soft -.halted: - ; ── clear all pending status bits ── +.halted_soft: + ; ── clear status bits ── lea rcx, [r12 + XHCI_OP_USBSTS] mov edx, 0xFFFFFFFF call asm_mmio_write32 call asm_bar_mfence -.do_reset: - ; ── step 2: HCRST ── + ; ── start controller (RS = 1, INTE = 1) — NO HCRST ── lea rcx, [r12 + XHCI_OP_USBCMD] - mov edx, XHCI_CMD_HCRST + mov edx, XHCI_CMD_RS | XHCI_CMD_INTE call asm_mmio_write32 call asm_bar_mfence + ; ── wait for HCH to clear (running) ── call asm_tsc_read mov rbx, rax -.wait_reset: - lea rcx, [r12 + XHCI_OP_USBCMD] - call asm_mmio_read32 - test eax, XHCI_CMD_HCRST - jz .reset_done - call asm_tsc_read - sub rax, rbx - cmp rax, r14 - jb .wait_reset - mov eax, 2 - jmp .out - -.reset_done: - ; ── step 3: wait CNR clear ── - call asm_tsc_read - mov rbx, rax -.wait_cnr: +.wait_running: lea rcx, [r12 + XHCI_OP_USBSTS] call asm_mmio_read32 - test eax, XHCI_STS_CNR - jz .ready + test eax, XHCI_STS_HCH + jz .running_soft call asm_tsc_read sub rax, rbx cmp rax, r14 - jb .wait_cnr - mov eax, 3 - jmp .out + jb .wait_running + mov eax, 2 ; start timeout + jmp .out_soft -.ready: +.running_soft: xor eax, eax -.out: +.out_soft: add rsp, 40 pop r14 pop r13 diff --git a/network/src/boot/block_probe.rs b/network/src/boot/block_probe.rs index d9fc4433..cbf696f5 100644 --- a/network/src/boot/block_probe.rs +++ b/network/src/boot/block_probe.rs @@ -195,7 +195,7 @@ pub enum BlockProbeResult { // ═══════════════════════════════════════════════════════════════════════════ /// Maximum block devices we can discover in a single scan. -const MAX_BLOCK_DEVICES: usize = 8; +const MAX_BLOCK_DEVICES: usize = 32; /// Scan PCI bus for supported block devices. /// @@ -230,7 +230,7 @@ pub fn scan_for_block_device() -> Option { /// Scan PCI bus for ALL supported block devices. /// -/// Returns all detected AHCI and VirtIO-blk devices (up to 8). +/// Returns all detected AHCI/SDHCI/USB/VirtIO devices (up to MAX_BLOCK_DEVICES). /// AHCI devices are listed first, then VirtIO-blk. pub fn scan_all_block_devices() -> ([Option; MAX_BLOCK_DEVICES], usize) { let mut result: [Option; MAX_BLOCK_DEVICES] = [None; MAX_BLOCK_DEVICES]; @@ -794,7 +794,20 @@ pub unsafe fn probe_and_create_block_driver( } DetectedBlockDevice::UsbMsd(info) => { + crate::serial_str("[BLK-PROBE] USB xHCI pci="); + dbg_hex8(info.pci_addr.bus); + crate::serial_str(":"); + dbg_hex8(info.pci_addr.device); + crate::serial_str("."); + dbg_hex8(info.pci_addr.function); + crate::serial_str(" bar0="); + dbg_hex64(info.mmio_base); + crate::serial_str("\n"); + enable_pci_device(info.pci_addr); + // also disable INTx so BIOS SMI handlers can't interfere + let cmd = pci_cfg_read16(info.pci_addr, offset::COMMAND); + pci_cfg_write16(info.pci_addr, offset::COMMAND, cmd | (1 << 10)); let usb_config = UsbMsdConfig { tsc_freq: config.tsc_freq, @@ -917,7 +930,20 @@ pub unsafe fn create_unified_from_detected( Ok(UnifiedBlockDevice::Sdhci(driver)) } DetectedBlockDevice::UsbMsd(info) => { + crate::serial_str("[BLK-PROBE] USB xHCI pci="); + dbg_hex8(info.pci_addr.bus); + crate::serial_str(":"); + dbg_hex8(info.pci_addr.device); + crate::serial_str("."); + dbg_hex8(info.pci_addr.function); + crate::serial_str(" bar0="); + dbg_hex64(info.mmio_base); + crate::serial_str("\n"); + enable_pci_device(info.pci_addr); + let cmd = pci_cfg_read16(info.pci_addr, offset::COMMAND); + pci_cfg_write16(info.pci_addr, offset::COMMAND, cmd | (1 << 10)); + let usb_config = UsbMsdConfig { tsc_freq: config.tsc_freq, dma_phys: 0, diff --git a/network/src/driver/usb_msd/mod.rs b/network/src/driver/usb_msd/mod.rs index 2a32d71a..cc97b902 100644 --- a/network/src/driver/usb_msd/mod.rs +++ b/network/src/driver/usb_msd/mod.rs @@ -18,8 +18,8 @@ extern "win64" { /// Reads CAPLENGTH + HCIVERSION. 0 = dead controller. /// Low byte = CAPLENGTH, bits 31:16 = HCIVERSION. fn asm_usb_host_probe(mmio_base: u64) -> u32; - /// Stop + HCRST + wait CNR. 0 = ok, 1/2/3 = timeout at halt/reset/cnr. - fn asm_xhci_controller_reset(op_base: u64, tsc_freq: u64) -> u32; + /// Soft restart: stop + start (NO HCRST). Preserves UEFI port state. 0 = ok, 1/2 = timeout. + fn asm_xhci_controller_soft_restart(op_base: u64, tsc_freq: u64) -> u32; /// Walk extended caps, claim ownership from BIOS/SMM. 0 = ok. fn asm_xhci_bios_handoff(mmio_base: u64, hccparams1: u64, tsc_freq: u64) -> u32; } @@ -53,11 +53,23 @@ const STS_HCH: u32 = 1 << 0; const PORTSC_CCS: u32 = 1 << 0; const PORTSC_PED: u32 = 1 << 1; const PORTSC_PR: u32 = 1 << 4; +const PORTSC_PLS_MASK: u32 = 0xF << 5; const PORTSC_PP: u32 = 1 << 9; +const PORTSC_LWS: u32 = 1 << 16; +const PORTSC_WRC: u32 = 1 << 19; const PORTSC_PRC: u32 = 1 << 21; +const PORTSC_CAS: u32 = 1 << 24; +const PORTSC_WPR: u32 = 1 << 31; // RW1C mask: bits 17-23 must be written 0 to preserve, 1 to clear const PORTSC_RW1C: u32 = 0x00FE_0000; const PORTSC_SPEED_SHIFT: u32 = 10; +const PLS_U0: u32 = 0x0 << 5; +const PLS_U3: u32 = 0x3 << 5; +const PLS_RECOVERY: u32 = 0x8 << 5; +const PLS_RESUME: u32 = 0xF << 5; +const PLS_INACTIVE: u32 = 0x6 << 5; +const PLS_POLLING: u32 = 0x7 << 5; +const PLS_COMPLIANCE: u32 = 0xA << 5; // ═══════════════════════════════════════════════════════════════════════════ // TRB types (pre-shifted to bits 15:10) @@ -90,6 +102,7 @@ const TRB_TYPE_MASK: u32 = 0x3F << 10; // ═══════════════════════════════════════════════════════════════════════════ const USB_CLASS_MASS_STORAGE: u8 = 0x08; +const USB_CLASS_HUB: u8 = 0x09; const USB_SUBCLASS_SCSI: u8 = 0x06; const USB_PROTOCOL_BOT: u8 = 0x50; @@ -103,7 +116,7 @@ const SCSI_READ_10: u8 = 0x28; // DMA region layout — all offsets 64-byte aligned inside a 64KB-aligned buf // ═══════════════════════════════════════════════════════════════════════════ -const DMA_SIZE: usize = 65536; +const DMA_SIZE: usize = 0x48000; const CMD_RING_LEN: u8 = 32; const EVT_RING_LEN: u8 = 32; const XFER_RING_LEN: u8 = 16; @@ -123,8 +136,8 @@ const OFF_DESC: usize = 0x4480; // 256B const OFF_DATA: usize = 0x5000; // 4KB sector bounce buffer (one page, no 64KB boundary crossing) const DATA_BUF_SIZE: usize = 4096; const OFF_SCRATCH_ARR: usize = 0x7000; // 64B -const OFF_SCRATCH_PG: usize = 0x8000; // up to 8 × 4KB pages -const MAX_SCRATCH: usize = 8; +const OFF_SCRATCH_PG: usize = 0x8000; // scratchpad pages begin here +const MAX_SCRATCH: usize = 64; #[repr(C, align(4096))] struct XhciDma([u8; DMA_SIZE]); @@ -151,6 +164,26 @@ pub struct UsbMsdConfig { pub enum UsbMsdInitError { InvalidConfig, ControllerInitFailed, + ControllerProbeFailed, + ControllerResetFailed, + ControllerScratchpadUnsupported, + ControllerStartFailed, + HubUnsupported, + PortResetFailed, + PortResetTimeout, + PortResetHotCmdTimeout, + PortResetHotSettleTimeout, + PortResetWarmTimeout, + PortResetNoLink, + EnableSlotFailed, + AddressDeviceFailed, + DeviceDescriptorFailed, + ConfigDescriptorFailed, + MassStorageProtocolUnsupported, + NoBotMassStorageInterface, + ActivePortsNoConnectedDevice, + SetConfigurationFailed, + ConfigureEndpointsFailed, DeviceEnumerationFailed, TransportInitFailed, NoMedia, @@ -164,6 +197,34 @@ impl core::fmt::Display for UsbMsdInitError { match self { Self::InvalidConfig => write!(f, "Invalid USB MSD configuration"), Self::ControllerInitFailed => write!(f, "USB xHCI controller init failed"), + Self::ControllerProbeFailed => write!(f, "USB xHCI probe failed (dead/invalid BAR)"), + Self::ControllerResetFailed => write!(f, "USB xHCI reset failed (halt/reset/cnr timeout)"), + Self::ControllerScratchpadUnsupported => { + write!(f, "USB xHCI requires too many scratchpad buffers") + } + Self::ControllerStartFailed => write!(f, "USB xHCI start failed (HCH never cleared)"), + Self::HubUnsupported => write!(f, "USB hub detected but hub traversal is not implemented"), + Self::PortResetFailed => write!(f, "USB port reset failed"), + Self::PortResetTimeout => write!(f, "USB port reset timed out"), + Self::PortResetHotCmdTimeout => write!(f, "USB hot reset command did not complete"), + Self::PortResetHotSettleTimeout => write!(f, "USB hot reset completed but link did not settle"), + Self::PortResetWarmTimeout => write!(f, "USB warm reset did not complete"), + Self::PortResetNoLink => write!(f, "USB reset completed but link never became usable"), + Self::EnableSlotFailed => write!(f, "USB enable-slot command failed"), + Self::AddressDeviceFailed => write!(f, "USB address-device command failed"), + Self::DeviceDescriptorFailed => write!(f, "USB device descriptor fetch failed"), + Self::ConfigDescriptorFailed => write!(f, "USB config descriptor fetch failed"), + Self::MassStorageProtocolUnsupported => { + write!(f, "USB mass-storage present but protocol is not BOT") + } + Self::NoBotMassStorageInterface => { + write!(f, "USB config has no BOT mass-storage interface") + } + Self::ActivePortsNoConnectedDevice => { + write!(f, "USB root ports active but no connected device detected") + } + Self::SetConfigurationFailed => write!(f, "USB SET_CONFIGURATION failed"), + Self::ConfigureEndpointsFailed => write!(f, "USB Configure Endpoint command failed"), Self::DeviceEnumerationFailed => write!(f, "USB device enumeration failed"), Self::TransportInitFailed => write!(f, "USB BOT transport init failed"), Self::NoMedia => write!(f, "No USB mass-storage device found"), @@ -185,6 +246,19 @@ enum Ring { BulkIn, } +enum ConfigParse { + MassStorage { + cfg_val: u8, + ep_in: u8, + ep_out: u8, + mp_in: u16, + mp_out: u16, + }, + Hub, + MassStorageUnsupported, + None, +} + // ═══════════════════════════════════════════════════════════════════════════ // Driver state // ═══════════════════════════════════════════════════════════════════════════ @@ -263,19 +337,66 @@ unsafe fn tsc_delay(tsc_freq: u64, ms: u64) { } } +// Preserve only RO+RWS bits on PORTSC writes (Linux xhci_port_state_to_neutral style). +#[inline(always)] +fn portsc_neutral(state: u32) -> u32 { + const PORT_RO: u32 = (1 << 0) | (1 << 3) | (0xF << 10) | (1 << 30); + const PORT_RWS: u32 = (0xF << 5) | (1 << 9) | (0x3 << 14) | (0x7 << 25); + state & (PORT_RO | PORT_RWS) +} + +#[inline(always)] +unsafe fn portsc_write(addr: u64, current: u32, set_bits: u32, clear_bits: u32) { + let mut v = portsc_neutral(current); + v &= !clear_bits; + v |= set_bits; + mmio::write32(addr, v); +} + +#[inline(always)] +fn warm_reset_needed(ps: u32) -> bool { + let pls = ps & PORTSC_PLS_MASK; + let speed = (ps >> PORTSC_SPEED_SHIFT) & 0xF; + (ps & PORTSC_CAS) != 0 + || pls == PLS_POLLING + || pls == PLS_COMPLIANCE + || pls == PLS_INACTIVE + || speed >= 4 +} + // ═══════════════════════════════════════════════════════════════════════════ // Implementation // ═══════════════════════════════════════════════════════════════════════════ -// serial hex dump for diagnostics. not pretty, don't care. +// serial and framebuffer diagnostics via console hook fn dbg(s: &str) { - crate::serial_str(s); + morpheus_hwinit::serial::puts(s); +} +fn dbg_u32(v: u32) { + let mut digits = [0u8; 10]; + let mut n = v; + let mut len = 0usize; + if n == 0 { + digits[0] = b'0'; + len = 1; + } else { + while n > 0 { + digits[len] = b'0' + (n % 10) as u8; + len += 1; + n /= 10; + } + digits[..len].reverse(); + } + if let Ok(s) = core::str::from_utf8(&digits[..len]) { + morpheus_hwinit::serial::puts(s); + } } fn dbg_hex32(v: u32) { const HEX: &[u8; 16] = b"0123456789abcdef"; - crate::serial_str("0x"); + morpheus_hwinit::serial::puts("0x"); for i in (0..8).rev() { - crate::serial_byte(HEX[((v >> (i * 4)) & 0xF) as usize]); + let byte = HEX[((v >> (i * 4)) & 0xF) as usize]; + morpheus_hwinit::serial::putc(byte); } } fn dbg_hex64(v: u64) { @@ -287,6 +408,68 @@ fn dbg_hex64(v: u64) { } impl UsbMsdDriver { + #[inline(always)] + fn err_label(e: UsbMsdInitError) -> &'static str { + match e { + UsbMsdInitError::InvalidConfig => "invalid-config", + UsbMsdInitError::ControllerInitFailed => "controller-init", + UsbMsdInitError::ControllerProbeFailed => "controller-probe", + UsbMsdInitError::ControllerResetFailed => "controller-restart", + UsbMsdInitError::ControllerScratchpadUnsupported => "scratchpad-unsupported", + UsbMsdInitError::ControllerStartFailed => "controller-start", + UsbMsdInitError::HubUnsupported => "hub-unsupported", + UsbMsdInitError::PortResetFailed => "port-reset", + UsbMsdInitError::PortResetTimeout => "port-timeout", + UsbMsdInitError::PortResetHotCmdTimeout => "port-hotcmd-timeout", + UsbMsdInitError::PortResetHotSettleTimeout => "port-settle-timeout", + UsbMsdInitError::PortResetWarmTimeout => "port-warm-timeout", + UsbMsdInitError::PortResetNoLink => "no-link", + UsbMsdInitError::EnableSlotFailed => "enable-slot", + UsbMsdInitError::AddressDeviceFailed => "address-device", + UsbMsdInitError::DeviceDescriptorFailed => "device-desc", + UsbMsdInitError::ConfigDescriptorFailed => "config-desc", + UsbMsdInitError::MassStorageProtocolUnsupported => "msd-protocol", + UsbMsdInitError::NoBotMassStorageInterface => "no-bot-intf", + UsbMsdInitError::ActivePortsNoConnectedDevice => "active-no-device", + UsbMsdInitError::SetConfigurationFailed => "set-config", + UsbMsdInitError::ConfigureEndpointsFailed => "config-eps", + UsbMsdInitError::DeviceEnumerationFailed => "enumeration", + UsbMsdInitError::TransportInitFailed => "transport-init", + UsbMsdInitError::NoMedia => "no-media", + UsbMsdInitError::CommandTimeout => "cmd-timeout", + UsbMsdInitError::IoError => "io", + UsbMsdInitError::NotImplemented => "not-implemented", + } + } + + #[inline(always)] + fn log_port_attempt(&self, port: u8, portsc: u32, err: UsbMsdInitError) { + dbg("[USB] p"); + dbg_u32(port as u32); + dbg(" ccs="); + dbg_u32(if (portsc & PORTSC_CCS) != 0 { 1 } else { 0 }); + dbg(" ped="); + dbg_u32(if (portsc & PORTSC_PED) != 0 { 1 } else { 0 }); + dbg(" speed="); + dbg_u32((portsc >> PORTSC_SPEED_SHIFT) & 0xF); + dbg(" -> "); + dbg(Self::err_label(err)); + dbg("\n"); + } + + #[inline(always)] + fn log_port_success(&self, port: u8, portsc: u32) { + dbg("[USB] p"); + dbg_u32(port as u32); + dbg(" ccs="); + dbg_u32(if (portsc & PORTSC_CCS) != 0 { 1 } else { 0 }); + dbg(" ped="); + dbg_u32(if (portsc & PORTSC_PED) != 0 { 1 } else { 0 }); + dbg(" speed="); + dbg_u32((portsc >> PORTSC_SPEED_SHIFT) & 0xF); + dbg(" -> ok\n"); + } + // ─── public entry point ────────────────────────────────────────────── /// Initialise xHCI controller, enumerate first USB mass-storage device, @@ -317,10 +500,25 @@ impl UsbMsdDriver { unsafe fn init_controller(mmio_base: u64, tsc_freq: u64) -> Result { // probe: low byte = CAPLENGTH, high half = HCIVERSION. 0 = dead. let probe = asm_usb_host_probe(mmio_base); + dbg("[USB] probe="); + dbg_hex32(probe); + dbg("\n"); if probe == 0 { - return Err(UsbMsdInitError::ControllerInitFailed); + dbg("[USB] FAIL: probe returned 0 (dead BAR or bad CAPLENGTH/HCIVERSION)\n"); + // read raw dword for diagnostics + let raw = mmio::read32(mmio_base); + dbg("[USB] raw mmio_base+0 = "); + dbg_hex32(raw); + dbg("\n"); + return Err(UsbMsdInitError::ControllerProbeFailed); } let cap_len = (probe & 0xFF) as u64; + let hci_ver = probe >> 16; + dbg("[USB] CAPLENGTH="); + dbg_hex32(cap_len as u32); + dbg(" HCIVERSION="); + dbg_hex32(hci_ver); + dbg("\n"); let op_base = mmio_base + cap_len; let hcsparams1 = mmio::read32(mmio_base + 0x04); @@ -339,27 +537,77 @@ impl UsbMsdDriver { let rt_base = mmio_base + rts_off as u64; let db_base = mmio_base + db_off as u64; + dbg("[USB] slots="); + crate::serial_u32(max_slots as u32); + dbg(" ports="); + crate::serial_u32(max_ports as u32); + dbg(" ctxsz="); + crate::serial_u32(ctx_size as u32); + dbg(" scratch="); + crate::serial_u32(n_scratch as u32); + dbg(" hccparams1="); + dbg_hex32(hccparams1); + dbg("\n"); + dbg("[USB] op_base="); + dbg_hex64(op_base); + dbg(" rt_base="); + dbg_hex64(rt_base); + dbg(" db_base="); + dbg_hex64(db_base); + dbg("\n"); + + // read USBSTS before we touch anything + let sts_before = mmio::read32(op_base + OP_USBSTS); + dbg("[USB] USBSTS before reset="); + dbg_hex32(sts_before); + dbg("\n"); + // static DMA buffer, identity-mapped in UEFI let dma_base = core::ptr::addr_of_mut!(XHCI_DMA) as u64; core::ptr::write_bytes(dma_base as *mut u8, 0, DMA_SIZE); + dbg("[USB] DMA base="); + dbg_hex64(dma_base); + dbg("\n"); - // ── rip controller from BIOS/UEFI/SMM ── - asm_xhci_bios_handoff(mmio_base, hccparams1 as u64, tsc_freq); + // ── legacy owner handoff (SMM/firmware) ── + dbg("[USB] xHCI legacy-owner handoff...\n"); + let handoff_rc = asm_xhci_bios_handoff(mmio_base, hccparams1 as u64, tsc_freq); tsc_delay(tsc_freq, 10); + if handoff_rc == 0 { + dbg("[USB] xHCI legacy-owner handoff done\n"); + } else { + dbg("[USB] xHCI legacy-owner handoff timed out; continuing without force takeover\n"); + } - // ── controller reset with 3 attempts because hardware lies ── - let mut reset_ok = false; - for attempt in 0..3u32 { - let rc = asm_xhci_controller_reset(op_base, tsc_freq); - if rc == 0 { - reset_ok = true; - break; + // If firmware already trained links, don't touch controller run state. + let mut linked_ports = 0u32; + let mut active_ports = 0u32; + for p in 0..max_ports { + let ps = mmio::read32(op_base + PORT_REG_BASE + (p as u64) * PORT_REG_STRIDE); + let speed = (ps >> PORTSC_SPEED_SHIFT) & 0xF; + if ps != 0 && ps != u32::MAX { + active_ports += 1; + } + if (ps & (PORTSC_CCS | PORTSC_PED)) != 0 || speed != 0 { + linked_ports += 1; } - // increasing backoff: 100ms, 200ms, 300ms - tsc_delay(tsc_freq, 100 * (attempt as u64 + 1)); } - if !reset_ok { - return Err(UsbMsdInitError::ControllerInitFailed); + dbg_u32(linked_ports); + dbg(" active="); + dbg_u32(active_ports); + dbg(" total="); + dbg_u32(max_ports as u32); + dbg("\n"); + + if linked_ports == 0 { + // All dead after handoff: one soft restart may wake stale HC run state. + let rc_soft = asm_xhci_controller_soft_restart(op_base, tsc_freq); + if rc_soft != 0 { + dbg("[USB] FAIL: soft restart failed rc="); + crate::serial_u32(rc_soft); + dbg("\n"); + return Err(UsbMsdInitError::ControllerResetFailed); + } } // post-reset settle @@ -370,7 +618,12 @@ impl UsbMsdDriver { // scratchpad buffers (controller refuses to start without them) if n_scratch > MAX_SCRATCH { - return Err(UsbMsdInitError::ControllerInitFailed); + dbg("[USB] FAIL: too many scratchpad bufs ("); + crate::serial_u32(n_scratch as u32); + dbg(" > "); + crate::serial_u32(MAX_SCRATCH as u32); + dbg(")\n"); + return Err(UsbMsdInitError::ControllerScratchpadUnsupported); } if n_scratch > 0 { let arr = dma_base + OFF_SCRATCH_ARR as u64; @@ -412,35 +665,63 @@ impl UsbMsdDriver { mmio::write32(rt_base + IR0_IMAN, iman | 0x02); // start controller: RS=1, INTE=1 + dbg("[USB] starting controller (RS=1)...\n"); mmio::write32(op_base + OP_USBCMD, CMD_RS | CMD_INTE); // wait HCH to clear (controller running) — 1 second let start = tsc::read_tsc(); let timeout = tsc_freq; loop { - if mmio::read32(op_base + OP_USBSTS) & STS_HCH == 0 { + let sts = mmio::read32(op_base + OP_USBSTS); + if sts & STS_HCH == 0 { + dbg("[USB] controller running (HCH cleared)\n"); break; } if tsc::read_tsc().wrapping_sub(start) > timeout { - return Err(UsbMsdInitError::ControllerInitFailed); + dbg("[USB] FAIL: HCH never cleared. USBSTS="); + dbg_hex32(sts); + dbg(" USBCMD="); + dbg_hex32(mmio::read32(op_base + OP_USBCMD)); + dbg("\n"); + return Err(UsbMsdInitError::ControllerStartFailed); } core::hint::spin_loop(); } - // ── port power cycle: turn off, wait, turn on, wait ── + // ensure PP is set and clear sticky change bits so we see fresh state. for p in 0..max_ports { let addr = op_base + PORT_REG_BASE + (p as u64) * PORT_REG_STRIDE; let ps = mmio::read32(addr); - mmio::write32(addr, (ps & !PORTSC_RW1C) & !PORTSC_PP); + if ps & PORTSC_PP == 0 { + portsc_write(addr, ps, PORTSC_PP, 0); + } + // clear any stale change bits from before HCRST + let clr = ps & PORTSC_RW1C; + if clr != 0 { + portsc_write(addr, ps, clr, 0); + } } - tsc_delay(tsc_freq, 50); + // give ports 200ms to surface CCS after controller restart + tsc_delay(tsc_freq, 200); + + // dump port status for diagnostics + dbg("[USB] port status after init:\n"); for p in 0..max_ports { let addr = op_base + PORT_REG_BASE + (p as u64) * PORT_REG_STRIDE; let ps = mmio::read32(addr); - mmio::write32(addr, (ps & !PORTSC_RW1C) | PORTSC_PP); + if ps & PORTSC_CCS != 0 { + dbg("[USB] port "); + crate::serial_u32(p as u32); + dbg(" PORTSC="); + dbg_hex32(ps); + dbg(" CCS=1"); + if ps & PORTSC_PED != 0 { dbg(" PED"); } + if ps & PORTSC_PP != 0 { dbg(" PP"); } + dbg(" speed="); + crate::serial_u32((ps >> PORTSC_SPEED_SHIFT) & 0xF); + dbg("\n"); + } } - // let devices settle after power-on - tsc_delay(tsc_freq, 200); Ok(Self { op_base, @@ -477,30 +758,190 @@ impl UsbMsdDriver { // ─── phase 2: USB device enumeration ───────────────────────────────── unsafe fn enumerate_and_configure(&mut self) -> Result<(), UsbMsdInitError> { + // Fast-fail signature seen on some firmware handoffs: MMIO active but no link state. + let mut pre_active = 0u32; + let mut pre_linked = 0u32; for port in 0..self.max_ports { let ps = mmio::read32(self.portsc(port)); - if ps & PORTSC_CCS == 0 { - continue; + if ps != 0 && ps != u32::MAX { + pre_active += 1; } - if ps & PORTSC_PP == 0 { - continue; + let speed = (ps >> PORTSC_SPEED_SHIFT) & 0xF; + if (ps & (PORTSC_CCS | PORTSC_PED)) != 0 || speed != 0 { + pre_linked += 1; } + } + if pre_active >= 8 && pre_linked == 0 { + dbg("[USB] active ports with zero link state; skip xHCI enum\n"); + return Err(UsbMsdInitError::ActivePortsNoConnectedDevice); + } + + let mut saw_connected = false; + let mut saw_activity = false; + let mut last_err: Option = None; + let mut kicked_mask: u64 = 0; + let mut speculative_mask: u64 = 0; + let scan_start = tsc::read_tsc(); + // Don't let one flaky port starve the rest; rounds stay quick, scan window is longer. + let scan_timeout = self.tsc_freq * 8; + + while tsc::read_tsc().wrapping_sub(scan_start) < scan_timeout { + let mut round_connected = false; + + for port in 0..self.max_ports { + let mut ps = mmio::read32(self.portsc(port)); + if ps != 0 && ps != u32::MAX { + saw_activity = true; + } + + // Some controllers/hubs don't present CCS until after a short settle. + if ps & PORTSC_CCS == 0 && ps != 0 && ps != u32::MAX { + let bit = if port < 64 { 1u64 << port } else { 0 }; + if bit == 0 || (kicked_mask & bit) == 0 { + self.kick_port_detect(port); + if bit != 0 { + kicked_mask |= bit; + } + ps = mmio::read32(self.portsc(port)); + } + } - // settle delay before touching connected device - tsc_delay(self.tsc_freq, 100); - self.reset_transfer_state(); + let speed_hint = (ps >> PORTSC_SPEED_SHIFT) & 0xF; + let candidate = (ps & PORTSC_CCS != 0) + || (ps & PORTSC_PED != 0) + || speed_hint != 0; + if !candidate { + // Some controllers don't expose CCS/speed promptly; try once anyway. + if ps != 0 && ps != u32::MAX { + let bit = if port < 64 { 1u64 << port } else { 0 }; + if bit == 0 || (speculative_mask & bit) == 0 { + if bit != 0 { + speculative_mask |= bit; + } + tsc_delay(self.tsc_freq, 20); + self.reset_transfer_state(); + match self.try_port_with_mode(port, false) { + Ok(()) => { + self.log_port_success(port, ps); + return Ok(()); + } + Err(e) => { + self.log_port_attempt(port, ps, e); + last_err = Some(e); + } + } + } + } + continue; + } + + if ps & PORTSC_CCS != 0 { + saw_connected = true; + } + round_connected = true; - match self.try_port(port) { - Ok(()) => return Ok(()), - Err(_) => continue, + // hubs and card readers can appear late; let them settle. + tsc_delay(self.tsc_freq, 80); + self.reset_transfer_state(); + + match self.try_port(port) { + Ok(()) => { + self.log_port_success(port, ps); + return Ok(()); + } + Err(e) => { + self.log_port_attempt(port, ps, e); + last_err = Some(e); + } + } + } + + if round_connected { + tsc_delay(self.tsc_freq, 80); + } else { + tsc_delay(self.tsc_freq, 100); } } - Err(UsbMsdInitError::NoMedia) + + if saw_connected { + Err(last_err.unwrap_or(UsbMsdInitError::NoBotMassStorageInterface)) + } else if saw_activity { + Err(last_err.unwrap_or(UsbMsdInitError::ActivePortsNoConnectedDevice)) + } else { + Err(UsbMsdInitError::NoMedia) + } + } + + // Detection nudge only; do not reset here because firmware may own link state. + unsafe fn kick_port_detect(&self, port: u8) { + let _ = port; + // Give hardware a breath to update CCS/speed without us poking PR. + tsc_delay(self.tsc_freq, 8); } unsafe fn try_port(&mut self, port: u8) -> Result<(), UsbMsdInitError> { - let speed = self.port_reset(port)?; - let slot = self.cmd_enable_slot()?; + self.try_port_with_mode(port, false) + } + + unsafe fn try_port_with_mode(&mut self, port: u8, force_reset: bool) -> Result<(), UsbMsdInitError> { + let ps0 = mmio::read32(self.portsc(port)); + let mut speed = ((ps0 >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + let connected = (ps0 & PORTSC_CCS) != 0; + let linked = connected || (ps0 & PORTSC_PED) != 0 || speed != 0; + + if !linked && !force_reset { + return Err(UsbMsdInitError::PortResetNoLink); + } + + // CCS=1 but PED=0 means USB 2.0 connect without bus reset. + // Controller reset wiped UEFI state so port reset is mandatory, not destructive. + let enabled = (ps0 & PORTSC_PED) != 0; + let needs_reset = connected && !enabled; + + if needs_reset || force_reset { + match self.port_reset(port, true) { + Ok(s) => speed = s, + Err(e) => { + // fallback: if port still claims a connected+typed link, keep going + let ps1 = mmio::read32(self.portsc(port)); + let speed1 = ((ps1 >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if (ps1 & PORTSC_CCS) != 0 && speed1 != 0 { + speed = speed1; + } else { + return Err(match e { + UsbMsdInitError::PortResetTimeout => UsbMsdInitError::PortResetTimeout, + UsbMsdInitError::PortResetHotCmdTimeout => { + UsbMsdInitError::PortResetHotCmdTimeout + } + UsbMsdInitError::PortResetHotSettleTimeout => { + UsbMsdInitError::PortResetHotSettleTimeout + } + UsbMsdInitError::PortResetWarmTimeout => { + UsbMsdInitError::PortResetWarmTimeout + } + UsbMsdInitError::PortResetNoLink => UsbMsdInitError::PortResetNoLink, + _ => UsbMsdInitError::PortResetFailed, + }); + } + } + } + } else if connected && speed == 0 { + // PED=1 but speed missing (USB 3.0 quirk) — poll briefly. + let start = tsc::read_tsc(); + let timeout = self.tsc_freq / 10; + loop { + let ps = mmio::read32(self.portsc(port)); + let s = ((ps >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if s != 0 { speed = s; break; } + if tsc::read_tsc().wrapping_sub(start) > timeout { break; } + core::hint::spin_loop(); + } + if speed == 0 { speed = 1; } + } + + let slot = self + .cmd_enable_slot() + .map_err(|_| UsbMsdInitError::EnableSlotFailed)?; self.slot_id = slot; // wire output context into DCBAA @@ -510,20 +951,43 @@ impl UsbMsdDriver { out_ctx, ); - self.cmd_address_device(port, speed)?; + self.cmd_address_device(port, speed) + .map_err(|_| UsbMsdInitError::AddressDeviceFailed)?; // GET_DESCRIPTOR device (18 bytes) - let _dev_desc = self.control_in(0x80, 0x06, 0x0100, 0, 18)?; + let dev_desc = self + .control_in(0x80, 0x06, 0x0100, 0, 18) + .map_err(|_| UsbMsdInitError::DeviceDescriptorFailed)?; + + // If root-port device is a hub, we'd need hub class traversal for downstream SD. + if dev_desc.len() >= 6 && dev_desc[4] == USB_CLASS_HUB { + return Err(UsbMsdInitError::HubUnsupported); + } // GET_DESCRIPTOR configuration (up to 255 bytes) - let _cfg = self.control_in(0x80, 0x06, 0x0200, 0, 255)?; + let _cfg = self + .control_in(0x80, 0x06, 0x0200, 0, 255) + .map_err(|_| UsbMsdInitError::ConfigDescriptorFailed)?; // parse for mass-storage interface + bulk endpoints - let (cfg_val, ep_in, ep_out, mpkt_in, mpkt_out) = - self.parse_config_desc().ok_or(UsbMsdInitError::DeviceEnumerationFailed)?; + let (cfg_val, ep_in, ep_out, mpkt_in, mpkt_out) = match self.parse_config_desc() { + ConfigParse::MassStorage { + cfg_val, + ep_in, + ep_out, + mp_in, + mp_out, + } => (cfg_val, ep_in, ep_out, mp_in, mp_out), + ConfigParse::Hub => return Err(UsbMsdInitError::HubUnsupported), + ConfigParse::MassStorageUnsupported => { + return Err(UsbMsdInitError::MassStorageProtocolUnsupported) + } + ConfigParse::None => return Err(UsbMsdInitError::NoBotMassStorageInterface), + }; // SET_CONFIGURATION - self.control_nodata(0x00, 0x09, cfg_val as u16, 0)?; + self.control_nodata(0x00, 0x09, cfg_val as u16, 0) + .map_err(|_| UsbMsdInitError::SetConfigurationFailed)?; // compute DCIs let dci_in = ((ep_in & 0x0F) * 2) + ((ep_in >> 7) & 1); @@ -532,7 +996,8 @@ impl UsbMsdDriver { self.dci_bulk_out = dci_out; // configure endpoint command - self.cmd_configure_eps(dci_in, dci_out, mpkt_in, mpkt_out)?; + self.cmd_configure_eps(dci_in, dci_out, mpkt_in, mpkt_out) + .map_err(|_| UsbMsdInitError::ConfigureEndpointsFailed)?; Ok(()) } @@ -567,29 +1032,187 @@ impl UsbMsdDriver { self.op_base + PORT_REG_BASE + (port as u64) * PORT_REG_STRIDE } - unsafe fn port_reset(&self, port: u8) -> Result { + unsafe fn port_reset(&self, port: u8, force: bool) -> Result { let addr = self.portsc(port); - // preserve non-RW1C bits, set PR + let mut stage_timeout: Option = None; + + // ensure port power before any reset attempt let ps = mmio::read32(addr); - mmio::write32(addr, (ps & !PORTSC_RW1C) | PORTSC_PR); - let start = tsc::read_tsc(); - let timeout = self.tsc_freq / 2; - loop { - let ps = mmio::read32(addr); - if ps & PORTSC_PRC != 0 { - // clear PRC - mmio::write32(addr, (ps & !PORTSC_RW1C) | PORTSC_PRC); - if ps & PORTSC_PED != 0 { - let speed = ((ps >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if ps & PORTSC_PP == 0 { + portsc_write(addr, ps, PORTSC_PP, 0); + tsc_delay(self.tsc_freq, 10); + } + + // No link indicators at all: don't burn reset time on a probably empty port. + let pre = mmio::read32(addr); + let pre_speed = ((pre >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if !force && (pre & PORTSC_CCS) == 0 && (pre & PORTSC_PED) == 0 && pre_speed == 0 { + dbg("[USB] no link indicators, returning PortResetNoLink\n"); + return Err(UsbMsdInitError::PortResetNoLink); + } + + // Linux-style nudge: if SS link is in U3/Recovery/Resume, strobe link state to U0 first. + let pls = pre & PORTSC_PLS_MASK; + if pre_speed >= 4 && (pls == PLS_U3 || pls == PLS_RECOVERY || pls == PLS_RESUME) { + portsc_write(addr, pre, PORTSC_LWS | PLS_U0, PORTSC_PLS_MASK); + let start_u0 = tsc::read_tsc(); + let timeout_u0 = self.tsc_freq / 10; + loop { + let p = mmio::read32(addr); + if (p & PORTSC_PLS_MASK) == PLS_U0 { + break; + } + if tsc::read_tsc().wrapping_sub(start_u0) > timeout_u0 { + break; + } + core::hint::spin_loop(); + } + } + + // one quick hot reset per scan pass; later passes retry other ports fairly. + for _ in 0..1 { + let psh = mmio::read32(addr); + portsc_write(addr, psh, PORTSC_PR, 0); + + // stage 1: controller accepts/completes reset command + let start_cmd = tsc::read_tsc(); + let timeout_cmd = self.tsc_freq / 8; + let mut reset_done = false; + loop { + let psn = mmio::read32(addr); + if (psn & PORTSC_PR) == 0 || (psn & PORTSC_PRC) != 0 { + reset_done = true; + break; + } + if tsc::read_tsc().wrapping_sub(start_cmd) > timeout_cmd { + break; + } + core::hint::spin_loop(); + } + + // Clear sticky change bits only after reset command phase settles. + let ps_post_cmd = mmio::read32(addr); + let clr_post_cmd = ps_post_cmd & PORTSC_RW1C; + if clr_post_cmd != 0 { + portsc_write(addr, ps_post_cmd, clr_post_cmd, 0); + } + + if !reset_done { + let pst = mmio::read32(addr); + let speed_t = ((pst >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if (pst & PORTSC_CCS) != 0 && speed_t != 0 { + return Ok(speed_t); + } + stage_timeout = Some(UsbMsdInitError::PortResetHotCmdTimeout); + continue; + } + + // stage 2: give link training time to become usable + let start_settle = tsc::read_tsc(); + let timeout_settle = self.tsc_freq / 5; + loop { + let psn = mmio::read32(addr); + let clr = psn & PORTSC_RW1C; + if clr != 0 { + portsc_write(addr, psn, clr, 0); + } + + if psn & PORTSC_PED != 0 { + let speed = ((psn >> PORTSC_SPEED_SHIFT) & 0xF) as u8; return Ok(speed); } - return Err(UsbMsdInitError::DeviceEnumerationFailed); + + let speed = ((psn >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if (psn & PORTSC_CCS != 0) && speed != 0 { + return Ok(speed); + } + + // SS links can get stuck in non-U0 states right after reset. + let pls = psn & PORTSC_PLS_MASK; + if speed >= 4 && (pls == PLS_U3 || pls == PLS_RECOVERY || pls == PLS_RESUME) { + portsc_write(addr, psn, PORTSC_LWS | PLS_U0, PORTSC_PLS_MASK); + } + + if tsc::read_tsc().wrapping_sub(start_settle) > timeout_settle { + stage_timeout = Some(UsbMsdInitError::PortResetHotSettleTimeout); + break; + } + core::hint::spin_loop(); } - if tsc::read_tsc().wrapping_sub(start) > timeout { - return Err(UsbMsdInitError::CommandTimeout); + } + + // warm reset only for superspeed/CAS/stuck-link cases + let psw = mmio::read32(addr); + let hot_cmd_timed_out = matches!( + stage_timeout, + Some(UsbMsdInitError::PortResetHotCmdTimeout) + ); + let speedw = ((psw >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + let warm_fallback = hot_cmd_timed_out + && ((psw & (PORTSC_CCS | PORTSC_PED)) != 0 || speedw != 0); + if warm_reset_needed(psw) || warm_fallback { + portsc_write(addr, psw, PORTSC_WPR, 0); + let start_w = tsc::read_tsc(); + let timeout_w = self.tsc_freq / 5; + + loop { + let psn = mmio::read32(addr); + let clr = psn & PORTSC_RW1C; + if clr != 0 { + portsc_write(addr, psn, clr, 0); + } + + if psn & PORTSC_PED != 0 { + let speed = ((psn >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + return Ok(speed); + } + let speed = ((psn >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if (psn & PORTSC_CCS != 0) && speed != 0 { + return Ok(speed); + } + + if tsc::read_tsc().wrapping_sub(start_w) > timeout_w { + stage_timeout = Some(UsbMsdInitError::PortResetWarmTimeout); + break; + } + core::hint::spin_loop(); } - core::hint::spin_loop(); + } + + let psf = mmio::read32(addr); + let speedf = ((psf >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if (psf & PORTSC_CCS) != 0 && speedf != 0 { + // Link can be valid even when PED lags behind on some controllers. + tsc_delay(self.tsc_freq, 20); + return Ok(speedf); + } + let looks_connected = (psf & PORTSC_CCS) != 0 || speedf != 0 || (psf & PORTSC_PED) != 0; + if looks_connected { + // Some controllers surface connect/enable first and speed later. + let start_late = tsc::read_tsc(); + let late_timeout = self.tsc_freq / 10; + loop { + let psl = mmio::read32(addr); + let speedl = ((psl >> PORTSC_SPEED_SHIFT) & 0xF) as u8; + if speedl != 0 { + return Ok(speedl); + } + if tsc::read_tsc().wrapping_sub(start_late) > late_timeout { + break; + } + core::hint::spin_loop(); + } + + // Connected but speed never surfaced; assume full-speed to keep enumeration moving. + if (psf & (PORTSC_CCS | PORTSC_PED)) != 0 { + return Ok(1); + } + } + if looks_connected { + Err(UsbMsdInitError::PortResetNoLink) + } else { + Err(stage_timeout.unwrap_or(UsbMsdInitError::PortResetTimeout)) } } @@ -978,9 +1601,8 @@ impl UsbMsdDriver { // ─── descriptor parsing ────────────────────────────────────────────── - /// Parse the configuration descriptor at OFF_DESC for a mass-storage - /// BOT interface. Returns (config_val, ep_in_addr, ep_out_addr, max_pkt_in, max_pkt_out). - unsafe fn parse_config_desc(&self) -> Option<(u8, u8, u8, u16, u16)> { + /// Parse configuration descriptor and classify the device function. + unsafe fn parse_config_desc(&self) -> ConfigParse { let d = self.dma_base + OFF_DESC as u64; let total = u16::from_le_bytes([ core::ptr::read_volatile((d + 2) as *const u8), @@ -995,6 +1617,8 @@ impl UsbMsdDriver { let mut ep_out: u8 = 0; let mut mp_in: u16 = 0; let mut mp_out: u16 = 0; + let mut saw_hub_iface = false; + let mut saw_mass_storage_non_bot = false; while off + 2 <= limit { let blen = core::ptr::read_volatile((d + off as u64) as *const u8) as usize; @@ -1010,6 +1634,14 @@ impl UsbMsdDriver { let cls = core::ptr::read_volatile((d + off as u64 + 5) as *const u8); let sub = core::ptr::read_volatile((d + off as u64 + 6) as *const u8); let proto = core::ptr::read_volatile((d + off as u64 + 7) as *const u8); + if cls == USB_CLASS_HUB { + saw_hub_iface = true; + } + if cls == USB_CLASS_MASS_STORAGE + && (sub != USB_SUBCLASS_SCSI || proto != USB_PROTOCOL_BOT) + { + saw_mass_storage_non_bot = true; + } in_msc = cls == USB_CLASS_MASS_STORAGE && sub == USB_SUBCLASS_SCSI && proto == USB_PROTOCOL_BOT; } @@ -1036,9 +1668,19 @@ impl UsbMsdDriver { } if ep_in != 0 && ep_out != 0 { - Some((cfg_val, ep_in, ep_out, mp_in, mp_out)) + ConfigParse::MassStorage { + cfg_val, + ep_in, + ep_out, + mp_in, + mp_out, + } + } else if saw_hub_iface { + ConfigParse::Hub + } else if saw_mass_storage_non_bot { + ConfigParse::MassStorageUnsupported } else { - None + ConfigParse::None } } diff --git a/persistent/src/pe/embedded_reloc_data.rs b/persistent/src/pe/embedded_reloc_data.rs index 1b3919a3..521d754c 100644 --- a/persistent/src/pe/embedded_reloc_data.rs +++ b/persistent/src/pe/embedded_reloc_data.rs @@ -8,116 +8,121 @@ //! Run: ./tools/extract-reloc-data.sh after each build /// Original .reloc section RVA -pub const RELOC_RVA: u32 = 0x0064d000; +pub const RELOC_RVA: u32 = 0x0070d000; /// Original .reloc section size -pub const RELOC_SIZE: u32 = 0x000004a0; +pub const RELOC_SIZE: u32 = 0x000004d8; /// Original ImageBase from linker script pub const ORIGINAL_IMAGE_BASE: u64 = 0x0000004001000000; -/// Hardcoded .reloc section data (1184 bytes) -/// Extracted from morpheus-bootloader.efi at file offset 0x001fe400 +/// Hardcoded .reloc section data (1240 bytes) +/// Extracted from morpheus-bootloader.efi at file offset 0x00202400 #[allow(dead_code)] -pub const RELOC_DATA: [u8; 1184] = [ - 0x00, 0x30, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, +pub const RELOC_DATA: [u8; 1240] = [ + 0x00, 0x60, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0x10, 0xa7, 0x28, 0xa7, - 0x48, 0xa7, 0x50, 0xa8, 0x00, 0x40, 0x07, 0x00, 0x7c, 0x00, 0x00, 0x00, - 0x48, 0xa2, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xd0, 0xa4, 0xe8, 0xa4, - 0x00, 0xa5, 0x18, 0xa5, 0x30, 0xa5, 0x48, 0xa5, 0x60, 0xa5, 0x78, 0xa5, - 0x90, 0xa5, 0xa8, 0xa5, 0xb0, 0xa5, 0xb8, 0xa5, 0xc0, 0xa5, 0xc8, 0xa5, - 0xd0, 0xa5, 0xd8, 0xa5, 0xe0, 0xa5, 0xf8, 0xa6, 0x10, 0xa7, 0x28, 0xa7, - 0x18, 0xa8, 0x30, 0xa8, 0x48, 0xa8, 0x88, 0xa8, 0x08, 0xa9, 0x20, 0xa9, - 0x38, 0xa9, 0x78, 0xa9, 0x88, 0xa9, 0xd0, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, - 0x18, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xb8, 0xaa, 0xd0, 0xaa, - 0xe8, 0xaa, 0x00, 0xab, 0x18, 0xad, 0xd8, 0xad, 0x08, 0xae, 0xa8, 0xae, - 0xc0, 0xae, 0xd8, 0xae, 0xf0, 0xae, 0x08, 0xaf, 0x20, 0xaf, 0xa0, 0xaf, - 0xb8, 0xaf, 0xd0, 0xaf, 0xe8, 0xaf, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x18, 0xa0, 0x30, 0xa0, 0x48, 0xa0, - 0x60, 0xa0, 0xe0, 0xa0, 0xf8, 0xa0, 0x58, 0xa1, 0x70, 0xa1, 0x88, 0xa1, - 0x18, 0xa2, 0x30, 0xa2, 0x48, 0xa2, 0x98, 0xa8, 0xb0, 0xa8, 0xc8, 0xa8, - 0xe0, 0xa8, 0xf8, 0xa8, 0x00, 0x60, 0x07, 0x00, 0x84, 0x00, 0x00, 0x00, - 0xe0, 0xa0, 0x60, 0xa1, 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, 0x78, 0xa2, - 0x90, 0xa2, 0xa8, 0xa2, 0xc0, 0xa2, 0xd8, 0xa2, 0xf0, 0xa2, 0x78, 0xa3, - 0x90, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x00, 0xa4, 0x18, 0xa4, 0xa0, 0xa4, - 0xb8, 0xa4, 0x80, 0xa5, 0x98, 0xa5, 0x80, 0xa6, 0x98, 0xa6, 0xb0, 0xa6, - 0xc8, 0xa6, 0xe0, 0xa6, 0xf8, 0xa6, 0x88, 0xa8, 0xd0, 0xa9, 0xe8, 0xa9, - 0x38, 0xaa, 0xb0, 0xaa, 0xc8, 0xaa, 0xe0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, - 0x28, 0xab, 0x40, 0xab, 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, 0xa0, 0xab, - 0xb8, 0xab, 0xd0, 0xab, 0xe8, 0xab, 0x00, 0xac, 0x68, 0xac, 0x80, 0xac, - 0x98, 0xac, 0xb0, 0xac, 0xc8, 0xac, 0xe0, 0xac, 0xf8, 0xac, 0x10, 0xad, - 0x28, 0xad, 0x40, 0xad, 0x58, 0xad, 0xa8, 0xad, 0xb8, 0xad, 0x10, 0xae, - 0x20, 0xae, 0x00, 0x00, 0x00, 0x70, 0x07, 0x00, 0x94, 0x00, 0x00, 0x00, - 0x00, 0xa0, 0x18, 0xa0, 0x30, 0xa0, 0x48, 0xa0, 0xc8, 0xa0, 0xe0, 0xa0, - 0xf8, 0xa0, 0x10, 0xa1, 0x28, 0xa1, 0x40, 0xa1, 0x58, 0xa1, 0x70, 0xa1, - 0x88, 0xa1, 0xb8, 0xa1, 0x58, 0xa2, 0x70, 0xa2, 0xf0, 0xa2, 0x70, 0xa3, - 0x88, 0xa3, 0xa0, 0xa3, 0xb8, 0xa3, 0xd0, 0xa3, 0xe8, 0xa3, 0x70, 0xa4, - 0x98, 0xa5, 0xa8, 0xa5, 0xb8, 0xa5, 0x30, 0xa6, 0x48, 0xa6, 0x60, 0xa6, - 0x78, 0xa6, 0x90, 0xa6, 0xa8, 0xa6, 0xc0, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, - 0x08, 0xa7, 0x20, 0xa7, 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0x80, 0xa7, - 0x98, 0xa7, 0x40, 0xa9, 0x58, 0xa9, 0x70, 0xa9, 0x88, 0xa9, 0xa0, 0xa9, - 0xb8, 0xa9, 0xd0, 0xa9, 0xe8, 0xa9, 0x00, 0xaa, 0x88, 0xaa, 0xc8, 0xaa, - 0xd8, 0xaa, 0x98, 0xab, 0x28, 0xac, 0x68, 0xac, 0xb0, 0xac, 0x48, 0xad, - 0x90, 0xad, 0xa0, 0xad, 0xb0, 0xad, 0xc0, 0xad, 0x40, 0xae, 0x58, 0xae, - 0xd8, 0xae, 0x78, 0xaf, 0x88, 0xaf, 0x98, 0xaf, 0x00, 0x80, 0x07, 0x00, - 0x6c, 0x00, 0x00, 0x00, 0x30, 0xa0, 0x68, 0xa0, 0x80, 0xa0, 0x98, 0xa0, - 0xb0, 0xa0, 0x30, 0xa1, 0x70, 0xa1, 0x88, 0xa1, 0xc8, 0xa2, 0x68, 0xa3, - 0x88, 0xa3, 0x20, 0xa4, 0x58, 0xa4, 0x18, 0xa5, 0x30, 0xa5, 0x88, 0xa5, - 0xa0, 0xa5, 0x10, 0xa6, 0x28, 0xa6, 0x40, 0xa6, 0x78, 0xa6, 0xa8, 0xa6, - 0xd8, 0xa6, 0xf0, 0xa6, 0x88, 0xa7, 0xa0, 0xa7, 0xb8, 0xa7, 0xd0, 0xa7, - 0xe8, 0xa7, 0xf8, 0xa8, 0x58, 0xa9, 0xd0, 0xa9, 0xe0, 0xaa, 0xf8, 0xaa, - 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, - 0xe0, 0xad, 0xf8, 0xad, 0x10, 0xae, 0x28, 0xae, 0x40, 0xae, 0x58, 0xae, - 0x70, 0xae, 0xa8, 0xae, 0xb8, 0xaf, 0xd0, 0xaf, 0x00, 0x90, 0x07, 0x00, - 0x28, 0x00, 0x00, 0x00, 0xd0, 0xa2, 0x38, 0xa3, 0x58, 0xa3, 0x70, 0xa3, - 0x88, 0xa3, 0xa0, 0xa3, 0xb8, 0xa3, 0xc0, 0xa4, 0x30, 0xa5, 0x48, 0xa5, - 0x60, 0xa5, 0x78, 0xa5, 0x90, 0xa5, 0xa8, 0xa5, 0xe8, 0xa5, 0x78, 0xa6, - 0x00, 0xa0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x10, 0xa1, 0x78, 0xa1, - 0x78, 0xa6, 0x90, 0xa6, 0xa8, 0xa6, 0xc0, 0xa6, 0xd8, 0xa6, 0xf0, 0xa6, - 0x60, 0xa8, 0xb8, 0xa9, 0xc8, 0xa9, 0xd8, 0xa9, 0xe8, 0xa9, 0xf8, 0xa9, - 0x08, 0xaa, 0x18, 0xaa, 0x28, 0xaa, 0x38, 0xaa, 0x48, 0xaa, 0x58, 0xaa, - 0x68, 0xaa, 0x78, 0xaa, 0x88, 0xaa, 0x98, 0xaa, 0xa8, 0xaa, 0xb8, 0xaa, - 0xc8, 0xaa, 0xd8, 0xaa, 0xe8, 0xaa, 0xf8, 0xaa, 0x08, 0xab, 0x18, 0xab, - 0x28, 0xab, 0x38, 0xab, 0x48, 0xab, 0x58, 0xab, 0x68, 0xab, 0x78, 0xab, - 0x88, 0xab, 0x98, 0xab, 0xa8, 0xab, 0x88, 0xad, 0x00, 0xb0, 0x07, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0xa0, 0xaf, 0xf8, 0xaf, 0x00, 0xc0, 0x07, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x68, 0xa1, 0x80, 0xa1, 0xd0, 0xa1, 0xe0, 0xa1, - 0xf0, 0xa1, 0x00, 0xa2, 0x10, 0xa2, 0x40, 0xa2, 0x78, 0xa2, 0xc0, 0xa2, - 0x10, 0xa3, 0x48, 0xa3, 0x60, 0xa3, 0xa8, 0xa3, 0x30, 0xa4, 0x00, 0x00, - 0x00, 0xd0, 0x07, 0x00, 0x54, 0x00, 0x00, 0x00, 0x70, 0xa0, 0x00, 0xa1, - 0x18, 0xa1, 0x50, 0xa1, 0x80, 0xa1, 0x98, 0xa1, 0xb0, 0xa1, 0xc8, 0xa1, - 0xe0, 0xa1, 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, 0x68, 0xa2, 0x80, 0xa2, - 0x98, 0xa2, 0xb0, 0xa2, 0xc8, 0xa2, 0x00, 0xa3, 0x30, 0xa3, 0x48, 0xa3, - 0x60, 0xa3, 0x78, 0xa3, 0xb0, 0xa3, 0xc8, 0xa3, 0xe0, 0xa3, 0xf8, 0xa3, - 0x10, 0xa4, 0x28, 0xa4, 0xb8, 0xa4, 0x00, 0xab, 0x10, 0xab, 0x40, 0xab, - 0x58, 0xab, 0xa0, 0xab, 0xb0, 0xab, 0xc0, 0xab, 0xe8, 0xab, 0x20, 0xac, - 0x00, 0xe0, 0x07, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0xa1, 0x70, 0xa1, - 0x80, 0xa1, 0x90, 0xa1, 0xe0, 0xa1, 0xf0, 0xa1, 0x00, 0xa2, 0x10, 0xa2, - 0x20, 0xa2, 0x48, 0xa2, 0x58, 0xa2, 0x68, 0xa2, 0xd0, 0xa2, 0xe0, 0xa2, - 0xf0, 0xa2, 0x50, 0xa3, 0x90, 0xa3, 0xe0, 0xa3, 0xf0, 0xa3, 0x28, 0xa4, - 0x38, 0xa4, 0x60, 0xa4, 0x70, 0xa4, 0xa8, 0xa4, 0xc0, 0xa4, 0x18, 0xa5, - 0x88, 0xab, 0xa0, 0xab, 0xd8, 0xab, 0x28, 0xac, 0x70, 0xac, 0x80, 0xac, - 0x08, 0xae, 0x40, 0xae, 0x78, 0xae, 0x90, 0xae, 0xd0, 0xae, 0xe8, 0xae, - 0x20, 0xaf, 0x38, 0xaf, 0x50, 0xaf, 0x68, 0xaf, 0x80, 0xaf, 0x00, 0x00, - 0x00, 0xf0, 0x07, 0x00, 0x7c, 0x00, 0x00, 0x00, 0xd0, 0xa3, 0xe0, 0xa3, - 0x18, 0xa4, 0x28, 0xa4, 0x58, 0xa4, 0x68, 0xa4, 0x80, 0xa4, 0xc0, 0xa4, - 0xd0, 0xa4, 0xe8, 0xa4, 0x40, 0xa5, 0x50, 0xa5, 0x68, 0xa5, 0x80, 0xa5, - 0x98, 0xa5, 0xe8, 0xa5, 0xf8, 0xa5, 0x28, 0xa6, 0x60, 0xa6, 0x70, 0xa6, - 0xc0, 0xa6, 0xd0, 0xa6, 0x18, 0xa7, 0x28, 0xa7, 0xa8, 0xa7, 0xc0, 0xa7, - 0xd8, 0xa7, 0xf0, 0xa7, 0x88, 0xa8, 0xa0, 0xa8, 0xb8, 0xa8, 0xd0, 0xa8, - 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0xe8, 0xaa, 0x00, 0xab, 0x40, 0xab, - 0x58, 0xab, 0x70, 0xab, 0x88, 0xab, 0xc8, 0xab, 0x48, 0xac, 0x60, 0xac, - 0x78, 0xac, 0x90, 0xac, 0xa8, 0xac, 0xc0, 0xac, 0xd8, 0xac, 0xf0, 0xac, - 0x08, 0xad, 0x20, 0xad, 0x38, 0xad, 0x50, 0xad, 0x68, 0xad, 0xb8, 0xaf, - 0xd0, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x24, 0x00, 0x00, 0x00, - 0xc8, 0xa0, 0xe0, 0xa0, 0xf8, 0xa0, 0x48, 0xa1, 0xe0, 0xa1, 0x60, 0xa2, - 0xe8, 0xa2, 0x40, 0xa3, 0x58, 0xa3, 0x70, 0xa3, 0x88, 0xa3, 0xa0, 0xa3, - 0xb8, 0xa3, 0xd0, 0xa3, 0x00, 0x10, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0xe0, 0xa1, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0xa7, 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x20, 0xa7, 0x28, 0xa7, - 0x00, 0xc0, 0x1e, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x90, 0xa8, - 0x98, 0xa8, 0xa0, 0xa8, 0xb0, 0xa8, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0xe8, 0xae, 0x00, 0x00, 0x00, 0xc0, 0x64, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x00, 0x00, + 0x48, 0xa7, 0x08, 0xa9, 0x00, 0x70, 0x07, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x58, 0xa7, 0x98, 0xa9, 0xb0, 0xa9, 0xc8, 0xa9, 0xe0, 0xa9, 0xf8, 0xa9, + 0x10, 0xaa, 0x28, 0xaa, 0x40, 0xaa, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, + 0xa0, 0xaa, 0xb8, 0xaa, 0xc0, 0xaa, 0xc8, 0xaa, 0xd0, 0xaa, 0xd8, 0xaa, + 0xe0, 0xaa, 0xe8, 0xaa, 0xf0, 0xaa, 0x38, 0xac, 0x90, 0xac, 0xa8, 0xac, + 0xc0, 0xac, 0xf8, 0xac, 0x00, 0x80, 0x07, 0x00, 0x54, 0x00, 0x00, 0x00, + 0xc8, 0xa3, 0xe0, 0xa3, 0xf8, 0xa3, 0x10, 0xa4, 0x28, 0xa4, 0xe8, 0xa4, + 0x18, 0xa5, 0x98, 0xa5, 0xb0, 0xa5, 0xc8, 0xa5, 0x78, 0xa8, 0x90, 0xa8, + 0xd0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0xa0, 0xa9, 0xb8, 0xa9, + 0x08, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xb8, 0xaa, 0xf8, 0xaa, 0x08, 0xab, + 0x50, 0xab, 0x68, 0xab, 0x80, 0xab, 0x98, 0xab, 0xf0, 0xab, 0x08, 0xac, + 0x20, 0xac, 0x38, 0xac, 0x50, 0xac, 0x68, 0xac, 0x80, 0xac, 0xd8, 0xac, + 0xf0, 0xac, 0x08, 0xad, 0x00, 0x90, 0x07, 0x00, 0x58, 0x00, 0x00, 0x00, + 0xb8, 0xa0, 0xd0, 0xa0, 0xe8, 0xa0, 0x00, 0xa1, 0x18, 0xa1, 0x30, 0xa1, + 0x48, 0xa1, 0x60, 0xa1, 0x78, 0xa1, 0xf8, 0xa1, 0x78, 0xa2, 0x48, 0xa3, + 0x60, 0xa3, 0x78, 0xa3, 0x90, 0xa3, 0xa8, 0xa3, 0xc0, 0xa3, 0x40, 0xa4, + 0x58, 0xa4, 0x70, 0xa4, 0x88, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0xd0, 0xa4, + 0xe8, 0xa4, 0x00, 0xa5, 0x80, 0xa5, 0x98, 0xa5, 0xf8, 0xa5, 0x10, 0xa6, + 0x28, 0xa6, 0x88, 0xac, 0xa0, 0xac, 0x88, 0xad, 0xa0, 0xad, 0xb8, 0xad, + 0xd0, 0xad, 0xe8, 0xad, 0x00, 0xae, 0xa8, 0xaf, 0x00, 0xa0, 0x07, 0x00, + 0xa0, 0x00, 0x00, 0x00, 0xf0, 0xa0, 0x08, 0xa1, 0x58, 0xa1, 0xd0, 0xa1, + 0xe8, 0xa1, 0x00, 0xa2, 0x18, 0xa2, 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, + 0x78, 0xa2, 0x90, 0xa2, 0xa8, 0xa2, 0xc0, 0xa2, 0xd8, 0xa2, 0xf0, 0xa2, + 0x08, 0xa3, 0x20, 0xa3, 0x88, 0xa3, 0xa0, 0xa3, 0xb8, 0xa3, 0xd0, 0xa3, + 0xe8, 0xa3, 0x00, 0xa4, 0x18, 0xa4, 0x30, 0xa4, 0x48, 0xa4, 0x60, 0xa4, + 0x78, 0xa4, 0xc8, 0xa4, 0xd8, 0xa4, 0x30, 0xa5, 0x40, 0xa5, 0x20, 0xa7, + 0x38, 0xa7, 0x50, 0xa7, 0x68, 0xa7, 0xe8, 0xa7, 0x00, 0xa8, 0x18, 0xa8, + 0x30, 0xa8, 0x48, 0xa8, 0x60, 0xa8, 0x78, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, + 0xd8, 0xa8, 0x78, 0xa9, 0x90, 0xa9, 0x10, 0xaa, 0x90, 0xaa, 0xa8, 0xaa, + 0xc0, 0xaa, 0xd8, 0xaa, 0xf0, 0xaa, 0x08, 0xab, 0x90, 0xab, 0xb8, 0xac, + 0xc8, 0xac, 0xd8, 0xac, 0x50, 0xad, 0x68, 0xad, 0x80, 0xad, 0x98, 0xad, + 0xb0, 0xad, 0xc8, 0xad, 0xe0, 0xad, 0xf8, 0xad, 0x10, 0xae, 0x28, 0xae, + 0x40, 0xae, 0x58, 0xae, 0x70, 0xae, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, + 0x00, 0xb0, 0x07, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x60, 0xa0, 0x78, 0xa0, + 0x90, 0xa0, 0xa8, 0xa0, 0xc0, 0xa0, 0xd8, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, + 0x20, 0xa1, 0xa8, 0xa1, 0xe8, 0xa1, 0xf8, 0xa1, 0xb8, 0xa2, 0x48, 0xa3, + 0x88, 0xa3, 0xd0, 0xa3, 0x68, 0xa4, 0xb0, 0xa4, 0xc0, 0xa4, 0xd0, 0xa4, + 0xe0, 0xa4, 0x60, 0xa5, 0x78, 0xa5, 0xf8, 0xa5, 0x98, 0xa6, 0xa8, 0xa6, + 0xb8, 0xa6, 0x50, 0xa7, 0x88, 0xa7, 0xa0, 0xa7, 0xb8, 0xa7, 0xd0, 0xa7, + 0x50, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, 0xe8, 0xa9, 0x18, 0xaa, 0x38, 0xaa, + 0xd0, 0xaa, 0x90, 0xab, 0xa8, 0xab, 0x00, 0xac, 0x18, 0xac, 0x88, 0xac, + 0xa0, 0xac, 0xb8, 0xac, 0xf0, 0xac, 0x20, 0xad, 0x50, 0xad, 0x68, 0xad, + 0x00, 0xae, 0x18, 0xae, 0x30, 0xae, 0x48, 0xae, 0x60, 0xae, 0x70, 0xaf, + 0xd0, 0xaf, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x48, 0xa0, 0x40, 0xa1, 0x58, 0xa1, 0x70, 0xa1, 0x88, 0xa1, 0xa0, 0xa1, + 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, 0x30, 0xa4, 0x48, 0xa4, 0x60, 0xa4, + 0x78, 0xa4, 0x90, 0xa4, 0xa8, 0xa4, 0xc0, 0xa4, 0x38, 0xa6, 0x38, 0xa9, + 0xa0, 0xa9, 0xc0, 0xa9, 0xd8, 0xa9, 0xf0, 0xa9, 0x08, 0xaa, 0x20, 0xaa, + 0x28, 0xab, 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, 0xe0, 0xab, + 0x20, 0xac, 0xb0, 0xac, 0x00, 0xd0, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x58, 0xa7, 0xe0, 0xac, 0xf8, 0xac, 0x10, 0xad, 0x28, 0xad, 0x40, 0xad, + 0x58, 0xad, 0x88, 0xae, 0x48, 0xaf, 0x00, 0x00, 0x00, 0xe0, 0x07, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0xa0, 0xa0, 0xb0, 0xa0, 0xc0, 0xa0, 0xd0, 0xa0, + 0xe0, 0xa0, 0xf0, 0xa0, 0x00, 0xa1, 0x10, 0xa1, 0x20, 0xa1, 0x30, 0xa1, + 0x40, 0xa1, 0x50, 0xa1, 0x60, 0xa1, 0x70, 0xa1, 0x80, 0xa1, 0x90, 0xa1, + 0xa0, 0xa1, 0xb0, 0xa1, 0xc0, 0xa1, 0xd0, 0xa1, 0xe0, 0xa1, 0xf0, 0xa1, + 0x00, 0xa2, 0x10, 0xa2, 0x20, 0xa2, 0x30, 0xa2, 0x40, 0xa2, 0x50, 0xa2, + 0x60, 0xa2, 0x70, 0xa2, 0x80, 0xa2, 0x90, 0xa2, 0x70, 0xa4, 0x00, 0x00, + 0x00, 0xf0, 0x07, 0x00, 0x28, 0x00, 0x00, 0x00, 0x60, 0xa8, 0xf8, 0xa9, + 0x10, 0xaa, 0x60, 0xaa, 0x70, 0xaa, 0x80, 0xaa, 0x90, 0xaa, 0xa0, 0xaa, + 0xd0, 0xaa, 0x08, 0xab, 0x50, 0xab, 0xa0, 0xab, 0xd8, 0xab, 0xf0, 0xab, + 0x38, 0xac, 0xc0, 0xac, 0x00, 0x00, 0x08, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x00, 0xa9, 0x90, 0xa9, 0xa8, 0xa9, 0xe0, 0xa9, 0x10, 0xaa, 0x28, 0xaa, + 0x40, 0xaa, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xb8, 0xaa, + 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x58, 0xab, 0x90, 0xab, + 0xc0, 0xab, 0xd8, 0xab, 0xf0, 0xab, 0x08, 0xac, 0x40, 0xac, 0x58, 0xac, + 0x70, 0xac, 0x88, 0xac, 0xa0, 0xac, 0xb8, 0xac, 0x48, 0xad, 0x00, 0x00, + 0x00, 0x10, 0x08, 0x00, 0x50, 0x00, 0x00, 0x00, 0x90, 0xa3, 0xa0, 0xa3, + 0xd0, 0xa3, 0xe8, 0xa3, 0x30, 0xa4, 0x40, 0xa4, 0x50, 0xa4, 0x78, 0xa4, + 0xb0, 0xa4, 0xf0, 0xa9, 0x00, 0xaa, 0x10, 0xaa, 0x20, 0xaa, 0x70, 0xaa, + 0x80, 0xaa, 0x90, 0xaa, 0xa0, 0xaa, 0xb0, 0xaa, 0xd8, 0xaa, 0xe8, 0xaa, + 0xf8, 0xaa, 0x60, 0xab, 0x70, 0xab, 0x80, 0xab, 0xe0, 0xab, 0x20, 0xac, + 0x70, 0xac, 0x80, 0xac, 0xb8, 0xac, 0xc8, 0xac, 0xf0, 0xac, 0x00, 0xad, + 0x38, 0xad, 0x50, 0xad, 0xa8, 0xad, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x18, 0xa4, 0x30, 0xa4, 0x68, 0xa4, 0xb8, 0xa4, + 0x00, 0xa5, 0x10, 0xa5, 0x98, 0xa6, 0xd0, 0xa6, 0xe8, 0xa6, 0x20, 0xa7, + 0x60, 0xa7, 0x78, 0xa7, 0xb0, 0xa7, 0xe8, 0xa7, 0x00, 0xa8, 0x18, 0xa8, + 0x30, 0xa8, 0x48, 0xa8, 0x60, 0xa8, 0x78, 0xa8, 0x90, 0xa8, 0x38, 0xae, + 0x40, 0xae, 0x48, 0xae, 0x50, 0xae, 0x58, 0xae, 0x60, 0xae, 0x68, 0xae, + 0x70, 0xae, 0x78, 0xae, 0x80, 0xae, 0x88, 0xae, 0x90, 0xae, 0x98, 0xae, + 0xa0, 0xae, 0xa8, 0xae, 0xb0, 0xae, 0xb8, 0xae, 0xc0, 0xae, 0xc8, 0xae, + 0xd0, 0xae, 0xd8, 0xae, 0xe0, 0xae, 0x30, 0xaf, 0x40, 0xaf, 0x70, 0xaf, + 0xa8, 0xaf, 0xb8, 0xaf, 0x00, 0x30, 0x08, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x08, 0xa0, 0x18, 0xa0, 0x60, 0xa0, 0x70, 0xa0, 0x00, 0xa5, 0x10, 0xa5, + 0x48, 0xa5, 0x58, 0xa5, 0x88, 0xa5, 0x98, 0xa5, 0xb0, 0xa5, 0xf0, 0xa5, + 0x00, 0xa6, 0x18, 0xa6, 0x70, 0xa6, 0x80, 0xa6, 0x98, 0xa6, 0xb0, 0xa6, + 0xc8, 0xa6, 0x48, 0xa7, 0x60, 0xa7, 0x78, 0xa7, 0x90, 0xa7, 0xa8, 0xa7, + 0xc0, 0xa7, 0xd8, 0xa7, 0xa8, 0xa9, 0xc0, 0xa9, 0x00, 0xaa, 0x18, 0xaa, + 0x30, 0xaa, 0x48, 0xaa, 0x88, 0xaa, 0x08, 0xab, 0x20, 0xab, 0x38, 0xab, + 0x50, 0xab, 0x68, 0xab, 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, + 0xe0, 0xab, 0xf8, 0xab, 0x10, 0xac, 0x28, 0xac, 0x78, 0xae, 0x90, 0xae, + 0x88, 0xaf, 0xa0, 0xaf, 0xb8, 0xaf, 0x00, 0x00, 0x00, 0x40, 0x08, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x28, 0xa0, 0xa8, 0xa0, 0x30, 0xa1, 0x90, 0xa1, + 0xe8, 0xa1, 0x00, 0xa2, 0x18, 0xa2, 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, + 0x78, 0xa2, 0x00, 0x00, 0x00, 0x50, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0xe0, 0xa1, 0x00, 0x00, 0x00, 0x60, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x20, 0xa7, 0x28, 0xa7, 0x30, 0xa7, + 0x00, 0x00, 0x1f, 0x00, 0x10, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x90, 0xab, + 0x98, 0xab, 0xa0, 0xab, 0x00, 0x10, 0x1f, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0xe0, 0xa6, 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x18, 0xad, 0x00, 0x00, 0x00, 0xc0, 0x70, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x20, 0xa0, 0x00, 0x00, ]; diff --git a/setup-dev.sh b/setup-dev.sh index afead4e0..eeb9fbc7 100755 --- a/setup-dev.sh +++ b/setup-dev.sh @@ -11,6 +11,7 @@ readonly VERSION="2.0.4" # Must match the nightly in rust-toolchain.toml comments. # To update: change both here and in rust-toolchain.toml, then rebuild. readonly PINNED_NIGHTLY="nightly-2026-02-22" +readonly HELIX_PAYLOAD_MB="512" readonly C_RED='\033[0;31m' readonly C_GREEN='\033[0;32m' @@ -1091,11 +1092,15 @@ cmd_flash() { rmdir "$mnt" log_success "ESP written to ${esp_part}" - log_step "Injecting apps into HELIX (${helix_part})" + log_step "Formatting HELIX (${helix_part})" # Build morpheus-cli first. cargo build --release -p morpheus-cli 2>&1 \ || die "morpheus-cli build failed" + sudo "${PROJECT_ROOT}/target/release/morpheus-cli" format "$helix_part" \ + || die "Failed to format HELIX partition" + log_success "HELIX formatted — clean HelixFS ready" + log_step "Injecting apps into HELIX (${helix_part})" local injected=0 for entry in "${USER_APPS[@]}"; do local pkg="${entry%%,*}" @@ -1111,8 +1116,44 @@ cmd_flash() { injected=$((injected + 1)) done + log_step "Writing helix.img to ESP (${esp_part})" + mnt=$(mktemp -d) + sudo mount -t vfat "$esp_part" "$mnt" || { + die "Failed to remount ESP on ${esp_part}" + } + sudo mkdir -p "$mnt/morpheus" + + # Auto-fit payload to available ESP space with safety headroom for FAT metadata. + local esp_free_bytes + esp_free_bytes=$(df -B1 --output=avail "$mnt" | tail -n 1 | tr -d '[:space:]') + local reserve_bytes=$((24 * 1024 * 1024)) + local max_payload_bytes=$((HELIX_PAYLOAD_MB * 1024 * 1024)) + local payload_bytes=0 + if [[ "$esp_free_bytes" -gt "$reserve_bytes" ]]; then + payload_bytes=$((esp_free_bytes - reserve_bytes)) + if [[ "$payload_bytes" -gt "$max_payload_bytes" ]]; then + payload_bytes=$max_payload_bytes + fi + fi + + local payload_mb=$((payload_bytes / 1024 / 1024)) + if [[ "$payload_mb" -lt 64 ]]; then + sudo umount "$mnt" || true + rmdir "$mnt" || true + die "ESP free space too small for helix.img payload (need >=64 MiB usable)" + fi + + log_info "helix.img payload size: ${payload_mb} MiB (ESP free: $((esp_free_bytes / 1024 / 1024)) MiB)" + if ! sudo "${PROJECT_ROOT}/target/release/morpheus-cli" pack "$helix_part" "$mnt/morpheus/helix.img" --max-mb "$payload_mb"; then + sudo umount "$mnt" || true + rmdir "$mnt" || true + die "Failed to write helix.img to ESP" + fi + sudo umount "$mnt" + rmdir "$mnt" + sudo sync - log_success "Flash complete — ${injected} app(s) on ${helix_part}" + log_success "Flash complete — ${injected} app(s) on ${helix_part}, helix.img on ${esp_part}:/morpheus/helix.img" printf "\n Eject the card and boot the ThinkPad from it.\n\n" } From dc73e7c6ec0dd0ca5530b18f157b0e558de8e118 Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Sun, 15 Mar 2026 21:10:31 +0100 Subject: [PATCH 7/8] fix(smp): resolve AP initialization QEMU triple-fault regression by correcting stack order ahead of serial markers --- hwinit/asm/cpu/ap_trampoline.s | 64 ++++++++++++++++++++++++++-------- hwinit/src/cpu/ap_boot.rs | 5 +-- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/hwinit/asm/cpu/ap_trampoline.s b/hwinit/asm/cpu/ap_trampoline.s index 6fdd9546..5d290667 100644 --- a/hwinit/asm/cpu/ap_trampoline.s +++ b/hwinit/asm/cpu/ap_trampoline.s @@ -11,13 +11,17 @@ ; DATA AREA at offset 0xF00 within this page is filled by ap_boot.rs ; before each AP is woken. Layout must match the TD_* constants there. ; -; DEBUG markers are intentionally disabled in normal builds to keep -; early-boot logs readable on SMP. + ; DEBUG markers are ENABLED for real-hardware bring-up diagnosis. +; Each emits a single char to COM1 showing where the AP reached. +; ; ; ═══════════════════════════════════════════════════════════════════════════ -; ── COM1 debug macro ───────────────────────────────────────────────────── +; ── COM1 debug macros ──────────────────────────────────────────────────── ; direct port I/O, no LSR check — QEMU's virtual UART is always ready. +; real UART is fast enough for single-byte markers. + +; 16-bit real mode version %macro SERIAL_MARKER 1 push ax push dx @@ -28,6 +32,28 @@ pop ax %endmacro +; 32-bit protected mode version (uses eax/edx) +%macro SERIAL_MARKER32 1 + push eax + push edx + mov dx, 0x3F8 + mov al, %1 + out dx, al + pop edx + pop eax +%endmacro + +; 64-bit long mode version (uses rax/rdx) +%macro SERIAL_MARKER64 1 + push rax + push rdx + mov dx, 0x3F8 + mov al, %1 + out dx, al + pop rdx + pop rax +%endmacro + bits 16 org 0x8000 ; physical load address @@ -45,7 +71,7 @@ ap_start: mov ss, ax xor sp, sp ; stack at top of segment (wraps to 0xFFFF) - ; SERIAL_MARKER '1' ; marker 1: real mode entry alive + ; SERIAL_MARKER '1' ; marker 1: real mode entry alive ; ── load a TEMPORARY GDT that lives inside this trampoline page ────── ; The BSP's GDT is above 4 GB (PE BSS at 0x140xxxxxx). In 16-bit @@ -68,7 +94,7 @@ ap_start: ; ─────────────────────────────────────────────────────────────────────────── bits 32 ap_pm32: - ; marker disabled + ; SERIAL_MARKER32 '2' ; marker 2: protected mode reached ; load data segments with kernel data selector (0x10) mov ax, 0x10 @@ -78,16 +104,20 @@ ap_pm32: mov gs, ax mov ss, ax - ; ── enable PAE (required for long mode) ─────────────────────────────── + ; ── enable PAE + SSE support (required before Rust code) ───────────── + ; PAE: long mode prerequisite. + ; OSFXSR: OS supports FXSAVE/FXRSTOR — without this, SSE = #UD. + ; OSXMMEXCPT: OS handles SIMD FP exceptions. + ; UEFI sets these on the BSP. INIT resets CR4 to 0. we fix it here. mov eax, cr4 - or eax, (1 << 5) ; CR4.PAE + or eax, (1 << 5) | (1 << 9) | (1 << 10) ; PAE + OSFXSR + OSXMMEXCPT mov cr4, eax ; ── load kernel CR3 from data area ──────────────────────────────────── mov eax, dword [0x8F00] ; TD_CR3 low 32 bits (phys address < 4GB) mov cr3, eax - ; marker disabled + ; SERIAL_MARKER32 '3' ; marker 3: CR3 loaded, about to enable LM ; ── enable long mode via IA32_EFER.LME + NXE ────────────────────────── ; NXE (bit 11) is MANDATORY: the kernel page tables have NX bits (bit 63) @@ -98,12 +128,17 @@ ap_pm32: or eax, (1 << 8) | (1 << 11) ; LME + NXE wrmsr - ; ── enable paging → activates long mode ─────────────────────────────── + ; ── enable paging + caching → activates long mode ───────────────────── + ; INIT leaves CR0 = 0x60000011 (CD=1, NW=1). if we just OR in PG the AP + ; runs with caches OFF — every fetch hits DRAM. clear CD+NW here. + ; also set MP (monitor coprocessor) and NE (numeric exceptions) so the + ; FPU/SSE doesn't trip over legacy x87 emulation traps. mov eax, cr0 - or eax, (1 << 31) ; CR0.PG + and eax, 0x9FFFFFF3 ; clear CD(30), NW(29), TS(3), EM(2) + or eax, (1 << 31) | (1 << 1) | (1 << 5) ; PG + MP + NE mov cr0, eax - ; marker disabled + ; SERIAL_MARKER32 '4' ; marker 4: paging enabled, about to jump LM64 ; far jump to 64-bit long mode code. ; selector 0x18 = temp GDT's 64-bit code descriptor (L=1, D=0). @@ -115,7 +150,9 @@ ap_pm32: ; ─────────────────────────────────────────────────────────────────────────── bits 64 ap_lm64: - ; marker disabled + ; load the stack FIRST so pushes work! + mov rsp, qword [0x8F10] + ; SERIAL_MARKER64 '5' ; marker 5: 64-bit long mode reached ; reload data segments for 64-bit mode mov ax, 0x10 @@ -141,7 +178,6 @@ ap_lm64: ; RSP is 0 from the real-mode `xor sp, sp`. push/retfq would write ; to 0xFFFFFFFF_FFFFFFF8 which is unmapped → #PF → triple fault. ; load the real stack first so the retfq has somewhere to push. - mov rsp, qword [0x8F10] ; TD_STACK ; reload CS via a far return trick — push selector:offset, retfq lea rax, [rel .cs_reloaded] @@ -156,7 +192,7 @@ ap_lm64: mov esi, dword [0x8F1C] ; TD_LAPIC_ID → RSI (arg2, SysV x64) ; ── jump to Rust entry point ────────────────────────────────────────── - ; marker disabled + ; SERIAL_MARKER64 '6' ; marker 6: about to jump to Rust entry mov rax, qword [0x8F08] ; TD_ENTRY64 jmp rax ; ap_rust_entry(core_idx, lapic_id) — never returns diff --git a/hwinit/src/cpu/ap_boot.rs b/hwinit/src/cpu/ap_boot.rs index addc21b1..ec948323 100644 --- a/hwinit/src/cpu/ap_boot.rs +++ b/hwinit/src/cpu/ap_boot.rs @@ -141,7 +141,7 @@ unsafe fn boot_single_ap(core_idx: u32, lapic_id: u32) -> bool { let before = per_cpu::AP_ONLINE_COUNT.load(Ordering::SeqCst); // INIT IPI - apic::send_init_ipi(lapic_id); + apic::send_init_assert(lapic_id); apic::delay_us(10_000); // 10ms // SIPI #1 @@ -331,7 +331,8 @@ pub unsafe extern "sysv64" fn ap_rust_entry(core_idx: u32, lapic_id: u32) -> ! { // 6. Initialize LAPIC + timer on this core apic::init_ap(); - apic::setup_timer(100); // 100 Hz, same as BSP + apic::setup_timer(100); + crate::cpu::per_cpu::AP_ONLINE_COUNT.fetch_add(1, core::sync::atomic::Ordering::SeqCst); // 7. Signal we're online (done in init_ap via per_cpu::init_ap) From 6258783998df3caeb3199cc1bdefd6d38bdf0302 Mon Sep 17 00:00:00 2001 From: "T. Andrew Davis" Date: Wed, 13 May 2026 18:57:09 +0200 Subject: [PATCH 8/8] feat(apic): add LAPIC timer initialization and synchronization for APs --- hwinit/src/cpu/apic.rs | 67 ++++++++---- hwinit/src/cpu/per_cpu.rs | 2 +- persistent/src/pe/embedded_reloc_data.rs | 133 +++++++++++------------ scripts/qemu-e2e.sh | 2 +- wait_test.rs | 3 + 5 files changed, 118 insertions(+), 89 deletions(-) create mode 100644 wait_test.rs diff --git a/hwinit/src/cpu/apic.rs b/hwinit/src/cpu/apic.rs index c66db193..69dbed6b 100644 --- a/hwinit/src/cpu/apic.rs +++ b/hwinit/src/cpu/apic.rs @@ -9,7 +9,12 @@ use crate::cpu::per_cpu; use crate::cpu::pio::outb; use crate::serial::{log_info, log_ok, log_warn}; -use core::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + +/// LAPIC timer init count calibrated by BSP — shared with all APs. +/// APs read this and skip the PIT calibration race. +/// 0 = not yet calibrated (do full calibration). +static LAPIC_TIMER_INIT_COUNT: AtomicU32 = AtomicU32::new(0); @@ -251,6 +256,18 @@ pub unsafe fn send_eoi() { pub unsafe fn setup_timer(target_hz: u32) { let base = lapic_base(); + // APs skip PIT calibration — they use the BSP's result directly. + // multiple APs racing on PIT channel 2 simultaneously will reset each + // other's countdown → all get stuck in the spin loop forever. + // BSP stores init_count after successful calibration. APs load it. + let cached = LAPIC_TIMER_INIT_COUNT.load(Ordering::Acquire); + if cached != 0 { + lapic_write(base, LAPIC_LVT_TIMER, TIMER_PERIODIC | TIMER_VECTOR as u32); + lapic_write(base, LAPIC_TIMER_DIV, 0x03); + lapic_write(base, LAPIC_TIMER_INIT, cached); + return; + } + // divide configuration: divide by 16 lapic_write(base, LAPIC_TIMER_DIV, 0x03); // 0b0011 = divide by 16 @@ -305,6 +322,10 @@ pub unsafe fn setup_timer(target_hz: u32) { let init_count = (ticks_per_second / target_hz as u64) as u32; let _ = (elapsed, init_count, target_hz); + + // store for APs before arming so they never touch the PIT + LAPIC_TIMER_INIT_COUNT.store(init_count, Ordering::Release); + if crate::cpu::per_cpu::current_core_index() == 0 { log_ok("LAPIC", 724, "timer configured"); } @@ -327,36 +348,42 @@ pub unsafe fn disable_pic8259() { // ── IPI ────────────────────────────────────────────────────────────────── -/// Send an INIT IPI to target APIC ID. -pub unsafe fn send_init_ipi(target_apic_id: u32) { +/// Assert INIT IPI to target APIC ID. +/// Caller MUST wait >= 200µs then call `send_init_deassert`. +pub unsafe fn send_init_assert(target_apic_id: u32) { let base = lapic_base(); if x2apic_enabled() { - // x2APIC uses MSR 0x830 for ICR writes; MMIO ICR can wedge on some hw. - let init_assert = (target_apic_id as u64) << 32 | (ICR_INIT | ICR_LEVEL_ASSERT) as u64; - wrmsr(IA32_X2APIC_ICR_MSR, init_assert); - wait_icr_idle(base); - - let init_deassert = (target_apic_id as u64) << 32 - | (ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT) as u64; - wrmsr(IA32_X2APIC_ICR_MSR, init_deassert); + // level-triggered + assert + INIT = 0xC500 + let icr = (target_apic_id as u64) << 32 + | (ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_ASSERT) as u64; + wrmsr(IA32_X2APIC_ICR_MSR, icr); wait_icr_idle(base); return; } - // INIT assert + // level-triggered assert. 0xC500 — matches linux/xv6/every OS ever shipped. lapic_write(base, LAPIC_ICR_HI, target_apic_id << 24); - lapic_write(base, LAPIC_ICR_LO, ICR_INIT | ICR_LEVEL_ASSERT); + lapic_write(base, LAPIC_ICR_LO, ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_ASSERT); wait_icr_idle(base); +} - // INIT deassert — trigger mode MUST be level (bit 15) with level=0. - // edge-triggered deassert is treated as another INIT assertion by KVM. +/// Deassert INIT IPI to target APIC ID. +/// Call after holding INIT assert for >= 200µs. +pub unsafe fn send_init_deassert(target_apic_id: u32) { + let base = lapic_base(); + + if x2apic_enabled() { + let icr = (target_apic_id as u64) << 32 + | (ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT) as u64; + wrmsr(IA32_X2APIC_ICR_MSR, icr); + wait_icr_idle(base); + return; + } + + // level-triggered deassert. trigger MUST be level or KVM treats it as assert. lapic_write(base, LAPIC_ICR_HI, target_apic_id << 24); - lapic_write( - base, - LAPIC_ICR_LO, - ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT, - ); + lapic_write(base, LAPIC_ICR_LO, ICR_INIT | ICR_TRIGGER_LEVEL | ICR_LEVEL_DEASSERT); wait_icr_idle(base); } diff --git a/hwinit/src/cpu/per_cpu.rs b/hwinit/src/cpu/per_cpu.rs index 3c9d27d4..a659d592 100644 --- a/hwinit/src/cpu/per_cpu.rs +++ b/hwinit/src/cpu/per_cpu.rs @@ -261,7 +261,7 @@ pub unsafe fn init_ap(core_idx: u32, lapic_id: u32, lapic_base: u64) { wrmsr(IA32_GS_BASE, addr); wrmsr(IA32_KERNEL_GS_BASE, 0); - AP_ONLINE_COUNT.fetch_add(1, Ordering::SeqCst); + // AP bring-up is noisy on many-core systems; summary is logged by SMP phase. } diff --git a/persistent/src/pe/embedded_reloc_data.rs b/persistent/src/pe/embedded_reloc_data.rs index 521d754c..0bf34a9b 100644 --- a/persistent/src/pe/embedded_reloc_data.rs +++ b/persistent/src/pe/embedded_reloc_data.rs @@ -11,15 +11,15 @@ pub const RELOC_RVA: u32 = 0x0070d000; /// Original .reloc section size -pub const RELOC_SIZE: u32 = 0x000004d8; +pub const RELOC_SIZE: u32 = 0x000004d4; /// Original ImageBase from linker script pub const ORIGINAL_IMAGE_BASE: u64 = 0x0000004001000000; -/// Hardcoded .reloc section data (1240 bytes) +/// Hardcoded .reloc section data (1236 bytes) /// Extracted from morpheus-bootloader.efi at file offset 0x00202400 #[allow(dead_code)] -pub const RELOC_DATA: [u8; 1240] = [ +pub const RELOC_DATA: [u8; 1236] = [ 0x00, 0x60, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x50, 0xa4, 0xe0, 0xa5, 0x38, 0xa6, 0x50, 0xa6, 0x68, 0xa6, 0x80, 0xa6, 0x10, 0xa7, 0x28, 0xa7, 0x48, 0xa7, 0x08, 0xa9, 0x00, 0x70, 0x07, 0x00, 0x3c, 0x00, 0x00, 0x00, @@ -55,74 +55,73 @@ pub const RELOC_DATA: [u8; 1240] = [ 0xc8, 0xac, 0xd8, 0xac, 0x50, 0xad, 0x68, 0xad, 0x80, 0xad, 0x98, 0xad, 0xb0, 0xad, 0xc8, 0xad, 0xe0, 0xad, 0xf8, 0xad, 0x10, 0xae, 0x28, 0xae, 0x40, 0xae, 0x58, 0xae, 0x70, 0xae, 0x88, 0xae, 0xa0, 0xae, 0xb8, 0xae, - 0x00, 0xb0, 0x07, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x60, 0xa0, 0x78, 0xa0, + 0x00, 0xb0, 0x07, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x60, 0xa0, 0x78, 0xa0, 0x90, 0xa0, 0xa8, 0xa0, 0xc0, 0xa0, 0xd8, 0xa0, 0xf0, 0xa0, 0x08, 0xa1, 0x20, 0xa1, 0xa8, 0xa1, 0xe8, 0xa1, 0xf8, 0xa1, 0xb8, 0xa2, 0x48, 0xa3, 0x88, 0xa3, 0xd0, 0xa3, 0x68, 0xa4, 0xb0, 0xa4, 0xc0, 0xa4, 0xd0, 0xa4, 0xe0, 0xa4, 0x60, 0xa5, 0x78, 0xa5, 0xf8, 0xa5, 0x98, 0xa6, 0xa8, 0xa6, 0xb8, 0xa6, 0x50, 0xa7, 0x88, 0xa7, 0xa0, 0xa7, 0xb8, 0xa7, 0xd0, 0xa7, - 0x50, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, 0xe8, 0xa9, 0x18, 0xaa, 0x38, 0xaa, - 0xd0, 0xaa, 0x90, 0xab, 0xa8, 0xab, 0x00, 0xac, 0x18, 0xac, 0x88, 0xac, - 0xa0, 0xac, 0xb8, 0xac, 0xf0, 0xac, 0x20, 0xad, 0x50, 0xad, 0x68, 0xad, - 0x00, 0xae, 0x18, 0xae, 0x30, 0xae, 0x48, 0xae, 0x60, 0xae, 0x70, 0xaf, - 0xd0, 0xaf, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x48, 0xa0, 0x40, 0xa1, 0x58, 0xa1, 0x70, 0xa1, 0x88, 0xa1, 0xa0, 0xa1, - 0xf8, 0xa1, 0x10, 0xa2, 0x28, 0xa2, 0x30, 0xa4, 0x48, 0xa4, 0x60, 0xa4, - 0x78, 0xa4, 0x90, 0xa4, 0xa8, 0xa4, 0xc0, 0xa4, 0x38, 0xa6, 0x38, 0xa9, - 0xa0, 0xa9, 0xc0, 0xa9, 0xd8, 0xa9, 0xf0, 0xa9, 0x08, 0xaa, 0x20, 0xaa, - 0x28, 0xab, 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, 0xe0, 0xab, - 0x20, 0xac, 0xb0, 0xac, 0x00, 0xd0, 0x07, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x58, 0xa7, 0xe0, 0xac, 0xf8, 0xac, 0x10, 0xad, 0x28, 0xad, 0x40, 0xad, - 0x58, 0xad, 0x88, 0xae, 0x48, 0xaf, 0x00, 0x00, 0x00, 0xe0, 0x07, 0x00, - 0x4c, 0x00, 0x00, 0x00, 0xa0, 0xa0, 0xb0, 0xa0, 0xc0, 0xa0, 0xd0, 0xa0, - 0xe0, 0xa0, 0xf0, 0xa0, 0x00, 0xa1, 0x10, 0xa1, 0x20, 0xa1, 0x30, 0xa1, - 0x40, 0xa1, 0x50, 0xa1, 0x60, 0xa1, 0x70, 0xa1, 0x80, 0xa1, 0x90, 0xa1, - 0xa0, 0xa1, 0xb0, 0xa1, 0xc0, 0xa1, 0xd0, 0xa1, 0xe0, 0xa1, 0xf0, 0xa1, - 0x00, 0xa2, 0x10, 0xa2, 0x20, 0xa2, 0x30, 0xa2, 0x40, 0xa2, 0x50, 0xa2, - 0x60, 0xa2, 0x70, 0xa2, 0x80, 0xa2, 0x90, 0xa2, 0x70, 0xa4, 0x00, 0x00, - 0x00, 0xf0, 0x07, 0x00, 0x28, 0x00, 0x00, 0x00, 0x60, 0xa8, 0xf8, 0xa9, - 0x10, 0xaa, 0x60, 0xaa, 0x70, 0xaa, 0x80, 0xaa, 0x90, 0xaa, 0xa0, 0xaa, - 0xd0, 0xaa, 0x08, 0xab, 0x50, 0xab, 0xa0, 0xab, 0xd8, 0xab, 0xf0, 0xab, - 0x38, 0xac, 0xc0, 0xac, 0x00, 0x00, 0x08, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x00, 0xa9, 0x90, 0xa9, 0xa8, 0xa9, 0xe0, 0xa9, 0x10, 0xaa, 0x28, 0xaa, - 0x40, 0xaa, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, 0xb8, 0xaa, - 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x58, 0xab, 0x90, 0xab, - 0xc0, 0xab, 0xd8, 0xab, 0xf0, 0xab, 0x08, 0xac, 0x40, 0xac, 0x58, 0xac, - 0x70, 0xac, 0x88, 0xac, 0xa0, 0xac, 0xb8, 0xac, 0x48, 0xad, 0x00, 0x00, - 0x00, 0x10, 0x08, 0x00, 0x50, 0x00, 0x00, 0x00, 0x90, 0xa3, 0xa0, 0xa3, - 0xd0, 0xa3, 0xe8, 0xa3, 0x30, 0xa4, 0x40, 0xa4, 0x50, 0xa4, 0x78, 0xa4, - 0xb0, 0xa4, 0xf0, 0xa9, 0x00, 0xaa, 0x10, 0xaa, 0x20, 0xaa, 0x70, 0xaa, - 0x80, 0xaa, 0x90, 0xaa, 0xa0, 0xaa, 0xb0, 0xaa, 0xd8, 0xaa, 0xe8, 0xaa, - 0xf8, 0xaa, 0x60, 0xab, 0x70, 0xab, 0x80, 0xab, 0xe0, 0xab, 0x20, 0xac, - 0x70, 0xac, 0x80, 0xac, 0xb8, 0xac, 0xc8, 0xac, 0xf0, 0xac, 0x00, 0xad, - 0x38, 0xad, 0x50, 0xad, 0xa8, 0xad, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, - 0x68, 0x00, 0x00, 0x00, 0x18, 0xa4, 0x30, 0xa4, 0x68, 0xa4, 0xb8, 0xa4, - 0x00, 0xa5, 0x10, 0xa5, 0x98, 0xa6, 0xd0, 0xa6, 0xe8, 0xa6, 0x20, 0xa7, - 0x60, 0xa7, 0x78, 0xa7, 0xb0, 0xa7, 0xe8, 0xa7, 0x00, 0xa8, 0x18, 0xa8, - 0x30, 0xa8, 0x48, 0xa8, 0x60, 0xa8, 0x78, 0xa8, 0x90, 0xa8, 0x38, 0xae, - 0x40, 0xae, 0x48, 0xae, 0x50, 0xae, 0x58, 0xae, 0x60, 0xae, 0x68, 0xae, - 0x70, 0xae, 0x78, 0xae, 0x80, 0xae, 0x88, 0xae, 0x90, 0xae, 0x98, 0xae, - 0xa0, 0xae, 0xa8, 0xae, 0xb0, 0xae, 0xb8, 0xae, 0xc0, 0xae, 0xc8, 0xae, - 0xd0, 0xae, 0xd8, 0xae, 0xe0, 0xae, 0x30, 0xaf, 0x40, 0xaf, 0x70, 0xaf, - 0xa8, 0xaf, 0xb8, 0xaf, 0x00, 0x30, 0x08, 0x00, 0x70, 0x00, 0x00, 0x00, - 0x08, 0xa0, 0x18, 0xa0, 0x60, 0xa0, 0x70, 0xa0, 0x00, 0xa5, 0x10, 0xa5, - 0x48, 0xa5, 0x58, 0xa5, 0x88, 0xa5, 0x98, 0xa5, 0xb0, 0xa5, 0xf0, 0xa5, - 0x00, 0xa6, 0x18, 0xa6, 0x70, 0xa6, 0x80, 0xa6, 0x98, 0xa6, 0xb0, 0xa6, - 0xc8, 0xa6, 0x48, 0xa7, 0x60, 0xa7, 0x78, 0xa7, 0x90, 0xa7, 0xa8, 0xa7, - 0xc0, 0xa7, 0xd8, 0xa7, 0xa8, 0xa9, 0xc0, 0xa9, 0x00, 0xaa, 0x18, 0xaa, - 0x30, 0xaa, 0x48, 0xaa, 0x88, 0xaa, 0x08, 0xab, 0x20, 0xab, 0x38, 0xab, - 0x50, 0xab, 0x68, 0xab, 0x80, 0xab, 0x98, 0xab, 0xb0, 0xab, 0xc8, 0xab, - 0xe0, 0xab, 0xf8, 0xab, 0x10, 0xac, 0x28, 0xac, 0x78, 0xae, 0x90, 0xae, - 0x88, 0xaf, 0xa0, 0xaf, 0xb8, 0xaf, 0x00, 0x00, 0x00, 0x40, 0x08, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x28, 0xa0, 0xa8, 0xa0, 0x30, 0xa1, 0x90, 0xa1, - 0xe8, 0xa1, 0x00, 0xa2, 0x18, 0xa2, 0x30, 0xa2, 0x48, 0xa2, 0x60, 0xa2, - 0x78, 0xa2, 0x00, 0x00, 0x00, 0x50, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0xe0, 0xa1, 0x00, 0x00, 0x00, 0x60, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x08, 0xa7, 0x10, 0xa7, 0x18, 0xa7, 0x20, 0xa7, 0x28, 0xa7, 0x30, 0xa7, - 0x00, 0x00, 0x1f, 0x00, 0x10, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x90, 0xab, - 0x98, 0xab, 0xa0, 0xab, 0x00, 0x10, 0x1f, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0xe0, 0xa6, 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x18, 0xad, 0x00, 0x00, 0x00, 0xc0, 0x70, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x20, 0xa0, 0x00, 0x00, + 0x50, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, 0xe8, 0xa9, 0x10, 0xab, 0x28, 0xab, + 0x58, 0xab, 0x90, 0xab, 0x18, 0xac, 0x30, 0xac, 0xc8, 0xac, 0xe0, 0xac, + 0xf8, 0xac, 0x10, 0xad, 0x28, 0xad, 0x38, 0xae, 0x98, 0xae, 0x10, 0xaf, + 0x00, 0xc0, 0x07, 0x00, 0x58, 0x00, 0x00, 0x00, 0x30, 0xa0, 0x88, 0xa0, + 0xa0, 0xa0, 0x10, 0xa1, 0x28, 0xa1, 0x40, 0xa1, 0x78, 0xa1, 0xd0, 0xa1, + 0xe8, 0xa1, 0x00, 0xa2, 0x18, 0xa2, 0x30, 0xa2, 0x60, 0xa2, 0x80, 0xa2, + 0x18, 0xa3, 0x30, 0xa3, 0x48, 0xa3, 0x50, 0xa5, 0x68, 0xa5, 0x80, 0xa5, + 0x98, 0xa5, 0xb0, 0xa5, 0xc8, 0xa5, 0xe0, 0xa5, 0xa0, 0xa6, 0x08, 0xa7, + 0x28, 0xa7, 0x40, 0xa7, 0x58, 0xa7, 0x70, 0xa7, 0x88, 0xa7, 0x90, 0xa8, + 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0x30, 0xa9, 0x48, 0xa9, 0x88, 0xa9, + 0x18, 0xaa, 0x00, 0x00, 0x00, 0xd0, 0x07, 0x00, 0x5c, 0x00, 0x00, 0x00, + 0xb8, 0xa4, 0xd0, 0xa4, 0x58, 0xaa, 0x70, 0xaa, 0x88, 0xaa, 0xa0, 0xaa, + 0xb8, 0xaa, 0xd0, 0xaa, 0xe8, 0xab, 0xa8, 0xac, 0x00, 0xae, 0x10, 0xae, + 0x20, 0xae, 0x30, 0xae, 0x40, 0xae, 0x50, 0xae, 0x60, 0xae, 0x70, 0xae, + 0x80, 0xae, 0x90, 0xae, 0xa0, 0xae, 0xb0, 0xae, 0xc0, 0xae, 0xd0, 0xae, + 0xe0, 0xae, 0xf0, 0xae, 0x00, 0xaf, 0x10, 0xaf, 0x20, 0xaf, 0x30, 0xaf, + 0x40, 0xaf, 0x50, 0xaf, 0x60, 0xaf, 0x70, 0xaf, 0x80, 0xaf, 0x90, 0xaf, + 0xa0, 0xaf, 0xb0, 0xaf, 0xc0, 0xaf, 0xd0, 0xaf, 0xe0, 0xaf, 0xf0, 0xaf, + 0x00, 0xe0, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xd0, 0xa1, 0x00, 0x00, + 0x00, 0xf0, 0x07, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x90, 0xa4, 0x20, 0xa7, + 0x30, 0xa8, 0xc8, 0xaa, 0xe0, 0xaa, 0x30, 0xab, 0x40, 0xab, 0x50, 0xab, + 0x60, 0xab, 0x70, 0xab, 0xa0, 0xab, 0xd8, 0xab, 0x20, 0xac, 0x70, 0xac, + 0xa8, 0xac, 0xc0, 0xac, 0x08, 0xad, 0x90, 0xad, 0x00, 0x00, 0x08, 0x00, + 0x44, 0x00, 0x00, 0x00, 0xd0, 0xa9, 0x60, 0xaa, 0x78, 0xaa, 0xb0, 0xaa, + 0xe0, 0xaa, 0xf8, 0xaa, 0x10, 0xab, 0x28, 0xab, 0x40, 0xab, 0x58, 0xab, + 0x70, 0xab, 0x88, 0xab, 0xc8, 0xab, 0xe0, 0xab, 0xf8, 0xab, 0x10, 0xac, + 0x28, 0xac, 0x60, 0xac, 0x90, 0xac, 0xa8, 0xac, 0xc0, 0xac, 0xd8, 0xac, + 0x10, 0xad, 0x28, 0xad, 0x40, 0xad, 0x58, 0xad, 0x70, 0xad, 0x88, 0xad, + 0x18, 0xae, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x60, 0xa4, 0x70, 0xa4, 0xa0, 0xa4, 0xb8, 0xa4, 0x00, 0xa5, 0x10, 0xa5, + 0x20, 0xa5, 0x48, 0xa5, 0x80, 0xa5, 0xc0, 0xaa, 0xd0, 0xaa, 0xe0, 0xaa, + 0xf0, 0xaa, 0x40, 0xab, 0x50, 0xab, 0x60, 0xab, 0x70, 0xab, 0x80, 0xab, + 0xa8, 0xab, 0xb8, 0xab, 0xc8, 0xab, 0x30, 0xac, 0x40, 0xac, 0x50, 0xac, + 0xb0, 0xac, 0xf0, 0xac, 0x40, 0xad, 0x50, 0xad, 0x88, 0xad, 0x98, 0xad, + 0xc0, 0xad, 0xd0, 0xad, 0x08, 0xae, 0x20, 0xae, 0x78, 0xae, 0x00, 0x00, + 0x00, 0x20, 0x08, 0x00, 0x60, 0x00, 0x00, 0x00, 0xe8, 0xa4, 0x00, 0xa5, + 0x38, 0xa5, 0x88, 0xa5, 0xd0, 0xa5, 0xe0, 0xa5, 0x68, 0xa7, 0xa0, 0xa7, + 0xb8, 0xa7, 0xf0, 0xa7, 0x30, 0xa8, 0x48, 0xa8, 0x80, 0xa8, 0xb8, 0xa8, + 0xd0, 0xa8, 0xe8, 0xa8, 0x00, 0xa9, 0x18, 0xa9, 0x30, 0xa9, 0x48, 0xa9, + 0x60, 0xa9, 0x08, 0xaf, 0x10, 0xaf, 0x18, 0xaf, 0x20, 0xaf, 0x28, 0xaf, + 0x30, 0xaf, 0x38, 0xaf, 0x40, 0xaf, 0x48, 0xaf, 0x50, 0xaf, 0x58, 0xaf, + 0x60, 0xaf, 0x68, 0xaf, 0x70, 0xaf, 0x78, 0xaf, 0x80, 0xaf, 0x88, 0xaf, + 0x90, 0xaf, 0x98, 0xaf, 0xa0, 0xaf, 0xa8, 0xaf, 0xb0, 0xaf, 0x00, 0x00, + 0x00, 0x30, 0x08, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x10, 0xa0, + 0x40, 0xa0, 0x78, 0xa0, 0x88, 0xa0, 0xd8, 0xa0, 0xe8, 0xa0, 0x30, 0xa1, + 0x40, 0xa1, 0xd0, 0xa5, 0xe0, 0xa5, 0x18, 0xa6, 0x28, 0xa6, 0x58, 0xa6, + 0x68, 0xa6, 0x80, 0xa6, 0xc0, 0xa6, 0xd0, 0xa6, 0xe8, 0xa6, 0x40, 0xa7, + 0x50, 0xa7, 0x68, 0xa7, 0x80, 0xa7, 0x98, 0xa7, 0x18, 0xa8, 0x30, 0xa8, + 0x48, 0xa8, 0x60, 0xa8, 0x78, 0xa8, 0x90, 0xa8, 0xa8, 0xa8, 0x78, 0xaa, + 0x90, 0xaa, 0xd0, 0xaa, 0xe8, 0xaa, 0x00, 0xab, 0x18, 0xab, 0x58, 0xab, + 0xd8, 0xab, 0xf0, 0xab, 0x08, 0xac, 0x20, 0xac, 0x38, 0xac, 0x50, 0xac, + 0x68, 0xac, 0x80, 0xac, 0x98, 0xac, 0xb0, 0xac, 0xc8, 0xac, 0xe0, 0xac, + 0xf8, 0xac, 0x78, 0xaf, 0x90, 0xaf, 0xa8, 0xaf, 0x00, 0x40, 0x08, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x18, 0xa0, 0x40, 0xa1, 0xa0, 0xa1, 0xf8, 0xa1, + 0x10, 0xa2, 0x28, 0xa2, 0x40, 0xa2, 0x58, 0xa2, 0x70, 0xa2, 0x88, 0xa2, + 0x00, 0x50, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xe0, 0xa1, 0x00, 0x00, + 0x00, 0x60, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0xa7, 0x10, 0xa7, + 0x18, 0xa7, 0x20, 0xa7, 0x28, 0xa7, 0x30, 0xa7, 0x00, 0x00, 0x1f, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x80, 0xa7, 0x88, 0xa7, 0x90, 0xa7, 0x98, 0xa7, + 0x00, 0x10, 0x1f, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xe0, 0xa2, 0x00, 0x00, + 0x00, 0x30, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x28, 0xad, 0x00, 0x00, + 0x00, 0xc0, 0x70, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x00, 0x00, ]; diff --git a/scripts/qemu-e2e.sh b/scripts/qemu-e2e.sh index b123d1b4..ebf3abda 100755 --- a/scripts/qemu-e2e.sh +++ b/scripts/qemu-e2e.sh @@ -155,7 +155,7 @@ run_qemu() { local verbose="$6" local serial_log="$7" - local qemu_args=( + local qemu_args=(-smp 4 -m 512M -bios "$ovmf" -drive "format=raw,file=$esp_img" diff --git a/wait_test.rs b/wait_test.rs new file mode 100644 index 00000000..d43c694e --- /dev/null +++ b/wait_test.rs @@ -0,0 +1,3 @@ +pub fn main() { + let freq = 0; // or something +}