Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@
url = https://github.com/libc0607/rtl8733bu-20230626.git
branch = v5.13.0.1
shallow = true
[submodule "reference/mt76"]
path = reference/mt76
url = https://github.com/openwrt/mt76.git
branch = master
shallow = true
320 changes: 320 additions & 0 deletions docs/mt7612u.md

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion reference/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# reference/ — vendor kernel drivers (git submodules)
# reference/ — reference kernel drivers (git submodules)

devourer is a userspace re-implementation of Realtek's out-of-tree Wi-Fi
drivers. The vendor kernel modules live here as **git submodules** so every
developer works against the same fork + commit devourer treats as ground-truth,
without committing driver source into devourer's own history.

One entry is not a Realtek vendor drop: `mt76` is the mainline Linux driver for
the MediaTek parts, pinned for the same reason — it is the ground-truth the
MT7612U backend is derived from and generated against.

## Fetching

```sh
Expand All @@ -28,6 +32,7 @@ upstream history.
| `rtl8852bu` | `josephnef/rtl8852bu` (fork of morrownr/rtl8852bu-20250826) | default | Kestrel — 8852BU/8832BU (Wi-Fi 6) |
| `rtl8852cu` | `josephnef/rtl8852cu` (fork of morrownr/rtl8852cu-20251113) | default | Kestrel — 8852CU/8832CU (Wi-Fi 6) |
| `rtl8733bu-20230626` | `libc0607/rtl8733bu-20230626` | `v5.13.0.1` | RTL8733B — 8731BU/8733BU (HALMAC 87xx) |
| `mt76` | `openwrt/mt76` | `master` @ `be5ce79` | MediaTek — MT7612U (mainline, not a vendor drop) |

## Why these forks

Expand Down Expand Up @@ -75,6 +80,13 @@ upstream history.
`hal/` is generated from it and the already hardware-validated artifacts were
not replaced with untested ones. `docs/rtl8733b.md` records what was compared.

- **openwrt/mt76 for MT7612U.** The MediaTek parts have no out-of-tree vendor
drop to mirror: mt76 *is* the reference, maintained in mainline Linux and
BSD-3-Clause-Clear rather than GPL-2-only, which is why `src/mt7612u/` can
carry ported sequences at all. Pinned at `be5ce79` so
`tools/extract_mt7612u_tables.py --check` and every register sequence
transcribed into `src/mt7612u/` stay re-verifiable from a fresh checkout.

## Consumers

- `tools/extract_*.py` read PHY / firmware / txpwr tables out of these trees to
Expand Down
1 change: 1 addition & 0 deletions reference/mt76
Submodule mt76 added at be5ce7
9 changes: 9 additions & 0 deletions src/mt7612u/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
bringup
tests/api_link
tests/frame_shape
tests/field_macros
*.o
*.d
firmware/
wrlog.txt
mculog.txt
46 changes: 46 additions & 0 deletions src/mt7612u/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Standalone: this subtree is not reachable from the project's CMakeLists.txt.
# tests/api_link.c links against the public header alone, which is what keeps
# that an honest test of the exported surface.
CC ?= gcc
CFLAGS ?= -O2 -g -Wall -Wextra -Wno-unused-parameter
# -MMD -MP: without header deps, editing internal.h leaves stale objects with
# the old struct layout linked against new ones. That corrupts the device
# struct at runtime and presents as a wild pointer, not a build problem.
DEPFLAGS = -MMD -MP
LDLIBS = -lusb-1.0 -lpthread
LIBSRCS = $(wildcard *.c)
TESTS = tests/api_link tests/frame_shape tests/field_macros
SRCS = $(LIBSRCS) tools/bringup.c $(TESTS:=.c)
OBJS = $(SRCS:.c=.o)
LIBOBJS = $(LIBSRCS:.c=.o)
DEPS = $(OBJS:.o=.d)

all: bringup $(TESTS)

bringup: $(LIBOBJS) tools/bringup.o
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

# Links against the public header only, so a declaration with no definition
# is a link error here rather than a surprise at the first caller.
tests/api_link: $(LIBOBJS) tests/api_link.o
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

tests/frame_shape: $(LIBOBJS) tests/frame_shape.o
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

tests/field_macros: $(LIBOBJS) tests/field_macros.o
$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)

%.o: %.c
$(CC) $(CFLAGS) $(DEPFLAGS) -I. -Iinclude -c -o $@ $<

# No hardware and no privileges needed: safe to run anywhere. `bringup` is a
# dependency but is not run: nothing else compiles it, so without it here a
# change that breaks a gate ships and no test notices.
check: $(TESTS) bringup
@for t in $(TESTS); do ./$$t || exit 1; done

clean:
rm -f $(OBJS) $(DEPS) bringup $(TESTS)
-include $(DEPS)
.PHONY: all check clean
136 changes: 136 additions & 0 deletions src/mt7612u/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# src/mt7612u — MediaTek MT7612U

**Not reachable from `CMakeLists.txt` yet.** This subtree is a complete,
self-contained C library for the part — a public header, its own transport, no
dependency on `RtlAdapter` — plus the bring-up harness that produced every
measurement in `docs/mt7612u.md`. Wiring it in behind `IRtlDevice` is a
follow-up PR; nothing in the shipped library links against this today.

It builds and tests on its own:

```sh
make -C src/mt7612u # -> src/mt7612u/bringup
make -C src/mt7612u check # offline tests: no hardware, no privileges
sudo ./src/mt7612u/bringup regs
```

Measurements, methods and limits: [`../../docs/mt7612u.md`](../../docs/mt7612u.md).

## Layout

| file | what |
|---|---|
| `usb.c` | libusb transport: EP0 vendor register access, sync bulk, open/claim/reset |
| `async.c` | event thread, 16-deep RX ring, 32-slot TX pool |
| `mcu.c` | in-band MCU command framing (EP 8 out, EP 5 in, 4-bit sequence) |
| `fw.c` | ROM patch + ILM/DLM firmware upload |
| `eeprom.c` | 512-byte EEPROM: identity, TX power tables, RX gain |
| `init.c` | power-on, MAC initvals, mac_start/stop, EP-4 flush |
| `phy.c` | band/bandwidth/TX power registers, channel + calibration sequence |
| `tx.c` | TXWI + TXINFO construction |
| `rx.c` | RXWI parse, per-chain RSSI, rate decode |
| `radiotap.c` | `send_packet` / `send_packets` (USB chaining via `NEXT_VLD`) |
| `caps.c` | TSF, capability descriptor, ACK responder |
| `tools/bringup.c` | one subcommand per verified gate |
| `tests/` | offline tests (`make check`): public-API link, frame shapes |
| `initvals.h` | **generated** — see Provenance |

## The receiver must never run undrained

Enabling MAC RX with nothing reading the bulk-IN endpoint wedges this part
*below* the USB level: `libusb_reset_device`, the sysfs `authorized` toggle
and rebinding the kernel driver all fail to recover it, and only a physical
replug does. So `mt_mac_start()` takes the receiver as an explicit argument,
Comment thread
josephnef marked this conversation as resolved.
`mt7612u_start()` enables RX only when `mt7612u_rx_start()` is already
running, and every gate that turns RX on starts the ring *first*.

## Portability

Done here, because these are correctness issues regardless of compiler:

- `FIELD_PREP`/`FIELD_GET` no longer use `__builtin_ctz`. MSVC has no such
builtin, and its `_BitScanForward` takes an out-parameter, so it cannot
appear in a constant expression - which these must be, since `FIELD_PREP`
initialises static tables. `MT_CTZ` is a constant expression everywhere and
folds to one instruction. `tests/field_macros` checks it against the
builtin over all 32 single-bit and all 528 contiguous masks, and fails to
compile if it ever stops being constant-foldable.
- The shift macro was named `_SHIFT`. Leading underscore plus a capital is
reserved to the implementation in every scope.
- `<libusb.h>` (this project's spelling) is tried first, with the
distribution's `<libusb-1.0/libusb.h>` as the fallback.

**Not** done here: `async.c` uses pthreads and `usb.c` uses `nanosleep` /
`clock_gettime`. This project has no C threading or time shim - its shim is
the C++ standard library, which every other backend uses directly. Building
a throwaway C shim now would be deleted at integration, so those two files
keep POSIX until the subtree joins the build. They are the only two.

## Firmware

Needs `mt7662_rom_patch.bin` and `mt7662.bin` from `linux-firmware`
(`/lib/firmware/mediatek/`, zstd-compressed on most distributions). Not
vendored here. Point `bringup` at a directory holding the decompressed pair:

```sh
zstd -d /lib/firmware/mediatek/mt7662{,_rom_patch}.bin.zst -o firmware/
```

## Gates

Each subcommand is a hardware check that fails loudly, in dependency order:

```
regs registers + EEPROM round-trip chan channel set, 20 MHz
fw ROM patch + firmware + MCU ack tx inject at a fixed rate
init full bring-up + register-stream log rx monitor receive
caps capabilities, TSF, 40 MHz soak sync vs async throughput
pwr TX power vs the kernel's values ampdu aggregation A/B
gateg per-frame rate control ack ACK responder (needs a stimulus)
rtap send_packet / send_packets hop channel-switch cost
```

`make` here builds it as `./bringup`, which is what the hardware notes use.
The integration PR adds a CMake target for the same source, named
`mt7612uprobe` to sit beside `pcieprobe` / `kestrelprobe` / `rtl8733bprobe`, so
the chip-specific tool is not the one part of this backend that only a second
build system can produce.

`sweep`, `coding` and `vht` take a width as their fourth argument, in the
`MT7612U_BW_*` numbering — `0` = 20, `1` = 40, `2` = 80 MHz:

```sh
./bringup sweep 149 120 2 # VHT ladder at 80 MHz, control channel 149
```

The witness has to listen at the same width (`DEVOURER_BW=40|80` for
devourer's own `rxdemo`). A 20 MHz receiver decodes *none* of an 80 MHz
frame — which makes it a good negative control and a misleading oracle.

At 80 MHz the HT ladder is skipped: 802.11n has no 80 MHz, so a rate word
naming `PHY=HT` with `BW=80` is not a wide HT frame, it is an unspecified one.

## Provenance

Register sequences and descriptor layouts are derived from `openwrt/mt76`
(`mt76x2/`, `mt76x02*`, `usb.c`), BSD-3-Clause-Clear, Copyright (C) 2016 Felix
Fietkau, (C) 2018 Lorenzo Bianconi / Stanislaw Gruszka. Files carrying ported
sequences keep that notice. The tree is pinned as `reference/mt76` at commit
`be5ce79`.

`initvals.h` is **generated** from it, not transcribed:

```sh
tools/extract_mt7612u_tables.py # regenerate
tools/extract_mt7612u_tables.py --check # byte-compare the checked-in file
```

The generator resolves the symbolic register names against `mt76x02_regs.h` and
evaluates the four `DEFAULT_PROT_CFG_*` macros, so a mistyped address cannot
survive as a plausible-looking number. It reproduces the previously hand-typed
table byte for byte, all sixty rows.

Two things here are **not** ports and were proven on air rather than copied:
the `MT_TXD_INFO_NEXT_VLD` USB chaining in `radiotap.c`, and the ACK responder
in `caps.c`. One thing copied from mt76 was **wrong** — see the TSF note in the
docs.
Loading
Loading