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(()) +}