use std::env; use std::fs; use std::path::PathBuf; #[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; #[cfg(target_os = "redox")] use linux_kpi::firmware::{release_firmware, request_firmware, Firmware}; #[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, 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 mut args = env::args().skip(1); let firmware_root = env::var_os("REDBEAR_IWLWIFI_FIRMWARE_ROOT") .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from("/lib/firmware")); match args.next().as_deref() { Some("--probe") => match detect_candidates(&firmware_root) { Ok(candidates) => print_candidates(&candidates), Err(err) => { eprintln!("redbear-iwlwifi: probe failed: {err}"); std::process::exit(1); } }, Some("--status") => { let target = args.next(); run_device_action(&firmware_root, target, status_candidate, "status") } Some("--prepare") => { let target = args.next(); run_device_action(&firmware_root, target, prepare_candidate, "prepare") } Some("--transport-probe") => { let target = args.next(); run_device_action( &firmware_root, target, transport_probe_candidate, "transport-probe", ) } Some("--init-transport") => { let target = args.next(); run_device_action( &firmware_root, target, init_transport_candidate, "init-transport", ) } Some("--activate-nic") => { let target = args.next(); run_device_action(&firmware_root, target, activate_candidate, "activate-nic") } Some("--scan") => { let target = args.next(); run_device_action(&firmware_root, target, scan_candidate, "scan") } Some("--connect") => { let target = args.next(); let ssid = args.next().unwrap_or_default(); let security = args.next().unwrap_or_else(|| "open".to_string()); let key = args.next(); run_connect_action(&firmware_root, target, &ssid, &security, key.as_deref()) } Some("--disconnect") => { let target = args.next(); run_device_action(&firmware_root, target, disconnect_candidate, "disconnect") } Some("--full-init") => { let target = args.next(); run_device_action(&firmware_root, target, full_init_candidate, "full-init") } Some("--irq-test") => { let target = args.next(); run_device_action(&firmware_root, target, irq_test_candidate, "irq-test") } Some("--dma-test") => { let target = args.next(); run_device_action(&firmware_root, target, dma_test_candidate, "dma-test") } Some("--retry") => { let target = args.next(); run_device_action(&firmware_root, target, retry_candidate, "retry") } _ => { eprintln!( "redbear-iwlwifi: use --probe, --status , --prepare , --transport-probe , --init-transport , --activate-nic , --scan , --connect [key], --disconnect , --full-init , --irq-test , --dma-test , or --retry " ); std::process::exit(1); } } } fn run_connect_action( firmware_root: &PathBuf, target: Option, ssid: &str, security: &str, key: Option<&str>, ) { match detect_candidates(firmware_root) { Ok(candidates) => { let candidate = match select_candidate(candidates, target.as_deref()) { Ok(candidate) => candidate, Err(err) => { eprintln!("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 { println!("{line}"); } } Err(err) => { eprintln!("redbear-iwlwifi: connect failed: {err}"); std::process::exit(1); } } } Err(err) => { eprintln!("redbear-iwlwifi: connect probe failed: {err}"); std::process::exit(1); } } } fn print_candidates(candidates: &[Candidate]) { println!("candidates={}", candidates.len()); for candidate in candidates { println!( "device={} family={} ucode_selected={} pnvm={} ucode_candidates={}", candidate.location, candidate.family, candidate .selected_ucode .clone() .unwrap_or_else(|| "missing".to_string()), candidate .pnvm_found .clone() .or_else(|| candidate.pnvm_candidate.clone()) .unwrap_or_else(|| "none".to_string()), candidate.ucode_candidates.join(",") ); } } fn run_device_action( firmware_root: &PathBuf, target: Option, action: fn(&Candidate, &PathBuf) -> Result, DriverError>, action_name: &str, ) { match detect_candidates(firmware_root) { Ok(candidates) => { let candidate = match select_candidate(candidates, target.as_deref()) { Ok(candidate) => candidate, Err(err) => { eprintln!("redbear-iwlwifi: {action_name} selection failed: {err}"); std::process::exit(1); } }; match action(&candidate, firmware_root) { Ok(lines) => { for line in lines { println!("{line}"); } } Err(err) => { eprintln!("redbear-iwlwifi: {action_name} failed: {err}"); std::process::exit(1); } } } Err(err) => { eprintln!("redbear-iwlwifi: {action_name} probe failed: {err}"); std::process::exit(1); } } } fn select_candidate( candidates: Vec, target: Option<&str>, ) -> Result { if let Some(target) = target { candidates .into_iter() .find(|candidate| candidate.location.to_string() == target) .ok_or_else(|| { DriverError::Unsupported(format!("no Intel Wi-Fi candidate matches {target}")) }) } else { candidates.into_iter().next().ok_or_else(|| { DriverError::Unsupported("no supported Intel Wi-Fi candidates detected".to_string()) }) } } 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); let selected_ucode = ucode_candidates .iter() .find(|candidate| firmware_root.join(candidate).exists()) .cloned(); let pnvm_found = pnvm_candidate .as_ref() .filter(|candidate| firmware_root.join(candidate).exists()) .cloned(); out.push(Candidate { location, config_path, device_id, subsystem_id, family, ucode_candidates, selected_ucode, 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-92.ucode", "iwlwifi-bz-b0-gf-a0-94.ucode", "iwlwifi-bz-b0-gf-a0-100.ucode", ], Some("iwlwifi-bz-b0-gf-a0.pnvm"), ), (0x7740, _) => ( vec![ "iwlwifi-bz-b0-fm-c0-92.ucode", "iwlwifi-bz-b0-fm-c0-94.ucode", "iwlwifi-bz-b0-fm-c0-100.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 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(); Ok(vec![ format!("device={}", candidate.location), format!("family={}", candidate.family), parsed, ]) } #[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) } #[cfg(test)] mod tests { use super::*; use std::time::{SystemTime, UNIX_EPOCH}; fn temp_root(prefix: &str) -> PathBuf { let stamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); let path = std::env::temp_dir().join(format!("{prefix}-{stamp}")); fs::create_dir_all(&path).unwrap(); path } #[test] fn detects_intel_candidate() { 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(); let mut cfg = vec![0u8; 48]; cfg[0x00] = 0x86; cfg[0x01] = 0x80; cfg[0x02] = 0x40; cfg[0x03] = 0x77; cfg[0x0A] = 0x80; 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(); unsafe { env::set_var("REDBEAR_IWLWIFI_PCI_ROOT", &pci); } let candidates = detect_candidates(&fw).unwrap(); assert_eq!(candidates.len(), 1); assert_eq!(candidates[0].family, "intel-bz-arrow-lake"); assert!(candidates[0] .ucode_candidates .iter() .any(|name| name.contains("iwlwifi-bz-b0-gf-a0-92.ucode"))); } #[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(); let candidate = Candidate { location: PciLocation { segment: 0, bus: 0, device: 0x14, function: 3, }, config_path: PathBuf::from("/tmp/config"), device_id: 0x7740, subsystem_id: 0x4090, family: "intel-bz-arrow-lake", ucode_candidates: vec!["iwlwifi-bz-b0-gf-a0-92.ucode".to_string()], selected_ucode: Some("iwlwifi-bz-b0-gf-a0-92.ucode".to_string()), pnvm_candidate: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; let lines = prepare_candidate(&candidate, &fw).unwrap(); assert!(lines.iter().any(|line| line == "status=firmware-ready")); assert!(lines .iter() .any(|line| line.contains("selected_pnvm=iwlwifi-bz-b0-gf-a0.pnvm"))); assert!(lines .iter() .any(|line| line.contains("selected_ucode=iwlwifi-bz-b0-gf-a0-92.ucode"))); } #[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(); let candidate = Candidate { location: PciLocation { segment: 0, bus: 0, device: 0x14, function: 3, }, config_path: PathBuf::from("/tmp/config"), device_id: 0x7740, subsystem_id: 0x4090, family: "intel-bz-arrow-lake", ucode_candidates: vec!["iwlwifi-bz-b0-gf-a0-92.ucode".to_string()], selected_ucode: Some("iwlwifi-bz-b0-gf-a0-92.ucode".to_string()), pnvm_candidate: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; let lines = init_transport_candidate(&candidate, &fw).unwrap(); assert!(lines.iter().any(|line| line == "status=transport-ready")); assert!(lines .iter() .any(|line| line.contains("bar0_addr=host-skipped"))); } #[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(); let candidate = Candidate { location: PciLocation { segment: 0, bus: 0, device: 0x14, function: 3, }, config_path: PathBuf::from("/tmp/config"), device_id: 0x7740, subsystem_id: 0x4090, family: "intel-bz-arrow-lake", ucode_candidates: vec!["iwlwifi-bz-b0-gf-a0-92.ucode".to_string()], selected_ucode: Some("iwlwifi-bz-b0-gf-a0-92.ucode".to_string()), pnvm_candidate: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; let lines = activate_candidate(&candidate, &fw).unwrap(); assert!(lines.iter().any(|line| line == "status=nic-activated")); assert!(lines .iter() .any(|line| line.contains("activation=host-skipped"))); } #[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(); let candidate = Candidate { location: PciLocation { segment: 0, bus: 0, device: 0x14, function: 3, }, config_path: PathBuf::from("/tmp/config"), device_id: 0x7740, subsystem_id: 0x4090, family: "intel-bz-arrow-lake", ucode_candidates: vec!["iwlwifi-bz-b0-gf-a0-92.ucode".to_string()], selected_ucode: Some("iwlwifi-bz-b0-gf-a0-92.ucode".to_string()), pnvm_candidate: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; let lines = scan_candidate(&candidate, &fw).unwrap(); assert!(lines.iter().any(|line| line == "status=scanning")); assert!(lines.iter().any(|line| line.contains("scan_result="))); } #[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(); let candidate = Candidate { location: PciLocation { segment: 0, bus: 0, device: 0x14, function: 3, }, config_path: PathBuf::from("/tmp/config"), device_id: 0x7740, subsystem_id: 0x4090, family: "intel-bz-arrow-lake", ucode_candidates: vec!["iwlwifi-bz-b0-gf-a0-92.ucode".to_string()], selected_ucode: Some("iwlwifi-bz-b0-gf-a0-92.ucode".to_string()), pnvm_candidate: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; 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="))); } #[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(); let candidate = Candidate { location: PciLocation { segment: 0, bus: 0, device: 0x14, function: 3, }, config_path: PathBuf::from("/tmp/config"), device_id: 0x7740, subsystem_id: 0x4090, family: "intel-bz-arrow-lake", ucode_candidates: vec!["iwlwifi-bz-b0-gf-a0-92.ucode".to_string()], selected_ucode: Some("iwlwifi-bz-b0-gf-a0-92.ucode".to_string()), pnvm_candidate: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), pnvm_found: Some("iwlwifi-bz-b0-gf-a0.pnvm".to_string()), }; let lines = retry_candidate(&candidate, &fw).unwrap(); assert!(lines.iter().any(|line| line == "status=device-detected")); assert!(lines.iter().any(|line| line == "link_state=link=retrying")); } }