Add Wi-Fi driver and control tools

Red Bear OS Team
This commit is contained in:
2026-04-16 12:45:07 +01:00
parent e565b6bceb
commit 54e63420ec
21 changed files with 5389 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,410 @@
mod backend;
mod scheme;
use std::env;
#[cfg(target_os = "redox")]
use std::os::fd::RawFd;
use std::path::Path;
use std::process;
use backend::{Backend, IntelBackend, NoDeviceBackend, StubBackend};
#[cfg(target_os = "redox")]
use log::info;
use log::LevelFilter;
#[cfg(target_os = "redox")]
use redox_scheme::{scheme::SchemeSync, SignalBehavior, Socket};
#[cfg(target_os = "redox")]
use scheme::WifiCtlScheme;
fn init_logging(level: LevelFilter) {
log::set_max_level(level);
}
#[cfg(target_os = "redox")]
unsafe fn get_init_notify_fd() -> RawFd {
let fd: RawFd = env::var("INIT_NOTIFY")
.expect("redbear-wifictl: INIT_NOTIFY not set")
.parse()
.expect("redbear-wifictl: INIT_NOTIFY is not a valid fd");
unsafe {
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC);
}
fd
}
#[cfg(target_os = "redox")]
fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut WifiCtlScheme) {
let cap_id = scheme
.scheme_root()
.expect("redbear-wifictl: scheme_root failed");
let cap_fd = socket
.create_this_scheme_fd(0, cap_id, 0, 0)
.expect("redbear-wifictl: create_this_scheme_fd failed");
syscall::call_wo(
notify_fd as usize,
&libredox::Fd::new(cap_fd).into_raw().to_ne_bytes(),
syscall::CallFlags::FD,
&[],
)
.expect("redbear-wifictl: failed to notify init that scheme is ready");
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BackendMode {
Intel,
NoDevice,
Stub,
}
fn iwlwifi_command_path() -> std::path::PathBuf {
env::var_os("REDBEAR_IWLWIFI_CMD")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::PathBuf::from("/usr/lib/drivers/redbear-iwlwifi"))
}
fn select_backend_mode(
explicit: Option<&str>,
intel_driver_present: bool,
intel_interfaces_present: bool,
redox_runtime: bool,
) -> BackendMode {
match explicit {
Some("intel") => BackendMode::Intel,
Some("stub") => BackendMode::Stub,
_ if redox_runtime && intel_driver_present && intel_interfaces_present => {
BackendMode::Intel
}
_ if redox_runtime && intel_driver_present => BackendMode::NoDevice,
_ => BackendMode::Stub,
}
}
fn build_backend() -> Box<dyn Backend> {
let explicit = env::var("REDBEAR_WIFICTL_BACKEND").ok();
let intel_driver_present = Path::new(&iwlwifi_command_path()).exists();
let intel_interfaces_present = if cfg!(target_os = "redox") && intel_driver_present {
!IntelBackend::from_env().interfaces().is_empty()
} else {
false
};
let mode = select_backend_mode(
explicit.as_deref(),
intel_driver_present,
intel_interfaces_present,
cfg!(target_os = "redox"),
);
match mode {
BackendMode::Intel => Box::new(IntelBackend::from_env()),
BackendMode::NoDevice => Box::new(NoDeviceBackend::new()),
BackendMode::Stub => Box::new(StubBackend::from_env()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_backend_selection_wins() {
assert_eq!(
select_backend_mode(Some("intel"), false, false, false),
BackendMode::Intel
);
assert_eq!(
select_backend_mode(Some("stub"), true, true, true),
BackendMode::Stub
);
}
#[test]
fn redox_runtime_prefers_intel_when_driver_present() {
assert_eq!(
select_backend_mode(None, true, true, true),
BackendMode::Intel
);
assert_eq!(
select_backend_mode(None, false, false, true),
BackendMode::Stub
);
}
#[test]
fn redox_runtime_uses_no_device_backend_without_detected_intel_interfaces() {
assert_eq!(
select_backend_mode(None, true, false, true),
BackendMode::NoDevice
);
}
#[test]
fn host_runtime_stays_stub_without_explicit_override() {
assert_eq!(
select_backend_mode(None, true, true, false),
BackendMode::Stub
);
assert_eq!(
select_backend_mode(None, false, false, false),
BackendMode::Stub
);
}
}
fn main() {
let log_level = match env::var("REDBEAR_WIFICTL_LOG").as_deref() {
Ok("debug") => LevelFilter::Debug,
Ok("trace") => LevelFilter::Trace,
Ok("warn") => LevelFilter::Warn,
Ok("error") => LevelFilter::Error,
_ => LevelFilter::Info,
};
init_logging(log_level);
let mut args = env::args().skip(1);
match args.next().as_deref() {
Some("--probe") => {
let backend = build_backend();
println!("interfaces={}", backend.interfaces().join(","));
println!("capabilities={}", backend.capabilities().join(","));
return;
}
Some("--prepare") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
match backend.prepare(&iface) {
Ok(status) => {
println!("interface={}", iface);
println!("status={}", status.as_str());
println!("firmware_status={}", backend.firmware_status(&iface));
println!("transport_status={}", backend.transport_status(&iface));
println!("transport_init_status=transport_init=not-run");
return;
}
Err(err) => {
eprintln!("redbear-wifictl: prepare failed for {}: {}", iface, err);
process::exit(1);
}
}
}
Some("--status") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let backend = build_backend();
println!("interface={}", iface);
println!("status={}", backend.initial_status(&iface).as_str());
println!("link_state={}", backend.initial_link_state(&iface));
println!("firmware_status={}", backend.firmware_status(&iface));
println!("transport_status={}", backend.transport_status(&iface));
println!("transport_init_status=transport_init=unknown");
println!("connect_result={}", backend.connect_result(&iface));
return;
}
Some("--scan") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
match backend.scan(&iface) {
Ok(results) => {
println!("interface={}", iface);
println!("status=scanning");
println!("firmware_status={}", backend.firmware_status(&iface));
println!("transport_status={}", backend.transport_status(&iface));
println!("scan_results={}", results.join(","));
return;
}
Err(err) => {
eprintln!("redbear-wifictl: scan failed for {}: {}", iface, err);
process::exit(1);
}
}
}
Some("--transport-probe") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
match backend.transport_probe(&iface) {
Ok(status) => {
println!("interface={}", iface);
println!("transport_status={}", status);
return;
}
Err(err) => {
eprintln!(
"redbear-wifictl: transport probe failed for {}: {}",
iface, err
);
process::exit(1);
}
}
}
Some("--init-transport") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
match backend.init_transport(&iface) {
Ok(status) => {
println!("interface={}", iface);
println!("transport_init_status={}", status);
println!("transport_status={}", backend.transport_status(&iface));
return;
}
Err(err) => {
eprintln!(
"redbear-wifictl: transport init failed for {}: {}",
iface, err
);
process::exit(1);
}
}
}
Some("--activate-nic") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
match backend.activate(&iface) {
Ok(status) => {
println!("interface={}", iface);
println!("activation_status={}", status);
println!("transport_status={}", backend.transport_status(&iface));
return;
}
Err(err) => {
eprintln!(
"redbear-wifictl: activate-nic failed for {}: {}",
iface, err
);
process::exit(1);
}
}
}
Some("--retry") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
match backend.retry(&iface) {
Ok(status) => {
println!("interface={}", iface);
println!("status={}", status.as_str());
println!("link_state=link=retrying");
return;
}
Err(err) => {
eprintln!("redbear-wifictl: retry failed for {}: {}", iface, err);
process::exit(1);
}
}
}
Some("--connect") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let ssid = args.next().unwrap_or_default();
let security = args.next().unwrap_or_else(|| "open".to_string());
let key = args.next().unwrap_or_default();
let mut backend = build_backend();
if let Err(err) = backend.prepare(&iface) {
eprintln!("redbear-wifictl: prepare failed for {}: {}", iface, err);
process::exit(1);
}
if let Err(err) = backend.init_transport(&iface) {
eprintln!(
"redbear-wifictl: transport init failed for {}: {}",
iface, err
);
process::exit(1);
}
if let Err(err) = backend.activate(&iface) {
eprintln!(
"redbear-wifictl: activate-nic failed for {}: {}",
iface, err
);
process::exit(1);
}
let state = backend::InterfaceState {
ssid,
security,
key,
..Default::default()
};
match backend.connect(&iface, &state) {
Ok(status) => {
println!("interface={}", iface);
println!("status={}", status.as_str());
println!("firmware_status={}", backend.firmware_status(&iface));
println!("transport_status={}", backend.transport_status(&iface));
println!("connect_result={}", backend.connect_result(&iface));
return;
}
Err(err) => {
eprintln!("redbear-wifictl: connect failed for {}: {}", iface, err);
process::exit(1);
}
}
}
Some("--disconnect") => {
let iface = args.next().unwrap_or_else(|| "wlan0".to_string());
let mut backend = build_backend();
if let Err(err) = backend.prepare(&iface) {
eprintln!("redbear-wifictl: prepare failed for {}: {}", iface, err);
process::exit(1);
}
if let Err(err) = backend.init_transport(&iface) {
eprintln!(
"redbear-wifictl: transport init failed for {}: {}",
iface, err
);
process::exit(1);
}
if let Err(err) = backend.activate(&iface) {
eprintln!(
"redbear-wifictl: activate-nic failed for {}: {}",
iface, err
);
process::exit(1);
}
match backend.disconnect(&iface) {
Ok(status) => {
println!("interface={}", iface);
println!("status={}", status.as_str());
println!("firmware_status={}", backend.firmware_status(&iface));
println!("transport_status={}", backend.transport_status(&iface));
println!("disconnect_result={}", backend.disconnect_result(&iface));
return;
}
Err(err) => {
eprintln!("redbear-wifictl: disconnect failed for {}: {}", iface, err);
process::exit(1);
}
}
}
_ => {}
}
#[cfg(not(target_os = "redox"))]
{
eprintln!("redbear-wifictl: daemon mode is only supported on Redox; use --probe on host");
process::exit(1);
}
#[cfg(target_os = "redox")]
{
let notify_fd = unsafe { get_init_notify_fd() };
let socket = Socket::create().expect("redbear-wifictl: failed to create scheme socket");
let mut scheme = WifiCtlScheme::new(build_backend());
let mut state = redox_scheme::scheme::SchemeState::new();
notify_scheme_ready(notify_fd, &socket, &mut scheme);
libredox::call::setrens(0, 0).expect("redbear-wifictl: failed to enter null namespace");
info!("redbear-wifictl: registered scheme:wifictl");
while let Some(request) = socket
.next_request(SignalBehavior::Restart)
.expect("redbear-wifictl: failed to read scheme request")
{
match request.kind() {
redox_scheme::RequestKind::Call(request) => {
let response = request.handle_sync(&mut scheme, &mut state);
socket
.write_response(response, SignalBehavior::Restart)
.expect("redbear-wifictl: failed to write response");
}
_ => {}
}
}
process::exit(0);
}
}
@@ -0,0 +1,743 @@
use std::collections::BTreeMap;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult};
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT, EROFS};
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE};
use syscall::schemev2::NewFdFlags;
use syscall::Stat;
use crate::backend::{Backend, InterfaceState, WifiStatus};
const SCHEME_ROOT_ID: usize = 1;
#[derive(Clone)]
enum HandleKind {
Root,
Ifaces,
Interface(String),
Capabilities,
Status(String),
LinkState(String),
FirmwareStatus(String),
TransportStatus(String),
TransportInitStatus(String),
ActivationStatus(String),
ConnectResult(String),
DisconnectResult(String),
ScanResults(String),
LastError(String),
Ssid(String),
Security(String),
Key(String),
Scan(String),
Prepare(String),
TransportProbe(String),
InitTransport(String),
ActivateNic(String),
Connect(String),
Disconnect(String),
Retry(String),
}
pub struct WifiCtlScheme {
backend: Box<dyn Backend>,
next_id: usize,
handles: BTreeMap<usize, HandleKind>,
states: BTreeMap<String, InterfaceState>,
}
impl WifiCtlScheme {
pub fn new(backend: Box<dyn Backend>) -> Self {
let mut states = BTreeMap::new();
for iface in backend.interfaces() {
states.insert(
iface.clone(),
InterfaceState {
status: backend.initial_status(&iface).as_str().to_string(),
link_state: backend.initial_link_state(&iface),
firmware_status: backend.firmware_status(&iface),
transport_status: backend.transport_status(&iface),
transport_init_status: "transport_init=not-run".to_string(),
activation_status: "activation=not-run".to_string(),
connect_result: backend.connect_result(&iface),
disconnect_result: backend.disconnect_result(&iface),
scan_results: backend.default_scan_results(&iface),
..Default::default()
},
);
}
Self {
backend,
next_id: SCHEME_ROOT_ID + 1,
handles: BTreeMap::new(),
states,
}
}
fn alloc_handle(&mut self, kind: HandleKind) -> usize {
let id = self.next_id;
self.next_id += 1;
self.handles.insert(id, kind);
id
}
fn handle(&self, id: usize) -> Result<&HandleKind> {
self.handles.get(&id).ok_or(Error::new(EBADF))
}
fn state(&self, iface: &str) -> Result<&InterfaceState> {
self.states.get(iface).ok_or(Error::new(ENOENT))
}
fn state_mut(&mut self, iface: &str) -> Result<&mut InterfaceState> {
self.states.get_mut(iface).ok_or(Error::new(ENOENT))
}
fn read_handle(&self, kind: &HandleKind) -> Result<String> {
Ok(match kind {
HandleKind::Root => "ifaces\ncapabilities\n".to_string(),
HandleKind::Ifaces => self.states.keys().cloned().collect::<Vec<_>>().join("\n") + "\n",
HandleKind::Interface(_) => {
"status\nlink-state\nfirmware-status\ntransport-status\ntransport-init-status\nactivation-status\nconnect-result\ndisconnect-result\nscan-results\nlast-error\nssid\nsecurity\nkey\nscan\nprepare\ntransport-probe\ninit-transport\nactivate-nic\nconnect\ndisconnect\nretry\n"
.to_string()
}
HandleKind::Capabilities => self.backend.capabilities().join("\n") + "\n",
HandleKind::Status(iface) => {
let state = self.state(iface)?;
format!(
"status={}\nlink_state={}\nfirmware_status={}\ntransport_status={}\ntransport_init_status={}\nactivation_status={}\nconnect_result={}\ndisconnect_result={}\nssid={}\nsecurity={}\n",
state.status,
state.link_state,
state.firmware_status,
state.transport_status,
state.transport_init_status,
state.activation_status,
state.connect_result,
state.disconnect_result,
state.ssid,
state.security
)
}
HandleKind::LinkState(iface) => format!("{}\n", self.state(iface)?.link_state),
HandleKind::FirmwareStatus(iface) => format!("{}\n", self.state(iface)?.firmware_status),
HandleKind::TransportStatus(iface) => {
format!("{}\n", self.state(iface)?.transport_status)
}
HandleKind::TransportInitStatus(iface) => {
format!("{}\n", self.state(iface)?.transport_init_status)
}
HandleKind::ActivationStatus(iface) => {
format!("{}\n", self.state(iface)?.activation_status)
}
HandleKind::ConnectResult(iface) => format!("{}\n", self.state(iface)?.connect_result),
HandleKind::DisconnectResult(iface) => {
format!("{}\n", self.state(iface)?.disconnect_result)
}
HandleKind::ScanResults(iface) => self.state(iface)?.scan_results.join("\n") + "\n",
HandleKind::LastError(iface) => format!("{}\n", self.state(iface)?.last_error),
HandleKind::Ssid(iface) => format!("{}\n", self.state(iface)?.ssid),
HandleKind::Security(iface) => format!("{}\n", self.state(iface)?.security),
HandleKind::Key(_iface) => "[redacted]\n".to_string(),
HandleKind::Scan(_)
| HandleKind::TransportProbe(_)
| HandleKind::InitTransport(_)
| HandleKind::ActivateNic(_)
| HandleKind::Retry(_)
| HandleKind::Prepare(_)
| HandleKind::Connect(_)
| HandleKind::Disconnect(_) => String::new(),
})
}
fn link_state_for_status(status: &WifiStatus) -> &'static str {
match status {
WifiStatus::Connected => "link=connected",
WifiStatus::Associating => "link=associating",
WifiStatus::Scanning => "link=scanning",
WifiStatus::FirmwareReady | WifiStatus::DeviceDetected => "link=down",
WifiStatus::Down => "link=down",
WifiStatus::Failed => "link=down",
}
}
fn apply_connect_outcome(
&mut self,
iface: &str,
status: WifiStatus,
firmware_status: String,
transport_status: String,
connect_result: String,
disconnect_result: String,
) -> Result<()> {
let state = self.state_mut(iface)?;
state.status = status.as_str().to_string();
state.link_state = Self::link_state_for_status(&status).to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.connect_result = connect_result;
state.disconnect_result = disconnect_result;
Ok(())
}
}
impl SchemeSync for WifiCtlScheme {
fn scheme_root(&mut self) -> Result<usize> {
Ok(SCHEME_ROOT_ID)
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
let kind = if dirfd == SCHEME_ROOT_ID {
match path.trim_matches('/') {
"" => HandleKind::Root,
"ifaces" => HandleKind::Ifaces,
"capabilities" => HandleKind::Capabilities,
_ => return Err(Error::new(ENOENT)),
}
} else {
match self.handle(dirfd)? {
HandleKind::Ifaces => {
let iface = path.trim_matches('/');
self.state(iface)?;
HandleKind::Interface(iface.to_string())
}
HandleKind::Interface(iface) => match path.trim_matches('/') {
"status" => HandleKind::Status(iface.clone()),
"link-state" => HandleKind::LinkState(iface.clone()),
"firmware-status" => HandleKind::FirmwareStatus(iface.clone()),
"transport-status" => HandleKind::TransportStatus(iface.clone()),
"transport-init-status" => HandleKind::TransportInitStatus(iface.clone()),
"activation-status" => HandleKind::ActivationStatus(iface.clone()),
"connect-result" => HandleKind::ConnectResult(iface.clone()),
"disconnect-result" => HandleKind::DisconnectResult(iface.clone()),
"scan-results" => HandleKind::ScanResults(iface.clone()),
"last-error" => HandleKind::LastError(iface.clone()),
"ssid" => HandleKind::Ssid(iface.clone()),
"security" => HandleKind::Security(iface.clone()),
"key" => HandleKind::Key(iface.clone()),
"scan" => HandleKind::Scan(iface.clone()),
"prepare" => HandleKind::Prepare(iface.clone()),
"transport-probe" => HandleKind::TransportProbe(iface.clone()),
"init-transport" => HandleKind::InitTransport(iface.clone()),
"activate-nic" => HandleKind::ActivateNic(iface.clone()),
"connect" => HandleKind::Connect(iface.clone()),
"disconnect" => HandleKind::Disconnect(iface.clone()),
"retry" => HandleKind::Retry(iface.clone()),
_ => return Err(Error::new(ENOENT)),
},
_ => return Err(Error::new(EACCES)),
}
};
Ok(OpenResult::ThisScheme {
number: self.alloc_handle(kind),
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let data = self.read_handle(self.handle(id)?)?;
let bytes = data.as_bytes();
let offset = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
if offset >= bytes.len() {
return Ok(0);
}
let count = (bytes.len() - offset).min(buf.len());
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
Ok(count)
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let value = std::str::from_utf8(buf)
.map_err(|_| Error::new(EINVAL))?
.trim()
.to_string();
match self.handle(id)?.clone() {
HandleKind::Ssid(iface) => self.state_mut(&iface)?.ssid = value,
HandleKind::Security(iface) => self.state_mut(&iface)?.security = value,
HandleKind::Key(iface) => self.state_mut(&iface)?.key = value,
HandleKind::Scan(iface) => {
let results = match self.backend.scan(&iface) {
Ok(results) => results,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
return Ok(buf.len());
}
};
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.status = WifiStatus::Scanning.as_str().to_string();
state.link_state = "link=scanning".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.scan_results = results;
state.last_error.clear();
}
HandleKind::Prepare(iface) => {
let status = match self.backend.prepare(&iface) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
return Ok(buf.len());
}
};
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.status = status.as_str().to_string();
state.link_state = "link=prepared".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.transport_init_status = "transport_init=not-run".to_string();
}
HandleKind::TransportProbe(iface) => {
let transport_status = match self.backend.transport_probe(&iface) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
return Ok(buf.len());
}
};
let state = self.state_mut(&iface)?;
state.transport_status = transport_status;
}
HandleKind::InitTransport(iface) => {
let transport_init_status = match self.backend.init_transport(&iface) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.transport_init_status = "transport_init=failed".to_string();
return Ok(buf.len());
}
};
let state = self.state_mut(&iface)?;
state.transport_init_status = transport_init_status;
state.link_state = "link=transport-initialized".to_string();
state.activation_status = "activation=not-run".to_string();
}
HandleKind::ActivateNic(iface) => {
let activation_status = match self.backend.activate(&iface) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.activation_status = "activation=failed".to_string();
return Ok(buf.len());
}
};
let connect_result = self.backend.connect_result(&iface);
let disconnect_result = self.backend.disconnect_result(&iface);
let state = self.state_mut(&iface)?;
state.activation_status = activation_status;
state.link_state = "link=nic-active".to_string();
state.connect_result = connect_result;
state.disconnect_result = disconnect_result;
}
HandleKind::Connect(iface) => {
let snapshot = self.state(&iface)?.clone();
let new_status = match self.backend.connect(&iface, &snapshot) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.transport_init_status = "transport_init=failed".to_string();
state.activation_status = "activation=failed".to_string();
return Ok(buf.len());
}
};
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let connect_result = self.backend.connect_result(&iface);
let disconnect_result = self.backend.disconnect_result(&iface);
self.apply_connect_outcome(
&iface,
new_status,
firmware_status,
transport_status,
connect_result,
disconnect_result,
)?;
}
HandleKind::Disconnect(iface) => {
let status = match self.backend.disconnect(&iface) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.activation_status = "activation=failed".to_string();
return Ok(buf.len());
}
};
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let disconnect_result = self.backend.disconnect_result(&iface);
let state = self.state_mut(&iface)?;
state.status = status.as_str().to_string();
state.link_state = "link=down".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.disconnect_result = disconnect_result;
}
HandleKind::Retry(iface) => {
let status = match self.backend.retry(&iface) {
Ok(status) => status,
Err(err) => {
let firmware_status = self.backend.firmware_status(&iface);
let transport_status = self.backend.transport_status(&iface);
let state = self.state_mut(&iface)?;
state.last_error = err;
state.status = WifiStatus::Failed.as_str().to_string();
state.link_state = "link=retry-failed".to_string();
state.firmware_status = firmware_status;
state.transport_status = transport_status;
state.activation_status = "activation=failed".to_string();
return Ok(buf.len());
}
};
let state = self.state_mut(&iface)?;
state.status = status.as_str().to_string();
state.link_state = "link=retrying".to_string();
}
_ => return Err(Error::new(EROFS)),
}
Ok(buf.len())
}
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
let kind = self.handle(id)?;
stat.st_mode = match kind {
HandleKind::Root | HandleKind::Ifaces | HandleKind::Interface(_) => MODE_DIR | 0o755,
HandleKind::Connect(_)
| HandleKind::Disconnect(_)
| HandleKind::Scan(_)
| HandleKind::TransportProbe(_)
| HandleKind::InitTransport(_)
| HandleKind::Retry(_)
| HandleKind::Prepare(_)
| HandleKind::Ssid(_)
| HandleKind::Security(_)
| HandleKind::Key(_) => MODE_FILE | 0o644,
_ => MODE_FILE | 0o444,
};
Ok(())
}
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
let _ = self.handle(id)?;
Ok(())
}
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
let path = match self.handle(id)? {
HandleKind::Root => "wifictl:/".to_string(),
HandleKind::Ifaces => "wifictl:/ifaces".to_string(),
HandleKind::Interface(iface) => format!("wifictl:/ifaces/{iface}"),
HandleKind::Capabilities => "wifictl:/capabilities".to_string(),
HandleKind::Status(iface) => format!("wifictl:/ifaces/{iface}/status"),
HandleKind::LinkState(iface) => format!("wifictl:/ifaces/{iface}/link-state"),
HandleKind::FirmwareStatus(iface) => format!("wifictl:/ifaces/{iface}/firmware-status"),
HandleKind::TransportStatus(iface) => {
format!("wifictl:/ifaces/{iface}/transport-status")
}
HandleKind::TransportInitStatus(iface) => {
format!("wifictl:/ifaces/{iface}/transport-init-status")
}
HandleKind::ActivationStatus(iface) => {
format!("wifictl:/ifaces/{iface}/activation-status")
}
HandleKind::ConnectResult(iface) => format!("wifictl:/ifaces/{iface}/connect-result"),
HandleKind::DisconnectResult(iface) => {
format!("wifictl:/ifaces/{iface}/disconnect-result")
}
HandleKind::ScanResults(iface) => format!("wifictl:/ifaces/{iface}/scan-results"),
HandleKind::LastError(iface) => format!("wifictl:/ifaces/{iface}/last-error"),
HandleKind::Ssid(iface) => format!("wifictl:/ifaces/{iface}/ssid"),
HandleKind::Security(iface) => format!("wifictl:/ifaces/{iface}/security"),
HandleKind::Key(iface) => format!("wifictl:/ifaces/{iface}/key"),
HandleKind::Scan(iface) => format!("wifictl:/ifaces/{iface}/scan"),
HandleKind::Prepare(iface) => format!("wifictl:/ifaces/{iface}/prepare"),
HandleKind::TransportProbe(iface) => format!("wifictl:/ifaces/{iface}/transport-probe"),
HandleKind::InitTransport(iface) => format!("wifictl:/ifaces/{iface}/init-transport"),
HandleKind::ActivateNic(iface) => format!("wifictl:/ifaces/{iface}/activate-nic"),
HandleKind::Connect(iface) => format!("wifictl:/ifaces/{iface}/connect"),
HandleKind::Disconnect(iface) => format!("wifictl:/ifaces/{iface}/disconnect"),
HandleKind::Retry(iface) => format!("wifictl:/ifaces/{iface}/retry"),
};
let bytes = path.as_bytes();
let count = bytes.len().min(buf.len());
buf[..count].copy_from_slice(&bytes[..count]);
Ok(count)
}
fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result<EventFlags> {
let _ = self.handle(id)?;
Ok(EventFlags::empty())
}
fn on_close(&mut self, id: usize) {
if id != SCHEME_ROOT_ID {
self.handles.remove(&id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::{IntelBackend, StubBackend, TEST_ENV_LOCK};
use std::env;
use std::fs;
use std::path::PathBuf;
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 = env::temp_dir().join(format!("{prefix}-{stamp}"));
fs::create_dir_all(&path).unwrap();
path
}
#[test]
fn status_updates_after_connect_and_disconnect() {
let mut scheme = WifiCtlScheme::new(Box::new(StubBackend::from_env()));
let iface = "wlan0".to_string();
{
let state = scheme.state_mut(&iface).unwrap();
state.ssid = "demo-ssid".to_string();
state.security = "wpa2-psk".to_string();
state.key = "secret".to_string();
}
let snapshot = scheme.state(&iface).unwrap().clone();
let status = scheme.backend.connect(&iface, &snapshot).unwrap();
scheme
.apply_connect_outcome(
&iface,
status,
scheme.backend.firmware_status(&iface),
scheme.backend.transport_status(&iface),
scheme.backend.connect_result(&iface),
scheme.backend.disconnect_result(&iface),
)
.unwrap();
assert_eq!(scheme.state(&iface).unwrap().status, "connected");
assert_eq!(scheme.state(&iface).unwrap().link_state, "link=connected");
let status = scheme.backend.disconnect(&iface).unwrap();
scheme.state_mut(&iface).unwrap().status = status.as_str().to_string();
assert_eq!(scheme.state(&iface).unwrap().status, "device-detected");
}
#[test]
fn apply_connect_outcome_preserves_pending_link_state() {
let mut scheme = WifiCtlScheme::new(Box::new(StubBackend::from_env()));
let iface = "wlan0".to_string();
scheme
.apply_connect_outcome(
&iface,
WifiStatus::Associating,
"firmware=present".to_string(),
"transport=active".to_string(),
"connect_result=host-bounded-pending ssid=demo security=wpa2-psk".to_string(),
"disconnect_result=not-run".to_string(),
)
.unwrap();
let state = scheme.state(&iface).unwrap();
assert_eq!(state.status, "associating");
assert_eq!(state.link_state, "link=associating");
assert!(state.connect_result.contains("host-bounded-pending"));
}
#[test]
fn stub_prepare_marks_firmware_ready() {
let mut scheme = WifiCtlScheme::new(Box::new(StubBackend::from_env()));
let iface = "wlan0".to_string();
let status = scheme.backend.prepare(&iface).unwrap();
let firmware_status = scheme.backend.firmware_status(&iface);
let state = scheme.state_mut(&iface).unwrap();
state.status = status.as_str().to_string();
state.firmware_status = firmware_status;
assert_eq!(scheme.state(&iface).unwrap().status, "firmware-ready");
assert_eq!(
scheme.state(&iface).unwrap().firmware_status,
"firmware=stub"
);
assert_eq!(
scheme.state(&iface).unwrap().transport_status,
"transport=stub"
);
assert_eq!(
scheme.state(&iface).unwrap().transport_init_status,
"transport_init=not-run"
);
}
#[test]
fn stub_scan_updates_scan_results() {
let mut scheme = WifiCtlScheme::new(Box::new(StubBackend::from_env()));
let iface = "wlan0".to_string();
let results = scheme.backend.scan(&iface).unwrap();
let state = scheme.state_mut(&iface).unwrap();
state.status = WifiStatus::Scanning.as_str().to_string();
state.scan_results = results;
assert_eq!(scheme.state(&iface).unwrap().status, "scanning");
assert_eq!(
scheme.state(&iface).unwrap().scan_results,
vec!["demo-ssid".to_string(), "demo-open".to_string()]
);
}
#[test]
fn intel_prepare_failure_records_last_error() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
let pci = temp_root("rbos-wifictl-pci-missing");
let firmware = temp_root("rbos-wifictl-fw-missing");
let slot = pci.join("0000--00--14.3");
fs::create_dir_all(&slot).unwrap();
let mut cfg = vec![0u8; 64];
cfg[0x00] = 0x86;
cfg[0x01] = 0x80;
cfg[0x02] = 0x40;
cfg[0x03] = 0x77;
cfg[0x0A] = 0x80;
cfg[0x0B] = 0x02;
cfg[0x04] = 0x06;
cfg[0x10] = 0x01;
cfg[0x2E] = 0x90;
cfg[0x2F] = 0x40;
cfg[0x3D] = 0x01;
fs::write(slot.join("config"), cfg).unwrap();
unsafe {
env::set_var("REDBEAR_WIFICTL_PCI_ROOT", &pci);
env::set_var("REDBEAR_WIFICTL_FIRMWARE_ROOT", &firmware);
env::remove_var("REDBEAR_IWLWIFI_CMD");
}
let mut scheme = WifiCtlScheme::new(Box::new(IntelBackend::from_env()));
let iface = "wlan0".to_string();
let err = scheme.backend.prepare(&iface).unwrap_err();
let firmware_status = scheme.backend.firmware_status(&iface);
let state = scheme.state_mut(&iface).unwrap();
state.last_error = err.clone();
state.status = WifiStatus::Failed.as_str().to_string();
state.firmware_status = firmware_status;
assert!(scheme
.state(&iface)
.unwrap()
.last_error
.contains("missing firmware"));
assert_eq!(scheme.state(&iface).unwrap().status, "failed");
assert!(scheme
.state(&iface)
.unwrap()
.firmware_status
.contains("firmware=missing"));
}
#[test]
fn stub_transport_probe_updates_transport_status() {
let mut scheme = WifiCtlScheme::new(Box::new(StubBackend::from_env()));
let iface = "wlan0".to_string();
let transport_status = scheme.backend.transport_probe(&iface).unwrap();
scheme.state_mut(&iface).unwrap().transport_status = transport_status;
assert!(scheme
.state(&iface)
.unwrap()
.transport_status
.contains("mmio_probe=host-skipped"));
}
#[test]
fn stub_init_transport_records_state() {
let mut scheme = WifiCtlScheme::new(Box::new(StubBackend::from_env()));
let iface = "wlan0".to_string();
let status = scheme.backend.init_transport(&iface).unwrap();
scheme.state_mut(&iface).unwrap().transport_init_status = status;
assert_eq!(
scheme.state(&iface).unwrap().transport_init_status,
"transport_init=stub"
);
}
}