Add v3.05.0 safety review documentation and Hamilton test suite docs
- firmware/custom-v305.mdx: watchdog, timeout protection, error codes, I2C return checks, do_tune() safety, DiSEqC fixes, hardware validation - tools/safety-testing.mdx: 55-test adversarial suite covering DiSEqC abuse, tune parameter abuse, I2C address abuse, state machine violations, boundary conditions, rapid-fire stress, and invalid vendor commands - Updated version comparison with v3.05 tab and architectural table - Sidebar entries for both new pages
This commit is contained in:
parent
7223fcf810
commit
8b4aacab81
4 changed files with 434 additions and 16 deletions
181
site/src/content/docs/firmware/custom-v305.mdx
Normal file
181
site/src/content/docs/firmware/custom-v305.mdx
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
---
|
||||
title: Custom Firmware v3.05
|
||||
description: Safety-hardened firmware with software watchdog, timeout protection on all I2C/USB/GPIF paths, and comprehensive error reporting.
|
||||
---
|
||||
|
||||
import { Badge, Aside, Steps } from '@astrojs/starlight/components';
|
||||
|
||||
Firmware v3.05.0 is the result of a systematic safety review applied to the entire codebase. Every infinite-spin loop has been replaced with timeout-protected equivalents, a software watchdog cuts LNB power if the main loop stalls, and every failure mode now sets a specific error code readable by the host. <Badge text="v3.05" variant="success" /> <Badge text="Safety" variant="caution" />
|
||||
|
||||
Built on the [v3.04 codebase](/firmware/custom-v301/) — same SDCC + fx2lib toolchain, same stock-compatible command set, plus all custom commands from [v3.02](/firmware/custom-v302/) through v3.04.
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Version ID | `0x030500` |
|
||||
| Date | 2026-02-16 |
|
||||
| Code size | 13,079 / 15,360 bytes (85%) |
|
||||
| XRAM | 218 / 512 bytes (43%) |
|
||||
| Stack | 132 bytes available |
|
||||
| Source lines | 2,256 |
|
||||
| New commands | None (safety hardening of existing paths) |
|
||||
|
||||
## Motivation
|
||||
|
||||
The SkyWalker-1 controls an LNB power supply capable of 750 mA at 18V with no hardware watchdog on the FX2LP. A firmware hang leaves the power supply energized indefinitely. Prior firmware versions contained multiple paths where a stuck I2C bus, an unresponsive GPIF, or a slow EP0 transfer could spin forever without recovery.
|
||||
|
||||
A structured safety review identified 21 issues across 4 severity levels:
|
||||
|
||||
| Severity | Count | Description |
|
||||
|----------|-------|-------------|
|
||||
| Critical | 3 | Infinite hangs in I2C bus scan, `do_tune()`, and DiSEqC Timer2 waits |
|
||||
| High | 6 | Unprotected GPIF/EP0/EP2 waits, unchecked I2C returns in sweep functions |
|
||||
| Medium | 7 | Missing error codes, payload validation, lock-bit magic numbers |
|
||||
| Low | 5 | Code clarity, documentation, saturation guards |
|
||||
|
||||
## Software Watchdog
|
||||
|
||||
A Timer0-based software watchdog provides a safety backstop for any remaining or unknown hang paths.
|
||||
|
||||
**Architecture:**
|
||||
|
||||
- Timer0 runs in Mode 1 (16-bit, no auto-reload) at 4 MHz (48 MHz / 12)
|
||||
- Overflow period: 16.384 ms per tick
|
||||
- Watchdog window: 122 ticks = ~2 seconds
|
||||
- The main loop calls `wdt_kick()` on every iteration to reset the counter
|
||||
- Long-running operations (spectrum sweep, blind scan) call `wdt_kick()` inside their loops
|
||||
|
||||
**If the watchdog fires:**
|
||||
|
||||
<Steps>
|
||||
1. Timer0 ISR sets `IOA` to cut LNB power (`PIN_PWR_EN` off, `PIN_PWR_DIS` on)
|
||||
2. ISR sets `wdt_armed = 2` (fired state) — prevents re-arming
|
||||
3. Main loop detects `wdt_armed == 2` on next iteration (if it recovers)
|
||||
4. Main loop sets `last_error = ERR_WDT_FIRED` (0x0D) and clears `config_status`
|
||||
5. Host can read `ERR_WDT_FIRED` via `GET_LAST_ERROR` (0xBC)
|
||||
</Steps>
|
||||
|
||||
<Aside type="caution">
|
||||
The watchdog shares `IOA` with the main loop via read-modify-write. To avoid RMW races between the ISR and main-loop GPIO operations, `wdt_armed` uses a tri-state protocol: `0` = off, `1` = armed, `2` = fired. The ISR only writes `IOA` once (on fire), and `wdt_kick()` refuses to re-arm when `wdt_armed == 2`.
|
||||
</Aside>
|
||||
|
||||
## Timeout Protection
|
||||
|
||||
Every bare spin loop in the firmware has been replaced with a countdown-protected equivalent. These are the paths that could previously hang forever:
|
||||
|
||||
| Path | Location | Timeout | Error Code |
|
||||
|------|----------|---------|------------|
|
||||
| `i2c_wait_done()` | I2C DONE bit | 6,000 iterations | `ERR_I2C_TIMEOUT` |
|
||||
| `i2c_wait_stop()` | I2C STOP bit | 6,000 iterations | `ERR_I2C_TIMEOUT` |
|
||||
| `ep0_wait_data()` | EP0 BUSY bit | 6,000 iterations | `ERR_EP0_TIMEOUT` |
|
||||
| `gpif_start()` | GPIF idle | 60,000 iterations | `ERR_GPIF_TIMEOUT` |
|
||||
| `gpif_stop()` | GPIF idle | 60,000 iterations | `ERR_GPIF_TIMEOUT` |
|
||||
| EP2 FIFO full | `do_param_sweep()` | 60,000 iterations | `ERR_EP2_TIMEOUT` |
|
||||
| EP2 FIFO full | `do_spectrum_sweep()` | 60,000 iterations | `ERR_EP2_TIMEOUT` |
|
||||
| Timer2 overflow | `diseqc_wait_ticks()` | 6,000 per tick | `ERR_DISEQC_TIMER` |
|
||||
| I2C bus scan | `0xB4` handler | via `i2c_wait_done/stop` | `ERR_I2C_TIMEOUT` |
|
||||
|
||||
The `ep0_wait_data()` helper replaces 7 separate bare `while (EP0CS & bmEPBUSY)` loops in vendor command handlers. After the wait, each handler validates `EP0BCL` against the expected payload size.
|
||||
|
||||
## Error Codes
|
||||
|
||||
All error codes are set in the `last_error` variable and readable via `GET_LAST_ERROR` (0xBC). The error is sticky — it persists until overwritten by a new error.
|
||||
|
||||
| Code | Name | Meaning |
|
||||
|------|------|---------|
|
||||
| `0x00` | `ERR_OK` | No error |
|
||||
| `0x01` | `ERR_I2C_TIMEOUT` | I2C DONE or STOP bit timeout |
|
||||
| `0x02` | `ERR_I2C_NAK` | I2C slave did not ACK |
|
||||
| `0x03` | `ERR_BCM_TIMEOUT` | BCM4500 poll-ready timeout |
|
||||
| `0x04` | `ERR_BCM_NOT_READY` | BCM4500 not booted (tune attempted before boot) |
|
||||
| `0x05` | `ERR_BCM_VERIFY` | BCM4500 register verify mismatch |
|
||||
| `0x06` | `ERR_TUNE_FAIL` | Tune operation failed |
|
||||
| `0x07` | `ERR_EP0_TIMEOUT` | EP0 data phase timeout or short payload |
|
||||
| `0x08` | `ERR_GPIF_TIMEOUT` | GPIF did not reach idle state |
|
||||
| `0x09` | `ERR_EP2_TIMEOUT` | EP2 FIFO full timeout during sweep |
|
||||
| `0x0A` | `ERR_NOT_SUPPORTED` | Operation not supported (e.g., tone burst B) |
|
||||
| `0x0B` | `ERR_DISEQC_LEN` | DiSEqC message length invalid |
|
||||
| `0x0C` | `ERR_DISEQC_TIMER` | Timer2 overflow timeout during DiSEqC |
|
||||
| `0x0D` | `ERR_WDT_FIRED` | Watchdog fired — LNB power was cut |
|
||||
|
||||
## I2C Return-Value Checks
|
||||
|
||||
All I2C write operations in sweep and scan functions now check return values. Failed writes skip to the next iteration instead of proceeding with stale data:
|
||||
|
||||
- `do_param_sweep()` — `bcm_indirect_write_block()` and `bcm_direct_write()` calls
|
||||
- `do_spectrum_sweep()` — `bcm_indirect_write_block()` calls
|
||||
- `do_blind_scan()` — `bcm_indirect_write_block()` and `bcm_direct_write()` calls
|
||||
- `do_adaptive_blind_scan()` — same pattern
|
||||
|
||||
## do_tune() Safety
|
||||
|
||||
The `do_tune()` function was the most dangerous code path — it performed a 12-byte I2C write using the fx2lib `i2c_write()` function, which has no timeout protection. In v3.05.0:
|
||||
|
||||
- The fx2lib `i2c_write()` call is replaced with `i2c_write_multi_timeout()`, which applies the standard I2C timeout to every byte
|
||||
- Both `bcm_poll_ready()` calls check their return values
|
||||
- Both `bcm_direct_write()` calls check their return values
|
||||
- An early guard rejects the tune if the BCM4500 is not booted (`ERR_BCM_NOT_READY`)
|
||||
|
||||
## DiSEqC Safety
|
||||
|
||||
- **Tone burst B** is explicitly rejected with `ERR_NOT_SUPPORTED` — the hardware only supports tone burst A (unmodulated carrier)
|
||||
- **Invalid message lengths** (<3 or >6 bytes) set `ERR_DISEQC_LEN`
|
||||
- **Timer2 waits** in `diseqc_wait_ticks()` have per-tick timeout protection — a stuck Timer2 no longer hangs the firmware
|
||||
- `diseqc_tone_burst()` was refactored to use `diseqc_wait_ticks()` instead of three separate bare `while (!TF2)` loops
|
||||
|
||||
## Other Fixes
|
||||
|
||||
| Fix | Description |
|
||||
|-----|-------------|
|
||||
| `hp_changes` saturation | Counter capped at 0xFFFF to prevent u16 wrap-around |
|
||||
| `stream_diag_poll()` | Only updates `sd_last_status` / `sd_last_lock` on successful I2C reads |
|
||||
| `BCM_LOCK_BIT` constant | Replaces 4 hardcoded `0x20` literals with named constant |
|
||||
| Hotplug data integrity | `hp_prev` bitmap is only updated after a successful scan completes |
|
||||
| EP0BUF zero-fill | Commands 0x87 and 0xB7 zero-fill EP0BUF before indirect reads, ensuring I2C failures return zeros instead of stale buffer contents |
|
||||
|
||||
## Memory Budget
|
||||
|
||||
```
|
||||
Code: 13,079 / 15,360 bytes (85%) +948 bytes from v3.04
|
||||
XRAM: 218 / 512 bytes (43%) +2 bytes (wdt_counter, wdt_armed)
|
||||
Stack: 132 bytes available unchanged
|
||||
```
|
||||
|
||||
The safety additions cost 948 bytes of code space — well within the 2,281-byte margin. The 8051 stack starts at `0x7C` with 132 bytes, which is sufficient for the deepest call chain (vendor command → sweep → I2C helpers → Timer0 ISR).
|
||||
|
||||
## Hardware Validation
|
||||
|
||||
Firmware v3.05.0 was loaded onto physical hardware and tested:
|
||||
|
||||
| Test | Result |
|
||||
|------|--------|
|
||||
| Firmware load + re-enumeration | Pass |
|
||||
| Version readback (0x92) | `v3.05.0 (2026-02-16)` |
|
||||
| BCM4500 boot (0x89) | `STARTED \| FW_LOADED`, stage `COMPLETE` |
|
||||
| I2C bus scan (0xB4) | Found BCM4500 (0x08), tuner (0x10), EEPROM (0x51) |
|
||||
| Spectrum sweep (950–2150 MHz) | 121 points, no hang |
|
||||
| LNB power control (13V/18V/22kHz) | All config bits track correctly |
|
||||
| DiSEqC 1.0 switch | No error |
|
||||
| DiSEqC tone burst A | No error |
|
||||
| DiSEqC 1.2 motor commands | No error |
|
||||
| Tune attempt (no signal) | No hang, `last_error` stays OK |
|
||||
| Signal monitor (no signal) | 0.0 dB SNR, no lock |
|
||||
| Adversarial test suite | 55/55 passed |
|
||||
| Watchdog false-fire | None observed |
|
||||
|
||||
See [Safety Testing](/tools/safety-testing/) for the full adversarial test methodology and results.
|
||||
|
||||
## What Changed from v3.04
|
||||
|
||||
| Feature | v3.04 | v3.05 |
|
||||
|---------|-------|-------|
|
||||
| Software watchdog | None | Timer0-based, 2-second window |
|
||||
| I2C timeout paths | Helpers only | All paths including bus scan, DiSEqC |
|
||||
| EP0 BUSY protection | None (bare spin) | `ep0_wait_data()` with timeout |
|
||||
| GPIF idle protection | None (bare spin) | Timeout with `ERR_GPIF_TIMEOUT` |
|
||||
| EP2 full protection | None (bare spin) | Timeout with `ERR_EP2_TIMEOUT` |
|
||||
| `do_tune()` I2C | fx2lib `i2c_write()` (no timeout) | `i2c_write_multi_timeout()` |
|
||||
| Error codes | 6 (`0x00`–`0x05`) | 14 (`0x00`–`0x0D`) |
|
||||
| Payload validation | None | EP0BCL checked against expected size |
|
||||
| DiSEqC Timer2 | Bare `while (!TF2)` | Timeout-protected `diseqc_wait_ticks()` |
|
||||
| Sweep I2C checks | Unchecked writes | Returns checked, `continue` on fail |
|
||||
| Adversarial testing | None | 55-test Hamilton suite |
|
||||
|
|
@ -5,12 +5,13 @@ description: Side-by-side comparison of all known SkyWalker-1 firmware revisions
|
|||
|
||||
import { Tabs, TabItem, Badge, Aside } from '@astrojs/starlight/components';
|
||||
|
||||
Five firmware versions have been analyzed through Ghidra reverse engineering and source code review. This page compares their architecture, features, and binary characteristics.
|
||||
Six firmware versions have been analyzed through Ghidra reverse engineering and source code review. This page compares their architecture, features, and binary characteristics.
|
||||
|
||||
## Version Summary
|
||||
|
||||
| Firmware | Version ID | Build Date | Target PID | Functions | Binary Size | Stack Pointer |
|
||||
|----------|-----------|------------|------------|-----------|-------------|---------------|
|
||||
| Custom v3.05.0 | `0x030500` | 2026-02-16 | `0x0203` | N/A | 13,079 bytes (RAM) | `0x7B` |
|
||||
| Custom v3.01.0 | `0x030100` | 2026-02-12 | `0x0203` | N/A | ~3 KB (RAM) | N/A |
|
||||
| v2.13.01 (FW1) | `0x020D01` | 2010-03-12 | `0x0203` | 82-88 | 9,322 bytes | `0x50` |
|
||||
| v2.13.02 (FW2) | `0x020D01` | 2010-03-12 | `0x0203` | 83 | 9,377 bytes | `0x50` |
|
||||
|
|
@ -19,12 +20,42 @@ Five firmware versions have been analyzed through Ghidra reverse engineering and
|
|||
| v2.06.04 | `0x020604` | 2007-07-13 | `0x0203` | 61 | 9,472 bytes | `0x72` |
|
||||
|
||||
<Aside type="note">
|
||||
Rev.2 v2.10 targets PID `0x0202` (a different product line). All other versions target `0x0203` (SkyWalker-1). The custom v3.01.0 is compiled with SDCC + fx2lib and loaded into FX2 RAM, not flashed to EEPROM.
|
||||
Rev.2 v2.10 targets PID `0x0202` (a different product line). All other versions target `0x0203` (SkyWalker-1). The custom firmware versions are compiled with SDCC + fx2lib and loaded into FX2 RAM, not flashed to EEPROM. Custom v3.05.0 is the safety-hardened release with software watchdog and comprehensive timeout protection.
|
||||
</Aside>
|
||||
|
||||
## Version-by-Version Details
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Custom v3.05">
|
||||
|
||||
### Custom v3.05.0 <Badge text="Custom" variant="success" /> <Badge text="Safety" variant="caution" />
|
||||
|
||||
Safety-hardened firmware with software watchdog and timeout protection on all code paths. Built on the v3.04 codebase with all features from v3.01 through v3.04.
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Version ID | `0x030500` |
|
||||
| Build date | 2026-02-16 |
|
||||
| Toolchain | SDCC + fx2lib |
|
||||
| Source | `firmware/skywalker1.c` (2,256 lines) |
|
||||
| Code size | 13,079 / 15,360 bytes (85%) |
|
||||
| XRAM | 218 / 512 bytes (43%) |
|
||||
| Stack | 132 bytes available |
|
||||
| Error codes | 14 (`0x00`--`0x0D`) |
|
||||
|
||||
**Additions over v3.04:**
|
||||
- Timer0 software watchdog (2-second window, cuts LNB power on stall)
|
||||
- Timeout protection on all EP0, GPIF, EP2, and DiSEqC Timer2 spin loops
|
||||
- `ep0_wait_data()` helper replacing 7 bare `while (EP0CS & bmEPBUSY)` loops
|
||||
- EP0 payload length validation on all OUT vendor commands
|
||||
- I2C return-value checks on all write operations in sweep/scan functions
|
||||
- `do_tune()` rewritten with timeout-protected I2C and early guard for unbooted BCM4500
|
||||
- 8 new error codes for observable failure modes
|
||||
- 55-test Hamilton adversarial test suite (`test_hamilton.py`)
|
||||
|
||||
See the [Custom v3.05](/firmware/custom-v305/) page for the full safety review findings and hardware validation results.
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Custom v3.01">
|
||||
|
||||
### Custom v3.01.0 <Badge text="Custom" variant="success" />
|
||||
|
|
@ -147,20 +178,22 @@ The original SkyWalker-1 firmware extracted from the device's onboard EEPROM.
|
|||
|
||||
## Architectural Differences
|
||||
|
||||
| Feature | Custom v3.01 | v2.13 | Rev.2 v2.10 | v2.06 |
|
||||
|---------|--------------|-------|-------------|-------|
|
||||
| Vendor commands | 30 stock + 7 custom | 30 | 27 | 30 |
|
||||
| INT0 handler | N/A (fx2lib ISR) | Demod polling | USB re-enum | USB re-enum |
|
||||
| Demod probe at boot | Yes (with timeout) | Yes (40 attempts) | No | No |
|
||||
| Retry loops | Yes (with timeout) | Yes (20-attempt) | No | No |
|
||||
| HW revision detect | No | Yes (flag `_1_3`) | Yes (descriptor walker) | No |
|
||||
| DiSEqC data pin | P0.7 | P0.0 | P0.4 | P0.7 |
|
||||
| Config byte IRAM addr | C variable | `0x4F` | `0x4E` | `0x6D` |
|
||||
| BCM4500 status poll | 1 register | 1 register | 3 registers | 3 registers |
|
||||
| I2C timeout | 6000-count | None | None | None |
|
||||
| Anti-tampering | No | Yes | No | No |
|
||||
| New commands | 0xB0--0xB6 | 0x99, 0x9A, 0x9C | 0x99/0x9A proto | -- |
|
||||
| 0x9D behavior | N/A | Conditional demod reset | N/A (out of range) | HW revision mode |
|
||||
| Feature | Custom v3.05 | Custom v3.01 | v2.13 | Rev.2 v2.10 | v2.06 |
|
||||
|---------|--------------|--------------|-------|-------------|-------|
|
||||
| Vendor commands | 30 stock + 18 custom | 30 stock + 7 custom | 30 | 27 | 30 |
|
||||
| INT0 handler | N/A (fx2lib ISR) | N/A (fx2lib ISR) | Demod polling | USB re-enum | USB re-enum |
|
||||
| Demod probe at boot | Yes (with timeout) | Yes (with timeout) | Yes (40 attempts) | No | No |
|
||||
| Retry loops | Yes (with timeout) | Yes (with timeout) | Yes (20-attempt) | No | No |
|
||||
| HW revision detect | No | No | Yes (flag `_1_3`) | Yes (descriptor walker) | No |
|
||||
| DiSEqC data pin | P0.7 | P0.7 | P0.0 | P0.4 | P0.7 |
|
||||
| Config byte IRAM addr | C variable | C variable | `0x4F` | `0x4E` | `0x6D` |
|
||||
| BCM4500 status poll | 1 register | 1 register | 1 register | 3 registers | 3 registers |
|
||||
| I2C timeout | All paths (6000-count) | Helpers only (6000-count) | None | None | None |
|
||||
| Software watchdog | Timer0 (2-second) | None | None | None | None |
|
||||
| EP0/GPIF/EP2 timeout | All protected | None | None | None | None |
|
||||
| Error codes | 14 | 6 | N/A | N/A | N/A |
|
||||
| Anti-tampering | No | No | Yes | No | No |
|
||||
| New commands | 0xB0--0xBE | 0xB0--0xB6 | 0x99, 0x9A, 0x9C | 0x99/0x9A proto | -- |
|
||||
|
||||
## Kernel Version Constants
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue