Skip to content

Firmware Architecture

r0r0x-xx edited this page Jul 30, 2026 · 1 revision

Firmware & System Architecture

Two operating systems, one chip

Cortex-A7 core RISC-V SCR1 core
OS Linux 5.10.160 RT-Thread (real-time OS)
Role Mission / userspace Deterministic hardware control
Owns USB gadget console, rpmsg clients, userspace tooling Both SX1262 radios (SPI), BME280+ICM42670 (I2C0), GPS (UART0)
Boots From U-Boot, standard Linux boot Started by U-Boot before Linux hands off; firmware embedded inside uboot.img/trust.img

Neither core can directly touch the other's peripherals — Linux never talks to the radios/sensors/GPS registers directly; it only ever talks to the RISC-V core's own request/response protocol over shared memory.

IPC: rpmsg

rpmsg ("remote processor messaging") is a lightweight, shared-memory-backed messaging protocol standard in AMP designs. Each side creates named endpoints; a message to a given endpoint address gets delivered to whichever side registered it.

Two endpoints matter for this project:

Endpoint name Address Purpose
rpmsg-command 0x4008 Radio/command control — start/stop uplink, inject a raw telecommand, query state (radio_test)
rpmsg-sensor 0x4006 Sensor queries (sensor_test)

The Linux-side client (radio_test, built from src/radio-client/radio_test.c) opens the relevant endpoint via a Linux kernel rpmsg_ctrl/rpmsg_char device, sends a small binary request, and blocks (with a timeout) for the RT-Thread side's response. On the RT-Thread side, command_service.c registers the endpoint and dispatches requests via a single switch on the first request byte (the opcode).

This request/response protocol is entirely separate from the CCSDS Space Packet Protocol that flies over RF — rpmsg is how Linux tells the RISC-V core "transmit this SPP telecommand" or "what's your current state," not the wire format itself. See CCSDS / Space Packet Protocol for that layer.

Command dispatch on the RT-Thread side

command_service.c's command_apid_handler() is the single dispatch point for every telecommand received on the uplink radio (radio0, 918 MHz) — the same function handles a telecommand whether it arrived over real RF or was injected locally via radio_test tcsend <apid> <payload> over rpmsg. This is deliberate and important for the attack scripts in this repo: injecting a command via USB and receiving the identical command over real RF exercise the exact same code path, with no separate validation layer for either source.

The dispatcher recognizes telecommand APIDs 0x01–0x13 (see the CCSDS page for the full table) and, for anything it doesn't recognize, always replies with a structurally valid ERROR telemetry frame (APID 0x009) — useful for black-box reconnaissance (see Attack 00).

Telemetry

A periodic worker (command_service_telemetry_worker(), called on every iteration of the RT-Thread main loop) drives several independent timers, each firing its own downlink broadcast on its own interval — sensor telemetry (~10.5s), sync/idle filler frames, and (since the GPS work in this project) a periodic NAV frame reporting current GPS state. None of these need a command to trigger them; they transmit automatically, in the clear, the whole time the board is powered.

The USB gadget console

Since the board has no external UART or Ethernet by default, all human/tooling interaction goes through a USB gadget console — a configfs-based USB gadget (VID:PID 2207:0011, generic serial bulk function, not CDC-ACM) that lands on a menu-driven program, pwnsat-console, running on ttyGS0. See Getting Started for how to talk to it, and Hardware Anatomy for why this and the flashing connector are mutually exclusive with USB host mode.

The console menu offers a live sensor/telemetry dashboard, a simulated CAN-bus console (SpaceCAN, ported from FlatSat), and a debug shell — the debug shell is where radio_test/sensor_test and everything the attack scripts in this repo drive actually execute.

Boot sequence, in order

  1. Boot ROM → SPL (DDR init) → U-Boot proper.
  2. U-Boot loads and starts the RISC-V core's firmware (rtthread.bin, embedded inside uboot.img/trust.img at build time) — the RISC-V side is running and owns its peripherals before Linux even starts.
  3. U-Boot hands off to the Linux kernel on the Cortex-A7 core.
  4. Linux boots normally (busybox init, rcS), bringing up the USB gadget and starting pwnsat-console on ttyGS0.
  5. Once both cores are up, rpmsg endpoints are established and the two sides can talk.

If you rebuild the RISC-V firmware, always rebuild uboot after mcu — uboot embeds a copy of rtthread.bin at build time, so an out-of-order build silently ships stale MCU firmware. See Getting Started.

Clone this wiki locally