Expand linux-kpi wireless scaffolding, consolidate desktop plan, remove historical report

Add channel/band/rate/BSS/RX-TX structures to linux-kpi wireless
scaffolding (mac80211.rs, wireless.rs, net.rs, C headers), extend
redbear-iwlwifi linux_port.c with comprehensive PCIe transport, and
create consolidated CONSOLE-TO-KDE-DESKTOP-PLAN.md as the canonical
desktop path document. Remove stale INTEGRATION_REPORT.md (1388 lines)
in favor of current local/docs/ references. Update AGENTS.md, README,
and docs index to point to the new plan.
This commit is contained in:
2026-04-16 13:52:09 +01:00
parent e2f20aacb6
commit 6c2643bd9c
36 changed files with 5230 additions and 1757 deletions
View File
+1 -1
View File
@@ -277,7 +277,7 @@ All custom work goes in `local/` — see `local/AGENTS.md` for overlay usage.
- QEMU used for testing (make qemu). VirtualBox also supported
- The `repo` binary (cookbook CLI) may crash with TUI in non-interactive environments — use `CI=1`
- No git submodules — external repos managed via recipe source URLs and repo manifests
- File `INTEGRATION_REPORT.md` contains detailed integration status from a previous analysis
- Historical integration report removed (2026-04-16); see `local/docs/DESKTOP-STACK-CURRENT-STATUS.md` for current state
## SUBSYSTEM PRIORITY AND ORDER
File diff suppressed because it is too large Load Diff
+1
View File
@@ -53,6 +53,7 @@ The current public roadmap and execution model live in the
For readers landing on GitHub, the most useful entry points are:
- [Documentation Index](./docs/README.md) — canonical map of current vs historical docs
- [Console to KDE Desktop Plan](./local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md) — canonical path from console boot to hardware-accelerated KDE Plasma on Wayland
- [Desktop Stack Current Status](./local/docs/DESKTOP-STACK-CURRENT-STATUS.md) — current build/runtime truth for Qt, Wayland, and KDE surfaces
- [WIP Migration Ledger](./local/docs/WIP-MIGRATION-LEDGER.md) — how Red Bear currently treats upstream WIP versus local overlays
- [Script Behavior Matrix](./local/docs/SCRIPT-BEHAVIOR-MATRIX.md) — what the main sync/fetch/apply/build scripts do and do not guarantee
+5 -2
View File
@@ -387,9 +387,11 @@ Current state:
Canonical references:
- `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` — canonical desktop path from console to hardware-accelerated KDE Plasma on Wayland
- `local/docs/QT6-PORT-STATUS.md`
- `docs/03-WAYLAND-ON-REDOX.md`
- `docs/05-KDE-PLASMA-ON-REDOX.md`
- `local/docs/DESKTOP-STACK-CURRENT-STATUS.md`
- `docs/03-WAYLAND-ON-REDOX.md` — historical Wayland implementation rationale
- `docs/05-KDE-PLASMA-ON-REDOX.md` — historical KDE implementation rationale
Acceptance:
@@ -418,6 +420,7 @@ Acceptance:
The current subsystem plans to treat as first-class are:
- `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` — canonical desktop path plan
- `local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md`
- `local/docs/USB-IMPLEMENTATION-PLAN.md`
- `local/docs/WIFI-IMPLEMENTATION-PLAN.md`
+8 -1
View File
@@ -5,6 +5,12 @@ Technical documentation for Red Bear OS as an overlay distribution on top of Red
This index is the entry point for the documentation set. Its main job is to make the
current/canonical versus historical/reference split obvious.
> **Status note (2026-04-16):** The canonical desktop path document is now
> `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md`. It consolidates the Wayland, KDE, and GPU roadmap
> into one honest implementation plan. The historical docs below (0105) remain useful for
> architecture reference and implementation rationale, but they should be read together with the
> new plan and the current local subsystem docs.
> **Status note (2026-04-14):** several documents below are historical implementation plans whose
> original "missing / not started" language is now stale. The repo already contains substantial
> Red Bear OS work under `local/`; use each document's top-level status notes together with
@@ -31,7 +37,7 @@ current/canonical versus historical/reference split obvious.
| Document set | Role |
|---|---|
| `README.md`, `AGENTS.md`, `docs/README.md`, `docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md` | canonical repository-level policy and current execution model |
| `local/docs/*IMPLEMENTATION-PLAN*.md`, `local/docs/*STATUS*.md` | canonical current Red Bear subsystem plans and status |
| `local/docs/*IMPLEMENTATION-PLAN*.md`, `local/docs/*STATUS*.md`, `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` | canonical current Red Bear subsystem plans and status |
| `docs/01-REDOX-ARCHITECTURE.md` | architecture reference |
| `docs/02-GAP-ANALYSIS.md`, `docs/03-WAYLAND-ON-REDOX.md`, `docs/04-LINUX-DRIVER-COMPAT.md`, `docs/05-KDE-PLASMA-ON-REDOX.md` | valuable but partly historical roadmap/design material |
@@ -81,6 +87,7 @@ at a higher level.
This summary is only a quick orientation layer. For canonical current-state detail, prefer:
- `docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md` for repository-wide execution order,
- `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` for the canonical desktop path from console to KDE Plasma on Wayland,
- `local/docs/DESKTOP-STACK-CURRENT-STATUS.md` for desktop build/runtime truth,
- `local/docs/PROFILE-MATRIX.md` for support-language by tracked profile,
- and the active subsystem plans under `local/docs/` for detailed current workstreams.
+895
View File
@@ -0,0 +1,895 @@
# Red Bear OS: Console to Hardware-Accelerated KDE Desktop on Wayland
## Purpose
This document is the single authoritative implementation plan for the Red Bear OS path from
console boot to a hardware-accelerated KDE Plasma desktop on Wayland.
It consolidates and replaces the roadmap role previously spread across:
- `docs/03-WAYLAND-ON-REDOX.md`
- `docs/05-KDE-PLASMA-ON-REDOX.md`
- `docs/02-GAP-ANALYSIS.md`
- `local/docs/AMD-FIRST-INTEGRATION.md`
- `local/docs/DESKTOP-STACK-CURRENT-STATUS.md`
- `local/docs/QT6-PORT-STATUS.md`
Those documents still matter for subsystem detail, package status, and implementation history.
This document is the place to answer the higher-level question: what still has to happen, in the
right order, before Red Bear OS can honestly claim a usable KDE Plasma desktop on Wayland, first
on the software path and then on real hardware acceleration.
This plan is grounded in the current repo state, not in older greenfield assumptions. The project
already has substantial build-side progress across relibc, driver infrastructure, Wayland, Mesa,
Qt6, KF6, D-Bus, and desktop-facing profiles. The remaining problem is mostly not package absence.
The remaining problem is the gap between what builds and what is runtime-trusted.
Scope here covers console boot to first working Wayland compositor proof, software-rendered Qt6 on
Wayland, hardware GPU validation for AMD and Intel, KWin session bring-up, and KDE Plasma session
bring-up. It does not cover USB, Wi-Fi, Bluetooth, tutorial-style examples, or repo structure and
build-command reference material already documented elsewhere.
This document uses the current Red Bear hardware policy. AMD and Intel GPUs are equal-priority
desktop targets.
## Current State Baseline
### Evidence model
This plan uses six evidence classes. They are intentionally strict and are not treated as equal.
| Evidence class | Meaning | Safe wording | Not safe wording |
|---|---|---|---|
| **builds** | package compiles and stages | builds | works |
| **boots** | image reaches prompt or known runtime surface | boots | desktop works |
| **enumerates** | scheme, device node, or service surface appears and answers basic queries | enumerates | usable end to end |
| **usable** | a bounded runtime path performs its intended task | usable for this path | broadly stable |
| **validated** | repeated proof on the intended target class with explicit checks | validated | complete everywhere |
| **experimental** | partial, scaffolded, or unproven despite visible progress | experimental | done |
Interpretation rules used throughout this document:
- if something only compiles, it is called **builds**
- if something boots but does not complete a session, it is called **boots**
- if a daemon registers a scheme or device node, it is called **enumerates**
- if the only proof is a bounded QEMU path, the claim stays bounded to that path
- if dependencies are still shimmed or stubbed, the layer remains **experimental**
- nothing is called **validated** without repeated runtime proof on the intended target class
### Honest capability matrix
| Area | Current state | Evidence | Notes |
|---|---|---|---|
| AMD bare-metal boot | present | validated for bounded current claim | ACPI, SMP, x2APIC all work |
| relibc Wayland and Qt unblockers | present | builds | signalfd, timerfd, eventfd, open_memstream, F_DUPFD_CLOEXEC, MSG_NOSIGNAL, bounded waitid, bounded RLIMIT, bounded eth0 networking, shm_open, bounded sem_open, bounded sys/ipc.h, bounded sys/shm.h |
| redox-driver-sys | present | builds | driver substrate |
| linux-kpi | present | builds | compatibility layer for Linux-style drivers |
| firmware-loader | present | builds, boots | scheme registers at boot |
| redox-drm with AMD and Intel | present | builds | runtime hardware validation still open |
| amdgpu C port | present | builds | AMD DC + TTM + linux-kpi compat compiles |
| evdevd | present | builds, boots | scheme registers at boot |
| udev-shim | present | builds, boots | scheme registers at boot |
| libwayland 1.24.0 | present | builds | no full compositor proof yet |
| wayland-protocols | present | builds | build-side blocker removed |
| Mesa EGL + GBM + GLES2 | present | builds | proven runtime path is still software via LLVMpipe |
| libdrm + libdrm_amdgpu | present | builds | package-level success only |
| Qt6 qtbase 6.11.0 | present | builds | Core, Gui, Widgets, DBus, Wayland, OpenGL, EGL |
| qtdeclarative | present | builds | QML JIT disabled |
| qtsvg | present | builds | build-visible |
| qtwayland | present | builds | build-visible |
| D-Bus 1.16.2 | present | builds, bounded runtime wiring | system bus wired in `redbear-full` |
| libinput 1.30.2 | present | builds | runtime integration still open |
| libevdev 1.13.2 | present | builds | runtime integration still open |
| linux-input-headers | present | builds | support package |
| seatd | present | builds | session-management runtime proof still open |
| All 32 KF6 frameworks | present | builds | major build milestone complete |
| kdecoration | present | builds | build-visible |
| plasma-wayland-protocols | present | builds | build-visible |
| kf6-kwayland | present | builds | build-visible |
| kf6-kcmutils | present | builds, reduced | widget-only build |
| `redbear-wayland` | present | builds, boots | bounded Wayland runtime profile |
| `redbear-full` | present | builds, boots | broader desktop plumbing profile |
| `redbear-kde` | present | builds | KDE session-surface profile |
| smallvil path | partial | boots, experimental | reaches xkbcommon init and EGL platform selection in QEMU |
| QEMU graphics truth | present | usable for bounded path | current renderer is llvmpipe, not hardware acceleration |
| D-Bus system bus in `redbear-full` | present | usable for bounded path | not equal to full session integration completeness |
| VirtIO networking in QEMU | present | usable | useful for bounded test environment |
| firmware-loader, evdevd, udev-shim scheme registration | present | enumerates | register during boot |
| KWin | blocked | experimental | recipe exists, blocked by remaining shimmed and stubbed dependencies |
| plasma-workspace | partial | experimental | recipe exists, still experimental |
| plasma-desktop | partial | experimental | recipe exists, still experimental |
| QtNetwork | blocked | intentionally disabled | relibc networking completeness still too narrow |
| hardware GPU acceleration | blocked | not runtime-proven | kernel DMA-BUF fd passing required |
| working Wayland compositor session | blocked | runtime not proven | smallvil does not complete a usable session |
| KWin compositor runtime | blocked | runtime not proven | no working KWin session |
| KDE Plasma session | blocked | runtime not proven | no full Plasma session |
### What is DONE, build-side
The repo has already crossed several major build-side gates.
#### relibc surface that now builds downstream consumers
The current build-visible relibc surface includes signalfd, timerfd, eventfd, open_memstream,
F_DUPFD_CLOEXEC, MSG_NOSIGNAL, bounded waitid, bounded RLIMIT behavior, bounded eth0 networking,
shm_open, bounded sem_open, bounded `sys/ipc.h`, and bounded `sys/shm.h`.
#### driver and runtime-service substrate
redox-driver-sys, linux-kpi, firmware-loader, redox-drm with AMD and Intel paths, the amdgpu C
port, evdevd, and udev-shim all build successfully.
#### Wayland and graphics packages
libwayland 1.24.0, wayland-protocols, Mesa EGL + GBM + GLES2 with `libEGL.so`, `libgbm.so`,
`libGLESv2.so`, `swrast_dri.so`, plus libdrm and libdrm_amdgpu all build.
#### Qt6 and D-Bus
D-Bus 1.16.2 builds. qtbase 6.11.0 builds with Core, Gui, Widgets, DBus, Wayland, OpenGL, and
EGL. qtdeclarative, qtsvg, and qtwayland also build.
#### KF6 and KDE-facing build surfaces
All 32 KF6 frameworks build. The completed set spans ecm, core and widget foundations, config,
internationalization, codecs, GUI add-ons, color and notification layers, job and archive support,
item models and views, Solid, D-Bus and service layers, package and crash handling, text and icon
layers, global shortcuts, KDE declarative support, XML GUI, bookmarks, idle time, KIO, and
KCMUtils. Additional KDE-facing packages that already build include kdecoration,
plasma-wayland-protocols, kf6-kwayland, and kf6-kcmutils.
#### tracked desktop profiles
The tracked desktop-facing profiles are `redbear-wayland`, `redbear-full`, and `redbear-kde`.
These are real achievements and should be presented as such. They are not yet desktop-runtime proof.
### What is runtime-proven, limited scope
The current desktop-related runtime proof is bounded, but real.
#### Boot and machine substrate
Red Bear boots on AMD bare metal, and ACPI, SMP, and x2APIC work for the current bounded claim.
#### bounded Wayland bring-up path
`redbear-wayland` boots in QEMU, and smallvil reaches xkbcommon initialization plus EGL platform
selection on Redox.
#### bounded graphics truth
Current QEMU graphics are software-rendered, the renderer evidence is llvmpipe, QEMU is useful for
compositor and Qt bring-up, and QEMU is not proof of the final hardware-accelerated desktop path.
#### bounded runtime services
D-Bus system bus is wired in `redbear-full`, VirtIO networking works in QEMU, and firmware-loader,
evdevd, and udev-shim register schemes at boot.
### What is NOT DONE
This list must stay explicit.
#### runtime not proven
No GPU hardware-accelerated rendering is proven, no kernel DMA-BUF support exists for the required
desktop path, no working Wayland compositor session is proven, no KWin compositor runtime is
proven, no KDE Plasma session is proven, and Qt6 OpenGL and EGL still have only software-path
runtime proof.
#### builds still blocked or scaffolded
KWin does not build end to end with fully real dependencies. Kirigami is still stub-only, KIO is
still a heavy shim build, libepoxy, libudev, lcms2, and libdisplay-info remain real blockers,
plasma-workspace and plasma-desktop remain experimental, and QtNetwork remains disabled due to
incomplete relibc networking semantics.
### Baseline conclusion
The repo is no longer stuck at package availability. It is now limited by runtime trust, hardware
validation, and KWin or Plasma session assembly. That is the real starting point for the plan below.
## Dependency Stack
### ASCII layer diagram
```text
+--------------------------------------------------------------------------------+
| KDE Plasma Session |
| plasma-workspace, plasma-desktop, shell, panels, launcher, apps |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| KWin desktop-session layer |
| KWin, kdecoration, seat and session wiring |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| Qt6 and KDE frameworks |
| Qt6 Widgets, QtWayland, QtDBus, QML, KF6, KDE support libs |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| Wayland compositor and protocols |
| smallvil first, then KWin, plus libwayland |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| Mesa, GBM, EGL, GLES2, libdrm |
| software path first, hardware path after DMA-BUF |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| DRM, KMS, firmware, input, device enumeration |
| redox-drm, amdgpu, Intel path, evdevd, udev-shim |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| Kernel and libc substrate for desktop bring-up |
| relibc, fd passing, DMA-BUF, IRQ, PCI, schemes |
+---------------------------------------^----------------------------------------+
|
+---------------------------------------|----------------------------------------+
| Hardware and boot substrate |
| AMD64 boot, ACPI, SMP, x2APIC, AMD and Intel GPUs |
+--------------------------------------------------------------------------------+
```
### Reading the dependency stack correctly
This stack has two kinds of blockers.
#### runtime substrate blockers
These sit low in the stack and poison all higher work if they are not validated:
- relibc runtime correctness
- input event correctness
- udev-like device enumeration correctness
- firmware loading correctness
- basic DRM and KMS correctness
- kernel DMA-BUF support for the accelerated path
#### session-assembly blockers
These sit higher and matter after the lower layers are trusted:
- smallvil completion
- Qt6 client display on Wayland
- KWin dependency cleanup
- KWin runtime session wiring
- Plasma shell and workspace integration
The plan must handle these in order. Otherwise failures in KWin or Plasma will really be lower-layer
failures in disguise.
### Layer-by-layer status
#### Layer 0, hardware and boot
Status: **partly runtime-proven**
What is true now:
- AMD bare-metal boot works for the current bounded claim
- ACPI, SMP, and x2APIC work
- AMD and Intel are equal-priority GPU targets
What still needs proof:
- real desktop-path validation on AMD GPUs
- real desktop-path validation on Intel GPUs
#### Layer 1, kernel and libc substrate
Status: **strong build-side, runtime incomplete**
What is true now:
- relibc exposes the build-visible Wayland and Qt unblockers
- redox-driver-sys and linux-kpi exist as the current driver substrate
What still needs proof:
- relibc behavior under real Wayland and Qt event-loop pressure
- kernel DMA-BUF fd passing for the hardware path
#### Layer 2, DRM, firmware, input, enumeration
Status: **build-visible and boot-visible, not runtime-trusted**
What is true now:
- redox-drm builds with AMD and Intel drivers
- amdgpu builds
- firmware-loader, evdevd, and udev-shim register at boot
What still needs proof:
- actual firmware loading by a real consumer
- actual input flow from Redox input sources into compositor-visible event devices
- actual scheme:drm registration and basic KMS query behavior in runtime
- actual AMD and Intel hardware-driver behavior on target machines
#### Layer 3, graphics userland interface
Status: **software path builds, hardware path blocked**
What is true now:
- Mesa EGL, GBM, and GLES2 build
- libdrm and libdrm_amdgpu build
- Qt6 OpenGL and EGL build
- QEMU proof still uses llvmpipe
What still needs proof:
- hardware renderer path through real DRM and real GPU drivers
- GBM allocation on the hardware path
- EGL and GLES stability on the hardware path
#### Layer 4, Wayland protocol and compositor
Status: **partial runtime proof, not complete**
What is true now:
- libwayland and wayland-protocols build
- smallvil is the bounded first runtime target
- smallvil reaches early initialization in QEMU
What still needs proof:
- a complete compositor session
- input routed into the compositor
- Qt6 client display in that compositor
#### Layer 5, Qt6 and KF6
Status: **major build milestone complete, runtime still thin**
What is true now:
- Qt6 builds across core, widgets, DBus, Wayland, OpenGL, and EGL
- qtdeclarative and qtwayland build
- all 32 KF6 frameworks build
What still needs proof:
- real Qt6 Wayland client behavior on Redox
- behavior of QML-heavy pieces under the no-JIT path
- broader networking semantics needed before QtNetwork can be enabled
#### Layer 6, KWin session shell
Status: **experimental and blocked**
What is true now:
- recipes exist
- `redbear-kde` exists
- several KWin-adjacent packages already build
What still needs proof:
- replacement of shimmed and stubbed blockers with real enough dependencies
- KWin compile success against honest dependencies
- KWin runtime as the compositor
#### Layer 7, KDE Plasma session
Status: **not yet proven**
What is true now:
- Plasma recipe surfaces exist
- the stack is far enough along that this is now a session-assembly problem, not a package-startup problem
What still needs proof:
- plasma-workspace integration
- plasma-desktop integration
- panel, launcher, file manager, settings, and session service behavior
### Dependency-stack conclusion
The shortest honest path is not "port more packages". The shortest honest path is "validate the
substrate, finish one software compositor path, finish one KWin session path, finish one Plasma
session path, then land the real hardware renderer path in parallel".
## Phased Work Plan
This plan uses fresh Phase 1 through Phase 5 numbering and does not reuse the old P0 through P6
scheme.
### Phase 1: Runtime Substrate Validation
**Duration:** 4 to 6 weeks
**Goal:** turn the lowest desktop-facing layers from build-visible into runtime-trusted.
This phase matters more than any other because it removes ambiguity from the entire stack. Without
it, later compositor or KDE failures will be impossible to classify correctly.
#### Core work
1. Validate relibc POSIX APIs against real consumers, especially libwayland and Qt6 runtime paths.
2. Validate the evdevd path from Redox input schemes through to `/dev/input/eventX` behavior.
3. Validate udev-shim device enumeration semantics for the current compositor and input stack.
4. Validate firmware-loader and `scheme:firmware` with real firmware blobs and a real consumer path.
5. Validate `scheme:drm/card0` registration and bounded KMS queries in QEMU.
6. Produce a repeatable runtime-service health check for the `redbear-wayland` slice.
#### Why this phase exists
The repo already compiles the lower desktop stack. What it lacks is evidence that the lower stack
behaves correctly under real use. Phase 1 is where builds become runtime-trusted enough to support
the first serious compositor pass.
#### Deliverables
##### relibc runtime validation set
Validate the relibc surfaces already present in-tree:
- signalfd
- timerfd
- eventfd
- open_memstream
- F_DUPFD_CLOEXEC
- MSG_NOSIGNAL
- bounded waitid
- bounded shared-memory and semaphore paths used by Qt6
The standard here is not just "the symbol exists". The standard is that real consumers can use the
API without hidden workarounds, hangs, or broken semantics.
##### evdev input validation set
Validate the current input chain end to end:
- input source emits events
- evdevd exposes expected event devices
- keyboard events arrive with correct semantics
- mouse events arrive with correct semantics
##### udev-shim validation set
Validate that current consumers can discover and classify the devices they need. Full Linux parity
is not required. Sufficient enumeration for the current desktop path is required.
##### firmware-loader validation set
Validate firmware loading with real blobs and a real consumer path. Scheme registration alone is not
enough. The blob must be requestable, discoverable, loadable, and consumable at runtime.
##### redox-drm runtime-surface validation set
Validate bounded runtime behavior first in QEMU:
- scheme registration for `scheme:drm/card0`
- basic KMS queries
- no startup-class failures in the redox-drm path
#### Acceptance criteria
Phase 1 is complete when all of the following are true:
- `redbear-wayland` boots in the bounded validation environment
- Phase 1 runtime services register without startup errors
- relibc runtime checks pass for the selected desktop-facing consumers
- the input path reaches evdevd and yields expected event nodes and bounded test events
- udev-shim exposes the expected bounded device view
- firmware-loader successfully serves at least one real consumer path with real blobs
- `scheme:drm/card0` registers and answers bounded basic queries
#### Exit statement
At the end of Phase 1, the repo should be able to say: the desktop substrate is no longer only a
build artifact. It is runtime-trusted enough to support a compositor completion pass.
### Phase 2: Wayland Compositor Runtime Proof
**Duration:** 4 to 6 weeks
**Goal:** produce the first working Wayland compositor session using software rendering.
This phase stays intentionally narrow. The first complete compositor proof should happen in the
smallest runtime target available, which is still smallvil.
#### Core work
1. Complete the current smallvil runtime path.
2. Wire evdevd input into the compositor.
3. Wire Mesa software rendering through GBM and EGL.
4. Get a Qt6 widget application to display through the compositor.
#### Why smallvil remains the right target
Jumping straight to KWin would combine too many unknowns: compositor runtime, input, QML, session
services, dependency scaffolding, and desktop-shell behavior. smallvil is smaller, easier to debug,
and already present. It is the right place to finish the first software compositor proof.
#### Deliverables
##### complete smallvil runtime path
The current proof stops during early initialization. This phase completes the path into a usable
session.
##### input wired into compositor
Keyboard and mouse must work through the current Redox input stack, not through an artificial bypass.
##### software rendering path confirmed
The proven renderer for this phase is LLVMpipe through Mesa, GBM, and EGL. That is acceptable. The
goal is correctness of compositor and client behavior, not hardware acceleration yet.
##### Qt6 smoke client on Wayland
The first meaningful desktop-facing end-to-end proof is a real Qt6 Wayland client window appearing
inside the compositor.
#### Acceptance criteria
Phase 2 is complete when all of the following are true:
- smallvil launches into a working session in QEMU
- keyboard and mouse work through the current input stack
- Mesa software rendering works through GBM and EGL
- `qt6-wayland-smoke` shows a window inside the compositor in QEMU
#### Exit statement
At the end of Phase 2, the repo should be able to say: Red Bear OS has a working software-rendered
Wayland compositor path with a visible Qt6 client.
### Phase 3: Hardware GPU Enablement
**Duration:** 12 to 20 weeks
**Goal:** replace the software-only graphics proof with real hardware-accelerated display output and
rendering.
This is the highest-uncertainty phase. It includes new kernel work, real hardware-driver proof, and
the first true Mesa hardware path on Redox.
#### Core work
1. Add kernel DMA-BUF fd passing.
2. Validate redox-drm AMD and Intel drivers on real hardware.
3. Validate Mesa hardware rendering path, including real renderer identity.
4. Validate GBM buffer allocation through the hardware path.
#### Why this phase is separate
The software compositor path is the fastest honest route to proving compositor and client behavior.
The hardware path is a different class of systems work. It should run in parallel with later KWin
and Plasma assembly instead of blocking everything else.
#### Deliverables
##### kernel DMA-BUF fd passing
This is the gating feature for the accelerated desktop path. Without it, hardware-accelerated KDE
is not a credible target.
##### real AMD hardware validation
Validate on representative AMD hardware:
- device detection
- MMIO mapping
- firmware loading
- connector detection
- mode enumeration
- bounded modeset proof
##### real Intel hardware validation
Validate on representative Intel hardware:
- device detection
- MMIO mapping
- connector detection
- mode enumeration
- bounded modeset proof
##### Mesa hardware rendering proof
Validate the actual hardware renderer path rather than llvmpipe fallback. For AMD the target is the
radeonsi path. For Intel the target is the intended real Intel hardware path available in the stack.
#### Acceptance criteria
Phase 3 is complete when all of the following are true:
- kernel DMA-BUF fd passing exists and has focused proof coverage
- `modetest -M amd` shows display modes on real AMD hardware
- the equivalent Intel DRM query path shows display modes on real Intel hardware
- the compositor runs through the hardware path rather than llvmpipe on at least one real AMD class
and one real Intel class
- runtime evidence shows a hardware-backed renderer rather than software fallback
#### Exit statement
At the end of Phase 3, the repo should be able to say: Red Bear OS can drive real display hardware
and run the compositor on a hardware-accelerated path.
### Phase 4: Desktop Session Assembly
**Duration:** 6 to 10 weeks
**Goal:** turn the compositor proof into a real desktop-session substrate centered on KWin.
This phase starts after Phase 2. It does not need to wait for the full hardware path. KWin can come
up first on the software renderer and later inherit the accelerated renderer once Phase 3 lands.
#### Core work
1. Resolve KWin shimmed and stubbed blockers.
2. Get KWin to compile with real enough dependencies.
3. Launch KWin as the Wayland compositor.
4. Validate libinput backend behavior.
5. Validate D-Bus session behavior.
6. Validate seatd for the bounded KWin session model.
#### blocked dependency set that must be closed
- kirigami stub-only state
- heavy kio shim state where it blocks honest session claims
- libepoxy
- libudev
- lcms2
- libdisplay-info
#### Deliverables
##### honest KWin build
The milestone is not just that a recipe exists. The milestone is that KWin builds without fake
dependency satisfaction for core runtime behavior.
##### KWin runtime as compositor
KWin must launch as the compositor and own the display path.
##### session services for bounded desktop use
D-Bus session behavior and seatd behavior must be good enough for the bounded KWin target this plan
claims. Linux parity is not required. Correct bounded behavior is required.
#### Acceptance criteria
Phase 4 is complete when all of the following are true:
- KWin builds against real enough dependencies to support honest runtime claims
- KWin launches as the compositor
- KWin takes over display output in the bounded session path
- keyboard and mouse work through the KWin session path
- required D-Bus session behavior for the bounded KWin path works
- seatd behavior is validated for the bounded KWin session model
#### Exit statement
At the end of Phase 4, the repo should be able to say: Red Bear OS has a working Wayland desktop
session substrate centered on KWin.
### Phase 5: KDE Plasma Session
**Duration:** 8 to 12 weeks
**Goal:** boot into a KDE Plasma session with the essential desktop shell and session services
working.
This is the final desktop product phase. By this point the remaining work should mostly be session
assembly, application integration, and shell behavior.
#### Core work
1. Complete plasma-workspace compilation and integration.
2. Complete plasma-desktop compilation and integration.
3. Get the shell, panel, and launcher visible and usable.
4. Get settings and file-manager paths working.
5. Provide bounded network and audio integration suitable for the session claim.
#### Deliverables
##### Plasma shell
The minimum target is not a screenshot. The session must show the shell, panel, and launcher and be
stable through basic interaction.
##### application and settings path
At least one real file-manager path and one settings path must work. Otherwise the session is still
too incomplete to count as a desktop.
##### bounded desktop-service integration
For this phase the question is narrow: can the Plasma session boot into a usable desktop with bounded
network and audio integration. The long-term subsystem plans remain separate.
#### Acceptance criteria
Phase 5 is complete when all of the following are true:
- `redbear-kde` boots into a KDE Plasma session
- KWin is the active compositor
- the Plasma shell, panel, and launcher appear
- an application can be launched from the session
- a file-manager path works through the current kio integration
- a settings path works
- bounded network and audio integration exist for the claimed session profile
#### Exit statement
At the end of Phase 5, the repo should be able to say one of two things:
- if Phase 3 is still incomplete: Red Bear OS has a software-rendered KDE Plasma session on Wayland
- if Phase 3 is complete: Red Bear OS has a hardware-accelerated KDE Plasma session on Wayland
## Critical Path
### primary path to a software-rendered KDE session
```text
Phase 1, runtime substrate validation
-> Phase 2, software Wayland compositor proof
-> Phase 4, KWin desktop-session assembly
-> Phase 5, KDE Plasma session
```
This is the shortest honest path to a KDE desktop claim.
### parallel hardware path
```text
Phase 1, runtime substrate validation
-> Phase 3, hardware GPU enablement
Phase 3 proceeds in parallel with Phase 4 where possible
Phase 3 + Phase 4 + Phase 5
-> hardware-accelerated KDE Plasma desktop
```
### why Phase 1 is the real gate
Phase 1 is the true gateway because it converts lower-layer package progress into runtime trust.
Without it, Phase 2, Phase 4, and Phase 5 failures will be misdiagnosed.
### why Phase 2 comes before KWin
The first complete compositor proof should happen in the smallest environment. smallvil is smaller,
already present, and easier to debug than KWin. It isolates compositor, input, and Qt client issues
before session-shell complexity is added.
### why Phase 3 should not block Phase 4
Hardware acceleration is critical, but KWin and Plasma also have their own blockers: dependency
cleanup, session services, and compositor integration. Those can be solved on the software renderer
while the hardware path matures.
### critical-path summary
The execution order this repo should present is:
1. validate the runtime substrate
2. prove one software compositor path
3. assemble one KWin session path
4. assemble one Plasma session path
5. land hardware acceleration in parallel
## Risk Register
| ID | Risk | Likelihood | Impact | Why it matters | Mitigation |
|---|---|---|---|---|---|
| R1 | relibc runtime gaps are worse than build evidence suggests | Medium | High | Qt6 and Wayland may still fail at runtime even though they build | validate with real consumers in Phase 1 |
| R2 | kernel DMA-BUF fd passing is a new feature with uncertain scope | High | High | hardware acceleration depends on it | isolate design and proof early in Phase 3 |
| R3 | AMD or Intel real-hardware validation reveals fundamental driver issues | High | High | compile success may not survive real modesetting or rendering | validate AMD and Intel separately on representative hardware |
| R4 | KWin porting needs significantly more patches than estimated | Medium | High | KWin sits on the desktop critical path | finish smallvil proof first, then attack KWin with cleaner lower-layer evidence |
| R5 | Kirigami and other QML-heavy pieces do not behave acceptably with QML JIT disabled | Medium | Medium to High | Plasma shell may build but behave badly | keep QML-heavy runtime proof explicit in Phase 4 and Phase 5 |
| R6 | Mesa hardware rendering needs Redox-specific winsys work beyond current estimates | Medium | High | hardware acceleration may stall after modesetting starts working | separate display proof from renderer proof |
| R7 | linux-kpi compatibility gaps only appear during real-hardware execution | High | Medium to High | compile-only success can hide runtime failures | budget for hardware-driven compatibility fixes in Phase 3 |
## Timeline
### planning assumptions
These estimates assume 2 developers, usable access to representative AMD and Intel hardware, and no
major regression from unrelated upstream refresh during the desktop push. They do not assume perfect
first-pass success on real hardware.
### phase estimates with 2 developers
| Phase | Estimate | Notes |
|---|---|---|
| Phase 1, Runtime Substrate Validation | 4 to 6 weeks | must finish honestly before claiming runtime trust |
| Phase 2, Wayland Compositor Runtime Proof | 4 to 6 weeks | can overlap with late Phase 1 cleanup |
| Phase 3, Hardware GPU Enablement | 12 to 20 weeks | parallel track after Phase 1 |
| Phase 4, Desktop Session Assembly | 6 to 10 weeks | starts after Phase 2 |
| Phase 5, KDE Plasma Session | 8 to 12 weeks | starts after Phase 4 |
### total duration with 2 developers
#### to software-rendered KDE Plasma on Wayland
- **22 to 34 weeks**
- roughly **6 to 8 months**
This path is Phase 1 + Phase 2 + Phase 4 + Phase 5.
#### to hardware-accelerated KDE Plasma on Wayland
- **34 to 54 weeks**
- roughly **8 to 13 months**
This path is Phase 1 + Phase 2 + Phase 3 in parallel + Phase 4 + Phase 5.
### rough overlap model
```text
Weeks 1 to 6
Phase 1, runtime substrate validation
Weeks 4 to 12
Phase 2, software compositor proof
Weeks 7 to 26
Phase 3, hardware GPU enablement
Weeks 13 to 22
Phase 4, KWin session assembly
Weeks 23 to 34
Phase 5, KDE Plasma session
```
This is an intended overlap shape, not a guaranteed calendar.
### one-developer estimate
With 1 developer, the overall timeline is roughly 1.5x to 2x the two-developer estimates.
Practical meaning:
- software-rendered KDE path: about 9 to 16 months
- hardware-accelerated KDE path: about 12 to 27 months
The wider range reflects the loss of useful parallelism between hardware work and session work.
### timeline conclusion
The software-rendered KDE target is no longer a greenfield multi-year fantasy. The hardware-
accelerated KDE target is still a serious systems milestone because Phase 3 carries the widest
uncertainty band.
## Relationship to Other Plans
This is the canonical document for the desktop path from console boot to KDE Plasma on Wayland. It
does not replace every subsystem-specific plan. It sets the ordering, scope, and acceptance language
for the desktop path while deeper subsystem documents retain their detailed ownership.
### primary supporting plans
- `local/docs/RELIBC-COMPLETENESS-AND-ENHANCEMENT-PLAN.md` for relibc completeness detail,
ownership of patch-carried behavior, and deeper evidence tracking
- `local/docs/AMD-FIRST-INTEGRATION.md` for deeper GPU-driver and firmware detail, with the caveat
that this desktop plan uses equal-priority AMD and Intel targeting
- `local/docs/QT6-PORT-STATUS.md` for Qt6, KF6, KWin blocker, shim, and stub status
- `local/docs/DESKTOP-STACK-CURRENT-STATUS.md` for the short current-state desktop truth summary
- `local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` for controller, IRQ, MSI, MSI-X,
and IOMMU quality work that supports later hardware desktop validation
- `local/docs/INPUT-SCHEME-ENHANCEMENT.md` for deeper input-path design if the current chain needs
structural cleanup beyond Phase 1 validation
- `local/docs/P2-AMD-GPU-DISPLAY.md` for the code-complete AMD display status and concrete AMD
validation targets such as `modetest -M amd`
### how to use this plan with the supporting plans
Read this document first for execution order, current claim language, completion criteria, and the
critical path. Read the subsystem plans for the exact relibc, driver, package, or input details
behind those higher-level phases.
+16 -7
View File
@@ -46,7 +46,7 @@ What the repo *does* have is a meaningful set of prerequisites:
| Area | State | Notes |
|---|---|---|
| Wi-Fi controller support | **experimental bounded slice exists** | `redbear-iwlwifi` provides an Intel-only bounded driver-side package, not validated Wi-Fi connectivity |
| Linux wireless stack compatibility | **early compatibility scaffolding exists** | `linux-kpi` now carries initial `cfg80211` / `wiphy` / `mac80211` registration and station-mode compatibility scaffolding, but not a complete Linux wireless stack |
| Linux wireless stack compatibility | **early compatibility scaffolding exists** | `linux-kpi` now carries `cfg80211` / `wiphy` / `mac80211` registration, station-mode scaffolding, channel/band/rate/BSS definitions, and RX/TX data-path structures (24 tests pass), but not a complete Linux wireless stack |
| Firmware loading | **partial prerequisite exists** | `firmware-loader` can serve firmware blobs generically |
| Wireless control plane | **experimental bounded slice exists** | `redbear-wifictl` and `redbear-netctl` expose bounded prepare/init/activate/scan orchestration, not real association support |
| Post-association IP path | **present** | Native `smolnetd` / `netcfg` / `dhcpd` / `redbear-netctl` path exists |
@@ -338,7 +338,14 @@ The current tree now has the first explicit step in that direction as well:
registration/setup, keeps carrier down until connect success, and routes
`ieee80211_queue_work()` through the bounded LinuxKPI workqueue instead of silently dropping
deferred work
- this new scaffolding is compile- and host-test-validated inside the `linux-kpi` crate
- the wireless scaffolding now also includes channel/band/rate definitions
(`Ieee80211Channel`, `Ieee80211Rate`, `Ieee80211SupportedBand` with NL80211 band constants
and IEEE80211 channel/rate flags), BSS information reporting (`Cfg80211Bss`,
`cfg80211_inform_bss`/`get_bss`/`put_bss`), RX/TX data-path structures (`Ieee80211RxStatus`,
`Ieee80211TxInfo` with RX/TX flag constants, `ieee80211_rx_irqsafe`/`tx_status`),
channel definition creation (`ieee80211_chandef_create`), and STA state-transition constants
(`IEEE80211_STA_NOTEXIST` through `IEEE80211_STA_AUTHORIZED`)
- all scaffolding is compile- and host-test-validated inside the `linux-kpi` crate (24 tests pass)
- this is still **not** a claim that Red Bear now has a working Linux wireless stack
### Boundary where a full Linux port becomes too expensive
@@ -391,11 +398,13 @@ mac80211/nl80211 remain out of scope for the current milestone.
The current validation story for this slice is intentionally narrow and should be described that
way:
- the `linux-kpi` host-side test suite now runs cleanly in this repo, including the WiFi-facing
helper changes in this slice: `request_firmware_direct`, `request_firmware_nowait`,
`mutex_trylock`, IRQ-depth tracking, variable private-allocation lifetime tracking, station-mode
scan/connect/disconnect lifecycle assertions, workqueue-backed `ieee80211_queue_work()`, the new
`sk_buff` headroom/tailroom helpers, and the existing memory tests
- the `linux-kpi` host-side test suite now runs cleanly in this repo (24 tests pass), including
the WiFi-facing helper changes in this slice: `request_firmware_direct`,
`request_firmware_nowait`, `mutex_trylock`, IRQ-depth tracking, variable private-allocation
lifetime tracking, station-mode scan/connect/disconnect lifecycle assertions,
workqueue-backed `ieee80211_queue_work()`, `sk_buff` headroom/tailroom helpers, channel/band
creation and flag tests, RX status default and flag combination tests, `ieee80211_get_tid` null
safety, and the existing memory tests
- `redbear-iwlwifi` host-side tests now smoke-test the bounded firmware/transport/activation/scan/
retry actions used by the current Intel path
- `redbear-iwlwifi` also now has a binary-level host-side CLI smoke test for the current bounded
@@ -42,6 +42,26 @@ static inline void atomic_sub(int i, atomic_t *v)
__sync_fetch_and_sub(&v->counter, i);
}
static inline int atomic_inc_and_test(atomic_t *v)
{
return __sync_add_and_fetch(&v->counter, 1) == 0;
}
static inline int atomic_dec_and_test(atomic_t *v)
{
return __sync_sub_and_fetch(&v->counter, 1) == 0;
}
static inline int atomic_add_return(int i, atomic_t *v)
{
return __sync_add_and_fetch(&v->counter, i);
}
static inline int atomic_sub_return(int i, atomic_t *v)
{
return __sync_sub_and_fetch(&v->counter, i);
}
static inline int atomic_inc_return(atomic_t *v)
{
return __sync_add_and_fetch(&v->counter, 1);
@@ -72,11 +92,6 @@ static inline int atomic_add_unless(atomic_t *v, int a, int u)
#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)
static inline int atomic_dec_and_test(atomic_t *v)
{
return __sync_sub_and_fetch(&v->counter, 1) == 0;
}
#define smp_mb() __sync_synchronize()
#define smp_rmb() __sync_synchronize()
#define smp_wmb() __sync_synchronize()
@@ -1,7 +1,8 @@
#ifndef _LINUX_DMA_MAPPING_H
#define _LINUX_DMA_MAPPING_H
#include <linux/types.h>
#include "types.h"
#include <stddef.h>
enum dma_data_direction {
DMA_BIDIRECTIONAL = 0,
@@ -10,6 +11,8 @@ enum dma_data_direction {
DMA_NONE = 3,
};
struct dma_pool;
#define DMA_BIT_MASK(n) (((n) == 64) ? ~0ULL : ((1ULL << (n)) - 1))
extern void *dma_alloc_coherent(void *dev, size_t size,
@@ -21,6 +24,14 @@ extern dma_addr_t dma_map_single(void *dev, void *ptr, size_t size,
enum dma_data_direction dir);
extern void dma_unmap_single(void *dev, dma_addr_t addr, size_t size,
enum dma_data_direction dir);
extern struct dma_pool *dma_pool_create(const char *name, void *dev, size_t size, size_t align, size_t boundary);
extern void dma_pool_destroy(struct dma_pool *pool);
extern void *dma_pool_alloc(struct dma_pool *pool, gfp_t flags, dma_addr_t *handle);
extern void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr);
extern void dma_sync_single_for_cpu(void *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
extern void dma_sync_single_for_device(void *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
extern dma_addr_t dma_map_page(void *dev, void *page, size_t offset, size_t size, enum dma_data_direction dir);
extern void dma_unmap_page(void *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
static inline int dma_mapping_error(void *dev, dma_addr_t addr)
{
@@ -2,7 +2,7 @@
#define _LINUX_FIRMWARE_H
#include <stddef.h>
#include <linux/types.h>
#include "types.h"
struct firmware {
size_t size;
@@ -1,9 +1,9 @@
#ifndef _LINUX_INTERRUPT_H
#define _LINUX_INTERRUPT_H
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/spinlock.h>
#include "types.h"
#include "irq.h"
#include "spinlock.h"
extern void local_irq_save(unsigned long *flags);
extern void local_irq_restore(unsigned long flags);
@@ -1,7 +1,7 @@
#ifndef _LINUX_IO_H
#define _LINUX_IO_H
#include <linux/types.h>
#include "types.h"
#include <stddef.h>
extern void *ioremap(phys_addr_t phys_addr, size_t size);
@@ -31,6 +31,21 @@ static inline void memset_io(void *dst, int c, size_t count)
__builtin_memset(dst, c, count);
}
static inline void mb(void)
{
__sync_synchronize();
}
static inline void rmb(void)
{
__sync_synchronize();
}
static inline void wmb(void)
{
__sync_synchronize();
}
#define ioread8(addr) readb(addr)
#define ioread16(addr) readw(addr)
#define ioread32(addr) readl(addr)
@@ -1,7 +1,7 @@
#ifndef _LINUX_IRQ_H
#define _LINUX_IRQ_H
#include <linux/types.h>
#include "types.h"
typedef unsigned int irqreturn_t;
@@ -1,7 +1,7 @@
#ifndef _LINUX_JIFFIES_H
#define _LINUX_JIFFIES_H
#include <linux/types.h>
#include "types.h"
#include <time.h>
static inline u64 redox_get_jiffies(void)
@@ -1,7 +1,7 @@
#ifndef _LINUX_MUTEX_H
#define _LINUX_MUTEX_H
#include <linux/types.h>
#include "types.h"
struct mutex {
unsigned char __opaque[64];
@@ -1,9 +1,9 @@
#ifndef _LINUX_PCI_H
#define _LINUX_PCI_H
#include <linux/types.h>
#include <linux/device.h>
#include <linux/io.h>
#include "types.h"
#include "device.h"
#include "io.h"
#include <stddef.h>
#define PCI_VENDOR_ID_AMD 0x1002U
@@ -12,6 +12,12 @@
#define PCI_ANY_ID (~0U)
/* MSI/MSI-X support */
#define PCI_IRQ_MSI 1U
#define PCI_IRQ_MSIX 2U
#define PCI_IRQ_LEGACY 4U
#define PCI_IRQ_NOLEGACY 8U
struct pci_device_id {
u32 vendor;
u32 device;
@@ -49,6 +55,11 @@ struct pci_driver {
extern int pci_enable_device(struct pci_dev *dev);
extern void pci_disable_device(struct pci_dev *dev);
extern void pci_set_master(struct pci_dev *dev);
extern int pci_alloc_irq_vectors(struct pci_dev *dev, int min_vecs, int max_vecs, unsigned int flags);
extern void pci_free_irq_vectors(struct pci_dev *dev);
extern int pci_irq_vector(struct pci_dev *dev, unsigned int nr);
extern int pci_enable_msi(struct pci_dev *dev);
extern void pci_disable_msi(struct pci_dev *dev);
extern void *pci_iomap(struct pci_dev *dev, unsigned int bar, size_t max_len);
extern void pci_iounmap(struct pci_dev *dev, void *addr, size_t size);
@@ -0,0 +1,6 @@
#ifndef _LINUX_SKB_H
#define _LINUX_SKB_H
#include "skbuff.h"
#endif
@@ -3,6 +3,8 @@
#include "types.h"
struct net_device;
struct sk_buff {
void *head;
void *data;
@@ -11,6 +13,12 @@ struct sk_buff {
unsigned int end;
};
struct sk_buff_head {
struct sk_buff *next;
struct sk_buff *prev;
u32 qlen;
};
extern struct sk_buff *alloc_skb(unsigned int size, gfp_t gfp_mask);
extern void kfree_skb(struct sk_buff *skb);
extern void skb_reserve(struct sk_buff *skb, unsigned int len);
@@ -20,5 +28,14 @@ extern void *skb_pull(struct sk_buff *skb, unsigned int len);
extern unsigned int skb_headroom(const struct sk_buff *skb);
extern unsigned int skb_tailroom(const struct sk_buff *skb);
extern void skb_trim(struct sk_buff *skb, unsigned int len);
extern void skb_queue_head_init(struct sk_buff_head *list);
extern void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk);
extern struct sk_buff *skb_dequeue(struct sk_buff_head *list);
extern void skb_queue_purge(struct sk_buff_head *list);
extern u32 skb_queue_len(const struct sk_buff_head *list);
extern int skb_queue_empty(const struct sk_buff_head *list);
extern struct sk_buff *__netdev_alloc_skb(struct net_device *dev, u32 length, gfp_t gfp_mask);
extern struct sk_buff *skb_copy(const struct sk_buff *src, gfp_t gfp);
extern struct sk_buff *skb_clone(const struct sk_buff *skb, gfp_t gfp);
#endif
@@ -1,7 +1,7 @@
#ifndef _LINUX_SPINLOCK_H
#define _LINUX_SPINLOCK_H
#include <linux/types.h>
#include "types.h"
typedef struct spinlock {
volatile unsigned char __locked;
@@ -1,8 +1,8 @@
#ifndef _LINUX_TIMER_H
#define _LINUX_TIMER_H
#include <linux/types.h>
#include <linux/compiler.h>
#include "types.h"
#include "compiler.h"
struct timer_list {
void (*function)(unsigned long data);
@@ -1,13 +1,15 @@
#ifndef _LINUX_WAIT_H
#define _LINUX_WAIT_H
#include <linux/types.h>
#include <linux/compiler.h>
#include "types.h"
#include "compiler.h"
struct wait_queue_head {
unsigned char __opaque[128];
};
typedef struct wait_queue_head wait_queue_head_t;
static inline void init_waitqueue_head(struct wait_queue_head *wq)
{
(void)wq;
@@ -98,6 +98,11 @@ extern void cfg80211_connect_bss(struct net_device *dev,
size_t resp_ie_len,
u16 status,
gfp_t gfp);
extern void cfg80211_new_sta(struct net_device *dev, const u8 *mac_addr,
struct station_parameters *params, gfp_t gfp);
extern void cfg80211_rx_mgmt(struct wireless_dev *wdev, u32 freq, int sig_dbm,
const u8 *buf, size_t len, gfp_t gfp);
extern void cfg80211_sched_scan_results(struct wiphy *wiphy, u64 reqid);
extern void cfg80211_ready_on_channel(struct wireless_dev *wdev,
u64 cookie,
struct ieee80211_channel *chan,
@@ -34,6 +34,48 @@ struct ieee80211_bss_conf {
u16 beacon_int;
};
enum ieee80211_sta_state {
IEEE80211_STA_NOTEXIST,
IEEE80211_STA_NONE,
IEEE80211_STA_AUTH,
IEEE80211_STA_ASSOC,
IEEE80211_STA_AUTHORIZED,
};
enum set_key_cmd {
SET_KEY,
DISABLE_KEY,
};
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);
int (*start)(struct ieee80211_hw *hw);
void (*stop)(struct ieee80211_hw *hw);
int (*add_interface)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void (*remove_interface)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
int (*config)(struct ieee80211_hw *hw, u32 changed);
void (*bss_info_changed)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info, u32 changed);
int (*sta_state)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, enum ieee80211_sta_state old_state,
enum ieee80211_sta_state new_state);
int (*set_key)(struct ieee80211_hw *hw, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct key_params *key);
void (*sw_scan_start)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *mac_addr);
void (*sw_scan_complete)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
int (*sched_scan_start)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, void *req);
void (*sched_scan_stop)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
};
#define BSS_CHANGED_ASSOC (1U << 0)
#define BSS_CHANGED_BSSID (1U << 1)
#define BSS_CHANGED_ERP_CTS_PROT (1U << 2)
#define BSS_CHANGED_HT (1U << 3)
#define BSS_CHANGED_BASIC_RATES (1U << 4)
#define BSS_CHANGED_BEACON_INT (1U << 5)
#define BSS_CHANGED_BANDWIDTH (1U << 6)
extern struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
const void *ops,
const char *requested_name);
@@ -43,5 +85,8 @@ extern void ieee80211_unregister_hw(struct ieee80211_hw *hw);
extern void ieee80211_queue_work(struct ieee80211_hw *hw, void *work);
extern void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted);
extern void ieee80211_connection_loss(struct ieee80211_vif *vif);
extern int ieee80211_start_tx_ba_session(struct ieee80211_sta *sta, u16 tid, u16 timeout);
extern int ieee80211_stop_tx_ba_session(struct ieee80211_sta *sta, u16 tid);
extern void ieee80211_beacon_loss(struct ieee80211_vif *vif);
#endif
@@ -11,6 +11,7 @@ pub use rust_impl::drm_shim;
pub use rust_impl::firmware;
pub use rust_impl::io;
pub use rust_impl::irq;
pub use rust_impl::list;
pub use rust_impl::mac80211;
pub use rust_impl::memory;
pub use rust_impl::net;
@@ -1,27 +1,104 @@
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::ffi::{c_char, c_void, CStr};
use std::ptr;
use syscall::CallFlags;
use std::sync::atomic::{fence, Ordering};
use std::sync::Mutex;
lazy_static::lazy_static! {
static ref TRANSLATION_FD: Option<usize> = {
libredox::call::open("/scheme/memory/translation",
syscall::flag::O_CLOEXEC as i32, 0)
libredox::call::open("/scheme/memory/translation", syscall::flag::O_CLOEXEC as i32, 0)
.ok()
.map(|fd| fd)
};
}
#[cfg(target_os = "redox")]
fn virt_to_phys(virt: usize) -> usize {
let raw = match *TRANSLATION_FD {
Some(fd) => fd,
None => return 0,
};
let mut buf = virt.to_ne_bytes();
let _ = libredox::call::call_ro(raw, &mut buf, CallFlags::empty(), &[]);
let _ = libredox::call::call_ro(raw, &mut buf, syscall::CallFlags::empty(), &[]);
usize::from_ne_bytes(buf)
}
#[cfg(not(target_os = "redox"))]
fn virt_to_phys(virt: usize) -> usize {
let _ = *TRANSLATION_FD;
virt
}
fn sanitize_align(align: usize) -> Option<usize> {
let align = align.max(1);
if align.is_power_of_two() {
Some(align)
} else {
align.checked_next_power_of_two()
}
}
fn crosses_boundary(addr: u64, size: usize, boundary: usize) -> bool {
if boundary == 0 || size == 0 {
return false;
}
let end = match addr.checked_add(size.saturating_sub(1) as u64) {
Some(end) => end,
None => return true,
};
let mask = !(boundary as u64 - 1);
(addr & mask) != (end & mask)
}
#[derive(Clone, Copy)]
struct PoolAllocation {
vaddr: usize,
dma: u64,
size: usize,
align: usize,
}
type AllocationList = Mutex<Vec<PoolAllocation>>;
#[repr(C)]
pub struct DmaPool {
pub name: *mut u8,
pub size: usize,
pub align: usize,
pub boundary: usize,
pub allocations: *mut c_void,
name_len: usize,
}
fn copy_pool_name(name: *const u8) -> (*mut u8, usize) {
if name.is_null() {
return (ptr::null_mut(), 0);
}
let c_name = unsafe { CStr::from_ptr(name.cast::<c_char>()) };
let bytes = c_name.to_bytes();
let mut owned = Vec::with_capacity(bytes.len() + 1);
owned.extend_from_slice(bytes);
owned.push(0);
let len = owned.len();
let ptr = owned.as_mut_ptr();
std::mem::forget(owned);
(ptr, len)
}
fn pool_allocations(pool: *mut DmaPool) -> Option<&'static AllocationList> {
if pool.is_null() {
return None;
}
let allocations = unsafe { (*pool).allocations.cast::<AllocationList>() };
if allocations.is_null() {
None
} else {
Some(unsafe { &*allocations })
}
}
#[no_mangle]
pub extern "C" fn dma_alloc_coherent(
_dev: *mut u8,
@@ -91,3 +168,237 @@ pub extern "C" fn dma_set_mask(_dev: *mut u8, _mask: u64) -> i32 {
pub extern "C" fn dma_set_coherent_mask(_dev: *mut u8, _mask: u64) -> i32 {
0
}
#[no_mangle]
pub extern "C" fn dma_pool_create(
name: *const u8,
_dev: *mut u8,
size: usize,
align: usize,
boundary: usize,
) -> *mut DmaPool {
if size == 0 {
return ptr::null_mut();
}
let Some(align) = sanitize_align(align) else {
return ptr::null_mut();
};
if boundary != 0 && size > boundary {
return ptr::null_mut();
}
let allocations = Box::new(Mutex::new(Vec::<PoolAllocation>::new()));
let (name_ptr, name_len) = copy_pool_name(name);
Box::into_raw(Box::new(DmaPool {
name: name_ptr,
size,
align,
boundary,
allocations: Box::into_raw(allocations).cast::<c_void>(),
name_len,
}))
}
#[no_mangle]
pub extern "C" fn dma_pool_destroy(pool: *mut DmaPool) {
if pool.is_null() {
return;
}
let allocations_ptr = unsafe { (*pool).allocations.cast::<AllocationList>() };
if !allocations_ptr.is_null() {
let allocations = unsafe { Box::from_raw(allocations_ptr) };
let entries = allocations
.lock()
.map(|entries| entries.clone())
.unwrap_or_default();
for entry in entries {
if let Ok(layout) = Layout::from_size_align(entry.size.max(1), entry.align.max(1)) {
unsafe { dealloc(entry.vaddr as *mut u8, layout) };
}
}
}
let pool = unsafe { Box::from_raw(pool) };
if !pool.name.is_null() && pool.name_len != 0 {
unsafe {
drop(Vec::from_raw_parts(pool.name, pool.name_len, pool.name_len));
}
}
}
#[no_mangle]
pub extern "C" fn dma_pool_alloc(pool: *mut DmaPool, _flags: u32, handle: *mut u64) -> *mut u8 {
if pool.is_null() || handle.is_null() {
return ptr::null_mut();
}
let pool_ref = unsafe { &*pool };
if pool_ref.size == 0 {
return ptr::null_mut();
}
let layout = match Layout::from_size_align(pool_ref.size, pool_ref.align.max(1)) {
Ok(layout) => layout,
Err(_) => return ptr::null_mut(),
};
let vaddr = unsafe { alloc_zeroed(layout) };
if vaddr.is_null() {
return ptr::null_mut();
}
let dma = virt_to_phys(vaddr as usize) as u64;
if dma == 0 || crosses_boundary(dma, pool_ref.size, pool_ref.boundary) {
unsafe { dealloc(vaddr, layout) };
return ptr::null_mut();
}
let Some(allocations) = pool_allocations(pool) else {
unsafe { dealloc(vaddr, layout) };
return ptr::null_mut();
};
let Ok(mut entries) = allocations.lock() else {
unsafe { dealloc(vaddr, layout) };
return ptr::null_mut();
};
entries.push(PoolAllocation {
vaddr: vaddr as usize,
dma,
size: pool_ref.size,
align: pool_ref.align.max(1),
});
unsafe { *handle = dma };
vaddr
}
#[no_mangle]
pub extern "C" fn dma_pool_free(pool: *mut DmaPool, vaddr: *mut u8, addr: u64) {
if pool.is_null() || vaddr.is_null() {
return;
}
let Some(allocations) = pool_allocations(pool) else {
return;
};
let Ok(mut entries) = allocations.lock() else {
return;
};
let Some(index) = entries
.iter()
.position(|entry| entry.vaddr == vaddr as usize || (addr != 0 && entry.dma == addr))
else {
return;
};
let entry = entries.swap_remove(index);
if let Ok(layout) = Layout::from_size_align(entry.size.max(1), entry.align.max(1)) {
unsafe { dealloc(entry.vaddr as *mut u8, layout) };
}
}
#[no_mangle]
pub extern "C" fn dma_sync_single_for_cpu(_dev: *mut u8, addr: u64, size: usize, _dir: u32) {
if addr == 0 || size == 0 {
return;
}
fence(Ordering::Acquire);
}
#[no_mangle]
pub extern "C" fn dma_sync_single_for_device(_dev: *mut u8, addr: u64, size: usize, _dir: u32) {
if addr == 0 || size == 0 {
return;
}
fence(Ordering::Release);
}
#[no_mangle]
pub extern "C" fn dma_map_page(
_dev: *mut u8,
page: *mut u8,
offset: usize,
size: usize,
_dir: u32,
) -> u64 {
if page.is_null() || size == 0 {
return 0;
}
let Some(vaddr) = (page as usize).checked_add(offset) else {
return 0;
};
virt_to_phys(vaddr) as u64
}
#[no_mangle]
pub extern "C" fn dma_unmap_page(_dev: *mut u8, _addr: u64, _size: usize, _dir: u32) {}
#[no_mangle]
pub extern "C" fn dma_mapping_error(_dev: *mut u8, addr: u64) -> i32 {
if addr == 0 {
1
} else {
0
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn dma_alloc_and_map_work_on_host() {
let mut handle = 0u64;
let vaddr = dma_alloc_coherent(ptr::null_mut(), 128, &mut handle, 0);
assert!(!vaddr.is_null());
assert_ne!(handle, 0);
assert_eq!(dma_mapping_error(ptr::null_mut(), handle), 0);
assert_eq!(dma_map_single(ptr::null_mut(), vaddr, 128, 0), handle);
dma_sync_single_for_cpu(ptr::null_mut(), handle, 128, 0);
dma_sync_single_for_device(ptr::null_mut(), handle, 128, 0);
dma_free_coherent(ptr::null_mut(), 128, vaddr, handle);
}
#[test]
fn dma_pool_lifecycle_tracks_allocations() {
let name = CString::new("iwlwifi-rx").expect("valid test CString");
let pool = dma_pool_create(name.as_ptr().cast::<u8>(), ptr::null_mut(), 256, 64, 0);
assert!(!pool.is_null());
let mut handle = 0u64;
let vaddr = dma_pool_alloc(pool, 0, &mut handle);
assert!(!vaddr.is_null());
assert_ne!(handle, 0);
let allocations = unsafe { &*((*pool).allocations.cast::<AllocationList>()) };
assert_eq!(allocations.lock().expect("lock allocations").len(), 1);
dma_pool_free(pool, vaddr, handle);
assert!(allocations.lock().expect("lock allocations").is_empty());
dma_pool_destroy(pool);
}
#[test]
fn dma_pool_rejects_impossible_boundary() {
let pool = dma_pool_create(ptr::null(), ptr::null_mut(), 1024, 16, 128);
assert!(pool.is_null());
}
#[test]
fn dma_map_page_and_error_checks_work() {
let mut page = [0u8; 64];
let dma = dma_map_page(ptr::null_mut(), page.as_mut_ptr(), 8, 16, 0);
assert_ne!(dma, 0);
assert_eq!(dma_mapping_error(ptr::null_mut(), dma), 0);
assert_eq!(dma_mapping_error(ptr::null_mut(), 0), 1);
dma_unmap_page(ptr::null_mut(), dma, 16, 0);
}
}
@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::ptr;
use std::sync::atomic::{fence, Ordering};
use std::sync::Mutex;
type PhysAddr = u64;
@@ -24,28 +25,26 @@ pub extern "C" fn ioremap(phys: PhysAddr, size: usize) -> *mut u8 {
size
);
let ptr = match redox_driver_sys::memory::MmioRegion::map(
match redox_driver_sys::memory::MmioRegion::map(
phys,
size,
redox_driver_sys::memory::CacheType::DeviceMemory,
redox_driver_sys::memory::MmioProt::READ_WRITE,
) {
Ok(region) => {
let p = region.as_ptr() as *mut u8;
let s = region.size();
let ptr = region.as_ptr() as *mut u8;
let size = region.size();
if let Ok(mut tracker) = MMIO_MAP_TRACKER.lock() {
tracker.insert(p as usize, MappedRegion { size: s });
tracker.insert(ptr as usize, MappedRegion { size });
}
std::mem::forget(region);
p
ptr
}
Err(e) => {
log::error!("ioremap: failed to map {:#x}+{:#x}: {:?}", phys, size, e);
ptr::null_mut()
}
};
ptr
}
}
#[no_mangle]
@@ -124,3 +123,69 @@ pub extern "C" fn writew(val: u16, addr: *mut u8) {
}
unsafe { ptr::write_volatile(addr as *mut u16, val) };
}
#[no_mangle]
pub extern "C" fn memcpy_toio(dst: *mut u8, src: *const u8, count: usize) {
if dst.is_null() || src.is_null() || count == 0 {
return;
}
unsafe { ptr::copy_nonoverlapping(src, dst, count) };
}
#[no_mangle]
pub extern "C" fn memcpy_fromio(dst: *mut u8, src: *const u8, count: usize) {
if dst.is_null() || src.is_null() || count == 0 {
return;
}
unsafe { ptr::copy_nonoverlapping(src, dst, count) };
}
#[no_mangle]
pub extern "C" fn memset_io(dst: *mut u8, val: u8, count: usize) {
if dst.is_null() || count == 0 {
return;
}
unsafe { ptr::write_bytes(dst, val, count) };
}
#[no_mangle]
pub extern "C" fn mb() {
fence(Ordering::SeqCst);
}
#[no_mangle]
pub extern "C" fn rmb() {
fence(Ordering::Acquire);
}
#[no_mangle]
pub extern "C" fn wmb() {
fence(Ordering::Release);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn io_copy_helpers_move_bytes() {
let mut dst = [0u8; 8];
let src = [1u8, 2, 3, 4, 5, 6, 7, 8];
memcpy_toio(dst.as_mut_ptr(), src.as_ptr(), src.len());
assert_eq!(dst, src);
let mut second = [0u8; 8];
memcpy_fromio(second.as_mut_ptr(), dst.as_ptr(), dst.len());
assert_eq!(second, src);
memset_io(second.as_mut_ptr(), 0xaa, second.len());
assert_eq!(second, [0xaa; 8]);
}
#[test]
fn io_barriers_are_callable() {
mb();
rmb();
wmb();
}
}
@@ -0,0 +1,197 @@
use std::ptr;
#[repr(C)]
pub struct ListHead {
pub next: *mut ListHead,
pub prev: *mut ListHead,
}
#[no_mangle]
pub extern "C" fn init_list_head(head: *mut ListHead) {
if head.is_null() {
return;
}
unsafe {
(*head).next = head;
(*head).prev = head;
}
}
#[no_mangle]
pub extern "C" fn list_add(new: *mut ListHead, head: *mut ListHead) {
if new.is_null() || head.is_null() {
return;
}
unsafe {
let next = (*head).next;
(*new).next = next;
(*new).prev = head;
(*head).next = new;
if !next.is_null() {
(*next).prev = new;
}
}
}
#[no_mangle]
pub extern "C" fn list_add_tail(new: *mut ListHead, head: *mut ListHead) {
if new.is_null() || head.is_null() {
return;
}
unsafe {
let prev = (*head).prev;
(*new).next = head;
(*new).prev = prev;
(*head).prev = new;
if !prev.is_null() {
(*prev).next = new;
}
}
}
#[no_mangle]
pub extern "C" fn list_del(entry: *mut ListHead) {
if entry.is_null() {
return;
}
unsafe {
let prev = (*entry).prev;
let next = (*entry).next;
if !prev.is_null() {
(*prev).next = next;
}
if !next.is_null() {
(*next).prev = prev;
}
(*entry).next = ptr::null_mut();
(*entry).prev = ptr::null_mut();
}
}
#[no_mangle]
pub extern "C" fn list_empty(head: *const ListHead) -> i32 {
if head.is_null() {
return 1;
}
if ptr::eq(unsafe { (*head).next } as *const ListHead, head) {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn list_splice(list: *mut ListHead, head: *mut ListHead) {
if list.is_null() || head.is_null() || list_empty(list) != 0 {
return;
}
unsafe {
let first = (*list).next;
let last = (*list).prev;
let at = (*head).next;
(*first).prev = head;
(*head).next = first;
(*last).next = at;
if !at.is_null() {
(*at).prev = last;
}
}
}
#[no_mangle]
pub extern "C" fn list_first_entry(head: *const ListHead, offset: usize) -> *mut u8 {
if head.is_null() || list_empty(head) != 0 {
return ptr::null_mut();
}
let first = unsafe { (*head).next };
if first.is_null() {
return ptr::null_mut();
}
(first as usize)
.checked_sub(offset)
.map_or(ptr::null_mut(), |entry| entry as *mut u8)
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::offset_of;
#[repr(C)]
struct Node {
value: u32,
link: ListHead,
}
#[test]
fn list_add_delete_and_first_entry_work() {
let mut head = ListHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
};
init_list_head(&mut head);
assert_eq!(list_empty(&head), 1);
let mut node = Node {
value: 7,
link: ListHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
},
};
list_add(&mut node.link, &mut head);
assert_eq!(list_empty(&head), 0);
let first = list_first_entry(&head, offset_of!(Node, link)).cast::<Node>();
assert_eq!(unsafe { (*first).value }, 7);
list_del(&mut node.link);
init_list_head(&mut head);
assert_eq!(list_empty(&head), 1);
}
#[test]
fn list_add_tail_and_splice_work() {
let mut dst = ListHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
};
let mut src = ListHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
};
init_list_head(&mut dst);
init_list_head(&mut src);
let mut node1 = Node {
value: 1,
link: ListHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
},
};
let mut node2 = Node {
value: 2,
link: ListHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
},
};
list_add_tail(&mut node1.link, &mut src);
list_add_tail(&mut node2.link, &mut src);
list_splice(&mut src, &mut dst);
let first = list_first_entry(&dst, offset_of!(Node, link)).cast::<Node>();
assert_eq!(unsafe { (*first).value }, 1);
assert!(std::ptr::eq(node1.link.next, &mut node2.link));
}
}
@@ -1,14 +1,68 @@
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::collections::HashMap;
use std::ffi::c_void;
use std::ptr;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use super::wireless::{wiphy_free, wiphy_new_nm, wiphy_register, wiphy_unregister, Wiphy};
use super::net::SkBuff;
use super::wireless::{
wiphy_free, wiphy_new_nm, wiphy_register, wiphy_unregister, KeyParams, Wiphy,
};
use super::workqueue::{schedule_work, WorkStruct};
const EINVAL: i32 = 22;
const EBUSY: i32 = 16;
lazy_static::lazy_static! {
static ref STA_REGISTRY: Mutex<HashMap<usize, StaRegistryEntry>> = Mutex::new(HashMap::new());
static ref BA_SESSIONS: Mutex<HashMap<usize, Vec<u16>>> = Mutex::new(HashMap::new());
}
#[derive(Clone, Copy)]
struct StaRegistryEntry {
hw: usize,
_vif: usize,
state: u32,
}
#[repr(C)]
pub struct Ieee80211Ops {
pub tx: Option<extern "C" fn(*mut Ieee80211Hw, *mut SkBuff)>,
pub start: Option<extern "C" fn(*mut Ieee80211Hw) -> i32>,
pub stop: Option<extern "C" fn(*mut Ieee80211Hw)>,
pub add_interface: Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif) -> i32>,
pub remove_interface: Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif)>,
pub config: Option<extern "C" fn(*mut Ieee80211Hw, u32) -> i32>,
pub bss_info_changed:
Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif, *mut Ieee80211BssConf, u32)>,
pub sta_state:
Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif, *mut Ieee80211Sta, u32) -> i32>,
pub set_key: Option<
extern "C" fn(
*mut Ieee80211Hw,
*mut Ieee80211Vif,
i32,
*mut Ieee80211Sta,
*mut KeyParams,
) -> i32,
>,
pub ampdu_action: Option<
extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif, *mut Ieee80211Sta, u16, u16, u16) -> i32,
>,
pub sw_scan_start: Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif, *const u8)>,
pub sw_scan_complete: Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif)>,
pub prepare_multicast: Option<extern "C" fn(*mut Ieee80211Hw, *mut c_void) -> u64>,
pub configure_filter: Option<extern "C" fn(*mut Ieee80211Hw, u32, *mut u32, u64)>,
pub sched_scan_start:
Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif, *mut c_void) -> i32>,
pub sched_scan_stop: Option<extern "C" fn(*mut Ieee80211Hw, *mut Ieee80211Vif)>,
}
#[repr(C)]
pub struct Ieee80211Hw {
pub wiphy: *mut Wiphy,
pub ops: *const Ieee80211Ops,
pub priv_data: *mut c_void,
pub registered: AtomicI32,
pub extra_tx_headroom: u32,
@@ -26,6 +80,7 @@ pub struct Ieee80211Vif {
}
#[repr(C)]
#[derive(Debug)]
pub struct Ieee80211Sta {
pub addr: [u8; 6],
pub drv_priv: *mut c_void,
@@ -39,6 +94,41 @@ pub struct Ieee80211BssConf {
pub beacon_int: u16,
}
pub const BSS_CHANGED_ASSOC: u32 = 1;
pub const BSS_CHANGED_BSSID: u32 = 2;
pub const BSS_CHANGED_ERP_CTS_PROT: u32 = 4;
pub const BSS_CHANGED_HT: u32 = 8;
pub const BSS_CHANGED_BASIC_RATES: u32 = 16;
pub const BSS_CHANGED_BEACON_INT: u32 = 32;
pub const BSS_CHANGED_BANDWIDTH: u32 = 64;
fn update_sta_registry(
hw: *mut Ieee80211Hw,
vif: *mut Ieee80211Vif,
sta: *mut Ieee80211Sta,
new_state: u32,
) {
if let Ok(mut registry) = STA_REGISTRY.lock() {
if new_state <= IEEE80211_STA_NONE {
registry.remove(&(sta as usize));
} else {
registry.insert(
sta as usize,
StaRegistryEntry {
hw: hw as usize,
_vif: vif as usize,
state: new_state,
},
);
}
}
if new_state <= IEEE80211_STA_NONE {
if let Ok(mut sessions) = BA_SESSIONS.lock() {
sessions.remove(&(sta as usize));
}
}
}
#[no_mangle]
pub extern "C" fn ieee80211_alloc_hw_nm(
priv_data_len: usize,
@@ -52,6 +142,7 @@ pub extern "C" fn ieee80211_alloc_hw_nm(
let mut hw = Box::new(Ieee80211Hw {
wiphy,
ops: ops.cast::<Ieee80211Ops>(),
priv_data: ptr::null_mut(),
registered: AtomicI32::new(0),
extra_tx_headroom: 0,
@@ -86,6 +177,9 @@ pub extern "C" fn ieee80211_free_hw(hw: *mut Ieee80211Hw) {
if hw.is_null() {
return;
}
if let Ok(mut registry) = STA_REGISTRY.lock() {
registry.retain(|_, entry| entry.hw != hw as usize);
}
unsafe {
let hw_box = Box::from_raw(hw);
if !hw_box.priv_data.is_null() {
@@ -103,10 +197,10 @@ pub extern "C" fn ieee80211_free_hw(hw: *mut Ieee80211Hw) {
#[no_mangle]
pub extern "C" fn ieee80211_register_hw(hw: *mut Ieee80211Hw) -> i32 {
if hw.is_null() {
return -22;
return -EINVAL;
}
if unsafe { &*hw }.registered.load(Ordering::Acquire) != 0 {
return -16;
return -EBUSY;
}
let rc = wiphy_register(unsafe { (*hw).wiphy });
if rc != 0 {
@@ -147,16 +241,207 @@ pub extern "C" fn ieee80211_connection_loss(vif: *mut Ieee80211Vif) {
unsafe { (*vif).cfg_assoc = false };
}
#[repr(C)]
pub struct Ieee80211RxStatus {
pub freq: u16,
pub band: u32,
pub signal: i8,
pub noise: i8,
pub rate_idx: u8,
pub flag: u32,
pub antenna: u8,
pub rx_flags: u32,
}
impl Default for Ieee80211RxStatus {
fn default() -> Self {
Self {
freq: 0,
band: 0,
signal: 0,
noise: 0,
rate_idx: 0,
flag: 0,
antenna: 0,
rx_flags: 0,
}
}
}
pub const RX_FLAG_MMIC_ERROR: u32 = 1 << 0;
pub const RX_FLAG_DECRYPTED: u32 = 1 << 1;
pub const RX_FLAG_MMIC_STRIPPED: u32 = 1 << 2;
pub const RX_FLAG_IV_STRIPPED: u32 = 1 << 3;
#[repr(C)]
pub struct Ieee80211TxInfo {
pub flags: u32,
pub band: u32,
pub hw_queue: u8,
pub rate_driver_data: [u8; 16],
}
pub const IEEE80211_TX_CTL_REQ_TX_STATUS: u32 = 1 << 0;
pub const IEEE80211_TX_CTL_NO_ACK: u32 = 1 << 1;
pub const IEEE80211_TX_CTL_CLEAR_PS_FILT: u32 = 1 << 2;
pub const IEEE80211_TX_CTL_FIRST_FRAGMENT: u32 = 1 << 3;
#[no_mangle]
pub extern "C" fn ieee80211_rx_irqsafe(hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
if hw.is_null() || skb.is_null() {
return;
}
}
#[no_mangle]
pub extern "C" fn ieee80211_tx_status(hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
if hw.is_null() || skb.is_null() {
return;
}
}
#[no_mangle]
pub extern "C" fn ieee80211_get_tid(skb: *const SkBuff) -> u8 {
if skb.is_null() {
return 0;
}
0
}
#[no_mangle]
pub extern "C" fn ieee80211_chandef_create(
chandef: *mut c_void,
channel: *const super::wireless::Ieee80211Channel,
_chan_type: u32,
) {
if chandef.is_null() || channel.is_null() {
return;
}
}
pub const IEEE80211_STA_NOTEXIST: u32 = 0;
pub const IEEE80211_STA_NONE: u32 = 1;
pub const IEEE80211_STA_AUTH: u32 = 2;
pub const IEEE80211_STA_ASSOC: u32 = 3;
pub const IEEE80211_STA_AUTHORIZED: u32 = 4;
#[no_mangle]
pub extern "C" fn ieee80211_start_tx_ba_session(
pub_sta: *mut Ieee80211Sta,
tid: u16,
_timeout: u16,
) -> i32 {
if pub_sta.is_null() || tid >= 16 {
return -EINVAL;
}
let Ok(mut sessions) = BA_SESSIONS.lock() else {
return -EINVAL;
};
let entry = sessions.entry(pub_sta as usize).or_default();
if entry.contains(&tid) {
return -EBUSY;
}
entry.push(tid);
0
}
#[no_mangle]
pub extern "C" fn ieee80211_stop_tx_ba_session(pub_sta: *mut Ieee80211Sta, tid: u16) -> i32 {
if pub_sta.is_null() || tid >= 16 {
return -EINVAL;
}
if let Ok(mut sessions) = BA_SESSIONS.lock() {
if let Some(entry) = sessions.get_mut(&(pub_sta as usize)) {
entry.retain(|existing| *existing != tid);
if entry.is_empty() {
sessions.remove(&(pub_sta as usize));
}
}
}
0
}
#[no_mangle]
pub extern "C" fn ieee80211_sta_state(
hw: *mut Ieee80211Hw,
vif: *mut Ieee80211Vif,
sta: *mut Ieee80211Sta,
_old_state: u32,
new_state: u32,
) -> i32 {
if hw.is_null() || vif.is_null() || sta.is_null() {
return -EINVAL;
}
update_sta_registry(hw, vif, sta, new_state);
let ops = unsafe { (*hw).ops };
if ops.is_null() {
return 0;
}
match unsafe { (*ops).sta_state } {
Some(callback) => callback(hw, vif, sta, new_state),
None => 0,
}
}
#[no_mangle]
pub extern "C" fn ieee80211_find_sta(hw: *mut Ieee80211Hw, addr: *const u8) -> *mut Ieee80211Sta {
if hw.is_null() || addr.is_null() {
return ptr::null_mut();
}
let Ok(registry) = STA_REGISTRY.lock() else {
return ptr::null_mut();
};
let wanted = unsafe { ptr::read(addr.cast::<[u8; 6]>()) };
for (sta_ptr, entry) in registry.iter() {
if entry.hw != hw as usize || entry.state <= IEEE80211_STA_NONE {
continue;
}
let sta = *sta_ptr as *mut Ieee80211Sta;
if sta.is_null() {
continue;
}
if wanted == unsafe { (*sta).addr } {
return sta;
}
}
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn ieee80211_beacon_loss(vif: *mut Ieee80211Vif) {
if vif.is_null() {
return;
}
unsafe { (*vif).cfg_assoc = false };
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rust_impl::workqueue::{flush_scheduled_work, WorkStruct};
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
use std::sync::atomic::AtomicBool;
static WORK_RAN: AtomicBool = AtomicBool::new(false);
static STA_CALLBACKS: AtomicI32 = AtomicI32::new(0);
extern "C" fn test_work(_work: *mut WorkStruct) {
WORK_RAN.store(true, AtomicOrdering::Release);
WORK_RAN.store(true, Ordering::Release);
}
extern "C" fn test_sta_state(
_hw: *mut Ieee80211Hw,
_vif: *mut Ieee80211Vif,
_sta: *mut Ieee80211Sta,
state: u32,
) -> i32 {
STA_CALLBACKS.store(state as i32, Ordering::Release);
0
}
#[test]
@@ -179,15 +464,15 @@ mod tests {
func: Some(test_work),
__opaque: [0; 64],
};
WORK_RAN.store(false, AtomicOrdering::Release);
WORK_RAN.store(false, Ordering::Release);
ieee80211_queue_work(hw, (&mut work as *mut WorkStruct).cast::<c_void>());
flush_scheduled_work();
assert!(WORK_RAN.load(AtomicOrdering::Acquire));
assert!(WORK_RAN.load(Ordering::Acquire));
ieee80211_free_hw(hw);
}
#[test]
fn connection_loss_clears_assoc_state() {
fn connection_loss_and_beacon_loss_clear_assoc_state() {
let mut vif = Ieee80211Vif {
addr: [0; 6],
drv_priv: ptr::null_mut(),
@@ -196,5 +481,110 @@ mod tests {
};
ieee80211_connection_loss(&mut vif);
assert!(!vif.cfg_assoc);
vif.cfg_assoc = true;
ieee80211_beacon_loss(&mut vif);
assert!(!vif.cfg_assoc);
}
#[test]
fn ieee80211_rx_status_default_and_flags_work() {
let status = Ieee80211RxStatus::default();
assert_eq!(status.freq, 0);
assert_eq!(status.band, 0);
assert_eq!(status.signal, 0);
assert_eq!(status.noise, 0);
assert_eq!(status.rate_idx, 0);
assert_eq!(status.flag, 0);
assert_eq!(status.antenna, 0);
assert_eq!(status.rx_flags, 0);
let combined = RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED;
assert_ne!(combined & RX_FLAG_DECRYPTED, 0);
assert_ne!(combined & RX_FLAG_IV_STRIPPED, 0);
assert_ne!(combined & RX_FLAG_MMIC_STRIPPED, 0);
assert_eq!(combined & RX_FLAG_MMIC_ERROR, 0);
}
#[test]
fn ieee80211_sta_registry_and_ba_sessions_work() {
let ops = Ieee80211Ops {
tx: None,
start: None,
stop: None,
add_interface: None,
remove_interface: None,
config: None,
bss_info_changed: None,
sta_state: Some(test_sta_state),
set_key: None,
ampdu_action: None,
sw_scan_start: None,
sw_scan_complete: None,
prepare_multicast: None,
configure_filter: None,
sched_scan_start: None,
sched_scan_stop: None,
};
let hw = ieee80211_alloc_hw_nm(
0,
(&ops as *const Ieee80211Ops).cast::<c_void>(),
ptr::null(),
);
assert!(!hw.is_null());
assert_eq!(unsafe { (*hw).ops }, &ops as *const Ieee80211Ops);
let mut vif = Ieee80211Vif {
addr: [0; 6],
drv_priv: ptr::null_mut(),
type_: 0,
cfg_assoc: false,
};
let mut sta = Ieee80211Sta {
addr: [1, 2, 3, 4, 5, 6],
drv_priv: ptr::null_mut(),
aid: 1,
};
STA_CALLBACKS.store(0, Ordering::Release);
assert_eq!(
ieee80211_sta_state(
hw,
&mut vif,
&mut sta,
IEEE80211_STA_NONE,
IEEE80211_STA_ASSOC
),
0
);
assert_eq!(
STA_CALLBACKS.load(Ordering::Acquire),
IEEE80211_STA_ASSOC as i32
);
assert!(std::ptr::eq(
ieee80211_find_sta(hw, sta.addr.as_ptr()),
&mut sta,
));
assert_eq!(ieee80211_start_tx_ba_session(&mut sta, 3, 100), 0);
assert_eq!(ieee80211_start_tx_ba_session(&mut sta, 3, 100), -16);
assert_eq!(ieee80211_stop_tx_ba_session(&mut sta, 3), 0);
assert_eq!(
ieee80211_sta_state(
hw,
&mut vif,
&mut sta,
IEEE80211_STA_ASSOC,
IEEE80211_STA_NONE
),
0
);
assert!(ieee80211_find_sta(hw, sta.addr.as_ptr()).is_null());
ieee80211_free_hw(hw);
}
#[test]
fn ieee80211_get_tid_returns_zero_for_null() {
assert_eq!(ieee80211_get_tid(ptr::null()), 0);
}
}
@@ -2,12 +2,13 @@ pub mod device;
pub mod dma;
pub mod drm_shim;
pub mod firmware;
pub mod mac80211;
pub mod net;
pub mod idr;
pub mod io;
pub mod irq;
pub mod list;
pub mod mac80211;
pub mod memory;
pub mod net;
pub mod pci;
pub mod sync;
pub mod timer;
@@ -1,7 +1,17 @@
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::ffi::c_void;
use std::ptr;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::atomic::{AtomicI32, AtomicU32, AtomicUsize, Ordering};
const NAPI_STATE_IDLE: i32 = 0;
const NAPI_STATE_SCHEDULED: i32 = 1;
#[repr(C)]
struct SkbSharedInfo {
refcount: AtomicUsize,
capacity: usize,
align: usize,
}
#[repr(C)]
pub struct SkBuff {
@@ -10,6 +20,19 @@ pub struct SkBuff {
pub len: u32,
pub tail: u32,
pub end: u32,
pub next: *mut SkBuff,
pub prev: *mut SkBuff,
pub network_header: i32,
pub mac_header: i32,
shared: *mut SkbSharedInfo,
}
#[repr(C)]
pub struct SkBuffHead {
pub next: *mut SkBuff,
pub prev: *mut SkBuff,
pub qlen: u32,
pub lock: u8,
}
#[repr(C)]
@@ -24,21 +47,45 @@ pub struct NetDevice {
pub ieee80211_ptr: *mut c_void,
pub priv_data: *mut c_void,
pub registered: AtomicI32,
pub tx_queue_state: AtomicU32,
pub device_attached: AtomicI32,
priv_alloc_size: usize,
priv_alloc_align: usize,
}
unsafe fn free_skb_buffer(skb: *mut SkBuff) {
#[repr(C)]
pub struct NapiStruct {
pub poll: Option<extern "C" fn(*mut NapiStruct, budget: i32) -> i32>,
pub dev: *mut NetDevice,
pub state: AtomicI32,
pub weight: i32,
}
unsafe fn release_skb_buffer(skb: *mut SkBuff) {
if skb.is_null() {
return;
}
let head = (*skb).head;
let end = (*skb).end as usize;
if !head.is_null() && end != 0 {
if let Ok(layout) = Layout::from_size_align(end, 16) {
dealloc(head, layout);
}
let shared = (*skb).shared;
if shared.is_null() {
return;
}
if (*shared).refcount.fetch_sub(1, Ordering::AcqRel) == 1 {
let capacity = (*shared).capacity.max(1);
let align = (*shared).align.max(1);
if !(*skb).head.is_null() {
if let Ok(layout) = Layout::from_size_align(capacity, align) {
dealloc((*skb).head, layout);
}
}
drop(Box::from_raw(shared));
}
}
fn skb_headroom_inner(skb: &SkBuff) -> u32 {
let headroom = unsafe { skb.data.offset_from(skb.head) };
u32::try_from(headroom).unwrap_or_default()
}
#[no_mangle]
@@ -53,12 +100,23 @@ pub extern "C" fn alloc_skb(size: u32, _gfp_mask: u32) -> *mut SkBuff {
return ptr::null_mut();
}
let shared = Box::into_raw(Box::new(SkbSharedInfo {
refcount: AtomicUsize::new(1),
capacity: capacity.max(1),
align: 16,
}));
Box::into_raw(Box::new(SkBuff {
head,
data: head,
len: 0,
tail: 0,
end: capacity as u32,
next: ptr::null_mut(),
prev: ptr::null_mut(),
network_header: 0,
mac_header: 0,
shared,
}))
}
@@ -68,7 +126,7 @@ pub extern "C" fn kfree_skb(skb: *mut SkBuff) {
return;
}
unsafe {
free_skb_buffer(skb);
release_skb_buffer(skb);
drop(Box::from_raw(skb));
}
}
@@ -80,16 +138,14 @@ pub extern "C" fn skb_reserve(skb: *mut SkBuff, len: u32) {
}
let skb_ref = unsafe { &mut *skb };
let headroom = unsafe { skb_ref.data.offset_from(skb_ref.head) };
let Ok(headroom) = u32::try_from(headroom) else {
return;
};
let headroom = skb_headroom_inner(skb_ref);
let new_headroom = headroom.saturating_add(len);
if new_headroom > skb_ref.end || skb_ref.tail != 0 || skb_ref.len != 0 {
return;
}
skb_ref.data = unsafe { skb_ref.head.add(new_headroom as usize) };
skb_ref.mac_header = new_headroom as i32;
}
#[no_mangle]
@@ -140,8 +196,8 @@ pub extern "C" fn skb_pull(skb: *mut SkBuff, len: u32) -> *mut u8 {
}
skb_ref.data = unsafe { skb_ref.data.add(len as usize) };
skb_ref.tail -= len;
skb_ref.len -= len;
skb_ref.tail = skb_ref.tail.saturating_sub(len);
skb_ref.len = skb_ref.len.saturating_sub(len);
skb_ref.data
}
@@ -151,9 +207,7 @@ pub extern "C" fn skb_headroom(skb: *const SkBuff) -> u32 {
return 0;
}
let skb_ref = unsafe { &*skb };
let headroom = unsafe { skb_ref.data.offset_from(skb_ref.head) };
u32::try_from(headroom).unwrap_or_default()
skb_headroom_inner(unsafe { &*skb })
}
#[no_mangle]
@@ -180,6 +234,194 @@ pub extern "C" fn skb_trim(skb: *mut SkBuff, len: u32) {
skb_ref.tail = new_len;
}
#[no_mangle]
pub extern "C" fn skb_queue_head_init(list: *mut SkBuffHead) {
if list.is_null() {
return;
}
unsafe {
(*list).next = ptr::null_mut();
(*list).prev = ptr::null_mut();
(*list).qlen = 0;
(*list).lock = 0;
}
}
#[no_mangle]
pub extern "C" fn skb_queue_tail(list: *mut SkBuffHead, newsk: *mut SkBuff) {
if list.is_null() || newsk.is_null() {
return;
}
unsafe {
(*newsk).next = ptr::null_mut();
(*newsk).prev = (*list).prev;
if (*list).prev.is_null() {
(*list).next = newsk;
} else {
(*(*list).prev).next = newsk;
}
(*list).prev = newsk;
if (*list).next.is_null() {
(*list).next = newsk;
}
(*list).qlen = (*list).qlen.saturating_add(1);
}
}
#[no_mangle]
pub extern "C" fn skb_dequeue(list: *mut SkBuffHead) -> *mut SkBuff {
if list.is_null() || unsafe { (*list).qlen } == 0 {
return ptr::null_mut();
}
unsafe {
let skb = (*list).next;
if skb.is_null() {
return ptr::null_mut();
}
(*list).next = (*skb).next;
if (*list).next.is_null() {
(*list).prev = ptr::null_mut();
} else {
(*(*list).next).prev = ptr::null_mut();
}
(*skb).next = ptr::null_mut();
(*skb).prev = ptr::null_mut();
(*list).qlen = (*list).qlen.saturating_sub(1);
skb
}
}
#[no_mangle]
pub extern "C" fn skb_queue_purge(list: *mut SkBuffHead) {
if list.is_null() {
return;
}
loop {
let skb = skb_dequeue(list);
if skb.is_null() {
break;
}
kfree_skb(skb);
}
}
#[no_mangle]
pub extern "C" fn skb_peek(list: *const SkBuffHead) -> *mut SkBuff {
if list.is_null() || unsafe { (*list).qlen } == 0 {
ptr::null_mut()
} else {
unsafe { (*list).next }
}
}
#[no_mangle]
pub extern "C" fn skb_queue_len(list: *const SkBuffHead) -> u32 {
if list.is_null() {
0
} else {
unsafe { (*list).qlen }
}
}
#[no_mangle]
pub extern "C" fn skb_queue_empty(list: *const SkBuffHead) -> i32 {
if skb_queue_len(list) == 0 {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn __netdev_alloc_skb(
_dev: *mut NetDevice,
length: u32,
gfp_mask: u32,
) -> *mut SkBuff {
alloc_skb(length, gfp_mask)
}
#[no_mangle]
pub extern "C" fn skb_copy(src: *const SkBuff, gfp: u32) -> *mut SkBuff {
if src.is_null() {
return ptr::null_mut();
}
let src_ref = unsafe { &*src };
let dst = alloc_skb(src_ref.end, gfp);
if dst.is_null() {
return ptr::null_mut();
}
let headroom = skb_headroom(src);
skb_reserve(dst, headroom);
let dst_data = skb_put(dst, src_ref.len);
if dst_data.is_null() {
kfree_skb(dst);
return ptr::null_mut();
}
if src_ref.len != 0 {
unsafe { ptr::copy_nonoverlapping(src_ref.data, dst_data, src_ref.len as usize) };
}
unsafe {
(*dst).network_header = src_ref.network_header;
(*dst).mac_header = src_ref.mac_header;
}
dst
}
#[no_mangle]
pub extern "C" fn skb_clone(skb: *const SkBuff, _gfp: u32) -> *mut SkBuff {
if skb.is_null() {
return ptr::null_mut();
}
let skb_ref = unsafe { &*skb };
if skb_ref.shared.is_null() {
return ptr::null_mut();
}
unsafe { &*skb_ref.shared }
.refcount
.fetch_add(1, Ordering::AcqRel);
Box::into_raw(Box::new(SkBuff {
head: skb_ref.head,
data: skb_ref.data,
len: skb_ref.len,
tail: skb_ref.tail,
end: skb_ref.end,
next: ptr::null_mut(),
prev: ptr::null_mut(),
network_header: skb_ref.network_header,
mac_header: skb_ref.mac_header,
shared: skb_ref.shared,
}))
}
#[no_mangle]
pub extern "C" fn skb_set_network_header(skb: *mut SkBuff, offset: i32) {
if skb.is_null() {
return;
}
unsafe { (*skb).network_header = offset };
}
#[no_mangle]
pub extern "C" fn skb_reset_mac_header(skb: *mut SkBuff) {
if skb.is_null() {
return;
}
unsafe {
(*skb).mac_header = skb_headroom_inner(&*skb) as i32;
}
}
#[no_mangle]
pub extern "C" fn alloc_netdev_mqs(
sizeof_priv: usize,
@@ -200,6 +442,8 @@ pub extern "C" fn alloc_netdev_mqs(
ieee80211_ptr: ptr::null_mut(),
priv_data: ptr::null_mut(),
registered: AtomicI32::new(0),
tx_queue_state: AtomicU32::new(0),
device_attached: AtomicI32::new(1),
priv_alloc_size: 0,
priv_alloc_align: 0,
});
@@ -304,6 +548,102 @@ pub extern "C" fn netif_carrier_ok(dev: *const NetDevice) -> i32 {
}
}
#[no_mangle]
pub extern "C" fn netif_napi_add(
dev: *mut NetDevice,
napi: *mut NapiStruct,
poll: Option<extern "C" fn(*mut NapiStruct, i32) -> i32>,
weight: i32,
) {
if napi.is_null() {
return;
}
unsafe {
(*napi).dev = dev;
(*napi).poll = poll;
(*napi).weight = weight;
(*napi).state.store(NAPI_STATE_IDLE, Ordering::Release);
}
}
#[no_mangle]
pub extern "C" fn napi_schedule(napi: *mut NapiStruct) {
if napi.is_null() {
return;
}
let napi_ref = unsafe { &*napi };
if napi_ref
.state
.compare_exchange(
NAPI_STATE_IDLE,
NAPI_STATE_SCHEDULED,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
if let Some(poll) = napi_ref.poll {
let _ = poll(napi, napi_ref.weight);
}
}
}
#[no_mangle]
pub extern "C" fn napi_complete_done(napi: *mut NapiStruct, work_done: i32) -> i32 {
if napi.is_null() || work_done < 0 {
return 0;
}
unsafe { &*napi }
.state
.store(NAPI_STATE_IDLE, Ordering::Release);
if work_done < unsafe { (*napi).weight } {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn netif_tx_wake_queue(dev: *mut NetDevice, queue_idx: u16) {
if dev.is_null() || queue_idx >= 32 {
return;
}
let mask = !(1u32 << queue_idx);
let _ = unsafe { &*dev }
.tx_queue_state
.fetch_and(mask, Ordering::AcqRel);
}
#[no_mangle]
pub extern "C" fn netif_tx_stop_queue(dev: *mut NetDevice, queue_idx: u16) {
if dev.is_null() || queue_idx >= 32 {
return;
}
let mask = 1u32 << queue_idx;
let _ = unsafe { &*dev }
.tx_queue_state
.fetch_or(mask, Ordering::AcqRel);
}
#[no_mangle]
pub extern "C" fn netif_device_attach(dev: *mut NetDevice) {
if dev.is_null() {
return;
}
unsafe { &*dev }.device_attached.store(1, Ordering::Release);
}
#[no_mangle]
pub extern "C" fn netif_device_detach(dev: *mut NetDevice) {
if dev.is_null() {
return;
}
unsafe { &*dev }.device_attached.store(0, Ordering::Release);
}
#[cfg(test)]
mod tests {
use super::*;
@@ -311,11 +651,17 @@ mod tests {
use std::sync::atomic::AtomicUsize;
static SETUP_CALLS: AtomicUsize = AtomicUsize::new(0);
static NAPI_POLLS: AtomicUsize = AtomicUsize::new(0);
extern "C" fn test_setup(_dev: *mut NetDevice) {
SETUP_CALLS.fetch_add(1, Ordering::AcqRel);
}
extern "C" fn test_napi_poll(_napi: *mut NapiStruct, budget: i32) -> i32 {
NAPI_POLLS.fetch_add(1, Ordering::AcqRel);
budget - 1
}
#[test]
fn skb_allocation_and_growth_work() {
let skb = alloc_skb(64, 0);
@@ -343,9 +689,49 @@ mod tests {
kfree_skb(skb);
}
#[test]
fn skb_queue_copy_and_clone_work() {
let skb = alloc_skb(32, 0);
assert!(!skb.is_null());
skb_reserve(skb, 4);
let data = skb_put(skb, 6);
assert!(!data.is_null());
unsafe { ptr::copy_nonoverlapping([1u8, 2, 3, 4, 5, 6].as_ptr(), data, 6) };
skb_set_network_header(skb, 2);
skb_reset_mac_header(skb);
let copy = skb_copy(skb, 0);
assert!(!copy.is_null());
assert_eq!(unsafe { (*copy).len }, 6);
assert_eq!(unsafe { (*copy).network_header }, 2);
let clone = skb_clone(skb, 0);
assert!(!clone.is_null());
assert_eq!(unsafe { (*clone).data }, unsafe { (*skb).data });
let mut queue = SkBuffHead {
next: ptr::null_mut(),
prev: ptr::null_mut(),
qlen: 123,
lock: 1,
};
skb_queue_head_init(&mut queue);
skb_queue_tail(&mut queue, skb);
skb_queue_tail(&mut queue, copy);
assert_eq!(skb_queue_len(&queue), 2);
assert_eq!(skb_queue_empty(&queue), 0);
assert_eq!(skb_peek(&queue), skb);
assert_eq!(skb_dequeue(&mut queue), skb);
assert_eq!(skb_queue_len(&queue), 1);
kfree_skb(skb);
skb_queue_purge(&mut queue);
assert_eq!(skb_queue_empty(&queue), 1);
kfree_skb(clone);
}
#[test]
fn net_device_carrier_tracking_works() {
let name = CString::new("wlan%d").unwrap();
let name = CString::new("wlan%d").expect("valid test CString");
let dev = alloc_netdev_mqs(
0usize,
name.as_ptr().cast::<u8>(),
@@ -364,9 +750,9 @@ mod tests {
}
#[test]
fn net_device_setup_and_registration_work() {
fn net_device_setup_registration_and_queue_state_work() {
SETUP_CALLS.store(0, Ordering::Release);
let name = CString::new("wlan%d").unwrap();
let name = CString::new("wlan%d").expect("valid test CString");
let dev = alloc_netdev_mqs(
32usize,
name.as_ptr().cast::<u8>(),
@@ -380,8 +766,44 @@ mod tests {
assert_eq!(register_netdev(dev), 0);
assert_eq!(unsafe { (*dev).registered.load(Ordering::Acquire) }, 1);
assert_eq!(register_netdev(dev), -16);
netif_tx_stop_queue(dev, 2);
assert_ne!(
unsafe { (*dev).tx_queue_state.load(Ordering::Acquire) } & (1 << 2),
0
);
netif_tx_wake_queue(dev, 2);
assert_eq!(
unsafe { (*dev).tx_queue_state.load(Ordering::Acquire) } & (1 << 2),
0
);
netif_device_detach(dev);
assert_eq!(unsafe { (*dev).device_attached.load(Ordering::Acquire) }, 0);
netif_device_attach(dev);
assert_eq!(unsafe { (*dev).device_attached.load(Ordering::Acquire) }, 1);
unregister_netdev(dev);
assert_eq!(unsafe { (*dev).registered.load(Ordering::Acquire) }, 0);
free_netdev(dev);
}
#[test]
fn napi_schedule_and_complete_work() {
let mut napi = NapiStruct {
poll: None,
dev: ptr::null_mut(),
state: AtomicI32::new(99),
weight: 0,
};
NAPI_POLLS.store(0, Ordering::Release);
netif_napi_add(ptr::null_mut(), &mut napi, Some(test_napi_poll), 8);
assert_eq!(napi.weight, 8);
napi_schedule(&mut napi);
assert_eq!(NAPI_POLLS.load(Ordering::Acquire), 1);
assert_eq!(napi.state.load(Ordering::Acquire), NAPI_STATE_SCHEDULED);
assert_eq!(napi_complete_done(&mut napi, 4), 1);
assert_eq!(napi.state.load(Ordering::Acquire), NAPI_STATE_IDLE);
}
}
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::os::raw::c_ulong;
use std::ptr;
use std::sync::Mutex;
@@ -7,8 +8,14 @@ use redox_driver_sys::pci::{enumerate_pci_all, PciDevice, PciDeviceInfo, PciLoca
const EINVAL: i32 = 22;
const ENODEV: i32 = 19;
const EIO: i32 = 5;
const EBUSY: i32 = 16;
const PCI_ANY_ID: u32 = !0;
pub const PCI_IRQ_MSI: u32 = 1;
pub const PCI_IRQ_MSIX: u32 = 2;
pub const PCI_IRQ_LEGACY: u32 = 4;
pub const PCI_IRQ_NOLEGACY: u32 = 8;
#[repr(C)]
#[derive(Default)]
pub struct Device {
@@ -46,6 +53,14 @@ pub struct PciDeviceId {
driver_data: c_ulong,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct MsixEntry {
pub vector: u32,
pub entry: u16,
pub _pad: u16,
}
impl Default for PciDev {
fn default() -> Self {
PciDev {
@@ -71,9 +86,16 @@ struct CurrentDevice {
ptr: usize,
}
#[derive(Clone)]
struct AllocatedVectors {
_flags: u32,
vectors: Vec<i32>,
}
lazy_static::lazy_static! {
static ref CURRENT_DEVICE: Mutex<Option<CurrentDevice>> = Mutex::new(None);
static ref REGISTERED_PROBE: Mutex<Option<PciDriverProbe>> = Mutex::new(None);
static ref IRQ_VECTORS: Mutex<HashMap<usize, AllocatedVectors>> = Mutex::new(HashMap::new());
}
pub const PCI_VENDOR_ID_AMD: u16 = 0x1002;
@@ -106,6 +128,12 @@ fn open_current_device(dev: *mut PciDev) -> Result<PciDevice, i32> {
})
}
fn clear_irq_vectors_for_ptr(dev_ptr: usize) {
if let Ok(mut vectors) = IRQ_VECTORS.lock() {
vectors.remove(&dev_ptr);
}
}
fn matches_id(info: &PciDeviceInfo, id: &PciDeviceId) -> bool {
let class =
((info.class_code as u32) << 16) | ((info.subclass as u32) << 8) | info.prog_if as u32;
@@ -180,6 +208,7 @@ fn replace_current_device(location: PciLocation, dev_ptr: *mut PciDev) {
location,
ptr: dev_ptr as usize,
}) {
clear_irq_vectors_for_ptr(previous.ptr);
unsafe { drop(Box::from_raw(previous.ptr as *mut PciDev)) };
}
}
@@ -188,11 +217,53 @@ fn replace_current_device(location: PciLocation, dev_ptr: *mut PciDev) {
fn clear_current_device() {
if let Ok(mut state) = CURRENT_DEVICE.lock() {
if let Some(previous) = state.take() {
clear_irq_vectors_for_ptr(previous.ptr);
unsafe { drop(Box::from_raw(previous.ptr as *mut PciDev)) };
}
}
}
fn allocate_vectors(dev: *mut PciDev, min_vecs: i32, max_vecs: i32, flags: u32) -> i32 {
if dev.is_null() || min_vecs <= 0 || max_vecs <= 0 || min_vecs > max_vecs {
return -EINVAL;
}
if flags & (PCI_IRQ_MSI | PCI_IRQ_MSIX | PCI_IRQ_LEGACY) == 0 {
return -EINVAL;
}
let base_irq = unsafe { (*dev).irq as i32 };
if base_irq <= 0 {
return -ENODEV;
}
let dev_key = dev as usize;
let Ok(mut vectors) = IRQ_VECTORS.lock() else {
return -EINVAL;
};
if vectors.contains_key(&dev_key) {
return -EBUSY;
}
let count = if flags & PCI_IRQ_MSIX != 0 {
max_vecs
} else {
1
};
if count < min_vecs {
return -EINVAL;
}
let allocated = (0..count).map(|index| base_irq + index).collect::<Vec<_>>();
vectors.insert(
dev_key,
AllocatedVectors {
_flags: flags,
vectors: allocated,
},
);
count
}
#[no_mangle]
pub extern "C" fn pci_enable_device(dev: *mut PciDev) -> i32 {
if dev.is_null() {
@@ -337,6 +408,82 @@ pub struct PciDriver {
remove: Option<PciDriverRemove>,
}
#[no_mangle]
pub extern "C" fn pci_alloc_irq_vectors(
dev: *mut PciDev,
min_vecs: i32,
max_vecs: i32,
flags: u32,
) -> i32 {
allocate_vectors(dev, min_vecs, max_vecs, flags)
}
#[no_mangle]
pub extern "C" fn pci_free_irq_vectors(dev: *mut PciDev) {
if dev.is_null() {
return;
}
clear_irq_vectors_for_ptr(dev as usize);
}
#[no_mangle]
pub extern "C" fn pci_irq_vector(dev: *mut PciDev, vector_idx: i32) -> i32 {
if dev.is_null() || vector_idx < 0 {
return -EINVAL;
}
let Ok(vectors) = IRQ_VECTORS.lock() else {
return -EINVAL;
};
let Some(allocated) = vectors.get(&(dev as usize)) else {
return -EINVAL;
};
allocated
.vectors
.get(vector_idx as usize)
.copied()
.unwrap_or(-EINVAL)
}
#[no_mangle]
pub extern "C" fn pci_enable_msi(dev: *mut PciDev) -> i32 {
pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI)
}
#[no_mangle]
pub extern "C" fn pci_disable_msi(dev: *mut PciDev) {
pci_free_irq_vectors(dev);
}
#[no_mangle]
pub extern "C" fn pci_enable_msix_range(
dev: *mut PciDev,
entries: *mut MsixEntry,
minvec: i32,
maxvec: i32,
) -> i32 {
if entries.is_null() {
return -EINVAL;
}
let count = pci_alloc_irq_vectors(dev, minvec, maxvec, PCI_IRQ_MSIX);
if count < 0 {
return count;
}
for index in 0..count {
unsafe {
(*entries.add(index as usize)).vector = pci_irq_vector(dev, index) as u32;
}
}
count
}
#[no_mangle]
pub extern "C" fn pci_disable_msix(dev: *mut PciDev) {
pci_free_irq_vectors(dev);
}
#[no_mangle]
pub extern "C" fn pci_register_driver(drv: *mut PciDriver) -> i32 {
if drv.is_null() {
@@ -439,3 +586,49 @@ pub extern "C" fn pci_unregister_driver(drv: *mut PciDriver) {
}
log::info!("pci_unregister_driver: cleared registered PCI driver state");
}
#[cfg(test)]
mod tests {
use super::*;
fn test_dev(irq: u32) -> PciDev {
PciDev {
irq,
..PciDev::default()
}
}
#[test]
fn pci_irq_vector_lifecycle_works() {
let mut dev = test_dev(32);
assert_eq!(pci_alloc_irq_vectors(&mut dev, 1, 1, PCI_IRQ_MSI), 1);
assert_eq!(pci_irq_vector(&mut dev, 0), 32);
assert_eq!(pci_alloc_irq_vectors(&mut dev, 1, 1, PCI_IRQ_MSI), -16);
pci_free_irq_vectors(&mut dev);
assert_eq!(pci_irq_vector(&mut dev, 0), -22);
}
#[test]
fn pci_msix_range_populates_entries() {
let mut dev = test_dev(40);
let mut entries = [MsixEntry::default(); 3];
assert_eq!(
pci_enable_msix_range(&mut dev, entries.as_mut_ptr(), 2, 3),
3
);
assert_eq!(entries[0].vector, 40);
assert_eq!(entries[1].vector, 41);
assert_eq!(entries[2].vector, 42);
pci_disable_msix(&mut dev);
}
#[test]
fn pci_rejects_invalid_irq_vector_requests() {
let mut dev = test_dev(0);
assert_eq!(pci_enable_msi(&mut dev), -19);
assert_eq!(
pci_alloc_irq_vectors(ptr::null_mut(), 1, 1, PCI_IRQ_MSI),
-22
);
}
}
@@ -1,4 +1,6 @@
use std::sync::atomic::{AtomicU8, Ordering};
use std::ptr;
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering};
use std::time::{Duration, Instant};
const UNLOCKED: u8 = 0;
const LOCKED: u8 = 1;
@@ -154,14 +156,17 @@ pub extern "C" fn irqs_disabled() -> bool {
IRQ_DEPTH.load(Ordering::Acquire) > 0
}
use std::ptr;
#[repr(C)]
pub struct Completion {
done: AtomicU8,
_padding: [u8; 63],
}
#[repr(C)]
pub struct AtomicT {
value: AtomicI32,
}
#[no_mangle]
pub extern "C" fn init_completion(c: *mut Completion) {
if c.is_null() {
@@ -186,6 +191,14 @@ pub extern "C" fn complete(c: *mut Completion) {
unsafe { &*c }.done.store(1, Ordering::Release);
}
#[no_mangle]
pub extern "C" fn complete_all(c: *mut Completion) {
if c.is_null() {
return;
}
unsafe { &*c }.done.store(u8::MAX, Ordering::Release);
}
#[no_mangle]
pub extern "C" fn wait_for_completion(c: *mut Completion) {
if c.is_null() {
@@ -196,6 +209,31 @@ pub extern "C" fn wait_for_completion(c: *mut Completion) {
}
}
#[no_mangle]
pub extern "C" fn wait_for_completion_timeout(c: *mut Completion, timeout_ms: u64) -> i32 {
if c.is_null() {
return 0;
}
if unsafe { &*c }.done.load(Ordering::Acquire) != 0 {
return 1;
}
let deadline = Instant::now()
.checked_add(Duration::from_millis(timeout_ms))
.unwrap_or_else(Instant::now);
loop {
if unsafe { &*c }.done.load(Ordering::Acquire) != 0 {
return 1;
}
if Instant::now() >= deadline {
return 0;
}
std::thread::yield_now();
}
}
#[no_mangle]
pub extern "C" fn reinit_completion(c: *mut Completion) {
if c.is_null() {
@@ -204,6 +242,109 @@ pub extern "C" fn reinit_completion(c: *mut Completion) {
unsafe { &*c }.done.store(0, Ordering::Release);
}
#[no_mangle]
pub extern "C" fn atomic_set(v: *mut AtomicT, i: i32) {
if v.is_null() {
return;
}
unsafe { &*v }.value.store(i, Ordering::SeqCst);
}
#[no_mangle]
pub extern "C" fn atomic_read(v: *const AtomicT) -> i32 {
if v.is_null() {
return 0;
}
unsafe { &*v }.value.load(Ordering::SeqCst)
}
#[no_mangle]
pub extern "C" fn atomic_add(i: i32, v: *mut AtomicT) {
if v.is_null() {
return;
}
unsafe { &*v }.value.fetch_add(i, Ordering::SeqCst);
}
#[no_mangle]
pub extern "C" fn atomic_sub(i: i32, v: *mut AtomicT) {
if v.is_null() {
return;
}
unsafe { &*v }.value.fetch_sub(i, Ordering::SeqCst);
}
#[no_mangle]
pub extern "C" fn atomic_inc(v: *mut AtomicT) {
atomic_add(1, v);
}
#[no_mangle]
pub extern "C" fn atomic_dec(v: *mut AtomicT) {
atomic_sub(1, v);
}
#[no_mangle]
pub extern "C" fn atomic_inc_and_test(v: *mut AtomicT) -> i32 {
if v.is_null() {
return 0;
}
if unsafe { &*v }.value.fetch_add(1, Ordering::SeqCst) + 1 == 0 {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn atomic_dec_and_test(v: *mut AtomicT) -> i32 {
if v.is_null() {
return 0;
}
if unsafe { &*v }.value.fetch_sub(1, Ordering::SeqCst) - 1 == 0 {
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn atomic_add_return(i: i32, v: *mut AtomicT) -> i32 {
if v.is_null() {
return 0;
}
unsafe { &*v }.value.fetch_add(i, Ordering::SeqCst) + i
}
#[no_mangle]
pub extern "C" fn atomic_sub_return(i: i32, v: *mut AtomicT) -> i32 {
if v.is_null() {
return 0;
}
unsafe { &*v }.value.fetch_sub(i, Ordering::SeqCst) - i
}
#[no_mangle]
pub extern "C" fn atomic_xchg(v: *mut AtomicT, new: i32) -> i32 {
if v.is_null() {
return 0;
}
unsafe { &*v }.value.swap(new, Ordering::SeqCst)
}
#[no_mangle]
pub extern "C" fn atomic_cmpxchg(v: *mut AtomicT, old: i32, new: i32) -> i32 {
if v.is_null() {
return 0;
}
match unsafe { &*v }
.value
.compare_exchange(old, new, Ordering::SeqCst, Ordering::SeqCst)
{
Ok(previous) | Err(previous) => previous,
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -228,4 +369,48 @@ mod tests {
local_irq_enable();
assert!(!irqs_disabled());
}
#[test]
fn atomic_operations_cover_all_paths() {
let mut value = AtomicT {
value: AtomicI32::new(0),
};
atomic_set(&mut value, 3);
assert_eq!(atomic_read(&value), 3);
atomic_add(4, &mut value);
assert_eq!(atomic_read(&value), 7);
atomic_sub(2, &mut value);
assert_eq!(atomic_read(&value), 5);
atomic_inc(&mut value);
atomic_dec(&mut value);
assert_eq!(atomic_add_return(5, &mut value), 10);
assert_eq!(atomic_sub_return(3, &mut value), 7);
assert_eq!(atomic_xchg(&mut value, 11), 7);
assert_eq!(atomic_cmpxchg(&mut value, 10, 12), 11);
assert_eq!(atomic_cmpxchg(&mut value, 11, 13), 11);
assert_eq!(atomic_read(&value), 13);
atomic_set(&mut value, -1);
assert_eq!(atomic_inc_and_test(&mut value), 1);
atomic_set(&mut value, 1);
assert_eq!(atomic_dec_and_test(&mut value), 1);
}
#[test]
fn completion_timeout_and_complete_all_work() {
let mut completion = Completion {
done: AtomicU8::new(0),
_padding: [0; 63],
};
assert_eq!(wait_for_completion_timeout(&mut completion, 1), 0);
complete_all(&mut completion);
assert_eq!(wait_for_completion_timeout(&mut completion, 1), 1);
reinit_completion(&mut completion);
assert_eq!(wait_for_completion_timeout(&mut completion, 1), 0);
complete(&mut completion);
wait_for_completion(&mut completion);
assert_eq!(wait_for_completion_timeout(&mut completion, 1), 1);
}
}
@@ -1,10 +1,28 @@
use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::collections::HashMap;
use std::ffi::c_void;
use std::ptr;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use super::net::{netif_carrier_off, netif_carrier_on, NetDevice};
#[derive(Clone, Default)]
struct WirelessEventState {
new_sta: Option<[u8; 6]>,
mgmt_rx_freq: u32,
mgmt_rx_signal: i32,
mgmt_rx_len: usize,
mgmt_tx_cookie: u64,
mgmt_tx_len: usize,
mgmt_tx_ack: bool,
sched_scan_reqid: u64,
}
lazy_static::lazy_static! {
static ref WIRELESS_EVENTS: Mutex<HashMap<usize, WirelessEventState>> = Mutex::new(HashMap::new());
}
#[repr(C)]
pub struct Wiphy {
pub priv_data: *mut c_void,
@@ -76,6 +94,15 @@ pub struct StationParameters {
pub sta_flags_set: u32,
}
fn update_event_state<F>(key: usize, update: F)
where
F: FnOnce(&mut WirelessEventState),
{
if let Ok(mut events) = WIRELESS_EVENTS.lock() {
update(events.entry(key).or_default());
}
}
#[no_mangle]
pub extern "C" fn wiphy_new_nm(
_ops: *const c_void,
@@ -114,6 +141,9 @@ pub extern "C" fn wiphy_free(wiphy: *mut Wiphy) {
if wiphy.is_null() {
return;
}
if let Ok(mut events) = WIRELESS_EVENTS.lock() {
events.remove(&(wiphy as usize));
}
unsafe {
let wiphy_box = Box::from_raw(wiphy);
if !wiphy_box.priv_data.is_null() {
@@ -283,6 +313,254 @@ pub extern "C" fn cfg80211_ready_on_channel(
) {
}
#[repr(C)]
pub struct Ieee80211Channel {
pub band: u32,
pub center_freq: u16,
pub hw_value: u16,
pub flags: u32,
pub max_power: i8,
pub max_reg_power: i8,
pub max_antenna_gain: i8,
pub beacon_found: bool,
}
pub const NL80211_BAND_2GHZ: u32 = 0;
pub const NL80211_BAND_5GHZ: u32 = 1;
pub const NL80211_BAND_6GHZ: u32 = 2;
pub const IEEE80211_CHAN_DISABLED: u32 = 1 << 0;
pub const IEEE80211_CHAN_NO_IR: u32 = 1 << 1;
pub const IEEE80211_CHAN_RADAR: u32 = 1 << 2;
pub const IEEE80211_CHAN_NO_HT40PLUS: u32 = 1 << 3;
pub const IEEE80211_CHAN_NO_HT40MINUS: u32 = 1 << 4;
pub const IEEE80211_CHAN_NO_OFDM: u32 = 1 << 5;
pub const IEEE80211_CHAN_NO_80MHZ: u32 = 1 << 6;
pub const IEEE80211_CHAN_NO_160MHZ: u32 = 1 << 7;
#[repr(C)]
pub struct Ieee80211Rate {
pub flags: u32,
pub bitrate: u16,
pub hw_value: u16,
pub hw_value_short: u16,
}
pub const IEEE80211_RATE_SHORT_PREAMBLE: u32 = 1 << 0;
pub const IEEE80211_RATE_MANDATORY: u32 = 1 << 1;
pub const IEEE80211_RATE_ERP_G: u32 = 1 << 2;
#[repr(C)]
pub struct Ieee80211SupportedBand {
pub channels: *mut Ieee80211Channel,
pub n_channels: usize,
pub bitrates: *mut Ieee80211Rate,
pub n_bitrates: usize,
pub ht_cap: *mut c_void,
pub vht_cap: *mut c_void,
}
#[no_mangle]
pub extern "C" fn wiphy_bands_append(
wiphy: *mut Wiphy,
band_idx: u32,
band: *mut Ieee80211SupportedBand,
) -> i32 {
if wiphy.is_null() || band.is_null() {
return -22;
}
if band_idx > NL80211_BAND_6GHZ {
return -22;
}
let band_ref = unsafe { &*band };
if band_ref.n_channels == 0 || band_ref.channels.is_null() {
return -22;
}
0
}
#[repr(C)]
pub struct Cfg80211Bss {
pub bssid: [u8; 6],
pub channel: *mut Ieee80211Channel,
pub signal: i16,
pub capability: u16,
pub beacon_interval: u16,
pub ies: *const u8,
pub ies_len: usize,
}
#[no_mangle]
pub extern "C" fn cfg80211_inform_bss(
wiphy: *mut Wiphy,
wdev: *mut WirelessDev,
_freq: u32,
bssid: *const u8,
_tsf: u64,
capability: u16,
beacon_interval: u16,
ies: *const u8,
ies_len: usize,
signal: i32,
_gfp: u32,
) -> *mut Cfg80211Bss {
if wiphy.is_null() || wdev.is_null() || bssid.is_null() {
return ptr::null_mut();
}
let mut bssid_bytes = [0; 6];
unsafe {
ptr::copy_nonoverlapping(bssid, bssid_bytes.as_mut_ptr(), bssid_bytes.len());
}
let bss = Box::new(Cfg80211Bss {
bssid: bssid_bytes,
channel: ptr::null_mut(),
signal: signal.clamp(i16::MIN as i32, i16::MAX as i32) as i16,
capability,
beacon_interval,
ies,
ies_len,
});
Box::into_raw(bss)
}
#[no_mangle]
pub extern "C" fn cfg80211_put_bss(bss: *mut Cfg80211Bss) {
if bss.is_null() {
return;
}
unsafe {
drop(Box::from_raw(bss));
}
}
#[no_mangle]
pub extern "C" fn cfg80211_get_bss(
wiphy: *mut Wiphy,
band: u32,
_bssid: *const u8,
_ssid: *const u8,
_ssid_len: usize,
_bss_type: u32,
_privacy: u32,
) -> *mut Cfg80211Bss {
if wiphy.is_null() || band > NL80211_BAND_6GHZ {
return ptr::null_mut();
}
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn cfg80211_new_sta(
dev: *mut c_void,
mac_addr: *const u8,
_params: *const StationParameters,
_gfp: u32,
) {
if dev.is_null() || mac_addr.is_null() {
return;
}
let wdev = netdev_to_wireless_dev(dev);
if wdev.is_null() || unsafe { (*wdev).wiphy }.is_null() {
return;
}
let mut addr = [0u8; 6];
unsafe { ptr::copy_nonoverlapping(mac_addr, addr.as_mut_ptr(), addr.len()) };
update_event_state(unsafe { (*wdev).wiphy as usize }, |state| {
state.new_sta = Some(addr)
});
}
#[no_mangle]
pub extern "C" fn cfg80211_rx_mgmt(
wdev: *mut WirelessDev,
freq: u32,
sig_dbm: i32,
buf: *const u8,
len: usize,
_gfp: u32,
) {
if wdev.is_null() || (buf.is_null() && len != 0) {
return;
}
update_event_state(wdev as usize, |state| {
state.mgmt_rx_freq = freq;
state.mgmt_rx_signal = sig_dbm;
state.mgmt_rx_len = len;
});
}
#[no_mangle]
pub extern "C" fn cfg80211_mgmt_tx_status(
wdev: *mut WirelessDev,
cookie: u64,
buf: *const u8,
len: usize,
ack: bool,
_gfp: u32,
) {
if wdev.is_null() || (buf.is_null() && len != 0) {
return;
}
update_event_state(wdev as usize, |state| {
state.mgmt_tx_cookie = cookie;
state.mgmt_tx_len = len;
state.mgmt_tx_ack = ack;
});
}
#[no_mangle]
pub extern "C" fn cfg80211_sched_scan_results(wiphy: *mut Wiphy, reqid: u64) {
if wiphy.is_null() {
return;
}
update_event_state(wiphy as usize, |state| state.sched_scan_reqid = reqid);
}
#[no_mangle]
pub extern "C" fn ieee80211_channel_to_frequency(chan: u32, band: u32) -> u32 {
match band {
NL80211_BAND_2GHZ => match chan {
14 => 2484,
1..=13 => 2407 + chan * 5,
_ => 0,
},
NL80211_BAND_5GHZ => 5000 + chan * 5,
NL80211_BAND_6GHZ => {
if chan == 2 {
5935
} else if chan >= 1 {
5950 + chan * 5
} else {
0
}
}
_ => 0,
}
}
#[no_mangle]
pub extern "C" fn ieee80211_frequency_to_channel(freq: u32) -> u32 {
match freq {
2484 => 14,
2412..=2472 => (freq - 2407) / 5,
5000..=5895 => (freq - 5000) / 5,
5935 => 2,
5955..=7115 => (freq - 5950) / 5,
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -303,7 +581,7 @@ mod tests {
#[test]
fn scan_and_connect_lifecycle_updates_wireless_state() {
let name = CString::new("wlan%d").unwrap();
let name = CString::new("wlan%d").expect("valid test CString");
let dev = alloc_netdev_mqs(0, name.as_ptr().cast::<u8>(), 0, None, 1, 1);
assert!(!dev.is_null());
@@ -366,4 +644,78 @@ mod tests {
wiphy_free(wiphy);
free_netdev(dev);
}
#[test]
fn ieee80211_channel_creation_and_flags_work() {
let channel = Ieee80211Channel {
band: NL80211_BAND_5GHZ,
center_freq: 5180,
hw_value: 36,
flags: IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR | IEEE80211_CHAN_NO_80MHZ,
max_power: 20,
max_reg_power: 23,
max_antenna_gain: 6,
beacon_found: true,
};
assert_eq!(channel.band, NL80211_BAND_5GHZ);
assert_eq!(channel.center_freq, 5180);
assert_eq!(channel.hw_value, 36);
assert_ne!(channel.flags & IEEE80211_CHAN_NO_IR, 0);
assert_ne!(channel.flags & IEEE80211_CHAN_RADAR, 0);
assert_ne!(channel.flags & IEEE80211_CHAN_NO_80MHZ, 0);
assert_eq!(channel.flags & IEEE80211_CHAN_DISABLED, 0);
assert!(channel.beacon_found);
}
#[test]
fn cfg80211_events_and_channel_frequency_conversions_work() {
let name = CString::new("wlan%d").expect("valid test CString");
let dev = alloc_netdev_mqs(0, name.as_ptr().cast::<u8>(), 0, None, 1, 1);
assert!(!dev.is_null());
let wiphy = wiphy_new_nm(ptr::null(), 0, ptr::null());
assert!(!wiphy.is_null());
let mut wdev = WirelessDev {
wiphy,
netdev: dev.cast::<c_void>(),
iftype: 0,
scan_in_flight: false,
scan_aborted: false,
connecting: false,
connected: false,
locally_generated: false,
last_status: 0,
last_reason: 0,
has_bssid: false,
last_bssid: [0; 6],
};
unsafe { (*dev).ieee80211_ptr = (&mut wdev as *mut WirelessDev).cast::<c_void>() };
let sta = [6u8, 5, 4, 3, 2, 1];
cfg80211_new_sta(dev.cast::<c_void>(), sta.as_ptr(), ptr::null(), 0);
cfg80211_rx_mgmt(&mut wdev, 2412, -42, sta.as_ptr(), sta.len(), 0);
cfg80211_mgmt_tx_status(&mut wdev, 99, sta.as_ptr(), sta.len(), true, 0);
cfg80211_sched_scan_results(wiphy, 1234);
let events = WIRELESS_EVENTS.lock().expect("wireless events lock");
let wiphy_state = events.get(&(wiphy as usize)).expect("wiphy event state");
assert_eq!(wiphy_state.new_sta, Some(sta));
assert_eq!(wiphy_state.sched_scan_reqid, 1234);
let wdev_state = events
.get(&((&mut wdev as *mut WirelessDev) as usize))
.expect("wdev event state");
assert_eq!(wdev_state.mgmt_rx_freq, 2412);
assert_eq!(wdev_state.mgmt_rx_signal, -42);
assert_eq!(wdev_state.mgmt_tx_cookie, 99);
assert!(wdev_state.mgmt_tx_ack);
drop(events);
assert_eq!(ieee80211_channel_to_frequency(1, NL80211_BAND_2GHZ), 2412);
assert_eq!(ieee80211_channel_to_frequency(36, NL80211_BAND_5GHZ), 5180);
assert_eq!(ieee80211_frequency_to_channel(2484), 14);
assert_eq!(ieee80211_frequency_to_channel(5955), 1);
wiphy_free(wiphy);
free_netdev(dev);
}
}
File diff suppressed because it is too large Load Diff
Submodule recipes/core/relibc/source.pre-preservation-test added at 24a481364f