redbear-iwlwifi: port network.wlan0 scheme to redox-scheme SchemeSync
bridge/scheme.rs was a legacy redox_syscall Packet-based scheme server (syscall::Packet / syscall::open / SYS_OPEN / EVENT_READ-from-libredox) whose ABI no longer exists -> the driver failed to compile. Rewrote the redox path onto the current redox-scheme SchemeSync API, mirroring the canonical NIC scheme (base drivers/net/driver-network) so smolnetd's network.* consumer works unchanged: root dir; open "" -> Data (raw eth frames), open "mac" -> 6-byte positioned MAC; read pops one RX frame (EAGAIN when empty), write does eth->802.11->C TX submit; blocked readers woken via post_fevent(EVENT_READ). Kept the self-contained non-blocking 1ms poll loop (also pumps bridge TX). Also made the two extern "C" blocks (callback.rs, scheme.rs FFI) for Rust 2024, and added redox-scheme as a redox-target dep. Compile-checked; not yet hardware-validated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1855,3 +1855,67 @@ Config comparison:
|
||||
## ANTI-PATTERNS (COMMIT POLICY)
|
||||
|
||||
- **DO NOT** include AI attribution in commit messages — no "Ultraworked with [Sisyphus]", "Co-authored-by: Sisyphus", or similar AI agent footers. Commits belong to the human author only.
|
||||
|
||||
## LIBRARY-ONLY RED BEAR CRATES
|
||||
|
||||
The following Red Bear crates exist as **library-only dependencies** — they provide shared
|
||||
types, protocols, or utility code consumed by other Red Bear daemons and binaries. They are
|
||||
**not recipes in any config TOML**, have no init service, and are never cooked as standalone
|
||||
packages by the build system. They are consumed exclusively via Cargo `path` dependencies.
|
||||
|
||||
| Crate | Location | Purpose | Consumers |
|
||||
|-------|----------|---------|-----------|
|
||||
| `redbear-hid-core` | `local/recipes/system/redbear-hid-core/source/` | Shared HID protocol types, report descriptors, and device classification enums | `redbear-inputd`, `redbear-bthid`, future HID daemons |
|
||||
| `redbear-login-protocol` | `local/recipes/system/redbear-login-protocol/source/` | Shared types and wire format for the greeter-auth-session handshake: login request/response, session token, PAM conversation messages | `redbear-greeter`, `redbear-authd`, `redbear-session-launch` |
|
||||
| `redbear-tui-theme` | `local/recipes/system/redbear-tui-theme/source/` | (When wired as lib): shared ratatui color palettes, text styles, and layout constants for consistent TUI theming | `cub`, `tlc`, `redbear-power`, other ratatui-based TUI tools |
|
||||
| `redbear-passwd` | `local/recipes/system/redbear-passwd/source/` | `passwd` file parsing, user/group lookup, shadow entry validation, and credential verification types | `redbear-authd`, `redbear-polkit`, `redbear-greeter`, `redbear-session-launch` |
|
||||
|
||||
### Consumption Pattern
|
||||
|
||||
These crates are used via **path dependencies** in other Red Bear daemon Cargo.toml files:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
redbear-hid-core = { path = "../../../system/redbear-hid-core/source" }
|
||||
redbear-login-protocol = { path = "../../../system/redbear-login-protocol/source" }
|
||||
redbear-passwd = { path = "../../../system/redbear-passwd/source" }
|
||||
```
|
||||
|
||||
The path is relative to the consuming crate's `Cargo.toml` location.
|
||||
|
||||
### Why Library-Only
|
||||
|
||||
These crates are **intentionally excluded from all config TOMLs** (`redbear-full.toml`,
|
||||
`redbear-mini.toml`, `redbear-grub.toml`) and have no `[package]` / `installs` in their
|
||||
recipe. Reasons:
|
||||
|
||||
1. **No standalone binary** — They produce only a `lib` target, never a `bin`.
|
||||
2. **No init service** — They are not daemons and do not register a scheme or D-Bus name.
|
||||
3. **Config presence would be misleading** — Including them as a package suggests they
|
||||
contribute runtime artifacts to the image, which they do not.
|
||||
4. **Linking is handled by the consumer** — The consumer daemon's `Cargo.toml` pulls them
|
||||
in; the build system compiles them transitively during the consumer's `repo cook`.
|
||||
|
||||
### Host Testing
|
||||
|
||||
Since these are pure-logic crates with no Redox-specific syscalls (no `libredox`, no
|
||||
`redox_syscall` dep, no scheme I/O), their unit tests are **host-runnable**:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path local/recipes/system/redbear-hid-core/source/Cargo.toml
|
||||
cargo test --manifest-path local/recipes/system/redbear-login-protocol/source/Cargo.toml
|
||||
cargo test --manifest-path local/recipes/system/redbear-passwd/source/Cargo.toml
|
||||
```
|
||||
|
||||
They are included in the CI `cargo test host` step.
|
||||
|
||||
### Finding All Library-Only Crates
|
||||
|
||||
```bash
|
||||
# List crates not in any config TOML
|
||||
for crate in redbear-hid-core redbear-login-protocol redbear-passwd; do
|
||||
if ! grep -rq "\"$crate\"" config/redbear-*.toml; then
|
||||
echo "$crate: library-only (not in config)"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
@@ -21,6 +21,7 @@ redox-driver-sys = { path = "../../redox-driver-sys/source", features = ["redox"
|
||||
pcid_interface = { path = "../../../../sources/base/drivers/pcid", package = "pcid" }
|
||||
libredox = { path = "../../../../../local/sources/libredox" }
|
||||
syscall = { package = "redox_syscall", path = "../../../../../local/sources/syscall", features = ["std"] }
|
||||
redox-scheme = { path = "../../../../../local/sources/redox-scheme" }
|
||||
|
||||
[patch.crates-io]
|
||||
redox-driver-sys = { path = "../../redox-driver-sys/source" }
|
||||
|
||||
@@ -44,7 +44,7 @@ pub struct SkBuff {
|
||||
}
|
||||
|
||||
// Forward declaration of kfree_skb from linux-kpi
|
||||
extern "C" {
|
||||
unsafe extern "C" {
|
||||
fn kfree_skb(skb: *mut SkBuff);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ struct HandleMap {
|
||||
|
||||
impl HandleMap {
|
||||
fn new() -> Self {
|
||||
Self { next: 0, map: std::collections::HashMap::new() }
|
||||
// Start handle ids at 1; 0 is avoided so a zero-initialised id is never
|
||||
// mistaken for a valid handle.
|
||||
Self { next: 1, map: std::collections::HashMap::new() }
|
||||
}
|
||||
|
||||
fn insert(&mut self, kind: HandleKind) -> usize {
|
||||
@@ -77,158 +79,135 @@ pub fn run_event_loop(
|
||||
}
|
||||
|
||||
// —— Redox implementation ——————————————————————————————————————
|
||||
//
|
||||
// Ported from the legacy redox_syscall Packet-based scheme to the current
|
||||
// redox-scheme SchemeSync API. The legacy `syscall::Packet` / `syscall::open`
|
||||
// / `SYS_OPEN` scheme-server ABI no longer exists; this mirrors the canonical
|
||||
// NIC scheme (base drivers/net/driver-network) so smolnetd's `network.*`
|
||||
// consumer works unchanged:
|
||||
// * the scheme root is a directory;
|
||||
// * opening "" yields a Data handle for raw Ethernet frame read/write;
|
||||
// * opening "mac" yields a 6-byte, positioned MAC-address handle.
|
||||
// Read on Data pops one RX frame (EAGAIN when empty on a non-blocking handle);
|
||||
// write on Data converts Ethernet -> 802.11 and submits via the C TX path.
|
||||
// Blocked readers are woken with post_fevent(EVENT_READ) whenever RX frames
|
||||
// become available. The daemon uses a non-blocking socket and a 1 ms poll loop
|
||||
// (matching the legacy behaviour) so it can also pump the bridge TX path and
|
||||
// post readiness without a dedicated event fd.
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
mod inner {
|
||||
use super::*;
|
||||
use libredox::flag::{EVENT_READ, O_NONBLOCK};
|
||||
use std::cmp;
|
||||
use super::{ethernet_to_wifi, HandleKind, HandleMap, WifiLinkBridge};
|
||||
use std::io;
|
||||
use syscall::flag;
|
||||
use syscall::{
|
||||
Error, Result as SysResult, EBADF, EAGAIN, EINVAL, EWOULDBLOCK, ERANGE, MODE_FILE, Packet,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct SchemeInner {
|
||||
use redox_scheme::scheme::{register_sync_scheme, SchemeState, SchemeSync};
|
||||
use redox_scheme::{
|
||||
CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket,
|
||||
};
|
||||
use syscall::error::{
|
||||
Error, Result as SysResult, EACCES, EAGAIN, EBADF, EINVAL, EIO, EWOULDBLOCK,
|
||||
};
|
||||
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_NONBLOCK};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::Stat;
|
||||
|
||||
// FFI: TX submission — called to send a Wi-Fi frame down the C driver path.
|
||||
unsafe extern "C" {
|
||||
fn rb_iwlwifi_bridge_tx_submit(data: *const u8, len: usize) -> i32;
|
||||
}
|
||||
|
||||
pub struct WlanScheme {
|
||||
name: String,
|
||||
handles: HandleMap,
|
||||
fd: usize,
|
||||
bridge: Arc<Mutex<WifiLinkBridge>>,
|
||||
mac: [u8; 6],
|
||||
bssid: [u8; 6],
|
||||
}
|
||||
|
||||
const SYS_OPEN: usize = syscall::number::SYS_OPEN;
|
||||
const SYS_READ: usize = syscall::number::SYS_READ;
|
||||
const SYS_WRITE: usize = syscall::number::SYS_WRITE;
|
||||
const SYS_FPATH: usize = syscall::number::SYS_FPATH;
|
||||
const SYS_FSTAT: usize = syscall::number::SYS_FSTAT;
|
||||
const SYS_FSYNC: usize = syscall::number::SYS_FSYNC;
|
||||
const SYS_CLOSE: usize = syscall::number::SYS_CLOSE;
|
||||
const SYS_FEVENT: usize = syscall::number::SYS_FEVENT;
|
||||
|
||||
impl SchemeInner {
|
||||
impl WlanScheme {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
bridge: Arc<Mutex<WifiLinkBridge>>,
|
||||
mac: [u8; 6],
|
||||
bssid: [u8; 6],
|
||||
) -> io::Result<Self> {
|
||||
let path = format!(":{}", name);
|
||||
let fd = syscall::open(&path, flag::O_RDWR | flag::O_NONBLOCK | flag::O_CREAT)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("scheme socket {}: {:?}", path, e)))?;
|
||||
|
||||
log::info!("bridge::scheme: registered {}", name);
|
||||
|
||||
Ok(Self {
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
handles: HandleMap::new(),
|
||||
fd,
|
||||
bridge,
|
||||
mac,
|
||||
bssid,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fd(&self) -> usize { self.fd }
|
||||
|
||||
pub fn tick(&mut self) -> io::Result<bool> {
|
||||
let mut pkt = Packet::default();
|
||||
match syscall::read(self.fd, &mut pkt) {
|
||||
Ok(_) => { self.dispatch(pkt)?; Ok(true) }
|
||||
Err(err) if err.errno == syscall::EAGAIN => Ok(false),
|
||||
Err(err) => Err(io::Error::from_raw_os_error(err.errno)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_readers(&self) {
|
||||
for id in self.handles.data_handles() {
|
||||
let p = Packet {
|
||||
id: 0,
|
||||
a: SYS_FEVENT,
|
||||
b: id,
|
||||
c: EVENT_READ.bits(),
|
||||
d: 0,
|
||||
};
|
||||
let _ = syscall::write(self.fd, &p);
|
||||
}
|
||||
fn kind(&self, id: usize) -> SysResult<&HandleKind> {
|
||||
self.handles.get(id).ok_or(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn dispatch(&mut self, pkt: Packet) -> io::Result<()> {
|
||||
let result = match pkt.a {
|
||||
SYS_OPEN => self.do_open(&pkt),
|
||||
SYS_READ => self.do_read(&pkt),
|
||||
SYS_WRITE => self.do_write(&pkt),
|
||||
SYS_FPATH => self.do_fpath(&pkt),
|
||||
SYS_FSTAT => self.do_fstat(&pkt),
|
||||
SYS_FSYNC => self.do_fsync(&pkt),
|
||||
SYS_CLOSE => { self.handles.remove(pkt.b); Ok(None) }
|
||||
_ => Err(Error::new(syscall::ENOSYS)),
|
||||
/// Ids of all currently-open Data handles, for readiness notification.
|
||||
pub fn data_handle_ids(&self) -> Vec<usize> {
|
||||
self.handles.data_handles()
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for WlanScheme {
|
||||
fn scheme_root(&mut self) -> SysResult<usize> {
|
||||
Ok(self.handles.insert(HandleKind::Root))
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> SysResult<OpenResult> {
|
||||
if !matches!(self.kind(dirfd)?, HandleKind::Root) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
if ctx.uid != 0 {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
let (kind, flags) = match path.trim_matches('/') {
|
||||
"" => (HandleKind::Data, NewFdFlags::empty()),
|
||||
"mac" => (HandleKind::Mac, NewFdFlags::POSITIONED),
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
|
||||
let id = pkt.id;
|
||||
match result {
|
||||
Ok(Some(mut r)) => { r.id = id; syscall::write(self.fd, &r).ok(); }
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
let mut r = Packet::default();
|
||||
r.id = id;
|
||||
r.a = err.errno;
|
||||
if err.errno != EAGAIN && err.errno != EWOULDBLOCK {
|
||||
syscall::write(self.fd, &r).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
let number = self.handles.insert(kind);
|
||||
Ok(OpenResult::ThisScheme { number, flags })
|
||||
}
|
||||
|
||||
fn do_open(&mut self, _pkt: &Packet) -> SysResult<Option<Packet>> {
|
||||
let id = self.handles.insert(HandleKind::Data);
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
r.b = id;
|
||||
Ok(Some(r))
|
||||
}
|
||||
|
||||
fn do_read(&mut self, pkt: &Packet) -> SysResult<Option<Packet>> {
|
||||
let hid = pkt.b;
|
||||
let max = pkt.c;
|
||||
let fl = pkt.d as u32;
|
||||
let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?;
|
||||
|
||||
match kind {
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
offset: u64,
|
||||
fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> SysResult<usize> {
|
||||
match self.kind(id)? {
|
||||
HandleKind::Mac => {
|
||||
let off = pkt.d as usize;
|
||||
let off = offset as usize;
|
||||
let mac = self.mac;
|
||||
if off >= 6 {
|
||||
return Err(Error::new(ERANGE));
|
||||
if off >= mac.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let n = cmp::min(max, 6 - off);
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
r.b = n;
|
||||
// SAFETY: caller must verify the safety contract for this operation
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(mac[off..].as_ptr(), &mut r.c as *mut usize as *mut u8, n);
|
||||
}
|
||||
Ok(Some(r))
|
||||
let n = core::cmp::min(buf.len(), mac.len() - off);
|
||||
buf[..n].copy_from_slice(&mac[off..off + n]);
|
||||
Ok(n)
|
||||
}
|
||||
HandleKind::Data => {
|
||||
let mut bridge = self.bridge.lock().unwrap();
|
||||
match bridge.pop_rx() {
|
||||
let frame = self.bridge.lock().unwrap().pop_rx();
|
||||
match frame {
|
||||
Some(frame) => {
|
||||
let n = cmp::min(frame.len(), max);
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
r.b = n;
|
||||
// SAFETY: caller must verify the safety contract for this operation
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(frame.as_ptr(), &mut r.c as *mut usize as *mut u8, n);
|
||||
}
|
||||
Ok(Some(r))
|
||||
let n = core::cmp::min(frame.len(), buf.len());
|
||||
buf[..n].copy_from_slice(&frame[..n]);
|
||||
Ok(n)
|
||||
}
|
||||
None => {
|
||||
if fl & O_NONBLOCK as u32 != 0 {
|
||||
if fcntl_flags & O_NONBLOCK as u32 != 0 {
|
||||
Err(Error::new(EAGAIN))
|
||||
} else {
|
||||
Err(Error::new(EWOULDBLOCK))
|
||||
@@ -236,84 +215,180 @@ mod inner {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(Error::new(EBADF)),
|
||||
HandleKind::Root => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_write(&mut self, pkt: &Packet) -> SysResult<Option<Packet>> {
|
||||
let hid = pkt.b;
|
||||
let len = pkt.c;
|
||||
let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?;
|
||||
|
||||
if !matches!(kind, HandleKind::Data) {
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> SysResult<usize> {
|
||||
if !matches!(self.kind(id)?, HandleKind::Data) {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
// Extract data from the packet's inline area
|
||||
let mut buf = vec![0u8; cmp::min(len, std::mem::size_of::<usize>() * 4)];
|
||||
unsafe {
|
||||
let src = &pkt.c as *const usize as *const u8;
|
||||
std::ptr::copy_nonoverlapping(src, buf.as_mut_ptr(), buf.len());
|
||||
}
|
||||
buf.truncate(len);
|
||||
|
||||
// Convert Ethernet → 802.11 and submit
|
||||
let bssid = self.bssid;
|
||||
let mac = self.mac;
|
||||
if let Some(wifi) = ethernet_to_wifi(&buf, &bssid, &mac) {
|
||||
// Try submitting via C TX path
|
||||
let submitted = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) };
|
||||
if submitted != 0 {
|
||||
self.bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
return Err(Error::new(syscall::EIO));
|
||||
match ethernet_to_wifi(buf, &bssid, &mac) {
|
||||
Some(wifi) => {
|
||||
// SAFETY: `wifi` is a live, contiguous byte buffer; the C
|
||||
// side only reads `len` bytes from `data` and does not
|
||||
// retain the pointer past the call.
|
||||
let rc = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) };
|
||||
if rc != 0 {
|
||||
self.bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
self.bridge.lock().unwrap().stats.inc_tx_frame(buf.len());
|
||||
Ok(buf.len())
|
||||
}
|
||||
self.bridge.lock().unwrap().stats.inc_tx_frame(buf.len());
|
||||
None => {
|
||||
self.bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
r.b = len;
|
||||
Ok(Some(r))
|
||||
fn fevent(
|
||||
&mut self,
|
||||
id: usize,
|
||||
_flags: EventFlags,
|
||||
_ctx: &CallerCtx,
|
||||
) -> SysResult<EventFlags> {
|
||||
let _ = self.kind(id)?;
|
||||
Ok(EventFlags::empty())
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> SysResult<usize> {
|
||||
let leaf = match self.kind(id)? {
|
||||
HandleKind::Root | HandleKind::Data => "",
|
||||
HandleKind::Mac => "mac",
|
||||
};
|
||||
let path = if leaf.is_empty() {
|
||||
format!("{}:", self.name)
|
||||
} else {
|
||||
self.bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
format!("{}:{}", self.name, leaf)
|
||||
};
|
||||
let bytes = path.as_bytes();
|
||||
let n = core::cmp::min(buf.len(), bytes.len());
|
||||
buf[..n].copy_from_slice(&bytes[..n]);
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn do_fpath(&mut self, _pkt: &Packet) -> SysResult<Option<Packet>> // SAFETY: caller must verify the safety contract for this operation
|
||||
{
|
||||
let name = self.name.clone();
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
r.b = name.len();
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(name.as_ptr(), &mut r.c as *mut usize as *mut u8, name.len());
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> SysResult<()> {
|
||||
match self.kind(id)? {
|
||||
HandleKind::Root => {
|
||||
stat.st_mode = MODE_DIR | 0o500;
|
||||
}
|
||||
HandleKind::Data => {
|
||||
stat.st_mode = MODE_FILE | 0o700;
|
||||
}
|
||||
HandleKind::Mac => {
|
||||
stat.st_mode = MODE_FILE | 0o400;
|
||||
stat.st_size = 6;
|
||||
}
|
||||
}
|
||||
Ok(Some(r))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_fstat(&mut self, pkt: &Packet) -> SysResult<Option<Packet>> {
|
||||
let hid = pkt.b;
|
||||
let kind = self.handles.get(hid).ok_or(Error::new(EBADF))?;
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
match kind {
|
||||
HandleKind::Data => { r.c = MODE_FILE | 0o700; }
|
||||
HandleKind::Mac => { r.c = MODE_FILE | 0o400; r.d = 6; }
|
||||
HandleKind::Root => { r.c = MODE_FILE | 0o500; }
|
||||
}
|
||||
Ok(Some(r))
|
||||
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> SysResult<()> {
|
||||
let _ = self.kind(id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_fsync(&mut self, _pkt: &Packet) -> SysResult<Option<Packet>> {
|
||||
let mut r = Packet::default();
|
||||
r.a = 0;
|
||||
Ok(Some(r))
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
// FFI: TX submission — called by bridge to send a Wi-Fi frame
|
||||
extern "C" {
|
||||
fn rb_iwlwifi_bridge_tx_submit(data: *const u8, len: usize) -> i32;
|
||||
/// Register the `network.<name>` scheme and service it until the socket
|
||||
/// closes. Non-blocking: also pumps the bridge TX path and posts read
|
||||
/// readiness on each poll tick.
|
||||
pub fn run(
|
||||
scheme_name: &str,
|
||||
bridge: Arc<Mutex<WifiLinkBridge>>,
|
||||
mac: [u8; 6],
|
||||
bssid: [u8; 6],
|
||||
) -> io::Result<()> {
|
||||
let socket = Socket::nonblock().map_err(|e| {
|
||||
io::Error::new(io::ErrorKind::Other, format!("create scheme socket: {e}"))
|
||||
})?;
|
||||
let mut scheme = WlanScheme::new(scheme_name, Arc::clone(&bridge), mac, bssid);
|
||||
register_sync_scheme(&socket, scheme_name, &mut scheme).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("register scheme {scheme_name}: {e}"),
|
||||
)
|
||||
})?;
|
||||
let mut state = SchemeState::new();
|
||||
|
||||
log::info!("bridge::scheme: registered {} (redox-scheme)", scheme_name);
|
||||
|
||||
let read_flag = EventFlags::EVENT_READ.bits();
|
||||
loop {
|
||||
// Drain all currently-pending scheme requests.
|
||||
loop {
|
||||
match socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(request)) => match request.kind() {
|
||||
RequestKind::Call(call) => {
|
||||
let response = call.handle_sync(&mut scheme, &mut state);
|
||||
if let Err(e) =
|
||||
socket.write_response(response, SignalBehavior::Restart)
|
||||
{
|
||||
log::error!("bridge::scheme: write_response: {e}");
|
||||
}
|
||||
}
|
||||
RequestKind::OnClose { id } => scheme.on_close(id),
|
||||
_ => {}
|
||||
},
|
||||
Ok(None) => {
|
||||
log::info!("bridge::scheme: socket closed, exiting");
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) if e.errno == EAGAIN => break,
|
||||
Err(e) => {
|
||||
log::error!("bridge::scheme: next_request: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pump a pending Ethernet TX frame (submit_tx path) to the C driver.
|
||||
let tx_frame = bridge.lock().unwrap().take_tx();
|
||||
if let Some(eth) = tx_frame {
|
||||
let (bssid_copy, mac_copy) = {
|
||||
let b = bridge.lock().unwrap();
|
||||
(b.bssid, b.mac)
|
||||
};
|
||||
if let Some(wifi) = ethernet_to_wifi(ð, &bssid_copy, &mac_copy) {
|
||||
// SAFETY: `wifi` is live for the duration of the call; the C
|
||||
// side copies out `len` bytes and does not retain the pointer.
|
||||
let rc = unsafe { rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len()) };
|
||||
if rc != 0 {
|
||||
bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
} else {
|
||||
bridge.lock().unwrap().stats.inc_tx_frame(eth.len());
|
||||
}
|
||||
} else {
|
||||
bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
}
|
||||
}
|
||||
|
||||
// Wake blocked readers when RX frames are available.
|
||||
if bridge.lock().unwrap().available_for_read() > 0 {
|
||||
for id in scheme.data_handle_ids() {
|
||||
let _ = socket.write_response(
|
||||
Response::post_fevent(id, read_flag),
|
||||
SignalBehavior::Restart,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,62 +401,16 @@ pub fn run_event_loop(
|
||||
mac: [u8; 6],
|
||||
bssid: [u8; 6],
|
||||
) -> ! {
|
||||
use inner::SchemeInner;
|
||||
|
||||
bridge.lock().unwrap().active.store(true, Ordering::Release);
|
||||
|
||||
let mut scheme = match SchemeInner::new(scheme_name, Arc::clone(&bridge), mac, bssid) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("bridge::scheme: failed to create scheme: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("bridge::scheme: event loop running on {}", scheme_name);
|
||||
|
||||
loop {
|
||||
// Process incoming scheme requests
|
||||
match scheme.tick() {
|
||||
Ok(true) => { /* handled one request */ }
|
||||
Ok(false) => { /* no work */ }
|
||||
Err(e) => {
|
||||
log::error!("bridge::scheme: tick error: {}", e);
|
||||
// Continue — don't crash on transient errors
|
||||
}
|
||||
match inner::run(scheme_name, bridge, mac, bssid) {
|
||||
Ok(()) => std::process::exit(0),
|
||||
Err(e) => {
|
||||
log::error!("bridge::scheme: event loop error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Drain pending TX
|
||||
let tx_frame = {
|
||||
let mut b = bridge.lock().unwrap();
|
||||
b.take_tx()
|
||||
};
|
||||
if let Some(eth) = tx_frame {
|
||||
let (bssid_copy, mac_copy) = {
|
||||
let b = bridge.lock().unwrap();
|
||||
(b.bssid, b.mac)
|
||||
};
|
||||
if let Some(wifi) = ethernet_to_wifi(ð, &bssid_copy, &mac_copy) {
|
||||
unsafe {
|
||||
let rc = inner::rb_iwlwifi_bridge_tx_submit(wifi.as_ptr(), wifi.len());
|
||||
if rc != 0 {
|
||||
bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
} else {
|
||||
bridge.lock().unwrap().stats.inc_tx_frame(eth.len());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bridge.lock().unwrap().stats.inc_tx_dropped();
|
||||
}
|
||||
}
|
||||
|
||||
// Notify readers if data is available
|
||||
if bridge.lock().unwrap().available_for_read() > 0 {
|
||||
scheme.notify_readers();
|
||||
}
|
||||
|
||||
// Small yield to avoid busy-looping when idle
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -169,7 +169,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redbear-authd"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"redbear-login-protocol",
|
||||
"rust-argon2",
|
||||
@@ -180,7 +180,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redbear-login-protocol"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
Reference in New Issue
Block a user