Add Wi-Fi driver and control tools
Red Bear OS Team
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
[source]
|
||||
path = "source"
|
||||
|
||||
[build]
|
||||
template = "cargo"
|
||||
dependencies = [
|
||||
"redox-driver-sys",
|
||||
"linux-kpi",
|
||||
]
|
||||
|
||||
[package.files]
|
||||
"/usr/lib/drivers/redbear-iwlwifi" = "redbear-iwlwifi"
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "redbear-iwlwifi"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-iwlwifi"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
thiserror = "2"
|
||||
redox-driver-sys = { path = "../../redox-driver-sys/source" }
|
||||
linux-kpi = { path = "../../linux-kpi/source" }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox-driver-sys = { path = "../../redox-driver-sys/source", features = ["redox"] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
@@ -0,0 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let linux_kpi_headers = PathBuf::from("../../linux-kpi/source/src/c_headers");
|
||||
|
||||
cc::Build::new()
|
||||
.file("src/linux_port.c")
|
||||
.include(linux_kpi_headers)
|
||||
.warnings(true)
|
||||
.compile("redbear_iwlwifi_linux_port");
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/nl80211.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/timer.h>
|
||||
#include <net/cfg80211.h>
|
||||
#include <net/mac80211.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static DEFINE_MUTEX(rb_iwlwifi_transport_lock);
|
||||
static struct ieee80211_hw *rb_iwlwifi_hw;
|
||||
static struct net_device *rb_iwlwifi_netdev;
|
||||
static struct wireless_dev rb_iwlwifi_wdev;
|
||||
|
||||
static void rb_iwlwifi_release_wireless_stack(void)
|
||||
{
|
||||
if (rb_iwlwifi_netdev) {
|
||||
if (rb_iwlwifi_netdev->registered)
|
||||
unregister_netdev(rb_iwlwifi_netdev);
|
||||
free_netdev(rb_iwlwifi_netdev);
|
||||
rb_iwlwifi_netdev = NULL;
|
||||
}
|
||||
|
||||
if (rb_iwlwifi_hw) {
|
||||
if (rb_iwlwifi_hw->registered)
|
||||
ieee80211_unregister_hw(rb_iwlwifi_hw);
|
||||
ieee80211_free_hw(rb_iwlwifi_hw);
|
||||
rb_iwlwifi_hw = NULL;
|
||||
}
|
||||
|
||||
memset(&rb_iwlwifi_wdev, 0, sizeof(rb_iwlwifi_wdev));
|
||||
}
|
||||
|
||||
static int rb_iwlwifi_ensure_wireless_stack(void)
|
||||
{
|
||||
if (!rb_iwlwifi_hw) {
|
||||
rb_iwlwifi_hw = ieee80211_alloc_hw_nm(0, NULL, "rb-iwlwifi");
|
||||
if (!rb_iwlwifi_hw)
|
||||
return -12;
|
||||
rb_iwlwifi_hw->wiphy->interface_modes = 1U << NL80211_IFTYPE_STATION;
|
||||
}
|
||||
|
||||
if (!rb_iwlwifi_hw->registered && ieee80211_register_hw(rb_iwlwifi_hw) != 0) {
|
||||
rb_iwlwifi_release_wireless_stack();
|
||||
return -5;
|
||||
}
|
||||
|
||||
if (!rb_iwlwifi_netdev) {
|
||||
rb_iwlwifi_netdev = alloc_netdev_mqs(0, "wlan%d", 0, NULL, 1, 1);
|
||||
if (!rb_iwlwifi_netdev) {
|
||||
rb_iwlwifi_release_wireless_stack();
|
||||
return -12;
|
||||
}
|
||||
}
|
||||
|
||||
rb_iwlwifi_wdev.wiphy = rb_iwlwifi_hw->wiphy;
|
||||
rb_iwlwifi_wdev.netdev = rb_iwlwifi_netdev;
|
||||
rb_iwlwifi_wdev.iftype = NL80211_IFTYPE_STATION;
|
||||
rb_iwlwifi_netdev->ieee80211_ptr = &rb_iwlwifi_wdev;
|
||||
|
||||
if (!rb_iwlwifi_netdev->registered && register_netdev(rb_iwlwifi_netdev) != 0) {
|
||||
rb_iwlwifi_release_wireless_stack();
|
||||
return -5;
|
||||
}
|
||||
|
||||
netif_carrier_off(rb_iwlwifi_netdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rb_iwlwifi_timer_callback(unsigned long data)
|
||||
{
|
||||
unsigned long *flag = (unsigned long *)data;
|
||||
if (flag)
|
||||
*flag = 1;
|
||||
}
|
||||
|
||||
static void rb_iwlwifi_wait_for_timer(unsigned long delay_ms)
|
||||
{
|
||||
struct timer_list timer = {0};
|
||||
unsigned long fired = 0;
|
||||
|
||||
setup_timer(&timer, rb_iwlwifi_timer_callback, (unsigned long)&fired);
|
||||
mod_timer(&timer, jiffies + delay_ms);
|
||||
while (!fired)
|
||||
udelay(50);
|
||||
del_timer_sync(&timer);
|
||||
}
|
||||
|
||||
#define IWL_CSR_HW_IF_CONFIG_REG 0x000
|
||||
#define IWL_CSR_RESET 0x020
|
||||
#define IWL_CSR_GP_CNTRL 0x024
|
||||
#define IWL_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ 0x00000008U
|
||||
#define IWL_CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ 0x00200000U
|
||||
#define IWL_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY 0x00000004U
|
||||
#define IWL_CSR_GP_CNTRL_REG_FLAG_SW_RESET_BZ 0x80000000U
|
||||
#define IWL_CSR_RESET_REG_FLAG_SW_RESET 0x00000080U
|
||||
#define IWL_CSR_GP_CNTRL_REG_FLAG_INIT_DONE 0x00000004U
|
||||
|
||||
int rb_iwlwifi_linux_prepare(struct pci_dev *dev, const char *ucode, const char *pnvm,
|
||||
char *out, unsigned long out_len)
|
||||
{
|
||||
const struct firmware *fw = 0;
|
||||
int ret;
|
||||
|
||||
if (!dev || !ucode || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
ret = pci_enable_device(dev);
|
||||
if (ret) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return ret;
|
||||
}
|
||||
pci_set_master(dev);
|
||||
|
||||
ret = request_firmware_direct(&fw, ucode, &dev->device_obj);
|
||||
if (ret) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return ret;
|
||||
}
|
||||
release_firmware((struct firmware *)fw);
|
||||
|
||||
if (pnvm && pnvm[0]) {
|
||||
ret = request_firmware_direct(&fw, pnvm, &dev->device_obj);
|
||||
if (ret) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return ret;
|
||||
}
|
||||
release_firmware((struct firmware *)fw);
|
||||
}
|
||||
|
||||
rb_iwlwifi_wait_for_timer(1);
|
||||
snprintf(out, out_len, "linux_kpi_prepare=ok firmware_api=direct timer_sync=ok");
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rb_iwlwifi_linux_transport_probe(struct pci_dev *dev, unsigned int bar, char *out,
|
||||
unsigned long out_len)
|
||||
{
|
||||
void *mmio;
|
||||
uint32_t reg0;
|
||||
size_t len;
|
||||
|
||||
unsigned long irq_flags = 0;
|
||||
|
||||
if (!dev || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
len = pci_resource_len(dev, bar);
|
||||
if (!len) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -19;
|
||||
}
|
||||
|
||||
mmio = pci_iomap(dev, bar, len);
|
||||
if (!mmio) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -5;
|
||||
}
|
||||
|
||||
local_irq_save(&irq_flags);
|
||||
reg0 = readl(mmio);
|
||||
local_irq_restore(irq_flags);
|
||||
snprintf(out, out_len, "linux_kpi_transport_probe=ok reg0=0x%08x irq_guarded=yes", reg0);
|
||||
pci_iounmap(dev, mmio, len);
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rb_iwlwifi_linux_init_transport(struct pci_dev *dev, unsigned int bar, int bz_family,
|
||||
char *out, unsigned long out_len)
|
||||
{
|
||||
void *mmio;
|
||||
size_t len;
|
||||
uint32_t gp_before, gp_after, hw_if;
|
||||
uint32_t access_req = bz_family ? IWL_CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ
|
||||
: IWL_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ;
|
||||
|
||||
unsigned long irq_flags = 0;
|
||||
|
||||
if (!dev || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
if (pci_enable_device(dev)) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -5;
|
||||
}
|
||||
pci_set_master(dev);
|
||||
|
||||
len = pci_resource_len(dev, bar);
|
||||
if (!len) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -19;
|
||||
}
|
||||
|
||||
mmio = pci_iomap(dev, bar, len);
|
||||
if (!mmio) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -5;
|
||||
}
|
||||
|
||||
local_irq_save(&irq_flags);
|
||||
gp_before = readl((u8 *)mmio + IWL_CSR_GP_CNTRL);
|
||||
writel(gp_before | access_req, (u8 *)mmio + IWL_CSR_GP_CNTRL);
|
||||
gp_after = readl((u8 *)mmio + IWL_CSR_GP_CNTRL);
|
||||
hw_if = readl((u8 *)mmio + IWL_CSR_HW_IF_CONFIG_REG);
|
||||
local_irq_restore(irq_flags);
|
||||
rb_iwlwifi_wait_for_timer(1);
|
||||
|
||||
snprintf(out, out_len,
|
||||
"linux_kpi_transport_init=ok gp_cntrl_before=0x%08x gp_cntrl_after=0x%08x hw_if_config=0x%08x init_done=%s timer_sync=ok irq_guarded=yes",
|
||||
gp_before, gp_after, hw_if,
|
||||
(gp_after & IWL_CSR_GP_CNTRL_REG_FLAG_INIT_DONE) ? "yes" : "no");
|
||||
pci_iounmap(dev, mmio, len);
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rb_iwlwifi_linux_activate_nic(struct pci_dev *dev, unsigned int bar, int bz_family,
|
||||
char *out, unsigned long out_len)
|
||||
{
|
||||
void *mmio;
|
||||
size_t len;
|
||||
|
||||
unsigned long irq_flags = 0;
|
||||
|
||||
if (!dev || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
len = pci_resource_len(dev, bar);
|
||||
if (!len) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -19;
|
||||
}
|
||||
|
||||
mmio = pci_iomap(dev, bar, len);
|
||||
if (!mmio) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -5;
|
||||
}
|
||||
|
||||
local_irq_save(&irq_flags);
|
||||
if (bz_family) {
|
||||
uint32_t gp_before = readl((u8 *)mmio + IWL_CSR_GP_CNTRL);
|
||||
writel(gp_before | IWL_CSR_GP_CNTRL_REG_FLAG_SW_RESET_BZ,
|
||||
(u8 *)mmio + IWL_CSR_GP_CNTRL);
|
||||
local_irq_restore(irq_flags);
|
||||
rb_iwlwifi_wait_for_timer(1);
|
||||
snprintf(out, out_len,
|
||||
"linux_kpi_activate=ok activation_method=gp-cntrl-sw-reset activation_before=0x%08x activation_after=0x%08x timer_sync=ok irq_guarded=yes",
|
||||
gp_before, readl((u8 *)mmio + IWL_CSR_GP_CNTRL));
|
||||
} else {
|
||||
uint32_t reset_before = readl((u8 *)mmio + IWL_CSR_RESET);
|
||||
writel(reset_before | IWL_CSR_RESET_REG_FLAG_SW_RESET,
|
||||
(u8 *)mmio + IWL_CSR_RESET);
|
||||
local_irq_restore(irq_flags);
|
||||
rb_iwlwifi_wait_for_timer(1);
|
||||
snprintf(out, out_len,
|
||||
"linux_kpi_activate=ok activation_method=csr-reset-sw-reset activation_before=0x%08x activation_after=0x%08x timer_sync=ok irq_guarded=yes",
|
||||
reset_before, readl((u8 *)mmio + IWL_CSR_RESET));
|
||||
}
|
||||
|
||||
pci_iounmap(dev, mmio, len);
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rb_iwlwifi_linux_scan(struct pci_dev *dev, const char *ssid, char *out, unsigned long out_len)
|
||||
{
|
||||
struct cfg80211_scan_request request = {0};
|
||||
struct cfg80211_scan_info info = {0};
|
||||
int rc;
|
||||
|
||||
if (!dev || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
rc = rb_iwlwifi_ensure_wireless_stack();
|
||||
if (rc != 0) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
request.wiphy = rb_iwlwifi_hw->wiphy;
|
||||
request.wdev = &rb_iwlwifi_wdev;
|
||||
request.n_ssids = (ssid && ssid[0]) ? 1 : 0;
|
||||
request.n_channels = 1;
|
||||
rb_iwlwifi_wdev.scan_in_flight = true;
|
||||
rb_iwlwifi_wdev.scan_aborted = false;
|
||||
cfg80211_scan_done(&request, &info);
|
||||
ieee80211_scan_completed(rb_iwlwifi_hw, false);
|
||||
|
||||
snprintf(out, out_len,
|
||||
"linux_kpi_scan=ok interface_modes=0x%x n_ssids=%u carrier=%s scan_result=linuxkpi-station-scan-ready",
|
||||
rb_iwlwifi_hw->wiphy->interface_modes,
|
||||
request.n_ssids,
|
||||
netif_carrier_ok(rb_iwlwifi_netdev) ? "up" : "down");
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rb_iwlwifi_linux_connect(struct pci_dev *dev, const char *ssid, const char *security,
|
||||
const char *key, char *out, unsigned long out_len)
|
||||
{
|
||||
struct cfg80211_connect_params params = {0};
|
||||
int rc;
|
||||
|
||||
if (!dev || !ssid || !ssid[0] || !security || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
rc = rb_iwlwifi_ensure_wireless_stack();
|
||||
if (rc != 0) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (strcmp(security, "open") != 0 && strcmp(security, "wpa2-psk") != 0) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -95;
|
||||
}
|
||||
|
||||
if (strcmp(security, "wpa2-psk") == 0 && (!key || !key[0])) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -22;
|
||||
}
|
||||
|
||||
params.ssid = (const u8 *)ssid;
|
||||
params.ssid_len = strlen(ssid);
|
||||
params.key.key = (const u8 *)key;
|
||||
params.key.key_len = key ? (u8)strlen(key) : 0;
|
||||
params.key.cipher = strcmp(security, "open") == 0 ? 0 : 0x000fac04;
|
||||
rb_iwlwifi_wdev.connecting = true;
|
||||
rb_iwlwifi_wdev.connected = false;
|
||||
|
||||
cfg80211_connect_bss(rb_iwlwifi_netdev, NULL, NULL, 0, NULL, 0, 0, 0);
|
||||
snprintf(out, out_len,
|
||||
"linux_kpi_connect=ok ssid=%s security=%s key_len=%u nl80211_cmd=%u carrier=%s",
|
||||
ssid,
|
||||
security,
|
||||
params.key.key_len,
|
||||
NL80211_CMD_CONNECT,
|
||||
netif_carrier_ok(rb_iwlwifi_netdev) ? "up" : "down");
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rb_iwlwifi_linux_disconnect(struct pci_dev *dev, char *out, unsigned long out_len)
|
||||
{
|
||||
if (!dev || !out || out_len == 0)
|
||||
return -22;
|
||||
|
||||
if (!mutex_trylock(&rb_iwlwifi_transport_lock))
|
||||
return -16;
|
||||
|
||||
if (!rb_iwlwifi_netdev) {
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return -19;
|
||||
}
|
||||
|
||||
cfg80211_disconnected(rb_iwlwifi_netdev, 0, NULL, 0, true, 0);
|
||||
snprintf(out, out_len, "linux_kpi_disconnect=ok carrier=%s", netif_carrier_ok(rb_iwlwifi_netdev) ? "up" : "down");
|
||||
mutex_unlock(&rb_iwlwifi_transport_lock);
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
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
|
||||
}
|
||||
|
||||
fn write_intel_candidate(pci_root: &PathBuf) {
|
||||
let slot = pci_root.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();
|
||||
}
|
||||
|
||||
fn run_iwlwifi(args: &[&str], pci_root: &PathBuf, fw_root: &PathBuf) -> String {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_redbear-iwlwifi"))
|
||||
.args(args)
|
||||
.env("REDBEAR_IWLWIFI_PCI_ROOT", pci_root)
|
||||
.env("REDBEAR_IWLWIFI_FIRMWARE_ROOT", fw_root)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"command {:?} failed: {}",
|
||||
args,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
String::from_utf8(output.stdout).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_flow_reports_bounded_intel_progression() {
|
||||
let pci = temp_root("rbos-iwlwifi-cli-pci");
|
||||
let fw = temp_root("rbos-iwlwifi-cli-fw");
|
||||
write_intel_candidate(&pci);
|
||||
fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap();
|
||||
fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap();
|
||||
|
||||
let status = run_iwlwifi(&["--status"], &pci, &fw);
|
||||
assert!(status.contains("status=firmware-ready"));
|
||||
assert!(status.contains("selected_pnvm=iwlwifi-bz-b0-gf-a0.pnvm"));
|
||||
|
||||
let prepare = run_iwlwifi(&["--prepare"], &pci, &fw);
|
||||
assert!(prepare.contains("status=firmware-ready"));
|
||||
assert!(prepare.contains("selected_ucode=iwlwifi-bz-b0-gf-a0-92.ucode"));
|
||||
|
||||
let init = run_iwlwifi(&["--init-transport"], &pci, &fw);
|
||||
assert!(init.contains("status=transport-ready"));
|
||||
assert!(init.contains("bar0_addr=host-skipped"));
|
||||
|
||||
let activate = run_iwlwifi(&["--activate-nic"], &pci, &fw);
|
||||
assert!(activate.contains("status=nic-activated"));
|
||||
assert!(activate.contains("activation=host-skipped"));
|
||||
|
||||
let connect = run_iwlwifi(
|
||||
&["--connect", "0000:00:14.3", "demo", "wpa2-psk", "secret"],
|
||||
&pci,
|
||||
&fw,
|
||||
);
|
||||
assert!(connect.contains("status=associating"));
|
||||
assert!(connect.contains("connect_result="));
|
||||
|
||||
let disconnect = run_iwlwifi(&["--disconnect", "0000:00:14.3"], &pci, &fw);
|
||||
assert!(disconnect.contains("status=device-detected"));
|
||||
assert!(disconnect.contains("disconnect_result="));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::process;
|
||||
|
||||
use redox_driver_sys::dma::DmaBuffer;
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
println!("=== Red Bear OS DMA Runtime Check ===");
|
||||
|
||||
let mut one_page =
|
||||
DmaBuffer::allocate(4096, 4096).map_err(|err| format!("alloc 4K failed: {err}"))?;
|
||||
println!(
|
||||
"dma_4k cpu={:#x} phys={:#x} len={:#x}",
|
||||
one_page.as_ptr() as usize,
|
||||
one_page.physical_address(),
|
||||
one_page.len()
|
||||
);
|
||||
unsafe {
|
||||
(one_page.as_mut_ptr() as *mut u32).write_volatile(0x1122_3344);
|
||||
let value = (one_page.as_ptr() as *const u32).read_volatile();
|
||||
println!("dma_4k_value={:#x}", value);
|
||||
}
|
||||
|
||||
let mut two_page =
|
||||
DmaBuffer::allocate(8192, 4096).map_err(|err| format!("alloc 8K failed: {err}"))?;
|
||||
println!(
|
||||
"dma_8k cpu={:#x} phys={:#x} len={:#x}",
|
||||
two_page.as_ptr() as usize,
|
||||
two_page.physical_address(),
|
||||
two_page.len()
|
||||
);
|
||||
unsafe {
|
||||
let second_page = two_page.as_mut_ptr().add(4096) as *mut u32;
|
||||
second_page.write_volatile(0x5566_7788);
|
||||
let value = (two_page.as_ptr().add(4096) as *const u32).read_volatile();
|
||||
println!("dma_8k_second_page_value={:#x}", value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("redbear-phase-dma-check: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use std::fs;
|
||||
use std::process;
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
use serde_json::Value;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase5-wifi-analyze";
|
||||
const USAGE: &str =
|
||||
"Usage: redbear-phase5-wifi-analyze <capture.json>\n\nSummarize a Wi-Fi capture bundle into likely blocker categories.";
|
||||
|
||||
fn read_text<'a>(value: &'a Value, path: &[&str]) -> &'a str {
|
||||
let mut current = value;
|
||||
for segment in path {
|
||||
match current.get(*segment) {
|
||||
Some(next) => current = next,
|
||||
None => return "",
|
||||
}
|
||||
}
|
||||
current.as_str().unwrap_or("")
|
||||
}
|
||||
|
||||
fn classify(capture: &Value) -> Vec<&'static str> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
let driver_probe = read_text(capture, &["commands", "driver_probe", "stdout"]);
|
||||
let connect = read_text(capture, &["commands", "phase5_wifi_check", "stdout"]);
|
||||
let connect_result = read_text(capture, &["scheme", "connect_result", "value"]);
|
||||
let disconnect_result = read_text(capture, &["scheme", "disconnect_result", "value"]);
|
||||
let last_error = read_text(capture, &["scheme", "last_error", "value"]);
|
||||
let netctl_status = read_text(capture, &["commands", "netctl_status", "stdout"]);
|
||||
let redbear_info = read_text(capture, &["commands", "redbear_info_json", "stdout"]);
|
||||
|
||||
if !driver_probe.contains("candidates=") || driver_probe.contains("candidates=0") {
|
||||
out.push("device-detection");
|
||||
}
|
||||
if connect.contains("missing firmware") || last_error.contains("firmware") {
|
||||
out.push("firmware");
|
||||
}
|
||||
if connect_result.is_empty() || connect_result.contains("not-run") {
|
||||
out.push("association-control-path");
|
||||
}
|
||||
if disconnect_result.is_empty() || disconnect_result.contains("not-run") {
|
||||
out.push("disconnect-lifecycle");
|
||||
}
|
||||
if !netctl_status.contains("address=") || netctl_status.contains("address=unknown") {
|
||||
out.push("dhcp-or-addressing");
|
||||
}
|
||||
if !redbear_info.contains("wifi_connect_result")
|
||||
|| !redbear_info.contains("wifi_disconnect_result")
|
||||
{
|
||||
out.push("reporting-surface");
|
||||
}
|
||||
if last_error.contains("timed out") || last_error.contains("failed") {
|
||||
out.push("runtime-failure");
|
||||
}
|
||||
|
||||
if out.is_empty() {
|
||||
out.push("bounded-lifecycle-pass-no-real-link-proof");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
parse_args(PROGRAM, USAGE, args.clone().into_iter()).map_err(|err| {
|
||||
if err.is_empty() {
|
||||
process::exit(0);
|
||||
}
|
||||
err
|
||||
})?;
|
||||
|
||||
let path = args
|
||||
.get(1)
|
||||
.ok_or_else(|| "missing capture.json path".to_string())?;
|
||||
let text = fs::read_to_string(path).map_err(|err| format!("failed to read {}: {err}", path))?;
|
||||
let capture: Value = serde_json::from_str(&text)
|
||||
.map_err(|err| format!("failed to parse {} as JSON: {err}", path))?;
|
||||
|
||||
println!("=== Red Bear Wi-Fi Capture Analysis ===");
|
||||
println!("capture={path}");
|
||||
println!(
|
||||
"profile={}",
|
||||
capture
|
||||
.get("profile")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown")
|
||||
);
|
||||
println!(
|
||||
"interface={}",
|
||||
capture
|
||||
.get("interface")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown")
|
||||
);
|
||||
|
||||
let classes = classify(&capture);
|
||||
println!("classification={}", classes.join(","));
|
||||
for item in classes {
|
||||
println!("blocker={item}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn classify_flags_missing_detection() {
|
||||
let capture = json!({
|
||||
"commands": {
|
||||
"driver_probe": {"stdout": "candidates=0"},
|
||||
"phase5_wifi_check": {"stdout": ""},
|
||||
"netctl_status": {"stdout": "address=unknown"},
|
||||
"redbear_info_json": {"stdout": "{}"}
|
||||
},
|
||||
"scheme": {
|
||||
"connect_result": {"value": ""},
|
||||
"disconnect_result": {"value": ""},
|
||||
"last_error": {"value": ""}
|
||||
}
|
||||
});
|
||||
let classes = classify(&capture);
|
||||
assert!(classes.contains(&"device-detection"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_pass_path_when_only_bounded_state_exists() {
|
||||
let capture = json!({
|
||||
"commands": {
|
||||
"driver_probe": {"stdout": "candidates=1"},
|
||||
"phase5_wifi_check": {"stdout": "PASS: bounded Intel Wi-Fi runtime path exercised on target"},
|
||||
"netctl_status": {"stdout": "address=10.0.0.44/24"},
|
||||
"redbear_info_json": {"stdout": "wifi_connect_result wifi_disconnect_result"}
|
||||
},
|
||||
"scheme": {
|
||||
"connect_result": {"value": "connect_result=bounded-associated"},
|
||||
"disconnect_result": {"value": "disconnect_result=bounded-disconnected"},
|
||||
"last_error": {"value": "none"}
|
||||
}
|
||||
});
|
||||
let classes = classify(&capture);
|
||||
assert_eq!(classes, vec!["bounded-lifecycle-pass-no-real-link-proof"]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::{self, Command};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
use serde_json::json;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase5-wifi-capture";
|
||||
const USAGE: &str = "Usage: redbear-phase5-wifi-capture [PROFILE] [INTERFACE] [OUTPUT_PATH]\n\nCapture the current bounded Intel Wi-Fi runtime surfaces into a single JSON bundle.";
|
||||
|
||||
fn run_command(program: &str, args: &[&str]) -> serde_json::Value {
|
||||
match Command::new(program).args(args).output() {
|
||||
Ok(output) => json!({
|
||||
"ok": output.status.success(),
|
||||
"status": output.status.code(),
|
||||
"stdout": String::from_utf8_lossy(&output.stdout),
|
||||
"stderr": String::from_utf8_lossy(&output.stderr),
|
||||
}),
|
||||
Err(err) => json!({
|
||||
"ok": false,
|
||||
"status": null,
|
||||
"stdout": "",
|
||||
"stderr": format!("failed to run {} {:?}: {}", program, args, err),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_optional(path: &str) -> serde_json::Value {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(content) => json!({"present": true, "value": content}),
|
||||
Err(err) => json!({"present": false, "error": err.to_string()}),
|
||||
}
|
||||
}
|
||||
|
||||
fn list_optional(path: &str) -> serde_json::Value {
|
||||
match fs::read_dir(path) {
|
||||
Ok(entries) => {
|
||||
let mut values = entries
|
||||
.flatten()
|
||||
.map(|entry| entry.file_name().to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
values.sort();
|
||||
json!({"present": true, "entries": values})
|
||||
}
|
||||
Err(err) => json!({"present": false, "error": err.to_string()}),
|
||||
}
|
||||
}
|
||||
|
||||
fn unix_timestamp_secs() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_secs())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
parse_args(PROGRAM, USAGE, args.clone().into_iter()).map_err(|err| {
|
||||
if err.is_empty() {
|
||||
process::exit(0);
|
||||
}
|
||||
err
|
||||
})?;
|
||||
|
||||
let profile = args
|
||||
.get(1)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("wifi-open-bounded");
|
||||
let iface = args
|
||||
.get(2)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("wlan0");
|
||||
let output_path = args
|
||||
.get(3)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str);
|
||||
|
||||
let scheme_root = format!("/scheme/wifictl/ifaces/{iface}");
|
||||
let payload = json!({
|
||||
"captured_at_unix": unix_timestamp_secs(),
|
||||
"profile": profile,
|
||||
"interface": iface,
|
||||
"installed": {
|
||||
"driver": Path::new("/usr/lib/drivers/redbear-iwlwifi").exists(),
|
||||
"wifictl": Path::new("/usr/bin/redbear-wifictl").exists(),
|
||||
"netctl": Path::new("/usr/bin/redbear-netctl").exists(),
|
||||
"redbear_info": Path::new("/usr/bin/redbear-info").exists(),
|
||||
},
|
||||
"host": {
|
||||
"uname": run_command("uname", &["-a"]),
|
||||
},
|
||||
"commands": {
|
||||
"driver_probe": run_command("redbear-iwlwifi", &["--probe"]),
|
||||
"driver_status": run_command("redbear-iwlwifi", &["--status", iface]),
|
||||
"wifictl_probe": run_command("redbear-wifictl", &["--probe"]),
|
||||
"wifictl_status": run_command("redbear-wifictl", &["--status", iface]),
|
||||
"netctl_list": run_command("redbear-netctl", &["list"]),
|
||||
"netctl_status_all": run_command("redbear-netctl", &["status"]),
|
||||
"netctl_status": run_command("redbear-netctl", &["status", profile]),
|
||||
"redbear_info_json": run_command("redbear-info", &["--json"]),
|
||||
"phase5_network_check": run_command("redbear-phase5-network-check", &[]),
|
||||
"phase5_wifi_check": run_command("redbear-phase5-wifi-check", &[profile, iface]),
|
||||
"lspci": run_command("lspci", &[]),
|
||||
},
|
||||
"filesystem": {
|
||||
"wifictl_ifaces": list_optional("/scheme/wifictl/ifaces"),
|
||||
"netcfg_ifaces": list_optional("/scheme/netcfg/ifaces"),
|
||||
"netctl_profiles": list_optional("/etc/netctl"),
|
||||
"active_profile": read_optional("/etc/netctl/active"),
|
||||
"profile_contents": read_optional(&format!("/etc/netctl/{profile}")),
|
||||
},
|
||||
"scheme": {
|
||||
"status": read_optional(&format!("{scheme_root}/status")),
|
||||
"link_state": read_optional(&format!("{scheme_root}/link-state")),
|
||||
"firmware_status": read_optional(&format!("{scheme_root}/firmware-status")),
|
||||
"transport_status": read_optional(&format!("{scheme_root}/transport-status")),
|
||||
"transport_init_status": read_optional(&format!("{scheme_root}/transport-init-status")),
|
||||
"activation_status": read_optional(&format!("{scheme_root}/activation-status")),
|
||||
"connect_result": read_optional(&format!("{scheme_root}/connect-result")),
|
||||
"disconnect_result": read_optional(&format!("{scheme_root}/disconnect-result")),
|
||||
"scan_results": read_optional(&format!("{scheme_root}/scan-results")),
|
||||
"last_error": read_optional(&format!("{scheme_root}/last-error")),
|
||||
}
|
||||
});
|
||||
|
||||
let rendered = serde_json::to_string_pretty(&payload)
|
||||
.map_err(|err| format!("failed to serialize capture payload: {err}"))?;
|
||||
if let Some(output_path) = output_path {
|
||||
fs::write(output_path, &rendered)
|
||||
.map_err(|err| format!("failed to write capture bundle to {}: {err}", output_path))?;
|
||||
}
|
||||
println!("{}", rendered);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use std::path::Path;
|
||||
use std::process::{self, Command};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase5-wifi-check";
|
||||
const USAGE: &str = "Usage: redbear-phase5-wifi-check [PROFILE] [INTERFACE]\n\nExercise the bounded Intel Wi-Fi runtime path inside a Red Bear OS guest or target runtime. The packaged runtime path defaults to the bounded open-profile flow; WPA2-PSK remains implemented and host/unit-verified, but is not yet the default packaged runtime proof.";
|
||||
|
||||
fn require_path(path: &str) -> Result<(), String> {
|
||||
if Path::new(path).exists() {
|
||||
println!("{path}");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("missing {path}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn require_contains(label: &str, haystack: &str, needle: &str) -> Result<(), String> {
|
||||
if haystack.contains(needle) {
|
||||
println!("{label}={needle}");
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{label} missing {needle}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn run_command(program: &str, args: &[&str]) -> Result<String, String> {
|
||||
let output = Command::new(program)
|
||||
.args(args)
|
||||
.output()
|
||||
.map_err(|err| format!("failed to run {} {:?}: {err}", program, args))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if stderr.trim().is_empty() {
|
||||
return Err(format!(
|
||||
"{} {:?} exited with status {}",
|
||||
program, args, output.status
|
||||
));
|
||||
}
|
||||
return Err(format!(
|
||||
"{} {:?} exited with status {}: {}",
|
||||
program,
|
||||
args,
|
||||
output.status,
|
||||
stderr.trim()
|
||||
));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
parse_args(PROGRAM, USAGE, args.clone().into_iter()).map_err(|err| {
|
||||
if err.is_empty() {
|
||||
process::exit(0);
|
||||
}
|
||||
err
|
||||
})?;
|
||||
|
||||
let profile = args
|
||||
.get(1)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("wifi-open-bounded");
|
||||
let iface = args
|
||||
.get(2)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("wlan0");
|
||||
|
||||
println!("=== Red Bear OS Phase 5 Wi-Fi Check ===");
|
||||
println!("profile={profile}");
|
||||
println!("interface={iface}");
|
||||
|
||||
require_path("/usr/lib/drivers/redbear-iwlwifi")?;
|
||||
require_path("/usr/bin/redbear-wifictl")?;
|
||||
require_path("/usr/bin/redbear-netctl")?;
|
||||
require_path("/usr/bin/redbear-info")?;
|
||||
|
||||
let driver_probe = run_command("redbear-iwlwifi", &["--probe"])?;
|
||||
print!("{driver_probe}");
|
||||
require_contains("driver_probe", &driver_probe, "candidates=")?;
|
||||
|
||||
let probe = run_command("redbear-wifictl", &["--probe"])?;
|
||||
print!("{probe}");
|
||||
require_contains("wifictl_probe", &probe, "interfaces=")?;
|
||||
require_contains("wifictl_probe", &probe, iface)?;
|
||||
|
||||
let connect = run_command("redbear-wifictl", &["--connect", iface, "demo", "open"])?;
|
||||
print!("{connect}");
|
||||
require_contains("connect", &connect, "status=connected")
|
||||
.or_else(|_| require_contains("connect", &connect, "status=associated"))
|
||||
.or_else(|_| require_contains("connect", &connect, "status=associating"))?;
|
||||
require_contains("connect", &connect, "connect_result=")?;
|
||||
|
||||
let disconnect = run_command("redbear-wifictl", &["--disconnect", iface])?;
|
||||
print!("{disconnect}");
|
||||
require_contains("disconnect", &disconnect, "disconnect_result=")?;
|
||||
|
||||
let start = run_command("redbear-netctl", &["start", profile])?;
|
||||
print!("{start}");
|
||||
let status = run_command("redbear-netctl", &["status", profile])?;
|
||||
print!("{status}");
|
||||
require_contains("netctl_status", &status, &format!("interface={iface}"))?;
|
||||
require_contains("netctl_status", &status, "connect_result=")?;
|
||||
|
||||
let stop = run_command("redbear-netctl", &["stop", profile])?;
|
||||
print!("{stop}");
|
||||
|
||||
let info = run_command("redbear-info", &["--json"])?;
|
||||
print!("{info}");
|
||||
require_contains("redbear_info", &info, "wifi_control_state")?;
|
||||
require_contains("redbear_info", &info, "wifi_connect_result")?;
|
||||
require_contains("redbear_info", &info, "wifi_disconnect_result")?;
|
||||
|
||||
println!("PASS: bounded Intel Wi-Fi runtime path exercised inside target runtime");
|
||||
println!("NOTE: the packaged runtime checker currently validates the bounded open-profile path by default; WPA2-PSK is implemented and host/unit-verified elsewhere in-repo but is not yet the default packaged runtime proof");
|
||||
println!("NOTE: this still does not prove real AP scan/auth/association, packet flow, DHCP success over Wi-Fi, or validated end-to-end connectivity");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn require_contains_accepts_present_substring() {
|
||||
assert!(
|
||||
require_contains("test", "abc wifi_control_state xyz", "wifi_control_state").is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn require_contains_rejects_missing_substring() {
|
||||
assert!(require_contains("test", "abc", "wifi_connect_result").is_err());
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
use std::process::{self, Command};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
use serde_json::Value;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase5-wifi-link-check";
|
||||
const USAGE: &str = "Usage: redbear-phase5-wifi-link-check\n\nCheck whether the current runtime exposes Wi-Fi interface/address/route signals beyond the bounded lifecycle layer.";
|
||||
|
||||
fn require_field<'a>(value: &'a Value, field: &str) -> Result<&'a Value, String> {
|
||||
value
|
||||
.get(field)
|
||||
.ok_or_else(|| format!("redbear-info --json did not report {field}"))
|
||||
}
|
||||
|
||||
fn present_nonempty(value: &Value) -> bool {
|
||||
value
|
||||
.as_str()
|
||||
.map(|s| !s.trim().is_empty() && s != "unknown")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
parse_args(PROGRAM, USAGE, std::env::args()).map_err(|err| {
|
||||
if err.is_empty() {
|
||||
process::exit(0);
|
||||
}
|
||||
err
|
||||
})?;
|
||||
|
||||
println!("=== Red Bear OS Phase 5 Wi-Fi Link Check ===");
|
||||
let output = Command::new("redbear-info")
|
||||
.arg("--json")
|
||||
.output()
|
||||
.map_err(|err| format!("failed to run redbear-info --json: {err}"))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("redbear-info --json failed: {}", stderr.trim()));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let json: Value = serde_json::from_str(&stdout)
|
||||
.map_err(|err| format!("failed to parse redbear-info --json output: {err}"))?;
|
||||
let network = json
|
||||
.get("network")
|
||||
.ok_or_else(|| "redbear-info --json did not include network section".to_string())?;
|
||||
|
||||
let interface = require_field(network, "interface")?;
|
||||
let address = require_field(network, "address")?;
|
||||
let default_route = require_field(network, "default_route")?;
|
||||
let wifi_connect_result = require_field(network, "wifi_connect_result")?;
|
||||
|
||||
if present_nonempty(interface) {
|
||||
println!("WIFI_INTERFACE=present");
|
||||
} else {
|
||||
return Err("Wi-Fi/network interface is not reported".to_string());
|
||||
}
|
||||
|
||||
if present_nonempty(wifi_connect_result) {
|
||||
println!("WIFI_CONNECT_RESULT=present");
|
||||
} else {
|
||||
return Err("Wi-Fi connect result is not reported".to_string());
|
||||
}
|
||||
|
||||
if present_nonempty(address) {
|
||||
println!("WIFI_ADDRESS=present");
|
||||
} else {
|
||||
println!("WIFI_ADDRESS=missing");
|
||||
}
|
||||
|
||||
if present_nonempty(default_route) {
|
||||
println!("WIFI_DEFAULT_ROUTE=present");
|
||||
} else {
|
||||
println!("WIFI_DEFAULT_ROUTE=missing");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn present_nonempty_rejects_unknown() {
|
||||
assert!(!present_nonempty(&json!("unknown")));
|
||||
assert!(!present_nonempty(&json!("")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn present_nonempty_accepts_value() {
|
||||
assert!(present_nonempty(&json!("wlan0")));
|
||||
assert!(present_nonempty(&json!("10.0.0.44/24")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::process::{self, Command};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase5-wifi-run";
|
||||
const USAGE: &str = "Usage: redbear-phase5-wifi-run [PROFILE] [INTERFACE] [OUTPUT_PATH]\n\nRun the packaged bounded Wi-Fi validator and then emit a JSON capture bundle.";
|
||||
|
||||
fn run_command(program: &str, args: &[&str]) -> Result<String, String> {
|
||||
let output = Command::new(program)
|
||||
.args(args)
|
||||
.output()
|
||||
.map_err(|err| format!("failed to run {} {:?}: {err}", program, args))?;
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if stderr.trim().is_empty() {
|
||||
return Err(format!(
|
||||
"{} {:?} exited with status {}",
|
||||
program, args, output.status
|
||||
));
|
||||
}
|
||||
return Err(format!(
|
||||
"{} {:?} exited with status {}: {}",
|
||||
program,
|
||||
args,
|
||||
output.status,
|
||||
stderr.trim()
|
||||
));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
parse_args(PROGRAM, USAGE, args.clone().into_iter()).map_err(|err| {
|
||||
if err.is_empty() {
|
||||
process::exit(0);
|
||||
}
|
||||
err
|
||||
})?;
|
||||
|
||||
let profile = args
|
||||
.get(1)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("wifi-open-bounded");
|
||||
let iface = args
|
||||
.get(2)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("wlan0");
|
||||
let output_path = args
|
||||
.get(3)
|
||||
.filter(|arg| !arg.starts_with('-'))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("/tmp/redbear-phase5-wifi-capture.json");
|
||||
|
||||
let check = run_command("redbear-phase5-wifi-check", &[profile, iface])?;
|
||||
print!("{check}");
|
||||
|
||||
let link = run_command("redbear-phase5-wifi-link-check", &[])?;
|
||||
print!("{link}");
|
||||
|
||||
let capture = run_command(
|
||||
"redbear-phase5-wifi-capture",
|
||||
&[profile, iface, output_path],
|
||||
)?;
|
||||
print!("{capture}");
|
||||
println!("capture_output={output_path}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn default_capture_path_is_stable() {
|
||||
assert_eq!(
|
||||
"/tmp/redbear-phase5-wifi-capture.json",
|
||||
"/tmp/redbear-phase5-wifi-capture.json"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[source]
|
||||
path = "source"
|
||||
|
||||
[build]
|
||||
template = "cargo"
|
||||
|
||||
[package.files]
|
||||
"/usr/bin/redbear-wifictl" = "redbear-wifictl"
|
||||
"/usr/bin/wifictl" = "redbear-wifictl"
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "redbear-wifictl"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-wifictl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
libredox = { version = "0.1", features = ["call", "std"] }
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
redox-scheme = "0.11"
|
||||
syscall = { package = "redox_syscall", version = "0.7", features = ["std"] }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox-driver-sys = { path = "../../../drivers/redox-driver-sys/source", features = ["redox"] }
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
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
|
||||
}
|
||||
|
||||
fn write_intel_candidate(pci_root: &PathBuf) {
|
||||
let slot = pci_root.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[0x10] = 0x01;
|
||||
cfg[0x2E] = 0x90;
|
||||
cfg[0x2F] = 0x40;
|
||||
cfg[0x3D] = 0x01;
|
||||
fs::write(slot.join("config"), cfg).unwrap();
|
||||
}
|
||||
|
||||
fn write_mock_driver(path: &PathBuf) {
|
||||
fs::write(
|
||||
path,
|
||||
r##"#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
case "${1:-}" in
|
||||
--transport-probe)
|
||||
printf 'transport_status=transport=cli-probe-path\n'
|
||||
;;
|
||||
--init-transport)
|
||||
printf 'transport_init_status=transport_init=cli-init-path\n'
|
||||
printf 'transport_status=transport=cli-init-path\n'
|
||||
;;
|
||||
--prepare)
|
||||
printf 'status=firmware-ready\n'
|
||||
printf 'transport_status=transport=prepared\n'
|
||||
;;
|
||||
--activate-nic)
|
||||
printf 'activation=ok\n'
|
||||
printf 'transport_status=transport=active\n'
|
||||
;;
|
||||
--connect)
|
||||
printf 'status=associated\n'
|
||||
printf 'connect_result=cli-associated ssid=%s security=%s\n' "${3:-}" "${4:-}"
|
||||
;;
|
||||
--disconnect)
|
||||
printf 'status=device-detected\n'
|
||||
printf 'disconnect_result=cli-disconnected\n'
|
||||
;;
|
||||
*)
|
||||
printf 'status=unexpected-action\n'
|
||||
;;
|
||||
esac
|
||||
"##,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(path).unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(path, perms).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn run_wifictl(args: &[&str], pci_root: &PathBuf, fw_root: &PathBuf, driver: &PathBuf) -> String {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_redbear-wifictl"))
|
||||
.args(args)
|
||||
.env("REDBEAR_WIFICTL_BACKEND", "intel")
|
||||
.env("REDBEAR_WIFICTL_PCI_ROOT", pci_root)
|
||||
.env("REDBEAR_WIFICTL_FIRMWARE_ROOT", fw_root)
|
||||
.env("REDBEAR_IWLWIFI_CMD", driver)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"command {:?} failed: {}",
|
||||
args,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
String::from_utf8(output.stdout).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_transport_probe_uses_probe_path() {
|
||||
let pci = temp_root("rbos-wifictl-cli-pci");
|
||||
let fw = temp_root("rbos-wifictl-cli-fw");
|
||||
write_intel_candidate(&pci);
|
||||
fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap();
|
||||
fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap();
|
||||
|
||||
let driver = temp_root("rbos-wifictl-cli-driver").join("redbear-iwlwifi-mock.sh");
|
||||
write_mock_driver(&driver);
|
||||
|
||||
let probe = run_wifictl(&["--transport-probe", "wlan0"], &pci, &fw, &driver);
|
||||
assert!(probe.contains("transport_status=transport=cli-probe-path"));
|
||||
assert!(!probe.contains("cli-init-path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_connect_reports_driver_status_honestly() {
|
||||
let pci = temp_root("rbos-wifictl-cli-pci-connect");
|
||||
let fw = temp_root("rbos-wifictl-cli-fw-connect");
|
||||
write_intel_candidate(&pci);
|
||||
fs::write(fw.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap();
|
||||
fs::write(fw.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap();
|
||||
|
||||
let driver = temp_root("rbos-wifictl-cli-driver-connect").join("redbear-iwlwifi-mock.sh");
|
||||
write_mock_driver(&driver);
|
||||
|
||||
let connect = run_wifictl(
|
||||
&["--connect", "wlan0", "demo", "wpa2-psk", "secret"],
|
||||
&pci,
|
||||
&fw,
|
||||
&driver,
|
||||
);
|
||||
assert!(connect.contains("status=connected"));
|
||||
assert!(connect.contains("transport_status=transport=active"));
|
||||
|
||||
let pending_driver =
|
||||
temp_root("rbos-wifictl-cli-driver-pending").join("redbear-iwlwifi-pending.sh");
|
||||
fs::write(
|
||||
&pending_driver,
|
||||
r##"#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
case "${1:-}" in
|
||||
--prepare)
|
||||
printf 'status=firmware-ready\n'
|
||||
;;
|
||||
--activate-nic)
|
||||
printf 'activation=ok\n'
|
||||
;;
|
||||
--connect)
|
||||
printf 'status=associating\n'
|
||||
printf 'connect_result=host-bounded-pending ssid=%s security=%s\n' "${3:-}" "${4:-}"
|
||||
;;
|
||||
--disconnect)
|
||||
printf 'status=device-detected\n'
|
||||
printf 'disconnect_result=cli-disconnected\n'
|
||||
;;
|
||||
*)
|
||||
printf 'status=device-detected\n'
|
||||
;;
|
||||
esac
|
||||
"##,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&pending_driver).unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&pending_driver, perms).unwrap();
|
||||
}
|
||||
|
||||
let pending = run_wifictl(
|
||||
&["--connect", "wlan0", "demo", "wpa2-psk", "secret"],
|
||||
&pci,
|
||||
&fw,
|
||||
&pending_driver,
|
||||
);
|
||||
assert!(pending.contains("status=associating"));
|
||||
assert!(pending.contains("connect_result=host-bounded-pending"));
|
||||
|
||||
let disconnect = run_wifictl(&["--disconnect", "wlan0"], &pci, &fw, &driver);
|
||||
assert!(disconnect.contains("status=device-detected"));
|
||||
assert!(disconnect.contains("disconnect_result=cli-disconnected"));
|
||||
}
|
||||
Reference in New Issue
Block a user