89350ed795
Phase 3 of the systematic networking plan.
The bridge lives entirely in the redbear-iwlwifi recipe. It
exposes a network.wlan0 scheme on top of the existing iwlwifi
control plane, so netstack treats it as a normal Ethernet
device without any change to netstack itself.
Components (all in local/recipes/drivers/redbear-iwlwifi):
- src/bridge/mod.rs (15 KB): WifiLinkBridge struct, RX/TX
state, BSSID, mac state, stats, associated flag. All
state behind Arc<Mutex<>> for safe sharing with the
scheme handler thread.
- src/bridge/convert.rs (26 KB): wifi_to_ethernet() and
ethernet_to_wifi() pure functions. All four ToDS/FromDS
addressing modes, full LLC/SNAP detection (handles
both AA-AA-03-00-00-00 framing and the Linux 4-2
stripped form), and a complete round-trip test
suite.
- src/bridge/callback.rs (11 KB): the unsafe extern C
callback that ieee80211_rx_drain calls. Drops
kernel-injected management frames and passes
filtered data frames through convert.rs.
- src/bridge/scheme.rs (16 KB): the Redox scheme handler.
Registers network.wlan0 with read/write/handles.
Read drains the bridge RX queue; write calls
ethernet_to_wifi then iwl_ops_tx_skb.
linux_port.c additions:
- rb_iwlwifi_bridge_register_rx(hw) is invoked from
rb_iwlwifi_register_mac80211_locked after
ieee80211_register_hw, registering bridge_rx_callback
as the RX handler.
- rb_iwlwifi_bridge_tx_submit(data, len) wraps a frame
in an sk_buff and calls iwl_ops_tx_skb.
- rb_iwlwifi_bridge_hw keeps a single static
ieee80211_hw* for the callback dispatch.
main.rs changes:
- The --daemon path now initializes the bridge after
full_init, hands it to the bridge module, and runs
bridge::scheme::run_event_loop. The previous
'loop { sleep(3600); }' is gone.
Verification contract built into the bridge modules:
- convert.rs: all 4 ToDS/FromDS modes, LLC/SNAP
presence/absence, IPv4/IPv6/ARP payloads, round-trip
preservation.
- mod.rs: push/pop/activate/deactivate state machine.
- scheme.rs: scheme read/write handshake with mock
driver backend.
Netstack impact: zero. The netcfg scheme already
discovers network.* and creates EthernetLink on
top; wlan0 looks identical to netstack.
NOT yet validated on real hardware (Phase 6 deferred
to hardware acquisition). Hardware validation will
require a real Intel BE201/BE200 NIC and an AP with
known credentials.
256 lines
8.6 KiB
Rust
256 lines
8.6 KiB
Rust
use std::collections::HashMap;
|
|
use std::io;
|
|
|
|
use redox_scheme::{CallerCtx, OpenResult};
|
|
use redox_scheme::scheme::SchemeSync;
|
|
use syscall::data::Stat;
|
|
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
|
|
use syscall::flag::{MODE_DIR, MODE_FILE};
|
|
use syscall::schemev2::NewFdFlags;
|
|
|
|
use crate::keymap::Keymap;
|
|
|
|
#[derive(Clone)]
|
|
enum HandleKind {
|
|
Root,
|
|
Active,
|
|
List,
|
|
Keymap { name: String },
|
|
}
|
|
|
|
struct Handle {
|
|
kind: HandleKind,
|
|
offset: usize,
|
|
}
|
|
|
|
pub struct KeymapScheme {
|
|
next_id: usize,
|
|
handles: HashMap<usize, Handle>,
|
|
keymaps: HashMap<String, Keymap>,
|
|
active_keymap: String,
|
|
}
|
|
|
|
impl KeymapScheme {
|
|
pub fn new() -> Self {
|
|
KeymapScheme {
|
|
next_id: 0,
|
|
handles: HashMap::new(),
|
|
keymaps: HashMap::new(),
|
|
active_keymap: "us".to_string(),
|
|
}
|
|
}
|
|
|
|
pub fn load_builtin(&mut self, builtins: &crate::keymap::BuiltinKeymaps) {
|
|
for (name, km) in [
|
|
("us", &builtins.us),
|
|
("gb", &builtins.gb),
|
|
("dvorak", &builtins.dvorak),
|
|
("azerty", &builtins.azerty),
|
|
("bepo", &builtins.bepo),
|
|
("it", &builtins.it),
|
|
] {
|
|
self.keymaps.insert(name.to_string(), km.clone());
|
|
}
|
|
}
|
|
|
|
pub fn load_from_dir(&mut self, dir: &str) -> io::Result<()> {
|
|
let entries = std::fs::read_dir(dir)?;
|
|
for entry in entries {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
if path.extension().map_or(false, |e| e == "json") {
|
|
let name = path
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.unwrap_or("unknown")
|
|
.to_string();
|
|
let json_str = std::fs::read_to_string(&path)?;
|
|
if let Ok(km) = Keymap::from_json(&name, &json_str) {
|
|
self.keymaps.insert(name, km);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn load_xkb(
|
|
&mut self,
|
|
xkb_dir: &str,
|
|
layout: &str,
|
|
variant: Option<&str>,
|
|
) -> io::Result<()> {
|
|
let km = crate::xkb::load_xkb_keymap(xkb_dir, layout, variant)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
let name = match variant {
|
|
Some(v) => format!("{}({})", layout, v),
|
|
None => layout.to_string(),
|
|
};
|
|
self.keymaps.insert(name, km);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn keymap_count(&self) -> usize {
|
|
self.keymaps.len()
|
|
}
|
|
|
|
fn active_keymap(&self) -> &Keymap {
|
|
self.keymaps
|
|
.get(&self.active_keymap)
|
|
.or_else(|| self.keymaps.get("us"))
|
|
.expect("at least one keymap must be loaded")
|
|
}
|
|
|
|
pub fn translate(&self, scancode: u8, shift: bool, altgr: bool) -> char {
|
|
self.active_keymap().get_char(scancode, shift, altgr)
|
|
}
|
|
}
|
|
|
|
impl SchemeSync for KeymapScheme {
|
|
fn scheme_root(&mut self) -> Result<usize> {
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
self.handles.insert(id, Handle { kind: HandleKind::Root, offset: 0 });
|
|
Ok(id)
|
|
}
|
|
|
|
fn openat(&mut self, _dirfd: usize, path: &str, _flags: usize, _fcntl_flags: u32, _ctx: &CallerCtx) -> Result<OpenResult> {
|
|
let cleaned = path.trim_matches('/');
|
|
|
|
let kind = if cleaned.is_empty() {
|
|
HandleKind::Root
|
|
} else if cleaned == "active" {
|
|
HandleKind::Active
|
|
} else if cleaned == "list" {
|
|
HandleKind::List
|
|
} else if let Some(name) = cleaned.strip_prefix("keymap/") {
|
|
let name = name.trim_end_matches('/').to_string();
|
|
if !self.keymaps.contains_key(&name) {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
HandleKind::Keymap { name }
|
|
} else if self.keymaps.contains_key(cleaned) {
|
|
// Existence is already validated by contains_key above; the keymap
|
|
// content is materialized lazily in read() via keymaps.get().
|
|
let name = cleaned.to_string();
|
|
HandleKind::Keymap { name }
|
|
} else if cleaned.starts_with("set/") {
|
|
let requested = &cleaned[4..];
|
|
if !self.keymaps.contains_key(requested) {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
self.active_keymap = requested.to_string();
|
|
HandleKind::Active
|
|
} else {
|
|
return Err(Error::new(ENOENT));
|
|
};
|
|
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
self.handles.insert(id, Handle { kind, offset: 0 });
|
|
Ok(OpenResult::ThisScheme { number: id, flags: NewFdFlags::empty() })
|
|
}
|
|
|
|
fn read(&mut self, id: usize, buf: &mut [u8], offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx) -> Result<usize> {
|
|
let kind = { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; handle.kind.clone() };
|
|
|
|
let content: Vec<u8> = match &kind {
|
|
HandleKind::Root => {
|
|
let mut listing = String::new();
|
|
listing.push_str("active\nlist\n");
|
|
for name in self.keymaps.keys() {
|
|
listing.push_str(&format!("keymap/{}\n", name));
|
|
}
|
|
listing.into_bytes()
|
|
}
|
|
HandleKind::Active => format!(
|
|
"name={}\nsample_scancode_30={}\n",
|
|
self.active_keymap,
|
|
self.translate(0x1E, false, false),
|
|
)
|
|
.into_bytes(),
|
|
HandleKind::List => {
|
|
let mut listing = String::new();
|
|
for (i, name) in self.keymaps.keys().enumerate() {
|
|
if i > 0 {
|
|
listing.push('\n');
|
|
}
|
|
listing.push_str(name);
|
|
}
|
|
listing.push('\n');
|
|
listing.into_bytes()
|
|
}
|
|
HandleKind::Keymap { name } => {
|
|
let km = self.keymaps.get(name).ok_or(Error::new(ENOENT))?;
|
|
format!(
|
|
"name={}\nentries={}\ncompose={}\ndead_keys={}\nsample_scancode_30={}\nsample_altgr_scancode_30={}\nsample_compose_a_acute={}\nsample_compose_sequence={}\n",
|
|
km.name,
|
|
km.entries.len(),
|
|
km.compose.len(),
|
|
km.dead_keys.len(),
|
|
km.get_char(0x1E, false, false),
|
|
km.get_char(0x1E, false, true),
|
|
km.compose('a', '\''),
|
|
km.lookup_compose("compose").unwrap_or('\0'),
|
|
)
|
|
.into_bytes()
|
|
}
|
|
};
|
|
|
|
let _handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
|
let offset = offset as usize;
|
|
if offset >= content.len() {
|
|
return Ok(0);
|
|
}
|
|
let remaining = &content[offset..];
|
|
let to_copy = remaining.len().min(buf.len());
|
|
buf[..to_copy].copy_from_slice(&remaining[..to_copy]);
|
|
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
|
handle.offset = offset + to_copy;
|
|
Ok(to_copy)
|
|
}
|
|
|
|
fn write(&mut self, id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx) -> Result<usize> {
|
|
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
|
match &handle.kind {
|
|
HandleKind::Active => {
|
|
let name = String::from_utf8_lossy(buf);
|
|
let name = name.trim();
|
|
if self.keymaps.contains_key(name) {
|
|
self.active_keymap = name.to_string();
|
|
Ok(buf.len())
|
|
} else {
|
|
Err(Error::new(ENOENT))
|
|
}
|
|
}
|
|
_ => Err(Error::new(EINVAL)),
|
|
}
|
|
}
|
|
|
|
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
|
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
|
match &handle.kind {
|
|
HandleKind::Root => {
|
|
stat.st_mode = MODE_DIR | 0o555;
|
|
}
|
|
_ => {
|
|
stat.st_mode = MODE_FILE | 0o644;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
|
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
|
let path = match &handle.kind {
|
|
HandleKind::Root => "keymap:".to_string(),
|
|
HandleKind::Active => "keymap:active".to_string(),
|
|
HandleKind::List => "keymap:list".to_string(),
|
|
HandleKind::Keymap { name } => format!("keymap:keymap/{}", name),
|
|
};
|
|
let bytes = path.as_bytes();
|
|
let to_copy = bytes.len().min(buf.len());
|
|
buf[..to_copy].copy_from_slice(&bytes[..to_copy]);
|
|
Ok(to_copy)
|
|
}
|
|
}
|