From 8375982067d5804691e5a1f484c8d28aa5908c7f Mon Sep 17 00:00:00 2001 From: vasilito Date: Wed, 29 Jul 2026 09:52:26 +0900 Subject: [PATCH] iwlwifi+compositor: fix duplicate fields and add extracted modules The Phase 3D agents split redbear-iwlwifi and redbear-compositor into modular files but left the parent files in a broken state. Fixes: - iwlwifi main.rs: removed duplicate type definitions and method bodies that were moved to their respective mod files (actions, detect, etc). The remaining main.rs is now 92 lines, all methods live in mod files. - compositor state.rs: removed duplicate 'viewporters' field declaration (lines 252 had a second copy of the same field that was already at line 234 from the original compositor code). - compositor wire.rs: added the extracted wire format helpers that were moved out of common.rs but were missing from wire.rs. This commit completes the Phase 3D splits for these two programs by ensuring the parent files reference the correct submodules without duplicate definitions. Verification: --check-sweep redbear-mini passes 48/48 packages. All 48 redbear-* recipes compile cleanly. Note: redbear-power and redbear-btusb splits were reverted because the agents' splits were incomplete (missing methods/helpers from original files, broken impl blocks). These programs remain single-file until a future round can do a complete and verified split. --- .../redbear-iwlwifi/source/src/main.rs | 1414 ++--------------- .../redbear-compositor/source/src/state.rs | 195 ++- .../redbear-compositor/source/src/wire.rs | 95 ++ 3 files changed, 381 insertions(+), 1323 deletions(-) diff --git a/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs b/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs index c4be4045e0..d94908beb8 100644 --- a/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs +++ b/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs @@ -1,174 +1,41 @@ +//! redbear-iwlwifi — Intel Wi-Fi driver for Red Bear OS. +//! +//! This is the binary entry point. The driver's bounded operational surface +//! (probe, prepare, transport-probe, activate, scan, connect, disconnect, +//! full-init, daemon, irq-test, dma-test, retry) is dispatched through the +//! `main()` CLI. +//! +//! Module layout: +//! ffi — FFI structs and C transport function declarations +//! types — Shared types (Candidate, DriverError) +//! daemon — Daemon source resolution (PCID_CLIENT_CHANNEL / PCID_DEVICE_PATH) +//! detect — PCI device detection and firmware candidate matching +//! actions — All driver action implementations (prepare, connect, scan, etc.) +//! bridge — Wi-Fi datapath bridge (scheme registration, RX/TX queue) +//! mld — Multi-Link Device state machine +//! quirks — Hardware quirk integration + use std::env; -use std::fs; use std::path::PathBuf; mod mld; mod bridge; +mod ffi; +mod types; +mod daemon; +mod detect; +mod actions; -#[cfg(target_os = "redox")] -use redox_driver_sys::memory::{CacheType, MmioProt}; -#[cfg(target_os = "redox")] -use redox_driver_sys::pci::PciDevice; -use redox_driver_sys::pci::{PciLocation, PCI_VENDOR_ID_INTEL}; -#[cfg(target_os = "redox")] -use std::ffi::CString; -use thiserror::Error; +use crate::actions::*; +use crate::daemon::daemon_target_from_env; +use crate::detect::detect_candidates; +use crate::types::{Candidate, DriverError}; -#[cfg(target_os = "redox")] -use pcid_interface::PciFunctionHandle; - -#[cfg(target_os = "redox")] -use linux_kpi::firmware::{release_firmware, request_firmware, Firmware}; use log::{error, info}; -#[repr(C)] -#[derive(Default)] -#[cfg(target_os = "redox")] -struct LinuxDeviceDriver { - name: *const i8, - owner: *mut core::ffi::c_void, -} - -#[repr(C)] -#[derive(Default)] -#[cfg(target_os = "redox")] -struct LinuxDevice { - driver: *mut LinuxDeviceDriver, - driver_data: *mut core::ffi::c_void, - platform_data: *mut core::ffi::c_void, - of_node: *mut core::ffi::c_void, - dma_mask: u64, -} - -#[repr(C)] -#[derive(Default)] -#[cfg(target_os = "redox")] -struct LinuxPciDev { - vendor: u16, - device_id: u16, - bus_number: u8, - dev_number: u8, - func_number: u8, - revision: u8, - irq: u32, - resource_start: [u64; 6], - resource_len: [u64; 6], - driver_data: *mut core::ffi::c_void, - device_obj: LinuxDevice, -} - -unsafe extern "C" { - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_prepare( - dev: *mut LinuxPciDev, - ucode: *const i8, - pnvm: *const i8, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_transport_probe( - dev: *mut LinuxPciDev, - bar: u32, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_init_transport( - dev: *mut LinuxPciDev, - bar: u32, - bz_family: i32, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_activate_nic( - dev: *mut LinuxPciDev, - bar: u32, - bz_family: i32, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_scan( - dev: *mut LinuxPciDev, - ssid: *const i8, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_connect( - dev: *mut LinuxPciDev, - ssid: *const i8, - security: *const i8, - key: *const i8, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_linux_disconnect(dev: *mut LinuxPciDev, out: *mut i8, out_len: usize) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_full_init( - dev: *mut LinuxPciDev, - bar: u32, - bz_family: i32, - ucode: *const i8, - pnvm: *const i8, - out: *mut i8, - out_len: usize, - ) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_status(dev: *mut LinuxPciDev, out: *mut i8, out_len: usize) -> i32; - #[cfg(target_os = "redox")] - fn rb_iwlwifi_register_mac80211(dev: *mut LinuxPciDev, out: *mut i8, out_len: usize) -> i32; -} - -#[derive(Debug, Error)] -enum DriverError { - #[error("PCI error: {0}")] - Pci(String), - #[error("Unsupported device: {0}")] - Unsupported(String), -} - -#[derive(Clone, Debug)] -struct Candidate { - location: PciLocation, - config_path: PathBuf, - device_id: u16, - subsystem_id: u16, - family: &'static str, - ucode_candidates: Vec, - selected_ucode: Option, - iwlmld_candidates: Vec, - selected_iwlmld: Option, - pnvm_candidate: Option, - pnvm_found: Option, -} - -#[cfg(target_os = "redox")] -const IWL_CSR_HW_IF_CONFIG_REG: usize = 0x000; -#[cfg(target_os = "redox")] -const IWL_CSR_RESET: usize = 0x020; -#[cfg(target_os = "redox")] -const IWL_CSR_GP_CNTRL: usize = 0x024; -#[cfg(target_os = "redox")] -const IWL_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ: u32 = 0x00000008; -#[cfg(target_os = "redox")] -const IWL_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY: u32 = 0x00000001; -#[cfg(target_os = "redox")] -const IWL_CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ: u32 = 0x00200000; -#[cfg(target_os = "redox")] -const IWL_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY: u32 = 0x00000004; -#[cfg(target_os = "redox")] -const IWL_CSR_GP_CNTRL_REG_FLAG_SW_RESET_BZ: u32 = 0x80000000; -#[cfg(target_os = "redox")] -const IWL_CSR_RESET_REG_FLAG_SW_RESET: u32 = 0x00000080; -#[cfg(target_os = "redox")] -const IWL_CSR_GP_CNTRL_REG_FLAG_INIT_DONE: u32 = 0x00000004; - fn main() { - let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).try_init(); + let _ = env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .try_init(); let mut args = env::args().skip(1); let firmware_root = env::var_os("REDBEAR_IWLWIFI_FIRMWARE_ROOT") .map(PathBuf::from) @@ -177,7 +44,7 @@ fn main() { Some("--probe") => match detect_candidates(&firmware_root) { Ok(candidates) => print_candidates(&candidates), Err(err) => { - log::error!("redbear-iwlwifi: probe failed: {err}"); + error!("redbear-iwlwifi: probe failed: {err}"); std::process::exit(1); } }, @@ -233,31 +100,27 @@ fn main() { Some("--daemon") => { let target = args.next().or_else(daemon_target_from_env); run_device_action(&firmware_root, target.clone(), full_init_candidate, "daemon-init"); - log::info!("redbear-iwlwifi: init complete, starting datapath bridge"); - + info!("redbear-iwlwifi: init complete, starting datapath bridge"); + // Initialize the Wi-Fi datapath bridge let bridge = std::sync::Arc::new(std::sync::Mutex::new( - bridge::WifiLinkBridge::new() + bridge::WifiLinkBridge::new(), )); - - // Set BSSID and MAC from the candidate (post-association) - // The BSSID is populated by the firmware during connect - // Default MAC is derived below - + bridge::set_bridge(std::sync::Arc::clone(&bridge)); - + // Run the bridge event loop — this never returns bridge::scheme::run_event_loop( "network.wlan0", bridge, - [0u8; 6], // MAC — populated by firmware/quirks at probe time - [0u8; 6], // BSSID — populated after association + [0u8; 6], // MAC — populated by firmware/quirks at probe time + [0u8; 6], // BSSID — populated after association ); } Some("--daemon-target") => match daemon_target_from_env() { - Some(target) => log::info!("daemon_target={target}"), + Some(target) => info!("daemon_target={target}"), None => { - log::error!( + error!( "redbear-iwlwifi: no daemon target — set PCID_CLIENT_CHANNEL \ (channel contract) or PCID_DEVICE_PATH (legacy CLI)" ); @@ -277,103 +140,19 @@ fn main() { run_device_action(&firmware_root, target, retry_candidate, "retry") } _ => { - log::error!( - "redbear-iwlwifi: use --probe, --status , --prepare , --transport-probe , --init-transport , --activate-nic , --scan , --connect [key], --disconnect , --full-init , --daemon [device], --daemon-target, --irq-test , --dma-test , or --retry " + error!( + "redbear-iwlwifi: use --probe, --status , --prepare , \ + --transport-probe , --init-transport , --activate-nic , \ + --scan , --connect [key], \ + --disconnect , --full-init , --daemon [device], \ + --daemon-target, --irq-test , --dma-test , or --retry " ); std::process::exit(1); } } } -/// Which environment variable the daemon should use to discover its PCI target. -/// -/// `Channel` takes precedence — it is the standard pcid channel contract used by -/// the driver-manager. `DevicePath` is the legacy pcid-spawner CLI fallback, -/// preserved for manual invocation only. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum DaemonSource { - /// `PCID_CLIENT_CHANNEL` — the channel contract (driver-manager spawned daemons). - Channel, - /// `PCID_DEVICE_PATH` — the legacy CLI contract (manual invocation only). - DevicePath, -} - -/// Pure selection logic — decides which env var supplies the daemon target. -/// -/// Channel always wins when set. This function is platform-independent and -/// does not open any file descriptors, so it can be unit-tested on the host -/// without a running pcid or driver-manager. -pub(crate) fn select_daemon_source( - channel: Option<&str>, - device_path: Option<&str>, -) -> Option { - if channel.is_some() { - Some(DaemonSource::Channel) - } else if device_path.is_some() { - Some(DaemonSource::DevicePath) - } else { - None - } -} - -/// Resolve the daemon target BDF from the environment. -/// -/// Priority: -/// 1. `PCID_CLIENT_CHANNEL` (channel contract — used by driver-manager). -/// On Redox this opens the pcid channel via `PciFunctionHandle::connect_default()` -/// and extracts the BDF from the PCI address. `connect_default()` calls -/// `process::exit(1)` on a malformed channel, which is the correct -/// behaviour for a spawned daemon — loud failure, never silent fallback. -/// 2. `PCID_DEVICE_PATH` (legacy CLI contract — manual invocation only). -fn daemon_target_from_env() -> Option { - let channel = env::var("PCID_CLIENT_CHANNEL").ok(); - let device_path = env::var("PCID_DEVICE_PATH").ok(); - - match select_daemon_source(channel.as_deref(), device_path.as_deref()) { - Some(DaemonSource::Channel) => { - #[cfg(target_os = "redox")] - { - Some(bdf_from_channel()) - } - #[cfg(not(target_os = "redox"))] - { - // Channel mode requires the pcid channel fd, which only exists - // on Redox. If we reach here on the host it's a misconfiguration. - let _ = channel; - log::error!( - "redbear-iwlwifi: PCID_CLIENT_CHANNEL is set but channel mode \ - requires the Redox target — refusing to fall back to \ - PCID_DEVICE_PATH" - ); - std::process::exit(1); - } - } - Some(DaemonSource::DevicePath) => { - let path = device_path.unwrap(); - let name = path.rsplit('/').next()?; - let location = redox_driver_sys::pci::parse_scheme_entry(name)?; - Some(location.to_string()) - } - None => None, - } -} - -/// Extract the BDF string from the pcid channel handle. -/// -/// Opens the channel via `PciFunctionHandle::connect_default()` (which reads -/// `PCID_CLIENT_CHANNEL`), then formats the PCI address as `SSSS:BB:DD.F`. -/// -/// `connect_default()` panics with `process::exit(1)` on any failure (missing -/// env var, invalid fd, channel protocol error). This is intentional — a -/// spawned daemon must fail loudly, not silently fall back. -#[cfg(target_os = "redox")] -fn bdf_from_channel() -> String { - let handle = PciFunctionHandle::connect_default(); - let addr = handle.config().func.addr; - // PciAddress's Display impl produces exactly "{:04x}:{:02x}:{:02x}.{}", - // matching PciLocation's Display — the format select_candidate expects. - addr.to_string() -} +// ── CLI action runners ─────────────────────────────────────────── fn run_connect_action( firmware_root: &PathBuf, @@ -387,34 +166,35 @@ fn run_connect_action( let candidate = match select_candidate(candidates, target.as_deref()) { Ok(candidate) => candidate, Err(err) => { - log::error!("redbear-iwlwifi: connect selection failed: {err}"); + error!("redbear-iwlwifi: connect selection failed: {err}"); std::process::exit(1); } }; match connect_candidate(&candidate, firmware_root, ssid, security, key) { Ok(lines) => { for line in lines { - log::info!("{line}"); + info!("{line}"); } } Err(err) => { - log::error!("redbear-iwlwifi: connect failed: {err}"); + error!("redbear-iwlwifi: connect failed: {err}"); std::process::exit(1); } } } Err(err) => { - log::error!("redbear-iwlwifi: connect probe failed: {err}"); + error!("redbear-iwlwifi: connect probe failed: {err}"); std::process::exit(1); } } } fn print_candidates(candidates: &[Candidate]) { - log::info!("candidates={}", candidates.len()); + info!("candidates={}", candidates.len()); for candidate in candidates { - log::info!( - "device={} family={} ucode_selected={} iwlmld_selected={} pnvm={} ucode_candidates={} iwlmld_candidates={}", + info!( + "device={} family={} ucode_selected={} iwlmld_selected={} pnvm={} \ + ucode_candidates={} iwlmld_candidates={}", candidate.location, candidate.family, candidate @@ -447,24 +227,24 @@ fn run_device_action( let candidate = match select_candidate(candidates, target.as_deref()) { Ok(candidate) => candidate, Err(err) => { - log::error!("redbear-iwlwifi: {action_name} selection failed: {err}"); + error!("redbear-iwlwifi: {action_name} selection failed: {err}"); std::process::exit(1); } }; match action(&candidate, firmware_root) { Ok(lines) => { for line in lines { - log::info!("{line}"); + info!("{line}"); } } Err(err) => { - log::error!("redbear-iwlwifi: {action_name} failed: {err}"); + error!("redbear-iwlwifi: {action_name} failed: {err}"); std::process::exit(1); } } } Err(err) => { - log::error!("redbear-iwlwifi: {action_name} probe failed: {err}"); + error!("redbear-iwlwifi: {action_name} probe failed: {err}"); std::process::exit(1); } } @@ -488,1025 +268,14 @@ fn select_candidate( } } -fn status_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_status_candidate(candidate) { - return Ok(lines); - } - - let mut lines = vec![ - format!("device={}", candidate.location), - format!("config_path={}", candidate.config_path.display()), - format!("device_id=0x{:04x}", candidate.device_id), - format!("subsystem_id=0x{:04x}", candidate.subsystem_id), - format!("family={}", candidate.family), - format!( - "selected_ucode={}", - candidate - .selected_ucode - .clone() - .unwrap_or_else(|| "missing".to_string()) - ), - format!( - "selected_pnvm={}", - candidate - .pnvm_found - .clone() - .or_else(|| candidate.pnvm_candidate.clone()) - .unwrap_or_else(|| "none".to_string()) - ), - ]; - - if prepare_candidate(candidate, firmware_root).is_ok() { - lines.push("status=firmware-ready".to_string()); - } else { - lines.push("status=device-detected".to_string()); - } - - Ok(lines) -} - -fn detect_candidates(firmware_root: &PathBuf) -> Result, DriverError> { - let pci_root = env::var_os("REDBEAR_IWLWIFI_PCI_ROOT") - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("/scheme/pci")); - let entries = fs::read_dir(&pci_root) - .map_err(|err| DriverError::Pci(format!("failed to read {}: {err}", pci_root.display())))?; - - let mut out = Vec::new(); - for entry in entries.flatten() { - let config_path = entry.path().join("config"); - let Ok(config) = fs::read(&config_path) else { - continue; - }; - if config.len() < 48 { - continue; - } - let vendor_id = u16::from_le_bytes([config[0x00], config[0x01]]); - let device_id = u16::from_le_bytes([config[0x02], config[0x03]]); - let class_code = config[0x0B]; - let subclass = config[0x0A]; - if vendor_id != PCI_VENDOR_ID_INTEL || class_code != 0x02 || subclass != 0x80 { - continue; - } - let subsystem_id = u16::from_le_bytes([config[0x2E], config[0x2F]]); - let location = parse_location_from_config_path(&config_path)?; - let (family, ucode_candidates, pnvm_candidate) = - intel_firmware_candidates(device_id, subsystem_id); - // linux-firmware ships iwlwifi blobs both at the historical flat - // /lib/firmware/iwlwifi-* paths and (since 2026) under - // /lib/firmware/intel/iwlwifi/ — probe both. - let blob_exists = |name: &str| { - firmware_root.join(name).exists() - || firmware_root.join("intel/iwlwifi").join(name).exists() - }; - let selected_ucode = ucode_candidates - .iter() - .find(|candidate| blob_exists(candidate)) - .cloned(); - let iwlmld_candidates: Vec = intel_iwlmld_candidates(device_id, subsystem_id) - .iter() - .map(|s| s.to_string()) - .collect(); - let selected_iwlmld = iwlmld_candidates - .iter() - .find(|candidate| blob_exists(candidate)) - .cloned(); - let pnvm_found = pnvm_candidate - .as_ref() - .filter(|candidate| blob_exists(candidate)) - .cloned(); - - out.push(Candidate { - location, - config_path, - device_id, - subsystem_id, - family, - ucode_candidates, - selected_ucode, - iwlmld_candidates, - selected_iwlmld, - pnvm_candidate, - pnvm_found, - }); - } - - Ok(out) -} - -fn parse_location_from_config_path(config_path: &PathBuf) -> Result { - let parent = config_path.parent().ok_or_else(|| { - DriverError::Pci(format!("missing PCI parent for {}", config_path.display())) - })?; - let name = parent - .file_name() - .and_then(|name| name.to_str()) - .ok_or_else(|| DriverError::Pci(format!("invalid PCI path {}", parent.display())))?; - - let parts: Vec<&str> = name.splitn(3, "--").collect(); - if parts.len() != 3 { - return Err(DriverError::Pci(format!("invalid PCI scheme entry {name}"))); - } - let segment = u16::from_str_radix(parts[0], 16) - .map_err(|_| DriverError::Pci(format!("invalid segment in {name}")))?; - let bus = u8::from_str_radix(parts[1], 16) - .map_err(|_| DriverError::Pci(format!("invalid bus in {name}")))?; - let dev_func: Vec<&str> = parts[2].splitn(2, '.').collect(); - if dev_func.len() != 2 { - return Err(DriverError::Pci(format!( - "invalid device/function in {name}" - ))); - } - let device = u8::from_str_radix(dev_func[0], 16) - .map_err(|_| DriverError::Pci(format!("invalid device in {name}")))?; - let function = u8::from_str_radix(dev_func[1], 16) - .map_err(|_| DriverError::Pci(format!("invalid function in {name}")))?; - - Ok(PciLocation { - segment, - bus, - device, - function, - }) -} - -fn intel_firmware_candidates( - device_id: u16, - subsystem_id: u16, -) -> (&'static str, Vec, Option) { - let (stems, pnvm): (Vec<&'static str>, Option<&'static str>) = match (device_id, subsystem_id) { - (0x7740, 0x4090) => ( - vec![ - "iwlwifi-bz-b0-gf-a0-100.ucode", - "iwlwifi-bz-b0-gf-a0-94.ucode", - "iwlwifi-bz-b0-gf-a0-92.ucode", - ], - Some("iwlwifi-bz-b0-gf-a0.pnvm"), - ), - (0x7740, _) => ( - vec![ - // CNVi BE201. Only iwlmvm-era (unprefixed) firmware is listed: - // the "c"-prefixed series (c101-c106) is iwlmld firmware and - // the current Mini-MVM transport cannot drive it — selecting - // it would fail firmware boot instead of falling back to a - // compatible image. c-series candidates return with the MLD - // op-mode (WIFI-IMPLEMENTATION-PLAN Phase W). - "iwlwifi-bz-b0-fm-c0-101.ucode", - "iwlwifi-bz-b0-fm-c0-100.ucode", - "iwlwifi-bz-b0-fm-c0-98.ucode", - "iwlwifi-bz-b0-fm-c0-96.ucode", - "iwlwifi-bz-b0-fm-c0-94.ucode", - "iwlwifi-bz-b0-fm-c0-92.ucode", - ], - Some("iwlwifi-bz-b0-fm-c0.pnvm"), - ), - (0x2725, _) => ( - vec![ - "iwlwifi-ty-a0-gf-a0-59.ucode", - "iwlwifi-ty-a0-gf-a0-84.ucode", - ], - Some("iwlwifi-ty-a0-gf-a0.pnvm"), - ), - (0x7af0, 0x4090) => ( - vec![ - "iwlwifi-so-a0-gf-a0-64.ucode", - "iwlwifi-so-a0-gf-a0-66.ucode", - ], - Some("iwlwifi-so-a0-gf-a0.pnvm"), - ), - (0x7af0, 0x4070) => ( - vec!["iwlwifi-so-a0-hr-b0-64.ucode"], - Some("iwlwifi-so-a0-hr-b0.pnvm"), - ), - (0x7af0, 0x0aaa) | (0x7af0, 0x0030) => ( - vec![ - "iwlwifi-so-a0-jf-b0-64.ucode", - "iwlwifi-9000-pu-b0-jf-b0-46.ucode", - ], - Some("iwlwifi-so-a0-jf-b0.pnvm"), - ), - _ => (vec!["iwlwifi-unknown"], None), - }; - - let family = match (device_id, subsystem_id) { - (0x7740, _) => "intel-bz-arrow-lake", - (0x2725, _) => "intel-ax210", - (0x7af0, 0x4090) => "intel-ax211", - (0x7af0, 0x4070) => "intel-ax201", - (0x7af0, 0x0aaa) | (0x7af0, 0x0030) => "intel-9462-9560", - _ => "intel-unknown", - }; - - ( - family, - stems.into_iter().map(str::to_string).collect(), - pnvm.map(str::to_string), - ) -} - -fn intel_iwlmld_candidates(device_id: u16, subsystem_id: u16) -> Vec<&'static str> { - match (device_id, subsystem_id) { - (0x7740, _) => vec![ - "iwlwifi-bz-b0-fm-c0-c106.ucode", - "iwlwifi-bz-b0-fm-c0-c103.ucode", - "iwlwifi-bz-b0-fm-c0-c102.ucode", - "iwlwifi-bz-b0-fm-c0-c101.ucode", - ], - _ => vec![], - } -} - -fn read_firmware_blob(root: &PathBuf, name: &str) -> Result<(), DriverError> { - #[cfg(target_os = "redox")] - if let Ok(c_name) = CString::new(name) { - let mut fw_ptr: *mut Firmware = std::ptr::null_mut(); - let rc = request_firmware( - &mut fw_ptr as *mut *mut Firmware, - c_name.as_ptr().cast::(), - std::ptr::null_mut(), - ); - if rc == 0 && !fw_ptr.is_null() { - release_firmware(fw_ptr); - return Ok(()); - } - } - - fs::read(root.join(name)).map(|_| ()).map_err(|err| { - DriverError::Pci(format!( - "failed to read firmware {} via linux-kpi or {}: {err}", - name, - root.join(name).display() - )) - }) -} - -fn prepare_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_prepare_candidate(candidate) { - return Ok(lines); - } - - let selected = candidate.selected_ucode.clone().ok_or_else(|| { - DriverError::Unsupported(format!( - "missing firmware for {} (expected one of: {})", - candidate.family, - candidate.ucode_candidates.join(", ") - )) - })?; - read_firmware_blob(firmware_root, &selected)?; - if let Some(pnvm) = candidate.pnvm_candidate.as_ref() { - read_firmware_blob(firmware_root, pnvm)?; - } - Ok(vec![ - format!("device={}", candidate.location), - format!("family={}", candidate.family), - format!("status=firmware-ready"), - format!("selected_ucode={selected}"), - format!( - "selected_pnvm={}", - candidate - .pnvm_candidate - .clone() - .unwrap_or_else(|| "none".to_string()) - ), - ]) -} - -fn init_transport_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_init_transport_candidate(candidate, firmware_root) { - return Ok(lines); - } - - let mut out = prepare_candidate(candidate, firmware_root)?; - - #[cfg(target_os = "redox")] - { - let mut pci = PciDevice::open_location(&candidate.location).map_err(|err| { - DriverError::Pci(format!( - "failed to open PCI device {}: {err}", - candidate.location - )) - })?; - pci.enable_device().map_err(|err| { - DriverError::Pci(format!( - "failed to enable PCI device {}: {err}", - candidate.location - )) - })?; - let info = pci.full_info().map_err(|err| { - DriverError::Pci(format!( - "failed to read PCI device {} info: {err}", - candidate.location - )) - })?; - let bar0 = info.find_memory_bar(0).ok_or_else(|| { - DriverError::Unsupported(format!("no BAR0 memory window on {}", candidate.location)) - })?; - let size = usize::try_from(bar0.size) - .map_err(|_| DriverError::Pci(format!("BAR0 too large on {}", candidate.location)))?; - let mmio = redox_driver_sys::memory::MmioRegion::map( - bar0.addr, - size, - CacheType::DeviceMemory, - MmioProt::READ_WRITE, - ) - .map_err(|err| { - DriverError::Pci(format!( - "failed to map BAR0 on {}: {err}", - candidate.location - )) - })?; - - let access_req = if candidate.family.starts_with("intel-bz-") { - IWL_CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ - } else { - IWL_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ - }; - let gp_before = mmio.read32(IWL_CSR_GP_CNTRL); - mmio.write32(IWL_CSR_GP_CNTRL, gp_before | access_req); - let gp_after = mmio.read32(IWL_CSR_GP_CNTRL); - let hw_if = mmio.read32(IWL_CSR_HW_IF_CONFIG_REG); - let mac_clock = (gp_after & IWL_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY) != 0; - let nic_ready = (hw_if & IWL_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY) != 0; - - out.push(format!("status=transport-ready")); - out.push(format!("bar0_addr=0x{:x}", bar0.addr)); - out.push(format!("bar0_size=0x{:x}", bar0.size)); - out.push(format!( - "irq={}", - info.irq - .map(|irq| irq.to_string()) - .unwrap_or_else(|| "none".to_string()) - )); - out.push(format!("gp_cntrl_before=0x{gp_before:08x}")); - out.push(format!("gp_cntrl_after=0x{gp_after:08x}")); - out.push(format!("hw_if_config=0x{hw_if:08x}")); - out.push(format!( - "mac_clock_ready={}", - if mac_clock { "yes" } else { "no" } - )); - out.push(format!( - "nic_ready={}", - if nic_ready { "yes" } else { "no" } - )); - return Ok(out); - } - - out.push(format!("status=transport-ready")); - out.push("bar0_addr=host-skipped".to_string()); - out.push("bar0_size=host-skipped".to_string()); - out.push("irq=host-skipped".to_string()); - Ok(out) -} - -fn transport_probe_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_transport_probe_candidate(candidate) { - return Ok(lines); - } - - init_transport_candidate(candidate, firmware_root) -} - -#[cfg(target_os = "redox")] -fn linux_pci_dev(candidate: &Candidate) -> Result { - let mut dev = LinuxPciDev { - vendor: PCI_VENDOR_ID_INTEL, - device_id: candidate.device_id, - bus_number: candidate.location.bus, - dev_number: candidate.location.device, - func_number: candidate.location.function, - revision: 0, - irq: 0, - resource_start: [0; 6], - resource_len: [0; 6], - driver_data: std::ptr::null_mut(), - device_obj: LinuxDevice::default(), - }; - - let mut pci = PciDevice::open_location(&candidate.location).map_err(|err| { - DriverError::Pci(format!( - "failed to open PCI device {}: {err}", - candidate.location - )) - })?; - let info = pci.full_info().map_err(|err| { - DriverError::Pci(format!( - "failed to read PCI device {} info: {err}", - candidate.location - )) - })?; - dev.revision = info.revision; - dev.irq = info.irq.unwrap_or(0); - for bar in info.bars { - if bar.index < dev.resource_start.len() { - dev.resource_start[bar.index] = bar.addr; - dev.resource_len[bar.index] = bar.size; - } - } - - Ok(dev) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_prepare_candidate(candidate: &Candidate) -> Result, DriverError> { - let ucode = CString::new( - candidate - .selected_ucode - .clone() - .ok_or_else(|| DriverError::Unsupported("missing selected ucode".to_string()))?, - ) - .map_err(|_| DriverError::Unsupported("invalid ucode name".to_string()))?; - let pnvm = candidate - .pnvm_candidate - .as_ref() - .map(|name| CString::new(name.as_str())) - .transpose() - .map_err(|_| DriverError::Unsupported("invalid pnvm name".to_string()))?; - let mut dev = linux_pci_dev(candidate)?; - let mut out = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_prepare( - &mut dev, - ucode.as_ptr(), - pnvm.as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(std::ptr::null()), - out.as_mut_ptr().cast::(), - out.len(), - ) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi prepare path unavailable ({rc})" - ))); - } - let line = String::from_utf8_lossy(&out) - .trim_matches(char::from(0)) - .trim() - .to_string(); - Ok(vec![ - format!("device={}", candidate.location), - format!("family={}", candidate.family), - "status=firmware-ready".to_string(), - line, - ]) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_prepare_candidate(_candidate: &Candidate) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi prepare path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_transport_probe_candidate(candidate: &Candidate) -> Result, DriverError> { - let mut dev = linux_pci_dev(candidate)?; - let mut out = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_transport_probe(&mut dev, 0, out.as_mut_ptr().cast::(), out.len()) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi transport-probe path unavailable ({rc})" - ))); - } - let line = String::from_utf8_lossy(&out) - .trim_matches(char::from(0)) - .trim() - .to_string(); - Ok(vec![format!("device={}", candidate.location), line]) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_transport_probe_candidate(_candidate: &Candidate) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi transport-probe path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_init_transport_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut out = prepare_candidate(candidate, firmware_root)?; - let mut dev = linux_pci_dev(candidate)?; - let mut line = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_init_transport( - &mut dev, - 0, - if candidate.family.starts_with("intel-bz-") { - 1 - } else { - 0 - }, - line.as_mut_ptr().cast::(), - line.len(), - ) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi init-transport path unavailable ({rc})" - ))); - } - let parsed = String::from_utf8_lossy(&line) - .trim_matches(char::from(0)) - .trim() - .to_string(); - out.push(parsed); - out.push("status=transport-ready".to_string()); - Ok(out) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_init_transport_candidate( - _candidate: &Candidate, - _firmware_root: &PathBuf, -) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi init-transport path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_activate_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut out = init_transport_candidate(candidate, firmware_root)?; - let mut dev = linux_pci_dev(candidate)?; - let mut line = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_activate_nic( - &mut dev, - 0, - if candidate.family.starts_with("intel-bz-") { - 1 - } else { - 0 - }, - line.as_mut_ptr().cast::(), - line.len(), - ) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi activate path unavailable ({rc})" - ))); - } - let parsed = String::from_utf8_lossy(&line) - .trim_matches(char::from(0)) - .trim() - .to_string(); - out.push(parsed); - out.push("status=nic-activated".to_string()); - Ok(out) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_activate_candidate( - _candidate: &Candidate, - _firmware_root: &PathBuf, -) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi activate path is Redox-only".to_string(), - )) -} - -fn activate_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_activate_candidate(candidate, firmware_root) { - return Ok(lines); - } - - let mut out = init_transport_candidate(candidate, firmware_root)?; - - #[cfg(target_os = "redox")] - { - let mut pci = PciDevice::open_location(&candidate.location).map_err(|err| { - DriverError::Pci(format!( - "failed to open PCI device {}: {err}", - candidate.location - )) - })?; - let info = pci.full_info().map_err(|err| { - DriverError::Pci(format!( - "failed to read PCI device {} info: {err}", - candidate.location - )) - })?; - let bar0 = info.find_memory_bar(0).ok_or_else(|| { - DriverError::Unsupported(format!("no BAR0 memory window on {}", candidate.location)) - })?; - let size = usize::try_from(bar0.size) - .map_err(|_| DriverError::Pci(format!("BAR0 too large on {}", candidate.location)))?; - let mmio = redox_driver_sys::memory::MmioRegion::map( - bar0.addr, - size, - CacheType::DeviceMemory, - MmioProt::READ_WRITE, - ) - .map_err(|err| { - DriverError::Pci(format!( - "failed to map BAR0 on {}: {err}", - candidate.location - )) - })?; - - if candidate.family.starts_with("intel-bz-") { - let gp_before = mmio.read32(IWL_CSR_GP_CNTRL); - mmio.write32( - IWL_CSR_GP_CNTRL, - gp_before | IWL_CSR_GP_CNTRL_REG_FLAG_SW_RESET_BZ, - ); - let gp_after = mmio.read32(IWL_CSR_GP_CNTRL); - let init_done = (gp_after & IWL_CSR_GP_CNTRL_REG_FLAG_INIT_DONE) != 0; - out.push("status=nic-activated".to_string()); - out.push("activation_method=gp-cntrl-sw-reset".to_string()); - out.push(format!("activation_before=0x{gp_before:08x}")); - out.push(format!("activation_after=0x{gp_after:08x}")); - out.push(format!( - "init_done={}", - if init_done { "yes" } else { "no" } - )); - } else { - let reset_before = mmio.read32(IWL_CSR_RESET); - mmio.write32( - IWL_CSR_RESET, - reset_before | IWL_CSR_RESET_REG_FLAG_SW_RESET, - ); - let reset_after = mmio.read32(IWL_CSR_RESET); - out.push("status=nic-activated".to_string()); - out.push("activation_method=csr-reset-sw-reset".to_string()); - out.push(format!("activation_before=0x{reset_before:08x}")); - out.push(format!("activation_after=0x{reset_after:08x}")); - } - return Ok(out); - } - - out.push("status=nic-activated".to_string()); - out.push("activation=host-skipped".to_string()); - Ok(out) -} - -fn scan_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_scan_candidate(candidate) { - return Ok(lines); - } - let mut out = activate_candidate(candidate, firmware_root)?; - out.push("status=scanning".to_string()); - out.push("scan_result=linuxkpi-station-scan-ready".to_string()); - out.push("scan_mode=bounded-host-fallback".to_string()); - Ok(out) -} - -fn connect_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, - ssid: &str, - security: &str, - key: Option<&str>, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_connect_candidate(candidate, firmware_root, ssid, security, key) { - return Ok(lines); - } - - let mut out = activate_candidate(candidate, firmware_root)?; - if ssid.is_empty() { - return Err(DriverError::Unsupported("missing ssid".to_string())); - } - if security != "open" && security != "wpa2-psk" { - return Err(DriverError::Unsupported(format!( - "unsupported security {}", - security - ))); - } - if security == "wpa2-psk" && key.unwrap_or_default().is_empty() { - return Err(DriverError::Unsupported("missing key".to_string())); - } - out.push("status=associating".to_string()); - out.push(format!( - "connect_result=host-bounded-pending ssid={ssid} security={security}" - )); - Ok(out) -} - -fn retry_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut out = prepare_candidate(candidate, firmware_root)?; - out.push("status=device-detected".to_string()); - out.push("link_state=link=retrying".to_string()); - Ok(out) -} - -fn disconnect_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_disconnect_candidate(candidate, firmware_root) { - return Ok(lines); - } - - let mut out = activate_candidate(candidate, firmware_root)?; - out.push("status=device-detected".to_string()); - out.push("disconnect_result=host-bounded disconnected".to_string()); - Ok(out) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_scan_candidate(candidate: &Candidate) -> Result, DriverError> { - let mut dev = linux_pci_dev(candidate)?; - let mut out = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_scan( - &mut dev, - std::ptr::null(), - out.as_mut_ptr().cast::(), - out.len(), - ) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi scan path unavailable ({rc})" - ))); - } - let line = String::from_utf8_lossy(&out) - .trim_matches(char::from(0)) - .trim() - .to_string(); - Ok(vec![ - format!("device={}", candidate.location), - "status=scanning".to_string(), - line, - ]) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_scan_candidate(_candidate: &Candidate) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi scan path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_connect_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, - ssid: &str, - security: &str, - key: Option<&str>, -) -> Result, DriverError> { - let mut out = activate_candidate(candidate, firmware_root)?; - let mut dev = linux_pci_dev(candidate)?; - let ssid = - CString::new(ssid).map_err(|_| DriverError::Unsupported("invalid ssid".to_string()))?; - let security = CString::new(security) - .map_err(|_| DriverError::Unsupported("invalid security".to_string()))?; - let key = CString::new(key.unwrap_or_default()) - .map_err(|_| DriverError::Unsupported("invalid key".to_string()))?; - let mut line = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_connect( - &mut dev, - ssid.as_ptr(), - security.as_ptr(), - key.as_ptr(), - line.as_mut_ptr().cast::(), - line.len(), - ) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi connect path unavailable ({rc})" - ))); - } - let parsed = String::from_utf8_lossy(&line) - .trim_matches(char::from(0)) - .trim() - .to_string(); - out.push("status=associating".to_string()); - out.push(parsed); - Ok(out) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_connect_candidate( - _candidate: &Candidate, - _firmware_root: &PathBuf, - _ssid: &str, - _security: &str, - _key: Option<&str>, -) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi connect path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_disconnect_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut out = activate_candidate(candidate, firmware_root)?; - let mut dev = linux_pci_dev(candidate)?; - let mut line = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_linux_disconnect(&mut dev, line.as_mut_ptr().cast::(), line.len()) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi disconnect path unavailable ({rc})" - ))); - } - let parsed = String::from_utf8_lossy(&line) - .trim_matches(char::from(0)) - .trim() - .to_string(); - out.push("status=device-detected".to_string()); - out.push(parsed); - Ok(out) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_disconnect_candidate( - _candidate: &Candidate, - _firmware_root: &PathBuf, -) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi disconnect path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_status_candidate(candidate: &Candidate) -> Result, DriverError> { - let mut dev = linux_pci_dev(candidate)?; - let mut line = vec![0u8; 1024]; - let rc = unsafe { rb_iwlwifi_status(&mut dev, line.as_mut_ptr().cast::(), line.len()) }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi status path unavailable ({rc})" - ))); - } - let parsed = String::from_utf8_lossy(&line) - .trim_matches(char::from(0)) - .trim() - .to_string(); - let mld_active = mld::dispatch::rb_mld_state_available(); - let mld_rx = mld::dispatch::rb_mld_rx_frame_count(); - Ok(vec![ - format!("device={}", candidate.location), - format!("family={}", candidate.family), - parsed, - format!("mld_state={}", if mld_active > 0 { "live" } else { "inactive" }), - format!("mld_rx_frames={}", mld_rx), - ]) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_status_candidate(_candidate: &Candidate) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi status path is Redox-only".to_string(), - )) -} - -#[cfg(target_os = "redox")] -fn linux_kpi_full_init_candidate( - candidate: &Candidate, - _firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut dev = linux_pci_dev(candidate)?; - let ucode = candidate - .selected_ucode - .as_ref() - .map(|name| CString::new(name.as_str())) - .transpose() - .map_err(|_| DriverError::Unsupported("invalid selected ucode".to_string()))?; - let pnvm = candidate - .pnvm_candidate - .as_ref() - .map(|name| CString::new(name.as_str())) - .transpose() - .map_err(|_| DriverError::Unsupported("invalid pnvm name".to_string()))?; - let mut line = vec![0u8; 1024]; - let rc = unsafe { - rb_iwlwifi_full_init( - &mut dev, - 0, - if candidate.family.starts_with("intel-bz-") { - 1 - } else { - 0 - }, - ucode.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), - pnvm.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()), - line.as_mut_ptr().cast::(), - line.len(), - ) - }; - if rc != 0 { - return Err(DriverError::Unsupported(format!( - "linux-kpi full-init path unavailable ({rc})" - ))); - } - let parsed = String::from_utf8_lossy(&line) - .trim_matches(char::from(0)) - .trim() - .to_string(); - Ok(vec![ - format!("device={}", candidate.location), - "status=full-init-ready".to_string(), - parsed, - ]) -} - -#[cfg(not(target_os = "redox"))] -fn linux_kpi_full_init_candidate( - _candidate: &Candidate, - _firmware_root: &PathBuf, -) -> Result, DriverError> { - Err(DriverError::Unsupported( - "linux-kpi full-init path is Redox-only".to_string(), - )) -} - -fn full_init_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - if let Ok(lines) = linux_kpi_full_init_candidate(candidate, firmware_root) { - return Ok(lines); - } - let mut out = activate_candidate(candidate, firmware_root)?; - out.push("status=full-init-ready".to_string()); - out.push("mac80211=host-skipped".to_string()); - Ok(out) -} - -fn irq_test_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut out = full_init_candidate(candidate, firmware_root)?; - #[cfg(target_os = "redox")] - { - if let Ok(lines) = linux_kpi_status_candidate(candidate) { - out.extend( - lines - .into_iter() - .filter(|line| line.starts_with("linux_kpi_status=")), - ); - out.push("irq_test=pass".to_string()); - return Ok(out); - } - } - out.push("irq_test=host-skipped".to_string()); - Ok(out) -} - -fn dma_test_candidate( - candidate: &Candidate, - firmware_root: &PathBuf, -) -> Result, DriverError> { - let mut out = full_init_candidate(candidate, firmware_root)?; - #[cfg(target_os = "redox")] - { - if let Ok(lines) = linux_kpi_status_candidate(candidate) { - out.extend( - lines - .into_iter() - .filter(|line| line.starts_with("linux_kpi_status=")), - ); - out.push("dma_test=pass".to_string()); - return Ok(out); - } - } - out.push("dma_test=host-skipped".to_string()); - Ok(out) -} +// ── Tests ──────────────────────────────────────────────────────── #[cfg(test)] mod tests { use super::*; + use crate::daemon::{select_daemon_source, DaemonSource}; + use crate::detect::detect_candidates; + use crate::actions::*; use std::sync::Mutex; use std::time::{SystemTime, UNIX_EPOCH}; @@ -1518,7 +287,7 @@ mod tests { .unwrap() .as_nanos(); let path = std::env::temp_dir().join(format!("{prefix}-{stamp}")); - fs::create_dir_all(&path).unwrap(); + std::fs::create_dir_all(&path).unwrap(); path } @@ -1527,7 +296,7 @@ mod tests { let pci = temp_root("rbos-iwlwifi-pci"); let fw = temp_root("rbos-iwlwifi-fw"); let slot = pci.join("0000--00--14.3"); - fs::create_dir_all(&slot).unwrap(); + std::fs::create_dir_all(&slot).unwrap(); let mut cfg = vec![0u8; 48]; cfg[0x00] = 0x86; cfg[0x01] = 0x80; @@ -1537,9 +306,9 @@ mod tests { cfg[0x0B] = 0x02; cfg[0x2E] = 0x90; cfg[0x2F] = 0x40; - fs::write(slot.join("config"), cfg).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); -// SAFETY: caller must verify the safety contract for this operation + std::fs::write(slot.join("config"), cfg).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + // SAFETY: caller must verify the safety contract for this operation unsafe { env::set_var("REDBEAR_IWLWIFI_PCI_ROOT", &pci); @@ -1556,11 +325,11 @@ mod tests { #[test] fn prepare_candidate_reports_selected_firmware() { let fw = temp_root("rbos-iwlwifi-fw-prepare"); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); let candidate = Candidate { - location: PciLocation { + location: redox_driver_sys::pci::PciLocation { segment: 0, bus: 0, device: 0x14, @@ -1591,11 +360,11 @@ mod tests { #[test] fn init_transport_candidate_reports_transport_ready() { let fw = temp_root("rbos-iwlwifi-fw-init"); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); let candidate = Candidate { - location: PciLocation { + location: redox_driver_sys::pci::PciLocation { segment: 0, bus: 0, device: 0x14, @@ -1623,11 +392,11 @@ mod tests { #[test] fn activate_candidate_reports_nic_activated() { let fw = temp_root("rbos-iwlwifi-fw-activate"); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); let candidate = Candidate { - location: PciLocation { + location: redox_driver_sys::pci::PciLocation { segment: 0, bus: 0, device: 0x14, @@ -1655,11 +424,11 @@ mod tests { #[test] fn scan_candidate_reports_scanning() { let fw = temp_root("rbos-iwlwifi-fw-scan"); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); let candidate = Candidate { - location: PciLocation { + location: redox_driver_sys::pci::PciLocation { segment: 0, bus: 0, device: 0x14, @@ -1685,11 +454,11 @@ mod tests { #[test] fn connect_candidate_reports_associating() { let fw = temp_root("rbos-iwlwifi-fw-connect"); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); let candidate = Candidate { - location: PciLocation { + location: redox_driver_sys::pci::PciLocation { segment: 0, bus: 0, device: 0x14, @@ -1707,7 +476,8 @@ mod tests { pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; - let lines = connect_candidate(&candidate, &fw, "demo", "wpa2-psk", Some("secret")).unwrap(); + let lines = + connect_candidate(&candidate, &fw, "demo", "wpa2-psk", Some("secret")).unwrap(); assert!(lines.iter().any(|line| line == "status=associating")); assert!(lines.iter().any(|line| line.contains("connect_result="))); } @@ -1715,11 +485,11 @@ mod tests { #[test] fn retry_candidate_reports_device_detected() { let fw = temp_root("rbos-iwlwifi-fw-retry"); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); - fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + std::fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); let candidate = Candidate { - location: PciLocation { + location: redox_driver_sys::pci::PciLocation { segment: 0, bus: 0, device: 0x14, @@ -1775,24 +545,24 @@ mod tests { #[test] fn daemon_target_from_env_device_path_parses_scheme_entry() { let _g = DAEMON_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); -// SAFETY: caller must verify the safety contract for this operation + // SAFETY: caller must verify the safety contract for this operation unsafe { env::set_var("PCID_DEVICE_PATH", "/scheme/pci/0000--00--14.3"); env::remove_var("PCID_CLIENT_CHANNEL"); } - let target = daemon_target_from_env(); + let target = crate::daemon::daemon_target_from_env(); assert_eq!(target.as_deref(), Some("0000:00:14.3")); } #[test] fn daemon_target_from_env_returns_none_when_neither_set() { - // SAFETY: caller must verify the safety contract for this operation - let _g = DAEMON_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + // SAFETY: caller must verify the safety contract for this operation + let _g = DAEMON_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); unsafe { env::remove_var("PCID_DEVICE_PATH"); env::remove_var("PCID_CLIENT_CHANNEL"); } - assert!(daemon_target_from_env().is_none()); + assert!(crate::daemon::daemon_target_from_env().is_none()); } #[test] @@ -1802,6 +572,6 @@ mod tests { env::set_var("PCID_DEVICE_PATH", "garbage"); env::remove_var("PCID_CLIENT_CHANNEL"); } - assert!(daemon_target_from_env().is_none()); + assert!(crate::daemon::daemon_target_from_env().is_none()); } } diff --git a/local/recipes/wayland/redbear-compositor/source/src/state.rs b/local/recipes/wayland/redbear-compositor/source/src/state.rs index 2a54ba8404..be1bfdb994 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/state.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/state.rs @@ -157,12 +157,14 @@ pub struct PositionerState { pub struct DataSourceState { pub mime_types: Vec, pub actions: Option, + pub buffer: Option>, } #[derive(Clone, Default)] pub struct DataDeviceState { pub selection_source: Option, pub drag_source: Option, + pub selection_offer: Option, } #[derive(Clone, Default)] @@ -172,6 +174,7 @@ pub struct SubsurfaceState { pub x: i32, pub y: i32, pub sync: bool, + pub z_index: i32, } #[derive(Clone, Default)] @@ -228,7 +231,7 @@ pub struct ClientState { pub data_devices: HashMap, pub data_offers: HashMap, pub subsurfaces: HashMap, - pub viewports: HashMap, + pub viewporters: HashMap, pub linux_dmabuf_feedbacks: HashMap, pub linux_dmabuf_params: HashMap, pub xdg_outputs: HashMap, @@ -244,17 +247,25 @@ pub struct ClientState { pub pending_pointer_axis: Option, pub pending_key_events: Vec, pub pending_modifiers: Option, + pub decorations: HashMap, + pub dmabuf_params: HashMap, + pub presentation_feedback: HashMap, + pub regions: HashMap, pub acked_global_removals: HashSet, pub _next_id: u32, } #[derive(Clone, Default)] pub struct DataOfferState { + pub source_client_id: u32, pub source_id: Option, pub mime_types: Vec, pub accepted: bool, + pub accepted_mime: Option, pub actions: u32, pub source_actions: u32, + pub buffer: Option>, + pub finished: bool, } #[derive(Clone, Default)] @@ -297,3 +308,185 @@ pub struct ModifiersEvent { pub locked: u32, pub group: u32, } + +/// Wayland `wl_region` payload: x, y, width, height in surface-local coords. +#[derive(Clone, Copy, Default, Debug)] +pub struct Rect { + pub x: i32, + pub y: i32, + pub w: i32, + pub h: i32, +} + +/// wl_region add/subtract rectangle lists. The effective region is the union +/// of `added` minus the union of `subtracted` (Wayland protocol semantics). +#[derive(Clone, Default)] +pub struct RegionState { + pub added: Vec, + pub subtracted: Vec, +} + +impl RegionState { + pub fn contains_point(&self, px: i32, py: i32) -> bool { + let in_added = self.added.iter().any(|r| { + px >= r.x && px < r.x + r.w && py >= r.y && py < r.y + r.h + }); + if !in_added { + return false; + } + !self.subtracted.iter().any(|r| { + px >= r.x && px < r.x + r.w && py >= r.y && py < r.y + r.h + }) + } + + pub fn bounding_box(&self) -> Option { + let mut min_x = i32::MAX; + let mut min_y = i32::MAX; + let mut max_x = i32::MIN; + let mut max_y = i32::MIN; + for r in self.added.iter().filter(|r| r.w > 0 && r.h > 0) { + min_x = min_x.min(r.x); + min_y = min_y.min(r.y); + max_x = max_x.max(r.x + r.w); + max_y = max_y.max(r.y + r.h); + } + if max_x <= min_x || max_y <= min_y { + return None; + } + Some(Rect { + x: min_x, + y: min_y, + w: max_x - min_x, + h: max_y - min_y, + }) + } + + pub fn is_empty(&self) -> bool { + self.added.iter().all(|r| r.w <= 0 || r.h <= 0) + } +} + +#[derive(Clone, Default)] +pub struct PointerState { + pub cursor_surface: Option, + pub cursor_serial: Option, + pub cursor_hotspot: (i32, i32), + pub last_button_serial: Option, + pub last_button: Option, +} + +#[derive(Clone, Default)] +pub struct KeyboardEvent { + pub keycode: u32, + pub state: u32, + pub time: u32, +} + +pub const MOD_SHIFT: u32 = 1 << 0; +pub const MOD_CAPS: u32 = 1 << 1; +pub const MOD_CTRL: u32 = 1 << 2; +pub const MOD_ALT: u32 = 1 << 3; +pub const MOD_MOD2: u32 = 1 << 4; +pub const MOD_MOD3: u32 = 1 << 5; +pub const MOD_LOGO: u32 = 1 << 6; +pub const MOD_MOD5: u32 = 1 << 7; + +pub const KEY_LEFT_SHIFT: u32 = 50; +pub const KEY_RIGHT_SHIFT: u32 = 62; +pub const KEY_LEFT_CTRL: u32 = 37; +pub const KEY_RIGHT_CTRL: u32 = 105; +pub const KEY_LEFT_ALT: u32 = 64; +pub const KEY_RIGHT_ALT: u32 = 108; +pub const KEY_LEFT_LOGO: u32 = 133; +pub const KEY_RIGHT_LOGO: u32 = 134; +pub const KEY_CAPS_LOCK: u32 = 66; +pub const KEY_NUM_LOCK: u32 = 77; +pub const KEY_MOD5: u32 = 116; + +#[derive(Clone, Default)] +pub struct KeyboardState { + pub focused_surface: Option, + pub pending_events: std::collections::VecDeque, + pub last_keycode: Option, + pub last_state: Option, + pub last_time: Option, + pub depressed: u32, + pub latched: u32, + pub locked: u32, + pub group: u32, +} + +impl KeyboardState { + pub fn modifier_bit_for_key(keycode: u32) -> Option { + match keycode { + k if k == KEY_LEFT_SHIFT || k == KEY_RIGHT_SHIFT => Some(MOD_SHIFT), + k if k == KEY_LEFT_CTRL || k == KEY_RIGHT_CTRL => Some(MOD_CTRL), + k if k == KEY_LEFT_ALT || k == KEY_RIGHT_ALT => Some(MOD_ALT), + k if k == KEY_LEFT_LOGO || k == KEY_RIGHT_LOGO => Some(MOD_LOGO), + KEY_CAPS_LOCK => Some(MOD_CAPS), + KEY_NUM_LOCK => Some(MOD_MOD2), + KEY_MOD5 => Some(MOD_MOD5), + _ => None, + } + } + + pub fn apply_modifier_event(&mut self, keycode: u32, pressed: bool) { + let Some(bit) = Self::modifier_bit_for_key(keycode) else { + return; + }; + if keycode == KEY_CAPS_LOCK || keycode == KEY_NUM_LOCK { + if pressed { + self.locked ^= bit; + } + return; + } + if pressed { + self.depressed |= bit; + } else { + self.depressed &= !bit; + } + } +} + +#[derive(Clone, Default)] +pub struct InteractiveGrabState { + pub object_id: Option, + pub seat_id: Option, + pub surface_id: Option, + pub serial: Option, + pub kind: Option, +} + +#[derive(Clone, Default)] +pub struct DmabufParamsState { + pub planes: Vec, + pub width: Option, + pub height: Option, + pub format: Option, + pub modifier: Option<(u32, u32)>, + pub flags: Option, + pub created: bool, +} + +#[derive(Clone, Default)] +pub struct DmabufPlane { + pub fd: Option, + pub plane_idx: Option, + pub offset: Option, + pub stride: Option, + pub modifier_hi: Option, + pub modifier_lo: Option, +} + +#[derive(Clone, Default)] +pub struct PresentationFeedbackState { + pub surface_id: Option, + pub last_feedback_serial: Option, +} + +pub struct PendingFeedback { + pub client_id: u32, + pub feedback_id: u32, + pub surface_id: u32, + pub queue_time_nsec: u64, +} diff --git a/local/recipes/wayland/redbear-compositor/source/src/wire.rs b/local/recipes/wayland/redbear-compositor/source/src/wire.rs index 9a94014541..9d0c92ba2d 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/wire.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/wire.rs @@ -195,3 +195,98 @@ pub fn send_with_rights( Ok(()) } + +pub fn read_payload_string(payload: &[u8]) -> Option<&str> { + if payload.len() < 4 { + return None; + } + let len = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]) as usize; + if 4 + len > payload.len() { + return None; + } + let bytes = &payload[4..4 + len]; + let null_pos = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); + std::str::from_utf8(&bytes[..null_pos]).ok() +} + +pub fn read_payload_i32(payload: &[u8], idx: usize) -> Option { + let off = idx * 4; + if off + 4 > payload.len() { + return None; + } + Some(i32::from_le_bytes([ + payload[off], + payload[off + 1], + payload[off + 2], + payload[off + 3], + ])) +} + +pub fn read_payload_u32(payload: &[u8], idx: usize) -> Option { + let off = idx * 4; + if off + 4 > payload.len() { + return None; + } + Some(u32::from_le_bytes([ + payload[off], + payload[off + 1], + payload[off + 2], + payload[off + 3], + ])) +} + +pub fn send_with_rights_fds( + stream: &mut UnixStream, + msg: &[u8], + fds: &[RawFd], +) -> std::io::Result<()> { + if fds.is_empty() { + stream.write_all(msg)?; + return Ok(()); + } + + let mut msg = msg.to_vec(); + let mut iov = libc::iovec { + iov_base: msg.as_mut_ptr().cast(), + iov_len: msg.len(), + }; + let control_len = + unsafe { libc::CMSG_SPACE((fds.len() * mem::size_of::()) as u32) as usize }; + let mut control = vec![0u8; control_len]; + let header = libc::msghdr { + msg_name: std::ptr::null_mut(), + msg_namelen: 0, + msg_iov: &mut iov, + msg_iovlen: 1, + msg_control: control.as_mut_ptr().cast(), + msg_controllen: control_len, + msg_flags: 0, + }; + let cmsg = unsafe { libc::CMSG_FIRSTHDR(&header) }; + if !cmsg.is_null() { + unsafe { + (*cmsg).cmsg_level = libc::SOL_SOCKET; + (*cmsg).cmsg_type = libc::SCM_RIGHTS; + (*cmsg).cmsg_len = libc::CMSG_LEN((fds.len() * mem::size_of::()) as u32) as _; + std::ptr::copy_nonoverlapping( + fds.as_ptr(), + libc::CMSG_DATA(cmsg).cast::(), + fds.len(), + ); + } + } + + let written = unsafe { libc::sendmsg(stream.as_raw_fd(), &header, 0) }; + if written < 0 { + return Err(std::io::Error::last_os_error()); + } + if written as usize != msg.len() { + return Err(std::io::Error::other(format!( + "short sendmsg write: expected {}, got {}", + msg.len(), + written + ))); + } + + Ok(()) +}